Created
June 29, 2025 06:15
-
-
Save goranefbl/ff9093aa1c4b2f23e1752ef499199150 to your computer and use it in GitHub Desktop.
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
<?php | |
class WPGL_Points_API | |
{ | |
private static $instance = null; | |
public static function init() | |
{ | |
if (self::$instance === null) { | |
self::$instance = new self(); | |
} | |
self::register_routes(); | |
} | |
public static function register_routes() | |
{ | |
add_action('rest_api_init', function () { | |
// Points Program Routes | |
register_rest_route(WPGENS_LOYALTY_REST_API_ROUTE, '/points/status', array( | |
'methods' => array('GET', 'POST'), | |
'callback' => array(__CLASS__, 'manage_points_status'), | |
'permission_callback' => array(__CLASS__, 'get_permission') | |
)); | |
register_rest_route(WPGENS_LOYALTY_REST_API_ROUTE, '/points/settings', array( | |
'methods' => array('GET', 'POST'), | |
'callback' => array(__CLASS__, 'manage_points_settings'), | |
'permission_callback' => array(__CLASS__, 'get_permission') | |
)); | |
register_rest_route(WPGENS_LOYALTY_REST_API_ROUTE, '/points/earning-actions', array( | |
'methods' => array('GET', 'POST'), | |
'callback' => array(__CLASS__, 'manage_points_earning_actions'), | |
'permission_callback' => array(__CLASS__, 'get_permission') | |
)); | |
register_rest_route(WPGENS_LOYALTY_REST_API_ROUTE, '/points/earning-actions/(?P<id>\\d+)', array( | |
'methods' => array('POST', 'DELETE'), | |
'callback' => array(__CLASS__, 'manage_points_earning_action'), | |
'permission_callback' => array(__CLASS__, 'get_permission') | |
)); | |
register_rest_route(WPGENS_LOYALTY_REST_API_ROUTE, '/points/redeem-actions', array( | |
'methods' => array('GET', 'POST'), | |
'callback' => array(__CLASS__, 'manage_points_redeem_actions'), | |
'permission_callback' => array(__CLASS__, 'get_permission') | |
)); | |
register_rest_route(WPGENS_LOYALTY_REST_API_ROUTE, '/points/redeem-actions/(?P<id>\\d+)', array( | |
'methods' => array('POST', 'DELETE'), | |
'callback' => array(__CLASS__, 'manage_points_redeem_action'), | |
'permission_callback' => array(__CLASS__, 'get_permission') | |
)); | |
register_rest_route(WPGENS_LOYALTY_REST_API_ROUTE, '/points/activity', array( | |
'methods' => 'GET', | |
'callback' => array(__CLASS__, 'get_points_activity'), | |
'permission_callback' => array(__CLASS__, 'get_permission') | |
)); | |
register_rest_route(WPGENS_LOYALTY_REST_API_ROUTE, '/points/activity/download', array( | |
'methods' => 'GET', | |
'callback' => array(__CLASS__, 'download_points_activity'), | |
'permission_callback' => array(__CLASS__, 'get_permission') | |
)); | |
// Member points management routes | |
register_rest_route(WPGENS_LOYALTY_REST_API_ROUTE, '/points/import', array( | |
'methods' => 'POST', | |
'callback' => array(__CLASS__, 'import_points'), | |
'permission_callback' => array(__CLASS__, 'get_permission') | |
)); | |
register_rest_route(WPGENS_LOYALTY_REST_API_ROUTE, '/points/adjust', array( | |
'methods' => 'POST', | |
'callback' => array(__CLASS__, 'adjust_points'), | |
'permission_callback' => array(__CLASS__, 'get_permission') | |
)); | |
}); | |
// Register Store Checkout Block API endpoints | |
add_action('woocommerce_blocks_loaded', function() { | |
woocommerce_store_api_register_update_callback( | |
[ | |
'namespace' => 'wpgens-points-and-rewards-program', | |
'callback' => function($data) { | |
if (!function_exists('WC')) { | |
return; | |
} | |
$points = $data['points'] ?? 0; | |
if ($points > 0) { | |
WPGL_Points_Checkout::handle_apply_points_discount($points); | |
} else { | |
WPGL_Points_Checkout::handle_remove_points_discount(); | |
} | |
}, | |
] | |
); | |
}); | |
} | |
public static function get_permission() | |
{ | |
return current_user_can('administrator') || current_user_can('manage_woocommerce'); | |
} | |
public static function manage_points_status($request) | |
{ | |
$prefix = 'wpgens_loyalty_'; | |
$key = $prefix . 'points_program_active'; | |
if ($request->get_method() === 'POST') { | |
$data = $request->get_params(); | |
if (isset($data['active'])) { | |
update_option($key, $data['active']); | |
return new WP_REST_Response(['active' => $data['active']], 200); | |
} | |
return new WP_REST_Response(['message' => 'Invalid data'], 400); | |
} | |
return new WP_REST_Response(['active' => get_option($key, '0')], 200); | |
} | |
public static function manage_points_settings($request) | |
{ | |
$settings_key = WPGL_Points_Settings::OPTION_POINTS_SETTINGS; | |
if ($request->get_method() === 'POST') { | |
$data = $request->get_params(); | |
if (isset($data['settings'])) { | |
$existing_settings = get_option($settings_key, '{}'); | |
$existing_settings = json_decode($existing_settings, true); | |
$new_settings = wp_parse_args($data['settings'], $existing_settings); | |
update_option($settings_key, wp_json_encode($new_settings)); | |
} | |
return new WP_REST_Response($data, 200); | |
} | |
$settings = get_option($settings_key, '{}'); | |
$settings = wp_parse_args(json_decode($settings, true), WPGL_Points_Settings::get_default_settings()); | |
return new WP_REST_Response(['settings' => $settings], 200); | |
} | |
public static function manage_points_earning_actions($request) | |
{ | |
$prefix = 'wpgens_loyalty_'; | |
$key = $prefix . 'points_earning_actions'; | |
$actions = get_option($key, array()); | |
if ($request->get_method() === 'POST') { | |
$data = $request->get_params(); | |
if (!empty($data)) { | |
// Only set referFriendIsPercent for REFER_FRIEND | |
if (isset($data['type']) && $data['type'] === 'REFER_FRIEND') { | |
$data['referFriendIsPercent'] = !empty($data['referFriendIsPercent']); | |
} | |
// Find the highest existing ID and add 1 to ensure uniqueness | |
$highest_id = 0; | |
foreach ($actions as $action) { | |
if (isset($action['id']) && $action['id'] > $highest_id) { | |
$highest_id = $action['id']; | |
} | |
} | |
$data['id'] = $highest_id + 1; | |
if (!isset($data['referFriendIsPercent']) && $data['type'] === 'REFER_FRIEND') { | |
$data['referFriendIsPercent'] = false; | |
} | |
$actions[] = $data; | |
update_option($key, $actions); | |
return new WP_REST_Response($data, 200); | |
} | |
return new WP_REST_Response(['message' => 'Invalid data'], 400); | |
} | |
return new WP_REST_Response($actions, 200); | |
} | |
public static function manage_points_earning_action($request) | |
{ | |
$id = $request['id']; | |
$key = 'wpgens_loyalty_points_earning_actions'; | |
$actions = get_option($key, array()); | |
if ($request->get_method() === 'DELETE') { | |
foreach ($actions as $index => $action) { | |
if ($action['id'] == $id) { | |
array_splice($actions, $index, 1); | |
break; | |
} | |
} | |
update_option($key, $actions); | |
return new WP_REST_Response(null, 204); | |
} | |
$data = $request->get_params(); | |
if (!empty($data)) { | |
foreach ($actions as $index => $action) { | |
if ($action['id'] == $id) { | |
// Only set referFriendIsPercent for REFER_FRIEND | |
if (isset($data['type']) && $data['type'] === 'REFER_FRIEND') { | |
$data['referFriendIsPercent'] = !empty($data['referFriendIsPercent']); | |
} | |
$actions[$index] = array_merge($action, $data); | |
break; | |
} | |
} | |
update_option($key, $actions); | |
return new WP_REST_Response($data, 200); | |
} | |
return new WP_REST_Response(['message' => 'Invalid data'], 400); | |
} | |
public static function manage_points_redeem_actions($request) | |
{ | |
$prefix = 'wpgens_loyalty_'; | |
$key = $prefix . 'points_redeem_actions'; | |
$actions = get_option($key, array()); | |
if ($request->get_method() === 'POST') { | |
$data = $request->get_params(); | |
if (!empty($data)) { | |
$data['id'] = (string)(count($actions) + 1); | |
$actions[] = $data; | |
update_option($key, $actions); | |
return new WP_REST_Response($data, 200); | |
} | |
return new WP_REST_Response(['message' => 'Invalid data'], 400); | |
} | |
return new WP_REST_Response($actions, 200); | |
} | |
public static function manage_points_redeem_action($request) | |
{ | |
$id = (string)$request['id']; | |
$key = 'wpgens_loyalty_points_redeem_actions'; | |
$actions = get_option($key, array()); | |
if ($request->get_method() === 'DELETE') { | |
foreach ($actions as $index => $action) { | |
if ((string)$action['id'] === $id) { | |
array_splice($actions, $index, 1); | |
break; | |
} | |
} | |
update_option($key, $actions); | |
return new WP_REST_Response(null, 204); | |
} | |
$data = $request->get_params(); | |
if (!empty($data)) { | |
foreach ($actions as $index => $action) { | |
if ((string)$action['id'] === $id) { | |
$actions[$index] = array_merge($action, $data); | |
break; | |
} | |
} | |
update_option($key, $actions); | |
return new WP_REST_Response($data, 200); | |
} | |
return new WP_REST_Response(['message' => 'Invalid data'], 400); | |
} | |
public static function get_points_activity($request) | |
{ | |
// Sanitize and validate inputs | |
$per_page = min(50, absint($request->get_param('limit')) ?: 10); | |
$page = max(1, absint($request->get_param('page')) ?: 1); | |
$filter = $request->get_param('filter'); | |
$sort_by = $request->get_param('sort_by'); | |
// Get activity data | |
$activity = WPGL_Database::get_points_activity([ | |
'page' => $page, | |
'limit' => $per_page, | |
'filter' => $filter, | |
'sort_by' => $sort_by | |
]); | |
// Get stats with filters | |
$stats = WPGL_Database::get_points_stats($filter); | |
// Format response | |
$response = [ | |
'data' => $activity['items'], | |
'meta' => [ | |
'currentPage' => (int)$activity['current_page'], | |
'itemsPerPage' => (int)$activity['per_page'], | |
'totalItems' => (int)$activity['total_items'], | |
'totalPages' => (int)$activity['total_pages'] | |
], | |
'stats' => [ | |
'totalEarned' => $stats->total_earned, | |
'totalSpent' => $stats->total_spent, | |
'redemptionRate' => $stats->redemption_rate | |
] | |
]; | |
return new WP_REST_Response($response, 200); | |
} | |
public static function download_points_activity($request) | |
{ | |
$filter = $request->get_param('filter'); | |
$items = WPGL_Database::get_points_activity_for_export(['filter' => $filter]); | |
$filename = 'points-activity-' . gmdate('Y-m-d') . '.csv'; | |
// Create CSV content | |
$csv_content = ''; | |
// Add headers | |
$headers = ['Date', 'User Email', 'Points', 'Type', 'Source', 'Reference ID', 'Description']; | |
$csv_content .= implode(',', array_map(function ($value) { | |
return strpos($value, ',') !== false ? '"' . str_replace('"', '""', esc_html($value)) . '"' : esc_html($value); | |
}, $headers)) . "\n"; | |
// Add data rows | |
foreach ($items as $item) { | |
$row = [ | |
esc_html($item->timestamp), | |
esc_html($item->user_email), | |
absint($item->points), | |
esc_html($item->type), | |
esc_html($item->source), | |
esc_html($item->reference_id), | |
esc_html($item->description) | |
]; | |
$csv_content .= implode(',', array_map(function ($value) { | |
return strpos($value, ',') !== false ? '"' . str_replace('"', '""', $value) . '"' : $value; | |
}, $row)) . "\n"; | |
} | |
// Send headers | |
nocache_headers(); | |
header('Content-Type: text/csv; charset=utf-8'); | |
header('Content-Disposition: attachment; filename="' . sanitize_file_name($filename) . '"'); | |
// Output the CSV content | |
echo esc_html($csv_content); | |
exit; | |
} | |
public static function import_points($request) | |
{ | |
// Verify nonce | |
$nonce = $request->get_param('nonce'); | |
if (!$nonce || !wp_verify_nonce($nonce, 'wp_rest')) { | |
return new WP_Error('invalid_nonce', 'Invalid security token', array('status' => 403)); | |
} | |
// Validate file upload | |
if ( | |
!isset($_FILES['file']) || | |
!isset($_FILES['file']['tmp_name']) || | |
!isset($_FILES['file']['name']) || | |
!isset($_FILES['file']['type']) || | |
!isset($_FILES['file']['error']) || | |
!isset($_FILES['file']['size']) | |
) { | |
return new WP_Error('missing_file', 'Invalid file upload', array('status' => 400)); | |
} | |
// Sanitize and validate the uploaded file | |
$file = array( | |
'name' => sanitize_file_name($_FILES['file']['name']), | |
'type' => sanitize_mime_type($_FILES['file']['type']), | |
'tmp_name' => sanitize_text_field($_FILES['file']['tmp_name']), | |
'error' => absint($_FILES['file']['error']), | |
'size' => absint($_FILES['file']['size']) | |
); | |
// Accept any file type if it has a .csv extension | |
$file_extension = pathinfo($file['name'], PATHINFO_EXTENSION); | |
if (strtolower($file_extension) !== 'csv') { | |
return new WP_Error('invalid_type', 'Invalid file type. Please upload a CSV file.', array('status' => 400)); | |
} | |
// Validate file size (max 5MB) | |
if ($file['size'] > 5 * 1024 * 1024) { | |
return new WP_Error('file_too_large', 'File is too large. Maximum size is 5MB.', array('status' => 400)); | |
} | |
$mode = sanitize_text_field($request->get_param('mode')); | |
if ($file['error'] !== UPLOAD_ERR_OK) { | |
return new WP_Error('upload_error', 'File upload failed', array('status' => 400)); | |
} | |
// Read file contents directly | |
$file_contents = file_get_contents($file['tmp_name']); | |
if (false === $file_contents) { | |
return new WP_Error('file_read_error', 'Could not read file', array('status' => 400)); | |
} | |
// Check if file is empty | |
if (empty($file_contents)) { | |
return new WP_Error('empty_file', 'The uploaded file is empty', array('status' => 400)); | |
} | |
$success_count = 0; | |
$error_count = 0; | |
$errors = array(); | |
// Parse CSV contents - handle different line endings | |
$file_contents = str_replace(["\r\n", "\r"], "\n", $file_contents); | |
$rows = explode("\n", $file_contents); | |
// Remove header row | |
$header = str_getcsv(array_shift($rows), ',', '"', '\\'); | |
// Validate header row - accept 2 or 3 columns | |
$header_count = count($header); | |
if ($header_count < 2 || $header_count > 3 || | |
strtolower($header[0]) !== 'email' || | |
strtolower($header[1]) !== 'points' || | |
($header_count === 3 && strtolower($header[2]) !== 'description')) { | |
return new WP_Error('invalid_format', 'Invalid CSV format. The first row should contain "email,points" or "email,points,description"', array('status' => 400)); | |
} | |
foreach ($rows as $index => $row) { | |
// Skip empty rows | |
if (empty($row)) { | |
continue; | |
} | |
// Parse the CSV row | |
$data = str_getcsv($row, ',', '"', '\\'); | |
if (!is_array($data) || count($data) < 2 || count($data) > 3) { | |
$error_count++; | |
$errors[] = "Row " . ($index + 2) . ": Invalid format. Expected 2 or 3 columns (email,points,description)."; | |
continue; | |
} | |
$email = sanitize_email($data[0]); | |
$points = absint($data[1]); | |
$description = isset($data[2]) && !empty(trim($data[2])) ? sanitize_text_field(trim($data[2])) : 'Points imported'; | |
if (!is_email($email)) { | |
$errors[] = "Row " . ($index + 2) . ": Invalid email format: " . esc_html($email); | |
$error_count++; | |
continue; | |
} | |
$user = get_user_by('email', $email); | |
if (!$user) { | |
$errors[] = "Row " . ($index + 2) . ": User not found: " . esc_html($email); | |
$error_count++; | |
continue; | |
} | |
try { | |
if ($mode === 'replace') { | |
// First set points to 0, then add the new amount | |
$current_points = WPGL_Database::get_user_points($user->ID); | |
if ($current_points > 0) { | |
// Directly update points to avoid issues with action hooks | |
$result = WPGL_Database::update_user_points( | |
$user->ID, | |
-$current_points, | |
WPGL_Points_Activity_Type::DEDUCT, | |
WPGL_Points_Source_Type::IMPORT, | |
null, | |
'Points reset during import' | |
); | |
if (is_wp_error($result)) { | |
throw new Exception($result->get_error_message()); | |
} | |
} | |
// Add the new points | |
$result = WPGL_Database::update_user_points( | |
$user->ID, | |
$points, | |
WPGL_Points_Activity_Type::EARN, | |
WPGL_Points_Source_Type::IMPORT, | |
null, | |
$description | |
); | |
if (is_wp_error($result)) { | |
throw new Exception($result->get_error_message()); | |
} | |
} else { | |
// Directly update points to avoid issues with action hooks | |
$result = WPGL_Database::update_user_points( | |
$user->ID, | |
$points, | |
WPGL_Points_Activity_Type::EARN, | |
WPGL_Points_Source_Type::IMPORT, | |
null, | |
$description | |
); | |
if (is_wp_error($result)) { | |
throw new Exception($result->get_error_message()); | |
} | |
} | |
// Verify points were actually updated | |
$new_balance = WPGL_Database::get_user_points($user->ID); | |
$success_count++; | |
} catch (Exception $e) { | |
$errors[] = "Row " . ($index + 2) . ": Error updating points for " . esc_html($email) . ": " . esc_html($e->getMessage()); | |
$error_count++; | |
} | |
} | |
// If no rows were processed successfully, return an error | |
if ($success_count === 0) { | |
if (count($errors) > 0) { | |
return new WP_Error('import_failed', 'Import failed: ' . implode('; ', $errors), array('status' => 400)); | |
} else { | |
return new WP_Error('import_failed', 'Import failed: No valid rows found in the CSV file', array('status' => 400)); | |
} | |
} | |
return new WP_REST_Response(array( | |
'success' => true, | |
'success_count' => $success_count, | |
'error_count' => $error_count, | |
'errors' => $errors | |
)); | |
} | |
public static function adjust_points($request) | |
{ | |
// Verify nonce | |
$nonce = $request->get_param('nonce'); | |
if (!$nonce || !wp_verify_nonce($nonce, 'wp_rest')) { | |
return new WP_Error('invalid_nonce', 'Invalid security token', array('status' => 403)); | |
} | |
$data = $request->get_params(); | |
if (!isset($data['user_id']) || !isset($data['points'])) { | |
return new WP_REST_Response(['message' => 'Missing required fields'], 400); | |
} | |
$user_id = intval($data['user_id']); | |
$points = intval($data['points']); | |
$description = isset($data['description']) ? sanitize_text_field($data['description']) : null; | |
// Determine type based on points value | |
$type = $points >= 0 ? WPGL_Points_Activity_Type::EARN : WPGL_Points_Activity_Type::DEDUCT; | |
try { | |
// Use the action hook to trigger all registered callbacks | |
do_action('wpgens_loyalty_update_points', $user_id, $points, $type, WPGL_Points_Source_Type::MANUAL, null, $description); | |
// Get the new balance after the action has been processed | |
$new_balance = WPGL_Database::get_user_points($user_id); | |
// Check if points were actually updated | |
if ($new_balance === 0 && $points > 0) { | |
// This might indicate an error in the update process | |
return new WP_REST_Response([ | |
'success' => false, | |
'message' => 'Points update may have failed. Please check the logs.' | |
], 400); | |
} | |
return new WP_REST_Response([ | |
'success' => true, | |
'new_balance' => $new_balance | |
]); | |
} catch (Exception $e) { | |
return new WP_REST_Response([ | |
'success' => false, | |
'message' => $e->getMessage() | |
], 500); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment