PluginProbe
Plugin Check (PCP) / trunk
Plugin Check (PCP) vtrunk
2.1.0 trunk 0.1 0.2.0 0.2.1 0.2.2 0.2.3 1.0.0 1.0.1 1.0.2 1.1.0 1.2.0 1.3.0 1.3.1 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 1.9.0 2.0.0 ci-artifacts
plugin-check / includes / Admin / Settings_Page.php

Settings_Page.php in Plugin Check (PCP) trunk, at includes/Admin/Settings_Page.php

391 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class WordPress\Plugin_Check\Admin\Settings_Page
4 *
5 * @package plugin-check
6 */
7
8 namespace WordPress\Plugin_Check\Admin;
9
10 use WordPress\Plugin_Check\Traits\AI_Utils;
11
12 /**
13 * Class to handle the Settings page for Plugin Check.
14 *
15 * Provides AI model selection (from WordPress 7.0 core AI connectors)
16 * and severity threshold configuration for AI false positive detection.
17 *
18 * @since 2.0.0
19 *
20 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
21 */
22 final class Settings_Page {
23
24 use AI_Utils;
25
26 /**
27 * Option group name.
28 *
29 * @since 2.0.0
30 * @var string
31 */
32 const OPTION_GROUP = 'plugin_check_settings';
33
34 /**
35 * Option name.
36 *
37 * @since 2.0.0
38 * @var string
39 */
40 const OPTION_NAME = 'plugin_check_settings';
41
42 /**
43 * Page slug.
44 *
45 * @since 2.0.0
46 * @var string
47 */
48 const PAGE_SLUG = 'plugin-check-settings';
49
50 /**
51 * Admin page hook suffix.
52 *
53 * @since 2.0.0
54 * @var string
55 */
56 protected $hook_suffix = '';
57
58 /**
59 * Registers WordPress hooks for the settings page.
60 *
61 * @since 2.0.0
62 */
63 public function add_hooks() {
64 add_action( 'admin_menu', array( $this, 'add_page' ) );
65 add_action( 'admin_init', array( $this, 'register_settings' ) );
66 }
67
68 /**
69 * Adds the settings page under the Settings menu.
70 *
71 * @since 2.0.0
72 */
73 public function add_page() {
74 $this->hook_suffix = add_submenu_page(
75 'options-general.php',
76 __( 'Plugin Check', 'plugin-check' ),
77 __( 'Plugin Check', 'plugin-check' ),
78 'manage_options',
79 self::PAGE_SLUG,
80 array( $this, 'render_page' )
81 );
82 }
83
84 /**
85 * Registers settings and settings fields.
86 *
87 * @since 2.0.0
88 */
89 public function register_settings() {
90 register_setting(
91 self::OPTION_GROUP,
92 self::OPTION_NAME,
93 array(
94 'sanitize_callback' => array( $this, 'sanitize_settings' ),
95 'default' => array(
96 'ai_model_preference' => '',
97 'ai_severity_errors' => 7,
98 'ai_severity_warnings' => 6,
99 ),
100 )
101 );
102
103 // AI Code Review section.
104 add_settings_section(
105 'ai_code_review_section',
106 __( 'AI Code Review', 'plugin-check' ),
107 array( $this, 'render_ai_section_description' ),
108 self::PAGE_SLUG
109 );
110
111 add_settings_field(
112 'ai_model_preference',
113 __( 'AI Model', 'plugin-check' ),
114 array( $this, 'render_model_preference_field' ),
115 self::PAGE_SLUG,
116 'ai_code_review_section',
117 array(
118 'label_for' => 'ai_model_preference',
119 )
120 );
121
122 // Severity threshold section.
123 add_settings_section(
124 'ai_severity_section',
125 __( 'Severity Threshold', 'plugin-check' ),
126 array( $this, 'render_severity_section_description' ),
127 self::PAGE_SLUG
128 );
129
130 add_settings_field(
131 'ai_severity_errors',
132 __( 'Errors', 'plugin-check' ),
133 array( $this, 'render_severity_errors_field' ),
134 self::PAGE_SLUG,
135 'ai_severity_section',
136 array(
137 'label_for' => 'ai_severity_errors',
138 )
139 );
140
141 add_settings_field(
142 'ai_severity_warnings',
143 __( 'Warnings', 'plugin-check' ),
144 array( $this, 'render_severity_warnings_field' ),
145 self::PAGE_SLUG,
146 'ai_severity_section',
147 array(
148 'label_for' => 'ai_severity_warnings',
149 )
150 );
151 }
152
153 /**
154 * Renders the AI settings section description.
155 *
156 * @since 2.0.0
157 */
158 public function render_ai_section_description() {
159 $has_connectors = ! $this->has_no_active_ai_connectors();
160 ?>
161 <p>
162 <?php esc_html_e( 'Select the AI model to use for code review and false positive detection. Models are provided by the AI connectors configured in WordPress.', 'plugin-check' ); ?>
163 </p>
164 <?php if ( ! $has_connectors ) : ?>
165 <div class="notice notice-warning inline">
166 <p>
167 <?php
168 $configured_connector_message = sprintf(
169 /* translators: %s: URL to WordPress AI settings. */
170 __( 'No AI connectors are configured. Please <a href="%s">configure an AI connector</a> in WordPress settings first.', 'plugin-check' ),
171 esc_url( admin_url( 'options-connectors.php' ) )
172 );
173
174 echo wp_kses(
175 $configured_connector_message,
176 array( 'a' => array( 'href' => array() ) )
177 );
178 ?>
179 </p>
180 </div>
181 <?php endif; ?>
182 <?php
183 }
184
185 /**
186 * Renders the severity section description.
187 *
188 * @since 2.0.0
189 */
190 public function render_severity_section_description() {
191 ?>
192 <p>
193 <?php esc_html_e( 'Set the severity threshold (1-10). AI will analyze issues with severity BELOW this value. Low severity issues are more likely to be false positives.', 'plugin-check' ); ?>
194 </p>
195 <?php
196 }
197
198 /**
199 * Renders the AI model preference field.
200 *
201 * Dynamically populated from WordPress 7.0 AI connectors.
202 *
203 * @since 2.0.0
204 *
205 * @param array $args Field arguments.
206 */
207 public function render_model_preference_field( $args ) {
208 $settings = get_option( self::OPTION_NAME, array() );
209 $value = isset( $settings['ai_model_preference'] ) ? $settings['ai_model_preference'] : '';
210 $grouped_models = $this->get_available_model_preferences();
211 $has_models = ! empty( $grouped_models );
212 ?>
213 <select
214 id="<?php echo esc_attr( $args['label_for'] ); ?>"
215 name="<?php echo esc_attr( self::OPTION_NAME . '[ai_model_preference]' ); ?>"
216 class="regular-text"
217 <?php echo ! $has_models ? 'disabled' : ''; ?>
218 >
219 <option value=""><?php esc_html_e( '-- Default (auto) --', 'plugin-check' ); ?></option>
220 <?php foreach ( $grouped_models as $group_label => $models ) : ?>
221 <optgroup label="<?php echo esc_attr( $group_label ); ?>">
222 <?php foreach ( $models as $model ) : ?>
223 <option value="<?php echo esc_attr( $model['value'] ); ?>" <?php selected( $value, $model['value'] ); ?>>
224 <?php echo esc_html( $model['label'] ); ?>
225 </option>
226 <?php endforeach; ?>
227 </optgroup>
228 <?php endforeach; ?>
229 </select>
230 <?php if ( ! $has_models ) : ?>
231 <p class="description" style="color: #d63638;">
232 <?php esc_html_e( 'No AI models available. Please configure an AI connector in WordPress settings.', 'plugin-check' ); ?>
233 </p>
234 <?php else : ?>
235 <p class="description">
236 <?php esc_html_e( 'Select the AI model for code review. Code-optimized models (e.g., GPT-4o, Claude Sonnet) are recommended for best results.', 'plugin-check' ); ?>
237 </p>
238 <?php endif; ?>
239 <?php
240 }
241
242 /**
243 * Renders the severity threshold field for errors.
244 *
245 * @since 2.0.0
246 *
247 * @param array $args Field arguments.
248 */
249 public function render_severity_errors_field( $args ) {
250 $settings = get_option( self::OPTION_NAME, array() );
251 $value = isset( $settings['ai_severity_errors'] ) ? intval( $settings['ai_severity_errors'] ) : 7;
252 ?>
253 <input
254 type="number"
255 id="<?php echo esc_attr( $args['label_for'] ); ?>"
256 name="<?php echo esc_attr( self::OPTION_NAME . '[ai_severity_errors]' ); ?>"
257 value="<?php echo esc_attr( (string) $value ); ?>"
258 min="1"
259 max="10"
260 class="small-text"
261 />
262 <p class="description">
263 <?php esc_html_e( 'Analyze errors with severity < this value (Default: 7)', 'plugin-check' ); ?>
264 </p>
265 <?php
266 }
267
268 /**
269 * Renders the severity threshold field for warnings.
270 *
271 * @since 2.0.0
272 *
273 * @param array $args Field arguments.
274 */
275 public function render_severity_warnings_field( $args ) {
276 $settings = get_option( self::OPTION_NAME, array() );
277 $value = isset( $settings['ai_severity_warnings'] ) ? intval( $settings['ai_severity_warnings'] ) : 6;
278 ?>
279 <input
280 type="number"
281 id="<?php echo esc_attr( $args['label_for'] ); ?>"
282 name="<?php echo esc_attr( self::OPTION_NAME . '[ai_severity_warnings]' ); ?>"
283 value="<?php echo esc_attr( (string) $value ); ?>"
284 min="1"
285 max="10"
286 class="small-text"
287 />
288 <p class="description">
289 <?php esc_html_e( 'Analyze warnings with severity < this value (Default: 6)', 'plugin-check' ); ?>
290 </p>
291 <?php
292 }
293
294 /**
295 * Sanitizes settings input.
296 *
297 * @since 2.0.0
298 *
299 * @param array $input Settings input.
300 * @return array Sanitized settings.
301 */
302 public function sanitize_settings( $input ) {
303 $sanitized = array();
304
305 if ( isset( $input['ai_model_preference'] ) ) {
306 $sanitized['ai_model_preference'] = sanitize_text_field( $input['ai_model_preference'] );
307 } else {
308 $sanitized['ai_model_preference'] = '';
309 }
310
311 if ( isset( $input['ai_severity_errors'] ) ) {
312 $value = intval( $input['ai_severity_errors'] );
313 $sanitized['ai_severity_errors'] = ( $value >= 1 && $value <= 10 ) ? $value : 7;
314 } else {
315 $sanitized['ai_severity_errors'] = 7;
316 }
317
318 if ( isset( $input['ai_severity_warnings'] ) ) {
319 $value = intval( $input['ai_severity_warnings'] );
320 $sanitized['ai_severity_warnings'] = ( $value >= 1 && $value <= 10 ) ? $value : 6;
321 } else {
322 $sanitized['ai_severity_warnings'] = 6;
323 }
324
325 return $sanitized;
326 }
327
328 /**
329 * Gets the saved AI model preference.
330 *
331 * @since 2.0.0
332 *
333 * @return string AI model preference (e.g., 'openai::gpt-4o') or empty for auto.
334 */
335 public static function get_model_preference() {
336 $settings = get_option( self::OPTION_NAME, array() );
337 return isset( $settings['ai_model_preference'] ) ? $settings['ai_model_preference'] : '';
338 }
339
340 /**
341 * Gets the AI severity threshold for errors.
342 *
343 * @since 2.0.0
344 *
345 * @return int AI severity threshold for errors.
346 */
347 public static function get_severity_errors() {
348 $settings = get_option( self::OPTION_NAME, array() );
349 return isset( $settings['ai_severity_errors'] ) ? intval( $settings['ai_severity_errors'] ) : 7;
350 }
351
352 /**
353 * Gets the AI severity threshold for warnings.
354 *
355 * @since 2.0.0
356 *
357 * @return int AI severity threshold for warnings.
358 */
359 public static function get_severity_warnings() {
360 $settings = get_option( self::OPTION_NAME, array() );
361 return isset( $settings['ai_severity_warnings'] ) ? intval( $settings['ai_severity_warnings'] ) : 6;
362 }
363
364 /**
365 * Renders the settings page.
366 *
367 * @since 2.0.0
368 */
369 public function render_page() {
370 if ( ! current_user_can( 'manage_options' ) ) {
371 wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'plugin-check' ) );
372 }
373
374 ?>
375 <div class="wrap">
376 <h1><?php esc_html_e( 'Plugin Check Settings', 'plugin-check' ); ?></h1>
377
378 <?php settings_errors( self::OPTION_NAME ); ?>
379
380 <form method="post" action="options.php">
381 <?php
382 settings_fields( self::OPTION_GROUP );
383 do_settings_sections( self::PAGE_SLUG );
384 submit_button();
385 ?>
386 </form>
387 </div>
388 <?php
389 }
390 }
391