PluginProbe
Social Media Auto Poster – Schedule & Publish to Buffer / trunk
Social Media Auto Poster – Schedule & Publish to Buffer vtrunk
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 3.8.8 All 124 releases
wp-to-buffer / lib / social / includes / class-admin.php

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

1,176 lines 37.0 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 */
606 $autocomplete_configuration = apply_filters( $this->base->plugin->filter_name . '_admin_get_autocomplete_configuration', $autocomplete_configuration );
607
608 // Return.
609 return $autocomplete_configuration;
610
611 }
612
613 /**
614 * Add the Plugin to the WordPress Administration Menu
615 *
616 * @since 1.0.0
617 */
618 public function admin_menu() {
619
620 // Define the minimum capability required to access settings.
621 $minimum_capability = 'manage_options';
622
623 /**
624 * Defines the minimum capability required to access the Plugin's
625 * Menu and Sub Menus
626 *
627 * @since 4.3.6
628 *
629 * @param string $capability Minimum Required Capability.
630 * @return string Minimum Required Capability
631 */
632 $minimum_capability = apply_filters( $this->base->plugin->filter_name . '_admin_admin_menu_minimum_capability', $minimum_capability );
633
634 /**
635 * Add settings menus and sub menus for the Plugin's settings.
636 *
637 * @since 5.2.4
638 *
639 * @param string $minimum_capability Minimum capability required.
640 */
641 do_action( $this->base->plugin->filter_name . '_admin_admin_menu', $minimum_capability );
642
643 }
644
645 /**
646 * Define links to display below the Plugin Name on the WP_List_Table at in the Plugins screen.
647 *
648 * @since 5.0.2
649 *
650 * @param array $links Links.
651 * @return array Links
652 */
653 public function plugin_action_links_settings_page( $links ) {
654
655 // Add link to Plugin settings screen.
656 $links['settings'] = sprintf(
657 '<a href="%s">%s</a>',
658 add_query_arg(
659 array(
660 'page' => $this->base->plugin->name . '-settings',
661 ),
662 admin_url( 'admin.php' )
663 ),
664 __( 'Settings', 'wp-to-buffer' )
665 );
666
667 // Return.
668 return $links;
669
670 }
671
672 /**
673 * Upgrade Screen
674 *
675 * @since 3.2.5
676 */
677 public function upgrade_screen() {
678 // We never reach here, as we redirect earlier in the process.
679 }
680
681 /**
682 * Outputs the Settings Screen
683 *
684 * @since 3.0.0
685 */
686 public function settings_screen() {
687
688 // Setup notices class.
689 $this->base->get_class( 'notices' )->set_key_prefix( $this->base->plugin->filter_name . '_' . wp_get_current_user()->ID );
690
691 // Maybe disconnect an account.
692 $this->maybe_disconnect_account();
693
694 // Maybe refresh profiles.
695 $this->maybe_refresh_profiles();
696
697 // Maybe save settings.
698 $result = $this->save_settings();
699 if ( is_wp_error( $result ) ) {
700 // Error notice.
701 $this->base->get_class( 'notices' )->add_error_notice( $result->get_error_message() );
702 } elseif ( $result === true ) {
703 // Success notice.
704 $this->base->get_class( 'notices' )->add_success_notice( __( 'Settings saved successfully.', 'wp-to-buffer' ) );
705 }
706
707 // If the Plugin isn't connected an account, show the screen to do this now.
708 if ( ! $this->base->get_class( 'settings' )->account_connected() ) {
709 $this->auth_screen();
710 return;
711 }
712
713 // Get Profiles for accounts.
714 $profiles = $this->get_cached_profiles();
715
716 // Get Settings Tab and Post Type we're managing settings for.
717 $tab = $this->get_tab( $profiles );
718 $post_type = $this->get_post_type_tab();
719 $disable_save_button = false;
720
721 // Post Types.
722 $post_types = $this->base->get_class( 'common' )->get_post_types();
723
724 // Accounts.
725 $accounts = $this->base->get_class( 'settings' )->get_accounts();
726
727 // Depending on the screen we're on, load specific options.
728 switch ( $tab ) {
729 /**
730 * Settings
731 */
732 case 'auth':
733 // Log Settings.
734 $log_levels = $this->base->get_class( 'log' )->get_level_options();
735
736 // Documentation URL.
737 $documentation_url = $this->base->plugin->documentation_url . '/authentication-settings';
738 break;
739
740 /**
741 * No Profiles
742 */
743 case 'profiles-missing':
744 // Disable Save button, as there are no settings displayed to save.
745 $disable_save_button = true;
746
747 // Documentation URL.
748 $documentation_url = $this->base->plugin->documentation_url . '/status-settings';
749 break;
750
751 /**
752 * Profiles Error
753 */
754 case 'profiles-error':
755 // Disable Save button, as there are no settings displayed to save.
756 $disable_save_button = true;
757
758 // Documentation URL.
759 $documentation_url = $this->base->plugin->documentation_url . '/status-settings';
760 break;
761
762 /**
763 * Post Type
764 */
765 default:
766 // Get original statuses that will be stored in a hidden field so they are preserved if the screen is saved
767 // with no changes that trigger an update to the hidden field.
768 $original_statuses = $this->base->get_class( 'settings' )->get_settings( $post_type );
769
770 // Get some other information.
771 $post_type_object = get_post_type_object( $post_type );
772 $actions_plural = $this->base->get_class( 'common' )->get_post_actions_past_tense();
773 $post_actions = $this->base->get_class( 'common' )->get_post_actions();
774 $documentation_url = $this->base->plugin->documentation_url . '/status-settings';
775 $is_post_screen = false; // Disables the 'specific' schedule option, which can only be used on individual Per-Post Settings.
776
777 // Check if this Post Type is enabled.
778 if ( ! $this->base->get_class( 'settings' )->is_post_type_enabled( $post_type ) ) {
779 $this->base->get_class( 'notices' )->add_warning_notice(
780 sprintf(
781 '%1$s <a href="%2$s" target="_blank">%3$s</a>',
782 sprintf(
783 /* translators: %1$s: Post Type, %2$s: Social Media Service Name (Buffer, Hootsuite), %3$s: Documentation URL */
784 __( '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' ),
785 $post_type_object->label,
786 $this->base->plugin->account
787 ),
788 $documentation_url,
789 __( 'See Documentation', 'wp-to-buffer' )
790 )
791 );
792 }
793 break;
794 }
795
796 // Load View.
797 include_once $this->base->plugin->folder . 'lib/social/views/settings.php';
798
799 // Add footer action to output overlay modal markup.
800 add_action( 'admin_footer', array( $this, 'output_modal' ) );
801
802 }
803
804 /**
805 * Outputs the auth screen, allowing the user to begin the process of connecting the Plugin
806 * to the API, without showing other settings.
807 *
808 * @since 4.6.4
809 */
810 public function auth_screen() {
811
812 // Load View.
813 include_once $this->base->plugin->folder . 'lib/social/views/settings-auth-required.php';
814
815 }
816
817 /**
818 * Outputs the hidden Javascript Modal and Overlay in the Footer
819 *
820 * @since 1.0.0
821 */
822 public function output_modal() {
823
824 // Load view.
825 require_once $this->base->plugin->folder . 'lib/shared/views/modal.php';
826
827 }
828
829 /**
830 * Outputs the Log Screen
831 *
832 * @since 3.9.6
833 */
834 public function log_screen() {
835
836 // Init table.
837 $table = new \WPZinc\Social\Log_Table( $this->base );
838 $table->prepare_items();
839
840 // Load View.
841 include_once $this->base->plugin->folder . 'lib/social/views/log.php';
842
843 }
844
845 /**
846 * Helper method to get the setting value from the plugin settings
847 *
848 * @since 3.0.0
849 *
850 * @param string $type Setting Type.
851 * @param string $key Setting Key.
852 * @param mixed $default_value Default Value if Setting does not exist.
853 * @return mixed Value
854 */
855 public function get_setting( $type = '', $key = '', $default_value = '' ) {
856
857 // Post Type Setting or Bulk Setting.
858 if ( post_type_exists( $type ) ) {
859 return $this->base->get_class( 'settings' )->get_setting( $type, $key, $default_value );
860 }
861
862 // Depending on the type, return settings / options.
863 switch ( $type ) {
864 case 'text_to_image':
865 case 'log':
866 case 'hide_meta_box_by_roles':
867 case 'roles':
868 case 'custom_tags':
869 case 'repost':
870 return $this->base->get_class( 'settings' )->get_setting( $type, $key, $default_value );
871
872 default:
873 return $this->base->get_class( 'settings' )->get_option( $key, $default_value );
874 }
875
876 }
877
878 /**
879 * Fetches fresh profiles from the API for the given account, if the
880 * user clicks the refresh profiles link. Bypasses the transient cache
881 * and updates the stored profile IDs on the account.
882 *
883 * @since 6.1.2
884 */
885 private function maybe_refresh_profiles() {
886
887 // Bail if no nonce.
888 if ( ! isset( $_GET['nonce'] ) ) {
889 return;
890 }
891
892 // Bail if nonce is invalid.
893 if ( ! wp_verify_nonce( sanitize_key( $_GET['nonce'] ), $this->base->plugin->name . '-refresh-profiles' ) ) {
894 return;
895 }
896
897 // Bail if account ID is not set.
898 if ( ! isset( $_GET[ $this->base->plugin->name . '-refresh-profiles' ] ) ) {
899 return;
900 }
901
902 // Get account.
903 $account_id = sanitize_text_field( wp_unslash( $_GET[ $this->base->plugin->name . '-refresh-profiles' ] ) );
904 $accounts = $this->base->get_class( 'settings' )->get_accounts();
905 if ( ! isset( $accounts[ $account_id ] ) ) {
906 return;
907 }
908 $account = $accounts[ $account_id ];
909
910 // Configure API for this account.
911 $this->base->get_class( 'api' )->set_tokens( $account['access_token'], $account['refresh_token'], $account['token_expires'] );
912
913 // Fetch fresh profiles from the API.
914 $profiles = $this->base->get_class( 'api' )->profiles( true, $account_id );
915
916 // Display error and bail.
917 if ( is_wp_error( $profiles ) ) {
918 $this->base->get_class( 'notices' )->add_error_notice( $profiles->get_error_message() );
919 return;
920 }
921
922 // If the service supports organizations, refresh the stored account
923 // information (name, email, channel limit, plan) alongside the profiles.
924 $organizations = array();
925 $api = $this->base->get_class( 'api' );
926 if ( method_exists( $api, 'organizations' ) ) {
927 $organizations = $api->organizations( true );
928
929 // Display error and bail.
930 if ( is_wp_error( $organizations ) ) {
931 $this->base->get_class( 'notices' )->add_error_notice( $organizations->get_error_message() );
932 return;
933 }
934 }
935
936 // Update the stored account information (where available) and profile IDs.
937 if ( isset( $organizations[ $account_id ] ) ) {
938 $this->base->get_class( 'settings' )->update_account_information(
939 $account_id,
940 $organizations[ $account_id ]['name'],
941 $organizations[ $account_id ]['email'],
942 $organizations[ $account_id ]['channel_limit'],
943 $organizations[ $account_id ]['plan'],
944 array_keys( $profiles )
945 );
946 } else {
947 $this->base->get_class( 'settings' )->update_account_profile_ids( $account_id, array_keys( $profiles ) );
948 }
949
950 $this->base->get_class( 'notices' )->add_success_notice(
951 __( 'Profiles refreshed successfully.', 'wp-to-buffer' )
952 );
953
954 }
955
956 /**
957 * Disconnects an account if the user clicks the disconnect link.
958 *
959 * @since 5.4.0
960 */
961 private function maybe_disconnect_account() {
962
963 // Bail if no nonce.
964 if ( ! isset( $_GET['nonce'] ) ) {
965 return;
966 }
967
968 // Bail if nonce is invalid.
969 if ( ! wp_verify_nonce( sanitize_key( $_GET['nonce'] ), $this->base->plugin->name . '-disconnect' ) ) {
970 return;
971 }
972
973 // Bail if account ID is not set.
974 if ( ! isset( $_GET[ $this->base->plugin->name . '-disconnect' ] ) ) {
975 return;
976 }
977
978 // Disconnect account.
979 $this->base->get_class( 'settings' )->delete_account( sanitize_text_field( wp_unslash( $_GET[ $this->base->plugin->name . '-disconnect' ] ) ) );
980 $this->base->get_class( 'notices' )->add_success_notice(
981 sprintf(
982 /* translators: Social Media Service Name (Buffer, Hootsuite) */
983 __( '%s account disconnected successfully.', 'wp-to-buffer' ),
984 $this->base->plugin->account
985 )
986 );
987
988 }
989
990 /**
991 * Helper method to save settings
992 *
993 * @since 3.0.0
994 *
995 * @return mixed \WP_Error | bool
996 */
997 public function save_settings() {
998
999 // Check if a POST request was made.
1000 if ( ! isset( $_POST['submit'] ) ) {
1001 return false;
1002 }
1003
1004 // Missing nonce.
1005 if ( ! isset( $_POST[ $this->base->plugin->name . '_nonce' ] ) ) {
1006 return new \WP_Error(
1007 $this->base->plugin->filter_name . '_admin_save_settings_error',
1008 __( 'Nonce field is missing. Settings NOT saved.', 'wp-to-buffer' )
1009 );
1010 }
1011
1012 // Invalid nonce.
1013 if ( ! wp_verify_nonce( sanitize_key( $_POST[ $this->base->plugin->name . '_nonce' ] ), $this->base->plugin->name ) ) {
1014 return new \WP_Error(
1015 $this->base->plugin->filter_name . '_admin_save_settings_error',
1016 __( 'Invalid nonce specified. Settings NOT saved.', 'wp-to-buffer' )
1017 );
1018 }
1019
1020 // Get URL parameters.
1021 $tab = $this->get_tab();
1022 $post_type = $this->get_post_type_tab();
1023
1024 switch ( $tab ) {
1025 /**
1026 * Authentication
1027 */
1028 case 'auth':
1029 // oAuth settings are now handled by this class' oauth() function.
1030 // Save other Settings.
1031 $settings = map_deep( $_POST, 'sanitize_text_field' );
1032
1033 // General Settings.
1034 $this->base->get_class( 'settings' )->update_option( 'test_mode', ( isset( $settings['test_mode'] ) ? 1 : 0 ) );
1035 $this->base->get_class( 'settings' )->update_option( 'force_trailing_forwardslash', ( isset( $settings['force_trailing_forwardslash'] ) ? 1 : 0 ) );
1036 $this->base->get_class( 'settings' )->update_option( 'proxy', ( isset( $settings['proxy'] ) ? 1 : 0 ) );
1037
1038 // Log Settings.
1039 // Always force errors.
1040 $log = isset( $settings['log'] ) ? $settings['log'] : array();
1041 if ( ! isset( $log['log_level'] ) ) {
1042 $log['log_level'] = array(
1043 'error',
1044 );
1045 } else {
1046 // 'Error' is disabled on the form and not sent if another option is chosen.
1047 // We always want errors to be logged so add it to the log levels now.
1048 $log['log_level'][] = 'error';
1049 }
1050 $this->base->get_class( 'settings' )->update_option( 'log', $log );
1051
1052 // Reschedule CRON events.
1053 $this->base->get_class( 'cron' )->reschedule_log_cleanup_event();
1054 $this->base->get_class( 'cron' )->reschedule_media_cleanup_event();
1055
1056 // Done.
1057 return true;
1058
1059 /**
1060 * Post Type
1061 */
1062 default:
1063 if ( ! isset( $_POST[ $this->base->plugin->name ]['statuses'] ) ) {
1064 return new \WP_Error(
1065 $this->base->plugin->filter_name . '_admin_save_settings_error',
1066 __( 'Statuses field is missing. Settings NOT saved.', 'wp-to-buffer' )
1067 );
1068 }
1069
1070 // Unslash and decode JSON field.
1071 $settings = json_decode( wp_unslash( $_POST[ $this->base->plugin->name ]['statuses'] ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1072
1073 // Save Settings for this Post Type.
1074 return $this->base->get_class( 'settings' )->update_settings( $post_type, $settings );
1075 }
1076
1077 }
1078
1079 /**
1080 * Returns the profiles for all accounts from the cache.
1081 * Queries the API if the cache is empty.
1082 *
1083 * @since 6.0.5
1084 *
1085 * @return array
1086 */
1087 private function get_cached_profiles() {
1088
1089 $profiles = array();
1090
1091 foreach ( $this->base->get_class( 'settings' )->get_accounts() as $account_id => $account ) {
1092 // Configure API for this account.
1093 $this->base->get_class( 'api' )->set_tokens( $account['access_token'], $account['refresh_token'], $account['token_expires'] );
1094
1095 // Get account profiles.
1096 $account_profiles = $this->base->get_class( 'api' )->profiles( false, $account_id );
1097
1098 // Display an error.
1099 if ( is_wp_error( $account_profiles ) ) {
1100 $this->base->get_class( 'notices' )->add_error_notice( $account_profiles->get_error_message() );
1101 continue;
1102 }
1103
1104 // Merge profiles with existing profiles from other accounts.
1105 // array_merge() is not used here as it will re-index numeric keys.
1106 foreach ( $account_profiles as $profile ) {
1107 $profiles[ $profile['id'] ] = $profile;
1108 }
1109 }
1110
1111 return $profiles;
1112
1113 }
1114
1115 /**
1116 * Returns the settings tab that the user has selected.
1117 *
1118 * @since 3.7.2
1119 *
1120 * @param mixed $profiles API Profiles (false|\WP_Error|array).
1121 * @return string Tab
1122 */
1123 private function get_tab( $profiles = false ) {
1124
1125 // If no tab, default to auth.
1126 if ( ! filter_has_var( INPUT_GET, 'tab' ) ) {
1127 return 'auth';
1128 }
1129
1130 // Get current tab.
1131 $tab = filter_input( INPUT_GET, 'tab', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
1132
1133 // If Profiles are an error, show error.
1134 if ( is_wp_error( $profiles ) ) {
1135 return 'profiles-error';
1136 }
1137
1138 // If no Profiles exist, show error.
1139 if ( is_array( $profiles ) && ! count( $profiles ) ) {
1140 return 'profiles-missing';
1141 }
1142
1143 // Return tab.
1144 return $tab;
1145
1146 }
1147
1148 /**
1149 * Returns the Post Type tab that the user has selected.
1150 *
1151 * @since 3.7.2
1152 *
1153 * @return string Tab
1154 */
1155 private function get_post_type_tab() {
1156
1157 // If no type, default to empty string.
1158 if ( ! filter_has_var( INPUT_GET, 'type' ) ) {
1159 return '';
1160 }
1161
1162 // Get supported post types.
1163 $post_types = array_keys( $this->base->get_class( 'common' )->get_post_types() );
1164 $post_type = filter_input( INPUT_GET, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
1165
1166 // If the post type is not supported, return empty string.
1167 if ( ! in_array( $post_type, $post_types, true ) ) {
1168 return '';
1169 }
1170
1171 return $post_type;
1172
1173 }
1174
1175 }
1176