classify admin

This commit is contained in:
Husanjonazamov
2026-02-24 12:52:01 +05:00
commit e0f1989655
769 changed files with 1263008 additions and 0 deletions

View File

@@ -0,0 +1,624 @@
<?php
namespace App\Services;
use App\Models\Area;
use App\Models\Category;
use App\Models\Setting;
use App\Services\CachingService;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use JsonException;
class HelperService
{
public static function changeEnv($updateData = []): bool
{
if (count($updateData) > 0) {
// Read .env-file
$env = file_get_contents(base_path() . '/.env');
// Split string on every " " and write into array
// $env = explode(PHP_EOL, $env);
$env = preg_split('/\r\n|\r|\n/', $env);
$env_array = [];
foreach ($env as $env_value) {
if (empty($env_value)) {
// Add and Empty Line
$env_array[] = '';
continue;
}
$entry = explode('=', $env_value, 2);
$env_array[$entry[0]] = $entry[0] . '="' . str_replace('"', '', $entry[1]) . '"';
}
foreach ($updateData as $key => $value) {
$env_array[$key] = $key . '="' . str_replace('"', '', $value) . '"';
}
// Turn the array back to a String
$env = implode("\n", $env_array);
// And overwrite the .env with the new data
file_put_contents(base_path() . '/.env', $env);
return true;
}
return false;
}
/**
* @description - This function will return the nested category Option tags using in memory optimization
*/
public static function childCategoryRendering(&$categories, int $level = 0, ?string $parentCategoryID = ''): bool
{
// Foreach loop only on the parent category objects
foreach (collect($categories)->where('parent_category_id', $parentCategoryID) as $key => $category) {
echo "<option value='$category->id'>" . str_repeat('&nbsp;', $level * 4) . '|-- ' . $category->name . '</option>';
// Once the parent category object is rendered we can remove the category from the main object so that redundant data can be removed
$categories->forget($key);
// Now fetch the subcategories of the main category
$subcategories = $categories->where('parent_category_id', $category->id);
if (! empty($subcategories)) {
// Finally if subcategories are available then call the recursive function & see the magic
self::childCategoryRendering($categories, $level + 1, $category->id);
}
}
return false;
}
public static function buildNestedChildSubcategoryObject($categories)
{
// Used json_decode & encode simultaneously because i wanted to convert whole nested array into object
try {
return json_decode(json_encode(self::buildNestedChildSubcategoryArray($categories), JSON_THROW_ON_ERROR), false, 512, JSON_THROW_ON_ERROR);
} catch (JsonException) {
return (object) [];
}
}
private static function buildNestedChildSubcategoryArray($categories)
{
$children = [];
// First Add Parent Categories to root level in an array
foreach ($categories->toArray() as $value) {
if ($value['parent_category_id'] == '') {
$children[] = $value;
}
}
// Then loop on the Parent Category to find the children categories
foreach ($children as $key => $value) {
$children[$key]['subcategories'] = self::findChildCategories($categories->toArray(), $value['id']);
}
return $children;
}
public static function findChildCategories($arr, $parent)
{
$children = [];
foreach ($arr as $key => $value) {
if ($value['parent_category_id'] == $parent) {
$children[] = $value;
}
}
foreach ($children as $key => $value) {
$children[$key]['subcategories'] = self::findChildCategories($arr, $value['id']);
}
return $children;
}
/*
* Sagar's Code :
* in this i have approached the reverse object moving & removing.
* which is not working as of now.
* but will continue working on this in future as it seems bit optimized approach from the current one
public static function buildNestedChildSubcategoryObject($categories, $finalCategories = []) {
echo "<pre>";
// Foreach loop only on the parent category objects
if (!empty($finalCategories)) {
$finalCategories = $categories->whereNull('parent_category_id');
}
foreach ($categories->whereNotNull('parent_category_id')->sortByDesc('parent_category_id') as $key => $category) {
echo "----------------------------------------------------------------------<br>";
$parentCategoryIndex = $categories->search(function ($data) use ($category) {
return $data['id'] == $category->parent_category_id;
});
if (!$parentCategoryIndex) {
continue;
}
// echo "*** This category will be moved to its parent category object ***<br>";
// print_r($category->toArray());
// Once the parent category object is rendered we can remove the category from the main object so that redundant data can be removed
$categories[$parentCategoryIndex]->subcategories[] = $category->toArray();
$categories->forget($key);
echo "<br>*** After all the operation main categories object will look like this ***<br>";
print_r($categories->toArray());
if (!empty($categories)) {
// Finally if subcategories are available then call the recursive function & see the magic
return self::buildNestedChildSubcategoryObject($categories, $finalCategories);
}
}
return $categories;
} */
public static function findParentCategory($category, $finalCategories = [])
{
$category = Category::find($category);
if (! empty($category)) {
$finalCategories[] = $category->id;
if (! empty($category->parent_category_id)) {
$finalCategories[] = self::findParentCategory($category->id, $finalCategories);
}
}
return $finalCategories;
}
/**
* Generate Slug for any model
*
* @param $model - Instance of Model
*/
public static function generateUniqueSlug($model, string $slug, ?int $excludeID = null, int $count = 0): string
{
/* NOTE : This can be improved by directly calling in the UI on type of title via AJAX */
$slug = Str::slug($slug);
$newSlug = $count ? $slug . '-' . $count : $slug;
$data = $model::where('slug', $newSlug);
if ($excludeID !== null) {
$data->where('id', '!=', $excludeID);
}
if (in_array(SoftDeletes::class, class_uses_recursive($model), true)) {
$data->withTrashed();
}
while ($data->exists()) {
return self::generateUniqueSlug($model, $slug, $excludeID, $count + 1);
}
return $newSlug;
}
public static function findAllCategoryIds($model): array
{
$ids = [];
foreach ($model as $item) {
$ids[] = $item['id'];
if (! empty($item['children'])) {
$ids = array_merge($ids, self::findAllCategoryIds($item['children']));
}
}
return $ids;
}
public static function generateRandomSlug($length = 10)
{
// Generate a random string of lowercase letters and numbers
$characters = 'abcdefghijklmnopqrstuvwxyz-';
$slug = '';
for ($i = 0; $i < $length; $i++) {
$index = rand(0, strlen($characters) - 1);
$slug .= $characters[$index];
}
return $slug;
}
/**
* Apply location filters to Item query with fallback logic
* Priority: area_id > city > state > country > latitude/longitude
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Http\Request $request
* @param callable $applyAuthFilters Function to apply auth-specific filters
* @return array ['query' => $query, 'message' => $locationMessage]
*/
public static function applyLocationFilters($query, $request, $applyAuthFilters)
{
$isHomePage = $request->current_page === 'home';
$locationMessage = null;
$hasLocationFilter = $request->latitude !== null && $request->longitude !== null;
$hasCityFilter = !empty($request->city);
$hasStateFilter = !empty($request->state);
$hasCountryFilter = !empty($request->country);
$hasAreaFilter = !empty($request->area_id);
$hasAreaLocationFilter = !empty($request->area_latitude) && !empty($request->area_longitude);
$cityName = $request->city ?? null;
$stateName = $request->state ?? null;
$countryName = $request->country ?? null;
$areaId = $request->area_id ?? null;
$cityItemCount = 0;
$stateItemCount = 0;
$countryItemCount = 0;
$areaItemCount = 0;
$areaName = null;
// Handle area location filter (find closest area by lat/long)
if ($hasAreaLocationFilter && !$hasAreaFilter) {
$areaLat = $request->area_latitude;
$areaLng = $request->area_longitude;
$haversine = "(6371 * acos(cos(radians($areaLat))
* cos(radians(latitude))
* cos(radians(longitude) - radians($areaLng))
+ sin(radians($areaLat)) * sin(radians(latitude))))";
$closestArea = Area::whereNotNull('latitude')
->whereNotNull('longitude')
->selectRaw("areas.*, {$haversine} AS distance")
// ->orderBy('distance', 'asc')
->orderByRaw(
'(6371 * acos(
cos(radians(?)) *
cos(radians(latitude)) *
cos(radians(longitude) - radians(?)) +
sin(radians(?)) * sin(radians(latitude))
)) ASC',
[$areaLat, $areaLng, $areaLat]
)
->first();
if ($closestArea) {
$hasAreaFilter = true;
$areaId = $closestArea->id;
}
}
// Get area name if area filter is set
if ($hasAreaFilter) {
$area = Area::find($areaId);
$areaName = $area ? $area->name : __('the selected area');
}
// Save base query before location filters for fallback
$baseQueryBeforeLocation = clone $query;
// First, check for area filter (highest priority)
if ($hasAreaFilter) {
$areaQuery = clone $query;
$areaQuery->where('area_id', $areaId);
$areaQuery = $applyAuthFilters($areaQuery);
$areaItemExists = $areaQuery->exists();
if ($areaItemExists) {
$query = $areaQuery;
$areaItemCount = 1;
} else {
if ($isHomePage) {
$locationMessage = __('No Ads found in :area. Showing all available Ads.', ['area' => $areaName]);
} else {
$query = $areaQuery;
}
$areaItemCount = 0;
}
}
// Second, check for city filter
if ($hasCityFilter && (!$hasAreaFilter || $areaItemCount == 0)) {
$cityQuery = clone $query;
$cityQuery->where('city', $cityName);
$cityQuery = $applyAuthFilters($cityQuery);
$cityItemExists = $cityQuery->exists();
if ($cityItemExists) {
$query = $cityQuery;
$cityItemCount = 1;
if ($hasAreaFilter && $areaItemCount == 0 && $isHomePage) {
$locationMessage = __('No Ads found in :city. Showing all available Ads.', ['city' => $cityName]);
}
} else {
$cityItemCount = 0;
if ($isHomePage) {
if (!$locationMessage) {
$locationMessage = __('No Ads found in :city. Showing all available Ads.', ['city' => $cityName]);
} else {
$locationMessage = __('No Ads found in :area or :city. Showing all available Ads.', ['area' => $areaName, 'city' => $cityName]);
}
} else {
$query = $cityQuery;
}
}
}
// Third, check for state filter
if ($hasStateFilter && (!$hasAreaFilter || $areaItemCount == 0) && (!$hasCityFilter || $cityItemCount == 0)) {
$stateQuery = clone $query;
$stateQuery->where('state', $stateName);
$stateQuery = $applyAuthFilters($stateQuery);
$stateItemExists = $stateQuery->exists();
if ($stateItemExists) {
$query = $stateQuery;
$stateItemCount = 1;
if (($hasAreaFilter && $areaItemCount == 0) || ($hasCityFilter && $cityItemCount == 0)) {
if ($isHomePage) {
$locationMessage = __('No Ads found in :state. Showing all available Ads.', ['state' => $stateName]);
}
}
} else {
$stateItemCount = 0;
if ($isHomePage) {
if (!$locationMessage) {
$locationMessage = __('No Ads found in :state. Showing all available Ads.', ['state' => $stateName]);
} else {
$parts = [];
if ($hasAreaFilter && $areaItemCount == 0) {
$parts[] = $areaName;
}
if ($hasCityFilter && $cityItemCount == 0) {
$parts[] = $cityName;
}
$parts[] = $stateName;
$locationMessage = __('No Ads found in :locations. Showing all available Ads.', ['locations' => implode(', ', $parts)]);
}
} else {
$query = $stateQuery;
}
}
}
// Fourth, check for country filter
if ($hasCountryFilter && (!$hasAreaFilter || $areaItemCount == 0) && (!$hasCityFilter || $cityItemCount == 0) && (!$hasStateFilter || $stateItemCount == 0)) {
$countryQuery = clone $query;
$countryQuery->where('country', $countryName);
$countryQuery = $applyAuthFilters($countryQuery);
$countryItemExists = $countryQuery->exists();
if ($countryItemExists) {
$query = $countryQuery;
$countryItemCount = 1;
if (($hasAreaFilter && $areaItemCount == 0) || ($hasCityFilter && $cityItemCount == 0) || ($hasStateFilter && $stateItemCount == 0)) {
if ($isHomePage) {
$locationMessage = __('No Ads found in :country. Showing all available Ads.', ['country' => $countryName]);
}
}
} else {
$countryItemCount = 0;
if ($isHomePage) {
if (!$locationMessage) {
$locationMessage = __('No Ads found in :country. Showing all available Ads.', ['country' => $countryName]);
} else {
$parts = [];
if ($hasAreaFilter && $areaItemCount == 0) {
$parts[] = $areaName;
}
if ($hasCityFilter && $cityItemCount == 0) {
$parts[] = $cityName;
}
if ($hasStateFilter && $stateItemCount == 0) {
$parts[] = $stateName;
}
$parts[] = $countryName;
$locationMessage = __('No Ads found in :locations. Showing all available Ads.', ['locations' => implode(', ', $parts)]);
}
} else {
$query = $countryQuery;
}
}
}
// Fifth, handle latitude/longitude location-based search
$hasHigherPriorityFilter = ($hasAreaFilter && $areaItemCount > 0) || ($hasCityFilter && $cityItemCount > 0) || ($hasStateFilter && $stateItemCount > 0) || ($hasCountryFilter && $countryItemCount > 0);
if ($hasLocationFilter && ((!$hasAreaFilter && !$hasCityFilter && !$hasStateFilter && !$hasCountryFilter) || $hasHigherPriorityFilter)) {
$latitude = $request->latitude;
$longitude = $request->longitude;
$requestedRadius = (float)($request->radius ?? null);
$exactLocationRadius = $request->radius;
$haversine = '(6371 * acos(cos(radians(?))
* cos(radians(latitude))
* cos(radians(longitude) - radians(?))
+ sin(radians(?)) * sin(radians(latitude))))';
$exactLocationQuery = clone $query;
$exactLocationQuery
->select('items.*')
->selectRaw("$haversine AS distance", [$latitude, $longitude, $latitude])
->where('latitude', '!=', 0)
->where('longitude', '!=', 0)
// CHANGE THIS: Use whereRaw instead of having to support pagination count
// Use <= so radius=0.0 still returns items at the exact same coordinates (distance 0)
->whereRaw("$haversine <= ?", [$latitude, $longitude, $latitude, $exactLocationRadius])
->orderBy('distance', 'asc');
if (Auth::check()) {
$exactLocationQuery->with(['item_offers' => function ($q) {
$q->where('buyer_id', Auth::user()->id);
}, 'user_reports' => function ($q) {
$q->where('user_id', Auth::user()->id);
}]);
$currentURI = explode('?', $request->getRequestUri(), 2);
if ($currentURI[0] == '/api/my-items') {
$exactLocationQuery->where(['user_id' => Auth::user()->id])->withTrashed();
} else {
$exactLocationQuery->where('status', 'approved')->has('user')->onlyNonBlockedUsers()->getNonExpiredItems();
}
} else {
$exactLocationQuery->where('status', 'approved')->getNonExpiredItems();
}
$exactLocationExists = $exactLocationQuery->exists();
if ($exactLocationExists) {
$query = $exactLocationQuery;
} else {
$searchRadius = $requestedRadius !== null && $requestedRadius > 0 ? $requestedRadius : 50;
$nearbyQuery = clone $query;
$nearbyQuery
->select('items.*')
->selectRaw("$haversine AS distance", [$latitude, $longitude, $latitude])
->where('latitude', '!=', 0)
->where('longitude', '!=', 0)
// CHANGE THIS: Use whereRaw instead of having
->whereRaw("$haversine < ?", [$latitude, $longitude, $latitude, $searchRadius])
->orderBy('distance', 'asc');
$nearbyQuery = $applyAuthFilters($nearbyQuery);
$nearbyItemExists = $nearbyQuery->exists();
if ($nearbyItemExists) {
$query = $nearbyQuery;
if (!$locationMessage) {
$locationMessage = __('No Ads found at your location. Showing nearby Ads.');
}
} else {
if ($isHomePage) {
$query = clone $baseQueryBeforeLocation;
if (!$locationMessage) {
$locationMessage = __('No Ads found at your location. Showing all available Ads.');
}
} else {
$query = $nearbyQuery;
}
}
}
}
return ['query' => $query, 'message' => $locationMessage];
}
/**
* Get watermark configuration status (enabled/disabled)
*
* @return bool
*/
public static function getWatermarkConfigStatus(): bool
{
$enabled = CachingService::getSystemSettings('watermark_enabled');
return !empty($enabled) && (int)$enabled === 1;
}
/**
* Get watermark configuration as decoded array
*
* @return array
*/
public static function getWatermarkConfigDecoded(): array
{
$settings = CachingService::getSystemSettings([
'watermark_enabled',
'watermark_image',
'watermark_opacity',
'watermark_size',
'watermark_style',
'watermark_position',
'watermark_rotation',
]);
return [
'enabled' => (int)($settings['watermark_enabled'] ?? 0),
'watermark_image' => $settings['watermark_image'] ?? null,
'opacity' => (int)($settings['watermark_opacity'] ?? 25),
'size' => (int)($settings['watermark_size'] ?? 10),
'style' => $settings['watermark_style'] ?? 'tile',
'position' => $settings['watermark_position'] ?? 'center',
'rotation' => (int)($settings['watermark_rotation'] ?? -30),
];
}
/**
* Get a specific setting value
*
* @param string $key
* @return string|null
*/
public static function getSettingData(string $key): ?string
{
return CachingService::getSystemSettings($key);
}
/**
* Calculate item expiry date based on package listing duration
* Priority: listing_duration > package expiry > default
*
* @param \App\Models\Package|null $package
* @param \App\Models\UserPurchasedPackage|null $userPackage
* @return \Carbon\Carbon|null
*/
public static function calculateItemExpiryDate($package = null, $userPackage = null)
{
if (!$package) {
$freeAdUnlimited = Setting::where('name', 'free_ad_unlimited')->value('value') ?? 0;
$freeAdDays = Setting::where('name', 'free_ad_duration_days')->value('value') ?? 0;
// Unlimited free ads
if ((int) $freeAdUnlimited === 1) {
return null;
}
// Limited free ads
if (!empty($freeAdDays) && (int) $freeAdDays > 0) {
return Carbon::now()->addDays((int) $freeAdDays);
}
// Safety fallback (no expiry)
return null;
}
// ---------------------------------------
// PACKAGE LOGIC (existing, unchanged)
// ---------------------------------------
$listingDurationType = $package->listing_duration_type ?? null;
$listingDurationDays = $package->listing_duration_days ?? null;
// If listing_duration_type is null → use package expiry
if ($listingDurationType === null) {
if ($userPackage && $userPackage->end_date) {
return Carbon::parse($userPackage->end_date);
}
if ($package->duration === 'unlimited') {
return null;
}
return Carbon::now()->addDays((int) $package->duration);
}
if ($listingDurationType === 'package') {
if ($package->duration === 'unlimited') {
return null;
}
return Carbon::now()->addDays((int) $package->duration);
}
if ($listingDurationType === 'custom') {
if (!empty($listingDurationDays) && (int) $listingDurationDays > 0) {
return Carbon::now()->addDays((int) $listingDurationDays);
}
return Carbon::now()->addDays(30);
}
// Standard fallback
if (!empty($listingDurationDays) && (int) $listingDurationDays > 0) {
return Carbon::now()->addDays((int) $listingDurationDays);
}
return Carbon::now()->addDays(30);
}
}