Plugin Directory

source: plugin-check/trunk/includes/Admin/Namer_Page.php

Last change on this file was 3483193, checked in by githubsync, 13 days ago

Update to version 1.9.0 from GitHub

File size: 14.2 KB
Line 
1<?php
2/**
3 * Class WordPress\Plugin_Check\Admin\Namer_Page
4 *
5 * @package plugin-check
6 */
7
8namespace WordPress\Plugin_Check\Admin;
9
10use WordPress\Plugin_Check\Traits\AI_Check_Names;
11use WordPress\Plugin_Check\Traits\AI_Utils;
12use WP_Error;
13
14/**
15 * Admin page for the Plugin Check Namer tool.
16 *
17 * @since 1.8.0
18 */
19final class Namer_Page {
20
21        use AI_Check_Names;
22        use AI_Utils;
23
24        /**
25         * Menu slug.
26         *
27         * @since 1.8.0
28         * @var string
29         */
30        const MENU_SLUG = 'plugin-check-namer';
31
32        /**
33         * Admin-post action for analysis.
34         *
35         * @since 1.8.0
36         * @var string
37         */
38        const ACTION_ANALYZE = 'plugin_check_namer_analyze';
39
40        /**
41         * Hook suffix for the tools page.
42         *
43         * @since 1.8.0
44         * @var string
45         */
46        protected $hook_suffix = '';
47
48        /**
49         * Registers WordPress hooks.
50         *
51         * @since 1.8.0
52         */
53        public function add_hooks() {
54                add_action( 'admin_menu', array( $this, 'add_page' ) );
55                add_action( 'admin_post_' . self::ACTION_ANALYZE, array( $this, 'handle_analyze' ) );
56                add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
57                add_action( 'admin_notices', array( $this, 'render_ai_notice' ) );
58                add_action( 'wp_ajax_plugin_check_namer_analyze', array( $this, 'ajax_analyze' ) );
59        }
60
61        /**
62         * Adds the tools page.
63         *
64         * @since 1.8.0
65         */
66        public function add_page() {
67                $this->hook_suffix = add_management_page(
68                        __( 'Plugin Check Namer', 'plugin-check' ),
69                        __( 'Plugin Check Namer', 'plugin-check' ),
70                        'manage_options',
71                        self::MENU_SLUG,
72                        array( $this, 'render_page' )
73                );
74        }
75
76        /**
77         * Enqueues scripts for the tools page.
78         *
79         * @since 1.8.0
80         *
81         * @param string $hook_suffix Current admin page hook suffix.
82         */
83        public function enqueue_scripts( $hook_suffix ) {
84                if ( $hook_suffix !== $this->hook_suffix ) {
85                        return;
86                }
87
88                wp_enqueue_style(
89                        'plugin-check-admin',
90                        plugins_url( 'assets/css/plugin-check-admin.css', WP_PLUGIN_CHECK_MAIN_FILE ),
91                        array(),
92                        WP_PLUGIN_CHECK_VERSION
93                );
94
95                wp_enqueue_script(
96                        'plugin-check-namer',
97                        plugins_url( 'assets/js/plugin-check-namer.js', WP_PLUGIN_CHECK_MAIN_FILE ),
98                        array(),
99                        WP_PLUGIN_CHECK_VERSION,
100                        true
101                );
102
103                wp_localize_script(
104                        'plugin-check-namer',
105                        'pluginCheckNamer',
106                        array(
107                                'ajaxUrl'  => admin_url( 'admin-ajax.php' ),
108                                'nonce'    => wp_create_nonce( 'plugin_check_namer_ajax' ),
109                                'messages' => array(
110                                        'missingName'  => __( 'Please enter a plugin name.', 'plugin-check' ),
111                                        'genericError' => __( 'An unexpected error occurred.', 'plugin-check' ),
112                                ),
113                        )
114                );
115        }
116
117        /**
118         * AJAX handler to analyze a plugin name.
119         *
120         * @since 1.8.0
121         */
122        public function ajax_analyze() {
123                check_ajax_referer( 'plugin_check_namer_ajax', 'nonce' );
124
125                if ( ! current_user_can( 'manage_options' ) ) {
126                        wp_send_json_error( array( 'message' => __( 'Insufficient permissions.', 'plugin-check' ) ) );
127                }
128
129                $name = $this->get_plugin_name_from_request();
130                if ( empty( $name ) ) {
131                        wp_send_json_error( array( 'message' => __( 'Please enter a plugin name.', 'plugin-check' ) ) );
132                }
133
134                $author = $this->get_author_name_from_request();
135
136                $model_preference = $this->get_model_preference_from_request();
137                $ai_config        = $this->get_ai_config( $model_preference );
138                if ( is_wp_error( $ai_config ) ) {
139                        wp_send_json_error( array( 'message' => $ai_config->get_error_message() ) );
140                }
141
142                $analysis = $this->run_name_analysis( $ai_config['model_preference'], $name, $author );
143                if ( is_wp_error( $analysis ) ) {
144                        wp_send_json_error( array( 'message' => $analysis->get_error_message() ) );
145                }
146
147                $parsed   = $this->parse_analysis( $analysis );
148                $response = $this->build_ajax_response( $parsed, $analysis, $ai_config );
149
150                wp_send_json_success( $response );
151        }
152
153        /**
154         * Builds AJAX response from parsed analysis.
155         *
156         * @since 1.8.0
157         *
158         * @param array        $parsed    Parsed analysis.
159         * @param string|array $analysis  Raw analysis.
160         * @param array        $ai_config AI configuration with model preference info.
161         * @return array Response array.
162         */
163        protected function build_ajax_response( $parsed, $analysis, $ai_config = array() ) {
164                $raw_output = $this->get_raw_output( $parsed, $analysis );
165                $raw_output = $this->format_json_output( $raw_output );
166
167                $response = array(
168                        'verdict'     => $parsed['verdict'],
169                        'explanation' => $parsed['explanation'],
170                        'raw'         => $raw_output,
171                );
172
173                if ( ! empty( $parsed['confusion_existing_plugins'] ) ) {
174                        $response['confusion_existing_plugins'] = $parsed['confusion_existing_plugins'];
175                }
176                if ( ! empty( $parsed['confusion_existing_others'] ) ) {
177                        $response['confusion_existing_others'] = $parsed['confusion_existing_others'];
178                }
179                if ( ! empty( $parsed['token_usage'] ) ) {
180                        $response['token_usage'] = $parsed['token_usage'];
181                }
182
183                // Add AI model preference information.
184                if ( ! empty( $ai_config['model_preference'] ) ) {
185                        $response['ai_info'] = array(
186                                'model' => $ai_config['model_preference'],
187                        );
188                }
189
190                return $response;
191        }
192
193        /**
194         * Gets plugin name from request.
195         *
196         * @since 1.8.0
197         *
198         * @return string Plugin name or empty string.
199         */
200        protected function get_plugin_name_from_request() {
201                $name = isset( $_POST['plugin_name'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin_name'] ) ) : '';
202                return trim( $name );
203        }
204
205        /**
206         * Gets author name from request.
207         *
208         * @since 1.8.0
209         *
210         * @return string Author name or empty string.
211         */
212        protected function get_author_name_from_request() {
213                $author = isset( $_POST['author_name'] ) ? sanitize_text_field( wp_unslash( $_POST['author_name'] ) ) : '';
214                return trim( $author );
215        }
216
217        /**
218         * Renders admin notice when AI connectors are not configured.
219         *
220         * @since 1.9.0
221         */
222        public function render_ai_notice() {
223                $screen = get_current_screen();
224                if ( ! $screen || $screen->id !== $this->hook_suffix ) {
225                        return;
226                }
227
228                if ( ! current_user_can( 'manage_options' ) ) {
229                        return;
230                }
231
232                $ai_config = $this->get_ai_config();
233                if ( ! is_wp_error( $ai_config ) ) {
234                        return;
235                }
236                ?>
237                <div class="notice notice-warning is-dismissible">
238                        <p>
239                                <?php
240                                printf(
241                                        /* translators: %s: Error message. */
242                                        __( 'To use the Namer tool, configure AI connectors in WordPress 7.0+ settings. Details: %s', 'plugin-check' ),
243                                        esc_html( $ai_config->get_error_message() )
244                                );
245                                ?>
246                        </p>
247                </div>
248                <?php
249        }
250
251        /**
252         * Renders the page.
253         *
254         * @since 1.8.0
255         *
256         * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
257         */
258        public function render_page() {
259                if ( ! current_user_can( 'manage_options' ) ) {
260                        return;
261                }
262
263                $ai_config = $this->get_ai_config();
264                ?>
265                <div class="wrap">
266                        <h1><?php echo esc_html__( 'Plugin Check Namer Tool', 'plugin-check' ); ?></h1>
267
268                        <?php
269                        if ( is_wp_error( $ai_config ) ) {
270                                ?>
271                                <p class="description">
272                                        <?php esc_html_e( 'The Plugin Namer requires WordPress 7.0+ with configured AI connectors. Please enable AI connectors in core to use this tool.', 'plugin-check' ); ?>
273                                </p>
274                                <p>
275                                        <a class="button button-primary" href="<?php echo esc_url( admin_url( 'options-connectors.php' ) ); ?>">
276                                                <?php esc_html_e( 'Configure AI Connectors', 'plugin-check' ); ?>
277                                        </a>
278                                </p>
279                                <?php
280                                return;
281                        }
282                        ?>
283
284                        <form id="plugin-check-namer-form" method="post">
285                                <table class="form-table" role="presentation">
286                                        <tbody>
287                                                <tr>
288                                                        <th scope="row">
289                                                                <label for="plugin_check_namer_input"><?php echo esc_html__( 'Plugin name', 'plugin-check' ); ?></label>
290                                                        </th>
291                                                        <td>
292                                                                <input
293                                                                        type="text"
294                                                                        id="plugin_check_namer_input"
295                                                                        name="plugin_check_namer_input"
296                                                                        class="large-text"
297                                                                        value=""
298                                                                        required
299                                                                />
300                                                                <p class="description">
301                                                                        <?php echo esc_html__( 'Enter the plugin name you want to evaluate.', 'plugin-check' ); ?>
302                                                                </p>
303                                                        </td>
304                                                </tr>
305                                                <tr>
306                                                        <th scope="row">
307                                                                <label for="plugin_check_namer_author"><?php echo esc_html__( 'Author name', 'plugin-check' ); ?></label>
308                                                        </th>
309                                                        <td>
310                                                                <input
311                                                                        type="text"
312                                                                        id="plugin_check_namer_author"
313                                                                        name="plugin_check_namer_author"
314                                                                        class="regular-text"
315                                                                        value=""
316                                                                />
317                                                                <p class="description">
318                                                                        <?php echo esc_html__( 'Optional: Enter the author or brand name if you own the trademark.', 'plugin-check' ); ?>
319                                                                </p>
320                                                        </td>
321                                                </tr>
322                                                <tr>
323                                                        <th scope="row">
324                                                                <label for="plugin_check_namer_model"><?php echo esc_html__( 'AI model', 'plugin-check' ); ?></label>
325                                                        </th>
326                                                        <td>
327                                                                <?php $model_groups = $this->get_available_model_preferences(); ?>
328                                                                <select
329                                                                        id="plugin_check_namer_model"
330                                                                        name="model_preference"
331                                                                        class="regular-text"
332                                                                >
333                                                                        <option value=""><?php echo esc_html__( 'Automatic (recommended)', 'plugin-check' ); ?></option>
334                                                                        <?php foreach ( $model_groups as $group_label => $group_options ) : ?>
335                                                                                <?php if ( ! empty( $group_label ) ) : ?>
336                                                                                        <optgroup label="<?php echo esc_attr( $group_label ); ?>">
337                                                                                <?php endif; ?>
338                                                                                <?php foreach ( $group_options as $model_option ) : ?>
339                                                                                        <option value="<?php echo esc_attr( $model_option['value'] ); ?>">
340                                                                                                <?php echo esc_html( $model_option['label'] ); ?>
341                                                                                        </option>
342                                                                                <?php endforeach; ?>
343                                                                                <?php if ( ! empty( $group_label ) ) : ?>
344                                                                                        </optgroup>
345                                                                                <?php endif; ?>
346                                                                        <?php endforeach; ?>
347                                                                </select>
348                                                                <p class="description">
349                                                                        <?php echo esc_html__( 'Choose a specific model from connected AI providers, or leave as automatic to let WordPress decide.', 'plugin-check' ); ?>
350                                                                </p>
351                                                        </td>
352                                                </tr>
353                                        </tbody>
354                                </table>
355
356                                <p class="description">
357                                        <strong><?php echo esc_html__( 'Note:', 'plugin-check' ); ?></strong>
358                                        <br/>
359                                        <?php echo esc_html__( 'This tool provides guidance only and is not definitive. It contains a prompt that is used to evaluate the similarity of a plugin name to other plugin names and ensure compliance with trademark regulations.', 'plugin-check' ); ?>
360                                        <br/>
361                                        <?php echo esc_html__( 'This analysis performs two AI checks for similarity and trademark conflicts, which may take a moment to complete.', 'plugin-check' ); ?>
362                                </p>
363                                <p class="submit">
364                                        <button type="submit" class="button button-primary" id="plugin-check-namer-submit"><?php echo esc_html__( 'Evaluate name', 'plugin-check' ); ?></button>
365                                        <span class="spinner plugin-check-namer-spinner" id="plugin-check-namer-spinner"></span>
366                                </p>
367                        </form>
368
369                        <div id="plugin-check-namer-error" class="notice notice-error plugin-check-namer-hidden"><p></p></div>
370
371                        <div id="plugin-check-namer-result" class="plugin-check-namer-hidden">
372                                <h2><?php echo esc_html__( 'Result', 'plugin-check' ); ?></h2>
373                                <div id="plugin-check-namer-verdict-container" class="plugin-check-namer-verdict-container plugin-check-namer-hidden">
374                                        <p class="plugin-check-namer-verdict-item">
375                                                <strong><?php echo esc_html__( 'Verdict:', 'plugin-check' ); ?></strong>
376                                                <span id="plugin-check-namer-verdict"></span>
377                                        </p>
378                                        <p class="plugin-check-namer-verdict-item">
379                                                <strong><?php echo esc_html__( 'Explanation:', 'plugin-check' ); ?></strong>
380                                                <span id="plugin-check-namer-explanation"></span>
381                                        </p>
382                                <p id="plugin-check-namer-timing" class="plugin-check-namer-meta plugin-check-namer-hidden">
383                                        <strong><?php echo esc_html__( 'Analysis completed in:', 'plugin-check' ); ?></strong>
384                                        <span id="plugin-check-namer-timing-value"></span>
385                                </p>
386                                <p id="plugin-check-namer-tokens" class="plugin-check-namer-meta plugin-check-namer-hidden">
387                                        <strong><?php echo esc_html__( 'Tokens used:', 'plugin-check' ); ?></strong>
388                                        <span id="plugin-check-namer-tokens-value"></span>
389                                </p>
390                        </div>
391                                <div id="plugin-check-namer-confusion-plugins" class="plugin-check-namer-confusion plugin-check-namer-hidden">
392                                        <p><strong><?php echo esc_html__( 'Similar Existing Plugins', 'plugin-check' ); ?></strong></p>
393                                        <div id="plugin-check-namer-confusion-plugins-list"></div>
394                                </div>
395
396                                <div id="plugin-check-namer-confusion-others" class="plugin-check-namer-confusion plugin-check-namer-hidden">
397                                        <h3><?php echo esc_html__( 'Similar Existing Projects/Trademarks', 'plugin-check' ); ?></h3>
398                                        <div id="plugin-check-namer-confusion-others-list"></div>
399                                </div>
400                        </div>
401                </div>
402                <?php
403        }
404
405        /**
406         * Handles the analysis form submission.
407         *
408         * @since 1.8.0
409         */
410        public function handle_analyze() {
411                if ( ! current_user_can( 'manage_options' ) ) {
412                        wp_die( esc_html__( 'Insufficient permissions.', 'plugin-check' ) );
413                }
414
415                check_admin_referer( 'plugin_check_namer_analyze', 'plugin_check_namer_nonce' );
416
417                $input = isset( $_POST['plugin_check_namer_input'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin_check_namer_input'] ) ) : '';
418                $input = trim( $input );
419
420                $author = isset( $_POST['plugin_check_namer_author'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin_check_namer_author'] ) ) : '';
421                $author = trim( $author );
422
423                $model_preference = $this->get_model_preference_from_request();
424                $user_id          = get_current_user_id();
425
426                if ( empty( $input ) ) {
427                        $this->handle_analyze_error( $user_id, '', new WP_Error( 'missing_input', __( 'Please enter a plugin name.', 'plugin-check' ) ) );
428                        return;
429                }
430
431                $ai_config = $this->get_ai_config( $model_preference );
432                if ( is_wp_error( $ai_config ) ) {
433                        $this->handle_analyze_error( $user_id, $input, $ai_config );
434                        return;
435                }
436
437                $analysis = $this->run_name_analysis( $ai_config['model_preference'], $input, $author );
438
439                if ( is_wp_error( $analysis ) ) {
440                        $this->handle_analyze_error( $user_id, $input, $analysis );
441                        return;
442                }
443
444                $this->store_result(
445                        $user_id,
446                        array(
447                                'input'    => $input,
448                                'analysis' => $analysis,
449                        )
450                );
451                wp_safe_redirect( $this->get_page_url() );
452                exit;
453        }
454
455        /**
456         * Handles analyze error and redirects.
457         *
458         * @since 1.8.0
459         *
460         * @param int      $user_id User ID.
461         * @param string   $input   Input value.
462         * @param WP_Error $error   Error object.
463         */
464        protected function handle_analyze_error( $user_id, $input, $error ) {
465                $this->store_result(
466                        $user_id,
467                        array(
468                                'input' => $input,
469                                'error' => $error,
470                        )
471                );
472                wp_safe_redirect( $this->get_page_url() );
473                exit;
474        }
475
476        /**
477         * Gets the page URL.
478         *
479         * @since 1.8.0
480         *
481         * @return string
482         */
483        protected function get_page_url() {
484                return add_query_arg( array( 'page' => self::MENU_SLUG ), admin_url( 'tools.php' ) );
485        }
486}
Note: See TracBrowser for help on using the repository browser.