<?php
/**
* Pull UI functionality
*
* @package distributor
*/
namespace Distributor\PullUI;
//phpcs:ignoreFile WordPressVIPMinimum.Functions.RestrictedFunctions.cookies_setcookie -- Admin file, no full page caching.
use Distributor\EnqueueScript;
/**
* Setup actions and filters
*
* @since 0.8
*/
function setup() {
add_action(
'plugins_loaded',
function() {
add_action( 'admin_menu', __NAMESPACE__ . '\action_admin_menu' );
add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\admin_enqueue_scripts' );
add_action( 'load-distributor_page_pull', __NAMESPACE__ . '\setup_list_table' );
add_filter( 'set-screen-option', __NAMESPACE__ . '\set_screen_option', 10, 3 );
}
);
}
/**
* Create list table
*
* @since 0.8
*/
function setup_list_table() {
global $connection_list_table;
global $connection_now;
global $dt_pull_messages;
if ( ! empty( $_COOKIE['dt-skipped'] ) ) {
$dt_pull_messages['skipped'] = 1;
setcookie( 'dt-skipped', 1, time() - 60, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, is_ssl() );
} elseif ( ! empty( $_COOKIE['dt-unskipped'] ) ) {
$dt_pull_messages['unskipped'] = 1;
setcookie( 'dt-unskipped', 1, time() - 60, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, is_ssl() );
} elseif ( ! empty( $_COOKIE['dt-syndicated'] ) ) {
$dt_pull_messages['syndicated'] = 1;
setcookie( 'dt-syndicated', 1, time() - 60, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, is_ssl() );
} elseif ( ! empty( $_COOKIE['dt-duplicated'] ) ) {
$dt_pull_messages['duplicated'] = 1;
setcookie( 'dt-duplicated', 1, time() - 60, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, is_ssl() );
}
$external_connections = new \WP_Query(
array(
'post_type' => 'dt_ext_connection',
'fields' => 'ids',
'no_found_rows' => true,
'posts_per_page' => 100,
)
);
$connection_list_table = new \Distributor\PullListTable();
if ( ! empty( \Distributor\Connections::factory()->get_registered()['networkblog'] ) ) {
$sites = \Distributor\InternalConnections\NetworkSiteConnection::get_available_authorized_sites( 'pull' );
foreach ( $sites as $site_array ) {
$internal_connection = new \Distributor\InternalConnections\NetworkSiteConnection( $site_array['site'] );
$connection_list_table->connection_objects[] = $internal_connection;
if ( ! empty( $_GET['connection_id'] ) && ! empty( $_GET['connection_type'] ) && 'internal' === $_GET['connection_type'] && (int) $internal_connection->site->blog_id === (int) $_GET['connection_id'] ) { // @codingStandardsIgnoreLine Content is type casted, no need for nonce.
$connection_now = $internal_connection;
}
}
}
foreach ( $external_connections->posts as $external_connection_id ) {
$external_connection_type = get_post_meta( $external_connection_id, 'dt_external_connection_type', true );
if ( empty( \Distributor\Connections::factory()->get_registered()[ $external_connection_type ] ) ) {
continue;
}
$external_connection_status = get_post_meta( $external_connection_id, 'dt_external_connections', true );
if ( empty( $external_connection_status ) || empty( $external_connection_status['can_get'] ) ) {
continue;
}
$external_connection = \Distributor\ExternalConnection::instantiate( $external_connection_id );
if ( ! is_wp_error( $external_connection ) ) {
$connection_list_table->connection_objects[] = $external_connection;
if ( ! empty( $_GET['connection_id'] ) && ! empty( $_GET['connection_type'] ) && 'external' === $_GET['connection_type'] && (int) $external_connection_id === (int) $_GET['connection_id'] ) { // @codingStandardsIgnoreLine Content is type casted, no need for nonce.
$connection_now = $external_connection;
}
}
}
if ( empty( $connection_now ) && ! empty( $connection_list_table->connection_objects ) ) {
$connection_now = $connection_list_table->connection_objects[0];
}
process_actions();
}
/**
* Enqueue admin scripts for pull
*
* @param string $hook WP hook.
* @since 0.8
*/
function admin_enqueue_scripts( $hook ) {
if ( 'distributor_page_pull' !== $hook || empty( $_GET['page'] ) || 'pull' !== $_GET['page'] ) { // @codingStandardsIgnoreLine Comparing values, not using them.
return;
}
$admin_pull_script = new EnqueueScript( 'admin-pull', 'admin-pull.min' );
$admin_pull_script->load_in_footer()
->register_translations()
->enqueue();
wp_enqueue_style(
'dt-admin-pull',
plugins_url( '/dist/css/admin-pull-table.min.css', __DIR__ ),
array(),
$admin_pull_script->get_version()
);
}
/**
* Set up admin menu
*
* @since 0.8
*/
function action_admin_menu() {
$hook = add_submenu_page(
'distributor',
esc_html__( 'Pull Content', 'distributor' ),
esc_html__( 'Pull Content', 'distributor' ),
/**
* Filter Distributor capabilities allowed to pull content.
*
* @since 1.0.0
* @hook dt_pull_capabilities
*
* @param {string} 'manage_options' The capability allowed to pull content.
*
* @return {string} The capability allowed to pull content.
*/
apply_filters( 'dt_pull_capabilities', 'manage_options' ),
'pull',
__NAMESPACE__ . '\dashboard'
);
add_action( "load-$hook", __NAMESPACE__ . '\screen_option' );
}
/**
* Set screen option for posts per page
*
* @param string $status Option status.
* @param string $option Option.
* @param mixed $value New value.
* @since 0.8
* @return mixed
*/
function set_screen_option( $status, $option, $value ) {
return 'pull_posts_per_page' === $option ? $value : $status;
}
/**
* Set up screen options
*
* @since 0.8
*/
function screen_option() {
$option = 'per_page';
$args = [
'label' => esc_html__( 'Posts per page', 'distributor' ),
'option' => 'pull_posts_per_page',
'default' => get_option( 'posts_per_page' ),
];
add_screen_option( $option, $args );
}
/**
* Process content changing actions
*
* @since 0.8
*/
function process_actions() {
global $connection_list_table;
global $dt_pull_messages;
switch ( $connection_list_table->current_action() ) {
case 'syndicate':
case 'bulk-syndicate':
if ( empty( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'bulk-distributor_page_pull' ) ) {
exit;
}
// Filter documented above.
if ( ! current_user_can( apply_filters( 'dt_pull_capabilities', 'manage_options' ) ) ) {
wp_die(
'<h1>' . esc_html__( 'Cheatin’ uh?', 'distributor' ) . '</h1>' .
'<p>' . esc_html__( 'Sorry, you are not allowed to add this item.', 'distributor' ) . '</p>',
403
);
}
if ( empty( $_GET['pull_post_type'] ) || empty( $_GET['connection_type'] ) || empty( $_GET['connection_id'] ) || empty( $_GET['post'] ) ) {
break;
}
$posts = array_map( 'intval', (array) wp_unslash( $_GET['post'] ) );
$post_type = sanitize_text_field( $_GET['pull_post_type'] );
$post_status = ! empty( $_GET['dt_as_draft'] ) && 'draft' === $_GET['dt_as_draft'] ? 'draft' : '';
$posts = array_map(
function( $remote_post_id ) use ( $post_type, $post_status ) {
return [
'remote_post_id' => $remote_post_id,
'post_type' => $post_type,
'post_status' => $post_status,
];
},
$posts
);
if ( 'external' === $_GET['connection_type'] ) {
$connection = \Distributor\ExternalConnection::instantiate( intval( $_GET['connection_id'] ) );
$new_posts = $connection->pull( $posts );
$error_key = "external_{$connection->id}";
foreach ( $posts as $key => $post_array ) {
if ( is_wp_error( $new_posts[ $key ] ) ) {
continue;
}
\Distributor\Subscriptions\create_remote_subscription( $connection, $post_array['remote_post_id'], $new_posts[ $key ] );
}
} else {
$site = get_site( intval( $_GET['connection_id'] ) );
$connection = new \Distributor\InternalConnections\NetworkSiteConnection( $site );
$new_posts = $connection->pull( $posts );
$error_key = "internal_{$connection->site->blog_id}";
}
$post_id_mappings = array();
$pull_errors = array();
foreach ( $posts as $key => $post_array ) {
if ( is_wp_error( $new_posts[ $key ] ) ) {
$pull_errors[ $post_array['remote_post_id'] ] = [ $new_posts[ $key ]->get_error_message() ];
continue;
}
$post_id_mappings[ $post_array['remote_post_id'] ] = $new_posts[ $key ];
$media_errors = get_transient( 'dt_media_errors_' . $new_posts[ $key ] );
if ( ! empty( $media_errors ) ) {
delete_transient( 'dt_media_errors_' . $new_posts[ $key ] );
$pull_errors[ $post_array['remote_post_id'] ] = $media_errors;
}
}
if ( ! empty( $pull_errors ) ) {
set_transient( 'dt_connection_pull_errors_' . $error_key, $pull_errors, DAY_IN_SECONDS );
}
$connection->log_sync( $post_id_mappings );
if ( empty( $dt_pull_messages['duplicated'] ) ) {
setcookie( 'dt-syndicated', 1, time() + DAY_IN_SECONDS, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, is_ssl() );
}
if ( ! empty( $dt_pull_messages['duplicated'] ) ) {
setcookie( 'dt-duplicated', 1, time() + DAY_IN_SECONDS, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, is_ssl() );
}
// Redirect to the pulled content tab
wp_safe_redirect( add_query_arg( 'status', 'pulled', wp_get_referer() ) );
exit;
case 'bulk-skip':
case 'skip':
if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'dt_skip' ) && ! wp_verify_nonce( $_GET['_wpnonce'], 'bulk-distributor_page_pull' ) ) {
exit;
}
// Filter documented above.
if ( ! current_user_can( apply_filters( 'dt_pull_capabilities', 'manage_options' ) ) ) {
wp_die(
'<h1>' . esc_html__( 'Cheatin’ uh?', 'distributor' ) . '</h1>' .
'<p>' . esc_html__( 'Sorry, you are not allowed to add this item.', 'distributor' ) . '</p>',
403
);
}
if ( empty( $_GET['connection_type'] ) || empty( $_GET['connection_id'] ) || empty( $_GET['post'] ) ) {
break;
}
if ( 'external' === $_GET['connection_type'] ) {
$connection = \Distributor\ExternalConnection::instantiate( intval( $_GET['connection_id'] ) );
} else {
$site = get_site( intval( $_GET['connection_id'] ) );
$connection = new \Distributor\InternalConnections\NetworkSiteConnection( $site );
}
$posts = array_map( 'intval', (array) wp_unslash( $_GET['post'] ) );
$post_mapping = array();
foreach ( $posts as $post_id ) {
$post_mapping[ $post_id ] = false;
}
$connection->log_sync( $post_mapping );
setcookie( 'dt-skipped', 1, time() + DAY_IN_SECONDS, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, is_ssl() );
// Redirect to the skipped content tab
wp_safe_redirect( add_query_arg( 'status', 'skipped', wp_get_referer() ) );
exit;
case 'bulk-unskip':
case 'unskip':
if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'dt_unskip' ) && ! wp_verify_nonce( $_GET['_wpnonce'], 'bulk-distributor_page_pull' ) ) {
exit;
}
// Filter documented above.
if ( ! current_user_can( apply_filters( 'dt_pull_capabilities', 'manage_options' ) ) ) {
wp_die(
'<h1>' . esc_html__( 'Cheatin’ uh?', 'distributor' ) . '</h1>' .
'<p>' . esc_html__( 'Sorry, you are not allowed to add this item.', 'distributor' ) . '</p>',
403
);
}
if ( empty( $_GET['connection_type'] ) || empty( $_GET['connection_id'] ) || empty( $_GET['post'] ) ) {
break;
}
if ( 'external' === $_GET['connection_type'] ) {
$connection = \Distributor\ExternalConnection::instantiate( intval( $_GET['connection_id'] ) );
} else {
$site = get_site( intval( $_GET['connection_id'] ) );
$connection = new \Distributor\InternalConnections\NetworkSiteConnection( $site );
}
$posts = array_map( 'intval', (array) wp_unslash( $_GET['post'] ) );
$sync_log = $connection->get_sync_log( intval( $_GET['connection_id'] ) );
foreach ( $posts as $post_id ) {
if ( array_key_exists( $post_id, $sync_log ) ) {
unset( $sync_log[ $post_id ] );
}
}
$connection->log_sync( $sync_log, intval( $_GET['connection_id'] ), true );
setcookie( 'dt-unskipped', 1, time() + DAY_IN_SECONDS, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, is_ssl() );
// Redirect to the new content tab
wp_safe_redirect( add_query_arg( 'status', 'new', wp_get_referer() ) );
exit;
}
}
/**
* Output pull dashboard with custom list table
*
* @since 0.8
*/
function dashboard() {
global $connection_list_table;
global $connection_now;
global $dt_pull_messages;
if ( ! empty( $connection_now ) ) {
if ( is_a( $connection_now, '\Distributor\ExternalConnection' ) ) {
$connection_type = 'external';
$connection_id = $connection_now->id;
} else {
$connection_type = 'internal';
$connection_id = $connection_now->site->blog_id;
}
}
$internal_connection_group = array();
$external_connection_group = array();
foreach ( $connection_list_table->connection_objects as $connection ) {
if ( is_a( $connection, '\Distributor\ExternalConnection' ) ) {
$external_connection_group[] = $connection;
} else {
$internal_connection_group[] = $connection;
}
}
?>
<div class="wrap nosubsub">
<h1>
<?php
if ( empty( $connection_list_table->connection_objects ) ) :
$connection_now = 0;
?>
<?php
echo wp_kses_post(
sprintf(
/* translators: %s: link to add new connection */
__( 'No connections to pull from, <a href="%s">create one?</a>', 'distributor' ),
esc_url( admin_url( 'post-new.php?post_type=dt_ext_connection' ) )
)
);
?>
<?php else : ?>
<?php esc_html_e( 'Pull Content from', 'distributor' ); ?>
<select id="pull_connections" name="connection" method="get">
<?php if ( ! empty( $internal_connection_group ) ) : ?>
<?php if ( ! empty( $external_connection_group ) ) : ?>
<optgroup label="<?php esc_attr_e( 'Network Connections', 'distributor' ); ?>">
<?php endif; ?>
<?php
foreach ( $internal_connection_group as $connection ) :
$selected = false;
$type = 'internal';
$name = untrailingslashit( $connection->site->domain . $connection->site->path );
$id = $connection->site->blog_id;
if ( is_a( $connection_now, '\Distributor\InternalConnections\NetworkSiteConnection' ) && (int) $connection_now->site->blog_id === (int) $id ) {
$selected = true;
}
?>
<option <?php selected( true, $selected ); ?> data-pull-url="<?php echo esc_url( admin_url( 'admin.php?page=pull&connection_type=' . $type . '&connection_id=' . $id ) ); ?>"><?php echo esc_html( $name ); ?></option>
<?php endforeach; ?>
<?php if ( ! empty( $external_connection_group ) ) : ?>
</optgroup>
<?php endif; ?>
<?php endif; ?>
<?php if ( ! empty( $external_connection_group ) ) : ?>
<?php if ( ! empty( $internal_connection_group ) ) : ?>
<optgroup label="<?php esc_attr_e( 'External Connections', 'distributor' ); ?>">
<?php endif; ?>
<?php
foreach ( $external_connection_group as $connection ) :
$type = 'external';
$selected = false;
$name = $connection->name;
$id = $connection->id;
if ( is_a( $connection_now, '\Distributor\ExternalConnection' ) && (int) $connection_now->id === (int) $id ) {
$selected = true;
}
?>
<option <?php selected( true, $selected ); ?> data-pull-url="<?php echo esc_url( admin_url( 'admin.php?page=pull&connection_type=' . $type . '&connection_id=' . $id ) ); ?>"><?php echo esc_html( $name ); ?></option>
<?php endforeach; ?>
<?php if ( ! empty( $internal_connection_group ) ) : ?>
</optgroup>
<?php endif; ?>
<?php endif; ?>
</select>
<?php
$connection_now->pull_post_type = '';
$connection_now->pull_post_types = \Distributor\Utils\available_pull_post_types( $connection_now, $connection_type );
// Ensure we have at least one post type to pull.
$connection_now->pull_post_type = '';
if ( ! empty( $connection_now->pull_post_types ) ) {
$connection_now->pull_post_type = 'all';
}
// Set the post type we want to pull (if any)
// This is either from a query param, "post" post type, or the first in the list
foreach ( $connection_now->pull_post_types as $post_type ) {
if ( ! empty( $_GET['pull_post_type'] ) ) {
if ( 'all' === $_GET['pull_post_type'] ) {
$connection_now->pull_post_type = 'all';
break;
} elseif ( $_GET['pull_post_type'] === $post_type['slug'] ) {
$connection_now->pull_post_type = $post_type['slug'];
break;
}
} else {
$connection_now->pull_post_type = ! empty( $post_type['slug'] ) ? $post_type['slug'] : 'all';
break;
}
}
?>
<?php endif; ?>
</h1>
<?php if ( ! empty( $dt_pull_messages ) && ! empty( $dt_pull_messages['skipped'] ) ) : ?>
<div id="message" class="updated notice is-dismissible">
<p><?php esc_html_e( 'Post(s) have been marked as skipped.', 'distributor' ); ?></p>
</div>
<?php endif; ?>
<?php if ( ! empty( $dt_pull_messages ) && ! empty( $dt_pull_messages['unskipped'] ) ) : ?>
<div id="message" class="updated notice is-dismissible">
<p><?php esc_html_e( 'Post(s) have been unskipped.', 'distributor' ); ?></p>
</div>
<?php endif; ?>
<?php if ( ! empty( $dt_pull_messages ) && ! empty( $dt_pull_messages['syndicated'] ) ) : ?>
<div id="message" class="updated notice is-dismissible">
<p><?php esc_html_e( 'Post(s) have been pulled.', 'distributor' ); ?></p>
</div>
<?php endif; ?>
<?php if ( ! empty( $dt_pull_messages ) && ! empty( $dt_pull_messages['duplicated'] ) ) : ?>
<div id="message" class="notice notice-warning is-dismissible">
<p><?php esc_html_e( 'Post(s) have been already distributed.', 'distributor' ); ?></p>
</div>
<?php endif; ?>
<?php output_pull_errors(); ?>
<?php $connection_list_table->prepare_items(); ?>
<?php if ( ! empty( $connection_list_table->pull_error ) ) : ?>
<div class="notice notice-error">
<p><?php esc_html_e( 'Could not pull content from connection due to error.', 'distributor' ); ?></p>
<ul>
<?php foreach ( $connection_list_table->pull_error as $error ) : ?>
<li>
<ul>
<li><?php echo esc_html( $error ); ?></li>
</ul>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php else : ?>
<?php $connection_list_table->views(); ?>
<?php $connection_list_table->search_box( esc_html__( 'Search', 'distributor' ), 'post' ); ?>
<form id="posts-filter" class="status-<?php echo ( ! empty( $_GET['status'] ) ) ? esc_attr( $_GET['status'] ) : 'new'; // @codingStandardsIgnoreLine Nonce not needed. ?>" method="get">
<?php if ( ! empty( $connection_list_table->connection_objects ) ) : ?>
<input type="hidden" name="connection_type" value="<?php echo esc_attr( $connection_type ); ?>">
<input type="hidden" name="connection_id" value="<?php echo esc_attr( $connection_id ); ?>">
<?php endif; ?>
<input type="hidden" name="page" value="pull">
<?php $connection_list_table->display(); ?>
</form>
<?php endif; ?>
</div>
<?php
}
/**
* Get pull errors saved in transient and display it to user.
*
* @todo Log persistent
*/
function output_pull_errors() {
global $connection_now;
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No nonce needed here.
if ( empty( $_GET['connection_id'] ) ) {
return;
}
if ( is_a( $connection_now, '\Distributor\ExternalConnection' ) ) {
$error_key = "external_{$connection_now->id}";
} elseif ( is_a( $connection_now, '\Distributor\InternalConnections\NetworkSiteConnection' ) ) {
$error_key = "internal_{$connection_now->site->blog_id}";
} else {
return;
}
$pull_errors = get_transient( 'dt_connection_pull_errors_' . $error_key );
if ( empty( $pull_errors ) ) {
return;
}
delete_transient( 'dt_connection_pull_errors_' . $error_key );
$post_ids = array_keys( $pull_errors );
$_posts = $connection_now->remote_get( [ 'post__in' => $post_ids ] );
$posts = [];
if ( empty( $_posts ) ) {
return;
}
foreach ( $_posts['items'] as $post ) {
$posts[ $post->ID ] = $post->post_title;
}
?>
<div id="pull-errors" class="notice notice-warning is-dismissible">
<p><?php esc_html_e( 'Some errors occurred while pulling selected posts. Please review the list of the errors bellow for more details.', 'distributor' ); ?></p>
<ul>
<?php foreach ( $pull_errors as $id => $errors ) : ?>
<li>
<strong><?php echo esc_html( $posts[ $id ] ); ?>:</strong>
<ul>
<?php foreach ( $errors as $error ) : ?>
<li><?php echo esc_html( $error ); ?></li>
<?php endforeach; ?>
</ul>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php
}