├── .github └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── composer.json ├── phpunit.xml ├── rector.php ├── src ├── DateQuery.php ├── MetaQuery.php ├── PostQuery.php ├── Query.php ├── QueryBuilder │ ├── ComparisonBuilder.php │ ├── DateQueryBuilder.php │ ├── MetaQueryBuilder.php │ ├── QueryBuilder.php │ ├── SubQuery.php │ └── TaxQueryBuilder.php ├── QueryException.php ├── TaxQuery.php ├── Traits │ ├── Author.php │ ├── Caching.php │ ├── Category.php │ ├── Comment.php │ ├── DateQuery.php │ ├── Field.php │ ├── MetaQuery.php │ ├── MimeType.php │ ├── Order.php │ ├── Pagination.php │ ├── Password.php │ ├── Permission.php │ ├── Post.php │ ├── PostType.php │ ├── Query │ │ └── SubQuery.php │ ├── Search.php │ ├── Status.php │ ├── Tag.php │ └── TaxQuery.php └── Utils │ ├── Types │ ├── BooleanType.php │ ├── CharType.php │ ├── DatetimeType.php │ ├── NullableType.php │ ├── NumberType.php │ └── ValueTypeContract.php │ └── ValueTypeDetector.php └── tests ├── Feature ├── AuthorTest.php ├── CachingTest.php ├── CategoryTest.php ├── CommentTest.php ├── DateQueryTest.php ├── FieldTest.php ├── MetaQueryTest.php ├── MimeTypeTest.php ├── OrderTest.php ├── PaginationTest.php ├── PasswordTest.php ├── PermissionTest.php ├── PostTest.php ├── SearchTest.php ├── StatusTest.php ├── TagTest.php ├── TaxQueryTest.php └── ValueTypeTest.php ├── Pest.php ├── TestCase.php └── helpers └── wp.php /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: ['push', 'pull_request'] 4 | 5 | jobs: 6 | ci: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | 13 | - name: Setup PHP 14 | uses: shivammathur/setup-php@v2 15 | with: 16 | php-version: 8.2 17 | tools: composer:v2 18 | coverage: xdebug 19 | 20 | - name: Install Dependencies 21 | run: composer install --no-interaction --prefer-dist --optimize-autoloader 22 | 23 | - name: Tests 24 | run: ./vendor/bin/pest 25 | 26 | - name: Pint 27 | run: ./vendor/bin/pint 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | .idea/codeStyleSettings.xml 3 | .temp/* 4 | composer.lock 5 | /vendor/ 6 | coverage.xml 7 | .phpunit.result.cache 8 | .phpunit.cache 9 | /.php-cs-fixer.php 10 | .php-cs-fixer.cache 11 | .temp/coverage.php 12 | *.swp 13 | *.swo 14 | .vscode/ 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PostQuery Class 2 | 3 | The `PostQuery` class is a fluent interface for constructing WP_Query objects in WordPress. This class simplifies the process of building complex queries and provides a more readable and concise way to define query parameters. 4 | 5 | - [Advantages](#advantages-of-using-postquery-wrapper) 6 | - [Installation](#installation) 7 | - [Basic Usage](#basic-usage) 8 | - [Author](#author) 9 | - [Category](#category) 10 | - [Tag](#tag) 11 | - [Tax_Query](#tax_query) 12 | - [Search](#search) 13 | - [Post](#post) 14 | - [Password](#password) 15 | - [Status](#status) 16 | - [Comment Parameter](#comment-parameter) 17 | - [Pagination](#pagination) 18 | - [Order](#order) 19 | - [Date](#date) 20 | - [Meta Query](#meta-query) 21 | - [Permission](#permission) 22 | - [Mimetype](#mimetype) 23 | - [Cache](#cache) 24 | 25 | 26 | ## Advantages of Using `PostQuery` Wrapper 27 | 28 | When using the `PostQuery` wrapper, you gain several advantages over using the original `WP_Query` class for querying posts in WordPress: 29 | 30 | 1. **Method Chaining**: The `PostQuery` wrapper allows for method chaining, making it easier to construct and read complex queries in a more fluent and readable manner. 31 | 32 | 2. **Type Safety**: `PostQuery` provides type-hinted methods, reducing the risk of runtime errors by ensuring that the correct data types are used for query parameters. 33 | 34 | 3. **Readable and Expressive**: The methods in `PostQuery` have descriptive names, making it easier to understand the purpose of each query parameter. 35 | 36 | 4. **Consistency**: `PostQuery` enforces consistent naming conventions and provides a standardized way to query posts, enhancing code maintainability. 37 | 38 | 5. **Improved Code Structure**: Using `PostQuery` promotes cleaner and more organized code, separating query logic from the rest of your application. 39 | 40 | 6. **Reduced Boilerplate**: The wrapper simplifies common tasks, reducing the amount of boilerplate code needed to create queries. 41 | 42 | 6. **IDE Autocompletion:** Unlike array-based arguments, the `PostQuery` wrapper provides autocompletion support in IDEs, which can significantly improve developer productivity and reduce coding errors. 43 | 44 | ### Comprehensive Example 45 | 46 | Let's consider an example where we want to query posts with specific criteria using the `PostQuery` wrapper and compare it to the equivalent `WP_Query` code. We'll combine various methods to construct a complex query. 47 | 48 | #### Using `PostQuery` Wrapper 49 | 50 | ```php 51 | use Pollora\Query\PostQuery; 52 | 53 | $results = PostQuery::select() 54 | ->postType('product') 55 | ->taxQuery(function (TaxQuery $query) { 56 | $query->where( 57 | $query 58 | ->taxonomy('category') 59 | ->contains(['tee-shirt', 'sportswear']) 60 | ->searchByTermSlug() 61 | ); 62 | }) 63 | ->dateQuery(function (DateQuery $query) { 64 | $query->where( 65 | $query 66 | ->date('posted_at') 67 | ->between('2021-01-01', '2022-02-01') 68 | )->orWhere( 69 | $query->date()->created()->after('2021-01-01') 70 | ); 71 | }) 72 | ->metaQuery(function (MetaQuery $query) { 73 | $query 74 | ->where( 75 | $query->meta('status')->equalTo('active') 76 | )->orWhere( 77 | $query->meta('highlighted')->equalTo(0) 78 | ); 79 | }) 80 | ->userPermission('readable') 81 | ->cacheResults() 82 | ->get(); 83 | ``` 84 | 85 | #### Equivalent `WP_Query` Code 86 | 87 | ```php 88 | new WP_Query([ 89 | 'cache_results' => true, 90 | 'date_query' => [ 91 | 'relation' => 'OR', 92 | [ 93 | 'column' => 'posted_at', 94 | 'before' => 95 | [ 96 | 'year' => '2022', 97 | 'month' => '02', 98 | 'day' => '01', 99 | ], 100 | 'after' => 101 | [ 102 | 'year' => '2021', 103 | 'month' => '01', 104 | 'day' => '01', 105 | ], 106 | ], 107 | [ 108 | 'column' => 'post_date', 109 | 'after' => 110 | [ 111 | 'year' => '2021', 112 | 'month' => '01', 113 | 'day' => '01', 114 | ], 115 | ], 116 | ], 117 | 'fields' => 'all', 118 | 'meta_query' => [ 119 | 'relation' => 'OR', 120 | [ 121 | 'key' => 'status', 122 | 'compare' => '=', 123 | 'value' => 'active', 124 | ], 125 | [ 126 | 'key' => 'highlighted', 127 | 'compare' => '=', 128 | 'type' => 'BINARY', 129 | 'value' => 0, 130 | ], 131 | ], 132 | 'perm' => 'readable', 133 | 'post_type' => 'product', 134 | 'tax_query' => [ 135 | 'relation' => 'AND', 136 | [ 137 | 'taxonomy' => 'category', 138 | 'field' => 'slug', 139 | 'terms' => 140 | [ 141 | 'tee-shirt', 142 | 'sportswear', 143 | ], 144 | 'operator' => 'IN', 145 | 'include_children' => true, 146 | ], 147 | ], 148 | ]); 149 | ``` 150 | 151 | The table of our good old Wp_Query is a little complex, isn't it? :) 152 | 153 | ## Installation 154 | 155 | To use the `PostQuery` wrapper in your WordPress project, you need to install the `pollora/query` package using Composer. 156 | 157 | If you're not using a WordPress environment that integrate composer, you can follow the steps described [in this article](https://deliciousbrains.com/storing-wordpress-in-git/). 158 | 159 | To install Pollora Query, run the following command : 160 | 161 | ```bash 162 | composer require pollora/query 163 | ``` 164 | 165 | Composer will download and install the `pollora/query` package and its dependencies. 166 | 167 | ### Using the `PostQuery` Wrapper 168 | 169 | Once you've installed the `pollora/query` package and set up Composer in your WordPress environment, you can use the `PostQuery` wrapper to construct and execute WordPress post queries as described in the previous sections. 170 | 171 | Now, you're ready to harness the power and simplicity of the `PostQuery` wrapper in your WordPress project to streamline your post queries and improve code readability and maintainability. 172 | 173 | ## Basic Usage 174 | 175 | To get started with the `PostQuery` class, you can use the `select()` method to specify the fields you want to retrieve and the `get()` method to generate a WP_Query object with the corresponding arguments. 176 | 177 | By default, the `PostQuery` class adopts a more intuitive approach when it comes to querying posts by post type. Unlike the default behavior of WordPress's `WP_Query`, which sets the `post_type` parameter to `'post'` if not specified, the `PostQuery` class assumes a broader scope. It sets the `post_type` parameter to `'all'` by default, indicating that it will search across all post types. This default behavior aligns with a more logical and inclusive approach, ensuring that you can effortlessly query posts of any type without needing to explicitly specify the post type every time you create a query. This simplifies the process and enhances flexibility when constructing your queries. 178 | 179 | ### Retrieve All Fields 180 | 181 | ```php 182 | PostQuery::select()->get(); // Equivalent to PostQuery::select('*')->get(); 183 | ``` 184 | 185 | This will generate the following WP_Query arguments: 186 | 187 | ```php 188 | new WP_Query([ 189 | 'fields' => 'all', 190 | 'post_type' => 'any', 191 | ]); 192 | ``` 193 | 194 | In this case, the `fields` argument is set to `'all'`, indicating that all fields should be retrieved. 195 | 196 | ### Specify Specific Fields 197 | 198 | You can use the `select()` method with specific field names to tailor your query to your needs. 199 | 200 | ```php 201 | PostQuery::select('id=>parent')->get(); 202 | ``` 203 | 204 | This will generate the following WP_Query arguments: 205 | 206 | ```php 207 | new WP_Query([ 208 | 'fields' => 'id=>parent', 209 | 'post_type' => 'all', 210 | ]); 211 | ``` 212 | 213 | Here, the `fields` argument is set to `'id=>parent'`, which means only the 'id' and 'parent' fields will be retrieved. 214 | 215 | ### Retrieve Only IDs 216 | 217 | To retrieve only the IDs of the posts, you can use the following code: 218 | 219 | ```php 220 | PostQuery::select('ids')->get(); 221 | ``` 222 | 223 | This will generate the following WP_Query arguments: 224 | 225 | ```php 226 | new WP_Query([ 227 | 'fields' => 'ids', 228 | 'post_type' => 'all', 229 | ]); 230 | ``` 231 | 232 | In this case, the `fields` argument is set to `'ids'`, indicating that only the post IDs will be returned. 233 | 234 | ### Find specific post id 235 | 236 | The `find($postID)` method is used to create a WP_Query instance that retrieves a specific post by its ID. This method simplifies the process of querying a single post and allows you to specify the fields you want to retrieve. 237 | 238 | To use the `find()` method, follow these steps: 239 | 240 | 1. Call the `find($postID)` method on an instance of the `PostQuery` class, passing the desired post ID as a parameter. 241 | 2. Chain other methods to further customize the query, such as `fields()`, if needed. 242 | 3. Finally, call the `get()` method to generate a WP_Query object with the specified arguments. 243 | 244 | ```php 245 | PostQuery::find(1) // you can also pass an array of post ids. 246 | ->fields('ids') 247 | ->get(); 248 | ``` 249 | 250 | This will generate the following WP_Query arguments: 251 | 252 | ```php 253 | new WP_Query([ 254 | 'p' => 1, // Retrieve the post with ID 1 255 | 'fields' => 'ids', // Retrieve only the post ID 256 | 'post_type' => 'all', 257 | ]); 258 | ``` 259 | 260 | In this example, the `find(1)` method specifies that you want to retrieve the post with ID 1, the `fields('ids')` method indicates that you only want to retrieve the post ID, and the `post_type` argument is set to `'all'`, including all post types in the query. 261 | 262 | ## Author 263 | 264 | The `PostQuery` class provides several methods for specifying the author(s) of the posts you want to retrieve. These methods allow you to filter posts based on author IDs, author usernames, and author IDs to include or exclude. 265 | 266 | ### author($authorID) 267 | 268 | The `author($authorID)` method allows you to query posts authored by a specific user using their user ID. Here's an example: 269 | 270 | ```php 271 | PostQuery::select() 272 | ->author(1) 273 | ->get(); 274 | ``` 275 | 276 | This will generate the following WP_Query arguments: 277 | 278 | ```php 279 | new WP_Query([ 280 | 'author' => 1, // Retrieve posts authored by the user with ID 1 281 | 'post_type' => 'all' // Include all post types in the query 282 | ]); 283 | ``` 284 | 285 | ### authorName($authorUsername) 286 | 287 | The `authorName($authorUsername)` method allows you to query posts authored by a specific user using their username. Here's an example: 288 | 289 | ```php 290 | PostQuery::select() 291 | ->authorName('taylor') 292 | ->get(); 293 | ``` 294 | 295 | This will generate the following WP_Query arguments: 296 | 297 | ```php 298 | new WP_Query([ 299 | 'author_name' => 'taylor', // Retrieve posts authored by the user with the username 'taylor' 300 | 'post_type' => 'all' // Include all post types in the query 301 | ]); 302 | ``` 303 | 304 | ### authorIn($authorIDs) 305 | 306 | The `authorIn($authorIDs)` method allows you to query posts authored by one or more users specified by their user IDs. You can pass an array of user IDs to this method. Here's an example: 307 | 308 | ```php 309 | PostQuery::select() 310 | ->authorIn([1, 2, 3]) 311 | ->get(); 312 | ``` 313 | 314 | This will generate the following WP_Query arguments: 315 | 316 | ```php 317 | new WP_Query([ 318 | 'author__in' => [1, 2, 3], // Retrieve posts authored by users with IDs 1, 2, or 3 319 | 'post_type' => 'all' // Include all post types in the query 320 | ]); 321 | ``` 322 | 323 | ### authorNotIn($authorIDs) 324 | 325 | The `authorNotIn($authorIDs)` method allows you to exclude posts authored by one or more users specified by their user IDs. You can pass an array of user IDs to this method. Here's an example: 326 | 327 | ```php 328 | PostQuery::select() 329 | ->authorNotIn([1, 2, 3]) 330 | ->get(); 331 | ``` 332 | 333 | This will generate the following WP_Query arguments: 334 | 335 | ```php 336 | new WP_Query([ 337 | 'author__not_in' => [1, 2, 3], // Exclude posts authored by users with IDs 1, 2, or 3 338 | 'post_type' => 'all' // Include all post types in the query 339 | ]); 340 | ``` 341 | 342 | ## Category 343 | 344 | The `PostQuery` class allows you to query posts based on categories using various methods. These methods enable you to filter posts by category ID, category name, and include or exclude categories. 345 | 346 | ### cat($categoryID) 347 | 348 | The `cat($categoryID)` method allows you to query posts belonging to a specific category by specifying its category ID. Here's an example: 349 | 350 | ```php 351 | PostQuery::select() 352 | ->cat(1) 353 | ->get(); 354 | ``` 355 | 356 | This generates the following WP_Query arguments: 357 | 358 | ```php 359 | new WP_Query([ 360 | 'cat' => 1, // Retrieve posts from category with ID 1 361 | 'post_type' => 'all', // Include all post types in the query 362 | ]); 363 | ``` 364 | 365 | ### categoryName($categoryName) 366 | 367 | The `categoryName($categoryName)` method allows you to query posts belonging to a specific category by specifying its name. Here's an example: 368 | 369 | ```php 370 | PostQuery::select() 371 | ->categoryName('sales') 372 | ->get(); 373 | ``` 374 | 375 | This generates the following WP_Query arguments: 376 | 377 | ```php 378 | new WP_Query([ 379 | 'category_name' => 'sales', // Retrieve posts from the 'sales' category 380 | 'post_type' => 'all', // Include all post types in the query 381 | ]); 382 | ``` 383 | 384 | ### categoryIn($categoryIDs) 385 | 386 | The `categoryIn($categoryIDs)` method allows you to include posts from one or more categories specified by their category IDs. You can pass an array of category IDs to this method. Here's an example: 387 | 388 | ```php 389 | PostQuery::select() 390 | ->categoryIn([1, 2, 3]) 391 | ->get(); 392 | ``` 393 | 394 | This generates the following WP_Query arguments: 395 | 396 | ```php 397 | new WP_Query([ 398 | 'category__in' => [1, 2, 3], // Include posts from categories with IDs 1, 2, or 3 399 | 'post_type' => 'all', // Include all post types in the query 400 | ]); 401 | ``` 402 | 403 | ### categoryNotIn($categoryIDs) 404 | 405 | The `categoryNotIn($categoryIDs)` method allows you to exclude posts from one or more categories specified by their category IDs. You can pass an array of category IDs to this method. Here's an example: 406 | 407 | ```php 408 | PostQuery::select() 409 | ->categoryNotIn([1, 2, 3]) 410 | ->get(); 411 | ``` 412 | 413 | This generates the following WP_Query arguments: 414 | 415 | ```php 416 | new WP_Query([ 417 | 'category__not_in' => [1, 2, 3], // Exclude posts from categories with IDs 1, 2, or 3 418 | 'post_type' => 'all', // Include all post types in the query 419 | ]); 420 | ``` 421 | 422 | ## Tag 423 | 424 | The `PostQuery` class allows you to query posts based on tags using various methods. These methods enable you to filter posts by tag name, tag ID, tag slugs, and include or exclude tags. 425 | 426 | ### tag($tagName) 427 | 428 | The `tag($tagName)` method allows you to query posts associated with a specific tag by specifying its name. Here's an example: 429 | 430 | ```php 431 | PostQuery::select() 432 | ->tag('programming') 433 | ->get(); 434 | ``` 435 | 436 | This generates the following WP_Query arguments: 437 | 438 | ```php 439 | new WP_Query([ 440 | 'tag' => 'programming', // Retrieve posts associated with the 'programming' tag 441 | 'post_type' => 'all', // Include all post types in the query 442 | ]); 443 | ``` 444 | 445 | ### tagId($tagID) 446 | 447 | The `tagId($tagID)` method allows you to query posts associated with a specific tag by specifying its ID. Here's an example: 448 | 449 | ```php 450 | PostQuery::select() 451 | ->tagId(1) 452 | ->get(); 453 | ``` 454 | 455 | This generates the following WP_Query arguments: 456 | 457 | ```php 458 | new WP_Query([ 459 | 'tag_id' => 1, // Retrieve posts associated with the tag with ID 1 460 | 'post_type' => 'all', // Include all post types in the query 461 | ]); 462 | ``` 463 | 464 | ### tagAnd($tagIDs) 465 | 466 | The `tagAnd($tagIDs)` method allows you to query posts that are associated with all of the specified tags by specifying their IDs. You can pass an array of tag IDs to this method. Here's an example: 467 | 468 | ```php 469 | PostQuery::select() 470 | ->tagAnd([1, 2]) 471 | ->get(); 472 | ``` 473 | 474 | This generates the following WP_Query arguments: 475 | 476 | ```php 477 | new WP_Query([ 478 | 'tag__and' => [1, 2], // Retrieve posts associated with both tags with IDs 1 and 2 479 | 'post_type' => 'all', // Include all post types in the query 480 | ]); 481 | ``` 482 | 483 | ### tagIn($tagIDs) 484 | 485 | The `tagIn($tagIDs)` method allows you to query posts associated with one or more tags by specifying their IDs. You can pass an array of tag IDs to this method. Here's an example: 486 | 487 | ```php 488 | PostQuery::select() 489 | ->tagIn([3, 4]) 490 | ->get(); 491 | ``` 492 | 493 | This generates the following WP_Query arguments: 494 | 495 | ```php 496 | new WP_Query([ 497 | 'tag__in' => [3, 4], // Retrieve posts associated with tags with IDs 3 or 4 498 | 'post_type' => 'all', // Include all post types in the query 499 | ]); 500 | ``` 501 | 502 | ### tagNotIn($tagIDs) 503 | 504 | The `tagNotIn($tagIDs)` method allows you to exclude posts associated with one or more tags by specifying their IDs. You can pass an array of tag IDs to this method. Here's an example: 505 | 506 | ```php 507 | PostQuery::select() 508 | ->tagNotIn([5, 6]) 509 | ->get(); 510 | ``` 511 | 512 | This generates the following WP_Query arguments: 513 | 514 | ```php 515 | new WP_Query([ 516 | 'tag__not_in' => [5, 6], // Exclude posts associated with tags with IDs 5 or 6 517 | 'post_type' => 'all', // Include all post types in the query 518 | ]); 519 | ``` 520 | 521 | ### tagSlugAnd($tagSlugs) 522 | 523 | The `tagSlugAnd($tagSlugs)` method allows you to query posts that are associated with all of the specified tags by specifying their slugs. You can pass an array of tag slugs to this method. Here's an example: 524 | 525 | ```php 526 | PostQuery::select() 527 | ->tagSlugAnd(['dev', 'qa']) 528 | ->get(); 529 | ``` 530 | 531 | This generates the following WP_Query arguments: 532 | 533 | ```php 534 | new WP_Query([ 535 | 'tag_slug__and' => ['dev', 'qa'], // Retrieve posts associated with both 'dev' and 'qa' tags 536 | 'post_type' => 'all', // Include all post types in the query 537 | ]); 538 | ``` 539 | 540 | ### tagSlugIn($tagSlugs) 541 | 542 | The `tagSlugIn($tagSlugs)` method allows you to query posts associated with one or more tags by specifying their slugs. You can pass an array of tag slugs to this method. Here's an example: 543 | 544 | ```php 545 | PostQuery::select() 546 | ->tagSlugIn(['frontend', 'backend']) 547 | ->get(); 548 | ``` 549 | 550 | This generates the following WP_Query arguments: 551 | 552 | ```php 553 | new WP_Query([ 554 | 'tag_slug__in' => ['frontend', 'backend'], // Retrieve posts associated with 'frontend' or 'backend' tags 555 | 'post_type' => 'all', // Include all post types in the query 556 | ]); 557 | ``` 558 | 559 | ## Tax_Query 560 | 561 | The `PostQuery` class allows you to filter posts based on taxonomy terms using the `taxQuery()` method. This method provides a powerful way to construct complex queries involving taxonomy terms and their relationships. 562 | 563 | ### Simple tax query 564 | 565 | In its simplest form, you can use the `taxQuery()` method to filter posts by a single taxonomy and a list of terms. For example: 566 | 567 | ```php 568 | PostQuery::select() 569 | ->taxQuery(function (TaxQuery $query) { 570 | $query->where( 571 | $query 572 | ->taxonomy('category') 573 | ->contains(['tee-shirt', 'sportswear']) 574 | ->searchByTermSlug() 575 | ); 576 | }) 577 | ->get(); 578 | ``` 579 | 580 | This generates the following WP_Query arguments: 581 | 582 | ```php 583 | new WP_Query([ 584 | 'post_type' => 'all', 585 | 'tax_query' => [ 586 | 'relation' => 'AND', 587 | [ 588 | 'taxonomy' => 'category', 589 | 'field' => 'slug', 590 | 'terms' => ['tee-shirt', 'sportswear'], 591 | 'operator' => 'IN', 592 | 'include_children' => true, 593 | ], 594 | ], 595 | ]); 596 | ``` 597 | 598 | ### 'OR' Relation 599 | 600 | You can also use the 'OR' relation to query posts that match any of the specified taxonomy conditions. For example: 601 | 602 | ```php 603 | PostQuery::select() 604 | ->taxQuery(function (TaxQuery $query) { 605 | $query->where( 606 | $query 607 | ->taxonomy('category') 608 | ->contains([10, 20]) 609 | ->searchById() 610 | )->orWhere( 611 | $query 612 | ->taxonomy('event') 613 | ->contains(['black-friday', 'christmas-sales']) 614 | ->searchByTermSlug() 615 | ); 616 | }) 617 | ->get(); 618 | ``` 619 | 620 | This generates the following WP_Query arguments: 621 | 622 | ```php 623 | new WP_Query([ 624 | 'post_type' => 'all', 625 | 'tax_query' => [ 626 | 'relation' => 'OR', 627 | [ 628 | 'taxonomy' => 'category', 629 | 'field' => 'term_id', 630 | 'terms' => [10, 20], 631 | 'operator' => 'IN', 632 | 'include_children' => true, 633 | ], 634 | [ 635 | 'taxonomy' => 'event', 636 | 'field' => 'slug', 637 | 'terms' => ['black-friday', 'christmas-sales'], 638 | 'operator' => 'IN', 639 | 'include_children' => true, 640 | ], 641 | ], 642 | ]); 643 | ``` 644 | 645 | ### Nested Conditions 646 | 647 | You can also create nested conditions within the Tax_Query. In the following example, we use nested conditions with 'OR' and 'AND' relations: 648 | 649 | ```php 650 | PostQuery::select() 651 | ->taxQuery(function (TaxQuery $query) { 652 | $query 653 | ->where( 654 | $query->taxonomy('status')->notContains(['private']) 655 | ) 656 | ->orWhere(function (TaxQuery $query) { 657 | $query 658 | ->where( 659 | $query->taxonomy('attributes')->exists() 660 | ) 661 | ->orWhere( 662 | $query->taxonomy('product_cat')->contains(['tee-shirt', 'sportswear']) 663 | ); 664 | }) 665 | ->orWhere(function (TaxQuery $query) { 666 | $query 667 | ->where( 668 | $query->taxonomy('promo_cat')->contains(['summer', 'blackfriday']) 669 | ) 670 | ->andWhere( 671 | $query->taxonomy('new_products')->notExists() 672 | ); 673 | }); 674 | }) 675 | ->get(); 676 | ``` 677 | 678 | This generates the following WP_Query arguments: 679 | 680 | ```php 681 | new WP_Query([ 682 | 'post_type' => 'all', 683 | 'tax_query' => [ 684 | 'relation' => 'OR', 685 | [ 686 | 'taxonomy' => 'status', 687 | 'field' => 'term_id', 688 | 'terms' => [ 689 | 'private', 690 | ], 691 | 'operator' => 'NOT IN', 692 | 'include_children' => true, 693 | ], 694 | [ 695 | 'relation' => 'OR', 696 | [ 697 | 'taxonomy' => 'attributes', 698 | 'field' => 'term_id', 699 | 'terms' => null, 700 | 'operator' => 'EXISTS', 701 | 'include_children' => true, 702 | ], 703 | [ 704 | 'taxonomy' => 'product_cat', 705 | 'field' => 'term_id', 706 | 'terms' => [ 707 | 'tee-shirt', 708 | 'sportswear', 709 | ], 710 | 'operator' => 'IN', 711 | 'include_children' => true, 712 | ], 713 | ], 714 | [ 715 | 'relation' => 'AND', 716 | [ 717 | 'taxonomy' => 'promo_cat', 718 | 'field' => 'term_id', 719 | 'terms' => [ 720 | 'summer', 721 | 'blackfriday', 722 | ], 723 | 'operator' => 'IN', 724 | 'include_children' => true, 725 | ], 726 | [ 727 | 'taxonomy' => 'new_products', 728 | 'field' => 'term_id', 729 | 'terms' => null, 730 | 'operator' => 'NOT EXISTS', 731 | 'include_children' => true, 732 | ], 733 | ], 734 | ], 735 | ]) 736 | ``` 737 | 738 | ## Search 739 | 740 | The `PostQuery` class allows you to perform keyword-based searches on posts using the `search()` method. This method helps you filter posts that match a specific keyword or phrase. 741 | 742 | ### search($keyword) 743 | 744 | The `search($keyword)` method allows you to perform a keyword-based search for posts. You can specify the desired keyword or phrase as a parameter. Here's an example: 745 | 746 | ```php 747 | PostQuery::select() 748 | ->search('my keyword') 749 | ->get(); 750 | ``` 751 | 752 | This generates the following WP_Query argument: 753 | 754 | ```php 755 | new WP_Query([ 756 | 's' => 'my keyword', // Perform a keyword-based search for posts containing 'my keyword' 757 | 'post_type' => 'all', 758 | ]); 759 | ``` 760 | 761 | ## Post 762 | 763 | The `PostQuery` class provides methods for filtering posts based on various post-related parameters. These methods allow you to specify the post type, post ID, post slug, post parent, and more. 764 | 765 | ### postType($type) 766 | 767 | The `postType($type)` method allows you to specify the post type you want to query. You can provide the post type as a parameter. Here's an example: 768 | 769 | ```php 770 | PostQuery::select() 771 | ->postType('article') 772 | ->get(); 773 | ``` 774 | 775 | This generates the following WP_Query argument: 776 | 777 | ```php 778 | new WP_Query([ 779 | 'post_type' => 'article', // Retrieve posts of the 'article' post type 780 | ]); 781 | ``` 782 | 783 | ### postId($id) 784 | 785 | The `postId($id)` method allows you to query a specific post by its ID. You can provide the post ID as a parameter. For example: 786 | 787 | ```php 788 | PostQuery::select() 789 | ->postId(42) 790 | ->get(); 791 | ``` 792 | 793 | This generates the following WP_Query argument: 794 | 795 | ```php 796 | new WP_Query([ 797 | 'p' => 42, // Retrieve the post with ID 42 798 | 'post_type' => 'all' 799 | ]); 800 | ``` 801 | 802 | ### postSlug($slug) 803 | 804 | The `postSlug($slug)` method allows you to query a specific post by its slug. You can provide the post slug as a parameter. For example: 805 | 806 | ```php 807 | PostQuery::select() 808 | ->postSlug('mon-article') 809 | ->get(); 810 | ``` 811 | 812 | This generates the following WP_Query argument: 813 | 814 | ```php 815 | new WP_Query([ 816 | 'name' => 'mon-article', // Retrieve the post with the slug 'mon-article' 817 | 'post_type' => 'all' 818 | ]); 819 | 820 | ``` 821 | 822 | ### postParent($parentID) 823 | 824 | The `postParent($parentID)` method allows you to query posts that have a specific parent post. You can provide the parent post ID as a parameter. For example: 825 | 826 | ```php 827 | PostQuery::select() 828 | ->postParent(5) 829 | ->get(); 830 | ``` 831 | 832 | This generates the following WP_Query argument: 833 | 834 | ```php 835 | new WP_Query([ 836 | 'post_parent' => 5, // Retrieve posts with a parent post of ID 5 837 | 'post_type' => 'all', 838 | ]); 839 | ``` 840 | 841 | ### whereParentIn($parentIDs) 842 | 843 | The `whereParentIn($parentIDs)` method allows you to query posts that have parent posts specified by their IDs. You can pass an array of parent post IDs to this method. For example: 844 | 845 | ```php 846 | PostQuery::select() 847 | ->whereParentIn([1, 2, 3]) 848 | ->get(); 849 | ``` 850 | 851 | This generates the following WP_Query argument: 852 | 853 | ```php 854 | new WP_Query([ 855 | 'post_parent__in' => [1, 2, 3], // Retrieve posts with parent post IDs in the array 856 | 'post_type' => 'all' 857 | ]); 858 | ``` 859 | 860 | ### whereParentNotIn($parentIDs) 861 | 862 | The `whereParentNotIn($parentIDs)` method allows you to exclude posts that have parent posts specified by their IDs. You can pass an array of parent post IDs to this method. For example: 863 | 864 | ```php 865 | PostQuery::select() 866 | ->whereParentNotIn([4, 5, 6]) 867 | ->get(); 868 | ``` 869 | 870 | This generates the following WP_Query argument: 871 | 872 | ```php 873 | new WP_Query([ 874 | 'post_parent__not_in' => [4, 5, 6], // Exclude posts with parent post IDs in the array 875 | 'post_type' => 'all' 876 | ]); 877 | ``` 878 | 879 | ### whereIn($postIDs) 880 | 881 | The `whereIn($postIDs)` method allows you to query posts with specific IDs. You can pass an array of post IDs to this method. For example: 882 | 883 | ```php 884 | PostQuery::select() 885 | ->whereIn([7, 8, 9]) 886 | ->get(); 887 | ``` 888 | 889 | This generates the following WP_Query argument: 890 | 891 | ```php 892 | new WP_Query([ 893 | 'post__in' => [7, 8, 9], // Retrieve posts with IDs in the array 894 | 'post_type' => 'all' 895 | ]); 896 | ``` 897 | 898 | ### whereNotIn($postIDs) 899 | 900 | The `whereNotIn($postIDs)` method allows you to exclude posts with specific IDs. You can pass an array of post IDs to this method. For example: 901 | 902 | ```php 903 | PostQuery::select() 904 | ->whereNotIn([10, 11, 12]) 905 | ->get(); 906 | ``` 907 | 908 | This generates the following WP_Query argument: 909 | 910 | ```php 911 | new WP_Query([ 912 | 'post__not_in' => [10, 11, 12], // Exclude posts with IDs in the array 913 | 'post_type' => 'all' 914 | ]); 915 | ``` 916 | 917 | ### slugIn($slugs) 918 | 919 | The `slugIn($slugs)` method allows you to query posts with specific slugs. You can pass an array of post slugs to this method. For example: 920 | 921 | ```php 922 | PostQuery::select() 923 | ->slugIn(['slug-1', 'slug-2']) 924 | ->get(); 925 | ``` 926 | 927 | This generates the following WP_Query argument: 928 | 929 | ```php 930 | new WP_Query([ 931 | 'post_name__in' => ['slug-1', 'slug-2'], // Retrieve posts with slugs in the array 932 | 'post_type' => 'all' 933 | ]); 934 | ``` 935 | 936 | ### Note: 'pagename' and 'page_id' 937 | 938 | Unlike WP_Query, the `PostQuery` class does not provide separate methods for 'pagename' and 'page_id' parameters. Instead, you can use 'name' and 'p' parameters while specifying the 'post_type' as 'page' to achieve the equivalent results: 939 | 940 | ```php 941 | PostQuery::select() 942 | ->postType('page') 943 | ->postSlug('my-page') 944 | ->get(); 945 | ``` 946 | 947 | This generates the following WP_Query argument: 948 | 949 | ```php 950 | new WP_Query([ 951 | 'post_type' => 'page', // Query for pages 952 | 'name' => 'my-page', // Specify the page slug 953 | 'post_type' => 'all' 954 | ]); 955 | ``` 956 | 957 | ## Password 958 | 959 | The `PostQuery` class provides methods to filter posts based on their password protection status and the specific post password. 960 | 961 | ### withPassword() 962 | 963 | The `withPassword()` method allows you to query posts that have a password protection set. You can use this method to retrieve posts that require a password to access. For example: 964 | 965 | ```php 966 | PostQuery::select() 967 | ->withPassword() 968 | ->get(); 969 | ``` 970 | 971 | This generates the following WP_Query argument: 972 | 973 | ```php 974 | new WP_Query([ 975 | 'has_password' => true, // Retrieve posts with password protection 976 | 'post_type' => 'all' 977 | ]); 978 | ``` 979 | 980 | ### withoutPassword() 981 | 982 | The `withoutPassword()` method allows you to query posts that do not have a password protection set. You can use this method to exclude posts that require a password to access. For example: 983 | 984 | ```php 985 | PostQuery::select() 986 | ->withoutPassword() 987 | ->get(); 988 | ``` 989 | 990 | This generates the following WP_Query argument: 991 | 992 | ```php 993 | new WP_Query([ 994 | 'has_password' => false, // Exclude posts with password protection 995 | 'post_type' => 'all' 996 | ]); 997 | ``` 998 | 999 | ### withPassword($password) 1000 | 1001 | The `withPassword($password)` method allows you to query posts that have a specific post password set. You can provide the desired post password as a parameter. For example: 1002 | 1003 | ```php 1004 | PostQuery::select() 1005 | ->withPassword('zxcvbn') 1006 | ->get(); 1007 | ``` 1008 | 1009 | This generates the following WP_Query argument: 1010 | 1011 | ```php 1012 | new WP_Query([ 1013 | 'post_password' => 'zxcvbn', // Retrieve posts with the specified post password 1014 | 'post_type' => 'all' 1015 | ]); 1016 | ``` 1017 | 1018 | ## Status 1019 | 1020 | The `PostQuery` class allows you to filter posts based on their status using the `postStatus()` method. This method helps you query posts with a specific status. 1021 | 1022 | ### postStatus($status) 1023 | 1024 | The `postStatus($status)` method allows you to specify the status of posts you want to retrieve. You can provide the desired status as a parameter. For example: 1025 | 1026 | ```php 1027 | PostQuery::select() 1028 | ->postStatus('publish') 1029 | ->get(); 1030 | ``` 1031 | 1032 | This generates the following WP_Query argument: 1033 | 1034 | ```php 1035 | new WP_Query([ 1036 | 'post_status' => 'publish', // Retrieve posts with the 'publish' status 1037 | 'post_type' => 'all' 1038 | ]); 1039 | ``` 1040 | 1041 | You can use this method to query posts with different statuses such as 'publish,' 'draft,' 'pending,' or custom statuses defined in your WordPress installation. It provides precise control over the status of the posts you retrieve in your queries. 1042 | 1043 | ## Comment Parameter 1044 | 1045 | The `PostQuery` class provides a method to filter posts based on the number of comments they have using the `commentCount()` method. This method allows you to retrieve posts with a specific number of comments. 1046 | 1047 | ### commentCount($count) 1048 | 1049 | The `commentCount($count)` method allows you to specify the number of comments a post should have in order to be retrieved in the query. You can provide the desired comment count as a parameter. For example: 1050 | 1051 | ```php 1052 | PostQuery::select() 1053 | ->commentCount(1) 1054 | ->get(); 1055 | ``` 1056 | 1057 | This generates the following WP_Query argument: 1058 | 1059 | ```php 1060 | new WP_Query([ 1061 | 'comment_count' => 1, // Retrieve posts with exactly 1 comment 1062 | ]); 1063 | ``` 1064 | 1065 | ## Pagination 1066 | 1067 | The `PostQuery` class provides methods to control the pagination of query results. These methods allow you to specify the number of posts to retrieve per page, skip a certain number of posts, and more. 1068 | 1069 | ### take($count), limit($count) or postsPerPage($count) 1070 | 1071 | The `take($count)`, `limit($count)`, and `postsPerPage($count)` methods allow you to specify the number of posts to retrieve per page. You can provide the desired post count as a parameter. For example: 1072 | 1073 | ```php 1074 | PostQuery::select() 1075 | ->take(5) 1076 | ->get(); 1077 | ``` 1078 | 1079 | These methods generate the following WP_Query argument: 1080 | 1081 | ```php 1082 | new WP_Query([ 1083 | 'posts_per_page' => 5, // Retrieve 5 posts per page 1084 | ]); 1085 | ``` 1086 | 1087 | You can use these methods to control the number of posts displayed on each page of your query results. 1088 | 1089 | ### skip($count) or offset($count) 1090 | 1091 | The `skip($count)` and `offset($count)` methods allow you to skip a certain number of posts in the query results. You can provide the desired skip count as a parameter. For example: 1092 | 1093 | ```php 1094 | PostQuery::select() 1095 | ->skip(5) 1096 | ->get(); 1097 | ``` 1098 | 1099 | These methods generate the following WP_Query argument: 1100 | 1101 | ```php 1102 | new WP_Query([ 1103 | 'offset' => 5, // Skip the first 5 posts in the query results 1104 | ]); 1105 | ``` 1106 | 1107 | You can use these methods to skip a specific number of posts, useful for creating paginated queries. 1108 | 1109 | ### noPaging() 1110 | 1111 | The `noPaging()` method allows you to disable pagination entirely, retrieving all posts matching the query without any pagination. For example: 1112 | 1113 | ```php 1114 | PostQuery::select() 1115 | ->noPaging() 1116 | ->get(); 1117 | ``` 1118 | 1119 | This generates the following WP_Query argument: 1120 | 1121 | ```php 1122 | new WP_Query([ 1123 | 'nopaging' => true, // Retrieve all posts without pagination 1124 | ]); 1125 | ``` 1126 | 1127 | Use this method when you want to retrieve all matching posts in a single query, regardless of the number. 1128 | 1129 | ### postsPerArchivePage($count) 1130 | 1131 | The `postsPerArchivePage($count)` method allows you to specify the number of posts to display per archive page. This is particularly useful for archive pages like category or tag archives. For example: 1132 | 1133 | ```php 1134 | PostQuery::select() 1135 | ->postsPerArchivePage(5) 1136 | ->get(); 1137 | ``` 1138 | 1139 | This generates the following WP_Query argument: 1140 | 1141 | ```php 1142 | new WP_Query([ 1143 | 'posts_per_archive_page' => 5, // Display 5 posts per archive page 1144 | ]); 1145 | ``` 1146 | 1147 | You can use this method to control the number of posts displayed on archive pages. 1148 | 1149 | ### page($pageNumber) 1150 | 1151 | The `page($pageNumber)` method allows you to specify the page number when paginating query results. You can provide the desired page number as a parameter. For example: 1152 | 1153 | ```php 1154 | PostQuery::select() 1155 | ->page(666) 1156 | ->get(); 1157 | ``` 1158 | 1159 | This generates the following WP_Query argument: 1160 | 1161 | ```php 1162 | new WP_Query([ 1163 | 'page' => 666, // Retrieve results for page number 666 1164 | ]); 1165 | ``` 1166 | 1167 | Use this method when you want to retrieve a specific page of results in a paginated query. 1168 | 1169 | ### ignoreStickyPosts() 1170 | 1171 | The `ignoreStickyPosts()` method allows you to exclude sticky posts from the query results. Sticky posts are posts that are pinned to the top of the list. For example: 1172 | 1173 | ```php 1174 | PostQuery::select() 1175 | ->ignoreStickyPosts() 1176 | ->get(); 1177 | ``` 1178 | 1179 | This generates the following WP_Query argument: 1180 | 1181 | ```php 1182 | new WP_Query([ 1183 | 'ignore_sticky_posts' => true, // Exclude sticky posts from the query 1184 | ]); 1185 | ``` 1186 | 1187 | ## Order 1188 | 1189 | The `PostQuery` class provides methods to control the order in which posts are retrieved. You can specify one or more orderby parameters to sort the query results based on various criteria. 1190 | 1191 | ### orderBy($field, $order = 'DESC') 1192 | 1193 | The `orderBy($field, $order = 'DESC')` method allows you to specify the field by which the posts should be ordered and the order in which they should be sorted. You can provide the desired field and order as parameters. For example: 1194 | 1195 | ```php 1196 | PostQuery::select() 1197 | ->orderBy('most_comments') 1198 | ->orderBy('post_date', 'ASC') 1199 | ->get(); 1200 | ``` 1201 | 1202 | This method generates the following WP_Query argument: 1203 | 1204 | ```php 1205 | new WP_Query([ 1206 | 'orderby' => [ 1207 | 'most_comments' => 'DESC', // Order by 'most_comments' in descending order 1208 | 'post_date' => 'ASC', // Then order by 'post_date' in ascending order 1209 | ], 1210 | ]); 1211 | ``` 1212 | 1213 | ## Date 1214 | 1215 | The `PostQuery` class allows you to query posts based on date-related criteria using the `DateQuery` class. This class provides methods to create complex date queries, allowing you to filter posts by their publication, modification, or custom dates. 1216 | 1217 | ### `dateQuery($callback)` 1218 | 1219 | The `dateQuery($callback)` method allows you to define a complex date query using the `DateQuery` class. You can pass a callback function to create the date query conditions. For example: 1220 | 1221 | ```php 1222 | PostQuery::select()->dateQuery(function (DateQuery $query) { 1223 | $query 1224 | ->where( 1225 | $query->date('edited_at')->before('2022-01-01')->after([ 1226 | 'year' => '2021', 1227 | 'month' => '01', 1228 | 'day' => '01', 1229 | ]) 1230 | ) 1231 | ->inclusive(); 1232 | })->get(); 1233 | ``` 1234 | 1235 | This method generates the following WP_Query argument: 1236 | 1237 | ```php 1238 | new WP_Query([ 1239 | 'date_query' => [ 1240 | 'relation' => 'AND', 1241 | [ 1242 | 'column' => 'edited_at', 1243 | 'before' => [ 1244 | 'year' => 2022, 1245 | 'month' => 1, 1246 | 'day' => 1, 1247 | 'hour' => 12, 1248 | 'minute' => 0, 1249 | 'second' => 0, 1250 | ], 1251 | 'after' => [ 1252 | 'year' => 2021, 1253 | 'month' => 1, 1254 | 'day' => 1, 1255 | ], 1256 | ], 1257 | 'inclusive' => true, 1258 | ], 1259 | ]); 1260 | ``` 1261 | 1262 | You can use this method to create complex date queries that include conditions like before, after, and inclusive/exclusive settings. 1263 | 1264 | ### `date($column = 'post_date')` 1265 | 1266 | The `date($column = 'post_date')` method within the `DateQuery` class allows you to specify the column or type of date to query. You can choose from 'post_date,' 'post_modified,' or other custom date columns. For example: 1267 | 1268 | ```php 1269 | $query->date('edited_at'); 1270 | ``` 1271 | 1272 | ### `before($date)` 1273 | 1274 | The `before($date)` method within the `DateQuery` class allows you to specify that the date should be before a certain point in time. You can provide the date in various formats, such as 'YYYY-MM-DD' or an array specifying 'year,' 'month,' 'day,' 'hour,' 'minute,' and 'second.' 1275 | 1276 | ### `after($date)` 1277 | 1278 | The `after($date)` method within the `DateQuery` class allows you to specify that the date should be after a certain point in time. Like the `before()` method, you can provide the date in various formats. 1279 | 1280 | ### `between($start, $end)` 1281 | 1282 | The `between($start, $end)` method within the `DateQuery` class allows you to specify that the date should be between two points in time. You can provide the start and end dates in various formats. 1283 | 1284 | ### `created()` 1285 | 1286 | The `modified()` method within the `DateQuery` class allows you to specify that the date query should apply to the last modified date of the post, as opposed to the default 'post_date' column. This is useful for distinguishing between post creation and modification dates. 1287 | 1288 | ### `inclusive()` 1289 | 1290 | The `inclusive()` method within the `DateQuery` class allows you to specify that the date query should include posts that match the exact date and time specified. This is particularly useful when using the `before()` and `after()` methods. 1291 | 1292 | ## Meta Query 1293 | 1294 | The `PostQuery` class allows you to perform custom queries based on post metadata using the `MetaQuery` class. This class provides methods to create complex meta queries, allowing you to filter posts based on custom field values. 1295 | 1296 | ### `metaQuery($callback)` 1297 | 1298 | The `metaQuery($callback)` method allows you to define a complex meta query using the `MetaQuery` class. You can pass a callback function to create the meta query conditions. For example: 1299 | 1300 | ```php 1301 | PostQuery::select()->metaQuery(function (MetaQuery $query) { 1302 | $query->where( 1303 | $query->meta('status')->equalTo('active') 1304 | ); 1305 | })->get(); 1306 | ``` 1307 | 1308 | This method generates the following WP_Query argument: 1309 | 1310 | ```php 1311 | new WP_Query([ 1312 | 'meta_query' => [ 1313 | 'relation' => 'AND', 1314 | [ 1315 | 'key' => 'status', 1316 | 'compare' => '=', 1317 | 'type' => 'CHAR', 1318 | 'value' => 'active', 1319 | ], 1320 | ], 1321 | ]); 1322 | ``` 1323 | 1324 | ### meta($key) 1325 | 1326 | The `meta($key)` method within the `MetaQuery` class allows you to specify the custom field key you want to query. For example: 1327 | 1328 | ### Comparison Methods 1329 | 1330 | The `MetaQuery` class offers several methods to specify the comparison operations to perform on custom fields. You can chain these methods to build complex conditions. 1331 | 1332 | #### ofType($type) 1333 | 1334 | This method allows you to specify the type of comparison to use. Possible types are class constants such as `MetaQueryBuilder::NUMERIC`, `MetaQueryBuilder::CHAR`, `MetaQueryBuilder::DATE`, etc. By default, the type is automatically determined based on the value provided during comparison. 1335 | 1336 | Example of usage: 1337 | 1338 | ```php 1339 | $query->meta('my_post_meta')->ofType('numeric'); // You can also use a number 1340 | ``` 1341 | 1342 | ##### Note : 1343 | 1344 | The `detectValueType` method is used internally to automatically determine the data type of the value provided during comparison. It is called automatically when you use other comparison methods. 1345 | 1346 | This method performs the following checks to determine the data type: 1347 | 1348 | - If the value is an array, it takes the data type of the first element. 1349 | - If a data type is explicitly set using the `ofType` method, it uses that data type. 1350 | - If the value is `null`, it defaults to the `CHAR` data type. 1351 | - If the value is `0` or `1`, it uses the `BINARY` data type. 1352 | - If the value is a negative integer, it uses the `SIGNED` data type. 1353 | - If the value is a non-negative integer, it uses the `UNSIGNED` data type. 1354 | - If the value is a floating-point number, it uses the `DECIMAL` data type. 1355 | - If the value is a string containing only a numeric date (e.g., '2022-01-01'), it uses the `DATE` data type. 1356 | - If the value is a string containing only a numeric time (e.g., '12:00:00'), it uses the `TIME` data type. 1357 | - If the value is a string containing both date and time (e.g., '2022-01-01 12:00:00'), it uses the `DATETIME` data type. 1358 | - If none of the above conditions apply, it defaults to the `CHAR` data type. 1359 | 1360 | #### equalTo($value) 1361 | 1362 | This method specifies that the custom field must be equal to a certain value. 1363 | 1364 | Example of usage: 1365 | 1366 | ```php 1367 | $query->meta('my_post_meta')->equalTo('active'); // You can also use a number 1368 | ``` 1369 | 1370 | #### notEqualTo(mixed $value) 1371 | 1372 | This method specifies that the custom field must not be equal to a certain value. 1373 | 1374 | Example of usage: 1375 | 1376 | ```php 1377 | $query->meta('my_post_meta')->notEqualTo('inactive'); 1378 | ``` 1379 | 1380 | #### greaterThan($value) 1381 | 1382 | This method specifies that the custom field must be greater than a certain value. 1383 | 1384 | Example of usage: 1385 | 1386 | ```php 1387 | $query->meta('my_post_meta')->greaterThan(100); 1388 | ``` 1389 | 1390 | #### greaterOrEqualTo($value) 1391 | 1392 | This method specifies that the custom field must be greater than or equal to a certain value. 1393 | 1394 | Example of usage: 1395 | 1396 | ```php 1397 | $query->meta('my_post_meta')->greaterOrEqualTo(100); 1398 | ``` 1399 | 1400 | #### lessThan($value) 1401 | 1402 | This method specifies that the custom field must be less than a certain value. 1403 | 1404 | Example of usage: 1405 | 1406 | ```php 1407 | $query->meta('my_post_meta')->lessThan(5); 1408 | ``` 1409 | 1410 | #### lessOrEqualTo($value) 1411 | 1412 | This method specifies that the custom field must be less than or equal to a certain value. 1413 | 1414 | Example of usage: 1415 | 1416 | ```php 1417 | $query->meta('my_post_meta')->lessOrEqualTo(5); 1418 | ``` 1419 | 1420 | #### `between(mixed $lowerBoundary, mixed $upperBoundary): self` 1421 | 1422 | This method specifies that the custom field must be between two given values. 1423 | 1424 | Example of usage: 1425 | 1426 | ```php 1427 | $query->meta('my_post_meta')->between('2022-01-01', '2022-12-31'); 1428 | ``` 1429 | 1430 | #### notBetween($lowerBoundary, $upperBoundary) 1431 | 1432 | This method specifies that the custom field must not be between two given values. 1433 | 1434 | Example of usage: 1435 | 1436 | ```php 1437 | $query->meta('my_post_meta')->notBetween('2022-01-01', '2022-12-31'); 1438 | ``` 1439 | 1440 | #### `like(string $value): self` 1441 | 1442 | This method specifies a partial match of the custom field with a string. 1443 | 1444 | Example of usage: 1445 | 1446 | ```php 1447 | $query->meta('my_post_meta')->like('keyword'); 1448 | ``` 1449 | 1450 | #### notLike($value) 1451 | 1452 | This method specifies that the custom field must not partially match a string. 1453 | 1454 | Example of usage: 1455 | 1456 | ```php 1457 | $query->meta('my_post_meta')->notLike('excluded'); 1458 | ``` 1459 | 1460 | #### in($values) 1461 | 1462 | This method specifies that the custom field must match one of the values in a given array. 1463 | 1464 | Example of usage: 1465 | 1466 | ```php 1467 | $query->meta('my_post_meta')->in(['value1', 'value2', 'value3']); 1468 | ``` 1469 | 1470 | #### notIn($values) 1471 | 1472 | This method specifies that the custom field must not match any of the values in a given array. 1473 | 1474 | Example of usage: 1475 | 1476 | ```php 1477 | $query->meta('my_post_meta')->notIn(['excluded1', 'excluded2']); 1478 | ``` 1479 | 1480 | #### state($value) 1481 | 1482 | The `state($value)` method within the `MetaQuery` class allows you to use the named meta queries and use this state for ordering. See [this section of the documentation](https://developer.wordpress.org/reference/classes/wp_query/#order-orderby-parameters) for more information. 1483 | 1484 | Example of usage: 1485 | 1486 | ```php 1487 | PostQuery::select() 1488 | ->metaQuery(function (MetaQuery $query) { 1489 | $query 1490 | ->where( 1491 | $query 1492 | ->meta('menu_order') 1493 | ->state('menu_order_state') 1494 | ->greaterThan(10) 1495 | ); 1496 | 1497 | }) 1498 | ->orderBy('menu_order_state', 'ASC') 1499 | ->get(); 1500 | ``` 1501 | 1502 | #### exists() 1503 | 1504 | This method specifies that the custom field must exist (be defined). 1505 | 1506 | Example of usage: 1507 | 1508 | ```php 1509 | $query->meta('my_post_meta')->exists(); 1510 | ``` 1511 | 1512 | #### notExists() 1513 | 1514 | This method specifies that the custom field must not exist (not be defined). 1515 | 1516 | Example of usage: 1517 | 1518 | ```php 1519 | $query->meta('my_post_meta')->notExists(); 1520 | ``` 1521 | 1522 | ### `metaQuery` Method Chaining 1523 | 1524 | You can chain multiple `meta()` methods to create complex meta queries. For example: 1525 | 1526 | ```php 1527 | PostQuery::select()->metaQuery(function (MetaQuery $query) { 1528 | $query 1529 | ->where( 1530 | $query->meta('status')->equalTo('active') 1531 | ) 1532 | ->orWhere( 1533 | $query->meta('highlighted')->equalTo(1) 1534 | ); 1535 | 1536 | })->get(); 1537 | ``` 1538 | 1539 | This generates a meta query with an 'OR' relation between the two conditions. 1540 | 1541 | ### Nested Meta Queries 1542 | 1543 | You can create nested meta queries by using the `MetaQuery` class within the `metaQuery()` callback. This allows you to create complex meta queries with multiple levels of conditions. For example: 1544 | 1545 | ```php 1546 | PostQuery::select() 1547 | ->metaQuery(function (MetaQuery $query) { 1548 | $query 1549 | ->where( 1550 | $query->meta('status')->equalTo('active') 1551 | ) 1552 | ->orWhere(function (MetaQuery $query) { 1553 | $query 1554 | ->where( 1555 | $query->meta('_sku')->equalTo('H123456789') 1556 | ) 1557 | ->orWhere( 1558 | $query->meta('_sku')->equalTo('D123456784') 1559 | ); 1560 | }) 1561 | ->orWhere(function (MetaQuery $query) { 1562 | $query 1563 | ->where( 1564 | $query->meta('promo_cat')->notIn(['summer', 'blackfriday'])->state('promo_cat') 1565 | ) 1566 | ->andWhere( 1567 | $query->meta('promo_cat')->notEqualTo('sales') 1568 | ); 1569 | }) 1570 | ->orWhere( 1571 | $query->meta('sale_date') 1572 | ->between('2024-01-01', '2024-12-31') 1573 | ); 1574 | 1575 | }) 1576 | ->orderBy('promo_cat') 1577 | ->get(); 1578 | ``` 1579 | 1580 | ## Permission 1581 | 1582 | The `PostQuery` class allows you to query posts based on specific user permissions. You can use the following permission parameters to filter posts based on their accessibility to users. 1583 | 1584 | ### `userPermission(string $permission)` 1585 | 1586 | The `userPermission` method is used to filter posts based on user permissions. It accepts one parameter: 1587 | 1588 | - `$permission` (string): Specifies the type of permission to filter posts by. Valid values include: 1589 | 1590 | - `'readable'`: This permission filters posts that the user can read. 1591 | - `'editable'`: This permission filters posts that the user can edit. 1592 | 1593 | #### Filter posts that the user can read. 1594 | 1595 | ```php 1596 | PostQuery::select() 1597 | ->userPermission('readable') 1598 | ->get(); 1599 | ``` 1600 | 1601 | This method generates the following WP_Query argument: 1602 | 1603 | ```php 1604 | new WP_Query([ 1605 | 'perm' => 'readable', 1606 | 'post_type' => 'all', 1607 | ]); 1608 | ``` 1609 | 1610 | #### Filter posts that the user can edit. 1611 | 1612 | ```php 1613 | PostQuery::select() 1614 | ->userPermission('editable') 1615 | ->get(); 1616 | ``` 1617 | 1618 | This method generates the following WP_Query argument: 1619 | 1620 | ```php 1621 | new WP_Query([ 1622 | 'perm' => 'editable', 1623 | 'post_type' => 'all', 1624 | ]); 1625 | ``` 1626 | 1627 | #### Invalid permissions 1628 | 1629 | ```php 1630 | PostQuery::select() 1631 | ->userPermission('invalid') 1632 | ->get(); 1633 | ``` 1634 | 1635 | When an invalid permission is provided, all posts are returned in the generated WP_Query: 1636 | 1637 | ```php 1638 | new WP_Query([ 1639 | 'post_type' => 'all', 1640 | ]); 1641 | ``` 1642 | 1643 | ## Mimetype 1644 | 1645 | The `PostQuery` class allows you to filter posts based on their MIME types. You can use the `postMimeType` method to specify the MIME type(s) you want to filter by. 1646 | 1647 | ### `postMimeType(string|array $mimeTypes)` 1648 | 1649 | The `postMimeType` method filters posts based on MIME type(s). It accepts one parameter: 1650 | 1651 | - `$mimeTypes` (string|array): Specifies the MIME type(s) to filter posts by. You can provide a single MIME type as a string or an array of multiple MIME types. 1652 | 1653 | #### Filter posts by a single MIME type (e.g., 'image/gif'). 1654 | 1655 | ```php 1656 | PostQuery::select() 1657 | ->postMimeType('image/gif') 1658 | ->get(); 1659 | ``` 1660 | 1661 | This method generates the following WP_Query argument: 1662 | 1663 | ```php 1664 | new WP_Query([ 1665 | 'post_mime_type' => 'image/gif', 1666 | 'post_type' => 'all', 1667 | ]); 1668 | ``` 1669 | 1670 | #### Filter posts by an array of MIME types. 1671 | 1672 | ```php 1673 | PostQuery::select() 1674 | ->postMimeType(['image/jpeg', 'image/gif', 'image/png', 'image/bmp', 'image/tiff', 'image/x-icon']) 1675 | ->get(); 1676 | ``` 1677 | 1678 | This method generates the following WP_Query argument: 1679 | 1680 | ```php 1681 | new WP_Query([ 1682 | 'post_mime_type' => ['image/jpeg', 'image/gif', 'image/png', 'image/bmp', 'image/tiff', 'image/x-icon'], 1683 | 'post_type' => 'all', 1684 | ]); 1685 | ``` 1686 | 1687 | ## Cache 1688 | 1689 | The `PostQuery` class provides methods for controlling caching behavior when querying posts. 1690 | 1691 | ### cacheResults() 1692 | 1693 | The `cacheResults` method enables caching of query results. When you use this method, the query results will be cached for faster retrieval. It doesn't accept any parameters. 1694 | 1695 | ```php 1696 | PostQuery::select() 1697 | ->cacheResults() 1698 | ->get(); 1699 | ``` 1700 | 1701 | This method generates the following WP_Query argument: 1702 | 1703 | ```php 1704 | new WP_Query([ 1705 | 'cache_results' => true, 1706 | 'post_type' => 'all', 1707 | ]); 1708 | ``` 1709 | 1710 | ### updateMetaCache($update) 1711 | 1712 | The `updateMetaCache` method allows you to control whether post meta data is cached. You can specify whether to update the post meta cache or not using this method. 1713 | 1714 | - `$update` (bool): Pass `true` to update the post meta cache or `false` to disable it. 1715 | 1716 | ```php 1717 | PostQuery::select() 1718 | ->updateMetaCache(false) 1719 | ->get(); 1720 | ``` 1721 | 1722 | This method generates the following WP_Query argument: 1723 | 1724 | ```php 1725 | new WP_Query([ 1726 | 'update_post_meta_cache' => false, 1727 | 'post_type' => 'all', 1728 | ]); 1729 | ``` 1730 | 1731 | ### updateTermCache($update) 1732 | 1733 | The `updateTermCache` method allows you to control whether post term data is cached. You can specify whether to update the post term cache or not using this method. 1734 | 1735 | - `$update` (bool): Pass `true` to update the post term cache or `false` to disable it. 1736 | 1737 | ```php 1738 | PostQuery::select() 1739 | ->updateTermCache(true) 1740 | ->get(); 1741 | ``` 1742 | 1743 | This method generates the following WP_Query argument: 1744 | 1745 | ```php 1746 | new WP_Query([ 1747 | 'update_post_term_cache' => true, 1748 | 'post_type' => 'all', 1749 | ]); 1750 | ``` 1751 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pollora/query", 3 | "description": "Create WP Query in a fluent way.", 4 | "license": "GPL-2.0-or-later", 5 | "authors": [ 6 | { 7 | "name": "Olivier Gorzalka", 8 | "email": "olivier@amphibee.fr", 9 | "role": "Developer" 10 | } 11 | ], 12 | "autoload": { 13 | "psr-4": { 14 | "Pollora\\Query\\": "src/" 15 | } 16 | }, 17 | "require-dev": { 18 | "pestphp/pest": "^2.19", 19 | "rector/rector": "^0.18.4", 20 | "nunomaduro/phpinsights": "^2.8" 21 | }, 22 | "config": { 23 | "allow-plugins": { 24 | "pestphp/pest-plugin": true, 25 | "dealerdirect/phpcodesniffer-composer-installer": true 26 | } 27 | }, 28 | "require": { 29 | "php": "^8.1.0", 30 | "pollora/wordpress-args": "dev-main", 31 | "laravel/pint": "^1.13" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests 10 | 11 | 12 | 13 | 14 | ./app 15 | ./src 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | paths([ 12 | __DIR__.'/src', 13 | __DIR__.'/tests', 14 | ]); 15 | 16 | $rectorConfig->importNames(); 17 | 18 | // register a single rule 19 | $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); 20 | 21 | // define sets of rules 22 | $rectorConfig->sets([ 23 | LevelSetList::UP_TO_PHP_82, 24 | SetList::DEAD_CODE, 25 | ]); 26 | }; 27 | -------------------------------------------------------------------------------- /src/DateQuery.php: -------------------------------------------------------------------------------- 1 | stack[$this->depth]; 20 | $current['inclusive'] = true; 21 | 22 | return $this; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/MetaQuery.php: -------------------------------------------------------------------------------- 1 | 34 | */ 35 | private array $queryBuilder = []; 36 | 37 | public function __construct(array|int|null $postId = null, ?string $fields = null) 38 | { 39 | if (is_int($postId)) { 40 | $this->postId($postId); 41 | } elseif (is_array($postId)) { 42 | $this->whereIn($postId); 43 | } 44 | 45 | if ($fields) { 46 | $this->fields($fields); 47 | } 48 | } 49 | 50 | public static function find(array|int|null $postId = null): self 51 | { 52 | return new static($postId); 53 | } 54 | 55 | public static function select(?string $fields = 'all'): self 56 | { 57 | return new static(null, $fields); 58 | } 59 | 60 | /** 61 | * @return array 62 | */ 63 | public function getArguments(): array 64 | { 65 | $args = $this->buildArguments(); 66 | unset($args['query_builder']); 67 | unset($args['query_type']); 68 | 69 | return $args; 70 | } 71 | 72 | public function get(): WP_Query 73 | { 74 | $args = $this->getArguments(); 75 | 76 | return new WP_Query($args); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Query.php: -------------------------------------------------------------------------------- 1 | type = (new ValueTypeDetector($value, $this->type))->detect(); 16 | } 17 | 18 | public function withComparison(string $compare, mixed $value): self 19 | { 20 | return new self($compare, $value, $this->type); 21 | } 22 | 23 | /** 24 | * @return array 25 | */ 26 | public function buildConfig(): array 27 | { 28 | $config = [ 29 | 'compare' => $this->compare, 30 | 'type' => strtoupper((string) $this->type), 31 | ]; 32 | 33 | if ($this->value !== null) { 34 | $config['value'] = $this->value; 35 | } 36 | 37 | return $config; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/QueryBuilder/DateQueryBuilder.php: -------------------------------------------------------------------------------- 1 | column = self::POST_CREATED; 52 | 53 | return $this; 54 | } 55 | 56 | public function modified(): self 57 | { 58 | $this->column = self::POST_MODIFIED; 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * @param array $date 65 | * 66 | * @throws QueryException 67 | */ 68 | private function validateDateArray(array $date): bool 69 | { 70 | foreach ($date as $key => $part) { 71 | if (! in_array($key, self::ALLOWED_KEYS)) { 72 | throw new QueryException('Invalid key '.$key.' element supplied.'); 73 | } 74 | $this->$key = $part; 75 | } 76 | 77 | return true; 78 | } 79 | 80 | /** 81 | * @param array $date 82 | */ 83 | private function applyDateArray(array $date): void 84 | { 85 | foreach ($date as $key => $part) { 86 | $this->$key = (int) $part; 87 | } 88 | } 89 | 90 | /** 91 | * @throws QueryException 92 | */ 93 | public function within(array|string $date, string $extract = 'Ymdhis'): self 94 | { 95 | if (is_array($date)) { 96 | $this->handleArrayDate($date); 97 | } else { 98 | $this->handleStringOrNumericDate($date, $extract); 99 | } 100 | 101 | return $this; 102 | } 103 | 104 | /** 105 | * @param array|string $toDate 106 | * 107 | * @throws QueryException 108 | */ 109 | public function between(array|string $fromDate, array|string $toDate): self 110 | { 111 | 112 | $this->after = $this->extractFromDate($fromDate); 113 | $this->before = $this->extractFromDate($toDate); 114 | 115 | return $this; 116 | } 117 | 118 | /** 119 | * @param array|string $beforeDate 120 | * 121 | * @throws QueryException 122 | */ 123 | public function before(array|string $beforeDate): self 124 | { 125 | $this->before = $this->extractFromDate($beforeDate); 126 | 127 | return $this; 128 | } 129 | 130 | /** 131 | * @param array|string $afterDate 132 | * 133 | * @throws QueryException 134 | */ 135 | public function after(array|string $afterDate): self 136 | { 137 | $this->after = $this->extractFromDate($afterDate); 138 | 139 | return $this; 140 | } 141 | 142 | /** 143 | * @param array|string $date 144 | * @return array 145 | * 146 | * @throws QueryException 147 | */ 148 | public function extractFromDate(array|string $date, string $extract = 'Ymdhis'): array 149 | { 150 | if (is_array($date)) { 151 | return $date; 152 | } 153 | 154 | if (! is_numeric($date)) { 155 | $date = strtotime($date); 156 | } 157 | 158 | if ($date === false) { 159 | throw new QueryException('Provided datestring could not be converted to time'); 160 | } 161 | 162 | return $this->extractDateDetails($date, $extract); 163 | } 164 | 165 | /** 166 | * @return array{ 167 | * year: string, 168 | * month: string, 169 | * day: string, 170 | * jour: string, 171 | * minute: string, 172 | * second: string, 173 | * } 174 | */ 175 | 176 | private function extractDateDetails(int $date, string $extract): array 177 | { 178 | $extracted = []; 179 | $mapping = [ 180 | 'Y' => 'year', 181 | 'm' => 'month', 182 | 'd' => 'day', 183 | 'h' => 'hour', 184 | 'i' => 'minute', 185 | 's' => 'second', 186 | ]; 187 | 188 | foreach ($mapping as $key => $value) { 189 | if (str_contains($extract, $key)) { 190 | $extracted[$value] = date($key, $date); 191 | } 192 | } 193 | 194 | var_export($extracted); 195 | 196 | return $extracted; 197 | } 198 | 199 | /** 200 | * @param array $date 201 | * 202 | * @throws QueryException 203 | */ 204 | private function handleArrayDate(array $date): void 205 | { 206 | if ($this->validateDateArray($date)) { 207 | $this->applyDateArray($date); 208 | } 209 | } 210 | 211 | /** 212 | * @param array|string $date 213 | * 214 | * @throws QueryException 215 | */ 216 | private function handleStringOrNumericDate(string|array $date, string $extract): void 217 | { 218 | $parts = $this->extractFromDate($date, $extract); 219 | $this->applyDateArray($parts); 220 | } 221 | 222 | /** 223 | * @return array 224 | */ 225 | public function get(): array 226 | { 227 | $beforeIsNotNull = ! is_null($this->before); 228 | $afterIsNotNull = ! is_null($this->after); 229 | 230 | if ($beforeIsNotNull && $afterIsNotNull) { 231 | return $this->handleBeforeAndAfter(); 232 | } 233 | 234 | if ($beforeIsNotNull) { 235 | return $this->handleBeforeOnly(); 236 | } 237 | 238 | if ($afterIsNotNull) { 239 | return $this->handleAfterOnly(); 240 | } 241 | 242 | return $this->handleDefault(); 243 | } 244 | 245 | /** 246 | * @return array 247 | */ 248 | private function handleBeforeAndAfter(): array 249 | { 250 | return [ 251 | 'column' => $this->column, 252 | 'before' => $this->before, 253 | 'after' => $this->after, 254 | ]; 255 | } 256 | 257 | /** 258 | * @return array{ 259 | * column: string, 260 | * before: string|array 261 | * } 262 | */ 263 | private function handleBeforeOnly(): array 264 | { 265 | return [ 266 | 'column' => $this->column, 267 | 'before' => $this->before, 268 | ]; 269 | } 270 | 271 | /** 272 | * @return array 273 | */ 274 | private function handleAfterOnly(): array 275 | { 276 | return [ 277 | 'column' => $this->column, 278 | 'after' => $this->after, 279 | ]; 280 | } 281 | 282 | /** 283 | * @return array{ 284 | * column: string, 285 | * year: string, 286 | * month: string, 287 | * day: string, 288 | * hour: string, 289 | * minute: string, 290 | * second: string, 291 | * } 292 | */ 293 | private function handleDefault(): array 294 | { 295 | return [ 296 | 'column' => $this->column, 297 | 'year' => $this->year, 298 | 'month' => $this->month, 299 | 'day' => $this->day, 300 | 'hour' => $this->hour, 301 | 'minute' => $this->minute, 302 | 'second' => $this->second, 303 | ]; 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /src/QueryBuilder/MetaQueryBuilder.php: -------------------------------------------------------------------------------- 1 | comparisonBuilder = new ComparisonBuilder($this->value, $this->type ?? null); 20 | } 21 | 22 | public function ofType(string $type): self 23 | { 24 | $this->type = $type; 25 | 26 | return $this; 27 | } 28 | 29 | public function greaterThan(mixed $value): self 30 | { 31 | $this->comparisonBuilder = $this->comparisonBuilder->withComparison(self::GREATER, $value); 32 | 33 | return $this; 34 | } 35 | 36 | public function greaterOrEqualTo(mixed $value): self 37 | { 38 | $this->comparisonBuilder = $this->comparisonBuilder->withComparison(self::GREATER_EQUAL, $value); 39 | 40 | return $this; 41 | } 42 | 43 | public function lessThan(mixed $value): self 44 | { 45 | $this->comparisonBuilder = $this->comparisonBuilder->withComparison(self::LESS, $value); 46 | 47 | return $this; 48 | } 49 | 50 | public function lessOrEqualTo(mixed $value): self 51 | { 52 | $this->comparisonBuilder = $this->comparisonBuilder->withComparison(self::LESS_EQUAL, $value); 53 | 54 | return $this; 55 | } 56 | 57 | public function equalTo(mixed $value): self 58 | { 59 | $this->comparisonBuilder = $this->comparisonBuilder->withComparison(self::EQUAL, $value); 60 | 61 | return $this; 62 | } 63 | 64 | public function notEqualTo(mixed $value): self 65 | { 66 | $this->comparisonBuilder = $this->comparisonBuilder->withComparison(self::NOT_EQUAL, $value); 67 | 68 | return $this; 69 | } 70 | 71 | public function between(mixed $lowerBoundary, mixed $upperBoundary): self 72 | { 73 | $this->comparisonBuilder = $this->comparisonBuilder->withComparison(self::BETWEEN, [ 74 | $lowerBoundary, 75 | $upperBoundary, 76 | ]); 77 | 78 | return $this; 79 | } 80 | 81 | public function notBetween(mixed $lowerBoundary, mixed $upperBoundary): self 82 | { 83 | $this->comparisonBuilder = $this->comparisonBuilder->withComparison(self::NOT_BETWEEN, [ 84 | $lowerBoundary, 85 | $upperBoundary, 86 | ]); 87 | 88 | return $this; 89 | } 90 | 91 | public function like(string $value): self 92 | { 93 | $this->comparisonBuilder = $this->comparisonBuilder->withComparison(self::LIKE, $value); 94 | 95 | return $this; 96 | } 97 | 98 | public function notLike(string $value): self 99 | { 100 | $this->comparisonBuilder = $this->comparisonBuilder->withComparison(self::NOT_LIKE, $value); 101 | 102 | return $this; 103 | } 104 | 105 | /** 106 | * @param array $value 107 | * @return $this 108 | */ 109 | public function in(array $value): self 110 | { 111 | $this->comparisonBuilder = $this->comparisonBuilder->withComparison(self::IN, $value); 112 | 113 | return $this; 114 | } 115 | 116 | /** 117 | * @param array $value 118 | * @return $this 119 | */ 120 | public function notIn(array $value): self 121 | { 122 | $this->comparisonBuilder = $this->comparisonBuilder->withComparison(self::NOT_IN, $value); 123 | 124 | return $this; 125 | } 126 | 127 | public function exists(): self 128 | { 129 | $this->comparisonBuilder = $this->comparisonBuilder->withComparison(self::EXISTS, null); 130 | 131 | return $this; 132 | } 133 | 134 | public function notExists(): self 135 | { 136 | $this->comparisonBuilder = $this->comparisonBuilder->withComparison(self::NOT_EXISTS, null); 137 | 138 | return $this; 139 | } 140 | 141 | public function state(string $state): self 142 | { 143 | $this->state = $state; 144 | 145 | return $this; 146 | } 147 | 148 | /** 149 | * @return array 150 | */ 151 | public function get(): array 152 | { 153 | return ['key' => $this->key, 'state' => $this->state, ...$this->comparisonBuilder->buildConfig()]; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/QueryBuilder/QueryBuilder.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected array $query = ['relation' => 'AND']; 15 | 16 | /** 17 | * @var array 18 | */ 19 | protected array $stack = []; 20 | 21 | protected int $depth = 0; 22 | 23 | /** 24 | * @var array 25 | */ 26 | protected array $lastRelation = []; 27 | 28 | public function __construct() 29 | { 30 | $this->stack = [&$this->query]; 31 | } 32 | 33 | public function where(Closure|SubQuery $callback): self 34 | { 35 | return $this->addCondition('AND', $callback); 36 | } 37 | 38 | public function andWhere(Closure|SubQuery $callback): self 39 | { 40 | return $this->addCondition('AND', $callback); 41 | } 42 | 43 | public function orWhere(Closure|SubQuery $callback): self 44 | { 45 | return $this->addCondition('OR', $callback); 46 | } 47 | 48 | protected function addCondition(string $relation, Closure|SubQuery $callback): self 49 | { 50 | $current = &$this->stack[$this->depth]; 51 | $this->lastRelation[$this->depth] = $relation; 52 | 53 | if ($callback instanceof SubQuery) { 54 | $this->handleSubQuery($current, $callback); 55 | } elseif (is_callable($callback)) { 56 | $this->handleCallback($current, $callback); 57 | } 58 | 59 | $current['relation'] = $this->lastRelation[$this->depth] ?? 'AND'; 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * @param $current array 66 | */ 67 | private function handleSubQuery(array &$current, SubQuery $subQueryInstance): void 68 | { 69 | $subQuery = $subQueryInstance->get(); 70 | 71 | if (isset($subQuery['state']) && $subQuery['state']) { 72 | $state = $subQuery['state']; 73 | unset($subQuery['state']); 74 | $current[$state] = $subQuery; 75 | } else { 76 | unset($subQuery['state']); 77 | $current[] = $subQuery; 78 | } 79 | } 80 | 81 | /** 82 | * @param $current array 83 | */ 84 | private function handleCallback(array &$current, Closure $callback): void 85 | { 86 | $subQuery = ['relation' => 'AND']; 87 | $this->depth++; 88 | $this->stack[$this->depth] = &$subQuery; 89 | $callback($this); 90 | $this->depth--; 91 | 92 | $subQuery['relation'] = $this->lastRelation[$this->depth + 1] ?? 'AND'; 93 | $current[] = $subQuery; 94 | } 95 | 96 | 97 | /** 98 | * @return array 99 | */ 100 | public function get(): array 101 | { 102 | return $this->query; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/QueryBuilder/SubQuery.php: -------------------------------------------------------------------------------- 1 | '; 16 | 17 | public const GREATER_EQUAL = '>='; 18 | 19 | public const LESS = '<'; 20 | 21 | public const LESS_EQUAL = '<='; 22 | 23 | public const LIKE = 'LIKE'; 24 | 25 | public const NOT_LIKE = 'NOT LIKE'; 26 | 27 | public const IN = 'IN'; 28 | 29 | public const NOT_IN = 'NOT IN'; 30 | 31 | public const BETWEEN = 'BETWEEN'; 32 | 33 | public const NOT_BETWEEN = 'NOT BETWEEN'; 34 | 35 | public const EXISTS = 'EXISTS'; 36 | 37 | public const NOT_EXISTS = 'NOT EXISTS'; 38 | 39 | public const REGEXP = 'REGEXP'; 40 | 41 | public const NOT_REGEXP = 'NOT REGEXP'; 42 | 43 | public const RLIKE = 'RLIKE'; 44 | } 45 | -------------------------------------------------------------------------------- /src/QueryBuilder/TaxQueryBuilder.php: -------------------------------------------------------------------------------- 1 | |null 68 | * Variable that stores an array of terms used for searching. 69 | */ 70 | private ?array $terms = null; 71 | 72 | public function __construct( 73 | private readonly string $taxonomy 74 | ) { 75 | } 76 | 77 | /** 78 | * Sets the operator to "NOT EXISTS". 79 | * 80 | * @return self Returns the current instance of the object. 81 | */ 82 | public function notExists(): self 83 | { 84 | $this->operator = 'NOT EXISTS'; 85 | 86 | return $this; 87 | } 88 | 89 | /** 90 | * Sets the operator to "EXISTS". 91 | * 92 | * @return self Returns the current instance of the object. 93 | */ 94 | public function exists(): self 95 | { 96 | $this->operator = 'EXISTS'; 97 | 98 | return $this; 99 | } 100 | 101 | /** 102 | * Sets the field to be used in the query. 103 | * 104 | * @param string $field The field to be used in the query. 105 | * @return self Returns the current instance of the object. 106 | * 107 | * @throws QueryException If an invalid tax field type is supplied. 108 | */ 109 | public function field(string $field): self 110 | { 111 | 112 | $allowed = [self::SEARCH_BY_ID, self::SEARCH_BY_NAME, self::SEARCH_BY_TERM_TAX_ID, self::SEARCH_BY_SLUG]; 113 | 114 | if (! in_array($field, $allowed)) { 115 | throw new QueryException('Invalid tax field type supplied: '.$field); 116 | } 117 | 118 | $this->field = $field; 119 | 120 | return $this; 121 | } 122 | 123 | /** 124 | * Sets the terms to be used in the query and sets the operator to "IN". 125 | * 126 | * @param array $terms The terms to be used in the query. 127 | * @return self Returns the current instance of the object. 128 | */ 129 | public function contains(array $terms): self 130 | { 131 | $this->terms = $terms; 132 | $this->operator = 'IN'; 133 | 134 | return $this; 135 | } 136 | 137 | /** 138 | * Sets the terms to exclude from the search. 139 | * 140 | * @param array $terms The terms to exclude. 141 | */ 142 | public function notContains(array $terms): self 143 | { 144 | $this->terms = $terms; 145 | $this->operator = 'NOT IN'; 146 | 147 | return $this; 148 | } 149 | 150 | /** 151 | * Sets the field to search for term slugs. 152 | * 153 | * @throws QueryException 154 | */ 155 | public function searchByTermSlug(): self 156 | { 157 | $this->field(self::SEARCH_BY_SLUG); 158 | 159 | return $this; 160 | } 161 | 162 | /** 163 | * Sets the field to search for term names. 164 | * 165 | * @throws QueryException 166 | */ 167 | public function searchByTermName(): self 168 | { 169 | $this->field(self::SEARCH_BY_NAME); 170 | 171 | return $this; 172 | } 173 | 174 | /** 175 | * Sets the field to search for term taxonomy IDs. 176 | * 177 | * @throws QueryException 178 | */ 179 | public function searchByTermTaxId(): self 180 | { 181 | $this->field(self::SEARCH_BY_TERM_TAX_ID); 182 | 183 | return $this; 184 | } 185 | 186 | /** 187 | * Sets the field to search for term IDs. 188 | * 189 | * @throws QueryException 190 | */ 191 | public function searchById(): self 192 | { 193 | $this->field(self::SEARCH_BY_ID); 194 | 195 | return $this; 196 | } 197 | 198 | /** 199 | * Sets the flag to include children in the query. 200 | * 201 | * @return self Returns the current instance of the object. 202 | */ 203 | public function includeChildren(): self 204 | { 205 | $this->includeChildren = true; 206 | 207 | return $this; 208 | } 209 | 210 | /** 211 | * Sets the includeChildren property to "false", indicating that only parent items should be included in the query result. 212 | * 213 | * @return self Returns the current instance of the object. 214 | */ 215 | public function onlyParent(): self 216 | { 217 | $this->includeChildren = false; 218 | 219 | return $this; 220 | } 221 | 222 | /** 223 | * Sets the flag to exclude children from the query result. 224 | * 225 | * @return self Returns the current instance of the object. 226 | */ 227 | public function excludeChildren(): self 228 | { 229 | $this->includeChildren = false; 230 | 231 | return $this; 232 | } 233 | 234 | /** 235 | * @return array{ 236 | * taxonomy: string, 237 | * field: string, 238 | * terms: array|null, 239 | * operator: string, 240 | * include_children: bool 241 | * } 242 | */ 243 | public function get(): array 244 | { 245 | return [ 246 | 'taxonomy' => $this->taxonomy, 247 | 'field' => $this->field, 248 | 'terms' => $this->terms, 249 | 'operator' => $this->operator, 250 | 'include_children' => $this->includeChildren, 251 | ]; 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/QueryException.php: -------------------------------------------------------------------------------- 1 | |null 17 | */ 18 | protected ?array $author_In = null; 19 | 20 | /** 21 | * @var array|null 22 | */ 23 | protected ?array $author_NotIn = null; 24 | 25 | public function author(int $author): self 26 | { 27 | $this->author = $author; 28 | 29 | return $this; 30 | } 31 | 32 | public function authorName(string $authorName): self 33 | { 34 | $this->authorName = $authorName; 35 | 36 | return $this; 37 | } 38 | 39 | /** 40 | * @param array $authorIn 41 | * @return PostQuery|Author 42 | */ 43 | public function authorIn(array $authorIn): self 44 | { 45 | $this->author_In = $authorIn; 46 | 47 | return $this; 48 | } 49 | 50 | /** 51 | * @param array $authorNotIn 52 | * @return PostQuery|Author 53 | */ 54 | public function authorNotIn(array $authorNotIn): self 55 | { 56 | $this->author_NotIn = $authorNotIn; 57 | 58 | return $this; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Traits/Caching.php: -------------------------------------------------------------------------------- 1 | cacheResults = $value; 18 | 19 | return $this; 20 | } 21 | 22 | public function updateMetaCache(bool $value = true): self 23 | { 24 | $this->updatePostMetaCache = $value; 25 | 26 | return $this; 27 | } 28 | 29 | public function updateTermCache(bool $value = true): self 30 | { 31 | $this->updatePostTermCache = $value; 32 | 33 | return $this; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Traits/Category.php: -------------------------------------------------------------------------------- 1 | |null 17 | */ 18 | protected ?array $categoryAnd = null; 19 | 20 | /** 21 | * @var array|null 22 | */ 23 | protected ?array $category_In = null; 24 | 25 | /** 26 | * @var array|null 27 | */ 28 | protected ?array $category_NotIn = null; 29 | 30 | public function cat(int $cat): self 31 | { 32 | $this->cat = $cat; 33 | 34 | return $this; 35 | } 36 | 37 | public function categoryName(string $categoryName): self 38 | { 39 | $this->categoryName = $categoryName; 40 | 41 | return $this; 42 | } 43 | 44 | /** 45 | * @param array $categoryAnd 46 | * @return PostQuery|Category 47 | */ 48 | public function categoryAnd(array $categoryAnd): self 49 | { 50 | $this->categoryAnd = $categoryAnd; 51 | 52 | return $this; 53 | } 54 | 55 | /** 56 | * @param array $categoryIn 57 | * @return PostQuery|Category 58 | */ 59 | public function categoryIn(array $categoryIn): self 60 | { 61 | $this->category_In = $categoryIn; 62 | 63 | return $this; 64 | } 65 | 66 | /** 67 | * @param array $categoryNotIn 68 | * @return PostQuery|Category 69 | */ 70 | public function categoryNotIn(array $categoryNotIn): self 71 | { 72 | $this->category_NotIn = $categoryNotIn; 73 | 74 | return $this; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Traits/Comment.php: -------------------------------------------------------------------------------- 1 | commentCount = $count; 14 | 15 | return $this; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Traits/DateQuery.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | protected ?array $dateQuery = null; 16 | 17 | use SubQueryTrait { 18 | SubQueryTrait::query as genericQuery; 19 | } 20 | 21 | public function dateQuery(callable|SubQuery $callback): self 22 | { 23 | $this->initQuery('date'); 24 | 25 | return $this->genericQuery($callback); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Traits/Field.php: -------------------------------------------------------------------------------- 1 | fields = 'all'; 15 | } elseif (in_array($value, ['all', 'ids', 'id=>parent'])) { 16 | $this->fields = $value; 17 | } 18 | 19 | return $this; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Traits/MetaQuery.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | protected ?array $metaQuery = null; 16 | 17 | use SubQueryTrait { 18 | SubQueryTrait::query as genericQuery; 19 | } 20 | 21 | public function metaQuery(callable|SubQuery $callback): self 22 | { 23 | $this->initQuery('meta'); 24 | 25 | return $this->genericQuery($callback); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Traits/MimeType.php: -------------------------------------------------------------------------------- 1 | postMimeType = $value; 14 | 15 | return $this; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Traits/Order.php: -------------------------------------------------------------------------------- 1 | orderby) { 14 | $this->orderby = []; 15 | } 16 | $this->orderby[$orderby] = $order; 17 | 18 | return $this; 19 | } 20 | 21 | public function latest(): self 22 | { 23 | $this->orderby['post_date'] = 'DESC'; 24 | 25 | return $this; 26 | } 27 | 28 | public function oldest(): self 29 | { 30 | $this->orderby['post_date'] = 'ASC'; 31 | 32 | return $this; 33 | } 34 | 35 | public function inRandomOrder(): self 36 | { 37 | $this->orderby = 'rand'; 38 | 39 | return $this; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Traits/Pagination.php: -------------------------------------------------------------------------------- 1 | nopaging = $noPaging; 26 | 27 | return $this; 28 | } 29 | 30 | public function limit(int $postsPerPage): self 31 | { 32 | $this->postsPerPage($postsPerPage); 33 | 34 | return $this; 35 | } 36 | 37 | public function take(int $postsPerPage): self 38 | { 39 | $this->postsPerPage($postsPerPage); 40 | 41 | return $this; 42 | } 43 | 44 | public function postsPerPage(int $postsPerPage): self 45 | { 46 | $this->postsPerPage = $postsPerPage; 47 | 48 | return $this; 49 | } 50 | 51 | public function postsPerArchivePage(int $postsPerArchivePage): self 52 | { 53 | $this->postsPerArchivePage = $postsPerArchivePage; 54 | 55 | return $this; 56 | } 57 | 58 | public function skip(int $offset): self 59 | { 60 | $this->offset($offset); 61 | 62 | return $this; 63 | } 64 | 65 | public function offset(int $offset): self 66 | { 67 | $this->offset = $offset; 68 | 69 | return $this; 70 | } 71 | 72 | public function paged(int $paged): self 73 | { 74 | $this->paged = $paged; 75 | 76 | return $this; 77 | } 78 | 79 | public function page(int $page): self 80 | { 81 | $this->page = $page; 82 | 83 | return $this; 84 | } 85 | 86 | public function ignoreStickyPosts(bool $ignoreStickyPosts = true): self 87 | { 88 | $this->ignoreStickyPosts = $ignoreStickyPosts; 89 | 90 | return $this; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Traits/Password.php: -------------------------------------------------------------------------------- 1 | hasPassword = false; 16 | 17 | return $this; 18 | } 19 | 20 | public function withPassword(?string $postPassword = null): self 21 | { 22 | if ($postPassword !== null) { 23 | $this->postPassword = $postPassword; 24 | } else { 25 | $this->hasPassword = true; 26 | } 27 | 28 | return $this; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Traits/Permission.php: -------------------------------------------------------------------------------- 1 | isValidPermission($value)) { 14 | $this->perm = $value; 15 | } 16 | 17 | return $this; 18 | } 19 | 20 | protected function isValidPermission(string $value): bool 21 | { 22 | return in_array($value, ['readable', 'editable'], true); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Traits/Post.php: -------------------------------------------------------------------------------- 1 | |null 19 | */ 20 | protected ?array $postParent_In = null; 21 | 22 | /** 23 | * @var array|null 24 | */ 25 | protected ?array $postParent_NotIn = null; 26 | 27 | /** 28 | * @var array|null 29 | */ 30 | protected ?array $post_In = null; 31 | 32 | /** 33 | * @var array|null 34 | */ 35 | protected ?array $post_NotIn = null; 36 | 37 | /** 38 | * @var array|null 39 | */ 40 | protected ?array $postName_In = null; 41 | 42 | public function postId(int $postId): self 43 | { 44 | $this->p = $postId; 45 | 46 | return $this; 47 | } 48 | 49 | public function postSlug(string $postName): self 50 | { 51 | $this->name = $postName; 52 | 53 | return $this; 54 | } 55 | 56 | public function postParent(int $postParent): self 57 | { 58 | $this->postParent = $postParent; 59 | 60 | return $this; 61 | } 62 | 63 | /** 64 | * @param array $postParentIn 65 | * @return Post|PostQuery 66 | */ 67 | public function whereParentIn(array $postParentIn): self 68 | { 69 | $this->postParent_In = $postParentIn; 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * @param array $postParentNotIn 76 | * @return Post|PostQuery 77 | */ 78 | public function whereParentNotIn(array $postParentNotIn): self 79 | { 80 | $this->postParent_NotIn = $postParentNotIn; 81 | 82 | return $this; 83 | } 84 | 85 | /** 86 | * @param array $postIn 87 | * @return Post|PostQuery 88 | */ 89 | public function whereIn(array $postIn): self 90 | { 91 | $this->post_In = $postIn; 92 | 93 | return $this; 94 | } 95 | 96 | /** 97 | * @param array $postNotIn 98 | * @return Post|PostQuery 99 | */ 100 | public function whereNotIn(array $postNotIn): self 101 | { 102 | $this->post_NotIn = $postNotIn; 103 | 104 | return $this; 105 | } 106 | 107 | /** 108 | * @param array $postSlugIn 109 | * @return Post|PostQuery 110 | */ 111 | public function slugIn(array $postSlugIn): self 112 | { 113 | $this->postName_In = $postSlugIn; 114 | 115 | return $this; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Traits/PostType.php: -------------------------------------------------------------------------------- 1 | postType = $postType; 14 | 15 | return $this; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Traits/Query/SubQuery.php: -------------------------------------------------------------------------------- 1 | queryType = $type; 16 | 17 | return $this; 18 | } 19 | 20 | public function query(callable|SubQueryAbstract $callback): self 21 | { 22 | $this->initializeQueryField(); 23 | $this->initializeQueryBuilder(); 24 | 25 | if ($callback instanceof SubQueryAbstract) { 26 | $this->handleSubQueryCallback($callback); 27 | } else { 28 | $this->handleCallableCallback($callback); 29 | } 30 | 31 | return $this; 32 | } 33 | 34 | protected function initializeQueryField(): void 35 | { 36 | $queryField = $this->queryType.'Query'; 37 | 38 | if ($this->{$queryField}) { 39 | return; 40 | } 41 | 42 | $this->{$queryField} = ['relation' => 'AND']; 43 | } 44 | 45 | protected function initializeQueryBuilder(): void 46 | { 47 | $class = '\\Pollora\\Query\\'.ucfirst($this->queryType).'Query'; 48 | $this->queryBuilder[$this->queryType] = $this->queryBuilder[$this->queryType] ?? new $class(); 49 | } 50 | 51 | protected function handleSubQueryCallback(SubQueryAbstract $callback): void 52 | { 53 | $queryField = $this->queryType.'Query'; 54 | $subQuery = $this->getSubQueryState($callback); 55 | 56 | ['state' => $state, 'query' => $query] = $subQuery; 57 | 58 | if ($state) { 59 | $this->{$queryField}[$state] = $query; 60 | } else { 61 | $this->{$queryField} = array_merge($this->{$queryField}, [$query]); 62 | } 63 | } 64 | 65 | /** 66 | * @return array{ 67 | * state: string, 68 | * query: array 69 | * } 70 | * / 71 | */ 72 | private function getSubQueryState(SubQueryAbstract $callback): array 73 | { 74 | $subQuery = $callback->get(); 75 | 76 | $state = $subQuery['state'] ?? false; 77 | unset($subQuery['state']); 78 | 79 | return ['state' => $state, 'query' => $subQuery]; 80 | } 81 | 82 | protected function handleCallableCallback(callable $callback): void 83 | { 84 | $queryField = $this->queryType.'Query'; 85 | $callback($this->queryBuilder[$this->queryType]); 86 | $this->{$queryField} = array_merge($this->{$queryField}, $this->queryBuilder[$this->queryType]->get()); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Traits/Search.php: -------------------------------------------------------------------------------- 1 | s = $keyword; 14 | 15 | return $this; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Traits/Status.php: -------------------------------------------------------------------------------- 1 | postStatus = $status; 14 | 15 | return $this; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Traits/Tag.php: -------------------------------------------------------------------------------- 1 | |null 17 | */ 18 | protected ?array $tag_And = null; 19 | 20 | /** 21 | * @var array|null 22 | */ 23 | protected ?array $tag_In = null; 24 | 25 | /** 26 | * @var array|null 27 | */ 28 | protected ?array $tag_NotIn = null; 29 | 30 | /** 31 | * @var array|null 32 | */ 33 | protected ?array $tagSlug_And = null; 34 | 35 | /** 36 | * @var array|null 37 | */ 38 | protected ?array $tagSlug_In = null; 39 | 40 | public function tag(string $tag): self 41 | { 42 | $this->tag = $tag; 43 | 44 | return $this; 45 | } 46 | 47 | public function tagId(int $tagId): self 48 | { 49 | $this->tagId = $tagId; 50 | 51 | return $this; 52 | } 53 | 54 | /** 55 | * @param array $tagAnd 56 | * @return PostQuery|Tag 57 | */ 58 | public function tagAnd(array $tagAnd): self 59 | { 60 | $this->tag_And = $tagAnd; 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * @param array $tagIn 67 | * @return PostQuery|Tag 68 | */ 69 | public function tagIn(array $tagIn): self 70 | { 71 | $this->tag_In = $tagIn; 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * @param array $tagNotIn 78 | * @return PostQuery|Tag 79 | */ 80 | public function tagNotIn(array $tagNotIn): self 81 | { 82 | $this->tag_NotIn = $tagNotIn; 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * @param array $tagSlugAnd 89 | * @return PostQuery|Tag 90 | */ 91 | public function tagSlugAnd(array $tagSlugAnd): self 92 | { 93 | $this->tagSlug_And = $tagSlugAnd; 94 | 95 | return $this; 96 | } 97 | 98 | /** 99 | * @param array $tagSlugIn 100 | * @return PostQuery|Tag 101 | */ 102 | public function tagSlugIn(array $tagSlugIn): self 103 | { 104 | $this->tagSlug_In = $tagSlugIn; 105 | 106 | return $this; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Traits/TaxQuery.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | protected ?array $taxQuery = null; 16 | 17 | use SubQueryTrait { 18 | SubQueryTrait::query as genericQuery; 19 | } 20 | 21 | public function taxQuery(callable|SubQuery $callback): self 22 | { 23 | $this->initQuery('tax'); 24 | 25 | return $this->genericQuery($callback); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Utils/Types/BooleanType.php: -------------------------------------------------------------------------------- 1 | self::DATE, 25 | is_string($value) && preg_match(self::TIME_REGEX, $value) => self::TIME, 26 | is_string($value) && preg_match(self::DATETIME_REGEX, $value) => self::DATETIME, 27 | default => null 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Utils/Types/NullableType.php: -------------------------------------------------------------------------------- 1 | $value < 0 ? self::SIGNED : self::UNSIGNED, 21 | is_float($value) => self::DECIMAL, 22 | is_string($value) && is_numeric($value) => self::NUMERIC, 23 | default => null, 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Utils/Types/ValueTypeContract.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | private array $detectors = []; 20 | 21 | public function __construct(protected mixed $value, protected ?string $type = null) 22 | { 23 | $detectorClasses = [ 24 | BooleanType::class, 25 | DatetimeType::class, 26 | NullableType::class, 27 | NumberType::class, 28 | ]; 29 | 30 | foreach ($detectorClasses as $detectorClass) { 31 | $this->detectors[] = new $detectorClass(); 32 | } 33 | } 34 | 35 | public function detect(): string 36 | { 37 | if (is_array($this->value)) { 38 | $this->value = $this->value[0]; 39 | } 40 | 41 | if (! is_null($this->type)) { 42 | return $this->type; 43 | } 44 | 45 | return $this->detectType(); 46 | } 47 | 48 | private function detectType(): ?string 49 | { 50 | $type = array_reduce( 51 | $this->detectors, 52 | function ($carry, $detector) { 53 | return $carry ?? $detector->detect($this->value); 54 | } 55 | ); 56 | 57 | return $type ?? CharType::CHAR; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/Feature/AuthorTest.php: -------------------------------------------------------------------------------- 1 | author(1) 8 | ->getArguments(); 9 | 10 | expect($args)->toMatchArray([ 11 | 'author' => 1, 12 | ]); 13 | }); 14 | 15 | test('Author: author_name', function () { 16 | $args = PostQuery::select() 17 | ->authorName('taylor') 18 | ->getArguments(); 19 | 20 | expect($args)->toMatchArray([ 21 | 'author_name' => 'taylor', 22 | ]); 23 | }); 24 | 25 | test('Author: author__in', function () { 26 | $args = PostQuery::select() 27 | ->authorIn([1, 2, 3]) 28 | ->getArguments(); 29 | 30 | expect($args)->toMatchArray([ 31 | 'author__in' => [1, 2, 3], 32 | ]); 33 | }); 34 | 35 | test('Author: author__not_in', function () { 36 | $args = PostQuery::select() 37 | ->authorNotIn([1, 2, 3]) 38 | ->getArguments(); 39 | 40 | expect($args)->toMatchArray([ 41 | 'author__not_in' => [1, 2, 3], 42 | ]); 43 | }); 44 | -------------------------------------------------------------------------------- /tests/Feature/CachingTest.php: -------------------------------------------------------------------------------- 1 | cacheResults() 8 | ->getArguments(); 9 | 10 | expect($args)->toMatchArray([ 11 | 'cache_results' => true, 12 | ]); 13 | }); 14 | 15 | test('Caching: update_post_meta_cache=>false', function () { 16 | $args = PostQuery::select() 17 | ->updateMetaCache(false) 18 | ->getArguments(); 19 | 20 | expect($args)->toMatchArray([ 21 | 'update_post_meta_cache' => false, 22 | ]); 23 | }); 24 | 25 | test('Caching: update_post_term_cache=>true', function () { 26 | $args = PostQuery::select() 27 | ->updateTermCache(true) 28 | ->getArguments(); 29 | 30 | expect($args)->toMatchArray([ 31 | 'update_post_term_cache' => true, 32 | ]); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/Feature/CategoryTest.php: -------------------------------------------------------------------------------- 1 | cat(1) 8 | ->getArguments(); 9 | 10 | expect($args)->toMatchArray([ 11 | 'cat' => 1, 12 | ]); 13 | }); 14 | 15 | test('Category: category_name', function () { 16 | $args = PostQuery::select() 17 | ->categoryName('sales') 18 | ->getArguments(); 19 | 20 | expect($args)->toMatchArray([ 21 | 'category_name' => 'sales', 22 | ]); 23 | }); 24 | 25 | test('Category: category__in', function () { 26 | $args = PostQuery::select() 27 | ->categoryIn([1, 2, 3]) 28 | ->getArguments(); 29 | 30 | expect($args)->toMatchArray([ 31 | 'category__in' => [1, 2, 3], 32 | ]); 33 | }); 34 | 35 | test('Category: category__not_in', function () { 36 | $args = PostQuery::select() 37 | ->categoryNotIn([1, 2, 3]) 38 | ->getArguments(); 39 | 40 | expect($args)->toMatchArray([ 41 | 'category__not_in' => [1, 2, 3], 42 | ]); 43 | }); 44 | -------------------------------------------------------------------------------- /tests/Feature/CommentTest.php: -------------------------------------------------------------------------------- 1 | commentCount(1) 8 | ->getArguments(); 9 | 10 | expect($args)->toMatchArray([ 11 | 'comment_count' => 1, 12 | ]); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/Feature/DateQueryTest.php: -------------------------------------------------------------------------------- 1 | dateQuery(function (DateQuery $query) { 8 | $query 9 | ->where( 10 | $query->date('edited_at')->before('2022-01-01')->after([ 11 | 'year' => '2021', 12 | 'month' => '01', 13 | 'day' => '01', 14 | ]) 15 | ) 16 | ->inclusive(); 17 | })->getArguments(); 18 | 19 | expect($args)->toMatchArray([ 20 | 'date_query' => [ 21 | 'relation' => 'AND', 22 | [ 23 | 'column' => 'edited_at', 24 | 'before' => [ 25 | 'year' => 2022, 26 | 'month' => 1, 27 | 'day' => 1, 28 | 'hour' => 12, 29 | 'minute' => 0, 30 | 'second' => 0, 31 | ], 32 | 'after' => [ 33 | 'year' => 2021, 34 | 'month' => 1, 35 | 'day' => 1, 36 | ], 37 | ], 38 | 'inclusive' => true, 39 | ], 40 | ] 41 | ); 42 | }); 43 | 44 | test('DateQuery: date_query (or relation)', function () { 45 | $args = PostQuery::select()->dateQuery(function (DateQuery $query) { 46 | $query->where( 47 | $query 48 | ->date('posted_at') 49 | ->between('2021-01-01', '2022-02-01') 50 | )->orWhere( 51 | $query->date()->created()->after('2021-01-01') 52 | ); 53 | })->getArguments(); 54 | 55 | expect($args)->toMatchArray([ 56 | 'date_query' => [ 57 | 'relation' => 'OR', 58 | [ 59 | 'column' => 'posted_at', 60 | 'before' => [ 61 | 'year' => 2022, 62 | 'month' => 2, 63 | 'day' => 1, 64 | 'hour' => 12, 65 | 'minute' => 0, 66 | 'second' => 0, 67 | ], 68 | 'after' => [ 69 | 'year' => 2021, 70 | 'month' => 1, 71 | 'day' => 1, 72 | 'hour' => 12, 73 | 'minute' => 0, 74 | 'second' => 0, 75 | ], 76 | ], 77 | [ 78 | 'column' => 'post_date', 79 | 'after' => [ 80 | 'year' => 2021, 81 | 'month' => 01, 82 | 'day' => 01, 83 | 'hour' => 12, 84 | 'minute' => 0, 85 | 'second' => 0, 86 | ], 87 | ], 88 | ], 89 | ]); 90 | }); 91 | -------------------------------------------------------------------------------- /tests/Feature/FieldTest.php: -------------------------------------------------------------------------------- 1 | fields('ids') 8 | ->getArguments(); 9 | 10 | expect($args)->toMatchArray([ 11 | 'p' => 1, 12 | 'fields' => 'ids', 13 | ]); 14 | }); 15 | 16 | test('Field: wildcard', function () { 17 | $args = PostQuery::find(1) 18 | ->fields('*') 19 | ->getArguments(); 20 | 21 | expect($args)->toMatchArray([ 22 | 'p' => 1, 23 | 'fields' => 'all', 24 | ]); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/Feature/MetaQueryTest.php: -------------------------------------------------------------------------------- 1 | metaQuery(function (MetaQuery $query) { 9 | $query->where( 10 | Query::meta('status')->equalTo('active') 11 | ); 12 | })->getArguments(); 13 | 14 | expect($args)->toMatchArray([ 15 | 'meta_query' => [ 16 | 'relation' => 'AND', 17 | [ 18 | 'key' => 'status', 19 | 'compare' => '=', 20 | 'type' => 'CHAR', 21 | 'value' => 'active', 22 | ], 23 | ], 24 | ]); 25 | }); 26 | 27 | test('MetaQuery: meta_query (or relation)', function () { 28 | $args = PostQuery::select()->metaQuery(function (MetaQuery $query) { 29 | $query 30 | ->where( 31 | $query->meta('status')->equalTo('active') 32 | ) 33 | ->orWhere( 34 | $query->meta('highlighted')->equalTo(1) 35 | ); 36 | 37 | })->getArguments(); 38 | 39 | expect($args)->toMatchArray([ 40 | 'meta_query' => [ 41 | 'relation' => 'OR', 42 | [ 43 | 'key' => 'status', 44 | 'compare' => '=', 45 | 'type' => 'CHAR', 46 | 'value' => 'active', 47 | ], 48 | [ 49 | 'key' => 'highlighted', 50 | 'compare' => '=', 51 | 'type' => 'BINARY', 52 | 'value' => 1, 53 | ], 54 | ], 55 | ]); 56 | }); 57 | 58 | test('MetaQuery: meta_query called twice', function () { 59 | $args = PostQuery::select() 60 | ->metaQuery(function (MetaQuery $query) { 61 | $query 62 | ->where( 63 | $query->meta('status')->equalTo('active') 64 | )->orWhere( 65 | $query->meta('highlighted')->equalTo(0) 66 | ); 67 | }) 68 | ->metaQuery(Query::meta('hide')->notEqualTo(1)) 69 | ->getArguments(); 70 | 71 | expect($args)->toMatchArray([ 72 | 'meta_query' => [ 73 | 'relation' => 'OR', 74 | [ 75 | 'key' => 'status', 76 | 'compare' => '=', 77 | 'type' => 'CHAR', 78 | 'value' => 'active', 79 | ], 80 | [ 81 | 'key' => 'highlighted', 82 | 'compare' => '=', 83 | 'type' => 'BINARY', 84 | 'value' => 0, 85 | ], 86 | [ 87 | 'key' => 'hide', 88 | 'compare' => '!=', 89 | 'type' => 'BINARY', 90 | 'value' => 1, 91 | ], 92 | ], 93 | ]); 94 | }); 95 | 96 | test('MetaQuery: meta_query (and relation)', function () { 97 | $args = PostQuery::select()->metaQuery(function (MetaQuery $query) { 98 | $query 99 | ->where( 100 | $query->meta('date')->greaterThan('2021-01-01') 101 | ) 102 | ->andWhere( 103 | $query->meta('date')->lessOrEqualTo('2023-08-04 23:00:00') 104 | ); 105 | 106 | })->getArguments(); 107 | 108 | expect($args)->toMatchArray([ 109 | 'meta_query' => [ 110 | 'relation' => 'AND', 111 | [ 112 | 'key' => 'date', 113 | 'compare' => '>', 114 | 'type' => 'DATE', 115 | 'value' => '2021-01-01', 116 | ], 117 | [ 118 | 'key' => 'date', 119 | 'compare' => '<=', 120 | 'type' => 'DATETIME', 121 | 'value' => '2023-08-04 23:00:00', 122 | ], 123 | ], 124 | ]); 125 | }); 126 | 127 | test('MetaQuery: meta_query (nested)', function () { 128 | $args = PostQuery::select()->metaQuery(function (MetaQuery $query) { 129 | $query 130 | ->where( 131 | $query->meta('status')->equalTo('active') 132 | ) 133 | ->orWhere(function (MetaQuery $query) { 134 | $query 135 | ->where( 136 | $query->meta('_sku')->equalTo('H123456789') 137 | ) 138 | ->orWhere( 139 | $query->meta('_sku')->equalTo('D123456784') 140 | ); 141 | }) 142 | ->orWhere(function (MetaQuery $query) { 143 | $query 144 | ->where( 145 | $query->meta('promo_cat')->notIn(['summer', 'blackfriday'])->state('promo_cat') 146 | ) 147 | ->andWhere( 148 | $query->meta('promo_cat')->notEqualTo('sales') 149 | ); 150 | }) 151 | ->orWhere( 152 | $query->meta('sale_date') 153 | ->between('2024-01-01', '2024-12-31') 154 | ); 155 | 156 | })->getArguments(); 157 | 158 | expect($args)->toMatchArray([ 159 | 'meta_query' => [ 160 | 'relation' => 'OR', 161 | [ 162 | 'key' => 'status', 163 | 'compare' => '=', 164 | 'type' => 'CHAR', 165 | 'value' => 'active', 166 | ], 167 | [ 168 | 'relation' => 'OR', 169 | [ 170 | 'key' => '_sku', 171 | 'compare' => '=', 172 | 'type' => 'CHAR', 173 | 'value' => 'H123456789', 174 | ], 175 | [ 176 | 'key' => '_sku', 177 | 'compare' => '=', 178 | 'type' => 'CHAR', 179 | 'value' => 'D123456784', 180 | ], 181 | ], 182 | [ 183 | 'relation' => 'AND', 184 | 'promo_cat' => [ 185 | 'key' => 'promo_cat', 186 | 'compare' => 'NOT IN', 187 | 'type' => 'CHAR', 188 | 'value' => [ 189 | 'summer', 190 | 'blackfriday', 191 | ], 192 | ], 193 | [ 194 | 'key' => 'promo_cat', 195 | 'compare' => '!=', 196 | 'type' => 'CHAR', 197 | 'value' => 'sales', 198 | ], 199 | ], 200 | [ 201 | 'key' => 'sale_date', 202 | 'compare' => 'BETWEEN', 203 | 'type' => 'DATE', 204 | 'value' => [ 205 | '2024-01-01', 206 | '2024-12-31', 207 | ], 208 | ], 209 | ], 210 | ]); 211 | }); 212 | -------------------------------------------------------------------------------- /tests/Feature/MimeTypeTest.php: -------------------------------------------------------------------------------- 1 | postMimeType('image/gif') 8 | ->getArguments(); 9 | 10 | expect($args)->toMatchArray([ 11 | 'post_mime_type' => 'image/gif', 12 | ]); 13 | }); 14 | 15 | it('Mimetype: array', function () { 16 | $args = PostQuery::select() 17 | ->postMimeType(['image/jpeg', 'image/gif', 'image/png', 'image/bmp', 'image/tiff', 'image/x-icon']) 18 | ->getArguments(); 19 | 20 | expect($args)->toMatchArray([ 21 | 'post_mime_type' => ['image/jpeg', 'image/gif', 'image/png', 'image/bmp', 'image/tiff', 'image/x-icon'], 22 | ]); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/Feature/OrderTest.php: -------------------------------------------------------------------------------- 1 | orderBy('most_comments') 8 | ->orderBy('post_date', 'ASC') 9 | ->getArguments(); 10 | 11 | expect($args)->toMatchArray([ 12 | 'orderby' => [ 13 | 'most_comments' => 'DESC', 14 | 'post_date' => 'ASC', 15 | ], 16 | ]); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/Feature/PaginationTest.php: -------------------------------------------------------------------------------- 1 | take(5) 8 | ->getArguments(); 9 | 10 | expect($args)->toMatchArray([ 11 | 'posts_per_page' => 5, 12 | ]); 13 | }); 14 | 15 | test('pagination: limit', function () { 16 | $args = PostQuery::select() 17 | ->limit(5) 18 | ->getArguments(); 19 | 20 | expect($args)->toMatchArray([ 21 | 'posts_per_page' => 5, 22 | ]); 23 | }); 24 | 25 | test('pagination: post_per_page', function () { 26 | $args = PostQuery::select() 27 | ->postsPerPage(5) 28 | ->getArguments(); 29 | 30 | expect($args)->toMatchArray([ 31 | 'posts_per_page' => 5, 32 | ]); 33 | }); 34 | 35 | test('pagination: skip', function () { 36 | $args = PostQuery::select() 37 | ->skip(5) 38 | ->getArguments(); 39 | 40 | expect($args)->toMatchArray([ 41 | 'offset' => 5, 42 | ]); 43 | }); 44 | 45 | test('pagination: offset', function () { 46 | $args = PostQuery::select() 47 | ->offset(5) 48 | ->getArguments(); 49 | 50 | expect($args)->toMatchArray([ 51 | 'offset' => 5, 52 | ]); 53 | }); 54 | 55 | test('pagination: nopaging', function () { 56 | $args = PostQuery::select() 57 | ->noPaging() 58 | ->getArguments(); 59 | 60 | expect($args)->toMatchArray([ 61 | 'nopaging' => true, 62 | ]); 63 | }); 64 | 65 | test('pagination: posts_per_archive_page', function () { 66 | $args = PostQuery::select() 67 | ->postsPerArchivePage(5) 68 | ->getArguments(); 69 | 70 | expect($args)->toMatchArray([ 71 | 'posts_per_archive_page' => 5, 72 | ]); 73 | }); 74 | 75 | test('pagination: page', function () { 76 | $args = PostQuery::select() 77 | ->page(666) 78 | ->getArguments(); 79 | 80 | expect($args)->toMatchArray([ 81 | 'page' => 666, 82 | ]); 83 | }); 84 | 85 | test('pagination: ignore_sticky_posts', function () { 86 | $args = PostQuery::select() 87 | ->ignoreStickyPosts() 88 | ->getArguments(); 89 | 90 | expect($args)->toMatchArray([ 91 | 'ignore_sticky_posts' => true, 92 | ]); 93 | }); 94 | -------------------------------------------------------------------------------- /tests/Feature/PasswordTest.php: -------------------------------------------------------------------------------- 1 | true', function () { 6 | $args = PostQuery::select() 7 | ->withPassword() 8 | ->getArguments(); 9 | 10 | expect($args)->toMatchArray([ 11 | 'has_password' => true, 12 | ]); 13 | }); 14 | 15 | test('Password: has_password=>false', function () { 16 | $args = PostQuery::select() 17 | ->withoutPassword() 18 | ->getArguments(); 19 | 20 | expect($args)->toMatchArray([ 21 | 'has_password' => false, 22 | ]); 23 | }); 24 | 25 | test('Password: post_password', function () { 26 | $args = PostQuery::select() 27 | ->withPassword('zxcvbn') 28 | ->getArguments(); 29 | 30 | expect($args)->toMatchArray([ 31 | 'post_password' => 'zxcvbn', 32 | ]); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/Feature/PermissionTest.php: -------------------------------------------------------------------------------- 1 | userPermission('readable') 8 | ->getArguments(); 9 | 10 | expect($args)->toMatchArray([ 11 | 'perm' => 'readable', 12 | ]); 13 | }); 14 | 15 | test('Permission: editable', function () { 16 | $args = PostQuery::select() 17 | ->userPermission('editable') 18 | ->getArguments(); 19 | 20 | expect($args)->toMatchArray([ 21 | 'perm' => 'editable', 22 | ]); 23 | }); 24 | 25 | test('Permission: invalid', function () { 26 | $args = PostQuery::select() 27 | ->userPermission('invalid') 28 | ->getArguments(); 29 | 30 | expect($args)->toMatchArray([ 31 | 'fields' => 'all', 32 | ]); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/Feature/PostTest.php: -------------------------------------------------------------------------------- 1 | postType('article') 8 | ->getArguments(); 9 | 10 | expect($args)->toMatchArray([ 11 | 'post_type' => 'article', 12 | ]); 13 | }); 14 | 15 | test('Post: p (post_id)', function () { 16 | $args = PostQuery::select() 17 | ->postId(42) 18 | ->getArguments(); 19 | 20 | expect($args)->toMatchArray([ 21 | 'p' => 42, 22 | ]); 23 | }); 24 | 25 | test('Post: name (post_slug)', function () { 26 | $args = PostQuery::select() 27 | ->postSlug('mon-article') 28 | ->getArguments(); 29 | 30 | expect($args)->toMatchArray([ 31 | 'name' => 'mon-article', 32 | ]); 33 | }); 34 | 35 | test('Post: post_parent', function () { 36 | $args = PostQuery::select() 37 | ->postParent(5) 38 | ->getArguments(); 39 | 40 | expect($args)->toMatchArray([ 41 | 'post_parent' => 5, 42 | ]); 43 | }); 44 | 45 | test('Post: post_parent__in', function () { 46 | $args = PostQuery::select() 47 | ->whereParentIn([1, 2, 3]) 48 | ->getArguments(); 49 | 50 | expect($args)->toMatchArray([ 51 | 'post_parent__in' => [1, 2, 3], 52 | ]); 53 | }); 54 | 55 | test('Post: post_parent__not_in', function () { 56 | $args = PostQuery::select() 57 | ->whereParentNotIn([4, 5, 6]) 58 | ->getArguments(); 59 | 60 | expect($args)->toMatchArray([ 61 | 'post_parent__not_in' => [4, 5, 6], 62 | ]); 63 | }); 64 | 65 | test('Post: post__in', function () { 66 | $args = PostQuery::select() 67 | ->whereIn([7, 8, 9]) 68 | ->getArguments(); 69 | 70 | expect($args)->toMatchArray([ 71 | 'post__in' => [7, 8, 9], 72 | ]); 73 | }); 74 | 75 | test('Post: post__not_in', function () { 76 | $args = PostQuery::select() 77 | ->whereNotIn([10, 11, 12]) 78 | ->getArguments(); 79 | 80 | expect($args)->toMatchArray([ 81 | 'post__not_in' => [10, 11, 12], 82 | ]); 83 | }); 84 | 85 | test('Post: post_name__in', function () { 86 | $args = PostQuery::select() 87 | ->slugIn(['slug-1', 'slug-2']) 88 | ->getArguments(); 89 | 90 | expect($args)->toMatchArray([ 91 | 'post_name__in' => ['slug-1', 'slug-2'], 92 | ]); 93 | }); 94 | -------------------------------------------------------------------------------- /tests/Feature/SearchTest.php: -------------------------------------------------------------------------------- 1 | search('my keyword') 8 | ->getArguments(); 9 | 10 | expect($args)->toMatchArray([ 11 | 's' => 'my keyword', 12 | ]); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/Feature/StatusTest.php: -------------------------------------------------------------------------------- 1 | postStatus('publish') 8 | ->getArguments(); 9 | 10 | expect($args)->toMatchArray([ 11 | 'post_status' => 'publish', 12 | ]); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/Feature/TagTest.php: -------------------------------------------------------------------------------- 1 | tag('programming') 8 | ->getArguments(); 9 | 10 | expect($args)->toMatchArray([ 11 | 'tag' => 'programming', 12 | ]); 13 | }); 14 | 15 | test('Tag: tag_id', function () { 16 | $args = PostQuery::select() 17 | ->tagId(1) 18 | ->getArguments(); 19 | 20 | expect($args)->toMatchArray([ 21 | 'tag_id' => 1, 22 | ]); 23 | }); 24 | 25 | test('Tag: tag__and', function () { 26 | $args = PostQuery::select() 27 | ->tagAnd([1, 2]) 28 | ->getArguments(); 29 | 30 | expect($args)->toMatchArray([ 31 | 'tag__and' => [1, 2], 32 | ]); 33 | }); 34 | 35 | test('Tag: tag__in', function () { 36 | $args = PostQuery::select() 37 | ->tagIn([3, 4]) 38 | ->getArguments(); 39 | 40 | expect($args)->toMatchArray([ 41 | 'tag__in' => [3, 4], 42 | ]); 43 | }); 44 | 45 | test('Tag: tag__not_in', function () { 46 | $args = PostQuery::select() 47 | ->tagNotIn([5, 6]) 48 | ->getArguments(); 49 | 50 | expect($args)->toMatchArray([ 51 | 'tag__not_in' => [5, 6], 52 | ]); 53 | }); 54 | 55 | test('Tag: tag_slug__and', function () { 56 | $args = PostQuery::select() 57 | ->tagSlugAnd(['dev', 'qa']) 58 | ->getArguments(); 59 | 60 | expect($args)->toMatchArray([ 61 | 'tag_slug__and' => ['dev', 'qa'], 62 | ]); 63 | }); 64 | 65 | test('Tag: tag_slug__in', function () { 66 | $args = PostQuery::select() 67 | ->tagSlugIn(['frontend', 'backend']) 68 | ->getArguments(); 69 | 70 | expect($args)->toMatchArray([ 71 | 'tag_slug__in' => ['frontend', 'backend'], 72 | ]); 73 | }); 74 | -------------------------------------------------------------------------------- /tests/Feature/TaxQueryTest.php: -------------------------------------------------------------------------------- 1 | taxQuery(function (TaxQuery $query) { 8 | $query->where( 9 | $query 10 | ->taxonomy('category') 11 | ->contains(['tee-shirt', 'sportswear']) 12 | ->searchByTermSlug() 13 | ); 14 | })->getArguments(); 15 | 16 | expect($args)->toMatchArray([ 17 | 'tax_query' => [ 18 | 'relation' => 'AND', 19 | [ 20 | 'taxonomy' => 'category', 21 | 'field' => 'slug', 22 | 'terms' => [ 23 | 'tee-shirt', 24 | 'sportswear', 25 | ], 26 | 'operator' => 'IN', 27 | 'include_children' => true, 28 | ], 29 | ], 30 | ]); 31 | }); 32 | 33 | test('TaxQuery: tax_query (or relation)', function () { 34 | $args = PostQuery::select()->taxQuery(function (TaxQuery $query) { 35 | $query->where( 36 | $query 37 | ->taxonomy('category') 38 | ->contains([10, 20]) 39 | ->searchById() 40 | )->orWhere( 41 | $query 42 | ->taxonomy('event') 43 | ->contains(['black-friday', 'christmas-sales']) 44 | ->searchByTermSlug() 45 | ); 46 | })->getArguments(); 47 | 48 | expect($args)->toMatchArray([ 49 | 'tax_query' => [ 50 | 'relation' => 'OR', 51 | [ 52 | 'taxonomy' => 'category', 53 | 'field' => 'term_id', 54 | 'terms' => [ 55 | 10, 56 | 20, 57 | ], 58 | 'operator' => 'IN', 59 | 'include_children' => true, 60 | ], 61 | [ 62 | 'taxonomy' => 'event', 63 | 'field' => 'slug', 64 | 'terms' => [ 65 | 'black-friday', 66 | 'christmas-sales', 67 | ], 68 | 'operator' => 'IN', 69 | 'include_children' => true, 70 | ], 71 | ], 72 | ]); 73 | }); 74 | 75 | test('TaxQuery: tax_query (nested)', function () { 76 | $args = PostQuery::select()->taxQuery(function (TaxQuery $query) { 77 | $query 78 | ->where( 79 | $query->taxonomy('status')->notContains(['private']) 80 | ) 81 | ->orWhere(function (TaxQuery $query) { 82 | $query 83 | ->where( 84 | $query->taxonomy('attributes')->exists() 85 | ) 86 | ->orWhere( 87 | $query->taxonomy('product_cat')->contains(['tee-shirt', 'sportswear']) 88 | ); 89 | }) 90 | ->orWhere(function (TaxQuery $query) { 91 | $query 92 | ->where( 93 | $query->taxonomy('promo_cat')->contains(['summer', 'blackfriday']) 94 | ) 95 | ->andWhere( 96 | $query->taxonomy('new_products')->notExists() 97 | ); 98 | }); 99 | 100 | })->getArguments(); 101 | 102 | expect($args)->toMatchArray([ 103 | 'tax_query' => [ 104 | 'relation' => 'OR', 105 | [ 106 | 'taxonomy' => 'status', 107 | 'field' => 'term_id', 108 | 'terms' => [ 109 | 'private', 110 | ], 111 | 'operator' => 'NOT IN', 112 | 'include_children' => true, 113 | ], 114 | [ 115 | 'relation' => 'OR', 116 | [ 117 | 'taxonomy' => 'attributes', 118 | 'field' => 'term_id', 119 | 'terms' => null, 120 | 'operator' => 'EXISTS', 121 | 'include_children' => true, 122 | ], 123 | [ 124 | 'taxonomy' => 'product_cat', 125 | 'field' => 'term_id', 126 | 'terms' => [ 127 | 'tee-shirt', 128 | 'sportswear', 129 | ], 130 | 'operator' => 'IN', 131 | 'include_children' => true, 132 | ], 133 | ], 134 | [ 135 | 'relation' => 'AND', 136 | [ 137 | 'taxonomy' => 'promo_cat', 138 | 'field' => 'term_id', 139 | 'terms' => [ 140 | 'summer', 141 | 'blackfriday', 142 | ], 143 | 'operator' => 'IN', 144 | 'include_children' => true, 145 | ], 146 | [ 147 | 'taxonomy' => 'new_products', 148 | 'field' => 'term_id', 149 | 'terms' => null, 150 | 'operator' => 'NOT EXISTS', 151 | 'include_children' => true, 152 | ], 153 | ], 154 | ], 155 | ]); 156 | }); 157 | -------------------------------------------------------------------------------- /tests/Feature/ValueTypeTest.php: -------------------------------------------------------------------------------- 1 | null, 'expected' => \Pollora\Query\Utils\Types\CharType::CHAR, 'message' => 'with null value'], 7 | ['value' => -5, 'expected' => \Pollora\Query\Utils\Types\NumberType::SIGNED, 'message' => 'with negative integer value'], 8 | ['value' => 5, 'expected' => \Pollora\Query\Utils\Types\NumberType::UNSIGNED, 'message' => 'with positive integer value'], 9 | ['value' => 0, 'expected' => \Pollora\Query\Utils\Types\BooleanType::BINARY, 'message' => 'with negative binary value'], 10 | ['value' => 1, 'expected' => \Pollora\Query\Utils\Types\BooleanType::BINARY, 'message' => 'with positive binary value'], 11 | ['value' => 5.5, 'expected' => \Pollora\Query\Utils\Types\NumberType::DECIMAL, 'message' => 'with float value'], 12 | ['value' => '5', 'expected' => \Pollora\Query\Utils\Types\NumberType::NUMERIC, 'message' => 'with numeric string value'], 13 | ['value' => '2021-01-01', 'expected' => \Pollora\Query\Utils\Types\DatetimeType::DATE, 'message' => 'with date value'], 14 | ['value' => '12:34:56', 'expected' => \Pollora\Query\Utils\Types\DatetimeType::TIME, 'message' => 'with time value'], 15 | ['value' => '2021-01-01 12:34:56', 'expected' => \Pollora\Query\Utils\Types\DatetimeType::DATETIME, 'message' => 'with datetime value'], 16 | ['value' => 'SomeString', 'expected' => \Pollora\Query\Utils\Types\CharType::CHAR, 'message' => 'with other string value'], 17 | ['value' => 'SomeString', 'type' => 'DATETIME', 'expected' => \Pollora\Query\Utils\Types\DatetimeType::DATETIME, 'message' => 'with forced type'], 18 | ]; 19 | 20 | foreach ($dataProvider as $data) { 21 | $value = $data['value']; 22 | $expected = $data['expected']; 23 | $message = $data['message']; 24 | $forcedType = $data['type'] ?? null; 25 | 26 | test("MetaQueryBuilder: detect $message", function () use ($value, $expected, $forcedType) { 27 | expect((new ValueTypeDetector($value, $forcedType))->detect())->toBe($expected); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | in('Feature'); 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Expectations 21 | |-------------------------------------------------------------------------- 22 | | 23 | | When you're writing tests, you often need to check that values meet certain conditions. The 24 | | "expect()" function gives you access to a set of "expectations" methods that you can use 25 | | to assert different things. Of course, you may extend the Expectation API at any time. 26 | | 27 | */ 28 | 29 | expect()->extend('toBeOne', fn () => $this->toBe(1)); 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Functions 34 | |-------------------------------------------------------------------------- 35 | | 36 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your 37 | | project that you don't want to repeat in every file. Here you can also expose helpers as 38 | | global functions to help you to reduce the number of lines of code in your test files. 39 | | 40 | */ 41 | 42 | function something() 43 | { 44 | // .. 45 | } 46 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 |