| @@ -1,0 +1,226 @@ | ||
| 1 | +<?php | |
| 2 | + | |
| 3 | +declare(strict_types=1); | |
| 4 | + | |
| 5 | +namespace Metricool\Controllers; | |
| 6 | + | |
| 7 | +if (!defined('ABSPATH')) { | |
| 8 | + exit; | |
| 9 | +} | |
| 10 | + | |
| 11 | +use Metricool\Support\Helpers\Event; | |
| 12 | +use Metricool\Traits\HasViews; | |
| 13 | +use Metricool\Traits\HasNonces; | |
| 14 | +use Metricool\Support\Helpers\MetricoolUrl; | |
| 15 | +use Metricool\Traits\HasAllowlistControl; | |
| 16 | +use Metricool\Interfaces\ControllerInterface; | |
| 17 | +use Metricool\Support\Helpers\Storages\RequestStorage; | |
| 18 | +use Metricool\Support\Helpers\Storages\EnvironmentConfig; | |
| 19 | + | |
| 20 | +class SharePostController implements ControllerInterface | |
| 21 | +{ | |
| 22 | + use HasAllowlistControl; | |
| 23 | + use HasViews; | |
| 24 | + use HasNonces; | |
| 25 | + | |
| 26 | + private const DEFAULT_POST_TYPES = ['post', 'page']; | |
| 27 | + private const POST_COLUMN_KEY = 'metricool'; | |
| 28 | + private const SHARE_POST_ACTION = 'share_post'; | |
| 29 | + | |
| 30 | + private EnvironmentConfig $env; | |
| 31 | + private RequestStorage $request; | |
| 32 | + | |
| 33 | + public function __construct(EnvironmentConfig $env, RequestStorage $request) | |
| 34 | + { | |
| 35 | + $this->env = $env; | |
| 36 | + $this->request = $request; | |
| 37 | + } | |
| 38 | + | |
| 39 | + public function register(): void | |
| 40 | + { | |
| 41 | + if ($this->userCanManage() === false) { | |
| 42 | + return; | |
| 43 | + } | |
| 44 | + | |
| 45 | + add_action('init', [$this, 'registerSharePostColumn']); | |
| 46 | + add_action('admin_init', [$this, 'processShareAction']); | |
| 47 | + add_action('add_meta_boxes', [$this, 'addShareMetaBox']); | |
| 48 | + add_filter('default_hidden_columns', [$this, 'filterDefaultHiddenColumns'], 10, 2); | |
| 49 | + } | |
| 50 | + | |
| 51 | + /** | |
| 52 | + * Registers the Metricool share post column on all public post types. Due | |
| 53 | + * to {@see filterDefaultHiddenColumns} the column will be hidden by default | |
| 54 | + * on all post types except the ones defined in {@see DEFAULT_POST_TYPES}. | |
| 55 | + */ | |
| 56 | + public function registerSharePostColumn(): void | |
| 57 | + { | |
| 58 | + foreach ($this->getAllPublicPostTypes() as $postType) { | |
| 59 | + add_filter("manage_{$postType}_posts_columns", [$this, 'insertPostTableColumn']); | |
| 60 | + add_action("manage_{$postType}_posts_custom_column", [$this, 'insertPostTableColumnContent'], 10, 2); | |
| 61 | + } | |
| 62 | + } | |
| 63 | + | |
| 64 | + /** | |
| 65 | + * Retrieves all public and viewable post types. | |
| 66 | + */ | |
| 67 | + private function getAllPublicPostTypes(): array | |
| 68 | + { | |
| 69 | + $postTypes = get_post_types(['public' => true]); | |
| 70 | + return array_filter($postTypes, 'is_post_type_viewable'); | |
| 71 | + } | |
| 72 | + | |
| 73 | + /** | |
| 74 | + * Method is used for hiding the custom column by default on all post type | |
| 75 | + * list table screens except the configured default post types. Users can | |
| 76 | + * still enable the column manually through Screen Options. | |
| 77 | + */ | |
| 78 | + public function filterDefaultHiddenColumns(array $hiddenColumns, \WP_Screen $currentScreen): array | |
| 79 | + { | |
| 80 | + // Only act on post type list tables like edit-post, edit-page, etc. | |
| 81 | + if (strpos($currentScreen->id, 'edit-') !== 0) { | |
| 82 | + return $hiddenColumns; | |
| 83 | + } | |
| 84 | + | |
| 85 | + if (empty($currentScreen->post_type)) { | |
| 86 | + return $hiddenColumns; | |
| 87 | + } | |
| 88 | + | |
| 89 | + // Keep column visible if post type is in the default list. | |
| 90 | + if (in_array($currentScreen->post_type, self::DEFAULT_POST_TYPES, true)) { | |
| 91 | + return $hiddenColumns; | |
| 92 | + } | |
| 93 | + | |
| 94 | + // Hide the POST_COLUMN_KEY column by default if not already hidden. | |
| 95 | + if (!in_array(self::POST_COLUMN_KEY, $hiddenColumns, true)) { | |
| 96 | + $hiddenColumns[] = self::POST_COLUMN_KEY; | |
| 97 | + } | |
| 98 | + | |
| 99 | + return $hiddenColumns; | |
| 100 | + } | |
| 101 | + | |
| 102 | + /** | |
| 103 | + * Handles known actions from the {@see Storage} based on the | |
| 104 | + * metricool_action key. | |
| 105 | + */ | |
| 106 | + public function processShareAction(): void | |
| 107 | + { | |
| 108 | + $pageIsDashboardPage = ($this->request->getString('global.page') === 'metricool'); | |
| 109 | + $actionIsShareAction = ($this->request->getString('global.metricool_action') === self::SHARE_POST_ACTION); | |
| 110 | + | |
| 111 | + if (!$pageIsDashboardPage || !$actionIsShareAction) { | |
| 112 | + return; // abort | |
| 113 | + } | |
| 114 | + | |
| 115 | + // Validate nonce or wp_die on empty | |
| 116 | + $nonce = $this->request->getString('global._metricool_action_nonce'); | |
| 117 | + if (empty($nonce) || !$this->verifyNonce($nonce, 'metricool_action')) { | |
| 118 | + wp_die(esc_html__('Invalid nonce.', 'metricool')); | |
| 119 | + } | |
| 120 | + | |
| 121 | + $this->handleSharePostAction(); | |
| 122 | + } | |
| 123 | + | |
| 124 | + /** | |
| 125 | + * Sets the metricool column header to the post tables | |
| 126 | + */ | |
| 127 | + public function insertPostTableColumn(array $columns): array | |
| 128 | + { | |
| 129 | + $columns[self::POST_COLUMN_KEY] = $this->view('admin/share-post/column-header', [ | |
| 130 | + 'label' => __('Metricool', 'metricool') | |
| 131 | + ]); | |
| 132 | + return $columns; | |
| 133 | + } | |
| 134 | + | |
| 135 | + /** | |
| 136 | + * Sets the content of the metricool share post column | |
| 137 | + */ | |
| 138 | + public function insertPostTableColumnContent(string $columnName, int $postId): void | |
| 139 | + { | |
| 140 | + if (($columnName !== self::POST_COLUMN_KEY) || (get_post_status($postId) !== 'publish')) { | |
| 141 | + return; | |
| 142 | + } | |
| 143 | + | |
| 144 | + $this->render('admin/share-post/button', $this->getArgumentsToSharePost($postId)); | |
| 145 | + } | |
| 146 | + | |
| 147 | + /** | |
| 148 | + * Redirects to the Metricool create post screen with the content and media | |
| 149 | + */ | |
| 150 | + protected function handleSharePostAction(): void | |
| 151 | + { | |
| 152 | + $postId = $this->request->getInt('global.metricool_post_id'); | |
| 153 | + $postExists = (get_post_status($postId) !== false); | |
| 154 | + | |
| 155 | + if ($postExists === false) { | |
| 156 | + return; // abort | |
| 157 | + } | |
| 158 | + | |
| 159 | + $content = get_the_title($postId) . ' - ' . get_permalink($postId); | |
| 160 | + $mediaUrl = get_the_post_thumbnail_url($postId, 'large'); | |
| 161 | + $metricoolCreatePostUrl = MetricoolUrl::createPostUrl($content, $mediaUrl); | |
| 162 | + | |
| 163 | + // In Metricool the user can "schedule" the post they share from WP. | |
| 164 | + // That's where the naming mismatch comes from. Share <-> Schedule | |
| 165 | + Event::dispatch(Event::POST_SCHEDULED); | |
| 166 | + | |
| 167 | + // Redirect to the deeplink | |
| 168 | + header('Location: ' . $metricoolCreatePostUrl); | |
| 169 | + exit(); | |
| 170 | + } | |
| 171 | + | |
| 172 | + /** | |
| 173 | + * Adds the Metricool meta box to the post edit screen. Works for both | |
| 174 | + * Gutenberg and classic editor. Also adds the meta box for public | |
| 175 | + * post-types for which the column is not shown by default. | |
| 176 | + */ | |
| 177 | + public function addShareMetaBox(): void | |
| 178 | + { | |
| 179 | + foreach ($this->getAllPublicPostTypes() as $postType) { | |
| 180 | + add_meta_box( | |
| 181 | + 'metricool-share-post', | |
| 182 | + __('Metricool', 'metricool'), | |
| 183 | + [$this, 'renderShareMetaBox'], | |
| 184 | + $postType, | |
| 185 | + 'side', | |
| 186 | + ); | |
| 187 | + } | |
| 188 | + } | |
| 189 | + | |
| 190 | + /** | |
| 191 | + * Renders the content of the Metricool meta box on the post edit screen. | |
| 192 | + */ | |
| 193 | + public function renderShareMetaBox(\WP_Post $post): void | |
| 194 | + { | |
| 195 | + $arguments = [ | |
| 196 | + 'published' => (strtolower(get_post_status($post->ID)) === 'publish'), | |
| 197 | + 'unpublishedText' => __('Sharing is only possible for published posts.', 'metricool'), | |
| 198 | + ]; | |
| 199 | + | |
| 200 | + $this->render('admin/share-post/meta-box', $this->getArgumentsToSharePost($post->ID, $arguments)); | |
| 201 | + } | |
| 202 | + | |
| 203 | + /** | |
| 204 | + * Prepares the arguments needed to render the share post button used in: | |
| 205 | + * {@see insertPostTableColumnContent} and the meta box via: | |
| 206 | + * {@see renderShareMetaBox} | |
| 207 | + * | |
| 208 | + * @param ?array $arguments Additional args to merge, overrides defaults | |
| 209 | + */ | |
| 210 | + private function getArgumentsToSharePost(int $postID, ?array $arguments = []): array | |
| 211 | + { | |
| 212 | + $actionableUrl = add_query_arg([ | |
| 213 | + 'metricool_action' => self::SHARE_POST_ACTION, | |
| 214 | + 'metricool_post_id' => $postID, | |
| 215 | + '_metricool_action_nonce' => wp_create_nonce('metricool_action'), | |
| 216 | + ], $this->env->getUrl('plugin.dashboard_url')); | |
| 217 | + | |
| 218 | + $defaultArguments = [ | |
| 219 | + 'actionableUrl' => $actionableUrl, | |
| 220 | + 'label' => __('Share', 'metricool') | |
| 221 | + ]; | |
| 222 | + | |
| 223 | + // Merge passed arguments with priority over default arguments | |
| 224 | + return array_merge($defaultArguments, $arguments); | |
| 225 | + } | |
| 226 | +} | |