Skip to content

Commit 67f8380

Browse files
committed
DB/PreparedSQLPlaceholders: add namespaced tests
This commit adds tests to the `WordPress.DB.PreparedSQLPlaceholders` sniff covering the different forms of namespaced calls (partially qualified, fully qualified, and namespace-relative using the `namespace` keyword) to the `sprintf()`, `implode()`, and `array_fill()` functions the sniff analyzes, both when nested inside `sprintf()` and in direct string concatenation. It also adds tests for method calls named `sprintf()`, `implode()`, or `array_fill()`, which should not be treated as calls to the global functions.
1 parent 81704c7 commit 67f8380

2 files changed

Lines changed: 315 additions & 6 deletions

File tree

WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc

Lines changed: 283 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ $sql = $wpdb->prepare( 'SELECT * FROM `table` WHERE id = ' . $id ); // OK - this
1414
$sql = $wpdb->prepare( "SELECT * FROM `table` WHERE id = $id" ); // OK - this will be handled by the PreparedSQL sniff.
1515
$sql = $wpdb->prepare( "SELECT * FROM `table` WHERE id = {$id['some%sing']}" ); // OK - this will be handled by the PreparedSQL sniff.
1616
$sql = $wpdb?->prepare( 'SELECT * FROM ' . $wpdb->users ); // Warning.
17-
$sql = $wpdb->prepare( "SELECT * FROM `{$wpdb->users}`" ); // Warning.
17+
$sql = $wpdb->PREPARE( "SELECT * FROM `{$wpdb->users}`" ); // Warning.
1818
$sql = $wpdb->prepare( "SELECT * FROM `{$wpdb->users}` WHERE id = $id" ); // OK - this will be handled by the PreparedSQL sniff.
1919

2020
/*
@@ -80,7 +80,7 @@ $where = $wpdb->prepare(
8080
); // OK.
8181

8282
$where = $wpdb->prepare(
83-
sprintf(
83+
\sprintf(
8484
"{$wpdb->posts}.post_type IN (%s)
8585
AND {$wpdb->posts}.post_status IN (%s)",
8686
implode( ',', array_fill( 0, count($post_types), '%s' ), ),
@@ -99,7 +99,7 @@ $where = $wpdb->prepare(
9999
); // OK.
100100

101101
$query = $wpdb->prepare(
102-
sprintf(
102+
Sprintf(
103103
'SELECT COUNT(ID)
104104
FROM `%s`
105105
WHERE ID IN (%s)
@@ -124,7 +124,7 @@ $results = $wpdb->get_results(
124124
); // OK.
125125

126126
$query = $wpdb->prepare(
127-
sprintf(
127+
\SPRINTF(
128128
'SELECT COUNT(ID)
129129
FROM `%s`
130130
WHERE ID in (%s)
@@ -382,7 +382,7 @@ $where = $wpdb->prepare(
382382
$where = $wpdb->prepare(
383383
sprintf(
384384
"{$wpdb->posts}.post_type IN (%s)",
385-
\implode( ',', array_fill( 0, count($post_types), '%s' ) )
385+
\ImplodE( ',', array_fill( 0, count($post_types), '%s' ) )
386386
),
387387
$post_types
388388
); // OK.
@@ -397,7 +397,7 @@ $where = $wpdb->prepare(
397397

398398
$where = $wpdb->prepare(
399399
"{$wpdb->posts}.post_type IN ("
400-
. implode( ',', \array_fill( 0, count($post_types), '%s' ) )
400+
. implode( ',', \Array_Fill( 0, count($post_types), '%s' ) )
401401
. ") AND {$wpdb->posts}.post_status IN ("
402402
. implode( ',', \array_fill( 0, count($post_statusses), '%s' ) )
403403
. ')',
@@ -532,3 +532,280 @@ $sql = MyNamespace\wpdb::prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND
532532
$sql = \MyNamespace\WPDB::prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s" ); // OK.
533533
$sql = namespace\wpdb::prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s" ); // Ok. The sniff should start flagging this once it can resolve relative namespaces as this test file is not namespaced.
534534
$sql = namespace\Sub\wpdb::prepare( "SELECT * FROM $wpdb->users WHERE id = %d AND user_login = %s" ); // OK.
535+
536+
/*
537+
* Safeguard correct handling of namespaced implode() calls when nested as a parameter to sprintf() (except fully
538+
* qualified calls to the global implode() function, which is already handled above).
539+
*/
540+
$where = $wpdb->prepare(
541+
sprintf(
542+
"{$wpdb->posts}.post_type IN (%s)",
543+
MyNamespace\implode( ',', array_fill( 0, count($post_types), '%s' ) )
544+
),
545+
$post_types
546+
);
547+
$where = $wpdb->prepare(
548+
sprintf(
549+
"{$wpdb->posts}.post_type IN (%s)",
550+
\MyNamespace\implode( ',', array_fill( 0, count($post_types), '%s' ) )
551+
),
552+
$post_types
553+
);
554+
$where = $wpdb->prepare( // This should NOT be flagged in the future once the sniff is able to resolve relative namespaces as this test file is not namespaced.
555+
sprintf(
556+
"{$wpdb->posts}.post_type IN (%s)",
557+
namespace\implode( ',', array_fill( 0, count($post_types), '%s' ) )
558+
),
559+
$post_types
560+
);
561+
$where = $wpdb->prepare(
562+
sprintf(
563+
"{$wpdb->posts}.post_type IN (%s)",
564+
namespace\Sub\implode( ',', array_fill( 0, count($post_types), '%s' ) )
565+
),
566+
$post_types
567+
);
568+
569+
/*
570+
* Safeguard correct handling of namespaced array_fill() calls when nested inside sprintf() (except fully qualified
571+
* calls to the global array_fill() function, which is already handled above).
572+
*/
573+
$where = $wpdb->prepare(
574+
sprintf(
575+
"{$wpdb->posts}.post_type IN (%s)",
576+
implode( ',', MyNamespace\array_fill( 0, count($post_types), '%s' ) )
577+
),
578+
$post_types
579+
);
580+
$where = $wpdb->prepare(
581+
sprintf(
582+
"{$wpdb->posts}.post_type IN (%s)",
583+
implode( ',', \MyNamespace\array_fill( 0, count($post_types), '%s' ) )
584+
),
585+
$post_types
586+
);
587+
$where = $wpdb->prepare( // This should NOT be flagged in the future once the sniff is able to resolve relative namespaces as this test file is not namespaced.
588+
sprintf(
589+
"{$wpdb->posts}.post_type IN (%s)",
590+
implode( ',', namespace\array_fill( 0, count($post_types), '%s' ) )
591+
),
592+
$post_types
593+
);
594+
$where = $wpdb->prepare(
595+
sprintf(
596+
"{$wpdb->posts}.post_type IN (%s)",
597+
implode( ',', namespace\Sub\array_fill( 0, count($post_types), '%s' ) )
598+
),
599+
$post_types
600+
);
601+
602+
/*
603+
* Safeguard correct handling of namespaced implode() calls in direct string concatenation (when NOT nested inside
604+
* sprintf()). Fully qualified calls to the global implode() are already handled above.
605+
*/
606+
$where = $wpdb->prepare(
607+
"SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_type IN ("
608+
. MyNamespace\implode( ',', array_fill( 0, count($post_types), '%s' ) )
609+
. ')',
610+
'publish'
611+
);
612+
$where = $wpdb->prepare(
613+
"SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_type IN ("
614+
. \MyNamespace\implode( ',', array_fill( 0, count($post_types), '%s' ) )
615+
. ')',
616+
'publish'
617+
);
618+
$where = $wpdb->prepare( // This should NOT be flagged in the future once the sniff is able to resolve relative namespaces as this test file is not namespaced.
619+
"SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_type IN ("
620+
. namespace\implode( ',', array_fill( 0, count($post_types), '%s' ) )
621+
. ')',
622+
'publish'
623+
);
624+
$where = $wpdb->prepare(
625+
"SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_type IN ("
626+
. namespace\Sub\implode( ',', array_fill( 0, count($post_types), '%s' ) )
627+
. ')',
628+
'publish'
629+
);
630+
631+
/*
632+
* Safeguard correct handling of namespaced array_fill() calls in direct string concatenation (when NOT nested inside
633+
* sprintf()). Fully qualified calls to the global array_fill() are already handled above.
634+
*/
635+
$where = $wpdb->prepare(
636+
"SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_type IN ("
637+
. implode( ',', MyNamespace\array_fill( 0, count($post_types), '%s' ) )
638+
. ')',
639+
'publish'
640+
);
641+
$where = $wpdb->prepare(
642+
"SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_type IN ("
643+
. implode( ',', \MyNamespace\array_fill( 0, count($post_types), '%s' ) )
644+
. ')',
645+
'publish'
646+
);
647+
$where = $wpdb->prepare( // This should NOT be flagged in the future once the sniff is able to resolve relative namespaces as this test file is not namespaced.
648+
"SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_type IN ("
649+
. implode( ',', namespace\array_fill( 0, count($post_types), '%s' ) )
650+
. ')',
651+
'publish'
652+
);
653+
$where = $wpdb->prepare(
654+
"SELECT * FROM {$wpdb->posts} WHERE post_status = %s AND post_type IN ("
655+
. implode( ',', namespace\Sub\array_fill( 0, count($post_types), '%s' ) )
656+
. ')',
657+
'publish'
658+
);
659+
660+
/*
661+
* Safeguard correct handling of all types of namespaced calls to sprintf() except fully qualified calls to the
662+
* global sprintf() function, which is already handled above.
663+
*
664+
* The calls below are currently false negatives. The sniff incorrectly treats namespaced sprintf() calls as
665+
* calls to the global sprintf() function and does not flag them.
666+
* See: https://github.com/WordPress/WordPress-Coding-Standards/issues/2720
667+
*/
668+
$where = $wpdb->prepare(
669+
MyNamespace\sprintf(
670+
"{$wpdb->posts}.post_type IN (%s)",
671+
implode( ',', array_fill( 0, count($post_types), '%s' ) )
672+
),
673+
$post_types
674+
);
675+
$where = $wpdb->prepare(
676+
\MyNamespace\sprintf(
677+
"{$wpdb->posts}.post_type IN (%s)",
678+
implode( ',', array_fill( 0, count($post_types), '%s' ) )
679+
),
680+
$post_types
681+
);
682+
$where = $wpdb->prepare(
683+
namespace\sprintf( // This should NOT be flagged in the future once the sniff is able to resolve relative namespaces as this test file is not namespaced.
684+
"{$wpdb->posts}.post_type IN (%s)",
685+
implode( ',', array_fill( 0, count($post_types), '%s' ) )
686+
),
687+
$post_types
688+
);
689+
$where = $wpdb->prepare(
690+
namespace\Sub\sprintf(
691+
"{$wpdb->posts}.post_type IN (%s)",
692+
implode( ',', array_fill( 0, count($post_types), '%s' ) )
693+
),
694+
$post_types
695+
);
696+
697+
/*
698+
* Safeguard correct handling of method calls to methods named sprintf(), implode(), or array_fill().
699+
* These should NOT be analyzed as global function calls.
700+
*/
701+
702+
// sprintf() method calls. The sprintf() calls below are currently false negatives. The sniff incorrectly treats method
703+
// calls with the same name as global functions as calls to the global functions and does not flag them.
704+
// See: https://github.com/WordPress/WordPress-Coding-Standards/issues/2720
705+
$where = $wpdb->prepare(
706+
$obj->sprintf(
707+
"{$wpdb->posts}.post_type IN (%s)",
708+
implode( ',', array_fill( 0, count($post_types), '%s' ) )
709+
),
710+
$post_types
711+
);
712+
$where = $wpdb->prepare(
713+
$obj?->sprintf(
714+
"{$wpdb->posts}.post_type IN (%s)",
715+
implode( ',', array_fill( 0, count($post_types), '%s' ) )
716+
),
717+
$post_types
718+
);
719+
$where = $wpdb->prepare(
720+
MyClass::sprintf(
721+
"{$wpdb->posts}.post_type IN (%s)",
722+
implode( ',', array_fill( 0, count($post_types), '%s' ) )
723+
),
724+
$post_types
725+
);
726+
727+
// implode() method calls nested inside sprintf().
728+
$where = $wpdb->prepare(
729+
sprintf(
730+
"{$wpdb->posts}.post_type IN (%s)",
731+
$obj->implode( ',', array_fill( 0, count($post_types), '%s' ) )
732+
),
733+
$post_types
734+
);
735+
$where = $wpdb->prepare(
736+
sprintf(
737+
"{$wpdb->posts}.post_type IN (%s)",
738+
$obj?->implode( ',', array_fill( 0, count($post_types), '%s' ) )
739+
),
740+
$post_types
741+
);
742+
$where = $wpdb->prepare(
743+
sprintf(
744+
"{$wpdb->posts}.post_type IN (%s)",
745+
MyClass::implode( ',', array_fill( 0, count($post_types), '%s' ) )
746+
),
747+
$post_types
748+
);
749+
750+
// implode() method calls in direct string concatenation.
751+
$where = $wpdb->prepare(
752+
"post_status = %s AND post_type IN ("
753+
. $obj->implode( ',', array_fill( 0, count($post_types), '%s' ) )
754+
. ')',
755+
'publish'
756+
);
757+
$where = $wpdb->prepare(
758+
"post_status = %s AND post_type IN ("
759+
. $obj?->implode( ',', array_fill( 0, count($post_types), '%s' ) )
760+
. ')',
761+
'publish'
762+
);
763+
$where = $wpdb->prepare(
764+
"post_status = %s AND post_type IN ("
765+
. MyClass::implode( ',', array_fill( 0, count($post_types), '%s' ) )
766+
. ')',
767+
'publish'
768+
);
769+
770+
// array_fill() method calls nested inside implode() which is nested inside sprintf().
771+
$where = $wpdb->prepare(
772+
sprintf(
773+
"{$wpdb->posts}.post_type IN (%s)",
774+
implode( ',', $obj->array_fill( 0, count($post_types), '%s' ) )
775+
),
776+
$post_types
777+
);
778+
$where = $wpdb->prepare(
779+
sprintf(
780+
"{$wpdb->posts}.post_type IN (%s)",
781+
implode( ',', $obj?->array_fill( 0, count($post_types), '%s' ) )
782+
),
783+
$post_types
784+
);
785+
$where = $wpdb->prepare(
786+
sprintf(
787+
"{$wpdb->posts}.post_type IN (%s)",
788+
implode( ',', MyClass::array_fill( 0, count($post_types), '%s' ) )
789+
),
790+
$post_types
791+
);
792+
793+
// array_fill() method calls nested inside implode() in direct string concatenation.
794+
$where = $wpdb->prepare(
795+
"post_status = %s AND post_type IN ("
796+
. implode( ',', $obj->array_fill( 0, count($post_types), '%s' ) )
797+
. ')',
798+
'publish'
799+
);
800+
$where = $wpdb->prepare(
801+
"post_status = %s AND post_type IN ("
802+
. implode( ',', $obj?->array_fill( 0, count($post_types), '%s' ) )
803+
. ')',
804+
'publish'
805+
);
806+
$where = $wpdb->prepare(
807+
"post_status = %s AND post_type IN ("
808+
. implode( ',', MyClass::array_fill( 0, count($post_types), '%s' ) )
809+
. ')',
810+
'publish'
811+
);

WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,38 @@ public function getWarningList() {
165165
482 => 1,
166166
490 => 1,
167167
498 => 1,
168+
169+
// Namespaced sprintf/implode/array_fill calls.
170+
540 => 1,
171+
547 => 1,
172+
554 => 1,
173+
561 => 1,
174+
573 => 1,
175+
580 => 1,
176+
587 => 1,
177+
594 => 1,
178+
606 => 1,
179+
612 => 1,
180+
618 => 1,
181+
624 => 1,
182+
635 => 1,
183+
641 => 1,
184+
647 => 1,
185+
653 => 1,
186+
187+
// Method sprintf/implode/array_fill calls.
188+
728 => 1,
189+
735 => 1,
190+
742 => 1,
191+
751 => 1,
192+
757 => 1,
193+
763 => 1,
194+
771 => 1,
195+
778 => 1,
196+
785 => 1,
197+
794 => 1,
198+
800 => 1,
199+
806 => 1,
168200
);
169201
}
170202
}

0 commit comments

Comments
 (0)