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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ 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.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.

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.

## [2.0.4] — 2026-08-08

### Security
Expand Down Expand Up @@ -231,6 +244,7 @@ every install was non-functional regardless of configuration.

- Initial release.

[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
[2.0.2]: https://github.com/ibuilder/ProcoreWP/releases/tag/v2.0.2
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.4
* Version: 2.0.5
* Requires at least: 6.5
* Requires PHP: 7.4
* Author: ibuilder
Expand All @@ -22,7 +22,7 @@

defined( 'ABSPATH' ) || exit;

const VERSION = '2.0.4';
const VERSION = '2.0.5';

define( 'PROCORE_CONNECT_VERSION', VERSION );
define( 'PROCORE_CONNECT_FILE', __FILE__ );
Expand Down
9 changes: 8 additions & 1 deletion 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.4
Stable tag: 2.0.5
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

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

== Changelog ==

= 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.

= 2.0.4 =

* Security documentation no longer recommends 2.0.0 or 2.0.1, the versions that damage the stored Client Secret. Both are now marked unsupported with the remedy stated plainly.
Expand Down Expand Up @@ -183,6 +187,9 @@ A complete rewrite. See the upgrade notice below before updating.

== Upgrade Notice ==

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

= 2.0.4 =
Documentation and test coverage only. No functional change from 2.0.3.

Expand Down
40 changes: 39 additions & 1 deletion 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->resolve_columns( $atts ),
'columns' => $this->drop_empty_columns( $this->resolve_columns( $atts ), $rows, ! empty( $atts['show_email'] ) ),
'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 @@ -64,6 +64,44 @@ protected function output( $data, array $atts ): string {
);
}

/**
* Remove columns that render nothing for every 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.
*
* @param array<int, array<string, string>> $columns Column definitions.
* @param array<int, mixed> $rows Records being rendered.
* @param bool $show_email Whether the shortcode opted in to email output.
* @return array<int, array<string, string>> Columns that carry at least one value.
*/
protected function drop_empty_columns( array $columns, array $rows, bool $show_email ): array {
$kept = array();

foreach ( $columns as $column ) {
$has_value = false;

foreach ( $rows as $row ) {
if ( '' !== trim( Format::cell( $row, $column, $show_email ) ) ) {
$has_value = true;
break;
}
}

if ( $has_value ) {
$kept[] = $column;
}
}

// Never render a table with no columns at all.
return empty( $kept ) ? $columns : $kept;
}

/**
* The field to sort on, honouring the ProcoreWP 1.x `sort_by` alias.
*
Expand Down
70 changes: 70 additions & 0 deletions tests/unit/ShortcodeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,76 @@ public function test_team_hides_emails_by_default(): void {
$this->assertStringNotContainsString( 'dana@example.com', $html );
}

/**
* A column that renders nothing for every row must not be shown.
*
* Email suppression is on by default, so `[procore_team]` used to emit an
* Email header and a blank cell for every member.
*
* @return void
*/
public function test_drops_a_column_that_is_empty_for_every_row(): void {
$this->client_returning(
array(
$this->response(
array(
array(
'id' => 3,
'name' => 'Dana Reed',
'job_title' => 'PM',
'email_address' => 'dana@example.com',
),
)
),
)
);

$html = $this->shortcode( 'procore_team' )->render(
array(
'project_id' => '5',
'company_id' => '9',
)
);

$this->assertStringContainsString( 'Dana Reed', $html );
$this->assertStringNotContainsString( '>Email<', $html );
}

/**
* A column with data must survive the empty-column filter.
*
* @return void
*/
public function test_keeps_columns_that_carry_a_value(): void {
Settings::set( 'suppress_emails', false );

$this->client_returning(
array(
$this->response(
array(
array(
'id' => 3,
'name' => 'Dana Reed',
'job_title' => 'PM',
'email_address' => 'dana@example.com',
),
)
),
)
);

$html = $this->shortcode( 'procore_team' )->render(
array(
'project_id' => '5',
'company_id' => '9',
'show_email' => 'true',
)
);

$this->assertStringContainsString( '>Email<', $html );
$this->assertStringContainsString( 'dana@example.com', $html );
}

/**
* A field outside the allow-list must be refused.
*
Expand Down
Loading