PluginProbe
Plugin Check (PCP) / 1.5.0
Plugin Check (PCP) v1.5.0
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 / Checker / Checks / Plugin_Repo / Trademarks_Check.php

Trademarks_Check.php in Plugin Check (PCP) 1.5.0, at includes/Checker/Checks/Plugin_Repo/Trademarks_Check.php

569 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class Trademarks_Check.
4 *
5 * @package plugin-check
6 */
7
8 namespace WordPress\Plugin_Check\Checker\Checks\Plugin_Repo;
9
10 use Exception;
11 use WordPress\Plugin_Check\Checker\Check_Categories;
12 use WordPress\Plugin_Check\Checker\Check_Result;
13 use WordPress\Plugin_Check\Checker\Checks\Abstract_File_Check;
14 use WordPress\Plugin_Check\Traits\Amend_Check_Result;
15 use WordPress\Plugin_Check\Traits\Find_Readme;
16 use WordPress\Plugin_Check\Traits\Stable_Check;
17 use WordPressdotorg\Plugin_Directory\Readme\Parser;
18
19 /**
20 * Check for trademarks.
21 *
22 * @since 1.0.0
23 */
24 class Trademarks_Check extends Abstract_File_Check {
25
26 use Amend_Check_Result;
27 use Find_Readme;
28 use Stable_Check;
29
30 /**
31 * Lists of trademark terms that are commonly abused on WordPress.org.
32 *
33 * @since 1.0.0
34 *
35 * @var string[]
36 */
37 const TRADEMARK_SLUGS = array(
38 'adobe-',
39 'adsense-',
40 'advanced-custom-fields-',
41 'adwords-',
42 'akismet-',
43 'all-in-one-wp-migration',
44 'amazon-',
45 'android-',
46 'apple-',
47 'applenews-',
48 'applepay-',
49 'aws-',
50 'azon-',
51 'bbpress-',
52 'bing-',
53 'booking-com',
54 'bootstrap-',
55 'buddypress-',
56 'chatgpt-',
57 'chat-gpt-',
58 'cloudflare-',
59 'contact-form-7-',
60 'cpanel-',
61 'disqus-',
62 'divi-',
63 'dropbox-',
64 'easy-digital-downloads-',
65 'elementor-',
66 'envato-',
67 'fbook',
68 'facebook',
69 'fb-',
70 'fb-messenger',
71 'fedex-',
72 'feedburner',
73 'firefox-',
74 'fontawesome-',
75 'font-awesome-',
76 'ganalytics-',
77 'gberg',
78 'github-',
79 'givewp-',
80 'google-',
81 'googlebot-',
82 'googles-',
83 'gravity-form-',
84 'gravity-forms-',
85 'gravityforms-',
86 'gtmetrix-',
87 'gutenberg',
88 'guten-',
89 'hubspot-',
90 'ig-',
91 'insta-',
92 'instagram',
93 'internet-explorer-',
94 'ios-',
95 'jetpack-',
96 'macintosh-',
97 'macos-',
98 'mailchimp-',
99 'microsoft-',
100 'ninja-forms-',
101 'oculus',
102 'onlyfans-',
103 'only-fans-',
104 'opera-',
105 'paddle-',
106 'paypal-',
107 'pinterest-',
108 'plugin',
109 'skype-',
110 'stripe-',
111 'tiktok-',
112 'tik-tok-',
113 'trustpilot',
114 'twitch-',
115 'twitter-',
116 'tweet',
117 'ups-',
118 'usps-',
119 'vvhatsapp',
120 'vvcommerce',
121 'vva-',
122 'vvoo',
123 'wa-',
124 'webpush-vn',
125 'wh4tsapps',
126 'whatsapp',
127 'whats-app',
128 'watson',
129 'windows-',
130 'wocommerce',
131 'woocom-',
132 'woocommerce', // technically ending with '-for-woocommerce' is allowed.
133 'woocomerce',
134 'woo-commerce',
135 'woo-',
136 'wo-',
137 'wordpress',
138 'wordpess',
139 'wpress',
140 'wp', // it's allowed, but shows a warning.
141 'wc', // it's allowed, but shows a warning.
142 'wp-mail-smtp-',
143 'yandex-',
144 'yahoo-',
145 'yoast',
146 'youtube-',
147 'you-tube-',
148 );
149
150 /**
151 * Lists of allowed acronyms of trademarks.
152 *
153 * @since 1.3.0
154 *
155 * @var string[]
156 */
157 const ALLOWED_ACRONYMS = array(
158 'wp',
159 'wc',
160 );
161
162 /**
163 * Lists of trademarks that are allowed as 'for-whatever' ONLY.
164 *
165 * @since 1.0.0
166 *
167 * @var string[]
168 */
169 const FOR_USE_EXCEPTIONS = array(
170 'woocommerce',
171 );
172
173 /**
174 * Lists of commonly used 'combo' names (to prevent things like 'woopress').
175 *
176 * @since 1.0.0
177 *
178 * @var string[]
179 */
180 const PORTMANTEAUS = array(
181 'woo',
182 );
183
184 const TYPE_README = 1;
185 const TYPE_NAME = 2;
186 const TYPE_SLUG = 4;
187 const TYPE_ALL = 7; // Same as all of the above with bitwise OR.
188
189 /**
190 * Bitwise flags to control check behavior.
191 *
192 * @since 1.0.0
193 * @var int
194 */
195 protected $flags = 0;
196
197 /**
198 * Constructor.
199 *
200 * @since 1.0.0
201 *
202 * @param int $flags Bitwise flags to control check behavior.
203 */
204 public function __construct( $flags = self::TYPE_ALL ) {
205 $this->flags = $flags;
206 }
207
208 /**
209 * Gets the categories for the check.
210 *
211 * Every check must have at least one category.
212 *
213 * @since 1.0.0
214 *
215 * @return array The categories for the check.
216 */
217 public function get_categories() {
218 return array( Check_Categories::CATEGORY_PLUGIN_REPO );
219 }
220
221 /**
222 * Check for trademarks.
223 *
224 * @since 1.0.0
225 *
226 * @param Check_Result $result The Check Result to amend.
227 * @param array $files Array of plugin files.
228 */
229 protected function check_files( Check_Result $result, array $files ) {
230
231 // Check the trademarks in readme file plugin name.
232 if ( $this->flags & self::TYPE_README ) {
233 $this->check_for_name_in_readme( $result, $files );
234 }
235
236 // Check the trademarks in plugin name.
237 if ( $this->flags & self::TYPE_NAME ) {
238 $this->check_for_name_in_main_file( $result );
239 }
240
241 // Check the trademarks in plugin slug.
242 if ( $this->flags & self::TYPE_SLUG ) {
243 $this->check_for_slug( $result );
244 }
245 }
246
247 /**
248 * Checks the trademarks in readme file plugin name.
249 *
250 * @since 1.0.0
251 *
252 * @param Check_Result $result The Check Result to amend.
253 * @param array $files Array of plugin files.
254 */
255 private function check_for_name_in_readme( Check_Result $result, array $files ) {
256 $plugin_relative_path = $result->plugin()->path();
257
258 // Filter the readme files.
259 $readme = $this->filter_files_for_readme( $files, $plugin_relative_path );
260
261 // If the readme file does not exist, then skip test.
262 if ( empty( $readme ) ) {
263 return;
264 }
265
266 $readme_file = reset( $readme );
267
268 $parser = new Parser( $readme_file );
269
270 try {
271 $this->validate_name_has_no_trademarks( $parser->name );
272 } catch ( Exception $e ) {
273 $this->add_result_warning_for_file(
274 $result,
275 $e->getMessage(),
276 'trademarked_term',
277 $readme_file
278 );
279 }
280 }
281
282 /**
283 * Checks the readme file for default text.
284 *
285 * @since 1.0.0
286 *
287 * @param Check_Result $result The Check Result to amend.
288 */
289 private function check_for_name_in_main_file( Check_Result $result ) {
290 if ( ! function_exists( 'get_plugin_data' ) ) {
291 require_once ABSPATH . 'wp-admin/includes/plugin.php';
292 }
293
294 $plugin_main_file = $result->plugin()->main_file();
295 $plugin_header = get_plugin_data( $plugin_main_file );
296
297 if ( ! empty( $plugin_header['Name'] ) ) {
298 try {
299 $this->validate_name_has_no_trademarks( $plugin_header['Name'] );
300 } catch ( Exception $e ) {
301 $this->add_result_warning_for_file(
302 $result,
303 $e->getMessage(),
304 'trademarked_term',
305 $plugin_main_file
306 );
307 }
308 }
309 }
310
311 /**
312 * Checks the readme file for default text.
313 *
314 * @since 1.0.0
315 *
316 * @param Check_Result $result The Check Result to amend.
317 */
318 private function check_for_slug( Check_Result $result ) {
319 // Check if single file plugin, then bail early.
320 if ( $result->plugin()->is_single_file_plugin() ) {
321 return;
322 }
323
324 $plugin_slug = $result->plugin()->slug();
325
326 try {
327 $this->validate_slug_has_no_trademarks( $plugin_slug );
328 } catch ( Exception $e ) {
329 $this->add_result_warning_for_file(
330 $result,
331 $e->getMessage(),
332 'trademarked_term',
333 $result->plugin()->main_file()
334 );
335 }
336 }
337
338 /**
339 * Determines if we find a trademarked term in plugin name.
340 *
341 * @since 1.0.0
342 *
343 * @param string $plugin_name The plugin name.
344 *
345 * @throws Exception Thrown if we found trademarked term in plugin name.
346 */
347 private function validate_name_has_no_trademarks( $plugin_name ) {
348 $check = $this->has_trademarked_slug( sanitize_title_with_dashes( $plugin_name ) );
349 if ( ! $check ) {
350 return;
351 }
352
353 if (
354 trim( $check, '-' ) === $check
355 && in_array( $check, self::FOR_USE_EXCEPTIONS, true )
356 ) {
357 // Trademarks that do NOT end in "-", but are within the FOR_USE_EXCEPTIONS array can be used, but only if it ends with 'for x'.
358 $message = sprintf(
359 /* translators: 1: plugin name, 2: found trademarked term */
360 __( 'The plugin name includes a restricted term. Your chosen plugin name - "%1$s" - contains the restricted term "%2$s" which cannot be used within in your plugin name, unless your plugin name ends with "for %2$s". The term must still not appear anywhere else in your name.', 'plugin-check' ),
361 esc_html( $plugin_name ),
362 esc_html( trim( $check, '-' ) )
363 );
364 } elseif (
365 trim( $check, '-' ) === $check
366 && in_array( $check, self::ALLOWED_ACRONYMS, true )
367 ) {
368 // Trademarks that are allowed to use as an acronym.
369 $message = sprintf(
370 /* translators: 1: plugin slug, 2: found trademarked term */
371 __( 'The plugin name includes a restricted term. Your plugin name - "%1$s" - contains the restricted term "%2$s" which can be used , as long as you don\'t change it to the full name. For example: You can use WP but not WordPress.', 'plugin-check' ),
372 esc_html( $plugin_name ),
373 esc_html( trim( $check, '-' ) )
374 );
375 } elseif ( trim( $check, '-' ) === $check ) {
376 // Trademarks that do NOT end in "-" indicate slug cannot contain term at all.
377 $message = sprintf(
378 /* translators: 1: plugin name, 2: found trademarked term */
379 __( 'The plugin name includes a restricted term. Your chosen plugin name - "%1$s" - contains the restricted term "%2$s" which cannot be used at all in your plugin name.', 'plugin-check' ),
380 esc_html( $plugin_name ),
381 esc_html( trim( $check, '-' ) )
382 );
383 } else {
384 // Trademarks ending in "-" indicate slug cannot BEGIN with that term.
385 $message = sprintf(
386 /* translators: 1: plugin name, 2: found trademarked term */
387 __( 'The plugin name includes a restricted term. Your chosen plugin name - "%1$s" - contains the restricted term "%2$s" and cannot be used to begin your plugin name. We disallow the use of certain terms in ways that are abused, or potentially infringe on and/or are misleading with regards to trademarks. You may use the term "%2$s" elsewhere in your plugin name, such as "... for %2$s".', 'plugin-check' ),
388 esc_html( $plugin_name ),
389 esc_html( trim( $check, '-' ) )
390 );
391 }
392
393 throw new Exception( $message );
394 }
395
396 /**
397 * Determines if we find a trademarked term in plugin slug.
398 *
399 * @since 1.0.0
400 *
401 * @param string $plugin_slug The plugin slug.
402 *
403 * @throws Exception Thrown if we found trademarked term in plugin slug.
404 */
405 private function validate_slug_has_no_trademarks( $plugin_slug ) {
406 $check = $this->has_trademarked_slug( $plugin_slug );
407 if ( ! $check ) {
408 return;
409 }
410
411 if (
412 trim( $check, '-' ) === $check
413 && in_array( $check, self::FOR_USE_EXCEPTIONS, true )
414 ) {
415 // Trademarks that do NOT end in "-", but are within the FOR_USE_EXCEPTIONS array can be used, but only if it ends with 'for x'.
416 $message = sprintf(
417 /* translators: 1: plugin slug, 2: found trademarked term */
418 __( 'The plugin slug includes a restricted term. Your plugin slug - "%1$s" - contains the restricted term "%2$s" which cannot be used within in your plugin slug, unless your plugin slug ends with "for %2$s". The term must still not appear anywhere else in your plugin slug.', 'plugin-check' ),
419 esc_html( $plugin_slug ),
420 esc_html( trim( $check, '-' ) )
421 );
422 } elseif (
423 trim( $check, '-' ) === $check
424 && in_array( $check, self::ALLOWED_ACRONYMS, true )
425 ) {
426 // Trademarks that are allowed to use with Acronym.
427 $message = sprintf(
428 /* translators: 1: plugin slug, 2: found trademarked term */
429 __( 'The plugin slug includes a restricted term. Your plugin slug - "%1$s" - contains the restricted term "%2$s" which can be used within the plugin slug, as long as you don\'t use the full name in the plugin name. For example: You can use WP but not WordPress.', 'plugin-check' ),
430 esc_html( $plugin_slug ),
431 esc_html( trim( $check, '-' ) )
432 );
433 } elseif ( trim( $check, '-' ) === $check ) {
434 // Trademarks that do NOT end in "-" indicate slug cannot contain term at all.
435 $message = sprintf(
436 /* translators: 1: plugin slug, 2: found trademarked term */
437 __( 'The plugin slug includes a restricted term. Your plugin slug - "%1$s" - contains the restricted term "%2$s" which cannot be used at all in your plugin slug.', 'plugin-check' ),
438 esc_html( $plugin_slug ),
439 esc_html( trim( $check, '-' ) )
440 );
441 } else {
442 // Trademarks ending in "-" indicate slug cannot BEGIN with that term.
443 $message = sprintf(
444 /* translators: 1: plugin slug, 2: found trademarked term */
445 __( 'The plugin slug includes a restricted term. Your plugin slug - "%1$s" - contains the restricted term "%2$s" and cannot be used to begin your plugin slug. We disallow the use of certain terms in ways that are abused, or potentially infringe on and/or are misleading with regards to trademarks. You may use the term "%2$s" elsewhere in your plugin slug, such as "... for %2$s".', 'plugin-check' ),
446 esc_html( $plugin_slug ),
447 esc_html( trim( $check, '-' ) )
448 );
449 }
450
451 throw new Exception( $message );
452 }
453
454 /**
455 * Whether the plugin uses a trademark in the slug.
456 *
457 * @since 1.0.0
458 *
459 * @param string $slug The plugin slug.
460 * @return string|false The trademark slug if found, false otherwise.
461 */
462 private function has_trademarked_slug( $slug ) {
463 // Bail early if the plugin slug not provided.
464 if ( empty( $slug ) ) {
465 return false;
466 }
467
468 $has_trademarked_slug = false;
469
470 foreach ( self::TRADEMARK_SLUGS as $trademark ) {
471 if ( str_ends_with( $trademark, '-' ) ) {
472 // Trademarks ending in "-" indicate slug cannot begin with that term.
473 if ( 0 === strpos( $slug, $trademark ) ) {
474 $has_trademarked_slug = $trademark;
475 break;
476 }
477 } elseif ( false !== strpos( $slug, $trademark ) ) {
478 // Otherwise, the term cannot appear anywhere in slug.
479
480 // check for 'for-TRADEMARK' exceptions.
481 if ( $this->is_valid_for_use_exception( $slug, $trademark ) ) {
482 // It is a valid for-use exception, try the next trademark.
483 continue;
484 }
485
486 $has_trademarked_slug = $trademark;
487 break;
488 }
489 }
490
491 // Check portmanteaus.
492 if ( ! $has_trademarked_slug ) {
493 foreach ( self::PORTMANTEAUS as $portmanteau ) {
494 if ( 0 === stripos( $slug, $portmanteau ) ) {
495 $has_trademarked_slug = $portmanteau;
496 break;
497 }
498 }
499 }
500
501 return $has_trademarked_slug;
502 }
503
504 /**
505 * Validates whether the trademark is valid with a for-use exception.
506 *
507 * @since 1.0.0
508 *
509 * @param string $slug The plugin slug.
510 * @param string $trademark The trademark term.
511 * @return bool True if the trademark is valid with a for-use exception, false otherwise.
512 */
513 private function is_valid_for_use_exception( $slug, $trademark ) {
514 if ( empty( $slug ) ) {
515 return false;
516 }
517
518 if ( ! $trademark ) {
519 return false;
520 }
521
522 if ( ! in_array( $trademark, self::FOR_USE_EXCEPTIONS, true ) ) {
523 return false;
524 }
525
526 $for_trademark = '-for-' . $trademark;
527 $for_trademark_length = strlen( $for_trademark );
528 if ( ! ( substr( $slug, -$for_trademark_length ) === $for_trademark ) ) {
529 // The slug doesn't end with 'for-TRADEMARK', so it's an invalid use.
530 return false;
531 }
532
533 /*
534 * Yes if slug ENDS with 'for-TRADEMARK'.
535 * Validate that the term still doesn't appear in another position of the slug.
536 */
537 $short_slug = substr( $slug, 0, -1 * strlen( $for_trademark ) );
538
539 // If the trademark still doesn't exist in the slug, it's OK.
540 return false === strpos( $short_slug, $trademark );
541 }
542
543 /**
544 * Gets the description for the check.
545 *
546 * Every check must have a short description explaining what the check does.
547 *
548 * @since 1.1.0
549 *
550 * @return string Description.
551 */
552 public function get_description(): string {
553 return __( 'Checks the usage of trademarks or other projects in the plugin slug.', 'plugin-check' );
554 }
555
556 /**
557 * Gets the documentation URL for the check.
558 *
559 * Every check must have a URL with further information about the check.
560 *
561 * @since 1.1.0
562 *
563 * @return string The documentation URL.
564 */
565 public function get_documentation_url(): string {
566 return __( 'https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/', 'plugin-check' );
567 }
568 }
569