PluginProbe
Metricool – Social media and site statistics / trunk
Metricool – Social media and site statistics vtrunk
2.1.0 2.0.2 2.0.1 2.0.0 1.27 trunk
← All changes | app/Controllers/SharePostController.php +226 -0 2.0.1trunk View file →
@@ -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 +}