507 lines
17 KiB
PHP
507 lines
17 KiB
PHP
<?php
|
|
// 1. SESSION CONFIGURATION (MUST BE AT THE VERY TOP)
|
|
$now = time();
|
|
$midnight_timestamp = strtotime('tomorrow midnight') - 1;
|
|
$duration = $midnight_timestamp - $now;
|
|
|
|
ini_set('session.gc_maxlifetime', $duration);
|
|
ini_set('session.cookie_lifetime', $duration);
|
|
|
|
session_set_cookie_params([
|
|
'lifetime' => $duration,
|
|
'path' => '/',
|
|
'secure' => isset($_SERVER['HTTPS']),
|
|
'httponly' => true,
|
|
'samesite' => 'Lax'
|
|
]);
|
|
|
|
if (session_status() === PHP_SESSION_NONE) {
|
|
session_start();
|
|
}
|
|
|
|
// 2. RECOVERY LOGIC (Check cookie before WP loads)
|
|
if (!isset($_SESSION['user']) && isset($_COOKIE['telvero_remember'])) {
|
|
$decoded = json_decode(base64_decode($_COOKIE['telvero_remember']), true);
|
|
if ($decoded && $decoded['expires'] > time()) {
|
|
$_SESSION['user'] = $decoded['user'];
|
|
$_SESSION['full_name'] = $decoded['full_name'];
|
|
}
|
|
}
|
|
|
|
// 3. CAPTURE DATA FOR WP PROTECTION
|
|
$cap_user = $_SESSION['user'] ?? null;
|
|
$cap_name = $_SESSION['full_name'] ?? null;
|
|
|
|
// 4. LOAD WORDPRESS
|
|
$wp_load = __DIR__ . '/wp-load.php';
|
|
if (!file_exists($wp_load)) { $wp_load = dirname(__DIR__) . '/wp-load.php'; }
|
|
if (file_exists($wp_load)) { require_once $wp_load; }
|
|
|
|
// 5. RESTORE DATA
|
|
if ($cap_user && !isset($_SESSION['user'])) {
|
|
$_SESSION['user'] = $cap_user;
|
|
$_SESSION['full_name'] = $cap_name;
|
|
}
|
|
|
|
// 6. REST OF THE BOOTSTRAP
|
|
require __DIR__ . '/vendor/autoload.php';
|
|
if (file_exists(__DIR__ . '/.env')) {
|
|
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
|
$dotenv->load();
|
|
}
|
|
|
|
use Automattic\WooCommerce\Client;
|
|
header('Content-Type: application/json');
|
|
|
|
$db = new mysqli($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASS'], $_ENV['DB_NAME']);
|
|
if ($db->connect_error) { die(json_encode(['error' => 'Database connectie mislukt'])); }
|
|
|
|
$action = $_GET['action'] ?? '';
|
|
|
|
// 6. LOGIN ACTION
|
|
if ($action === 'login') {
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
$username = $input['username'] ?? '';
|
|
$stmt = $db->prepare("SELECT password, full_name FROM sales_users WHERE username = ?");
|
|
$stmt->bind_param("s", $username);
|
|
$stmt->execute();
|
|
$res = $stmt->get_result()->fetch_assoc();
|
|
|
|
if ($res && password_verify($input['password'], $res['password'])) {
|
|
$_SESSION['user'] = $username;
|
|
$_SESSION['full_name'] = $res['full_name'];
|
|
|
|
// Recovery cookie payload
|
|
$cookie_payload = base64_encode(json_encode([
|
|
'user' => $username,
|
|
'full_name' => $res['full_name'],
|
|
'expires' => $midnight_timestamp
|
|
]));
|
|
|
|
// Use the explicit $midnight_timestamp variable
|
|
setcookie('telvero_remember', $cookie_payload, $midnight_timestamp, '/', '', isset($_SERVER['HTTPS']), true);
|
|
|
|
echo json_encode(['success' => true, 'user' => $res['full_name']]);
|
|
} else {
|
|
http_response_code(401); echo json_encode(['error' => 'Login mislukt']);
|
|
}
|
|
exit;
|
|
}
|
|
|
|
// 7. SECURITY GATE (The ONLY one needed)
|
|
if (!isset($_SESSION['user'])) {
|
|
http_response_code(403);
|
|
echo json_encode(['error' => 'Not authenticated']);
|
|
exit;
|
|
}
|
|
|
|
// 8. LOGOUT
|
|
if ($action === 'logout') {
|
|
session_destroy();
|
|
setcookie('telvero_remember', '', time() - 3600, '/');
|
|
echo json_encode(['success' => true]);
|
|
exit;
|
|
}
|
|
|
|
$woocommerce = new Client($_ENV['WC_URL'], $_ENV['WC_KEY'], $_ENV['WC_SECRET'], ['version' => 'wc/v3', 'verify_ssl' => false]);
|
|
|
|
function ss_get_upsellwp_recommended_product_ids($trigger_product_id) {
|
|
if (!function_exists('get_option')) {
|
|
return [];
|
|
}
|
|
|
|
global $wpdb;
|
|
|
|
$trigger_product_id = (int) $trigger_product_id;
|
|
if ($trigger_product_id <= 0) return [];
|
|
|
|
// 1) Vind "campaign posts" waar in meta_value ergens dit product_id voorkomt.
|
|
// We weten niet exact welke meta_key UpsellWP gebruikt, dus zoeken we breed op meta_key LIKE '%upsellwp%'.
|
|
// Daarnaast zoeken we op bekende patronen in serialized/json/plain.
|
|
$like_json = '%' . $wpdb->esc_like('"' . $trigger_product_id . '"') . '%';
|
|
$like_plain = '%' . $wpdb->esc_like((string) $trigger_product_id) . '%';
|
|
$like_serial = '%' . $wpdb->esc_like('i:' . $trigger_product_id . ';') . '%';
|
|
|
|
$campaign_post_ids = $wpdb->get_col(
|
|
$wpdb->prepare(
|
|
"
|
|
SELECT DISTINCT post_id
|
|
FROM {$wpdb->postmeta}
|
|
WHERE meta_key LIKE %s
|
|
AND (
|
|
meta_value LIKE %s
|
|
OR meta_value LIKE %s
|
|
OR meta_value LIKE %s
|
|
)
|
|
",
|
|
'%upsellwp%',
|
|
$like_json,
|
|
$like_serial,
|
|
$like_plain
|
|
)
|
|
);
|
|
|
|
if (empty($campaign_post_ids)) {
|
|
return [];
|
|
}
|
|
|
|
// 2) Verzamel uit die campaigns alle product IDs die genoemd worden (offers / bundles / buy-more-save-more tiers).
|
|
$recommended = [];
|
|
|
|
foreach ($campaign_post_ids as $cid) {
|
|
$cid = (int) $cid;
|
|
|
|
$rows = $wpdb->get_results(
|
|
$wpdb->prepare(
|
|
"
|
|
SELECT meta_key, meta_value
|
|
FROM {$wpdb->postmeta}
|
|
WHERE post_id = %d
|
|
",
|
|
$cid
|
|
)
|
|
);
|
|
|
|
foreach ($rows as $r) {
|
|
$key = (string) $r->meta_key;
|
|
|
|
// Kleine bias naar keys die logisch product/offers bevatten
|
|
// maar alsnog niet te streng, want add-ons gebruiken soms andere keys.
|
|
if (!preg_match('/product|products|offer|offers|bundle|bmsm|tier|rule|condition|trigger|cart|recommend/i', $key)
|
|
&& stripos($key, 'upsellwp') === false
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
$val = (string) $r->meta_value;
|
|
|
|
// Extract alle integers uit json/serialized/plain
|
|
if (preg_match_all('/\b\d+\b/', $val, $m)) {
|
|
foreach ($m[0] as $id) {
|
|
$id = (int) $id;
|
|
if ($id > 0) $recommended[] = $id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3) Opschonen: unieke IDs, trigger zelf eruit, en alleen echte WC producten (best effort).
|
|
$recommended = array_values(array_unique(array_filter($recommended)));
|
|
$recommended = array_values(array_diff($recommended, [$trigger_product_id]));
|
|
|
|
// Filter op bestaande producten (scheelt false positives zoals campaign IDs)
|
|
$recommended = array_values(array_filter($recommended, function($id){
|
|
return get_post_type($id) === 'product' || get_post_type($id) === 'product_variation';
|
|
}));
|
|
|
|
return $recommended;
|
|
}
|
|
|
|
/**
|
|
* Build a map: product_id => related product ids from CUW (UpsellWP) campaigns.
|
|
* Uses {$wpdb->prefix}cuw_campaigns table (as in your screenshot).
|
|
*/
|
|
function ss_cuw_build_product_map() {
|
|
if (!function_exists('get_post_type')) return [];
|
|
|
|
global $wpdb;
|
|
|
|
$table = $wpdb->prefix . 'cuw_campaigns';
|
|
// Safety: table might not exist
|
|
$exists = $wpdb->get_var( $wpdb->prepare("SHOW TABLES LIKE %s", $table) );
|
|
if ($exists !== $table) return [];
|
|
|
|
$rows = $wpdb->get_results("
|
|
SELECT id, type, filters, data
|
|
FROM {$table}
|
|
WHERE enabled = 1
|
|
ORDER BY priority DESC, id DESC
|
|
");
|
|
|
|
if (empty($rows)) return [];
|
|
|
|
$map = [];
|
|
|
|
foreach ($rows as $row) {
|
|
$filters = json_decode((string) $row->filters, true);
|
|
if (!is_array($filters)) continue;
|
|
|
|
// Extract product lists from filters:
|
|
// Example (your screenshot):
|
|
// {"relation":"or","176...":{"type":"products","method":"in_list","values":["1831"]}}
|
|
$filterProductLists = [];
|
|
|
|
foreach ($filters as $k => $v) {
|
|
if ($k === 'relation') continue;
|
|
if (!is_array($v)) continue;
|
|
|
|
$type = $v['type'] ?? '';
|
|
$method = $v['method'] ?? '';
|
|
$values = $v['values'] ?? [];
|
|
|
|
if ($type !== 'products') continue;
|
|
if (!in_array($method, ['in_list', 'in', 'include', 'equals'], true)) continue;
|
|
if (!is_array($values)) continue;
|
|
|
|
$ids = array_values(array_unique(array_map('intval', $values)));
|
|
$ids = array_values(array_filter($ids));
|
|
|
|
if (!empty($ids)) {
|
|
$filterProductLists[] = $ids;
|
|
}
|
|
}
|
|
|
|
if (empty($filterProductLists)) continue;
|
|
|
|
// For each list: if a product is in that list, relate it to the other products in that list
|
|
foreach ($filterProductLists as $list) {
|
|
foreach ($list as $pid) {
|
|
$related = array_values(array_diff($list, [$pid]));
|
|
if (empty($related)) continue;
|
|
|
|
// Keep only real Woo products
|
|
$related = array_values(array_filter($related, function($id){
|
|
$pt = get_post_type($id);
|
|
return $pt === 'product' || $pt === 'product_variation';
|
|
}));
|
|
|
|
if (empty($related)) continue;
|
|
|
|
if (!isset($map[$pid])) $map[$pid] = [];
|
|
$map[$pid] = array_values(array_unique(array_merge($map[$pid], $related)));
|
|
}
|
|
}
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
function ss_cuw_build_deals_map() {
|
|
global $wpdb;
|
|
|
|
$table = $wpdb->prefix . 'cuw_campaigns';
|
|
|
|
$exists = $wpdb->get_var( $wpdb->prepare("SHOW TABLES LIKE %s", $table) );
|
|
if ($exists !== $table) return [];
|
|
|
|
$rows = $wpdb->get_results("
|
|
SELECT id, type, title, priority, filters, data
|
|
FROM {$table}
|
|
WHERE enabled = 1
|
|
ORDER BY priority DESC, id DESC
|
|
");
|
|
|
|
if (empty($rows)) return [];
|
|
|
|
$map = [];
|
|
|
|
foreach ($rows as $row) {
|
|
$type = (string) $row->type;
|
|
if ($type !== 'buy_more_save_more') {
|
|
continue;
|
|
}
|
|
|
|
$filters = json_decode((string) $row->filters, true);
|
|
$data = json_decode((string) $row->data, true);
|
|
|
|
if (!is_array($filters) || !is_array($data)) continue;
|
|
|
|
// 1) Product IDs uit filters halen (zoals jouw screenshot: type=products, method=in_list, values=[...])
|
|
$product_ids = [];
|
|
|
|
foreach ($filters as $k => $v) {
|
|
if ($k === 'relation') continue;
|
|
if (!is_array($v)) continue;
|
|
|
|
$f_type = $v['type'] ?? '';
|
|
$f_method = $v['method'] ?? '';
|
|
$values = $v['values'] ?? [];
|
|
|
|
if ($f_type !== 'products') continue;
|
|
if (!in_array($f_method, ['in_list', 'in', 'include', 'equals'], true)) continue;
|
|
if (!is_array($values)) continue;
|
|
|
|
foreach ($values as $pid) {
|
|
$pid = (int) $pid;
|
|
if ($pid > 0) $product_ids[] = $pid;
|
|
}
|
|
}
|
|
|
|
$product_ids = array_values(array_unique(array_filter($product_ids)));
|
|
if (empty($product_ids)) continue;
|
|
|
|
// 2) Deal details uit data halen
|
|
$cta_text = $data['template']['cta_text'] ?? '';
|
|
$display_location = $data['display_location'] ?? ($data['template']['display_location'] ?? '');
|
|
|
|
$discounts = $data['discounts'] ?? [];
|
|
if (!is_array($discounts) || empty($discounts)) continue;
|
|
|
|
foreach ($discounts as $disc) {
|
|
if (!is_array($disc)) continue;
|
|
|
|
$qty = isset($disc['qty']) ? (int) $disc['qty'] : 0;
|
|
$dType = isset($disc['type']) ? (string) $disc['type'] : '';
|
|
$value = isset($disc['value']) ? (float) $disc['value'] : 0.0;
|
|
|
|
if ($qty <= 0 || $dType === '' || $value <= 0) continue;
|
|
|
|
$deal = [
|
|
'campaign_id' => (int) $row->id,
|
|
'campaign_type' => $type,
|
|
'title' => (string) $row->title,
|
|
'priority' => (int) $row->priority,
|
|
'qty' => $qty,
|
|
'discount_type' => $dType, // fixed_price
|
|
'value' => $value, // 10 (korting op 2e in jouw geval)
|
|
'cta_text' => (string) $cta_text,
|
|
'display_location' => (string) $display_location,
|
|
];
|
|
|
|
foreach ($product_ids as $pid) {
|
|
if (!isset($map[$pid])) $map[$pid] = [];
|
|
$map[$pid][] = $deal;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
|
|
// --- POSTCODE CHECK ---
|
|
if ($action === 'postcode_check') {
|
|
$postcode = str_replace(' ', '', $_GET['postcode']);
|
|
$url = "https://postcode.tech/api/v1/postcode?postcode={$postcode}&number=" . $_GET['number'];
|
|
$ch = curl_init($url);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer " . $_ENV['POSTCODE_TECH_KEY']]);
|
|
echo curl_exec($ch);
|
|
exit;
|
|
}
|
|
|
|
// --- GET PRODUCTS ---
|
|
// --- GET PRODUCTS ---
|
|
if ($action === 'get_products') {
|
|
try {
|
|
$products = $woocommerce->get('products', ['status' => 'publish', 'per_page' => 100]);
|
|
$cuw_map = ss_cuw_build_product_map();
|
|
|
|
$enriched = [];
|
|
|
|
foreach ($products as $product) {
|
|
|
|
// variations (bestaand)
|
|
$variation_details = ($product->type === 'variable')
|
|
? (array) $woocommerce->get("products/{$product->id}/variations", ['per_page' => 50])
|
|
: [];
|
|
|
|
// wpupsell IDs uit meta_data halen (heuristisch, werkt bij veel plugins)
|
|
$wpupsell_ids = [];
|
|
if (!empty($product->meta_data) && is_array($product->meta_data)) {
|
|
foreach ($product->meta_data as $md) {
|
|
$key = isset($md->key) ? (string) $md->key : '';
|
|
if ($key === '' || !preg_match('/wp\s*upsell|wpupsell|upsell\s*campaign/i', $key)) {
|
|
continue;
|
|
}
|
|
|
|
$val = $md->value ?? null;
|
|
|
|
// value kan array/object/string zijn
|
|
if (is_array($val)) {
|
|
$flat = json_encode($val);
|
|
if (preg_match_all('/\b\d+\b/', $flat, $m)) {
|
|
foreach ($m[0] as $id) $wpupsell_ids[] = (int) $id;
|
|
}
|
|
} elseif (is_object($val)) {
|
|
$flat = json_encode($val);
|
|
if (preg_match_all('/\b\d+\b/', $flat, $m)) {
|
|
foreach ($m[0] as $id) $wpupsell_ids[] = (int) $id;
|
|
}
|
|
} else {
|
|
$flat = (string) $val;
|
|
if (preg_match_all('/\b\d+\b/', $flat, $m)) {
|
|
foreach ($m[0] as $id) $wpupsell_ids[] = (int) $id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// upsell + cross-sell + wpupsell bundelen
|
|
$upsell_ids = !empty($product->upsell_ids) ? array_map('intval', (array) $product->upsell_ids) : [];
|
|
$cross_sell_ids = !empty($product->cross_sell_ids) ? array_map('intval', (array) $product->cross_sell_ids) : [];
|
|
|
|
$cuw_ids = $cuw_map[(int)$product->id] ?? [];
|
|
|
|
$recommended_ids = array_values(array_unique(array_filter(array_merge(
|
|
$upsell_ids,
|
|
$cross_sell_ids,
|
|
$cuw_ids
|
|
))));
|
|
|
|
// product naar array + velden toevoegen
|
|
$p = (array) $product;
|
|
$p['variation_details'] = $variation_details;
|
|
|
|
// expliciet meesturen voor frontend
|
|
$p['cuw_ids'] = $cuw_ids;
|
|
$p['recommended_ids'] = $recommended_ids;
|
|
|
|
$enriched[] = $p;
|
|
}
|
|
|
|
echo json_encode($enriched);
|
|
} catch (Exception $e) {
|
|
echo json_encode([]);
|
|
}
|
|
exit;
|
|
}
|
|
|
|
|
|
// --- CREATE ORDER ---
|
|
if ($action === 'create_order') {
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
try {
|
|
$email = $input['billing']['email'];
|
|
$mediacode = $input['mediacode_internal'] ?? 'Geen';
|
|
|
|
$input['payment_method'] = 'cod';
|
|
$input['payment_method_title'] = 'Sales Panel Order';
|
|
$input['status'] = 'on-hold';
|
|
|
|
$existing = $woocommerce->get('customers', ['email' => $email]);
|
|
$input['customer_id'] = !empty($existing) ? $existing[0]->id : 0;
|
|
|
|
$shipping_incl = (float)($input['shipping_total'] ?? 0);
|
|
if ($shipping_incl > 0) {
|
|
$input['shipping_lines'] = [[
|
|
'method_id' => 'flat_rate',
|
|
'method_title' => 'Verzendkosten',
|
|
'total' => number_format($shipping_incl / 1.21, 4, '.', '')
|
|
]];
|
|
}
|
|
|
|
$input['customer_note'] = "Agent: {$_SESSION['user']} | Mediacode: $mediacode";
|
|
|
|
// --- ATTRIBUTION DATA (GEFIXTE SYNTAX) ---
|
|
$input['meta_data'][] = ['key' => '_wc_order_attribution_source_type', 'value' => 'utm'];
|
|
$input['meta_data'][] = ['key' => '_wc_order_attribution_utm_source', 'value' => 'SalesPanel'];
|
|
$input['meta_data'][] = ['key' => '_wc_order_attribution_utm_campaign', 'value' => $mediacode];
|
|
$input['meta_data'][] = ['key' => 'Mediacode', 'value' => $mediacode];
|
|
$input['meta_data'][] = ['key' => 'Bron', 'value' => 'SalesPanel'];
|
|
|
|
$order = $woocommerce->post('orders', $input);
|
|
|
|
$action_type = 'order_created';
|
|
$log_stmt = $db->prepare("INSERT INTO sales_logs (username, action_type, order_id, amount, mediacode, customer_email, created_at) VALUES (?, ?, ?, ?, ?, ?, NOW())");
|
|
$log_stmt->bind_param("ssidss", $_SESSION['user'], $action_type, $order->id, $order->total, $mediacode, $email);
|
|
$log_stmt->execute();
|
|
|
|
echo json_encode(['success' => true, 'order_id' => $order->id, 'total' => $order->total]);
|
|
} catch (Exception $e) {
|
|
http_response_code(422); echo json_encode(['error' => $e->getMessage()]);
|
|
}
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'logout') { session_destroy(); echo json_encode(['success' => true]); exit; } |