Quellcode durchsuchen

Add user story functionality with routing, controller, and admin menu integration; implement user event logging and cleanup script

szisz vor 1 Tag
Ursprung
Commit
37037e10e7

+ 10 - 3
application/config/config.php

@@ -12,14 +12,21 @@ $config['base_url'] = 'https://'.$_SERVER['SERVER_NAME'].$development_postfix; /
 $config['default_controller'] = 'main'; // Default controller to load
 $config['error_controller'] = 'error'; // Controller used for errors (e.g. 404, 500 etc)
 
-$config['db_host'] = 'localhost'; // Database host (e.g. localhost)
+/*$config['db_host'] = 'localhost'; // Database host (e.g. localhost)
 $config['db_name'] = 'admincitywebshop'; // Database name
 $config['db_username'] = 'admincitywebshop'; // Database username
+$config['db_password'] = '5jqPQEPGUihnY5XM6oPR'; // Database password*/
+
+$config['db_host'] = 'localhost'; // Database host (e.g. localhost)
+$config['db_name'] = 'openws'; // Database name
+$config['db_username'] = 'openws'; // Database username
 $config['db_password'] = '5jqPQEPGUihnY5XM6oPR'; // Database password
 
-$config['api_url'] = 'https://hoponticket.com/api/index.php';
-//$config['api_url'] = 'https://szollosil.bbus.smbinfo.hu/api/index.php';
+
+//$config['api_url'] = 'https://hoponticket.com/api/index.php';
 //$config['api_url'] = 'https://php82.umsbox.hu/api/index.php';
+$config['api_url'] = 'https://preprod2.bbus.umsbox.hu/api/index.php';
+//$config['api_url'] = 'https://szollosil.bbus.umsbox.hu/api/index.php';
 $config['api_key'] = '92JxvN5Zeti4E1FDwKg0QPEl3md4vY63';
 
 ?>

+ 18 - 0
application/controllers/api.php

@@ -204,4 +204,22 @@ class api extends Controller {
     echo json_encode($model->validateUUID($_REQUEST['uuid']));
   }
 
+  public function userEvent() {
+    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+      echo json_encode(['success' => false, 'message' => 'Only POST method is allowed']);
+      return;
+    }
+
+    $data = json_decode(file_get_contents('php://input'), true);
+
+    if (!$data || empty($data['uuid']) || empty($data['event_type']) || empty($data['event_name'])) {
+      echo json_encode(['success' => false, 'message' => 'Missing required fields: uuid, event_type, event_name']);
+      return;
+    }
+
+    $model = $this->loadModel('api_model');
+    $eventId = $model->logUserEvent($data);
+    echo json_encode(['success' => true, 'event_id' => $eventId]);
+  }
+
 }

+ 25 - 0
application/controllers/userstory.php

@@ -0,0 +1,25 @@
+<?php
+
+class userstory extends Controller {
+
+  public function index() {
+    $model = $this->loadModel('userstory_model');
+    $view = $this->loadView('userstory_list');
+    $view->set('stories', $model->getAllStories());
+    $view->render();
+  }
+
+  public function timeline() {
+    $uuid = isset($_REQUEST['uuid']) ? $_REQUEST['uuid'] : '';
+    if (empty($uuid)) {
+      header('Location: /userstory');
+      die();
+    }
+    $model = $this->loadModel('userstory_model');
+    $view = $this->loadView('userstory_timeline');
+    $view->set('uuid', $uuid);
+    $view->set('events', $model->getEventsByUUID($uuid));
+    $view->render();
+  }
+
+}

+ 56 - 6
application/models/api_model.php

@@ -2,9 +2,10 @@
 
 class api_model extends Model {
 
-  //private $API = 'https://szollosil.bbus.smbinfo.hu/api/index.php';
-  private $API = 'https://hoponticket.com/api/index.php';
+  //private $API = 'https://hoponticket.com/api/index.php';
+  private API = 'https://preprod2.bbus.umsbox.hu/api/index.php';
   //private $API = 'https://php82fpm.umsbox.hu/api/index.php';
+  //private $API = 'https://szollosil.bbus.umsbox.hu/api/index.php';
 
   private $API_KEY = '92JxvN5Zeti4E1FDwKg0QPEl3md4vY63';
 
@@ -78,7 +79,7 @@ class api_model extends Model {
   public function getEventsList($groupID, $eventID='') {
     $curl = curl_init();
     curl_setopt_array($curl, array(
-      CURLOPT_URL => $this->API.'/affiliateapi/events?group_id='.$groupID.'&groupByDate=1',
+      CURLOPT_URL => $this->API.'/affiliateapi/events?group_id='.$groupID.'&additionalDate=60&groupByDate=1',
       CURLOPT_RETURNTRANSFER => true,
       CURLOPT_ENCODING => '',
       CURLOPT_MAXREDIRS => 10,
@@ -127,7 +128,7 @@ class api_model extends Model {
   public function getEventsListByGroup($groupID, $eventID) {
     $curl = curl_init();
     curl_setopt_array($curl, array(
-      CURLOPT_URL => $this->API.'/affiliateapi/events?group_id='.$groupID.'&groupByDate=1',
+      CURLOPT_URL => $this->API.'/affiliateapi/events?group_id='.$groupID.'&additionalDate=60&groupByDate=1',
       CURLOPT_RETURNTRANSFER => true,
       CURLOPT_ENCODING => '',
       CURLOPT_MAXREDIRS => 10,
@@ -176,7 +177,7 @@ class api_model extends Model {
   private function getAllEventsListByGroup($groupID) {
     $curl = curl_init();
     curl_setopt_array($curl, array(
-      CURLOPT_URL => $this->API.'/affiliateapi/events?group_id='.$groupID.'&groupByDate=1',
+      CURLOPT_URL => $this->API.'/affiliateapi/events?group_id='.$groupID.'&additionalDate=60&groupByDate=1',
       CURLOPT_RETURNTRANSFER => true,
       CURLOPT_ENCODING => '',
       CURLOPT_MAXREDIRS => 10,
@@ -192,6 +193,8 @@ class api_model extends Model {
     curl_close($curl);
     $eventsByKey = [];
     $response = json_decode($response, true);
+    //print_r($response);
+    //die();
     foreach ($response as $key => $item) {
       $event = [];
       $event['key'] = $key;
@@ -254,7 +257,7 @@ class api_model extends Model {
         $curl = curl_init();
     
         curl_setopt_array($curl, array(
-          CURLOPT_URL => $this->API.'/affiliateapi/events?id='.$packageid.'&participants='.$participants,
+          CURLOPT_URL => $this->API.'/affiliateapi/events?id='.$packageid.'&additionalDate=60&participants='.$participants,
           CURLOPT_RETURNTRANSFER => true,
           CURLOPT_ENCODING => '',
           CURLOPT_MAXREDIRS => 10,
@@ -1051,6 +1054,53 @@ class api_model extends Model {
   }
 
 
+  public function getPreviousUUID($uuid) {
+    $uuid = $this->escapeString($uuid);
+    $rows = $this->query("SELECT previous_uuid FROM `azonics_user_events`
+      WHERE uuid = '".$uuid."' AND event_type = 'uuid_change'
+      ORDER BY event_id DESC LIMIT 1;");
+    if (count($rows) > 0 && !empty($rows[0]->previous_uuid)) {
+      return $rows[0]->previous_uuid;
+    }
+    return '';
+  }
+
+  public function logUserEvent($data) {
+    $uuid = $this->escapeString($data['uuid']);
+    $event_type = $this->escapeString($data['event_type']);
+
+    if ($event_type === 'uuid_change') {
+      $previous_uuid = isset($data['previous_uuid']) ? $this->escapeString($data['previous_uuid']) : '';
+    } else {
+      $previous_uuid = $this->getPreviousUUID($data['uuid']);
+    }
+
+    $hotel = isset($data['hotel']) ? $this->escapeString($data['hotel']) : '';
+    $order_id = isset($data['order_id']) ? $this->escapeString($data['order_id']) : '';
+    $cart = isset($data['cart']) ? $this->escapeString(json_encode($data['cart'])) : '';
+    $event_name = $this->escapeString($data['event_name']);
+    $event_data = isset($data['data']) ? $this->escapeString(json_encode($data['data'])) : '';
+    $page_url = isset($data['page_url']) ? $this->escapeString($data['page_url']) : '';
+    $ip_address = $this->escapeString($_SERVER['REMOTE_ADDR']);
+    $user_agent = $this->escapeString($_SERVER['HTTP_USER_AGENT'] ?? '');
+
+    $this->execute("INSERT INTO `azonics_user_events` SET
+      uuid = '".$uuid."',
+      previous_uuid = '".$previous_uuid."',
+      hotel = '".$hotel."',
+      order_id = '".$order_id."',
+      cart = '".$cart."',
+      event_type = '".$event_type."',
+      event_name = '".$event_name."',
+      event_data = '".$event_data."',
+      page_url = '".$page_url."',
+      ip_address = '".$ip_address."',
+      user_agent = '".$user_agent."',
+      created_at = NOW();");
+
+    return $this->getLastInsertID();
+  }
+
   public function getQRCodes($orderID) {
       $curl = curl_init();
       

+ 89 - 0
application/models/userstory_model.php

@@ -0,0 +1,89 @@
+<?php
+
+class userstory_model extends Model {
+
+  public function getAllStories() {
+    $rows = $this->query("SELECT
+      uuid,
+      previous_uuid,
+      MIN(created_at) as first_event,
+      MAX(created_at) as last_event,
+      COUNT(*) as event_count,
+      MAX(hotel) as hotel,
+      MAX(order_id) as order_id,
+      MAX(ip_address) as ip_address
+    FROM azonics_user_events
+    GROUP BY uuid
+    ORDER BY last_event DESC;");
+
+    // UUID change láncolatok feloldása: previous_uuid -> uuid mapping
+    $uuidChanges = $this->query("SELECT uuid, previous_uuid FROM azonics_user_events
+      WHERE event_type = 'uuid_change' AND previous_uuid != ''
+      ORDER BY event_id ASC;");
+
+    // Mapping: new_uuid => original_uuid (a lánc elejére mutat)
+    $childToParent = [];
+    foreach ($uuidChanges as $change) {
+      $parent = $change->previous_uuid;
+      // Ha a parent maga is egy child, kövessük a láncot
+      if (isset($childToParent[$parent])) {
+        $parent = $childToParent[$parent];
+      }
+      $childToParent[$change->uuid] = $parent;
+    }
+
+    // Sorok csoportosítása az eredeti (parent) UUID alapján
+    $grouped = [];
+    $order = [];
+    foreach ($rows as $row) {
+      $originalUUID = isset($childToParent[$row->uuid]) ? $childToParent[$row->uuid] : $row->uuid;
+
+      if (!isset($grouped[$originalUUID])) {
+        $grouped[$originalUUID] = (object)[
+          'uuid' => $originalUUID,
+          'new_uuids' => [],
+          'first_event' => $row->first_event,
+          'last_event' => $row->last_event,
+          'event_count' => 0,
+          'hotel' => '',
+          'order_id' => '',
+          'ip_address' => ''
+        ];
+        $order[] = $originalUUID;
+      }
+
+      $g = $grouped[$originalUUID];
+      $g->event_count += (int)$row->event_count;
+      if ($row->first_event < $g->first_event) $g->first_event = $row->first_event;
+      if ($row->last_event > $g->last_event) $g->last_event = $row->last_event;
+      if (!empty($row->hotel)) $g->hotel = $row->hotel;
+      if (!empty($row->order_id)) $g->order_id = $row->order_id;
+      if (!empty($row->ip_address)) $g->ip_address = $row->ip_address;
+
+      if ($row->uuid !== $originalUUID) {
+        $g->new_uuids[] = $row->uuid;
+      }
+    }
+
+    // Rendezés last_event desc szerint
+    usort($order, function($a, $b) use ($grouped) {
+      return strcmp($grouped[$b]->last_event, $grouped[$a]->last_event);
+    });
+
+    $result = [];
+    foreach ($order as $key) {
+      $result[] = $grouped[$key];
+    }
+    return $result;
+  }
+
+  public function getEventsByUUID($uuid) {
+    $uuid = $this->escapeString($uuid);
+    return $this->query("SELECT * FROM azonics_user_events
+      WHERE uuid = '".$uuid."' OR previous_uuid = '".$uuid."' OR uuid IN (
+        SELECT uuid FROM azonics_user_events WHERE previous_uuid = '".$uuid."'
+      )
+      ORDER BY created_at ASC, event_id ASC;");
+  }
+
+}

+ 15 - 0
application/views/admin_footer.php

@@ -108,6 +108,21 @@
                 }
             });
 
+            $("#userstory-table").dataTable({
+                aaSorting: [[4,"desc"]],
+                oLanguage: {
+                    sSearch:                    "<?=lang::_('')?>",
+                    sInfo:                      "_START_ <?=lang::_('elemtől')?> _END_ <?=lang::_('elemig')?> <?=lang::_('összesen')?>: _TOTAL_ <?=lang::_('elemből')?>",
+                    sLengthMenu:                "_MENU_ <?=lang::_('elem')?>",
+                    oPaginate: {
+                        sFirst: "<?=lang::_('Első')?>",
+                        sLast:  "<?=lang::_('Utolsó')?>",
+                        sNext:  "<?=lang::_('Következő')?>",
+                        sPrevious:  "<?=lang::_('Előző')?>"
+                    }
+                }
+            });
+
             if ($('#hotelFilter').length) {
                 $.fn.dataTableExt.afnFiltering.push(function(oSettings, aData, iDataIndex) {
                     var selected = $('#hotelFilter').val();

+ 7 - 1
application/views/admin_menu.php

@@ -112,7 +112,13 @@
                     </li>
                 <?php endif; ?>
             <?php endforeach; ?>
-            
+
+            <li class="<?=admin_utils::checkMenuActive($item->module_controller)?>">
+                <a href="/userstory">
+                    <i class="fa fa-road"></i> <span><?=lang::_('User story')?></span>
+                </a>
+            </li>
+
             <li class="<?=admin_utils::checkMenuActive($item->module_controller)?>">
                 <a href="/admin/logout">
                     <i class="fa fa-sign-out"></i> <span><?=lang::_('Kilépés')?></span>

+ 61 - 0
application/views/userstory_list.php

@@ -0,0 +1,61 @@
+<?php include 'admin_header.php'; ?>
+
+<div class="content-wrapper">
+    <section class="content-header">
+        <h1>
+            User Stories
+            <small>Visitors activity log</small>
+        </h1>
+    </section>
+
+    <section class="content">
+        <div class="box box-info">
+            <div class="box-header with-border">
+                <h3 class="box-title">User Stories</h3>
+            </div>
+            <div class="box-body" style="overflow-x: auto;">
+                <table id="userstory-table" class="table table-bordered table-hover table-striped">
+                    <thead>
+                        <tr>
+                            <th>UUID</th>
+                            <th>Hotel</th>
+                            <th>Events</th>
+                            <th>First event</th>
+                            <th>Last event</th>
+                            <th>IP</th>
+                            <th>&nbsp;</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        <?php foreach ($stories as $story) : ?>
+                        <tr>
+                            <td>
+                                <code><?=htmlspecialchars($story->uuid)?></code>
+                                <?php if (!empty($story->new_uuids)) : ?>
+                                <br>
+                                <small style="color: #f39c12;"><i class="fa fa-exchange"></i> UUID changed:</small>
+                                <?php foreach ($story->new_uuids as $new_uuid) : ?>
+                                <br><code style="color: #f39c12; font-size: 11px;"><?=htmlspecialchars($new_uuid)?></code>
+                                <?php endforeach; ?>
+                                <?php endif; ?>
+                            </td>
+                            <td><?=htmlspecialchars($story->hotel ?: '-')?></td>
+                            <td><span class="badge bg-blue"><?=$story->event_count?></span></td>
+                            <td><?=$story->first_event?></td>
+                            <td><?=$story->last_event?></td>
+                            <td><?=htmlspecialchars($story->ip_address ?: '-')?></td>
+                            <td>
+                                <a href="/userstory/timeline/?uuid=<?=urlencode($story->uuid)?>" class="btn btn-sm btn-primary">
+                                    <i class="fa fa-clock-o"></i> Timeline
+                                </a>
+                            </td>
+                        </tr>
+                        <?php endforeach; ?>
+                    </tbody>
+                </table>
+            </div>
+        </div>
+    </section>
+</div>
+
+<?php include 'admin_footer.php'; ?>

+ 230 - 0
application/views/userstory_timeline.php

@@ -0,0 +1,230 @@
+<?php include 'admin_header.php'; ?>
+
+<style>
+    .us-timeline {
+        position: relative;
+        padding: 20px 0;
+        max-width: 700px;
+        margin: 0 auto;
+    }
+    .us-timeline::before {
+        content: '';
+        position: absolute;
+        left: 84px;
+        top: 0;
+        bottom: 0;
+        width: 3px;
+        background: #3c8dbc;
+    }
+    .us-event {
+        position: relative;
+        margin-bottom: 0;
+        padding-left: 120px;
+        padding-bottom: 30px;
+    }
+    .us-event:last-child {
+        padding-bottom: 0;
+    }
+    .us-event-number {
+        position: absolute;
+        left: 0;
+        top: 5px;
+        font-size: 48px;
+        font-weight: 700;
+        color: #d2d6de;
+        line-height: 1;
+        text-align: right;
+        width: 60px;
+    }
+    .us-event-dot {
+        position: absolute;
+        left: 75px;
+        top: 18px;
+        width: 22px;
+        height: 22px;
+        border-radius: 50%;
+        background: #3c8dbc;
+        border: 3px solid #fff;
+        box-shadow: 0 0 0 2px #3c8dbc;
+        z-index: 1;
+    }
+    .us-event-dot.uuid_change {
+        background: #f39c12;
+        box-shadow: 0 0 0 2px #f39c12;
+    }
+    .us-event-dot.redirect,
+    .us-event-dot.return {
+        background: #00a65a;
+        box-shadow: 0 0 0 2px #00a65a;
+    }
+    .us-event-dot.ajax_request {
+        background: #605ca8;
+        box-shadow: 0 0 0 2px #605ca8;
+    }
+    .us-event-dot.ajax_response {
+        background: #00c0ef;
+        box-shadow: 0 0 0 2px #00c0ef;
+    }
+    .us-event-card {
+        background: #fff;
+        border: 1px solid #ddd;
+        border-left: 4px solid #3c8dbc;
+        border-radius: 4px;
+        padding: 14px 18px;
+        box-shadow: 0 1px 4px rgba(0,0,0,0.06);
+        transition: box-shadow 0.2s;
+    }
+    .us-event-card:hover {
+        box-shadow: 0 3px 12px rgba(0,0,0,0.12);
+    }
+    .us-event-card.uuid_change {
+        border-left-color: #f39c12;
+    }
+    .us-event-card.redirect,
+    .us-event-card.return {
+        border-left-color: #00a65a;
+    }
+    .us-event-card.ajax_request {
+        border-left-color: #605ca8;
+    }
+    .us-event-card.ajax_response {
+        border-left-color: #00c0ef;
+    }
+    .us-event-type {
+        font-size: 16px;
+        font-weight: 700;
+        margin-bottom: 2px;
+    }
+    .us-event-name {
+        font-size: 14px;
+        color: #555;
+        margin-bottom: 6px;
+    }
+    .us-event-time {
+        font-size: 12px;
+        font-style: italic;
+        color: #888;
+        margin-bottom: 4px;
+    }
+    .us-event-meta {
+        font-size: 11px;
+        color: #aaa;
+    }
+    .us-event-meta span {
+        margin-right: 15px;
+    }
+    .us-event-url {
+        display: inline-block;
+        max-width: 350px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        vertical-align: bottom;
+        cursor: default;
+    }
+    .us-event-data {
+        margin-top: 8px;
+        padding: 8px 10px;
+        background: #f7f7f7;
+        border-radius: 3px;
+        font-size: 12px;
+        font-family: monospace;
+        word-break: break-all;
+        max-height: 200px;
+        overflow-y: auto;
+        display: none;
+    }
+    .us-event-toggle {
+        cursor: pointer;
+        color: #3c8dbc;
+        font-size: 11px;
+        margin-top: 6px;
+        display: inline-block;
+    }
+    .us-event-toggle:hover {
+        text-decoration: underline;
+    }
+    .us-badge {
+        display: inline-block;
+        padding: 2px 8px;
+        border-radius: 3px;
+        color: #fff;
+        font-size: 11px;
+        font-weight: 600;
+    }
+    .us-badge.click { background: #3c8dbc; }
+    .us-badge.page_view { background: #3c8dbc; }
+    .us-badge.ajax_request { background: #605ca8; }
+    .us-badge.ajax_response { background: #00c0ef; }
+    .us-badge.redirect { background: #00a65a; }
+    .us-badge.return { background: #00a65a; }
+    .us-badge.uuid_change { background: #f39c12; }
+    .us-badge.form_submit { background: #d81b60; }
+</style>
+
+<div class="content-wrapper">
+    <section class="content-header">
+        <h1>
+            User Story Timeline
+            <small><code><?=htmlspecialchars($uuid)?></code></small>
+        </h1>
+        <div style="margin-top: 10px;">
+            <a href="/userstory" class="btn btn-default btn-sm"><i class="fa fa-arrow-left"></i> Back to list</a>
+        </div>
+    </section>
+
+    <section class="content">
+        <div class="box box-info">
+            <div class="box-header with-border">
+                <h3 class="box-title"><i class="fa fa-clock-o"></i> Timeline</h3>
+                <span class="badge bg-blue pull-right"><?=count($events)?> events</span>
+            </div>
+            <div class="box-body">
+                <div class="us-timeline">
+                    <?php foreach ($events as $i => $event) : ?>
+                    <div class="us-event">
+                        <div class="us-event-number"><?=$i + 1?></div>
+                        <div class="us-event-dot <?=htmlspecialchars($event->event_type)?>"></div>
+                        <div class="us-event-card <?=htmlspecialchars($event->event_type)?>">
+                            <div class="us-event-type">
+                                <span class="us-badge <?=htmlspecialchars($event->event_type)?>"><?=htmlspecialchars($event->event_type)?></span>
+                                <?=htmlspecialchars($event->event_name)?>
+                            </div>
+                            <div class="us-event-time"><?=$event->created_at?></div>
+                            <div class="us-event-meta">
+                                <span><i class="fa fa-globe"></i> <?=htmlspecialchars($event->ip_address ?: '-')?></span>
+                                <span><i class="fa fa-link"></i> <span class="us-event-url" title="<?=htmlspecialchars($event->page_url ?: '-')?>"><?=htmlspecialchars($event->page_url ?: '-')?></span></span>
+                            </div>
+                            <?php if ($event->event_type === 'uuid_change' && !empty($event->previous_uuid)) : ?>
+                            <div class="us-event-meta" style="margin-top: 4px;">
+                                <span><i class="fa fa-exchange"></i> <strong><?=htmlspecialchars($event->previous_uuid)?></strong> &rarr; <strong><?=htmlspecialchars($event->uuid)?></strong></span>
+                            </div>
+                            <?php endif; ?>
+                            <?php if (!empty($event->hotel)) : ?>
+                            <div class="us-event-meta" style="margin-top: 4px;">
+                                <span><i class="fa fa-building"></i> Hotel: <?=htmlspecialchars($event->hotel)?></span>
+                            </div>
+                            <?php endif; ?>
+                            <?php if (!empty($event->order_id)) : ?>
+                            <div class="us-event-meta" style="margin-top: 4px;">
+                                <span><i class="fa fa-shopping-cart"></i> Order: <?=htmlspecialchars($event->order_id)?></span>
+                            </div>
+                            <?php endif; ?>
+                            <?php if (!empty($event->event_data) && $event->event_data !== '""') : ?>
+                            <span class="us-event-toggle" onclick="$(this).next().slideToggle(200)"><i class="fa fa-code"></i> Show data</span>
+                            <div class="us-event-data"><pre style="margin:0;white-space:pre-wrap;"><?=htmlspecialchars($event->event_data)?></pre></div>
+                            <?php endif; ?>
+                            <?php if (!empty($event->cart) && $event->cart !== '""') : ?>
+                            <span class="us-event-toggle" onclick="$(this).next().slideToggle(200)"><i class="fa fa-shopping-basket"></i> Show cart</span>
+                            <div class="us-event-data"><pre style="margin:0;white-space:pre-wrap;"><?=htmlspecialchars($event->cart)?></pre></div>
+                            <?php endif; ?>
+                        </div>
+                    </div>
+                    <?php endforeach; ?>
+                </div>
+            </div>
+        </div>
+    </section>
+</div>
+
+<?php include 'admin_footer.php'; ?>

+ 30 - 0
cron_cleanup_user_events.php

@@ -0,0 +1,30 @@
+<?php
+
+error_reporting(E_ERROR);
+
+require_once __DIR__ . '/application/config/config.php';
+
+$db = new mysqli(
+    $config['db_host'],
+    $config['db_username'],
+    $config['db_password'],
+    $config['db_name']
+);
+
+if ($db->connect_error) {
+    echo 'DB connection failed: ' . $db->connect_error . PHP_EOL;
+    exit(1);
+}
+
+$db->set_charset('utf8');
+
+$sql = "DELETE FROM azonics_user_events WHERE created_at < DATE_SUB(NOW(), INTERVAL 60 DAY)";
+$result = $db->query($sql);
+
+if ($result) {
+    echo date('Y-m-d H:i:s') . ' - Deleted ' . $db->affected_rows . ' old user event(s).' . PHP_EOL;
+} else {
+    echo date('Y-m-d H:i:s') . ' - Error: ' . $db->error . PHP_EOL;
+}
+
+$db->close();

+ 1 - 1
index.php

@@ -4,7 +4,7 @@ header('Access-Control-Allow-Origin: *');
 header('Access-Control-Allow-Methods: POST, GET, DELETE, PUT, PATCH, OPTIONS');
 header('Access-Control-Allow-Headers: *');
 
-if (!stristr($_SERVER['REQUEST_URI'],'/admin') && !stristr($_SERVER['REQUEST_URI'],'/api')) {
+if (!stristr($_SERVER['REQUEST_URI'],'/admin') && !stristr($_SERVER['REQUEST_URI'],'/api') && !stristr($_SERVER['REQUEST_URI'],'/userstory')) {
     header('Location: /admin/login');
     die();
 }

+ 19 - 0
sql/azonics_user_events.sql

@@ -0,0 +1,19 @@
+CREATE TABLE `azonics_user_events` (
+  `event_id` int(11) NOT NULL AUTO_INCREMENT,
+  `uuid` varchar(255) NOT NULL,
+  `previous_uuid` varchar(255) DEFAULT NULL,
+  `hotel` varchar(255) DEFAULT NULL,
+  `order_id` varchar(255) DEFAULT NULL,
+  `cart` text DEFAULT NULL,
+  `event_type` varchar(100) NOT NULL,
+  `event_name` varchar(255) NOT NULL,
+  `event_data` text DEFAULT NULL,
+  `page_url` varchar(500) DEFAULT NULL,
+  `ip_address` varchar(45) DEFAULT NULL,
+  `user_agent` varchar(500) DEFAULT NULL,
+  `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (`event_id`),
+  KEY `idx_uuid` (`uuid`),
+  KEY `idx_created_at` (`created_at`),
+  KEY `idx_event_type` (`event_type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;

+ 50 - 0
sql/migration_2026_06_10.sql

@@ -0,0 +1,50 @@
+-- Migration: openws -> admincitywebshop
+-- Datum: 2026-06-10
+-- Leiras: Strukturalis kulonbsegek migracioja a forras (openws) es cel (admincitywebshop) kozott
+
+-- ============================================================
+-- 1. UJ TABLA: azonics_user_events
+--    (letezik a forrasban, hianyzik a celbol)
+-- ============================================================
+
+CREATE TABLE IF NOT EXISTS `azonics_user_events` (
+  `event_id` int(11) NOT NULL AUTO_INCREMENT,
+  `uuid` varchar(255) NOT NULL,
+  `previous_uuid` varchar(255) DEFAULT NULL,
+  `hotel` varchar(255) DEFAULT NULL,
+  `order_id` varchar(255) DEFAULT NULL,
+  `cart` text DEFAULT NULL,
+  `event_type` varchar(100) NOT NULL,
+  `event_name` varchar(255) NOT NULL,
+  `event_data` text DEFAULT NULL,
+  `page_url` varchar(500) DEFAULT NULL,
+  `ip_address` varchar(45) DEFAULT NULL,
+  `user_agent` varchar(500) DEFAULT NULL,
+  `created_at` datetime NOT NULL DEFAULT current_timestamp(),
+  PRIMARY KEY (`event_id`),
+  KEY `idx_uuid` (`uuid`),
+  KEY `idx_created_at` (`created_at`),
+  KEY `idx_event_type` (`event_type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
+
+-- ============================================================
+-- 2. OSZLOPTIPUS-KULONBSEGEK
+--    A cel (admincitywebshop) longtext-et hasznal ott, ahol a
+--    forras (openws) varchar(255)-ot.
+--    FIGYELEM: longtext -> varchar(255) adat-csonkulast okozhat!
+--    Csak akkor futtasd, ha biztosan nincs 255 karakternel
+--    hosszabb adat ezekben az oszlopokban.
+-- ============================================================
+
+-- 2a. azonics_boxes: box_subtitle, box_subtitle_en (longtext -> varchar(255))
+-- ALTER TABLE `azonics_boxes` MODIFY `box_subtitle` varchar(255) NOT NULL DEFAULT ' ';
+-- ALTER TABLE `azonics_boxes` MODIFY `box_subtitle_en` varchar(255) NOT NULL DEFAULT ' ';
+
+-- 2b. azonics_menus: box_subtitle, box_subtitle_en, box_banner (longtext -> varchar(255))
+-- ALTER TABLE `azonics_menus` MODIFY `box_subtitle` varchar(255) NOT NULL DEFAULT ' ';
+-- ALTER TABLE `azonics_menus` MODIFY `box_subtitle_en` varchar(255) NOT NULL DEFAULT ' ';
+-- ALTER TABLE `azonics_menus` MODIFY `box_banner` varchar(255) NOT NULL DEFAULT ' ';
+
+-- 2c. azonics_packages: box_subtitle, box_subtitle_en (longtext -> varchar(255))
+-- ALTER TABLE `azonics_packages` MODIFY `box_subtitle` varchar(255) NOT NULL DEFAULT ' ';
+-- ALTER TABLE `azonics_packages` MODIFY `box_subtitle_en` varchar(255) NOT NULL DEFAULT ' ';

+ 5 - 0
system/pip.php

@@ -573,6 +573,11 @@ function segmentRouter($segments,$controller,$action) {
 			$action = $segments[1];
 		break;
 
+		case 'userstory':
+			$controller = 'userstory';
+			$action = !empty($segments[1]) ? $segments[1] : 'index';
+		break;
+
 		case '':
 		break;
 

+ 45 - 0
userstory.md

@@ -0,0 +1,45 @@
+# Új user story logger modul
+Egy új modul fejlesztésére lenne szükségünk, amely az alábbiaknak megfelelően nézne ki:
+
+## Feladat:
+Egy modulra lenne szükségünk, amely a látogatók munkafolyamatait loggolja. Erre azért van szükségünk, mert látni szeretnénk, hogy egy látogató mit csinál a weboldalon, tehát: 
+- hogyan halad végig az egyes menüpontokon
+- milyen adatokat küld be az API felé
+- hova kattint az oldalon
+- mikor lép ki az oldalról (mondjuk simplepay fizetéskor) és mikor tér vissza (a fizetést követően). Közben milyen válaszokat kap a backend oldalról, illetve milyen adatokat küldd el a backend felé.
+
+Ezeket a felhasználói lépéseket illetve ezek láncolatát nevezzük user story-nak. Ez lényegében egy későbbi folyamatábrához kell majd. 
+
+## Megvalósítás
+A user story lépéseit külön-külön kell beküldeni az API-nak (ami még ugyan nincs kész, de a végpont neve az lesz, hogy "/userEvent"). A beküldendő adatok szerkezetét rádbízom, de ami biztosan kelleni fog azok az alábbiak:
+
+- A "uuid" ez a látogató egyedi azonosítója.
+- A "hotel" ez a szálloda ID-ja (ha ez megvan).
+- Az "orderID" (ha van ilyen adat).
+- "cart" object, ha ez is van.
+
+(Ezeket mint a localstorage-ből tudod kinyerni!)
+
+## Elvárt működés
+1. Bejön egy látogató a weboldalra: itt már beküldessz egy "Főoldal megnyitása" eseményt (user story item).
+2. Például rákattint a "Hop-On" menüpontra: ez is egye user story item beküldéssel járó esemény.
+3. Kiválaszt egy jegyet: itt is event generálódik.
+4. Kosárba teszi: ez is event.
+5. A megrendelési felületre lép: ez is event.
+6. A fizetési gombbra kattin: ez is event és itt már egy komplett form kitöltésre kerül, így itt is minden adatot el kell küldeni.
+7. A fizetési linkre történő kattintást követően a felhasználó átirányításra kerül a simplepay oldalára, ez is egy event lesz.
+8. Fizetés után (akár sikeres, akár nem) visszairányításra kerül a weboldalunkra: ez is egy fontos event adatokkal.
+
+!!!FONTOS!!! Minden kattintást vagy ajax hívás (akár POST, akár GET) eventként kell kezelni és az alap adatokkal illetve az adott ponton képződött adatokkal együtt be kell küldeni az API végpontnak, hogy loggolja azokat. Az AJAX hívások válaszait vagy a kattintások utáni oldalbetöltődések megtörténtét is eseményként be kell küldeni. Így egyfajta szendvics hívások jönnek létre, például:
+1. Gomb kattintás: esemény beküldés az alap adatokkal és ami még szükséges.
+3. Ebből jön egy POST ajax hívás: beküldöm az eseményt az alap és a generálódott vagy user által megadott adatokkal.
+2. Megjön a hívásra a válasz: újabb esemény, ami tartalmazza a szerver válaszát és az alap adatokat.
+
+Tehát a felhasználó bármilyen eseményt okoz az oldalon, azt be kell küldeni!
+
+Ami még fontos, hogy a simplepay_redirect-nél elveszítjük egy időre a user-t, viszont, amint visszatér, újra loggolni kell a dolgait. Amikor új "uuid"-t kap valamilyen okból, akkor ezt is követni kell! Ez talán a legnehezebb része, mivel az régi és az új "uuid" is kelleni fog. Talán a legegyszerűbb, ha erre is van egy event, ami logger enginnek leküldi az uuid változás tényét, amit a backend oldal már le tud kezelni.
+
+Első körben végezz egy tervezést a feladatra. Ha ezzel megvagy, egyeztessünk. Lépésről lépésre nézzük végig a tervet, hogy ahol kell korrigálni tudjunk!
+Bármilyen kérdésed van, tedd fel nekem! Közösen megnézzük, hogy megfelelő döntést hozhassunk.
+
+Ezt követően kezdődhet az implementációs szakasz, de erre külön kérdezz rá!