added visitor statistics

pull/44/head
A1Gard 5 months ago
parent 1c09293f1c
commit 8bef2c82b4

@ -40,7 +40,7 @@ class clientAssetGenerator extends Command
// prepare client.scss and add gfx variable
$js = "// PLEASE DO NOT EDIT THIS FILE, \n// IF YOU WANT ADD ANY CODE CREATE NEW JS INTO client-custom" . PHP_EOL;
$js = "// PLEASE DO NOT EDIT THIS FILE, \n// IF YOU WANT ADD ANY CODE CREATE NEW JS INTO client-custom \n import axios from 'axios'; \n window.axios = axios; \n \n window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';" . PHP_EOL;
$variables = "// PLEASE DO NOT EDIT THIS FILE, \n// IF YOU WANT ADD ANY CODE CREATE NEW SCSS INTO client-custom" . PHP_EOL;
foreach ($vars as $k => $var) {
$variables .= '$'."$k:$var;" . PHP_EOL;

@ -0,0 +1,426 @@
<?php
namespace App\Helpers;
/**
* @package Helpers
* @author A1Gard <a1gard@4xmen.ir>
* @date : 3-April-2013 (14-1-1392)
* @time : 20:32
* @subpackage TVisitor
* @version 1.0
* @todo : TVisitor class for get visitor info
*/
class TVisitor {
function __construct() {
}
/**
* @todo Detect visitor OS
* @return int os number
*/
public static function DetectOSI() {
if (!isset($_SERVER['HTTP_USER_AGENT']))
return 0;
$os_list = array(
'(Linux)',
'(Windows NT 11.0)', // Added Windows 11
'(Windows NT 10.0)',
'(Windows NT 6.3)',
'(Windows NT 6.2)',
'(Windows NT 6.1)',
'(Windows NT 6.0)',
'(Windows NT 5.2)',
'(Windows NT 5.1)',
'(Windows NT 5.0)',
'(Windows NT 4.0)',
'(Win 9x 4.90)',
'(Windows 98)',
'(Windows 95)',
'(Windows CE)',
'Windows (iPhone|iPad)',
'(iPhone)|(iPad)',
'(Mac OS X)',
'(MacPPC)|(Mac_PowerPC)|(Macintosh)',
'(Ubuntu)',
'(Linux Mint)',
'(Debian)',
'(Fedora)',
'(Red Hat)',
'(SuSE)',
'(Android)',
'(webOS)|(hpwOS)',
'(BlackBerry)',
'(Symbian)',
'(FreeBSD)',
'(OpenBSD)',
'(NetBSD)',
'(SunOS)',
'(OpenSolaris)',
'(Chrome OS)',
'(CrOS)',
'(bot)'
);
foreach ($os_list as $index => $match) {
if (preg_match("/$match/i", $_SERVER['HTTP_USER_AGENT'])) {
return $index + 1;
}
}
return null;
}
/**
* @todo Detect visitor OS
* @return string OS name
*/
public static function DetectOS() {
if (!isset($_SERVER['HTTP_USER_AGENT']))
return 'Unknown';
$os_list = array(
'Linux' => '(Linux)',
'Windows 11' => '(Windows NT 11.0)', // Added Windows 11
'Windows 10' => '(Windows NT 10.0)',
'Windows 8.1' => '(Windows NT 6.3)',
'Windows 8' => '(Windows NT 6.2)',
'Windows 7' => '(Windows NT 6.1)',
'Windows Vista' => '(Windows NT 6.0)',
'Windows Server 2003/XP x64' => '(Windows NT 5.2)',
'Windows XP' => '(Windows NT 5.1)',
'Windows 2000' => '(Windows NT 5.0)',
'Windows ME' => '(Win 9x 4.90)',
'Windows 98' => '(Windows 98)',
'Windows 95' => '(Windows 95)',
'Windows CE' => '(Windows CE)',
'Windows (iPhone/iPad)' => 'Windows (iPhone|iPad)',
'iPhone/iPad' => '(iPhone)|(iPad)',
'Mac OS X' => '(Mac OS X)',
'Mac OS' => '(MacPPC)|(Mac_PowerPC)|(Macintosh)',
'Ubuntu' => '(Ubuntu)',
'Linux Mint' => '(Linux Mint)',
'Debian' => '(Debian)',
'Fedora' => '(Fedora)',
'Red Hat' => '(Red Hat)',
'SuSE' => '(SuSE)',
'Android' => '(Android)',
'webOS' => '(webOS)|(hpwOS)',
'BlackBerry' => '(BlackBerry)',
'Symbian' => '(Symbian)',
'FreeBSD' => '(FreeBSD)',
'OpenBSD' => '(OpenBSD)',
'NetBSD' => '(NetBSD)',
'SunOS' => '(SunOS)',
'OpenSolaris' => '(OpenSolaris)',
'Chrome OS' => '(Chrome OS)|(CrOS)',
'bot' => '(bot)'
);
foreach ($os_list as $os => $pattern) {
if (preg_match("/$pattern/i", $_SERVER['HTTP_USER_AGENT'])) {
return $os;
}
}
return 'Unknown';
}
/**
* @todo Detect if visitor is using a mobile device
* @return bool
*/
public static function IsMobile() {
if (!isset($_SERVER['HTTP_USER_AGENT'])) {
return false;
}
$user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
// List of mobile devices and operating systems
$mobile_agents = [
'mobile', 'android', 'iphone', 'ipod', 'ipad', 'windows phone', 'blackberry', 'kindle', 'silk',
'opera mini', 'opera mobi', 'webos', 'symbian', 'nokia', 'samsung', 'lg', 'htc', 'mot', 'tablet',
'rim tablet', 'meego', 'netfront', 'bolt', 'fennec', 'series60', 'maemo', 'midp', 'cldc', 'up.browser',
'up.link', 'mmp', 'symbian', 'smartphone', 'wap'
];
// Check if user agent contains any mobile keywords
foreach ($mobile_agents as $agent) {
if (strpos($user_agent, $agent) !== false) {
return true;
}
}
// Check for mobile-specific headers
if (isset($_SERVER['HTTP_ACCEPT'])) {
if (strpos(strtolower($_SERVER['HTTP_ACCEPT']), 'application/vnd.wap.xhtml+xml') !== false) {
return true;
}
}
if (isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE'])) {
return true;
}
// Check for Opera Mini
if (isset($_SERVER['HTTP_X_OPERAMINI_PHONE_UA'])) {
return true;
}
// Use PHP's built-in mobile detection (if available)
if (function_exists('http_negotiate_language')) {
$accept = http_negotiate_language(['wap', 'html']);
if ($accept === 'wap') {
return true;
}
}
return false;
}
/**
* @todo Get browser name only
* @return string browser name
*/
public static function DetectBrowser() {
if (!isset($_SERVER['HTTP_USER_AGENT']))
return 'Unknown';
$browser_list = array(
'Firefox' => '(Firefox)',
'Edge' => '(Edg|Edge)',
'Chrome' => '(Chrome)(?!.*Edge)',
'Safari' => '(Safari)(?!.*Chrome)',
'Opera' => '(OPR|Opera)',
'Brave' => '(Brave)',
'Internet Explorer' => '(MSIE|Trident)',
'DeepNet Explorer' => '(Deepnet)',
'Flock' => '(Flock)',
'Maxthon' => '(Maxthon)',
'Avant Browser' => '(Avant)',
'AOL' => '(AOL)',
'Vivaldi' => '(Vivaldi)',
'UC Browser' => '(UCBrowser)',
'Yandex Browser' => '(YaBrowser)',
'Samsung Internet' => '(SamsungBrowser)',
);
foreach ($browser_list as $browser => $pattern) {
if (preg_match("/$pattern/i", $_SERVER['HTTP_USER_AGENT'])) {
return $browser;
}
}
return 'Other';
}
/**
* @todo Get browser name only
* @return int browser num
*/
public static function DetectBrowserI() {
if (!isset($_SERVER['HTTP_USER_AGENT']))
return 0;
$browser_list = array(
'(Firefox)',
'(Edg|Edge)',
'(Chrome)(?!.*Edge)',
'(Safari)(?!.*Chrome)',
'(OPR|Opera)',
'(Brave)',
'(MSIE|Trident)',
'(Deepnet)',
'(Flock)',
'(Maxthon)',
'(Avant)',
'(AOL)',
'(Vivaldi)',
'(UCBrowser)',
'(YaBrowser)',
'(SamsungBrowser)',
);
foreach ($browser_list as $index => $pattern) {
if (preg_match("/$pattern/i", $_SERVER['HTTP_USER_AGENT'])) {
return $index + 1;
}
}
return 0;
}
/**
* @todo Find browser version
* @return string version
*/
public static function BrowserVersion() {
if (!isset($_SERVER['HTTP_USER_AGENT'])) {
return '0';
}
$ua = $_SERVER['HTTP_USER_AGENT'];
$browser = self::DetectBrowser();
$version = null;
switch ($browser) {
case 'Edge':
if (preg_match('/(Edge|Edg)\/(\d+(\.\d+)*)/', $ua, $matches)) {
$version = $matches[2];
}
break;
case 'Chrome':
if (preg_match('/Chrome\/(\d+(\.\d+)*)/', $ua, $matches)) {
$version = $matches[1];
}
break;
case 'Firefox':
if (preg_match('/Firefox\/(\d+(\.\d+)*)/', $ua, $matches)) {
$version = $matches[1];
}
break;
case 'Safari':
if (preg_match('/Version\/(\d+(\.\d+)*)/', $ua, $matches)) {
$version = $matches[1];
}
break;
case 'Opera':
if (preg_match('/(OPR|Opera)\/(\d+(\.\d+)*)/', $ua, $matches)) {
$version = $matches[2];
}
break;
case 'Internet Explorer':
if (preg_match('/MSIE (\d+(\.\d+)*)/', $ua, $matches)) {
$version = $matches[1];
} elseif (preg_match('/rv:(\d+(\.\d+)*)/', $ua, $matches)) {
$version = $matches[1]; // For IE 11
}
break;
default:
// Generic version detection for other browsers
if (preg_match('/' . preg_quote($browser, '/') . '\/(\d+(\.\d+)*)/', $ua, $matches)) {
$version = $matches[1];
}
break;
}
return $version;
}
/**
* Get searched keywords from referrer URL
* @param string $referer
* @return array|null
*/
public static function GetKeyword($referer = null) {
if ($referer === null) {
$referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
}
if (empty($referer)) {
return null;
}
$engines = [
'google' => ['q', 'query'],
'bing' => ['q'],
'yahoo' => ['p'],
'yandex' => ['text'],
'baidu' => ['wd', 'word'],
'duckduckgo' => ['q'],
'ask' => ['q'],
'aol' => ['q'],
'naver' => ['query'],
'ecosia' => ['q'],
];
$parsed_url = parse_url($referer);
$host = isset($parsed_url['host']) ? strtolower($parsed_url['host']) : '';
$query = isset($parsed_url['query']) ? $parsed_url['query'] : '';
parse_str($query, $query_params);
foreach ($engines as $engine => $params) {
if (strpos($host, $engine) !== false) {
foreach ($params as $param) {
if (isset($query_params[$param]) && !empty($query_params[$param])) {
return [
'engine' => $engine,
'keyword' => urldecode($query_params[$param])
];
}
}
}
}
return null;
}
// /**
// * @param string $class alternative class
// * return vistor os icon
// * @uses awesome font
// */
// static public function GetOSIcon($class = '') {
// // get os
// $os_int = self::DetectOSI();
//
// $win = range(1, 16);
// $linux = range(17, 19);
// $osx = array(20, 21);
// $android = array(25);
// $searchbot = array(27);
// $other = array(0, 22, 23, 24, 26);
//
// switch (true) {
// case in_array($os_int, $win):
// $icon = 'windows';
// break;
// case in_array($os_int, $linux):
// $icon = 'linux';
// break;
// case in_array($os_int, $osx):
// $icon = 'apple';
// break;
//
// case in_array($os_int, $android):
// $icon = 'android';
// break;
//
// case in_array($os_int, $searchbot):
// $icon = 'google';
// break;
// default:
// $icon = 'question';
// break;
// }
//
// $result = '<span class="fa fa-' . $icon . ' ' . $class . '" title="'
// . self::DetectOS() . '" ></span>';
//
// return $result;
// }
// /**
// * @param string $class alternative class
// * return vistor browser icon
// * @uses awesome font
// */
// static public function GetBrowerIcon($class = '') {
// // get os
// $bowser = self::DetectBrowser();
//
//
//
// $result = '<span class="fa fa-' . strtolower($bowser) . ' ' . $class . '" title="'
// . self::DetectBrowser() . '" ></span>';
// return $result;
// }
}

@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Visitor;
use Illuminate\Http\Request;
class VisitorController extends Controller
{
//
public function display(Request $request){
$visitor = Visitor::where('ip', $request->ip())->orderByDesc('id')->first();
if ($visitor != null){
$visitor->display = $request->input('display',null);
$visitor->save();
}
return ['OK'=>true];
}
}

@ -0,0 +1,10 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class VisitorController extends Controller
{
//
}

@ -0,0 +1,38 @@
<?php
namespace App\Http\Middleware;
use App\Helpers\TVisitor;
use App\Models\Visitor;
use Carbon\Carbon;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class VisitorDetector
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$visitor = Visitor::where('updated_at','>',date("Y-m-d H:i:s" ,time() - (60*60)))
->where('ip', $request->ip())->first();
if ($visitor === null) {
$visitor = new Visitor();
$visitor->ip = $request->ip();
$visitor->browser = TVisitor::DetectBrowserI();
$visitor->os = TVisitor::DetectOSI();
$visitor->version = TVisitor::BrowserVersion();
$visitor->keywords = TVisitor::GetKeyword();
$visitor->is_mobile = TVisitor::IsMobile();
$visitor->save();
}else{
$visitor->increment('visit');
}
return $next($request);
}
}

@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Visitor extends Model
{
use HasFactory;
}

@ -0,0 +1,34 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Visitor>
*/
class VisitorFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
$displays = ['1920x1080','1366x768','1920x1080','1366x768','1280x1024',null, null];
$date = $this->faker->dateTimeBetween('-31 days', 'now');
return [
//
'ip' => $this->faker->ipv4(),
'visit' => rand(1,rand(2,12)),
'browser' => rand(0,5),
'os' => rand(0,14),
'version' => rand(100,132),
'display' => $displays[count($displays)-1],
'updated_at' => $date,
'created_at' => $date,
];
}
}

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('visitors', function (Blueprint $table) {
$table->id();
$table->ipAddress('ip');
$table->unsignedInteger('visit')->default(1);
$table->unsignedInteger('browser')->nullable();
$table->unsignedInteger('os')->nullable();
$table->string('version')->nullable();
$table->string('display')->nullable();
$table->string('keywords')->nullable();
$table->boolean('is_mobile')->default(false);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('visitors');
}
};

@ -37,6 +37,7 @@ class DatabaseSeeder extends Seeder
GfxSeeder::class,
AreaSeeder::class,
PartSeeder::class,
VisitorSeeder::class
]
);
}

@ -0,0 +1,19 @@
<?php
namespace Database\Seeders;
use App\Models\Visitor;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class VisitorSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
//
Visitor::factory()->count(110)->create();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

@ -0,0 +1,49 @@
window.addEventListener('load', function () {
const API_COOKIE_NAME = 'last_api_call';
const COOKIE_EXPIRY_MINUTES = 59;
function setCookie(name, value, minutes) {
let expires = "";
if (minutes) {
let date = new Date();
date.setTime(date.getTime() + (minutes * 60 * 1000));
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}
function getCookie(name) {
let nameEQ = name + "=";
let ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
function canSendData() {
let lastCall = getCookie(API_COOKIE_NAME);
if (!lastCall) return true;
let lastCallTime = new Date(parseInt(lastCall));
let currentTime = new Date();
let timeDiff = (currentTime - lastCallTime) / (1000 * 60); // difference in minutes
return timeDiff >= COOKIE_EXPIRY_MINUTES;
}
if (canSendData()) {
axios.post(document.querySelector('#api-display-url').value, {
display: window.screen.availWidth + 'x' + window.screen.availHeight,
}).then(function (response) {
// If the API call is successful, set the cookie
setCookie(API_COOKIE_NAME, new Date().getTime(), COOKIE_EXPIRY_MINUTES);
}).catch(function (error) {
console.error('Error sending data:', error);
});
} else {
console.log('Data was sent recently. Skipping this time.');
}
});

@ -1,9 +1,16 @@
// PLEASE DO NOT EDIT THIS FILE,
// IF YOU WANT ADD ANY CODE CREATE NEW JS INTO client-custom
// IF YOU WANT ADD ANY CODE CREATE NEW JS INTO client-custom
import axios from 'axios';
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
import "./client-custom/assetsNode.js";
import "./client-custom/confirm.js";
import "./client-custom/windowLoader.js";
import "../views/segments/preloader/PreloaderCircle/PreloaderCircle.js";
import "../views/segments/top/TopSimple/TopSimple.js";
import "../views/segments/slider/SliderSimple/SliderSimple.js";
import "../views/segments/posts/PostsIconSimple/PostsIconSimple.js";
import "../views/segments/index/GradientTextLink/GradientTextLink.js";
import "../views/segments/category/FavProductWithMeta/FavProductWithMeta.js";
import "../views/segments/parallax/ParallaxShort/ParallaxShort.js";

@ -1,19 +1,19 @@
// PLEASE DO NOT EDIT THIS FILE,
// IF YOU WANT ADD ANY CODE CREATE NEW SCSS INTO client-custom
$xshop-background:#ffffff;
$xshop-primary:#6e0000;
$xshop-primary:#009dff;
$xshop-diff:#ffffff;
$xshop-secondary:#ff0000;
$xshop-secondary:#0008ff;
$xshop-text:#111111;
$xshop-border-radius:6px;
$xshop-border-radius:0px;
$xshop-shadow:2px 2px 4px #777777;
:root{
--xshop-background:#ffffff;
--xshop-primary:#6e0000;
--xshop-primary:#009dff;
--xshop-diff:#ffffff;
--xshop-secondary:#ff0000;
--xshop-secondary:#0008ff;
--xshop-text:#111111;
--xshop-border-radius:6px;
--xshop-border-radius:0px;
--xshop-shadow:2px 2px 4px #777777;
}
@ -25,3 +25,5 @@ $xshop-shadow:2px 2px 4px #777777;
@import "../views/segments/slider/SliderSimple/SliderSimple";
@import "../views/segments/posts/PostsIconSimple/PostsIconSimple";
@import "../views/segments/index/GradientTextLink/GradientTextLink";
@import "../views/segments/category/FavProductWithMeta/FavProductWithMeta";
@import "../views/segments/parallax/ParallaxShort/ParallaxShort";

@ -1,3 +1,4 @@
@yield('custom-foot')
<input type="hidden" id="api-display-url" value="{{route('v1.visitor.display')}}">
</body>
</html>

@ -37,4 +37,5 @@ Route::prefix('v1')->name('v1.')->group(
Route::get('state/{state}', [\App\Http\Controllers\Api\StateController::class,'show'])->name('state.show');
Route::get('category/props/{category}', [\App\Http\Controllers\Api\CategoryController::class,'props'])->name('category.prop');
Route::post('morph/search', [\App\Http\Controllers\Api\MorphController::class,'search'])->name('morph.search');
Route::post('visitor/display', [\App\Http\Controllers\Api\VisitorController::class,'display'])->name('visitor.display');
});

@ -5,7 +5,7 @@ use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
})->name('welcome');
})->name('welcome')->middleware(\App\Http\Middleware\VisitorDetector::class);
Auth::routes(['register' => false]);
@ -346,3 +346,5 @@ Route::get('test',function (){
});

Loading…
Cancel
Save