Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .distignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
/docs
/tests
/node_modules
/vendor/bin
# Every dependency is dev-only — the plugin autoloads its own classes and
# requires nothing at runtime, so none of this belongs in a release.
/vendor
/build
# Generated by the CI checks that run immediately before the build is staged.
.phpunit.result.cache
Expand Down
33 changes: 31 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,43 @@ 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.6] — 2026-08-09

### Fixed

- **A column named in `columns=` is no longer dropped.** 2.0.5 applied its
empty-column filter to every table, including one where the page author had listed
the columns by hand. `[procore_team columns="name,email_address"]` rendered only
Name — no header, no cell, no explanation. The filter now applies only to the
default column set, which is the set nobody chose; an explicit list is rendered
verbatim, empty or not.

- **A column no record populates is now actually dropped.** 2.0.5 claimed to do this
but could not: a missing value renders as a placeholder dash, not as an empty
string, so the length test it used only ever matched a suppressed email address.
Every other empty column survived as a full column of dashes. Emptiness is now
judged by meaning rather than by length, so `[procore_rfis]` against a project
where no RFI has a due date drops the Due Date column as documented.

- **A column's visibility no longer depends on its neighbours.** Because the
never-render-an-empty-table fallback restored the whole list, `columns="email_address"`
kept the Email column while `columns="name,email_address"` dropped it — identical
data, opposite outcome. Adding a column to a working shortcode could silently remove
a different one.

Found by reviewing the 2.0.5 diff and checking each claim in its own commit message
against what the code does.

## [2.0.5] — 2026-08-08

### Fixed

- **`[procore_team]` rendered an empty Email column.** Email suppression is on by
default, so the Email header appeared above a blank cell for every team member —
visible dead weight on the most commonly used shortcode, on every default install.
Columns that render nothing for any row are now dropped. This also covers a column
no record happens to populate, and a table is never reduced to no columns at all.
The Email column is now dropped when it is blank for every row, and a table is never
reduced to no columns at all. (This entry originally also claimed to drop any column
no record populates. It did not — see 2.0.6, which makes that true.)

Found by standing up a real WordPress site, serving it over HTTP and reading what a
visitor actually gets, rather than by asserting on strings.
Expand Down Expand Up @@ -244,6 +272,7 @@ every install was non-functional regardless of configuration.

- Initial release.

[2.0.6]: https://github.com/ibuilder/ProcoreWP/releases/tag/v2.0.6
[2.0.5]: https://github.com/ibuilder/ProcoreWP/releases/tag/v2.0.5
[2.0.4]: https://github.com/ibuilder/ProcoreWP/releases/tag/v2.0.4
[2.0.3]: https://github.com/ibuilder/ProcoreWP/releases/tag/v2.0.3
Expand Down
20 changes: 19 additions & 1 deletion docs/shortcodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Accepted by every shortcode.
| `page` | `1` | Which page of results to request |
| `orderby` | — | Any field path, e.g. `name` or `vendor.name` |
| `order` | `asc` | `asc` or `desc` |
| `columns` | Per shortcode | Comma-separated field paths |
| `columns` | Per shortcode | Comma-separated field paths; see [Empty columns](#empty-columns) |
| `template` | Per shortcode | Template name, overridable in your theme |
| `class` | — | Extra CSS classes, sanitized |
| `title` | Per shortcode | Heading text; pass `-` to omit the heading |
Expand All @@ -37,6 +37,24 @@ Accepted by every shortcode.

Records missing the `orderby` field always sort last, in both directions.

### Empty columns

A default column that no record fills in is not rendered. Procore projects vary in
which tools they use and which fields they populate, so a default column set that
suits one project can produce a column of dashes on another. Rather than show a
header above nothing, the column is left out.

This applies only to the default columns — the ones you did not choose. If you pass
`columns=` the list is rendered exactly as written, empty or not, because you asked
for it:

```
[procore_rfis id="123"] Due Date drops if no RFI has one
[procore_rfis id="123" columns="number,due_date"] Due Date always renders
```

A table is never reduced to no columns at all.

---

## Projects
Expand Down
4 changes: 2 additions & 2 deletions procore-connect.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.5
* Version: 2.0.6
* Requires at least: 6.5
* Requires PHP: 7.4
* Author: ibuilder
Expand All @@ -22,7 +22,7 @@

defined( 'ABSPATH' ) || exit;

const VERSION = '2.0.5';
const VERSION = '2.0.6';

define( 'PROCORE_CONNECT_VERSION', VERSION );
define( 'PROCORE_CONNECT_FILE', __FILE__ );
Expand Down
13 changes: 11 additions & 2 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.5
Stable tag: 2.0.6
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Expand Down Expand Up @@ -127,9 +127,15 @@ Yes. Caching goes through the transient API, so a persistent object cache such a

== Changelog ==

= 2.0.6 =

* Fixed: a column you named in `columns=` could be dropped. 2.0.5 applied its empty-column filter to hand-written column lists too, so `[procore_team columns="name,email_address"]` rendered only Name. An explicit list is now rendered exactly as written; only the default column set is trimmed.
* Fixed: a column no record fills in is now genuinely hidden. 2.0.5 said it did this but could not — a missing value renders as a dash rather than as nothing, so only a suppressed email address was ever hidden and every other empty column showed as a full column of dashes.
* Fixed: whether a column appeared no longer depends on the other columns. `columns="email_address"` kept the Email column while `columns="name,email_address"` dropped it.

= 2.0.5 =

* Fixed: `[procore_team]` showed an empty Email column. Email output is suppressed by default, so the column appeared with a header and blank cells on every default install. Columns with no value in any row are now hidden.
* Fixed: `[procore_team]` showed an empty Email column. Email output is suppressed by default, so the column appeared with a header and blank cells on every default install. The Email column is now hidden when it is blank for every row. (This entry also claimed to hide any column with no value in any row; that did not work until 2.0.6.)

= 2.0.4 =

Expand Down Expand Up @@ -187,6 +193,9 @@ A complete rewrite. See the upgrade notice below before updating.

== Upgrade Notice ==

= 2.0.6 =
Corrects the 2.0.5 column-hiding fix, which dropped columns you had asked for by name and did not drop the empty ones it claimed to. Recommended for anyone on 2.0.5 who sets `columns=` on a shortcode.

= 2.0.5 =
Cosmetic fix: the team table no longer shows an empty Email column when email output is suppressed, which is the default.

Expand Down
39 changes: 31 additions & 8 deletions src/Frontend/Shortcodes/CollectionShortcode.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ protected function output( $data, array $atts ): string {
$this->template( $atts ),
array(
'rows' => $rows,
'columns' => $this->drop_empty_columns( $this->resolve_columns( $atts ), $rows, ! empty( $atts['show_email'] ) ),
'columns' => $this->visible_columns( $atts, $rows ),
'title' => $this->title( $atts ),
'class' => Format::classes( 'procore-connect procore-connect-collection procore-connect-' . str_replace( '_', '-', $this->endpoint ), (string) $atts['class'] ),
'show_email' => ! empty( $atts['show_email'] ),
Expand All @@ -65,15 +65,38 @@ protected function output( $data, array $atts ): string {
}

/**
* Remove columns that render nothing for every row.
* The columns this render should actually show.
*
* When the author named the columns, that list is used verbatim: they asked
* for a column, they get it, even if every row is blank. Only the default
* column set — which the author never saw and did not choose — is trimmed
* to what the data supports.
*
* @param array<string, mixed> $atts Sanitized attributes.
* @param array<int, mixed> $rows Records being rendered.
* @return array<int, array<string, string>> Column definitions to render.
*/
protected function visible_columns( array $atts, array $rows ): array {
$columns = $this->resolve_columns( $atts );

if ( '' !== (string) $atts['columns'] || '' !== (string) $atts['fields'] ) {
return $columns;
}

return $this->drop_empty_columns( $columns, $rows, ! empty( $atts['show_email'] ) );
}

/**
* Remove columns that carry no information for any row.
*
* The visible case is `[procore_team]`: email suppression is on by default,
* so the Email column produced a header and a blank cell for every member —
* dead weight on the most commonly used shortcode. This also covers a
* column no record happens to populate.
*
* A column an author asked for explicitly is always kept, so `columns=""`
* still means what it says.
* dead weight on the most commonly used shortcode. It also covers a column
* no record populates, such as a due date nobody has set. Those two look
* different in the markup — a suppressed email renders as nothing, an
* absent date renders as a placeholder dash — so emptiness is judged by
* {@see Format::is_blank()} rather than by string length, which would only
* ever catch the first.
*
* @param array<int, array<string, string>> $columns Column definitions.
* @param array<int, mixed> $rows Records being rendered.
Expand All @@ -87,7 +110,7 @@ protected function drop_empty_columns( array $columns, array $rows, bool $show_e
$has_value = false;

foreach ( $rows as $row ) {
if ( '' !== trim( Format::cell( $row, $column, $show_email ) ) ) {
if ( ! Format::is_blank( Format::cell( $row, $column, $show_email ) ) ) {
$has_value = true;
break;
}
Expand Down
40 changes: 34 additions & 6 deletions src/Support/Format.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,34 @@
*/
final class Format {

/**
* Stands in for a value Procore did not supply.
*
* An em dash reads as "no value here" in a table without leaving the cell
* looking broken. Because it is what an absent value renders as, anything
* deciding whether a cell carries information has to recognise it — see
* `is_blank()`.
*
* @var string
*/
public const PLACEHOLDER = '—';

/**
* Whether a rendered cell carries no information.
*
* A cell is blank when it is empty or when it is nothing but the
* placeholder. Both mean Procore returned no value; the difference is only
* whether the column's format prints a dash for it.
*
* @param string $cell Output of `cell()`.
* @return bool True when the cell conveys nothing.
*/
public static function is_blank( string $cell ): bool {
$trimmed = trim( $cell );

return '' === $trimmed || self::PLACEHOLDER === $trimmed;
}

/**
* Format an ISO-8601 or `Y-m-d` date using the configured format.
*
Expand Down Expand Up @@ -139,21 +167,21 @@ public static function cell( $record, array $column, bool $show_email = false ):

switch ( $format ) {
case 'date':
return esc_html( self::date( $raw, '—' ) );
return esc_html( self::date( $raw, self::PLACEHOLDER ) );

case 'currency':
return esc_html( self::currency( $raw, '—' ) );
return esc_html( self::currency( $raw, self::PLACEHOLDER ) );

case 'percent':
return is_numeric( $raw )
? esc_html( number_format_i18n( (float) $raw, 0 ) . '%' )
: '—';
: self::PLACEHOLDER;

case 'status':
$active = null === $raw ? null : (bool) $raw;

if ( null === $active ) {
return '—';
return self::PLACEHOLDER;
}

return sprintf(
Expand All @@ -179,7 +207,7 @@ public static function cell( $record, array $column, bool $show_email = false ):
$url = esc_url_raw( Arr::stringify( $raw ), array( 'http', 'https' ) );

if ( '' === $url ) {
return '—';
return self::PLACEHOLDER;
}

$host = wp_parse_url( $url, PHP_URL_HOST );
Expand All @@ -193,7 +221,7 @@ public static function cell( $record, array $column, bool $show_email = false ):
default:
$value = Arr::stringify( $raw );

return '' === $value ? '—' : esc_html( $value );
return '' === $value ? self::PLACEHOLDER : esc_html( $value );
}//end switch
}

Expand Down
Loading
Loading