-
Notifications
You must be signed in to change notification settings - Fork 1
feat(latepoint): add LatePoint booking integration #197
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
Changes from all commits
87c4e4a
6e35b82
cef7419
af36ce7
5205d75
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * LatePoint Integration | ||
| */ | ||
|
|
||
| namespace BitApps\Integrations\Actions\LatePoint; | ||
|
|
||
| use WP_Error; | ||
|
|
||
| /** | ||
| * Provide functionality for LatePoint integration | ||
| */ | ||
| class LatePointController | ||
| { | ||
| public static function isExists() | ||
| { | ||
| if (!class_exists('LatePoint')) { | ||
| wp_send_json_error( | ||
| __( | ||
| 'LatePoint is not activated or not installed', | ||
| 'bit-integrations' | ||
| ), | ||
| 400 | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| public static function latePointAuthorize() | ||
| { | ||
| self::isExists(); | ||
| wp_send_json_success(true); | ||
| } | ||
|
|
||
| public function refreshAgents() | ||
| { | ||
| self::isExists(); | ||
|
|
||
| $response['agents'] = self::fetchRows( | ||
| 'LATEPOINT_TABLE_AGENTS', | ||
| 'latepoint_agents', | ||
| ['id', 'first_name', 'last_name', 'email'], | ||
| function ($row) { | ||
| return (object) [ | ||
| 'value' => $row['id'], | ||
| 'label' => trim(($row['first_name'] ?? '') . ' ' . ($row['last_name'] ?? '')) ?: ($row['email'] ?? $row['id']), | ||
| ]; | ||
| } | ||
| ); | ||
|
|
||
| wp_send_json_success($response, 200); | ||
| } | ||
|
|
||
| public function refreshServices() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since public static function refreshServices()References
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as the |
||
| { | ||
| self::isExists(); | ||
|
|
||
| $response['services'] = self::fetchRows( | ||
| 'LATEPOINT_TABLE_SERVICES', | ||
| 'latepoint_services', | ||
| ['id', 'name'], | ||
| function ($row) { | ||
| return (object) [ | ||
| 'value' => $row['id'], | ||
| 'label' => $row['name'] ?? $row['id'], | ||
| ]; | ||
| } | ||
| ); | ||
|
|
||
| wp_send_json_success($response, 200); | ||
| } | ||
|
|
||
| public function refreshLocations() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since public static function refreshLocations()References
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as the |
||
| { | ||
| self::isExists(); | ||
|
|
||
| $response['locations'] = self::fetchRows( | ||
| 'LATEPOINT_TABLE_LOCATIONS', | ||
| 'latepoint_locations', | ||
| ['id', 'name'], | ||
| function ($row) { | ||
| return (object) [ | ||
| 'value' => $row['id'], | ||
| 'label' => $row['name'] ?? $row['id'], | ||
| ]; | ||
| } | ||
| ); | ||
|
|
||
| wp_send_json_success($response, 200); | ||
| } | ||
|
|
||
| public function refreshBundles() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since public static function refreshBundles()References
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as the |
||
| { | ||
| self::isExists(); | ||
|
|
||
| $response['bundles'] = self::fetchRows( | ||
| 'LATEPOINT_TABLE_BUNDLES', | ||
| 'latepoint_bundles', | ||
| ['id', 'name'], | ||
| function ($row) { | ||
| return (object) [ | ||
| 'value' => $row['id'], | ||
| 'label' => $row['name'] ?? $row['id'], | ||
| ]; | ||
| } | ||
| ); | ||
|
|
||
| wp_send_json_success($response, 200); | ||
| } | ||
|
|
||
| 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); | ||
| } | ||
|
|
||
| /** | ||
| * Read id/label pairs from a LatePoint table. | ||
| * | ||
| * LatePoint's Os*Model classes are only loaded on LatePoint's own screens, so the | ||
| * dropdown lists are read straight from the tables instead. | ||
| * | ||
| * @param string $constant Name of LatePoint's own table constant, preferred so a | ||
| * future rename or re-prefix is picked up automatically | ||
| * @param string $fallback Unprefixed table name, used when LatePoint has not | ||
| * defined the constant yet | ||
| * @param array $columns Columns to select — hardcoded literals, never user input | ||
| * @param callable $mapper Maps one row to a {value,label} object | ||
| * | ||
| * @return array | ||
| */ | ||
| private static function fetchRows($constant, $fallback, array $columns, $mapper) | ||
| { | ||
| global $wpdb; | ||
|
|
||
| if (!$wpdb) { | ||
| return []; | ||
| } | ||
|
|
||
| $tableName = \defined($constant) ? \constant($constant) : $wpdb->prefix . $fallback; | ||
|
|
||
| $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $tableName)); | ||
|
|
||
| if ($exists !== $tableName) { | ||
| return []; | ||
| } | ||
|
|
||
| $columnList = implode(', ', array_map('sanitize_key', $columns)); | ||
|
|
||
| // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery -- table and column names are hardcoded literals verified above | ||
| $rows = $wpdb->get_results("SELECT {$columnList} FROM {$tableName}", ARRAY_A); | ||
|
|
||
| if (empty($rows)) { | ||
| return []; | ||
| } | ||
|
|
||
| return array_map($mapper, $rows); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| <?php | ||
|
|
||
| /** | ||
| * LatePoint Record Api | ||
| */ | ||
|
|
||
| namespace BitApps\Integrations\Actions\LatePoint; | ||
|
|
||
| use BitApps\Integrations\Config; | ||
| use BitApps\Integrations\Core\Util\Common; | ||
| use BitApps\Integrations\Core\Util\Hooks; | ||
| use BitApps\Integrations\Log\LogHandler; | ||
|
|
||
| /** | ||
| * Provide functionality for LatePoint booking, customer, agent, order and coupon 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 (!class_exists('LatePoint')) { | ||
| return [ | ||
| 'success' => false, | ||
| 'message' => __('LatePoint is not installed or activated', 'bit-integrations') | ||
| ]; | ||
| } | ||
|
|
||
| $fieldData = static::generateReqDataFromFieldMap($fieldMap, $fieldValues); | ||
|
|
||
| $mainAction = $this->_integrationDetails->mainAction ?? 'create_booking'; | ||
| $integrationDetails = $this->_integrationDetails; | ||
|
|
||
| $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_booking': | ||
| $response = Hooks::apply(Config::withPrefix('latepoint_create_booking'), $defaultResponse, $fieldData, $utilities, $integrationDetails); | ||
| $type = 'booking'; | ||
| $actionType = 'create_booking'; | ||
|
|
||
| break; | ||
|
|
||
| case 'update_booking': | ||
| $response = Hooks::apply(Config::withPrefix('latepoint_update_booking'), $defaultResponse, $fieldData, $utilities, $integrationDetails); | ||
| $type = 'booking'; | ||
| $actionType = 'update_booking'; | ||
|
|
||
| break; | ||
|
|
||
| case 'cancel_booking': | ||
| $response = Hooks::apply(Config::withPrefix('latepoint_cancel_booking'), $defaultResponse, $fieldData); | ||
| $type = 'booking'; | ||
| $actionType = 'cancel_booking'; | ||
|
|
||
| break; | ||
|
|
||
| case 'create_agent': | ||
| $response = Hooks::apply(Config::withPrefix('latepoint_create_agent'), $defaultResponse, $fieldData, $integrationDetails); | ||
| $type = 'agent'; | ||
| $actionType = 'create_agent'; | ||
|
|
||
| break; | ||
|
|
||
| case 'create_customer': | ||
| $response = Hooks::apply(Config::withPrefix('latepoint_create_customer'), $defaultResponse, $fieldData); | ||
| $type = 'customer'; | ||
| $actionType = 'create_customer'; | ||
|
|
||
| break; | ||
|
|
||
| case 'create_order': | ||
| $response = Hooks::apply(Config::withPrefix('latepoint_create_order'), $defaultResponse, $fieldData, $utilities, $integrationDetails); | ||
| $type = 'order'; | ||
| $actionType = 'create_order'; | ||
|
|
||
| break; | ||
|
|
||
| case 'create_coupon': | ||
| $response = Hooks::apply(Config::withPrefix('latepoint_create_coupon'), $defaultResponse, $fieldData, $integrationDetails); | ||
| $type = 'coupon'; | ||
| $actionType = 'create_coupon'; | ||
|
|
||
| break; | ||
|
|
||
| case 'update_coupon': | ||
| $response = Hooks::apply(Config::withPrefix('latepoint_update_coupon'), $defaultResponse, $fieldData, $integrationDetails); | ||
| $type = 'coupon'; | ||
| $actionType = 'update_coupon'; | ||
|
|
||
| break; | ||
|
|
||
| default: | ||
| $response = [ | ||
| 'success' => false, | ||
| 'message' => __('Invalid action', 'bit-integrations') | ||
| ]; | ||
| $type = 'LatePoint'; | ||
| $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) { | ||
| $triggerValue = $item->formField; | ||
| $actionValue = $item->latePointField; | ||
|
|
||
| if (empty($actionValue)) { | ||
| continue; | ||
| } | ||
|
|
||
| $dataFinal[$actionValue] = $triggerValue === 'custom' && isset($item->customValue) | ||
| ? Common::replaceFieldWithValue($item->customValue, $fieldValues) | ||
| : $fieldValues[$triggerValue] ?? ''; | ||
| } | ||
|
|
||
| return $dataFinal; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <?php | ||
|
|
||
| if (!defined('ABSPATH')) { | ||
| exit; | ||
| } | ||
|
|
||
| use BitApps\Integrations\Actions\LatePoint\LatePointController; | ||
| use BitApps\Integrations\Core\Util\Route; | ||
|
|
||
| Route::post('latepoint_authorize', [LatePointController::class, 'latePointAuthorize']); | ||
| Route::post('refresh_latepoint_agents', [LatePointController::class, 'refreshAgents']); | ||
| Route::post('refresh_latepoint_services', [LatePointController::class, 'refreshServices']); | ||
| Route::post('refresh_latepoint_locations', [LatePointController::class, 'refreshLocations']); | ||
| Route::post('refresh_latepoint_bundles', [LatePointController::class, 'refreshBundles']); |
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.
Since
refreshAgentsdoes not access any instance properties ($this) and is registered as a static route callback, it should be explicitly declared asstaticto maintain consistency and adhere to coding standards.References
$this) and are registered as static route callbacks should be explicitly declared asstaticto maintain consistency and adhere to coding standards.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.
Not applying — this is a style preference rather than a defect, and it applies to all four
refresh*methods on this controller.Route::action()dispatches through reflection and supports both forms explicitly:A non-static callback is instantiated and invoked correctly, so nothing is broken. There are 30 non-static
refresh*methods acrossbackend/Actions/today, and this controller was modelled onFluentCartController, whose refresh methods are also non-static.Static is the wider majority (64 vs 30), so this is a fair thing to standardise — but as a single sweep across the directory rather than only on the newest integration. The same suggestion was raised and declined for the same reason on #196, and I would rather LatePoint and BadgeOS stay consistent with each other than have one of each.