/home/optimumoperation/digitalcard.optimumoperations.top/app/Http/Controllers/LinkBioController.php
<?php
namespace App\Http\Controllers;
use App\Models\BioLink;
use App\Models\Plan;
use App\Models\DomainRequest;
use App\Models\User;
use App\Services\DomainValidationService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Inertia\Inertia;
class LinkBioController extends Controller
{
/**
* Display a listing of the bio links.
*/
public function index(Request $request)
{
$authUser = Auth::user();
// Apply Bio Link filtering based on user type and permissions
if ($authUser->type === 'company') {
$staffIds = User::where('created_by', $authUser->id)->pluck('id')->toArray();
$staffIds = array_merge([$authUser->id], $staffIds);
$query = BioLink::whereIn('created_by', $staffIds);
} else {
// Staff users can only see Bio Links created by their company
if ($authUser->can('manage-bio-link-builder') || $authUser->can('view-bio-link-builder')) {
$query = BioLink::where('created_by', $authUser->id);
} else {
// No permission - return empty result
$query = BioLink::whereRaw('1 = 0');
}
}
// Apply search filter
if ($request->has('search') && !empty($request->search)) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('slug', 'like', "%{$search}%");
});
}
// Apply type filter
if ($request->has('type') && $request->type !== 'all') {
$query->where('link_type', $request->type);
}
// Apply sorting
$sortField = $request->sort_field ?? 'created_at';
$sortDirection = $request->sort_direction ?? 'desc';
$query->orderBy($sortField, $sortDirection);
// Apply pagination
$perPage = $request->per_page ?? 10;
$bioLinks = $query->paginate($perPage)->withQueryString();
// Get plan limits for company users and staff users
$planLimits = null;
$authUser = Auth::user();
if ($authUser->type === 'company' && $authUser->plan) {
$currentBioLinks = BioLink::where('created_by', $authUser->id)->count();
$planLimits = [
'current_biolinks' => $currentBioLinks,
'max_biolinks' => $authUser->plan->bio_links,
'can_create' => $currentBioLinks < $authUser->plan->bio_links
];
}
// Check for staff users (created by company users)
elseif ($authUser->type !== 'superadmin' && $authUser->created_by) {
$companyUser = User::find($authUser->created_by);
if ($companyUser && $companyUser->type === 'company' && $companyUser->plan) {
$currentBioLinks = BioLink::where('created_by', $companyUser->id)->count();
$planLimits = [
'current_biolinks' => $currentBioLinks,
'max_biolinks' => $companyUser->plan->bio_links,
'can_create' => $currentBioLinks < $companyUser->plan->bio_links
];
}
}
return Inertia::render('link-bio-builder/index', [
'bioLinks' => $bioLinks,
'filters' => $request->only(['search', 'type', 'per_page', 'view', 'sort_field', 'sort_direction']),
'planLimits' => $planLimits
]);
}
/**
* Show the form for creating a new bio link.
*/
public function create()
{
$user = Auth::user()->load(['plan', 'roles']);
// Check plan limits for company users
if ($user->type === 'company' && $user->plan) {
$currentBioLinks = BioLink::where('created_by', $user->id)->count();
$maxBioLinks = $user->plan->bio_links;
if ($currentBioLinks >= $maxBioLinks) {
return redirect()->route('link-bio-builder.index')
->with('error', __('Bio link limit exceeded. Your plan allows maximum :max bio links. Please upgrade your plan.', ['max' => $maxBioLinks]));
}
}
// Check plan limits for staff users (created by company users)
elseif ($user->type !== 'superadmin' && $user->created_by) {
$companyUser = User::find($user->created_by);
if ($companyUser && $companyUser->type === 'company' && $companyUser->plan) {
$currentBioLinks = BioLink::where('created_by', $companyUser->id)->count();
$maxBioLinks = $companyUser->plan->bio_links;
if ($currentBioLinks >= $maxBioLinks) {
return redirect()->route('link-bio-builder.index')
->with('error', __('Bio link limit exceeded. Your company plan allows maximum :max bio links. Please contact your administrator.', ['max' => $maxBioLinks]));
}
}
}
// Get plan data for form restrictions
$userPlan = null;
$planFeatures = [];
if ($user->type === 'company' && $user->plan) {
$userPlan = $user->plan;
$planFeatures = $user->plan->features ?: [];
} elseif ($user->type !== 'superadmin' && $user->created_by) {
$companyUser = User::find($user->created_by);
if ($companyUser && $companyUser->type === 'company' && $companyUser->plan) {
$userPlan = $companyUser->plan;
$planFeatures = $companyUser->plan->features ?: [];
}
}
return Inertia::render('link-bio-builder/create', [
'userPlan' => $userPlan,
'planFeatures' => $planFeatures,
'userRole' => $user->roles->pluck('name')->first() ?? null,
'themeImages' => [],
'baseUrl' => config('app.url')
]);
}
/**
* Store a newly created bio link in storage.
*/
public function store(Request $request)
{
$authUser = Auth::user();
// Check plan limits for company users
if ($authUser->type === 'company' && $authUser->plan) {
$currentBioLinks = BioLink::where('created_by', $authUser->id)->count();
$maxBioLinks = $authUser->plan->bio_links;
if ($currentBioLinks >= $maxBioLinks) {
return redirect()->back()->with('error', __('Bio link limit exceeded. Your plan allows maximum :max bio links. Please upgrade your plan.', ['max' => $maxBioLinks]));
}
}
// Check plan limits for staff users (created by company users)
elseif ($authUser->type !== 'superadmin' && $authUser->created_by) {
$companyUser = User::find($authUser->created_by);
if ($companyUser && $companyUser->type === 'company' && $companyUser->plan) {
$currentBioLinks = BioLink::where('created_by', $companyUser->id)->count();
$maxBioLinks = $companyUser->plan->bio_links;
if ($currentBioLinks >= $maxBioLinks) {
return redirect()->back()->with('error', __('Bio link limit exceeded. Your company plan allows maximum :max bio links. Please contact your administrator.', ['max' => $maxBioLinks]));
}
}
}
$validated = $request->validate([
'name' => 'required|string|max:255',
'slug' => 'required|string|max:255',
'link_type' => 'required|string',
'custom_domain' => 'nullable|string|max:255',
'url_prefix' => 'nullable|string|max:255',
'password' => 'nullable|string',
'password_enabled' => 'boolean',
'config' => 'required|array',
]);
// Validate using centralized service
$validationErrors = DomainValidationService::validateBioLink($validated);
if (!empty($validationErrors)) {
return back()->withErrors($validationErrors);
}
$data = $validated;
// Hash password if provided
if (!empty($data['password'])) {
$data['password'] = Hash::make($data['password']);
}
// Determine the creator ID based on user type
// Convert media URLs to relative paths in config
if (isset($data['config'])) {
$data['config'] = $this->convertMediaUrlsToRelativePaths($data['config']);
}
// Create the bio link
$bioLink = new BioLink();
$bioLink->created_by = $authUser->id;
$bioLink->name = $data['name'];
$bioLink->slug = $data['slug'];
$bioLink->link_type = $data['link_type'];
$bioLink->custom_domain = $data['custom_domain'] ?? null;
$bioLink->url_prefix = $data['url_prefix'] ?? 'bio';
$bioLink->password = $data['password'] ?? null;
$bioLink->password_enabled = $data['password_enabled'] ?? false;
$bioLink->config = $data['config'];
$bioLink->is_active = true; // Ensure bio link is active by default
$bioLink->save();
// Create domain request if custom domain or subdomain is used
if (in_array($request['domain_type'], ['domain', 'subdomain'])) {
if ($request['domain_type'] == 'domain' && isset($request['custom_domain'])) {
$existingRequest = DomainRequest::where('biolink_id', $bioLink->id)
->where('domain', $request['custom_domain'])
->first();
if (!$existingRequest) {
DomainRequest::create([
'biolink_id' => $bioLink->id,
'user_id' => auth()->id(),
'domain' => $request['custom_domain'],
'status' => 'pending'
]);
}
}
if ($request['domain_type'] == 'subdomain' && isset($request['slug'])) {
$domain = $request['slug'].'.'.$request->getHost();;
$existingRequest = DomainRequest::where('biolink_id', $bioLink->id)
->where('domain', $domain)
->first();
if (!$existingRequest) {
DomainRequest::create([
'biolink_id' => $bioLink->id,
'user_id' => auth()->id(),
'domain' => $domain,
'status' => 'pending'
]);
}
}
}
return redirect()->route('link-bio-builder.index')
->with('success', __('Bio link created successfully.'));
}
/**
* Show the form for editing the specified bio link.
*/
public function edit(BioLink $bioLink)
{
// Verify user has access to this Bio Link
// $authUser = Auth::user();
// $hasAccess = false;
// if ($authUser->type === 'superadmin' || $authUser->type === 'super admin') {
// $hasAccess = true;
// } elseif ($authUser->type === 'company') {
// $hasAccess = $bioLink->created_by === $authUser->id;
// } else {
// // Staff users can only edit Bio Links created by their company
// if ($authUser->can('manage-bio-link-builder') || $authUser->can('edit-bio-link-builder')) {
// $hasAccess = $bioLink->created_by === $authUser->created_by;
// }
// }
// if (!$hasAccess) {
// abort(403, 'You do not have permission to edit this Bio Link.');
// }
$user = Auth::user()->load(['plan', 'roles']);
// Get plan data for form restrictions
$userPlan = null;
$planFeatures = [];
if ($user->type === 'company' && $user->plan) {
$userPlan = $user->plan;
$planFeatures = $user->plan->features ?: [];
} elseif ($user->type !== 'superadmin' && $user->created_by) {
$companyUser = User::find($user->created_by);
if ($companyUser && $companyUser->type === 'company' && $companyUser->plan) {
$userPlan = $companyUser->plan;
$planFeatures = $companyUser->plan->features ?: [];
}
}
// Convert relative media paths to full URLs for editing
$bioLink = $this->convertRelativePathsToUrlsForEdit($bioLink);
return Inertia::render('link-bio-builder/edit', [
'bioLink' => $bioLink,
'userPlan' => $userPlan,
'planFeatures' => $planFeatures,
'userRole' => $user->roles->pluck('name')->first() ?? $user->type,
'themeImages' => []
]);
}
/**
* Update the specified bio link in storage.
*/
public function update(Request $request, BioLink $bioLink)
{
// Verify user has access to this Bio Link
// $authUser = Auth::user();
// $hasAccess = false;
// if ($authUser->type === 'superadmin' || $authUser->type === 'super admin') {
// $hasAccess = true;
// } elseif ($authUser->type === 'company') {
// $hasAccess = $bioLink->created_by === $authUser->id;
// } else {
// // Staff users can only update Bio Links created by their company
// if ($authUser->can('manage-bio-link-builder') || $authUser->can('edit-bio-link-builder')) {
// $hasAccess = $bioLink->created_by === $authUser->created_by;
// }
// }
// if (!$hasAccess) {
// abort(403, 'You do not have permission to update this Bio Link.');
// }
$validated = $request->validate([
'name' => 'required|string|max:255',
'slug' => 'required|string|max:255',
'link_type' => 'required|string',
'custom_domain' => 'nullable|string|max:255',
'url_prefix' => 'nullable|string|max:255',
'password' => 'nullable|string',
'password_enabled' => 'boolean',
'config' => 'required|array',
]);
// Validate using centralized service (excluding current bio link)
$validationErrors = DomainValidationService::validateBioLink($validated, $bioLink->id);
if (!empty($validationErrors)) {
return back()->withErrors($validationErrors);
}
$data = $validated;
// Only update password if a new one is provided
if (!empty($data['password'])) {
$data['password'] = Hash::make($data['password']);
} else {
unset($data['password']);
}
// Convert media URLs to relative paths in config
if (isset($data['config'])) {
$data['config'] = $this->convertMediaUrlsToRelativePaths($data['config']);
}
$bioLink->update($data);
if (in_array($request['domain_type'], ['domain', 'subdomain'])) {
if ($request['domain_type'] == 'domain' && isset($request['custom_domain'])) {
$existingRequest = DomainRequest::where('biolink_id', $bioLink->id)
->where('domain', $request['custom_domain'])
->first();
if (!$existingRequest) {
DomainRequest::create(attributes: [
'biolink_id' => $bioLink->id,
'user_id' => auth()->id(),
'domain' => $request['custom_domain'],
'status' => 'pending'
]);
}
}
if ($request['domain_type'] == 'subdomain' && isset($request['slug'])) {
$domain = $request['slug'].'.'.$request->getHost();;
$existingRequest = DomainRequest::where('biolink_id', $bioLink->id)
->where('domain', $domain)
->first();
if (!$existingRequest) {
DomainRequest::create([
'biolink_id' => $bioLink->id,
'user_id' => auth()->id(),
'domain' => $domain,
'status' => 'pending'
]);
}
}
}
return redirect()->route('link-bio-builder.index')
->with('success', __('Bio link updated successfully.'));
}
/**
* Remove the specified bio link from storage.
*/
public function destroy(BioLink $bioLink)
{
// Verify user has access to this Bio Link
// $authUser = Auth::user();
// $hasAccess = false;
// if ($authUser->type === 'superadmin' || $authUser->type === 'super admin') {
// $hasAccess = true;
// } elseif ($authUser->type === 'company') {
// $hasAccess = $bioLink->created_by === $authUser->id;
// } else {
// // Staff users can only delete Bio Links created by their company
// if ($authUser->can('manage-bio-link-builder') || $authUser->can('delete-bio-link-builder')) {
// $hasAccess = $bioLink->created_by === $authUser->created_by;
// }
// }
// if (!$hasAccess) {
// abort(403, 'You do not have permission to delete this Bio Link.');
// }
$bioLink->delete();
return redirect()->route('link-bio-builder.index')
->with('success', __('Bio link deleted successfully.'));
}
/**
* Check if a slug is available.
*/
public function checkSlug(Request $request)
{
$slug = $request->input('slug');
$bioLinkId = $request->input('bio_link_id');
$urlPrefix = $request->input('url_prefix', 'bio');
$available = DomainValidationService::isSlugAvailable($slug, $urlPrefix, null, $bioLinkId);
return response()->json([
'available' => $available,
'slug' => $slug
]);
}
/**
* Preview a bio link from localStorage data.
*/
public function preview()
{
return Inertia::render('public/BioLinkPreview', [
'themeImages' => []
]);
}
/**
* Generate a slug from a name.
*/
public function generateSlug(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
]);
$name = $request->input('name');
$bioLinkId = $request->input('bio_link_id');
$urlPrefix = $request->input('url_prefix', 'bio');
if (empty($name)) {
return response()->json(['slug' => '']);
}
$slug = $this->generateUniqueSlugAcrossTables($name, $bioLinkId, $urlPrefix);
return response()->json(['slug' => $slug]);
}
private function generateUniqueSlugAcrossTables($name, $excludeBioLinkId = null, $urlPrefix = 'bio')
{
return DomainValidationService::generateUniqueSlug($name, $urlPrefix, null, $excludeBioLinkId);
}
/**
* Convert media URLs to relative paths in config
*/
private function convertMediaUrlsToRelativePaths($config)
{
if (!is_array($config)) {
return $config;
}
return $this->recursivelyConvertUrls($config);
}
/**
* Recursively convert URLs in nested arrays
*/
private function recursivelyConvertUrls($data)
{
if (is_array($data)) {
foreach ($data as $key => $value) {
if (is_string($value) && $this->isMediaUrl($value)) {
$data[$key] = $this->convertToRelativePath($value);
} elseif (is_array($value)) {
$data[$key] = $this->recursivelyConvertUrls($value);
}
}
}
return $data;
}
/**
* Check if a string is a media URL
*/
private function isMediaUrl($url)
{
return is_string($url) && (str_contains($url, '/storage/media/') || str_contains($url, 'media/'));
}
/**
* Convert full URL to relative path for storage
*/
private function convertToRelativePath(string $url): string
{
if (!$url) return $url;
// Handle URLs that start with /storage/ (remove the /storage/ prefix)
if (str_starts_with($url, '/storage/')) {
return substr($url, 9); // Remove '/storage/'
}
// If it's already a relative path, return as is
if (!str_starts_with($url, 'http')) {
return $url;
}
// Extract the path after /storage/ and remove the /storage/ prefix
$storageIndex = strpos($url, '/storage/');
if ($storageIndex !== false) {
$path = substr($url, $storageIndex + 9); // +9 to skip '/storage/'
return $path;
}
return $url;
}
/**
* Show analytics for the specified bio link.
*/
public function analytics(Request $request, BioLink $bioLink)
{
// Verify user has access to this Bio Link
// $authUser = Auth::user();
// $hasAccess = false;
// if ($authUser->type === 'superadmin' || $authUser->type === 'super admin') {
// $hasAccess = true;
// } elseif ($authUser->type === 'company') {
// $hasAccess = $bioLink->created_by === $authUser->id;
// } else {
// // Staff users can only view analytics for Bio Links created by their company
// if ($authUser->can('manage-bio-link-builder') || $authUser->can('analytics-bio-link-builder')) {
// $hasAccess = $bioLink->created_by === $authUser->created_by;
// }
// }
// if (!$hasAccess) {
// abort(403, 'You do not have permission to view analytics for this Bio Link.');
// }
$startDate = $request->input('start_date', now()->subDays(30)->format('Y-m-d'));
$endDate = $request->input('end_date', now()->format('Y-m-d'));
// Get analytics data from visits table
$visitsTable = config('visitor.table_name');
// Page views and unique visitors
$pageViews = \DB::table($visitsTable)
->where('biolink_id', $bioLink->id)
->whereBetween('created_at', [$startDate, $endDate . ' 23:59:59'])
->count();
$uniqueVisitors = \DB::table($visitsTable)
->where('biolink_id', $bioLink->id)
->whereBetween('created_at', [$startDate, $endDate . ' 23:59:59'])
->distinct('ip')
->count('ip');
// Daily page views
$dailyPageViews = \DB::table($visitsTable)
->where('biolink_id', $bioLink->id)
->whereBetween('created_at', [$startDate, $endDate . ' 23:59:59'])
->selectRaw('DATE(created_at) as date, COUNT(*) as views')
->groupBy('date')
->orderBy('date')
->get()
->toArray();
// Top referrers
$referrers = \DB::table($visitsTable)
->where('biolink_id', $bioLink->id)
->whereBetween('created_at', [$startDate, $endDate . ' 23:59:59'])
->selectRaw('COALESCE(NULLIF(referer, ""), "Direct") as referrer, COUNT(*) as count')
->groupBy('referrer')
->orderBy('count', 'desc')
->limit(10)
->get()
->toArray();
// Device types - use platform and browser info to determine device type
$devices = \DB::table($visitsTable)
->where('biolink_id', $bioLink->id)
->whereBetween('created_at', [$startDate, $endDate . ' 23:59:59'])
->selectRaw('
CASE
WHEN NULLIF(device, "") IS NOT NULL THEN device
WHEN platform LIKE "%Android%" OR platform LIKE "%iOS%" OR platform LIKE "%iPhone%" OR platform LIKE "%iPad%" THEN "Mobile"
WHEN platform LIKE "%Windows%" OR platform LIKE "%Mac%" OR platform LIKE "%Linux%" THEN "Desktop"
ELSE "Unknown"
END as device_type,
COUNT(*) as count
')
->groupBy('device_type')
->orderBy('count', 'desc')
->get()
->toArray();
// Platform breakdown
$platforms = \DB::table($visitsTable)
->where('biolink_id', $bioLink->id)
->whereBetween('created_at', [$startDate, $endDate . ' 23:59:59'])
->selectRaw('COALESCE(NULLIF(platform, ""), "Unknown") as platform_name, COUNT(*) as count')
->groupBy('platform_name')
->orderBy('count', 'desc')
->get()
->toArray();
// Browser breakdown
$browsers = \DB::table($visitsTable)
->where('biolink_id', $bioLink->id)
->whereBetween('created_at', [$startDate, $endDate . ' 23:59:59'])
->selectRaw('COALESCE(NULLIF(browser, ""), "Unknown") as browser_name, COUNT(*) as count')
->groupBy('browser_name')
->orderBy('count', 'desc')
->get()
->toArray();
$analytics = [
'pageViews' => $pageViews,
'uniqueVisitors' => $uniqueVisitors,
'dailyPageViews' => $dailyPageViews,
'referrers' => $referrers,
'devices' => $devices,
'platforms' => $platforms,
'browsers' => $browsers,
'startDate' => $startDate,
'endDate' => $endDate,
];
return Inertia::render('link-bio-builder/analytics', [
'bioLink' => $bioLink,
'analytics' => $analytics,
'filters' => $request->only(['start_date', 'end_date'])
]);
}
/**
* Check if a domain is available.
*/
public function checkDomain(Request $request)
{
$domain = $request->input('domain');
$bioLinkId = $request->input('bio_link_id');
$available = DomainValidationService::isDomainAvailable($domain, null, $bioLinkId);
return response()->json([
'available' => $available,
'domain' => $domain
]);
}
/**
* Convert relative media paths to full URLs for editing
*/
private function convertRelativePathsToUrlsForEdit($bioLink)
{
if (isset($bioLink->config)) {
$bioLink->config = $this->recursivelyConvertPathsToUrlsForBioLink($bioLink->config);
}
return $bioLink;
}
/**
* Recursively convert relative paths to full URLs for bio links
*/
private function recursivelyConvertPathsToUrlsForBioLink($data)
{
if (is_array($data)) {
foreach ($data as $key => $value) {
if (is_string($value) && $this->isRelativeMediaPathForBioLink($value)) {
$data[$key] = $this->convertRelativePathToUrlForBioLink($value);
} elseif (is_array($value)) {
$data[$key] = $this->recursivelyConvertPathsToUrlsForBioLink($value);
}
}
}
return $data;
}
/**
* Check if a string is a relative media path for bio links
*/
private function isRelativeMediaPathForBioLink($path)
{
return is_string($path) && str_starts_with($path, 'media/') && !str_starts_with($path, 'http');
}
/**
* Convert relative path to full URL for bio links
*/
private function convertRelativePathToUrlForBioLink(string $path): string
{
return url('/storage/' . $path);
}
}