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
169 changes: 169 additions & 0 deletions backend/Actions/LatePoint/LatePointController.php
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()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since refreshAgents does not access any instance properties ($this) and is registered as a static route callback, it should be explicitly declared as static to maintain consistency and adhere to coding standards.

    public static function refreshAgents()
References
  1. In PHP, controller methods that do not access instance state ($this) and are registered as static route callbacks should be explicitly declared as static to maintain consistency and adhere to coding standards.

Copy link
Copy Markdown
Member Author

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:

$response = $reflectionMethod->invoke($reflectionMethod->isStatic() ? null : new $invokeable[0](), $data);

A non-static callback is instantiated and invoked correctly, so nothing is broken. There are 30 non-static refresh* methods across backend/Actions/ today, and this controller was modelled on FluentCartController, 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.

{
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()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since refreshServices does not access any instance properties ($this) and is registered as a static route callback, it should be explicitly declared as static to maintain consistency and adhere to coding standards.

    public static function refreshServices()
References
  1. In PHP, controller methods that do not access instance state ($this) and are registered as static route callbacks should be explicitly declared as static to maintain consistency and adhere to coding standards.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the refreshAgents thread above — not applying, for the same reason. Route::action() invokes non-static callbacks correctly via reflection, and this controller follows FluentCartController. Worth standardising across backend/Actions/ as one sweep rather than on this integration alone.

{
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()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since refreshLocations does not access any instance properties ($this) and is registered as a static route callback, it should be explicitly declared as static to maintain consistency and adhere to coding standards.

    public static function refreshLocations()
References
  1. In PHP, controller methods that do not access instance state ($this) and are registered as static route callbacks should be explicitly declared as static to maintain consistency and adhere to coding standards.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the refreshAgents thread above — not applying, for the same reason. Route::action() invokes non-static callbacks correctly via reflection, and this controller follows FluentCartController. Worth standardising across backend/Actions/ as one sweep rather than on this integration alone.

{
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()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since refreshBundles does not access any instance properties ($this) and is registered as a static route callback, it should be explicitly declared as static to maintain consistency and adhere to coding standards.

    public static function refreshBundles()
References
  1. In PHP, controller methods that do not access instance state ($this) and are registered as static route callbacks should be explicitly declared as static to maintain consistency and adhere to coding standards.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the refreshAgents thread above — not applying, for the same reason. Route::action() invokes non-static callbacks correctly via reflection, and this controller follows FluentCartController. Worth standardising across backend/Actions/ as one sweep rather than on this integration alone.

{
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);
}
}
150 changes: 150 additions & 0 deletions backend/Actions/LatePoint/RecordApiHelper.php
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;
}
}
14 changes: 14 additions & 0 deletions backend/Actions/LatePoint/Routes.php
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']);
1 change: 1 addition & 0 deletions backend/Core/Util/AllTriggersName.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public static function allTriggersName()
'JetpackCRM' => ['name' => 'Jetpack CRM', 'isPro' => true, 'is_active' => false],
'Kadence' => ['name' => 'Kadence Blocks Form', 'isPro' => true, 'is_active' => false],
'KaliForms' => ['name' => 'Kali Forms', 'isPro' => true, 'is_active' => false],
'LatePoint' => ['name' => 'LatePoint', 'isPro' => true, 'is_active' => false],
'LearnDash' => ['name' => 'LearnDash LMS', 'isPro' => true, 'is_active' => false],
'LearnPress' => ['name' => 'LearnPress LMS', 'isPro' => true, 'is_active' => false],
'LifterLms' => ['name' => 'LifterLMS', 'isPro' => true, 'is_active' => false],
Expand Down
1 change: 1 addition & 0 deletions frontend/src/Utils/StaticData/webhookIntegrations.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export const customFormIntegrations = [
'WpTableBuilder',
'GiveWp',
'SenseiLMS',
'LatePoint',
'ProfilePress',
'ClickWhale',
'FluentPlayer',
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/AllIntegrations/EditInteg.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ const EditBookly = lazy(() => import('./Bookly/EditBookly'))
const EditSureContact = lazy(() => import('./SureContact/EditSureContact'))
const EditBrilliantDirectories = lazy(() => import('./BrilliantDirectories/EditBrilliantDirectories'))
const EditFluentCart = lazy(() => import('./FluentCart/EditFluentCart'))
const EditLatePoint = lazy(() => import('./LatePoint/EditLatePoint'))
const EditProfilePress = lazy(() => import('./ProfilePress/EditProfilePress'))
const EditClickWhale = lazy(() => import('./ClickWhale/EditClickWhale'))
const EditBadgeOS = lazy(() => import('./BadgeOS/EditBadgeOS'))
Expand Down Expand Up @@ -648,6 +649,8 @@ const IntegType = memo(({ allIntegURL, flow }) => {
return <EditBrilliantDirectories allIntegURL={allIntegURL} />
case 'FluentCart':
return <EditFluentCart allIntegURL={allIntegURL} />
case 'LatePoint':
return <EditLatePoint allIntegURL={allIntegURL} />
case 'ProfilePress':
return <EditProfilePress allIntegURL={allIntegURL} />
case 'ClickWhale':
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/AllIntegrations/IntegInfo.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ const BrilliantDirectoriesAuthorization = lazy(
() => import('./BrilliantDirectories/BrilliantDirectoriesAuthorization')
)
const FluentCartAuthorization = lazy(() => import('./FluentCart/FluentCartAuthorization'))
const LatePointAuthorization = lazy(() => import('./LatePoint/LatePointAuthorization'))
const ProfilePressAuthorization = lazy(() => import('./ProfilePress/ProfilePressAuthorization'))
const ClickWhaleAuthorization = lazy(() => import('./ClickWhale/ClickWhaleAuthorization'))
const BadgeOSAuthorization = lazy(() => import('./BadgeOS/BadgeOSAuthorization'))
Expand Down Expand Up @@ -693,6 +694,8 @@ const IntegrationInfo = memo(({ integrationConf, location, editUrl }) => {
)
case 'FluentCart':
return <FluentCartAuthorization fluentCartConf={integrationConf} step={1} isInfo />
case 'LatePoint':
return <LatePointAuthorization latePointConf={integrationConf} step={1} isInfo />
case 'ProfilePress':
return <ProfilePressAuthorization profilePressConf={integrationConf} step={1} isInfo />
case 'ClickWhale':
Expand Down
Loading
Loading