PluginProbe
Code Snippets / 4.0.0-beta.2
Code Snippets v4.0.0-beta.2
4.0.0-beta.2 3.10.2 3.10.1 3.10.0 3.10.0-beta.2 3.10.0-beta.1 4.0.0-beta.1 3.9.6 trunk 2.10.0 2.10.1 2.12.0 2.12.1 2.13.0 2.13.1 2.13.2 2.13.3 2.14.0 2.14.1 2.14.2 2.14.3 2.14.4 2.14.5 2.14.6 3.0.0 All 65 releases
code-snippets / php / Admin / Feedback_Panel.php

Feedback_Panel.php in Code Snippets 4.0.0-beta.2, at php/Admin/Feedback_Panel.php

233 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Code_Snippets\Admin;
4
5 use Code_Snippets\REST_API\Feedback\Feedback_REST_Controller;
6 use Code_Snippets\Utils\System_Info;
7 use function Code_Snippets\code_snippets;
8 use function Code_Snippets\Settings\get_setting;
9 use const Code_Snippets\PLUGIN_FILE;
10 use const Code_Snippets\PLUGIN_VERSION;
11
12 /**
13 * Attaches the feedback reporter to this plugin's admin screens.
14 *
15 * The reporter is opt-in and is only mounted where it is useful: on a Code Snippets screen,
16 * for somebody allowed to manage snippets, once the Advanced setting has been switched on.
17 *
18 * @package Code_Snippets
19 */
20 class Feedback_Panel {
21
22 /**
23 * Setting field that gates the reporter.
24 */
25 public const SETTING_FIELD = 'enable_feedback_reporter';
26
27 /**
28 * Identifier of the element the panel mounts into.
29 */
30 public const CONTAINER_ID = 'code-snippets-feedback-container';
31
32 /**
33 * Transient holding the environment summary shown in the panel.
34 */
35 public const SUMMARY_TRANSIENT = 'code_snippets_feedback_summary';
36
37 /**
38 * How long, in seconds, the environment summary is reused for.
39 */
40 private const SUMMARY_TIMEOUT = 15 * MINUTE_IN_SECONDS;
41
42 /**
43 * Script handle.
44 */
45 private const SCRIPT_HANDLE = 'code-snippets-feedback';
46
47 /**
48 * Stylesheet handle.
49 */
50 private const STYLE_HANDLE = 'code-snippets-feedback';
51
52 /**
53 * Class constructor.
54 */
55 public function __construct() {
56 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
57 add_action( 'admin_footer', [ $this, 'render_container' ] );
58 }
59
60 /**
61 * Determine whether the reporter has been switched on.
62 *
63 * @return bool
64 */
65 public static function is_enabled(): bool {
66 return (bool) get_setting( 'general', self::SETTING_FIELD );
67 }
68
69 /**
70 * Determine whether the reporter belongs on the current request.
71 *
72 * @return bool
73 */
74 public function should_render(): bool {
75 return self::is_enabled() && code_snippets()->current_user_can() && $this->is_snippets_screen();
76 }
77
78 /**
79 * Enqueue the panel assets.
80 *
81 * @return void
82 */
83 public function enqueue_assets(): void {
84 if ( ! $this->should_render() ) {
85 return;
86 }
87
88 wp_enqueue_style(
89 self::STYLE_HANDLE,
90 plugins_url( 'dist/feedback.css', PLUGIN_FILE ),
91 [ 'wp-components' ],
92 PLUGIN_VERSION
93 );
94
95 wp_enqueue_script(
96 self::SCRIPT_HANDLE,
97 plugins_url( 'dist/feedback.js', PLUGIN_FILE ),
98 [ 'react', 'react-dom', 'wp-components', 'wp-element', 'wp-i18n' ],
99 PLUGIN_VERSION,
100 true
101 );
102
103 wp_set_script_translations( self::SCRIPT_HANDLE, 'code-snippets' );
104
105 $user = wp_get_current_user();
106
107 wp_localize_script(
108 self::SCRIPT_HANDLE,
109 'CODE_SNIPPETS_FEEDBACK',
110 [
111 'restUrl' => esc_url_raw( rest_url( Feedback_REST_Controller::get_base_route() ) ),
112 // Built here rather than appended in the browser: with plain permalinks the
113 // route travels in a query parameter, where a path cannot simply be added.
114 'searchUrl' => esc_url_raw( rest_url( Feedback_REST_Controller::get_base_route() . '/search' ) ),
115 'nonce' => wp_create_nonce( 'wp_rest' ),
116 'user' => [
117 'name' => $user->display_name,
118 'email' => $user->user_email,
119 ],
120 'summary' => $this->get_cached_summary(),
121 'badge' => self::get_badge_label(),
122 'version' => PLUGIN_VERSION,
123 'edition' => System_Info::get_edition(),
124 ]
125 );
126 }
127
128 /**
129 * Retrieve the environment summary shown in the panel.
130 *
131 * Collecting it means reading the header of every installed plugin, which is too much
132 * to repeat on every admin page load for a panel that is rarely opened. The report
133 * itself is assembled from freshly collected details when one is sent.
134 *
135 * @return array<string, string>
136 */
137 private function get_cached_summary(): array {
138 $summary = get_transient( self::SUMMARY_TRANSIENT );
139
140 if ( is_array( $summary ) ) {
141 return $summary;
142 }
143
144 $summary = System_Info::get_summary( System_Info::get_system_info() );
145
146 set_transient( self::SUMMARY_TRANSIENT, $summary, self::SUMMARY_TIMEOUT );
147
148 return $summary;
149 }
150
151 /**
152 * Print the element the panel mounts into.
153 *
154 * @return void
155 */
156 public function render_container(): void {
157 if ( ! $this->should_render() ) {
158 return;
159 }
160
161 printf( '<div id="%s"></div>', esc_attr( self::CONTAINER_ID ) );
162 }
163
164 /**
165 * Describe the build a report was sent from, when it is not a released one.
166 *
167 * A released build carries no badge: labeling every install as a test build would
168 * misrepresent it. Pre-release builds are named so that a report can be read against
169 * the build it came from.
170 *
171 * @param string|null $version Version to describe. Defaults to the running version.
172 *
173 * @return string Badge text, empty when there is nothing to say.
174 */
175 public static function get_badge_label( ?string $version = null ): string {
176 $version = null === $version ? PLUGIN_VERSION : $version;
177 $label = '';
178
179 if ( preg_match( '/-(alpha|beta|rc)/i', $version, $matches ) ) {
180 $names = [
181 'alpha' => _x( 'Alpha', 'pre-release build', 'code-snippets' ),
182 'beta' => _x( 'Beta', 'pre-release build', 'code-snippets' ),
183 'rc' => _x( 'RC', 'pre-release build', 'code-snippets' ),
184 ];
185
186 $label = sprintf( '%s %s', $names[ strtolower( $matches[1] ) ], $version );
187 }
188
189 return apply_filters( 'code_snippets_feedback_badge_label', $label, $version );
190 }
191
192 /**
193 * Determine whether the current screen belongs to this plugin.
194 *
195 * Matching this plugin's own menu slugs, rather than looking for 'snippet' anywhere in
196 * the screen identifier, keeps the reporter off screens belonging to other plugins.
197 *
198 * @return bool
199 */
200 private function is_snippets_screen(): bool {
201 if ( ! is_admin() ) {
202 return false;
203 }
204
205 $slugs = [];
206
207 foreach ( [ '', 'add', 'edit', 'import', 'settings', 'insights', 'welcome' ] as $menu ) {
208 $slugs[] = code_snippets()->get_menu_slug( $menu );
209 }
210
211 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
212 $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
213
214 if ( $page && in_array( $page, $slugs, true ) ) {
215 return true;
216 }
217
218 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
219
220 if ( ! $screen ) {
221 return false;
222 }
223
224 foreach ( $slugs as $slug ) {
225 if ( $slug && substr( $screen->id, -strlen( '_page_' . $slug ) ) === '_page_' . $slug ) {
226 return true;
227 }
228 }
229
230 return false;
231 }
232 }
233