diff --git a/CHANGELOG.md b/CHANGELOG.md index fb69693..03b0d3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,34 @@ All notable changes to Procore Connect are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.1] — 2026-08-08 + +Four defects found by running the plugin inside a real WordPress install +against a mocked Procore API — a surface the unit tests, which shim WordPress +functions, cannot reach. + +### Fixed + +- **Pagination parameters were sent to single-record endpoints.** Every request + carried `page=1`, including `/projects/{id}` and `/me`, which have no pages. + It was meaningless noise on the wire and it varied the cache key for what was + the same request. Pagination is now applied only to endpoints the registry + marks as paginated. +- **The REST proxy ignored the configured default company.** The route always + sends `company_id` as an integer defaulting to `0`, and the client treated a + supplied zero as a deliberate choice rather than an omission, so the site + default never applied and every request failed with a missing-company error. + A zero identifier now falls back to the configured default. +- **Translations were loaded before `init`.** Shortcode definitions carry + translated titles and were built on `plugins_loaded`, which triggers + WordPress 6.7's `_load_textdomain_just_in_time` notice and silently drops + translations. Shortcode registration is now deferred to `init`. +- **WP-CLI subcommands did not match the documentation.** They were registered + as `cache_clear`, `cache_warm` and `reset_token`; the documentation + advertised the hyphenated forms. Added `@subcommand` annotations so + `wp procore-connect cache-clear`, `cache-warm` and `reset-token` work as + documented. + ## [2.0.0] — 2026-08-07 A complete rewrite. Version 1.x could not authenticate against the live Procore API, so @@ -124,5 +152,6 @@ every install was non-functional regardless of configuration. - Initial release. +[2.0.1]: https://github.com/ibuilder/ProcoreWP/releases/tag/v2.0.1 [2.0.0]: https://github.com/ibuilder/ProcoreWP/releases/tag/v2.0.0 [1.0.0]: https://github.com/ibuilder/ProcoreWP/releases/tag/v1.0.0 diff --git a/languages/procore-connect.pot b/languages/procore-connect.pot index f3383b8..077639d 100644 --- a/languages/procore-connect.pot +++ b/languages/procore-connect.pot @@ -65,11 +65,11 @@ msgstr "" msgid "A service account keeps working when staff change and is installed by a Procore company administrator from the App Marketplace. A user connection inherits one person's permissions and breaks if their access is revoked." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:234 +#: src/Frontend/Shortcodes/Registrar.php:248 msgid "A single allow-listed field from a project record." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:67 +#: src/Frontend/Shortcodes/Registrar.php:81 msgid "A table of Procore projects for a company." msgstr "" @@ -91,8 +91,8 @@ msgstr "" msgid "Added inline after the plugin stylesheet. This survives plugin updates — editing files inside the plugin directory does not." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:112 -#: src/Frontend/Shortcodes/Registrar.php:488 +#: src/Frontend/Shortcodes/Registrar.php:126 +#: src/Frontend/Shortcodes/Registrar.php:502 msgid "Address" msgstr "" @@ -160,11 +160,11 @@ msgstr "" msgid "Change order packages" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:388 +#: src/Frontend/Shortcodes/Registrar.php:402 msgid "Change order packages raised on a project." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:387 +#: src/Frontend/Shortcodes/Registrar.php:401 msgid "Change orders" msgstr "" @@ -196,12 +196,12 @@ msgstr "" msgid "Companies" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:450 +#: src/Frontend/Shortcodes/Registrar.php:464 msgid "Companies in the Procore company directory." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:171 -#: src/Frontend/Shortcodes/Registrar.php:454 +#: src/Frontend/Shortcodes/Registrar.php:185 +#: src/Frontend/Shortcodes/Registrar.php:468 msgid "Company" msgstr "" @@ -232,11 +232,11 @@ msgstr "" msgid "Company offices" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:437 +#: src/Frontend/Shortcodes/Registrar.php:451 msgid "Complete" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:142 +#: src/Frontend/Shortcodes/Registrar.php:156 msgid "Completion date" msgstr "" @@ -317,11 +317,11 @@ msgstr "" msgid "Daily construction report logs" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:364 +#: src/Frontend/Shortcodes/Registrar.php:378 msgid "Daily construction report logs for a project." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:363 +#: src/Frontend/Shortcodes/Registrar.php:377 msgid "Daily logs" msgstr "" @@ -329,7 +329,7 @@ msgstr "" msgid "Database transients" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:368 +#: src/Frontend/Shortcodes/Registrar.php:382 msgid "Date" msgstr "" @@ -356,13 +356,13 @@ msgstr "" msgid "Depends on the endpoint attribute" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:201 -#: src/Frontend/Shortcodes/Registrar.php:220 -#: src/Frontend/Shortcodes/Registrar.php:314 +#: src/Frontend/Shortcodes/Registrar.php:215 +#: src/Frontend/Shortcodes/Registrar.php:234 +#: src/Frontend/Shortcodes/Registrar.php:328 msgid "Description" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:108 +#: src/Frontend/Shortcodes/Registrar.php:122 msgid "Detail panel for a single Procore project." msgstr "" @@ -394,7 +394,7 @@ msgstr "" msgid "Drawing areas" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:192 +#: src/Frontend/Shortcodes/Registrar.php:206 msgid "Drawing areas published for a project." msgstr "" @@ -402,7 +402,7 @@ msgstr "" msgid "Drawing revisions" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:191 +#: src/Frontend/Shortcodes/Registrar.php:205 msgid "Drawings" msgstr "" @@ -411,10 +411,10 @@ msgstr "" msgid "Drawings: Read Only" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:264 -#: src/Frontend/Shortcodes/Registrar.php:294 -#: src/Frontend/Shortcodes/Registrar.php:324 -#: src/Frontend/Shortcodes/Registrar.php:354 +#: src/Frontend/Shortcodes/Registrar.php:278 +#: src/Frontend/Shortcodes/Registrar.php:308 +#: src/Frontend/Shortcodes/Registrar.php:338 +#: src/Frontend/Shortcodes/Registrar.php:368 msgid "Due" msgstr "" @@ -422,7 +422,7 @@ msgstr "" msgid "Each environment needs its own credentials; tokens are not shared between them." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:176 +#: src/Frontend/Shortcodes/Registrar.php:190 msgid "Email" msgstr "" @@ -459,7 +459,7 @@ msgstr "" msgid "Expose cached Procore data at /wp-json/procore-connect/v1/" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:432 +#: src/Frontend/Shortcodes/Registrar.php:446 msgid "Finish" msgstr "" @@ -467,7 +467,7 @@ msgstr "" msgid "For the strongest protection, define PROCORE_CONNECT_CLIENT_SECRET in wp-config.php instead of storing it here." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:79 +#: src/Frontend/Shortcodes/Registrar.php:93 msgid "ID" msgstr "" @@ -475,7 +475,7 @@ msgstr "" msgid "Inactive" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:309 +#: src/Frontend/Shortcodes/Registrar.php:323 msgid "Item" msgstr "" @@ -507,10 +507,10 @@ msgstr "" msgid "Loading…" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:94 -#: src/Frontend/Shortcodes/Registrar.php:117 -#: src/Frontend/Shortcodes/Registrar.php:459 -#: src/Frontend/Shortcodes/Registrar.php:493 +#: src/Frontend/Shortcodes/Registrar.php:108 +#: src/Frontend/Shortcodes/Registrar.php:131 +#: src/Frontend/Shortcodes/Registrar.php:473 +#: src/Frontend/Shortcodes/Registrar.php:507 msgid "Location" msgstr "" @@ -534,8 +534,8 @@ msgstr "" msgid "Monthly Sandbox" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:161 -#: src/Frontend/Shortcodes/Registrar.php:196 +#: src/Frontend/Shortcodes/Registrar.php:175 +#: src/Frontend/Shortcodes/Registrar.php:210 msgid "Name" msgstr "" @@ -555,7 +555,7 @@ msgstr "" msgid "No Client Secret is set." msgstr "" -#: src/Api/Client.php:182 +#: src/Api/Client.php:196 msgid "No Procore company ID is available. Set a default company in Procore → Connection, or pass company_id." msgstr "" @@ -599,7 +599,7 @@ msgstr "" msgid "Not set" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:373 +#: src/Frontend/Shortcodes/Registrar.php:387 msgid "Notes" msgstr "" @@ -607,12 +607,12 @@ msgstr "" msgid "Nothing recorded. Turn on diagnostics under Tools to start collecting events." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:89 -#: src/Frontend/Shortcodes/Registrar.php:215 -#: src/Frontend/Shortcodes/Registrar.php:249 -#: src/Frontend/Shortcodes/Registrar.php:279 -#: src/Frontend/Shortcodes/Registrar.php:339 -#: src/Frontend/Shortcodes/Registrar.php:393 +#: src/Frontend/Shortcodes/Registrar.php:103 +#: src/Frontend/Shortcodes/Registrar.php:229 +#: src/Frontend/Shortcodes/Registrar.php:263 +#: src/Frontend/Shortcodes/Registrar.php:293 +#: src/Frontend/Shortcodes/Registrar.php:353 +#: src/Frontend/Shortcodes/Registrar.php:407 msgid "Number" msgstr "" @@ -620,12 +620,12 @@ msgstr "" msgid "Object cache" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:344 +#: src/Frontend/Shortcodes/Registrar.php:358 msgid "Observation" msgstr "" #: src/Api/Endpoints.php:221 -#: src/Frontend/Shortcodes/Registrar.php:333 +#: src/Frontend/Shortcodes/Registrar.php:347 msgid "Observations" msgstr "" @@ -642,15 +642,15 @@ msgstr "" msgid "Off by default, so deleting the plugin removes its credentials, tokens and cache." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:483 +#: src/Frontend/Shortcodes/Registrar.php:497 msgid "Office" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:478 +#: src/Frontend/Shortcodes/Registrar.php:492 msgid "Offices" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:479 +#: src/Frontend/Shortcodes/Registrar.php:493 msgid "Offices registered against the Procore company." msgstr "" @@ -675,7 +675,7 @@ msgstr "" msgid "Optional. Used whenever a project-scoped shortcode omits project_id." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:304 +#: src/Frontend/Shortcodes/Registrar.php:318 msgid "Outstanding punch list items for a project." msgstr "" @@ -687,8 +687,8 @@ msgstr "" msgid "Persistent" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:464 -#: src/Frontend/Shortcodes/Registrar.php:498 +#: src/Frontend/Shortcodes/Registrar.php:478 +#: src/Frontend/Shortcodes/Registrar.php:512 msgid "Phone" msgstr "" @@ -696,7 +696,7 @@ msgstr "" msgid "Plugin version" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:122 +#: src/Frontend/Shortcodes/Registrar.php:136 msgid "Postcode" msgstr "" @@ -745,7 +745,7 @@ msgstr "" msgid "Procore has not reported a rate limit yet." msgstr "" -#: src/Api/Client.php:415 +#: src/Api/Client.php:433 msgid "Procore is rate limiting this site. Cached data will be shown until the limit resets." msgstr "" @@ -757,7 +757,7 @@ msgstr "" msgid "Procore recommends keeping this at or below 2000." msgstr "" -#: src/Api/Client.php:394 +#: src/Api/Client.php:412 msgid "Procore rejected the access token." msgstr "" @@ -770,15 +770,15 @@ msgstr "" msgid "Procore requests are paused after repeated failures and will resume in %s. Cached data is being served in the meantime." msgstr "" -#: src/Api/Client.php:225 +#: src/Api/Client.php:243 msgid "Procore requests are paused after repeated failures. They will resume automatically." msgstr "" -#: src/Api/Client.php:467 +#: src/Api/Client.php:485 msgid "Procore returned HTTP %d." msgstr "" -#: src/Api/Client.php:482 +#: src/Api/Client.php:500 msgid "Procore returned a response that could not be decoded." msgstr "" @@ -800,7 +800,7 @@ msgstr "" msgid "Production" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:84 +#: src/Frontend/Shortcodes/Registrar.php:98 msgid "Project" msgstr "" @@ -822,16 +822,16 @@ msgstr "" msgid "Project information is temporarily unavailable." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:507 +#: src/Frontend/Shortcodes/Registrar.php:521 msgid "Project locations" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:127 +#: src/Frontend/Shortcodes/Registrar.php:141 msgid "Project number" msgstr "" #: src/Api/Endpoints.php:131 -#: src/Frontend/Shortcodes/Registrar.php:156 +#: src/Frontend/Shortcodes/Registrar.php:170 msgid "Project team" msgstr "" @@ -840,7 +840,7 @@ msgid "Project vendors" msgstr "" #: src/Api/Endpoints.php:110 -#: src/Frontend/Shortcodes/Registrar.php:66 +#: src/Frontend/Shortcodes/Registrar.php:80 msgid "Projects" msgstr "" @@ -849,7 +849,7 @@ msgstr "" msgid "Projects are readable for company %d." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:508 +#: src/Frontend/Shortcodes/Registrar.php:522 msgid "Projects that have coordinates, as an accessible location list with geo microdata." msgstr "" @@ -858,11 +858,11 @@ msgid "Punch List: Read Only" msgstr "" #: src/Api/Endpoints.php:210 -#: src/Frontend/Shortcodes/Registrar.php:303 +#: src/Frontend/Shortcodes/Registrar.php:317 msgid "Punch list" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:334 +#: src/Frontend/Shortcodes/Registrar.php:348 msgid "Quality and safety observations recorded on a project." msgstr "" @@ -875,7 +875,7 @@ msgid "REST proxy" msgstr "" #: src/Api/Endpoints.php:190 -#: src/Frontend/Shortcodes/Registrar.php:243 +#: src/Frontend/Shortcodes/Registrar.php:257 msgid "RFIs" msgstr "" @@ -928,13 +928,13 @@ msgstr "" #. translators: %d: number of cache entries removed. #: src/Admin/Ajax.php:77 -#: src/Cli/Commands.php:101 +#: src/Cli/Commands.php:103 msgid "Removed %d cached response." msgid_plural "Removed %d cached responses." msgstr[0] "" msgstr[1] "" -#: src/Frontend/Shortcodes/Registrar.php:517 +#: src/Frontend/Shortcodes/Registrar.php:531 msgid "Render any endpoint published in the Procore endpoint registry." msgstr "" @@ -942,7 +942,7 @@ msgstr "" msgid "Request timeout" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:244 +#: src/Frontend/Shortcodes/Registrar.php:258 msgid "Requests for information raised on a project." msgstr "" @@ -950,11 +950,11 @@ msgstr "" msgid "Response cache" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:225 +#: src/Frontend/Shortcodes/Registrar.php:239 msgid "Revision" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:166 +#: src/Frontend/Shortcodes/Registrar.php:180 msgid "Role" msgstr "" @@ -966,7 +966,7 @@ msgstr "" msgid "Runs through authentication, company access and every endpoint the shortcodes use, so you can see exactly which Procore tool permissions your credentials have." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:417 +#: src/Frontend/Shortcodes/Registrar.php:431 msgid "Schedule" msgstr "" @@ -974,7 +974,7 @@ msgstr "" msgid "Schedule tasks" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:418 +#: src/Frontend/Shortcodes/Registrar.php:432 msgid "Schedule tasks and milestones for a project." msgstr "" @@ -1018,11 +1018,11 @@ msgstr "" msgid "Specification sections" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:211 +#: src/Frontend/Shortcodes/Registrar.php:225 msgid "Specification sections published for a project." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:210 +#: src/Frontend/Shortcodes/Registrar.php:224 msgid "Specifications" msgstr "" @@ -1030,27 +1030,27 @@ msgstr "" msgid "Specifications: Read Only" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:132 +#: src/Frontend/Shortcodes/Registrar.php:146 msgid "Stage" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:427 +#: src/Frontend/Shortcodes/Registrar.php:441 msgid "Start" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:137 +#: src/Frontend/Shortcodes/Registrar.php:151 msgid "Start date" msgstr "" #: src/Admin/SettingsPage.php:147 -#: src/Frontend/Shortcodes/Registrar.php:99 -#: src/Frontend/Shortcodes/Registrar.php:147 -#: src/Frontend/Shortcodes/Registrar.php:259 -#: src/Frontend/Shortcodes/Registrar.php:289 -#: src/Frontend/Shortcodes/Registrar.php:319 -#: src/Frontend/Shortcodes/Registrar.php:349 -#: src/Frontend/Shortcodes/Registrar.php:378 -#: src/Frontend/Shortcodes/Registrar.php:403 +#: src/Frontend/Shortcodes/Registrar.php:113 +#: src/Frontend/Shortcodes/Registrar.php:161 +#: src/Frontend/Shortcodes/Registrar.php:273 +#: src/Frontend/Shortcodes/Registrar.php:303 +#: src/Frontend/Shortcodes/Registrar.php:333 +#: src/Frontend/Shortcodes/Registrar.php:363 +#: src/Frontend/Shortcodes/Registrar.php:392 +#: src/Frontend/Shortcodes/Registrar.php:417 msgid "Status" msgstr "" @@ -1062,16 +1062,16 @@ msgstr "" msgid "Stylesheet" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:254 +#: src/Frontend/Shortcodes/Registrar.php:268 msgid "Subject" msgstr "" #: src/Api/Endpoints.php:200 -#: src/Frontend/Shortcodes/Registrar.php:273 +#: src/Frontend/Shortcodes/Registrar.php:287 msgid "Submittals" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:274 +#: src/Frontend/Shortcodes/Registrar.php:288 msgid "Submittals tracked on a project." msgstr "" @@ -1079,11 +1079,11 @@ msgstr "" msgid "Submittals: Read Only" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:422 +#: src/Frontend/Shortcodes/Registrar.php:436 msgid "Task" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:157 +#: src/Frontend/Shortcodes/Registrar.php:171 msgid "Team members assigned to a project. Email addresses are hidden unless explicitly enabled." msgstr "" @@ -1115,7 +1115,7 @@ msgstr "" msgid "The Procore connection has expired. Open Procore → Connection and reconnect." msgstr "" -#: src/Api/Client.php:425 +#: src/Api/Client.php:443 msgid "The Procore request could not be completed." msgstr "" @@ -1138,7 +1138,7 @@ msgstr "" msgid "The field attribute is required." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:185 +#: src/Frontend/Shortcodes/Registrar.php:199 msgid "The logo or featured image for a project." msgstr "" @@ -1166,8 +1166,8 @@ msgstr "" msgid "This site is not connected to Procore. Open Procore → Connection and choose \"Connect to Procore\"." msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:284 -#: src/Frontend/Shortcodes/Registrar.php:398 +#: src/Frontend/Shortcodes/Registrar.php:298 +#: src/Frontend/Shortcodes/Registrar.php:412 msgid "Title" msgstr "" @@ -1209,11 +1209,11 @@ msgstr "" msgid "Users who can edit posts" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:408 +#: src/Frontend/Shortcodes/Registrar.php:422 msgid "Value" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:449 +#: src/Frontend/Shortcodes/Registrar.php:463 msgid "Vendors" msgstr "" @@ -1226,7 +1226,7 @@ msgstr "" msgid "View %s on a map" msgstr "" -#: src/Frontend/Shortcodes/Registrar.php:469 +#: src/Frontend/Shortcodes/Registrar.php:483 msgid "Website" msgstr "" diff --git a/procore-connect.php b/procore-connect.php index 6dd6149..dbdcd40 100644 --- a/procore-connect.php +++ b/procore-connect.php @@ -3,7 +3,7 @@ * Plugin Name: Procore Connect * Plugin URI: https://github.com/ibuilder/ProcoreWP * Description: Connect WordPress to the Procore construction management platform. Display projects, teams, drawings, RFIs and more with shortcodes, blocks and a cached REST proxy. - * Version: 2.0.0 + * Version: 2.0.1 * Requires at least: 6.5 * Requires PHP: 7.4 * Author: ibuilder @@ -22,7 +22,7 @@ defined( 'ABSPATH' ) || exit; -const VERSION = '2.0.0'; +const VERSION = '2.0.1'; define( 'PROCORE_CONNECT_VERSION', VERSION ); define( 'PROCORE_CONNECT_FILE', __FILE__ ); diff --git a/readme.txt b/readme.txt index d9b4e50..bef0866 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: procore, construction, project management, shortcode, api Requires at least: 6.5 Tested up to: 7.0 Requires PHP: 7.4 -Stable tag: 2.0.0 +Stable tag: 2.0.1 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -136,6 +136,15 @@ Yes. Caching goes through the transient API, so a persistent object cache such a == Changelog == += 2.0.1 = + +Fixes found by running the plugin inside a real WordPress install. + +* Fixed: pagination parameters were sent to single-record endpoints such as `/projects/{id}`, which have no pages. This was noise on the wire and varied the cache key for identical requests. +* Fixed: the REST proxy ignored the configured default company, so every request failed with a missing-company error unless company_id was passed explicitly. +* Fixed: translations were loaded before `init`, which triggers a `_load_textdomain_just_in_time` notice on WordPress 6.7+ and silently drops translations. +* Fixed: WP-CLI subcommands were registered with underscores while the documentation advertised hyphens. `wp procore-connect cache-clear`, `cache-warm` and `reset-token` now work as documented. + = 2.0.0 = A complete rewrite. See the upgrade notice below before updating. @@ -167,5 +176,8 @@ A complete rewrite. See the upgrade notice below before updating. == Upgrade Notice == += 2.0.1 = +Bug fixes from real-world integration testing: pagination on single-record endpoints, the REST proxy ignoring the default company, translations loading too early, and WP-CLI subcommand names. No action required. + = 2.0.0 = Rewrite. Fixes authentication, adds caching and encrypts credentials. Settings migrate and all 1.x shortcodes still work, but the main file was renamed: activate the plugin once, re-run the connection test, and rotate your client secret. diff --git a/src/Api/Client.php b/src/Api/Client.php index b0e9660..8bcc36c 100644 --- a/src/Api/Client.php +++ b/src/Api/Client.php @@ -173,8 +173,22 @@ public function fetch( string $slug, array $params = array(), array $options = a ); } - $company = isset( $options['company_id'] ) ? absint( $options['company_id'] ) : Settings::default_company_id(); - $project = isset( $options['project_id'] ) ? absint( $options['project_id'] ) : 0; + /* + * A zero is treated as "not supplied" rather than as an explicit + * choice, so callers that always pass the key — the REST proxy sends + * an integer parameter that defaults to 0 — still inherit the site + * defaults instead of failing with a missing-context error. + */ + $company = absint( $options['company_id'] ?? 0 ); + $project = absint( $options['project_id'] ?? 0 ); + + if ( $company <= 0 ) { + $company = Settings::default_company_id(); + } + + if ( $project <= 0 ) { + $project = Settings::default_project_id(); + } if ( Endpoints::SCOPE_NONE !== $definition['scope'] && $company <= 0 ) { return new \WP_Error( @@ -193,7 +207,11 @@ public function fetch( string $slug, array $params = array(), array $options = a if ( ! empty( $definition['paginated'] ) ) { $query['per_page'] = min( 2000, max( 1, (int) ( $options['per_page'] ?? Settings::get( 'per_page', 100 ) ) ) ); - $query['page'] = max( 1, (int) ( $query['page'] ?? 1 ) ); + $query['page'] = max( 1, (int) ( $query['page'] ?? $options['page'] ?? 1 ) ); + } else { + // A single-record resource has no pages; sending them is meaningless + // noise that also varies the cache key for identical requests. + unset( $query['page'], $query['per_page'] ); } $ttl = isset( $options['ttl'] ) ? (int) $options['ttl'] : (int) $definition['ttl']; diff --git a/src/Cli/Commands.php b/src/Cli/Commands.php index d335c87..d8edbd7 100644 --- a/src/Cli/Commands.php +++ b/src/Cli/Commands.php @@ -87,6 +87,8 @@ public function test( array $args, array $assoc_args ): void { * wp procore-connect cache-clear * wp procore-connect cache-clear --group=rfis * + * @subcommand cache-clear + * * @param array $args Positional arguments. * @param array $assoc_args Associative arguments. * @return void @@ -111,6 +113,8 @@ public function cache_clear( array $args, array $assoc_args ): void { * * wp procore-connect cache-warm * + * @subcommand cache-warm + * * @param array $args Positional arguments. * @param array $assoc_args Associative arguments. * @return void @@ -208,6 +212,8 @@ public function projects( array $args, array $assoc_args ): void { * * wp procore-connect reset-token * + * @subcommand reset-token + * * @param array $args Positional arguments. * @param array $assoc_args Associative arguments. * @return void diff --git a/src/Frontend/Shortcodes/AbstractShortcode.php b/src/Frontend/Shortcodes/AbstractShortcode.php index 4e322c5..b9b9a9d 100644 --- a/src/Frontend/Shortcodes/AbstractShortcode.php +++ b/src/Frontend/Shortcodes/AbstractShortcode.php @@ -222,17 +222,19 @@ protected function resolve( array $atts ) { /** * Query parameters sent with the request. * + * Pagination is deliberately not added here. It is passed through + * `fetch_options()` so the client can drop it for endpoints the registry + * marks as unpaginated — sending `page` to a single-record resource such as + * `/projects/{id}` is meaningless and needlessly varies the cache key. + * + * The parameter is retained because this is an extension point: subclasses + * override it to add endpoint-specific filters derived from the attributes. + * * @param array $atts Sanitized attributes. * @return array Query parameters. */ - protected function query_args( array $atts ): array { - $args = array(); - - if ( ! empty( $atts['page'] ) ) { - $args['page'] = (int) $atts['page']; - } - - return $args; + protected function query_args( array $atts ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Extension point for subclasses. + return array(); } /** @@ -246,6 +248,7 @@ protected function fetch_options( array $atts ): array { 'company_id' => $this->company_id( $atts ), 'project_id' => $this->project_id( $atts ), 'all' => ! empty( $atts['all'] ), + 'page' => max( 1, (int) $atts['page'] ), ); if ( '' !== $atts['cache'] && $atts['cache'] > 0 ) { diff --git a/src/Frontend/Shortcodes/Registrar.php b/src/Frontend/Shortcodes/Registrar.php index 7d8587a..496b56a 100644 --- a/src/Frontend/Shortcodes/Registrar.php +++ b/src/Frontend/Shortcodes/Registrar.php @@ -32,11 +32,25 @@ final class Registrar { private static $definitions = null; /** - * Register every shortcode with WordPress. + * Hook shortcode registration. + * + * Deferred to `init` because the definitions carry translated titles and + * descriptions. Building them on `plugins_loaded` would ask WordPress to + * load the text domain before `init`, which since 6.7 emits a + * `_load_textdomain_just_in_time` notice and silently drops translations. * * @return void */ public function register(): void { + add_action( 'init', array( $this, 'register_shortcodes' ) ); + } + + /** + * Register every shortcode with WordPress. + * + * @return void + */ + public function register_shortcodes(): void { foreach ( self::definitions() as $tag => $definition ) { $handler = $definition['handler']; $instance = new $handler( $definition ); diff --git a/tests/unit/ClientTest.php b/tests/unit/ClientTest.php index 96703d6..9d5c632 100644 --- a/tests/unit/ClientTest.php +++ b/tests/unit/ClientTest.php @@ -65,6 +65,57 @@ public function test_sets_timeout_and_user_agent(): void { $this->assertStringContainsString( 'Procore Connect/2.0.0', $calls[0]['args']['user-agent'] ); } + /** + * A single-record endpoint must not receive pagination parameters. + * + * `page` and `per_page` are meaningless on `/projects/{id}`, and including + * them also varies the cache key for what is the same request. + * + * @return void + */ + public function test_unpaginated_endpoints_get_no_pagination_params(): void { + $calls = array(); + $client = $this->client_returning( array( $this->response( array( 'id' => 123 ) ) ), $calls ); + + $client->fetch( + 'project', + array( 'page' => 3 ), + array( + 'company_id' => 1, + 'project_id' => 123, + 'per_page' => 50, + 'page' => 3, + ) + ); + + $this->assertSame( 'https://api.procore.com/rest/v1.0/projects/123', $calls[0]['url'] ); + $this->assertStringNotContainsString( 'page=', $calls[0]['url'] ); + } + + /** + * A paginated endpoint must still honour an explicitly requested page. + * + * @return void + */ + public function test_paginated_endpoints_honour_the_requested_page(): void { + $calls = array(); + $client = $this->client_returning( array( $this->response( array() ) ), $calls ); + + $client->fetch( + 'rfis', + array(), + array( + 'company_id' => 1, + 'project_id' => 123, + 'page' => 4, + 'per_page' => 25, + ) + ); + + $this->assertStringContainsString( 'page=4', $calls[0]['url'] ); + $this->assertStringContainsString( 'per_page=25', $calls[0]['url'] ); + } + /** * `all` must follow the Link header rather than guessing page numbers. * @@ -238,6 +289,36 @@ public function test_returns_an_error_when_no_fallback_exists(): void { $this->assertSame( 'Forbidden', $result->get_error_message() ); } + /** + * A zero identifier must fall back to the site default, not fail. + * + * The REST proxy always sends `company_id` and `project_id` as integers + * that default to 0, so treating a supplied zero as an explicit choice + * made the proxy ignore the configured defaults entirely. + * + * @return void + */ + public function test_zero_identifiers_fall_back_to_site_defaults(): void { + Settings::set( 'default_company_id', 4242 ); + Settings::set( 'default_project_id', 77 ); + + $calls = array(); + $client = $this->client_returning( array( $this->response( array() ) ), $calls ); + + $result = $client->fetch( + 'rfis', + array(), + array( + 'company_id' => 0, + 'project_id' => 0, + ) + ); + + $this->assertFalse( is_wp_error( $result ) ); + $this->assertSame( '4242', $calls[0]['args']['headers']['Procore-Company-Id'] ); + $this->assertStringContainsString( '/projects/77/rfis', $calls[0]['url'] ); + } + /** * A company-scoped endpoint must refuse to run without a company. *