-
Notifications
You must be signed in to change notification settings - Fork 1
feat(wptablebuilder): add WP Table Builder action integration #200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
35fe35e
feat(wptablebuilder): add WP Table Builder action integration
RishadAlam 7456444
refactor: clean up comments and adjust required field logic in WpTabl…
RishadAlam 19a8bed
fix: handle missing keys in field mapping and improve error handling …
RishadAlam 0e4efba
Merge branch 'main' into feat/wptablebuilder
RishadAlam d71c2c2
refactor(WpTableBuilder): remove authorization logic and integrate Au…
RishadAlam d224f9e
fix(WpTableBuilder): update condition for rendering add row button
RishadAlam dfbe910
Merge branch 'main' into feat/wptablebuilder
RishadAlam 5870825
Merge branch 'main' into feat/wptablebuilder
RishadAlam File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * WP Table Builder Record Api | ||
| */ | ||
|
|
||
| namespace BitApps\Integrations\Actions\WpTableBuilder; | ||
|
|
||
| use BitApps\Integrations\Config; | ||
| use BitApps\Integrations\Core\Util\Common; | ||
| use BitApps\Integrations\Core\Util\Hooks; | ||
| use BitApps\Integrations\Log\LogHandler; | ||
|
|
||
| /** | ||
| * Provide functionality for WP Table Builder table writes | ||
| */ | ||
| class RecordApiHelper | ||
| { | ||
| private $_integrationID; | ||
|
|
||
| private $_integrationDetails; | ||
|
|
||
| public function __construct($integrationDetails, $integId) | ||
| { | ||
| $this->_integrationDetails = $integrationDetails; | ||
| $this->_integrationID = $integId; | ||
| } | ||
|
|
||
| /** | ||
| * Execute the integration | ||
| * | ||
| * @param array $fieldValues Field values from trigger | ||
| * @param array $fieldMap Field mapping | ||
| * @param array $utilities Optional actions | ||
| * | ||
| * @return array | ||
| */ | ||
| public function execute($fieldValues, $fieldMap, $utilities) | ||
| { | ||
| if (!\defined('WPTB_PLUGIN_DIR')) { | ||
| return [ | ||
| 'success' => false, | ||
| 'message' => __('WP Table Builder is not installed or activated', 'bit-integrations') | ||
| ]; | ||
| } | ||
|
|
||
| $fieldData = static::generateReqDataFromFieldMap($fieldMap, $fieldValues); | ||
|
|
||
| // No fallback action: every action writes, and delete_table removes a table, so | ||
| // a flow that lost its mainAction should fail through the default branch. | ||
| $mainAction = $this->_integrationDetails->mainAction ?? ''; | ||
|
|
||
| $defaultResponse = [ | ||
| 'success' => false, | ||
| // translators: %s: Plugin name | ||
| 'message' => wp_sprintf(__('%s plugin is not installed or activated', 'bit-integrations'), 'Bit Integrations Pro') | ||
| ]; | ||
|
|
||
| switch ($mainAction) { | ||
| case 'create_table': | ||
| $response = Hooks::apply(Config::withPrefix('wptablebuilder_create_table'), $defaultResponse, $fieldData); | ||
| $type = 'table'; | ||
| $actionType = 'create_table'; | ||
|
|
||
| break; | ||
|
|
||
| case 'update_table': | ||
| $response = Hooks::apply(Config::withPrefix('wptablebuilder_update_table'), $defaultResponse, $fieldData); | ||
| $type = 'table'; | ||
| $actionType = 'update_table'; | ||
|
|
||
| break; | ||
|
|
||
| case 'delete_table': | ||
| $response = Hooks::apply(Config::withPrefix('wptablebuilder_delete_table'), $defaultResponse, $fieldData, $utilities); | ||
| $type = 'table'; | ||
| $actionType = 'delete_table'; | ||
|
|
||
| break; | ||
|
|
||
| case 'add_row': | ||
| $response = Hooks::apply(Config::withPrefix('wptablebuilder_add_row'), $defaultResponse, $fieldData, $this->_integrationDetails); | ||
| $type = 'row'; | ||
| $actionType = 'add_row'; | ||
|
|
||
| break; | ||
|
|
||
| default: | ||
| $response = [ | ||
| 'success' => false, | ||
| 'message' => __('Invalid action', 'bit-integrations') | ||
| ]; | ||
| $type = 'WpTableBuilder'; | ||
| $actionType = 'unknown'; | ||
|
|
||
| break; | ||
| } | ||
|
|
||
| $responseType = isset($response['success']) && $response['success'] ? 'success' : 'error'; | ||
| LogHandler::save($this->_integrationID, ['type' => $type, 'type_name' => $actionType], $responseType, $response); | ||
|
|
||
| return $response; | ||
| } | ||
|
|
||
| private static function generateReqDataFromFieldMap($fieldMap, $fieldValues) | ||
| { | ||
| $dataFinal = []; | ||
| foreach ($fieldMap as $item) { | ||
| // Direct property reads, so unlike isset()/empty() these do warn when a | ||
| // stored field-map row is missing a key. | ||
| $triggerValue = $item->formField ?? ''; | ||
| $actionValue = $item->wpTableBuilderField ?? ''; | ||
|
|
||
| if (empty($actionValue)) { | ||
| continue; | ||
| } | ||
|
|
||
| $dataFinal[$actionValue] = $triggerValue === 'custom' && isset($item->customValue) | ||
| ? Common::replaceFieldWithValue($item->customValue, $fieldValues) | ||
| : $fieldValues[$triggerValue] ?? ''; | ||
| } | ||
|
|
||
| return $dataFinal; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <?php | ||
|
|
||
| if (!defined('ABSPATH')) { | ||
| exit; | ||
| } | ||
|
|
||
| use BitApps\Integrations\Actions\WpTableBuilder\WpTableBuilderController; | ||
| use BitApps\Integrations\Core\Util\Route; | ||
|
|
||
| // Create/Update/Delete take table_id through the field map, so a flow can target a | ||
| // different table per run. Add Row is the exception: its column list has to be known | ||
| // while the flow is being configured, which only a fixed table can provide. | ||
| Route::post('refresh_wptablebuilder_tables', [WpTableBuilderController::class, 'refreshTables']); | ||
| Route::post('refresh_wptablebuilder_columns', [WpTableBuilderController::class, 'refreshColumns']); |
175 changes: 175 additions & 0 deletions
175
backend/Actions/WpTableBuilder/WpTableBuilderController.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * WP Table Builder Integration | ||
| */ | ||
|
|
||
| namespace BitApps\Integrations\Actions\WpTableBuilder; | ||
|
|
||
| use DOMDocument; | ||
| use DOMXPath; | ||
| use WP_Error; | ||
|
|
||
| /** | ||
| * Provide functionality for WP Table Builder integration | ||
| */ | ||
| class WpTableBuilderController | ||
| { | ||
| /** | ||
| * Tables are a custom post type, and the table body lives in a single post meta. | ||
| * Mirrors WP Table Builder's Cpt::POST_TYPE. | ||
| */ | ||
| public const POST_TYPE = 'wptb-tables'; | ||
|
|
||
| public const CONTENT_META_KEY = '_wptb_content_'; | ||
|
|
||
| public static function isExists() | ||
| { | ||
| if (!\defined('WPTB_PLUGIN_DIR')) { | ||
| wp_send_json_error( | ||
| __( | ||
| 'WP Table Builder is not activated or not installed', | ||
| 'bit-integrations' | ||
| ), | ||
| 400 | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * List the tables an Add Row flow can append to. | ||
| * | ||
| * Trashed tables are excluded — appending to one would silently write into a table | ||
| * nobody can see. | ||
| */ | ||
| public static function refreshTables() | ||
| { | ||
| self::isExists(); | ||
|
|
||
| $tables = get_posts( | ||
| [ | ||
| 'post_type' => self::POST_TYPE, | ||
| 'post_status' => ['publish', 'draft', 'pending', 'private', 'future'], | ||
| 'numberposts' => -1, | ||
| 'orderby' => 'title', | ||
| 'order' => 'ASC', | ||
| 'suppress_filters' => true, | ||
| ] | ||
| ); | ||
|
|
||
| $response['tables'] = array_map( | ||
| function ($table) { | ||
| return [ | ||
| 'value' => (string) $table->ID, | ||
| // An untitled table is still selectable, so fall back to the id. | ||
| 'label' => $table->post_title === '' | ||
| // Translators: %d is the table's post ID. WP Table Builder tables are a custom post type, and the title is optional. | ||
| ? wp_sprintf(__('Table #%d', 'bit-integrations'), $table->ID) | ||
| : $table->post_title, | ||
| ]; | ||
| }, | ||
| $tables | ||
| ); | ||
|
|
||
| wp_send_json_success($response); | ||
| } | ||
|
|
||
| /** | ||
| * Read a table's column labels from its header row so the field map can be built | ||
| * with real names instead of positional placeholders. | ||
| * | ||
| * @param mixed $requestParams | ||
| */ | ||
| public static function refreshColumns($requestParams) | ||
| { | ||
| self::isExists(); | ||
|
|
||
| if (empty($requestParams->selectedTable)) { | ||
| wp_send_json_error(__('Select a table first', 'bit-integrations'), 400); | ||
| } | ||
|
|
||
| $table = get_post((int) $requestParams->selectedTable); | ||
|
|
||
| if (!$table || $table->post_type !== self::POST_TYPE) { | ||
| wp_send_json_error(__('Table not found', 'bit-integrations'), 400); | ||
| } | ||
|
|
||
| $columns = self::readColumnLabels(get_post_meta($table->ID, self::CONTENT_META_KEY, true)); | ||
|
|
||
| if (empty($columns)) { | ||
| wp_send_json_error( | ||
| __('No columns found. Open the table in WP Table Builder and add at least one row.', 'bit-integrations'), | ||
| 400 | ||
| ); | ||
| } | ||
|
|
||
| wp_send_json_success(['columns' => $columns]); | ||
| } | ||
|
|
||
| public function execute($integrationData, $fieldValues) | ||
| { | ||
| $integrationDetails = $integrationData->flow_details; | ||
| $integId = $integrationData->id; | ||
| $fieldMap = $integrationDetails->field_map; | ||
| $utilities = isset($integrationDetails->utilities) ? $integrationDetails->utilities : []; | ||
|
|
||
| if (empty($fieldMap)) { | ||
| return new WP_Error('field_map_empty', __('Field map is empty', 'bit-integrations')); | ||
| } | ||
|
|
||
| $recordApiHelper = new RecordApiHelper($integrationDetails, $integId); | ||
|
|
||
| return $recordApiHelper->execute($fieldValues, $fieldMap, $utilities); | ||
| } | ||
|
|
||
| /** | ||
| * Extract one entry per column from the first row of the stored table markup. | ||
| * | ||
| * The stored value is rendered HTML, so the header labels are read from the DOM | ||
| * rather than from any structured source — WP Table Builder does not keep one. | ||
| * | ||
| * @param string $content | ||
| * | ||
| * @return array | ||
| */ | ||
| private static function readColumnLabels($content) | ||
| { | ||
| if (empty($content) || !\class_exists('DOMDocument')) { | ||
| return []; | ||
| } | ||
|
|
||
| $dom = new DOMDocument(); | ||
| $previous = libxml_use_internal_errors(true); | ||
| // The stored markup is a fragment, and it is authored content that routinely | ||
| // trips libxml — parse errors here are expected and must not surface. | ||
| $dom->loadHTML( | ||
| '<?xml encoding="utf-8" ?>' . $content, | ||
| LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | ||
| ); | ||
| libxml_clear_errors(); | ||
| libxml_use_internal_errors($previous); | ||
|
|
||
| $xpath = new DOMXPath($dom); | ||
| $firstRow = $xpath->query('//tr')->item(0); | ||
|
|
||
| if (!$firstRow) { | ||
| return []; | ||
| } | ||
|
|
||
| $columns = []; | ||
|
|
||
| foreach ($xpath->query('.//th|.//td', $firstRow) as $index => $cell) { | ||
| $label = trim($cell->textContent); | ||
|
|
||
| $columns[] = [ | ||
| 'key' => 'cell_' . $index, | ||
| 'label' => $label === '' | ||
| ? wp_sprintf(__('Column %d', 'bit-integrations'), $index + 1) | ||
| : $label, | ||
| 'required' => false, | ||
| ]; | ||
| } | ||
|
|
||
| return $columns; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To prevent potential PHP warnings or errors (such as attempting to read property on null) when the request body is empty or invalid, add a defensive check to ensure
$requestParamsis not empty before accessing its properties.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Declining —
empty()already covers this.empty($requestParams->selectedTable)does not evaluate the property read when$requestParamsis null; it returnstruesilently. Verified on the PHP this runs against:Only a direct read warns. So
empty($requestParams) ||is a second condition that can never change the outcome.The same review flagged a case where the read is direct —
$item->formFieldinRecordApiHelper— and that one was real and is now fixed.