Skip to content

Commit a21e071

Browse files
committed
DB/PreparedSQLPlaceholders: add namespaced tests
Tests for namespaced calls to the wpdb class currently result in false positives: the sniff treats namespaced wpdb class calls as calls to the global wpdb class. Tests for namespaced calls to a function called sprintf() currently result in false negatives: the sniff treats these calls as calls to the global sprintf() function, causing them to not be flagged when they should be. Both issues are documented in the test case file and GitHub and will be fixed in the future.
1 parent e0ebae2 commit a21e071

2 files changed

Lines changed: 343 additions & 6 deletions

File tree

WordPress/Tests/DB/PreparedSQLPlaceholdersUnitTest.inc

Lines changed: 298 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
. ')',
@@ -518,3 +518,295 @@ $where = $wpdb->prepare(
518518
*/
519519
$callback = $wpdb->prepare(...); // OK.
520520

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

0 commit comments

Comments
 (0)