Merge pull request #1389 from openclassify/beta

Beta
This commit is contained in:
Fatih Alp 2026-03-03 10:33:07 +03:00 committed by GitHub
commit f77c1885bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
342 changed files with 19040 additions and 7466 deletions

View File

@ -1,6 +0,0 @@
{
"name": "Openclassify",
// Script to run to bootstrap the app when the space is created
"postCreateCommand": "bash install.sh",
"outputCapture": "std"
}

18
.editorconfig Normal file
View File

@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[compose.yaml]
indent_size = 4

View File

@ -1,23 +0,0 @@
APP_NAME=oc
APP_ENV=production
INSTALLED="false"
APP_URL_TYPE="HTTP_X_FORWARDED_HOST"
APP_KEY=DYKEBxfEHK1PP4mUbP3gWPtsPZgXh0qX
APP_DEBUG=true
DEBUG_BAR=true
DB_CONNECTION=mysql
#DB_HOST=mysql
DB_HOST=host.docker.internal
DB_DATABASE=oc
DB_USERNAME=oc
DB_PASSWORD=oc
APPLICATION_NAME=Default
APPLICATION_REFERENCE=default
ADMIN_USERNAME=admin
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=admin
APP_LOCALE=en
APP_TIMEZONE=UTC
REDIS_CLIENT=phpredis
REDIS_HOST=redis
REDIS_PORT=6379

View File

@ -1,16 +1,65 @@
APP_NAME=Laravel
APP_ENV=local APP_ENV=local
INSTALLED=false APP_KEY=
APP_KEY=spNWIbUUSkRICcUwBGOaDzgwWsLjqUVq APP_DEBUG=true
DB_CONNECTION=mysql APP_URL=http://localhost
DB_HOST=localhost
DB_DATABASE=forge APP_LOCALE=en
DB_USERNAME=forge APP_FALLBACK_LOCALE=en
DB_PASSWORD=forge APP_FAKER_LOCALE=en_US
APPLICATION_NAME=Default
APPLICATION_REFERENCE=default APP_MAINTENANCE_DRIVER=file
ADMIN_USERNAME=$adminUserName # APP_MAINTENANCE_STORE=database
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=admin123 # PHP_CLI_SERVER_WORKERS=4
LOCALE=en
AUTO_TOKEN=spNWITUUSkRICcUwBGOaDzGwWsLqUVqX BCRYPT_ROUNDS=12
APP_TIMEZONE=UTC
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"

View File

@ -1,70 +0,0 @@
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3307
DB_DATABASE=oc
DB_USERNAME=root
DB_PASSWORD=""
APP_NAME="OpenClassify"
FORCE_SSL=0
APP_TIMEZONE="UTC"
DATE_FORMAT="j F, Y"
TIME_FORMAT="H:i"
UNIT_SYSTEM="imperial"
STANDARD_THEME="visiosoft.theme.default"
ADMIN_THEME="visiosoft.theme.defaultadmin"
RESULTS_PER_PAGE=15
DEFAULT_LOCALE="en"
ENABLED_LOCALES='a:1:{i:0;s:2:"en";}'
MAINTENANCE_MODE=0
MAINTENANCE_AUTH=0
FROM_ADDRESS="info@openclassify.com"
FROM_NAME="OpenClassify"
MAIL_DRIVER="smtp"
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME="029f2d0c9099d9"
MAIL_PASSWORD="8252f6d1c2ca42"
HTTP_CACHE=0
HTTP_CACHE_TTL=3600
HTTP_CACHE_ALLOW_BOTS=0
HTTP_CACHE_EXCLUDED="NULL"
HTTP_CACHE_RULES="NULL"
DB_CACHE=0
DB_CACHE_TTL=3600
ADV_COUNTRY=212
ADV_LOGO="NULL"
ADV_SITE_ADDRESS="visiosoft.com.tr"
ADV_AUTO_APPROVE=1
ADV_PUBLISH_TIME=10
ADV_GET=0
ADV_LIMIT=15
ADV_PHONE="212 555 55 55"
ADV_ADDRESS="Basaksehir Istanbul"
ADV_CITY=34
ADV_DISTRICT=1091
ADV_MAIL="support"
ADV_MAP_KEY="AIzaSyCAGc0z8kg9rKGVy2FizFKoz0FoWWWzoGQ"
ADV_MAP_LONG="28.74558607285155"
ADV_MAP_LAT="40.97817786299617"
ADV_CURRENCIES='a:1:{i:0;s:1:"0";}'
ADV_CURRENCY_CONVERT_API_KEY="1eea72940f3868c77420"
ADV_CURRENCY="USD"
ADV_TWITTER="/twitter.com/visiosoft"
ADV_FACEBOOK="/facebook.com/visiosoft"
ADV_YOUTUBE="/youtube.com/visiosoft"
ADV_GOOGLE="/plus.google.com/visiosoft"
ADV_WATERMARK_TYPE="text"
ADV_WATERMARK_TEXT="openclassify.com"
ADV_WATERMARK_POSITION="top-right"
ADV_WATERMARK_OPACITY=80
ADV_ENABLED_CURRENCIES='a:1:{i:0;s:3:"USD";}'
ENABLE_SENTRY_LARAVEL=true

11
.gitattributes vendored Normal file
View File

@ -0,0 +1,11 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

3
.github/FUNDING.yml vendored
View File

@ -1,3 +0,0 @@
# These are supported funding model platforms
patreon: openclassify

View File

@ -1,38 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

12
.github/workflows/issues.yml vendored Normal file
View File

@ -0,0 +1,12 @@
name: Issues
on:
issues:
types: [labeled]
permissions:
issues: write
jobs:
help-wanted:
uses: laravel/.github/.github/workflows/issues.yml@main

12
.github/workflows/pull-requests.yml vendored Normal file
View File

@ -0,0 +1,12 @@
name: Pull Requests
on:
pull_request_target:
types: [opened]
permissions:
pull-requests: write
jobs:
uneditable:
uses: laravel/.github/.github/workflows/pull-requests.yml@main

47
.github/workflows/tests.yml vendored Normal file
View File

@ -0,0 +1,47 @@
name: Tests
on:
push:
branches:
- master
- '*.x'
pull_request:
schedule:
- cron: '0 0 * * *'
permissions:
contents: read
jobs:
tests:
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
php: [8.2, 8.3, 8.4]
name: PHP ${{ matrix.php }}
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite
coverage: none
- name: Install Composer dependencies
run: composer install --prefer-dist --no-interaction --no-progress
- name: Copy environment file
run: cp .env.example .env
- name: Generate app key
run: php artisan key:generate
- name: Execute tests
run: php artisan test

13
.github/workflows/update-changelog.yml vendored Normal file
View File

@ -0,0 +1,13 @@
name: Update Changelog
on:
release:
types: [released]
permissions: {}
jobs:
update:
permissions:
contents: write
uses: laravel/.github/.github/workflows/update-changelog.yml@main

52
.gitignore vendored
View File

@ -1,32 +1,24 @@
.idea *.log
.DS_Store
.env .env
.coverage .env.backup
/bin .env.production
/core .phpactor.json
/build .phpunit.result.cache
/vendor /.fleet
/coverage /.idea
/.nova
/.phpunit.cache
/.vscode
/.zed
/auth.json
/node_modules /node_modules
/bower_components /public/build
composer.lock /public/hot
package-lock.json /public/storage
/storage/*.key
/storage/pail
* text=auto /vendor
Homestead.json
*.txt text eol=lf Homestead.yaml
*.xml text eol=lf Thumbs.db
*.json text eol=lf
*.properties text eol=lf
*.conf text eol=lf
*.sh text eol=lf
Dockerfile text eol=lf
*.awk text eol=lf
*.sed text eol=lf
*.sh text eol=lf
*.png binary
*.jpg binary
*.p12 binary

9
.styleci.yml Normal file
View File

@ -0,0 +1,9 @@
php:
preset: laravel
disabled:
- no_unused_imports
finder:
not-name:
- index.php
js: true
css: true

19
CHANGELOG.md Normal file
View File

@ -0,0 +1,19 @@
# OpenClassify Changelog
All notable changes to OpenClassify will be documented in this file.
## [1.0.0] - 2025-01-01
### Added
- Initial release of OpenClassify — a Laravel 12 classified ads platform (inspired by Letgo/OLX)
- **Category Module**: Hierarchical categories with icons and up to 10 levels of nesting; seeded with 8 top-level categories and 33 subcategories
- **Listing Module**: Classified ads with title, description, price, currency, location, featured flag, and contact info
- **Location Module**: Country/City/District/Neighborhood hierarchy with seed data for 5 countries
- **Profile Module**: User profile management with bio, phone, location, and website
- Home page with hero search bar, stats bar, category grid, featured listings, and recent listings
- Partner dashboard showing user's own listings with activity stats
- Language switcher with support for 10 locales: English, Turkish, Arabic, Chinese, Spanish, French, German, Portuguese, Russian, Japanese
- RTL layout support for Arabic
- SQLite database with full migration support
- Authentication via Laravel Breeze (login, register, password reset, email verification)
- Responsive UI using Tailwind CSS

View File

@ -1,20 +0,0 @@
# The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,36 @@
<?php
namespace Modules\Category\Database\Seeders;
use Illuminate\Database\Seeder;
use Modules\Category\Models\Category;
use Illuminate\Support\Str;
class CategorySeeder extends Seeder
{
public function run(): void
{
$categories = [
['name' => 'Electronics', 'icon' => '📱', 'children' => ['Mobile Phones', 'Laptops & Computers', 'Tablets', 'Cameras', 'Audio']],
['name' => 'Vehicles', 'icon' => '🚗', 'children' => ['Cars', 'Motorcycles', 'Trucks', 'Boats']],
['name' => 'Real Estate', 'icon' => '🏠', 'children' => ['Apartments for Rent', 'Houses for Sale', 'Commercial', 'Land']],
['name' => 'Furniture', 'icon' => '🛋️', 'children' => ['Sofas', 'Beds', 'Tables', 'Wardrobes']],
['name' => 'Fashion', 'icon' => '👗', 'children' => ['Women', 'Men', 'Kids', 'Accessories']],
['name' => 'Jobs', 'icon' => '💼', 'children' => ['IT & Technology', 'Marketing', 'Sales', 'Education']],
['name' => 'Services', 'icon' => '🔧', 'children' => ['Home Repair', 'Tutoring', 'Design', 'Cleaning']],
['name' => 'Sports & Hobbies', 'icon' => '⚽', 'children' => ['Sports Equipment', 'Musical Instruments', 'Books', 'Games']],
];
foreach ($categories as $catData) {
$parent = Category::firstOrCreate(
['slug' => Str::slug($catData['name'])],
['name' => $catData['name'], 'icon' => $catData['icon'] ?? null, 'level' => 0, 'is_active' => true]
);
foreach ($catData['children'] as $childName) {
Category::firstOrCreate(
['slug' => Str::slug($childName)],
['name' => $childName, 'parent_id' => $parent->id, 'level' => 1, 'is_active' => true]
);
}
}
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Modules\Category\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Modules\Category\Models\Category;
class CategoryController extends Controller
{
public function index()
{
$categories = Category::whereNull('parent_id')->with('children')->where('is_active', true)->get();
return view('category::index', compact('categories'));
}
public function show(Category $category)
{
$listings = $category->listings()->where('status', 'active')->paginate(12);
return view('category::show', compact('category', 'listings'));
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Modules\Category\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Category extends Model
{
protected $fillable = ['name', 'slug', 'description', 'icon', 'parent_id', 'level', 'sort_order', 'is_active'];
protected $casts = ['is_active' => 'boolean'];
public function parent(): BelongsTo
{
return $this->belongsTo(Category::class, 'parent_id');
}
public function children(): HasMany
{
return $this->hasMany(Category::class, 'parent_id');
}
public function listings(): HasMany
{
return $this->hasMany(\Modules\Listing\Models\Listing::class);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Modules\Category\Providers;
use Illuminate\Support\ServiceProvider;
class CategoryServiceProvider extends ServiceProvider
{
protected string $moduleName = 'Category';
public function boot(): void
{
$this->loadMigrationsFrom(module_path($this->moduleName, 'database/migrations'));
$this->loadRoutesFrom(module_path($this->moduleName, 'routes/web.php'));
$this->loadViewsFrom(module_path($this->moduleName, 'resources/views'), 'category');
}
public function register(): void {}
}

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->text('description')->nullable();
$table->string('icon')->nullable();
$table->foreignId('parent_id')->nullable()->constrained('categories')->nullOnDelete();
$table->integer('level')->default(0);
$table->integer('sort_order')->default(0);
$table->boolean('is_active')->default(true);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('categories');
}
};

View File

@ -0,0 +1,36 @@
<?php
namespace Modules\Category\Database\Seeders;
use Illuminate\Database\Seeder;
use Modules\Category\Models\Category;
use Illuminate\Support\Str;
class CategorySeeder extends Seeder
{
public function run(): void
{
$categories = [
['name' => 'Electronics', 'icon' => '📱', 'children' => ['Mobile Phones', 'Laptops & Computers', 'Tablets', 'Cameras', 'Audio']],
['name' => 'Vehicles', 'icon' => '🚗', 'children' => ['Cars', 'Motorcycles', 'Trucks', 'Boats']],
['name' => 'Real Estate', 'icon' => '🏠', 'children' => ['Apartments for Rent', 'Houses for Sale', 'Commercial', 'Land']],
['name' => 'Furniture', 'icon' => '🛋️', 'children' => ['Sofas', 'Beds', 'Tables', 'Wardrobes']],
['name' => 'Fashion', 'icon' => '👗', 'children' => ['Women', 'Men', 'Kids', 'Accessories']],
['name' => 'Jobs', 'icon' => '💼', 'children' => ['IT & Technology', 'Marketing', 'Sales', 'Education']],
['name' => 'Services', 'icon' => '🔧', 'children' => ['Home Repair', 'Tutoring', 'Design', 'Cleaning']],
['name' => 'Sports & Hobbies', 'icon' => '⚽', 'children' => ['Sports Equipment', 'Musical Instruments', 'Books', 'Games']],
];
foreach ($categories as $catData) {
$parent = Category::firstOrCreate(
['slug' => Str::slug($catData['name'])],
['name' => $catData['name'], 'icon' => $catData['icon'] ?? null, 'level' => 0, 'is_active' => true]
);
foreach ($catData['children'] as $childName) {
Category::firstOrCreate(
['slug' => Str::slug($childName)],
['name' => $childName, 'parent_id' => $parent->id, 'level' => 1, 'is_active' => true]
);
}
}
}
}

View File

@ -0,0 +1,12 @@
{
"name": "Category",
"alias": "category",
"description": "Categories with hierarchy support",
"keywords": [],
"priority": 0,
"providers": [
"Modules\\Category\\Providers\\CategoryServiceProvider"
],
"aliases": {},
"files": []
}

View File

@ -0,0 +1,15 @@
@extends('layouts.app')
@section('content')
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-6">{{ __('messages.categories') }}</h1>
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
@foreach($categories as $category)
<a href="{{ route('categories.show', $category) }}" class="bg-white rounded-lg shadow-md p-6 text-center hover:shadow-lg transition hover:bg-blue-50">
<div class="text-4xl mb-3">{{ $category->icon ?? '📦' }}</div>
<h3 class="font-semibold text-gray-900">{{ $category->name }}</h3>
<p class="text-gray-500 text-sm mt-1">{{ $category->children->count() }} subcategories</p>
</a>
@endforeach
</div>
</div>
@endsection

View File

@ -0,0 +1,33 @@
@extends('layouts.app')
@section('content')
<div class="container mx-auto px-4 py-8">
<div class="mb-6">
<h1 class="text-3xl font-bold">{{ $category->icon ?? '' }} {{ $category->name }}</h1>
@if($category->description)<p class="text-gray-600 mt-2">{{ $category->description }}</p>@endif
</div>
@if($category->children->count())
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
@foreach($category->children as $child)
<a href="{{ route('categories.show', $child) }}" class="bg-blue-50 rounded-lg p-4 text-center hover:bg-blue-100 transition">
<h3 class="font-medium text-blue-800">{{ $child->name }}</h3>
</a>
@endforeach
</div>
@endif
<h2 class="text-xl font-bold mb-4">Listings in {{ $category->name }}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@forelse($listings as $listing)
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<div class="p-4">
<h3 class="font-semibold">{{ $listing->title }}</h3>
<p class="text-green-600 font-bold">{{ $listing->price ? number_format($listing->price, 0).' '.$listing->currency : 'Free' }}</p>
<a href="{{ route('listings.show', $listing) }}" class="mt-2 block text-blue-600 hover:underline">View </a>
</div>
</div>
@empty
<p class="text-gray-500 col-span-3">No listings in this category yet.</p>
@endforelse
</div>
<div class="mt-6">{{ $listings->links() }}</div>
</div>
@endsection

View File

@ -0,0 +1,8 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\Category\Http\Controllers\CategoryController;
Route::prefix('categories')->name('categories.')->group(function () {
Route::get('/', [CategoryController::class, 'index'])->name('index');
Route::get('/{category}', [CategoryController::class, 'show'])->name('show');
});

View File

@ -0,0 +1,30 @@
<?php
namespace Modules\Listing\Database\Seeders;
use Illuminate\Database\Seeder;
use Modules\Listing\Models\Listing;
use Illuminate\Support\Str;
class ListingSeeder extends Seeder
{
public function run(): void
{
$listings = [
['title' => 'iPhone 14 Pro - Like New', 'price' => 750, 'category_id' => 1, 'status' => 'active'],
['title' => 'Samsung Galaxy S23', 'price' => 550, 'category_id' => 1, 'status' => 'active'],
['title' => 'MacBook Pro 2023', 'price' => 1800, 'category_id' => 2, 'status' => 'active'],
['title' => '2019 Toyota Corolla', 'price' => 15000, 'category_id' => 3, 'status' => 'active'],
['title' => 'Apartment for Rent - 2BR', 'price' => 1200, 'category_id' => 4, 'status' => 'active'],
['title' => 'Sofa Set - Excellent Condition', 'price' => 350, 'category_id' => 5, 'status' => 'active'],
];
foreach ($listings as $data) {
$data['slug'] = Str::slug($data['title']) . '-' . Str::random(6);
$data['description'] = 'Great item in excellent condition. Contact for more details.';
$data['contact_email'] = 'seller@example.com';
$data['city'] = 'Istanbul';
$data['country'] = 'Turkey';
Listing::firstOrCreate(['title' => $data['title']], $data);
}
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Modules\Listing\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Modules\Listing\Models\Listing;
class ListingController extends Controller
{
public function index(Request $request)
{
$listings = Listing::where('status', 'active')
->orderByDesc('is_featured')
->orderByDesc('created_at')
->paginate(12);
return view('listing::index', compact('listings'));
}
public function show(Listing $listing)
{
return view('listing::show', compact('listing'));
}
public function create()
{
return view('listing::create');
}
public function store(Request $request)
{
$data = $request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'price' => 'nullable|numeric',
'category_id' => 'nullable|integer',
'contact_email' => 'nullable|email',
'contact_phone' => 'nullable|string',
]);
$data['user_id'] = auth()->id();
$data['slug'] = \Illuminate\Support\Str::slug($data['title']) . '-' . \Illuminate\Support\Str::random(6);
$listing = Listing::create($data);
return redirect()->route('listings.show', $listing)->with('success', 'Listing created!');
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace Modules\Listing\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Listing extends Model
{
use HasFactory;
protected $fillable = [
'title', 'description', 'price', 'currency', 'category_id',
'user_id', 'status', 'images', 'slug',
'contact_phone', 'contact_email', 'is_featured', 'expires_at',
'city', 'country',
];
protected $casts = [
'images' => 'array',
'is_featured' => 'boolean',
'expires_at' => 'datetime',
'price' => 'decimal:2',
];
public function category()
{
return $this->belongsTo(\Modules\Category\Models\Category::class);
}
public function user()
{
return $this->belongsTo(\App\Models\User::class);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Modules\Listing\Providers;
use Illuminate\Support\ServiceProvider;
class ListingServiceProvider extends ServiceProvider
{
protected string $moduleName = 'Listing';
protected string $moduleNameLower = 'listing';
public function boot(): void
{
$this->loadViewsFrom(module_path($this->moduleName, 'resources/views'), $this->moduleNameLower);
$this->loadMigrationsFrom(module_path($this->moduleName, 'database/migrations'));
$this->loadRoutesFrom(module_path($this->moduleName, 'routes/web.php'));
}
public function register(): void {}
}

View File

@ -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
{
public function up(): void
{
Schema::create('listings', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('slug')->unique();
$table->text('description')->nullable();
$table->decimal('price', 10, 2)->nullable();
$table->string('currency', 3)->default('USD');
$table->foreignId('category_id')->nullable()->constrained('categories')->nullOnDelete();
$table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete();
$table->string('status')->default('active');
$table->json('images')->nullable();
$table->string('contact_phone')->nullable();
$table->string('contact_email')->nullable();
$table->boolean('is_featured')->default(false);
$table->timestamp('expires_at')->nullable();
$table->string('city')->nullable();
$table->string('country')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('listings');
}
};

View File

@ -0,0 +1,30 @@
<?php
namespace Modules\Listing\Database\Seeders;
use Illuminate\Database\Seeder;
use Modules\Listing\Models\Listing;
use Illuminate\Support\Str;
class ListingSeeder extends Seeder
{
public function run(): void
{
$listings = [
['title' => 'iPhone 14 Pro - Like New', 'price' => 750, 'category_id' => 1, 'status' => 'active'],
['title' => 'Samsung Galaxy S23', 'price' => 550, 'category_id' => 1, 'status' => 'active'],
['title' => 'MacBook Pro 2023', 'price' => 1800, 'category_id' => 2, 'status' => 'active'],
['title' => '2019 Toyota Corolla', 'price' => 15000, 'category_id' => 3, 'status' => 'active'],
['title' => 'Apartment for Rent - 2BR', 'price' => 1200, 'category_id' => 4, 'status' => 'active'],
['title' => 'Sofa Set - Excellent Condition', 'price' => 350, 'category_id' => 5, 'status' => 'active'],
];
foreach ($listings as $data) {
$data['slug'] = Str::slug($data['title']) . '-' . Str::random(6);
$data['description'] = 'Great item in excellent condition. Contact for more details.';
$data['contact_email'] = 'seller@example.com';
$data['city'] = 'Istanbul';
$data['country'] = 'Turkey';
Listing::firstOrCreate(['title' => $data['title']], $data);
}
}
}

View File

@ -0,0 +1,12 @@
{
"name": "Listing",
"alias": "listing",
"description": "Classified ads listings module",
"keywords": [],
"priority": 0,
"providers": [
"Modules\\Listing\\Providers\\ListingServiceProvider"
],
"aliases": {},
"files": []
}

View File

@ -0,0 +1,33 @@
@extends('layouts.app')
@section('content')
<div class="container mx-auto px-4 py-8">
<div class="max-w-2xl mx-auto">
<h1 class="text-2xl font-bold mb-6">Post a New Listing</h1>
<form method="POST" action="{{ route('listings.store') }}" class="bg-white rounded-lg shadow-md p-6 space-y-4">
@csrf
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Title *</label>
<input type="text" name="title" value="{{ old('title') }}" required class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
@error('title')<p class="text-red-500 text-sm mt-1">{{ $message }}</p>@enderror
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Description</label>
<textarea name="description" rows="4" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">{{ old('description') }}</textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Price</label>
<input type="number" name="price" value="{{ old('price') }}" step="0.01" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Contact Email</label>
<input type="email" name="contact_email" value="{{ old('contact_email') }}" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Contact Phone</label>
<input type="text" name="contact_phone" value="{{ old('contact_phone') }}" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<button type="submit" class="w-full bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700 transition font-medium">Post Listing</button>
</form>
</div>
</div>
@endsection

View File

@ -0,0 +1,27 @@
@extends('layouts.app')
@section('content')
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-6">{{ __('messages.listings') }}</h1>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
@foreach($listings as $listing)
<div class="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition">
<div class="bg-gray-200 h-48 flex items-center justify-center">
<svg class="w-16 h-16 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
</div>
<div class="p-4">
@if($listing->is_featured)
<span class="bg-yellow-100 text-yellow-800 text-xs font-medium px-2 py-1 rounded">Featured</span>
@endif
<h3 class="font-semibold text-gray-900 mt-2 truncate">{{ $listing->title }}</h3>
<p class="text-green-600 font-bold text-lg mt-1">
@if($listing->price) {{ number_format($listing->price, 0) }} {{ $listing->currency }} @else Free @endif
</p>
<p class="text-gray-500 text-sm mt-1">{{ $listing->city }}, {{ $listing->country }}</p>
<a href="{{ route('listings.show', $listing) }}" class="mt-3 block text-center bg-blue-600 text-white py-2 rounded hover:bg-blue-700 transition">View</a>
</div>
</div>
@endforeach
</div>
<div class="mt-8">{{ $listings->links() }}</div>
</div>
@endsection

View File

@ -0,0 +1,38 @@
@extends('layouts.app')
@section('content')
<div class="container mx-auto px-4 py-8">
<div class="max-w-4xl mx-auto">
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<div class="bg-gray-200 h-96 flex items-center justify-center">
<svg class="w-24 h-24 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
</div>
<div class="p-6">
<div class="flex justify-between items-start">
<h1 class="text-2xl font-bold text-gray-900">{{ $listing->title }}</h1>
<span class="text-3xl font-bold text-green-600">
@if($listing->price) {{ number_format($listing->price, 0) }} {{ $listing->currency }} @else Free @endif
</span>
</div>
<p class="text-gray-500 mt-2">{{ $listing->city }}, {{ $listing->country }}</p>
<p class="text-gray-500 text-sm">Posted {{ $listing->created_at->diffForHumans() }}</p>
<div class="mt-4 border-t pt-4">
<h2 class="font-semibold text-lg mb-2">Description</h2>
<p class="text-gray-700">{{ $listing->description }}</p>
</div>
<div class="mt-6 bg-gray-50 rounded-lg p-4">
<h2 class="font-semibold text-lg mb-3">Contact Seller</h2>
@if($listing->contact_phone)
<p class="text-gray-700"><span class="font-medium">Phone:</span> {{ $listing->contact_phone }}</p>
@endif
@if($listing->contact_email)
<p class="text-gray-700"><span class="font-medium">Email:</span> {{ $listing->contact_email }}</p>
@endif
</div>
<div class="mt-6">
<a href="{{ route('listings.index') }}" class="text-blue-600 hover:underline"> Back to listings</a>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,10 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\Listing\Http\Controllers\ListingController;
Route::prefix('listings')->name('listings.')->group(function () {
Route::get('/', [ListingController::class, 'index'])->name('index');
Route::get('/create', [ListingController::class, 'create'])->name('create')->middleware('auth');
Route::post('/', [ListingController::class, 'store'])->name('store')->middleware('auth');
Route::get('/{listing}', [ListingController::class, 'show'])->name('show');
});

View File

@ -0,0 +1,38 @@
<?php
namespace Modules\Location\Database\Seeders;
use Illuminate\Database\Seeder;
use Modules\Location\Models\Country;
use Modules\Location\Models\City;
use Modules\Location\Models\District;
class LocationSeeder extends Seeder
{
public function run(): void
{
$locations = [
['name' => 'Turkey', 'code' => 'TR', 'phone_code' => '+90', 'flag' => '🇹🇷',
'cities' => ['Istanbul' => ['Beyoglu', 'Kadikoy', 'Besiktas'], 'Ankara' => ['Cankaya', 'Kecioren'], 'Izmir' => ['Konak', 'Karsiyaka']]],
['name' => 'United States', 'code' => 'US', 'phone_code' => '+1', 'flag' => '🇺🇸',
'cities' => ['New York' => ['Manhattan', 'Brooklyn'], 'Los Angeles' => ['Hollywood', 'Venice'], 'Chicago' => ['Downtown', 'Midtown']]],
['name' => 'United Kingdom', 'code' => 'GB', 'phone_code' => '+44', 'flag' => '🇬🇧',
'cities' => ['London' => ['Westminster', 'Shoreditch'], 'Manchester' => ['City Centre'], 'Birmingham' => ['Jewellery Quarter']]],
['name' => 'Germany', 'code' => 'DE', 'phone_code' => '+49', 'flag' => '🇩🇪',
'cities' => ['Berlin' => ['Mitte', 'Prenzlauer Berg'], 'Munich' => ['Schwabing', 'Maxvorstadt']]],
['name' => 'France', 'code' => 'FR', 'phone_code' => '+33', 'flag' => '🇫🇷',
'cities' => ['Paris' => ['Marais', 'Montmartre'], 'Lyon' => ['Presquile']]],
];
foreach ($locations as $countryData) {
$cities = $countryData['cities'];
unset($countryData['cities']);
$country = Country::firstOrCreate(['code' => $countryData['code']], $countryData);
foreach ($cities as $cityName => $districts) {
$city = City::firstOrCreate(['name' => $cityName, 'country_id' => $country->id]);
foreach ($districts as $districtName) {
District::firstOrCreate(['name' => $districtName, 'city_id' => $city->id]);
}
}
}
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Modules\Location\Models;
use Illuminate\Database\Eloquent\Model;
class City extends Model
{
protected $fillable = ['name', 'country_id', 'is_active'];
protected $casts = ['is_active' => 'boolean'];
public function country() { return $this->belongsTo(Country::class); }
public function districts() { return $this->hasMany(District::class); }
}

View File

@ -0,0 +1,15 @@
<?php
namespace Modules\Location\Models;
use Illuminate\Database\Eloquent\Model;
class Country extends Model
{
protected $fillable = ['name', 'code', 'phone_code', 'flag', 'is_active'];
protected $casts = ['is_active' => 'boolean'];
public function cities()
{
return $this->hasMany(City::class);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Modules\Location\Models;
use Illuminate\Database\Eloquent\Model;
class District extends Model
{
protected $fillable = ['name', 'city_id', 'is_active'];
protected $casts = ['is_active' => 'boolean'];
public function city() { return $this->belongsTo(City::class); }
}

View File

@ -0,0 +1,17 @@
<?php
namespace Modules\Location\Providers;
use Illuminate\Support\ServiceProvider;
class LocationServiceProvider extends ServiceProvider
{
protected string $moduleName = 'Location';
public function boot(): void
{
$this->loadMigrationsFrom(module_path($this->moduleName, 'database/migrations'));
$this->loadRoutesFrom(module_path($this->moduleName, 'routes/web.php'));
}
public function register(): void {}
}

View File

@ -0,0 +1,52 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('countries', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('code', 3)->unique();
$table->string('phone_code', 10)->nullable();
$table->string('flag', 10)->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
});
Schema::create('cities', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->foreignId('country_id')->constrained('countries')->cascadeOnDelete();
$table->boolean('is_active')->default(true);
$table->timestamps();
});
Schema::create('districts', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->foreignId('city_id')->constrained('cities')->cascadeOnDelete();
$table->boolean('is_active')->default(true);
$table->timestamps();
});
Schema::create('neighborhoods', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->foreignId('district_id')->constrained('districts')->cascadeOnDelete();
$table->boolean('is_active')->default(true);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('neighborhoods');
Schema::dropIfExists('districts');
Schema::dropIfExists('cities');
Schema::dropIfExists('countries');
}
};

View File

@ -0,0 +1,38 @@
<?php
namespace Modules\Location\Database\Seeders;
use Illuminate\Database\Seeder;
use Modules\Location\Models\Country;
use Modules\Location\Models\City;
use Modules\Location\Models\District;
class LocationSeeder extends Seeder
{
public function run(): void
{
$locations = [
['name' => 'Turkey', 'code' => 'TR', 'phone_code' => '+90', 'flag' => '🇹🇷',
'cities' => ['Istanbul' => ['Beyoglu', 'Kadikoy', 'Besiktas'], 'Ankara' => ['Cankaya', 'Kecioren'], 'Izmir' => ['Konak', 'Karsiyaka']]],
['name' => 'United States', 'code' => 'US', 'phone_code' => '+1', 'flag' => '🇺🇸',
'cities' => ['New York' => ['Manhattan', 'Brooklyn'], 'Los Angeles' => ['Hollywood', 'Venice'], 'Chicago' => ['Downtown', 'Midtown']]],
['name' => 'United Kingdom', 'code' => 'GB', 'phone_code' => '+44', 'flag' => '🇬🇧',
'cities' => ['London' => ['Westminster', 'Shoreditch'], 'Manchester' => ['City Centre'], 'Birmingham' => ['Jewellery Quarter']]],
['name' => 'Germany', 'code' => 'DE', 'phone_code' => '+49', 'flag' => '🇩🇪',
'cities' => ['Berlin' => ['Mitte', 'Prenzlauer Berg'], 'Munich' => ['Schwabing', 'Maxvorstadt']]],
['name' => 'France', 'code' => 'FR', 'phone_code' => '+33', 'flag' => '🇫🇷',
'cities' => ['Paris' => ['Marais', 'Montmartre'], 'Lyon' => ['Presquile']]],
];
foreach ($locations as $countryData) {
$cities = $countryData['cities'];
unset($countryData['cities']);
$country = Country::firstOrCreate(['code' => $countryData['code']], $countryData);
foreach ($cities as $cityName => $districts) {
$city = City::firstOrCreate(['name' => $cityName, 'country_id' => $country->id]);
foreach ($districts as $districtName) {
District::firstOrCreate(['name' => $districtName, 'city_id' => $city->id]);
}
}
}
}
}

View File

@ -0,0 +1,12 @@
{
"name": "Location",
"alias": "location",
"description": "Country, city, district and neighborhood management",
"keywords": [],
"priority": 0,
"providers": [
"Modules\\Location\\Providers\\LocationServiceProvider"
],
"aliases": {},
"files": []
}

View File

@ -0,0 +1,10 @@
<?php
use Illuminate\Support\Facades\Route;
Route::get('/locations/cities/{country}', function(\Modules\Location\Models\Country $country) {
return response()->json($country->cities);
})->name('locations.cities');
Route::get('/locations/districts/{city}', function(\Modules\Location\Models\City $city) {
return response()->json($city->districts);
})->name('locations.districts');

View File

@ -0,0 +1,34 @@
<?php
namespace Modules\Profile\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Modules\Profile\Models\Profile;
class ProfileController extends Controller
{
public function show()
{
$profile = Profile::firstOrCreate(['user_id' => auth()->id()]);
return view('profile::show', compact('profile'));
}
public function edit()
{
$profile = Profile::firstOrCreate(['user_id' => auth()->id()]);
return view('profile::edit', compact('profile'));
}
public function update(Request $request)
{
$data = $request->validate([
'bio' => 'nullable|string|max:500',
'phone' => 'nullable|string|max:20',
'city' => 'nullable|string|max:100',
'country' => 'nullable|string|max:100',
'website' => 'nullable|url',
]);
Profile::updateOrCreate(['user_id' => auth()->id()], $data);
return redirect()->route('profile.show')->with('success', 'Profile updated!');
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Modules\Profile\Models;
use Illuminate\Database\Eloquent\Model;
class Profile extends Model
{
protected $fillable = ['user_id', 'avatar', 'bio', 'phone', 'city', 'country', 'website', 'is_verified'];
protected $casts = ['is_verified' => 'boolean'];
public function user()
{
return $this->belongsTo(\App\Models\User::class);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Modules\Profile\Providers;
use Illuminate\Support\ServiceProvider;
class ProfileServiceProvider extends ServiceProvider
{
protected string $moduleName = 'Profile';
public function boot(): void
{
$this->loadMigrationsFrom(module_path($this->moduleName, 'database/migrations'));
$this->loadRoutesFrom(module_path($this->moduleName, 'routes/web.php'));
$this->loadViewsFrom(module_path($this->moduleName, 'resources/views'), 'profile');
}
public function register(): void {}
}

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('profiles', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->unique()->constrained('users')->cascadeOnDelete();
$table->string('avatar')->nullable();
$table->text('bio')->nullable();
$table->string('phone')->nullable();
$table->string('city')->nullable();
$table->string('country')->nullable();
$table->string('website')->nullable();
$table->boolean('is_verified')->default(false);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('profiles');
}
};

View File

@ -0,0 +1,12 @@
{
"name": "Profile",
"alias": "profile",
"description": "User profile management",
"keywords": [],
"priority": 0,
"providers": [
"Modules\\Profile\\Providers\\ProfileServiceProvider"
],
"aliases": {},
"files": []
}

View File

@ -0,0 +1,34 @@
@extends('layouts.app')
@section('content')
<div class="container mx-auto px-4 py-8">
<div class="max-w-2xl mx-auto">
<h1 class="text-2xl font-bold mb-6">Edit Profile</h1>
<form method="POST" action="{{ route('profile.update') }}" class="bg-white rounded-lg shadow-md p-6 space-y-4">
@csrf @method('PUT')
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Bio</label>
<textarea name="bio" rows="3" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">{{ old('bio', $profile->bio) }}</textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Phone</label>
<input type="text" name="phone" value="{{ old('phone', $profile->phone) }}" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">City</label>
<input type="text" name="city" value="{{ old('city', $profile->city) }}" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Country</label>
<input type="text" name="country" value="{{ old('country', $profile->country) }}" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Website</label>
<input type="url" name="website" value="{{ old('website', $profile->website) }}" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<button type="submit" class="w-full bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700 transition font-medium">Save Profile</button>
</form>
</div>
</div>
@endsection

View File

@ -0,0 +1,25 @@
@extends('layouts.app')
@section('content')
<div class="container mx-auto px-4 py-8">
<div class="max-w-2xl mx-auto bg-white rounded-lg shadow-md p-6">
<div class="flex items-center space-x-4 mb-6">
<div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center">
<span class="text-2xl font-bold text-blue-600">{{ substr(auth()->user()->name, 0, 1) }}</span>
</div>
<div>
<h1 class="text-2xl font-bold">{{ auth()->user()->name }}</h1>
<p class="text-gray-500">{{ auth()->user()->email }}</p>
</div>
</div>
@if($profile->bio)<p class="text-gray-700 mb-4">{{ $profile->bio }}</p>@endif
<div class="space-y-2 text-gray-600">
@if($profile->phone)<p>📞 {{ $profile->phone }}</p>@endif
@if($profile->city)<p>📍 {{ $profile->city }}@if($profile->country), {{ $profile->country }}@endif</p>@endif
@if($profile->website)<p>🌐 <a href="{{ $profile->website }}" class="text-blue-600 hover:underline">{{ $profile->website }}</a></p>@endif
</div>
<div class="mt-6">
<a href="{{ route('profile.edit') }}" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition">Edit Profile</a>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,9 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\Profile\Http\Controllers\ProfileController;
Route::middleware('auth')->prefix('profile')->name('profile.')->group(function () {
Route::get('/', [ProfileController::class, 'show'])->name('show');
Route::get('/edit', [ProfileController::class, 'edit'])->name('edit');
Route::put('/extended', [ProfileController::class, 'update'])->name('update');
});

137
README.md
View File

@ -1,84 +1,59 @@
<br> <p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
<p align="center">
<a href="https://openclassify.com"><img src="https://raw.githubusercontent.com/openclassify/openclassify/master/public/openclassify-logo.png" width="250" alt="Openclassify Logo"></a>
OpenClassify is modular and most advanced open source classified platform build with Laravel included Pyrocms.
</p>
<br>
<p align="center">
<a href="https://packagist.org/packages/openclassify/openclassify" target="_blank">
<img class="badge" src="http://poser.pugx.org/openclassify/openclassify/v">
</a>
<a href="https://packagist.org/packages/openclassify/openclassify" target="_blank"><img class="badge" src="http://poser.pugx.org/openclassify/openclassify/downloads"></a>
<a href="https://packagist.org/packages/openclassify/openclassify" target="_blank"><img class="badge" src="http://poser.pugx.org/openclassify/openclassify/license"></a>
</p>
### Install with Docker
We suggest to use Docker. Nginx, Mysql and PHP 8.2 pre-installed. MacOS, Windows and Ubuntu is supported.
Install Docker and run it. Install by watching 2 min video on [Youtube](https://www.youtube.com/watch?v=vVpVmsxq-Z0&t=27s&pp=ygUTb3BlbmNsYXNzaWZ5IGRvY2tlcg%3D%3D)
1- Clone project
```bash
git clone https://github.com/openclassify/openclassify.git
```
2- Run install.sh
```bash
bash install.sh
```
3- That's it!
Open project at [localhost](http://localhost)
### Documentation
You can visit this link for detailed documentation.
https://visiosoft.gitbook.io/v2/
### CLI Commands
If you couldn't find a solution for any problem, please review our CLI Command document.
[View CLI Command Document](https://github.com/openclassify/openclassify/blob/master/docs/cli-commands.md)
### Other Installation Methods
Check [here](https://github.com/openclassify/openclassify/blob/master/docs/other-install-methods.md) for more.
### Translation
Openclassify support 22+ languages. If you'd like to contribute translations, please check out our [Crowdin](https://crowdin.com/project/openclassify) project.
### Server Requirements
- Only PHP > 8.2 Officially Supported!
- XML PHP Extension
- PDO PHP Extension
- cURL PHP Extension
- JSON PHP Extension
- Ctype PHP Extension
- BCMath PHP Extension
- SQLite PHP Extension
- OpenSSL PHP Extension
- Mbstring PHP Extension
- Fileinfo PHP Extension
- Tokenizer PHP Extension
- GD Library (>=2.0) **OR** Imagick PHP extension (>=6.5.7)
### Code Contributors
This project exists thanks to all the people who [contribute](https://github.com/openclassify/openclassify/graphs/contributors) and more.
<p align="center"> <p align="center">
<a href = "https://github.com/openclassify/openclassify/graphs/contributors"> <a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<img src = "https://contrib.rocks/image?repo=openclassify/openclassify"/> <a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
</a> <a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p> </p>
## About Laravel
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications.
## Learning Laravel
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. You can also check out [Laravel Learn](https://laravel.com/learn), where you will be guided through building a modern Laravel application.
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
## Laravel Sponsors
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
### Premium Partners
- **[Vehikl](https://vehikl.com)**
- **[Tighten Co.](https://tighten.co)**
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
- **[64 Robots](https://64robots.com)**
- **[Curotec](https://www.curotec.com/services/technologies/laravel)**
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
- **[Redberry](https://redberry.international/laravel-development)**
- **[Active Logic](https://activelogic.com)**
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

View File

@ -1,122 +0,0 @@
<?php
namespace App\Exceptions;
use Anomaly\Streams\Platform\Exception\ExceptionIdentifier;
use Swift_TransportException;
use Throwable;
use Illuminate\Support\Facades\Auth;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class ExceptionHandler extends Handler
{
protected $original;
protected $internalDontReport = [
\Illuminate\Auth\AuthenticationException::class,
\Illuminate\Auth\Access\AuthorizationException::class,
\Symfony\Component\HttpKernel\Exception\HttpException::class,
\Illuminate\Database\Eloquent\ModelNotFoundException::class,
\Illuminate\Session\TokenMismatchException::class,
\Illuminate\Validation\ValidationException::class,
];
protected function prepareException(Throwable $e)
{
$this->original = $e;
return parent::prepareException($e); // TODO: Change the autogenerated stub
}
public function render($request, Throwable $e)
{
if ($e instanceof AuthenticationException) {
return $this->unauthenticated($request, $e);
}
if ($e instanceof NotFoundHttpException && $redirect = config('streams::404.redirect')) {
return redirect($redirect);
}
return parent::render($request, $e);
}
protected function renderHttpException(HttpExceptionInterface $e)
{
if (env('APP_DEBUG') === true) {
return $this->convertExceptionToResponse($e);
}
$summary = $e->getMessage();
$headers = $e->getHeaders();
$code = $e->getStatusCode();
$name = trans("streams::error.{$code}.name");
$message = trans("streams::error.{$code}.message");
$id = $this->container->make(ExceptionIdentifier::class)->identify($this->original);
if (view()->exists($view = "streams::errors/{$code}")) {
return response()->view($view, compact('id', 'code', 'name', 'message', 'summary'), $code, $headers);
}
return response()->view(
'streams::errors/error',
compact('id', 'code', 'name', 'message', 'summary'),
$code,
$headers
);
}
public function report(Throwable $e)
{
if (app()->bound('sentry') &&
$this->shouldReport($e) &&
env('SENTRY_LARAVEL_DSN') &&
!empty(env('SENTRY_LARAVEL_DSN'))) {
app('sentry')->captureException($e);
}
if ($e instanceof Swift_TransportException) {
echo json_encode([
'success' => false,
'msg' => trans('visiosoft.theme.base::message.error_mail'),
]);
die();
}
parent::report($e);
}
protected function context()
{
try {
return array_filter(
[
'user' => Auth::id(),
'email' => Auth::user() ? Auth::user()->email : null,
'url' => request() ? request()->fullUrl() : null,
'identifier' => $this->container->make(ExceptionIdentifier::class)->identify($this->original),
]
);
} catch (Throwable $e) {
return [];
}
}
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
if ($request->segment(1) === 'admin') {
return redirect()->guest('admin/login');
} else {
return redirect()->guest('login');
}
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\LoginRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
class AuthenticatedSessionController extends Controller
{
/**
* Display the login view.
*/
public function create(): View
{
return view('auth.login');
}
/**
* Handle an incoming authentication request.
*/
public function store(LoginRequest $request): RedirectResponse
{
$request->authenticate();
$request->session()->regenerate();
return redirect()->intended(route('dashboard', absolute: false));
}
/**
* Destroy an authenticated session.
*/
public function destroy(Request $request): RedirectResponse
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
class ConfirmablePasswordController extends Controller
{
/**
* Show the confirm password view.
*/
public function show(): View
{
return view('auth.confirm-password');
}
/**
* Confirm the user's password.
*/
public function store(Request $request): RedirectResponse
{
if (! Auth::guard('web')->validate([
'email' => $request->user()->email,
'password' => $request->password,
])) {
throw ValidationException::withMessages([
'password' => __('auth.password'),
]);
}
$request->session()->put('auth.password_confirmed_at', time());
return redirect()->intended(route('dashboard', absolute: false));
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class EmailVerificationNotificationController extends Controller
{
/**
* Send a new email verification notification.
*/
public function store(Request $request): RedirectResponse
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(route('dashboard', absolute: false));
}
$request->user()->sendEmailVerificationNotification();
return back()->with('status', 'verification-link-sent');
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class EmailVerificationPromptController extends Controller
{
/**
* Display the email verification prompt.
*/
public function __invoke(Request $request): RedirectResponse|View
{
return $request->user()->hasVerifiedEmail()
? redirect()->intended(route('dashboard', absolute: false))
: view('auth.verify-email');
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
use Illuminate\View\View;
class NewPasswordController extends Controller
{
/**
* Display the password reset view.
*/
public function create(Request $request): View
{
return view('auth.reset-password', ['request' => $request]);
}
/**
* Handle an incoming new password request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'token' => ['required'],
'email' => ['required', 'email'],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function (User $user) use ($request) {
$user->forceFill([
'password' => Hash::make($request->password),
'remember_token' => Str::random(60),
])->save();
event(new PasswordReset($user));
}
);
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
return $status == Password::PASSWORD_RESET
? redirect()->route('login')->with('status', __($status))
: back()->withInput($request->only('email'))
->withErrors(['email' => __($status)]);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
class PasswordController extends Controller
{
/**
* Update the user's password.
*/
public function update(Request $request): RedirectResponse
{
$validated = $request->validateWithBag('updatePassword', [
'current_password' => ['required', 'current_password'],
'password' => ['required', Password::defaults(), 'confirmed'],
]);
$request->user()->update([
'password' => Hash::make($validated['password']),
]);
return back()->with('status', 'password-updated');
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
use Illuminate\View\View;
class PasswordResetLinkController extends Controller
{
/**
* Display the password reset link request view.
*/
public function create(): View
{
return view('auth.forgot-password');
}
/**
* Handle an incoming password reset link request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'email' => ['required', 'email'],
]);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$status = Password::sendResetLink(
$request->only('email')
);
return $status == Password::RESET_LINK_SENT
? back()->with('status', __($status))
: back()->withInput($request->only('email'))
->withErrors(['email' => __($status)]);
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
class ProfileController extends Controller
{
/**
* Update the user's profile information.
*/
public function update(Request $request): RedirectResponse
{
$validated = $request->validateWithBag('updateProfile', [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique('users')->ignore($request->user()->id)],
]);
$request->user()->fill($validated);
if ($request->user()->isDirty('email')) {
$request->user()->email_verified_at = null;
}
$request->user()->save();
return redirect('/profile')->with('status', 'profile-updated');
}
/**
* Delete the user's account.
*/
public function destroy(Request $request): RedirectResponse
{
$request->validateWithBag('userDeletion', [
'password' => ['required', 'current_password'],
]);
$user = $request->user();
Auth::logout();
$user->delete();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules;
use Illuminate\View\View;
class RegisteredUserController extends Controller
{
/**
* Display the registration view.
*/
public function create(): View
{
return view('auth.register');
}
/**
* Handle an incoming registration request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
event(new Registered($user));
Auth::login($user);
return redirect(route('dashboard', absolute: false));
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\Verified;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
use Illuminate\Http\RedirectResponse;
class VerifyEmailController extends Controller
{
/**
* Mark the authenticated user's email address as verified.
*/
public function __invoke(EmailVerificationRequest $request): RedirectResponse
{
if ($request->user()->hasVerifiedEmail()) {
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
}
if ($request->user()->markEmailAsVerified()) {
event(new Verified($request->user()));
}
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Modules\Listing\Models\Listing;
use Modules\Category\Models\Category;
use App\Models\User;
class HomeController extends Controller
{
public function index()
{
$categories = Category::whereNull('parent_id')->where('is_active', true)->get();
$featuredListings = Listing::where('status', 'active')->where('is_featured', true)->latest()->take(4)->get();
$recentListings = Listing::where('status', 'active')->latest()->take(8)->get();
$listingCount = Listing::where('status', 'active')->count();
$categoryCount = Category::where('is_active', true)->count();
$userCount = User::count();
return view('home', compact('categories', 'featuredListings', 'recentListings', 'listingCount', 'categoryCount', 'userCount'));
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class LanguageController extends Controller
{
public function switch(string $locale)
{
$available = config('app.available_locales', ['en']);
if (in_array($locale, $available)) {
session(['locale' => $locale]);
}
return redirect()->back()->withInput();
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Http\Controllers\Partner;
use App\Http\Controllers\Controller;
use Modules\Listing\Models\Listing;
class DashboardController extends Controller
{
public function index()
{
$myListings = Listing::where('user_id', auth()->id())->latest()->paginate(10);
$stats = [
'total' => Listing::where('user_id', auth()->id())->count(),
'active' => Listing::where('user_id', auth()->id())->where('status', 'active')->count(),
];
return view('partner.dashboard', compact('myListings', 'stats'));
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Http\Controllers\Partner;
use App\Http\Controllers\Controller;
use Modules\Listing\Models\Listing;
class ListingController extends Controller
{
public function index()
{
$listings = Listing::where('user_id', auth()->id())->latest()->paginate(15);
return view('partner.listings.index', compact('listings'));
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ProfileUpdateRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\View\View;
class ProfileController extends Controller
{
/**
* Display the user's profile form.
*/
public function edit(Request $request): View
{
return view('profile.edit', [
'user' => $request->user(),
]);
}
/**
* Update the user's profile information.
*/
public function update(ProfileUpdateRequest $request): RedirectResponse
{
$request->user()->fill($request->validated());
if ($request->user()->isDirty('email')) {
$request->user()->email_verified_at = null;
}
$request->user()->save();
return Redirect::route('profile.edit')->with('status', 'profile-updated');
}
/**
* Delete the user's account.
*/
public function destroy(Request $request): RedirectResponse
{
$request->validateWithBag('userDeletion', [
'password' => ['required', 'current_password'],
]);
$user = $request->user();
Auth::logout();
$user->delete();
$request->session()->invalidate();
$request->session()->regenerateToken();
return Redirect::to('/');
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class SetLocale
{
public function handle(Request $request, Closure $next)
{
$locale = session('locale', config('app.locale'));
$available = config('app.available_locales', ['en']);
if (!in_array($locale, $available)) {
$locale = config('app.locale');
}
app()->setLocale($locale);
return $next($request);
}
}

View File

@ -0,0 +1,85 @@
<?php
namespace App\Http\Requests\Auth;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'email' => ['required', 'string', 'email'],
'password' => ['required', 'string'],
];
}
/**
* Attempt to authenticate the request's credentials.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function authenticate(): void
{
$this->ensureIsNotRateLimited();
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey());
}
/**
* Ensure the login request is not rate limited.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function ensureIsNotRateLimited(): void
{
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
return;
}
event(new Lockout($this));
$seconds = RateLimiter::availableIn($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
]),
]);
}
/**
* Get the rate limiting throttle key for the request.
*/
public function throttleKey(): string
{
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class ProfileUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => [
'required',
'string',
'lowercase',
'email',
'max:255',
Rule::unique(User::class)->ignore($this->user()->id),
],
];
}
}

View File

@ -1,278 +0,0 @@
<?php namespace App\Lang;
use Anomaly\Streams\Platform\Addon\Addon;
use Anomaly\Streams\Platform\Addon\AddonCollection;
use Anomaly\Streams\Platform\Application\Application;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Translation\FileLoader;
/**
* Class Loader
*
* @link http://pyrocms.com/
* @author PyroCMS, Inc. <support@pyrocms.com>
* @author Ryan Thompson <ryan@pyrocms.com>
*/
class Loader extends FileLoader
{
/**
* The runtime cache.
*
* @var array
*/
protected static $disabled = [];
/**
* The streams path.
*
* @var string
*/
protected $streams;
/**
* The addon collection instance.
*
* @var AddonCollection
*/
protected $addons;
/**
* The application instance.
*
* @var Application
*/
protected $application;
/**
* Create a new Loader instance.
*
* @param Filesystem $files
* @param string $path
*/
public function __construct(Filesystem $files, $path)
{
$this->streams = base_path('vendor/visiosoft/streams-platform/resources/lang');
$this->application = app(Application::class);
$this->addons = app(AddonCollection::class);
parent::__construct($files, $path);
}
/**
* Load a locale from a given path.
*
* Keep streams overrides in place
* that are NOT namespaced cause
* we're overriding Laravel's
* base language files too.
*
* @param string $path
* @param string $locale
* @param string $group
* @return array
*/
protected function loadPath($path, $locale, $group)
{
$lines = parent::loadPath($path, $locale, $group);
if ($path == $this->streams && $lines) {
$lines = $this->loadAddonOverrides($lines, $locale, $group);
$lines = $this->loadSystemOverrides($lines, $locale, $group);
$lines = $this->loadApplicationOverrides($lines, $locale, $group);
}
return $lines;
}
/**
* Load namespaced overrides from
* system AND application paths.
*
* @param array $lines
* @param string $locale
* @param string $group
* @param string $namespace
* @return array
*/
protected function loadNamespaceOverrides(array $lines, $locale, $group, $namespace)
{
/**
* @deprecated since 1.6; Use manual loading or publishing.
*/
if (env('AUTOMATIC_ADDON_OVERRIDES', true)) {
$lines = $this->loadAddonOverrides($lines, $locale, $group, $namespace);
}
$lines = $this->loadSystemOverrides($lines, $locale, $group, $namespace);
$lines = $this->loadApplicationOverrides($lines, $locale, $group, $namespace);
return parent::loadNamespaceOverrides($lines, $locale, $group, $namespace);
}
/**
* Load system overrides.
*
* @param array $lines
* @param $locale
* @param $group
* @param $namespace
* @return array
*/
protected function loadSystemOverrides(array $lines, $locale, $group, $namespace = null)
{
if (!$namespace || $namespace == 'streams') {
$file = base_path("resources/streams/lang/{$locale}/{$group}.php");
if (is_dir(base_path("resources/streams/lang")) && $this->files->exists($file)) {
$lines = array_replace_recursive($lines, $this->files->getRequire($file));
}
}
if (str_is('*.*.*', $namespace)) {
list($vendor, $type, $slug) = explode('.', $namespace);
$file = base_path("resources/addons/{$vendor}/{$slug}-{$type}/lang/{$locale}/{$group}.php");
if (is_dir(base_path("resources/addons/{$vendor}/{$slug}-{$type}/lang")) && $this->files->exists($file)) {
$lines = array_replace_recursive($lines, $this->files->getRequire($file));
}
}
return $lines;
}
/**
* Load system overrides.
*
* @param array $lines
* @param $locale
* @param $group
* @param $namespace
* @return array
*/
protected function loadApplicationOverrides(array $lines, $locale, $group, $namespace = null)
{
if (!$namespace || $namespace == 'streams') {
$file = $this->application->getResourcesPath("streams/lang/{$locale}/{$group}.php");
if (is_dir($this->application->getResourcesPath("streams/lang")) && $this->files->exists($file)) {
$lines = array_replace_recursive($lines, $this->files->getRequire($file));
}
}
if (str_is('*.*.*', $namespace)) {
list($vendor, $type, $slug) = explode('.', $namespace);
$file = $this->application->getResourcesPath(
"addons/{$vendor}/{$slug}-{$type}/lang/{$locale}/{$group}.php"
);
if (
is_dir($this->application->getResourcesPath("addons/{$vendor}/{$slug}-{$type}/lang"))
&& $this->files->exists($file)
) {
$lines = array_replace_recursive($lines, $this->files->getRequire($file));
}
}
//Override System
$override_list = config('streams::translate.override');
foreach ($override_list as $override) {
$override = explode(':', $override);
if (count($override) > 1) {
$lines = $this->findArrayValue($override[0], $override[1], $lines);
}
}
if (config()->has('override_text')) {
foreach (config()->get('override_text') as $override) {
$override = explode(':', $override);
if (count($override) > 1) {
$lines = $this->findArrayValue($override[0], $override[1], $lines);
}
}
}
return $lines;
}
function replaceNewValue($find_value, $new_value, $arr)
{
if (is_array($arr)) {
foreach ($arr as $key => $item) {
$arr[$key] = $this->replaceNewValue($find_value, $new_value, $item);
}
return $arr;
}
if (strtolower($arr) == strtolower($find_value)) {
return $new_value;
}
return $arr;
}
function findArrayValue($find_value, $new_value, $arr)
{
foreach ($arr as $key => $item) {
$arr[$key] = $this->replaceNewValue($find_value, $new_value, $item);
}
return $arr;
}
/**
* @param array $lines
* @param $locale
* @param $group
* @param null $namespace
* @return array
*/
protected function loadAddonOverrides(array $lines, $locale, $group, $namespace = null)
{
/** @var Addon $addon */
foreach ($this->addons->enabled() as $addon) {
$disabled = array_get(self::$disabled, $key = $addon->getNamespace('streams'), false);
if (!$disabled && !$this->files->isDirectory($addon->getPath('resources/streams'))) {
self::$disabled[$key] = $disabled = true;
}
if (!$disabled && (!$namespace || $namespace == 'streams')) {
$file = $addon->getPath("resources/streams/lang/{$locale}/{$group}.php");
if ($this->files->exists($file)) {
$lines = array_replace_recursive($lines, $this->files->getRequire($file));
}
}
$disabled = array_get(self::$disabled, $key = $addon->getNamespace('addons'), false);
if (!$disabled && !$this->files->isDirectory($addon->getPath('resources/addons'))) {
self::$disabled[$key] = $disabled = true;
}
if (!$disabled && str_is('*.*.*', $namespace)) {
list($vendor, $type, $slug) = explode('.', $namespace);
$file = $addon->getPath(
"resources/addons/{$vendor}/{$slug}-{$type}/lang/{$locale}/{$group}.php"
);
if ($this->files->exists($file)) {
$lines = array_replace_recursive($lines, $this->files->getRequire($file));
}
}
}
return $lines;
}
}

View File

@ -1,23 +0,0 @@
<?php namespace App\Listeners;
use Anomaly\SettingsModule\Setting\Form\SettingFormRepository;
use Anomaly\Streams\Platform\Ui\Form\Event\FormWasSaved;
use Illuminate\Support\Facades\Artisan;
class EnableMaintenanceMode
{
public function handle(FormWasSaved $event)
{
$builder = $event->getBuilder();
if (get_class($builder->getRepository()) === SettingFormRepository::class) {
if ($builder->getFormValues()->has('maintenance')) {
if ($builder->getFormValues()->get('maintenance')) {
Artisan::call('down');
} elseif (config('streams::maintenance.enabled')) {
Artisan::call('up');
}
}
}
}
}

View File

@ -1,45 +0,0 @@
<?php namespace App\Listeners;
use Anomaly\Streams\Platform\Event\Booted;
use App\Lang\Loader;
use Illuminate\Translation\Translator;
class Translations
{
public function handle(Booted $event)
{
app()->singleton(
'translation.loader',
function ($application) {
return new Loader($application['files'], $application['path.lang']);
}
);
app()->singleton(
'translator',
function ($application) {
$loader = $application->make('translation.loader');
// When registering the translator component, we'll need to set the default
// locale as well as the fallback locale. So, we'll grab the application
// configuration so we can easily get both of these values from there.
$locale = $application['config']['app.locale'];
$trans = new Translator($loader, $locale);
$trans->setFallback($application['config']['app.fallback_locale']);
return $trans;
}
);
if (defined('LOCALE')) {
app()->setLocale(LOCALE);
config()->set('app.locale', LOCALE);
}
// Set our locale namespace.
app()->make('translator')->addNamespace('streams', realpath(__DIR__ . '/../../vendor/visiosoft/streams-platform/resources/lang'));
}
}

48
app/Models/User.php Normal file
View File

@ -0,0 +1,48 @@
<?php
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
}

View File

@ -1,64 +0,0 @@
<?php namespace App\Notification;
use Anomaly\Streams\Platform\Notification\Message\MailMessage;
use Anomaly\UsersModule\User\Contract\UserInterface;
use Illuminate\Notifications\Notification;
/**
* Class ActivateYourAccount
*
* @link http://pyrocms.com/
* @author PyroCMS, Inc. <support@pyrocms.com>
* @author Ryan Thompson <ryan@pyrocms.com>
*/
class ActivateYourAccount extends Notification
{
/**
* Redirect here after activating.
*
* @var string
*/
public $redirect;
/**
* Create a new UserHasRegistered instance.
*
* @param $redirect
*/
public function __construct($redirect = '/')
{
$this->redirect = $redirect;
}
/**
* Get the notification's delivery channels.
*
* @param UserInterface $notifiable
* @return array
*/
public function via(UserInterface $notifiable)
{
return ['mail'];
}
/**
* Return the mail message.
*
* @param UserInterface $notifiable
* @return MailMessage
*/
public function toMail(UserInterface $notifiable)
{
$data = $notifiable->attributesToArray();
return (new MailMessage())
->view('anomaly.module.users::notifications.activate_your_account',$data)
->subject(trans('anomaly.module.users::notification.activate_your_account.subject', $data))
->greeting(trans('anomaly.module.users::notification.activate_your_account.greeting', $data))
->line(trans('anomaly.module.users::notification.activate_your_account.instructions', $data))
->action(
trans('anomaly.module.users::notification.activate_your_account.button', $data),
$notifiable->route('activate', ['redirect' => $this->redirect])
);
}
}

View File

@ -1,72 +0,0 @@
<?php namespace App\Notification;
use Anomaly\Streams\Platform\Notification\Message\MailMessage;
use Anomaly\UsersModule\User\Contract\UserInterface;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
/**
* Class PasswordInvalidated
*
* @link http://pyrocms.com/
* @author PyroCMS, Inc. <support@pyrocms.com>
* @author Ryan Thompson <ryan@pyrocms.com>
*/
class PasswordInvalidated extends Notification implements ShouldQueue
{
use Queueable;
/**
* Redirect here after activating.
*
* @var string
*/
public $redirect;
/**
* Create a new UserHasRegistered instance.
*
* @param $redirect
*/
public function __construct($redirect = '/')
{
$this->redirect = $redirect;
}
/**
* Get the notification's delivery channels.
*
* @param UserInterface $notifiable
* @return array
*/
public function via(UserInterface $notifiable)
{
return ['mail'];
}
/**
* Return the mail message.
*
* @param UserInterface $notifiable
* @return MailMessage
*/
public function toMail(UserInterface $notifiable)
{
$data = $notifiable->attributesToArray();
return (new MailMessage())
->error()
->view('anomaly.module.users::notifications.password_invalidated')
->subject(trans('anomaly.module.users::notification.password_invalidated.subject', $data))
->greeting(trans('anomaly.module.users::notification.password_invalidated.greeting', $data))
->line(trans('anomaly.module.users::notification.password_invalidated.notice', $data))
->line(trans('anomaly.module.users::notification.password_invalidated.warning', $data))
->line(trans('anomaly.module.users::notification.password_invalidated.instructions', $data))
->action(
trans('anomaly.module.users::notification.password_invalidated.button', $data),
$notifiable->route('reset', ['redirect' => $this->redirect])
);
}
}

View File

@ -1,69 +0,0 @@
<?php namespace App\Notification;
use Anomaly\Streams\Platform\Notification\Message\MailMessage;
use Anomaly\UsersModule\User\Contract\UserInterface;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
/**
* Class ResetYourPassword
*
* @link http://pyrocms.com/
* @author PyroCMS, Inc. <support@pyrocms.com>
* @author Ryan Thompson <ryan@pyrocms.com>
*/
class ResetYourPassword extends Notification
{
/**
* Redirect here after activating.
*
* @var string
*/
public $redirect;
/**
* Create a new UserHasRegistered instance.
*
* @param $redirect
*/
public function __construct($redirect = '/')
{
$this->redirect = $redirect;
}
/**
* Get the notification's delivery channels.
*
* @param UserInterface $notifiable
* @return array
*/
public function via(UserInterface $notifiable)
{
return ['mail'];
}
/**
* Return the mail message.
*
* @param UserInterface $notifiable
* @return MailMessage
*/
public function toMail(UserInterface $notifiable)
{
$data = $notifiable->attributesToArray();
return (new MailMessage())
->error()
->view('anomaly.module.users::notifications.reset_your_password')
->subject(trans('anomaly.module.users::notification.reset_your_password.subject', $data))
->greeting(trans('anomaly.module.users::notification.reset_your_password.greeting', $data))
->line(trans('anomaly.module.users::notification.reset_your_password.notice', $data))
->line(trans('anomaly.module.users::notification.reset_your_password.warning', $data))
->line(trans('anomaly.module.users::notification.reset_your_password.instructions', $data))
->action(
trans('anomaly.module.users::notification.reset_your_password.button', $data),
$notifiable->route('reset', ['redirect' => $this->redirect])
);
}
}

View File

@ -1,52 +0,0 @@
<?php namespace App\Notification;
use Anomaly\Streams\Platform\Notification\Message\MailMessage;
use Anomaly\UsersModule\User\Contract\UserInterface;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
/**
* Class UserHasBeenActivated
*
* @link http://pyrocms.com/
* @author PyroCMS, Inc. <support@pyrocms.com>
* @author Ryan Thompson <ryan@pyrocms.com>
*/
class UserHasBeenActivated extends Notification implements ShouldQueue
{
use Queueable;
/**
* Get the notification's delivery channels.
*
* @param UserInterface $notifiable
* @return array
*/
public function via(UserInterface $notifiable)
{
return ['mail'];
}
/**
* Return the mail message.
*
* @param UserInterface $notifiable
* @return MailMessage
*/
public function toMail(UserInterface $notifiable)
{
$data = $notifiable->attributesToArray();
return (new MailMessage())
->view('anomaly.module.users::notifications.user_has_been_activated')
->subject(trans('anomaly.module.users::notification.user_has_been_activated.subject', $data))
->greeting(trans('anomaly.module.users::notification.user_has_been_activated.greeting', $data))
->line(trans('anomaly.module.users::notification.user_has_been_activated.instructions', $data))
->action(
trans('anomaly.module.users::notification.user_has_been_activated.button', $data),
route('anomaly.module.users::login')
);
}
}

View File

@ -1,110 +0,0 @@
<?php namespace App\Notification;
use Anomaly\Streams\Platform\Notification\Message\MailMessage;
use Anomaly\UsersModule\User\Contract\UserInterface;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\AnonymousNotifiable;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;
/**
* Class UserHasRegistered
*
* @link http://pyrocms.com/
* @author PyroCMS, Inc. <support@pyrocms.com>
* @author Ryan Thompson <ryan@pyrocms.com>
*/
class UserHasRegistered extends Notification implements ShouldQueue
{
use Queueable;
/**
* The user who registered.
*
* @var UserInterface
*/
public $user;
/**
* Create a new UserHasRegistered instance.
*
* @param UserInterface $user
*/
public function __construct(UserInterface $user)
{
$this->user = $user;
}
/**
* Get the notification's delivery channels.
*
* @return array
*/
public function via()
{
return ['mail', 'slack'];
}
/**
* Return the mail message.
*
* @param AnonymousNotifiable $notifiable
* @return MailMessage
*/
public function toMail(AnonymousNotifiable $notifiable)
{
$data = $this->user->attributesToArray();
return (new MailMessage())
->view('anomaly.module.users::notifications.user_has_registered')
->subject(trans('anomaly.module.users::notification.user_has_registered.subject', $data))
->line(trans('anomaly.module.users::notification.user_has_registered.instructions', $data))
->action(
trans('anomaly.module.users::notification.user_has_registered.button', $data),
$this->user->route('view')
);
}
/**
* Return the slack message.
*
* @param UserInterface $notifiable
*
* @return SlackMessage
*/
public function toSlack(UserInterface $notifiable)
{
return (new SlackMessage())
->success()
->content('Hmm.. What\'s Ryan up to?')
->attachment(
function ($attachment) {
$attachment
->title('Testing out teh goodies!', 'http://pyrocms.com/')
->fields(
[
'Username' => $this->user->getUsername(),
'Eamil' => $this->user->getEmail(),
]
);
}
);
}
/**
* Return the array storage data.
*
* @param Notifiable $notifiable
*
* @return array
*/
public function toDatabase(UserInterface $notifiable)
{
return [
'user' => $this->user,
];
}
}

View File

@ -1,68 +0,0 @@
<?php namespace App\Notification;
use Anomaly\Streams\Platform\Notification\Message\MailMessage;
use Anomaly\UsersModule\User\Contract\UserInterface;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\AnonymousNotifiable;
use Illuminate\Notifications\Notification;
/**
* Class UserPendingActivation
*
* @link http://pyrocms.com/
* @author PyroCMS, Inc. <support@pyrocms.com>
* @author Ryan Thompson <ryan@pyrocms.com>
*/
class UserPendingActivation extends Notification implements ShouldQueue
{
use Queueable;
/**
* The user pending activation.
*
* @var UserInterface
*/
public $user;
/**
* Create a new UserPendingActivation instance.
*
* @param UserInterface $user
*/
public function __construct(UserInterface $user)
{
$this->user = $user;
}
/**
* Get the notification's delivery channels.
*
* @return array
*/
public function via()
{
return ['mail'];
}
/**
* Return the mail message.
*
* @param AnonymousNotifiable $notifiable
* @return MailMessage
*/
public function toMail(AnonymousNotifiable $notifiable)
{
$data = $this->user->attributesToArray();
return (new MailMessage())
->view('anomaly.module.users::notifications.user_pending_activation')
->subject(trans('anomaly.module.users::notification.user_pending_activation.subject', $data))
->line(trans('anomaly.module.users::notification.user_pending_activation.instructions', $data))
->action(
trans('anomaly.module.users::notification.user_pending_activation.button', $data),
url('admin/users?view=pending')
);
}
}

View File

@ -2,46 +2,22 @@
namespace App\Providers; namespace App\Providers;
use Anomaly\Streams\Platform\Ui\ControlPanel\Component\Navigation\NavigationFactory;
use Anomaly\Streams\Platform\Ui\ControlPanel\ControlPanelBuilder;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
/** /**
* Bootstrap any application services. * Register any application services.
*
* @return void
*/ */
public function boot(ControlPanelBuilder $builder, NavigationFactory $factory) public function register(): void
{ {
view()->composer('*', function ($view) use ($builder, $factory) { //
if (auth()->check() and template()->get('cp')) {
//Hidden menu items in sidebar on dashboard
($navigation = template()->get('cp')->getNavigation()->get('anomaly.module.variables')) ? $navigation->setClass('hidden') : false;
($navigation = template()->get('cp')->getNavigation()->get('anomaly.module.system')) ? $navigation->setClass('hidden') : false;
($navigation = template()->get('cp')->getNavigation()->get('anomaly.module.redirects')) ? $navigation->setClass('hidden') : false;
($navigation = template()->get('cp')->getNavigation()->get('anomaly.module.repeaters')) ? $navigation->setClass('hidden') : false;
}
//Auto Language Switcher
if (config('advs.lang_switcher_for_browser') and is_null(Request()->session()->get('_locale')) and isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);//Get Browser Language
$acceptLang = config('streams::locales.enabled'); //Supported Language
$lang = in_array($lang, $acceptLang) ? $lang : config('streams::locales.default', 'en');
App()->setLocale($lang);
Request()->session()->put('_locale', $lang);
}
});
} }
/** /**
* Register any application services. * Bootstrap any application services.
*
* @return void
*/ */
public function register() public function boot(): void
{ {
// //
} }

View File

@ -1,29 +0,0 @@
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}

View File

@ -1,21 +0,0 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Broadcast::routes();
require base_path('routes/channels.php');
}
}

View File

@ -1,31 +0,0 @@
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
'App\Events\SomeEvent' => [
'App\Listeners\EventListener',
],
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
parent::boot();
//
}
}

View File

@ -1,85 +0,0 @@
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
/**
* This namespace is applied to your controller routes.
*
* In addition, it is set as the URL generator's root namespace.
*
* @var string
*/
protected $namespace = 'App\Http\Controllers';
/**
* Define your route model bindings, pattern filters, etc.
*
* @return void
*/
public function boot()
{
//
parent::boot();
}
/**
* Define the routes for the application.
*
* @return void
*/
public function map()
{
$this->mapWebRoutes();
$this->mapApiRoutes();
//
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* @return void
*/
protected function mapWebRoutes()
{
Route::group(
[
'middleware' => 'web',
'namespace' => $this->namespace,
],
function ($router) {
require base_path('routes/web.php');
}
);
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*
* @return void
*/
protected function mapApiRoutes()
{
Route::group(
[
'middleware' => 'api',
'namespace' => $this->namespace,
'prefix' => 'api',
],
function ($router) {
require base_path('routes/api.php');
}
);
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class AppLayout extends Component
{
/**
* Get the view / contents that represents the component.
*/
public function render(): View
{
return view('layouts.app');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\View\Components;
use Illuminate\View\Component;
use Illuminate\View\View;
class GuestLayout extends Component
{
/**
* Get the view / contents that represents the component.
*/
public function render(): View
{
return view('layouts.guest');
}
}

49
artisan Normal file → Executable file
View File

@ -1,53 +1,18 @@
#!/usr/bin/env php #!/usr/bin/env php
<?php <?php
use Illuminate\Foundation\Application;
use Symfony\Component\Console\Input\ArgvInput;
define('LARAVEL_START', microtime(true)); define('LARAVEL_START', microtime(true));
/* // Register the Composer autoloader...
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader
| for our application. We just need to utilize it! We'll require it
| into the script here so that we do not have to worry about the
| loading of any our classes "manually". Feels great to relax.
|
*/
require __DIR__.'/vendor/autoload.php'; require __DIR__.'/vendor/autoload.php';
// Bootstrap Laravel and handle the command...
/** @var Application $app */
$app = require_once __DIR__.'/bootstrap/app.php'; $app = require_once __DIR__.'/bootstrap/app.php';
/* $status = $app->handleCommand(new ArgvInput);
|--------------------------------------------------------------------------
| Run The Artisan Application
|--------------------------------------------------------------------------
|
| When we run the console application, the current CLI command will be
| executed in this console and the response sent back to a terminal
| or another output device for the developers. Here goes nothing!
|
*/
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);
/*
|--------------------------------------------------------------------------
| Shutdown The Application
|--------------------------------------------------------------------------
|
| Once Artisan has finished running, we will fire off the shutdown events
| so that any final work may be done by the application before we shut
| down the process. This is the last thing to happen to the request.
|
*/
$kernel->terminate($input, $status);
exit($status); exit($status);

View File

@ -1,55 +1,20 @@
<?php <?php
/* use Illuminate\Foundation\Application;
|-------------------------------------------------------------------------- use Illuminate\Foundation\Configuration\Exceptions;
| Create The Application use Illuminate\Foundation\Configuration\Middleware;
|--------------------------------------------------------------------------
|
| The first thing we will do is create a new Laravel application instance
| which serves as the "glue" for all the components of Laravel, and is
| the IoC container for the system binding all of the various parts.
|
*/
$app = new Illuminate\Foundation\Application( return Application::configure(basePath: dirname(__DIR__))
realpath(__DIR__ . '/../') ->withRouting(
); web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
/* health: '/up',
|-------------------------------------------------------------------------- )
| Bind Important Interfaces ->withMiddleware(function (Middleware $middleware): void {
|-------------------------------------------------------------------------- $middleware->web(append: [
| \App\Http\Middleware\SetLocale::class,
| Next, we need to bind some important interfaces into the container so ]);
| we will be able to resolve them when needed. The kernels serve the })
| incoming requests to this application from both the web and CLI. ->withExceptions(function (Exceptions $exceptions): void {
| //
*/ })->create();
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
'Anomaly\Streams\Platform\Http\Kernel'
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
'Anomaly\Streams\Platform\Console\Kernel'
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
Illuminate\Foundation\Exceptions\Handler::class
);
/*
|--------------------------------------------------------------------------
| Return The Application
|--------------------------------------------------------------------------
|
| This script returns the application instance. The instance is given to
| the calling script so we can separate the building of the instances
| from the actual running of the application and sending responses.
|
*/
return $app;

View File

@ -1,34 +0,0 @@
<?php
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Register The Composer Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader
| for our application. We just need to utilize it! We'll require it
| into the script here so that we do not have to worry about the
| loading of any our classes "manually". Feels great to relax.
|
*/
require __DIR__.'/../vendor/autoload.php';
/*
|--------------------------------------------------------------------------
| Include The Compiled Class File
|--------------------------------------------------------------------------
|
| To dramatically increase your application's performance, you may use a
| compiled class file which contains all of the classes commonly used
| by a request. The Artisan "optimize" is used to create this file.
|
*/
$compiledPath = __DIR__.'/cache/compiled.php';
if (file_exists($compiledPath)) {
require $compiledPath;
}

View File

@ -1,2 +1,2 @@
* *
!.gitignore !.gitignore

5
bootstrap/providers.php Normal file
View File

@ -0,0 +1,5 @@
<?php
return [
App\Providers\AppServiceProvider::class,
];

View File

@ -1,152 +1,90 @@
{ {
"name": "openclassify/openclassify", "$schema": "https://getcomposer.org/schema.json",
"description": "OpenClassify is the extensible and most advanced open source classified app build with Laravel.", "name": "laravel/laravel",
"type": "project", "type": "project",
"keywords": [ "description": "The skeleton application for the Laravel framework.",
"OpenClassify", "keywords": ["laravel", "framework"],
"classified",
"open Classify"
],
"license": "MIT", "license": "MIT",
"authors": [
{
"name": "Visiosoft, Inc.",
"email": "support@visiosoft.com.tr"
}
],
"require": { "require": {
"visiosoft/streams-platform": "~1.9.0", "php": "^8.2",
"anomaly/xml_feed_widget-extension": "~2.1.0", "laravel/framework": "^12.0",
"anomaly/default_authenticator-extension": "~2.1.0", "laravel/tinker": "^2.10.1",
"anomaly/throttle_security_check-extension": "~2.1.0", "nwidart/laravel-modules": "^11.0"
"anomaly/private_storage_adapter-extension": "~1.1.0",
"anomaly/default_page_handler-extension": "~2.1.0",
"anomaly/user_security_check-extension": "~2.1.0",
"anomaly/page_link_type-extension": "~2.1.0",
"anomaly/url_link_type-extension": "~2.1.0",
"anomaly/relationship-field_type": "~2.2.0",
"anomaly/colorpicker-field_type": "~2.3.0",
"anomaly/polymorphic-field_type": "~2.1.0",
"anomaly/checkboxes-field_type": "~2.4.0",
"anomaly/encrypted-field_type": "~2.1.0",
"anomaly/datetime-field_type": "~3.0.0",
"anomaly/repeater-field_type": "~1.3.0",
"anomaly/language-field_type": "~2.2.0",
"anomaly/multiple-field_type": "~2.3.0",
"anomaly/textarea-field_type": "~2.1.0",
"anomaly/markdown-field_type": "~3.1.0",
"anomaly/wysiwyg-field_type": "~3.1.0",
"anomaly/boolean-field_type": "~2.3.0",
"anomaly/country-field_type": "~2.3.0",
"anomaly/decimal-field_type": "~2.1.0",
"anomaly/integer-field_type": "~2.1.0",
"anomaly/editor-field_type": "~3.1.0",
"anomaly/select-field_type": "2.3.8",
"anomaly/slider-field_type": "~3.0.0",
"anomaly/addon-field_type": "~2.2.0",
"anomaly/email-field_type": "~2.1.0",
"anomaly/state-field_type": "~2.3.0",
"anomaly/files-field_type": "~2.3.0",
"anomaly/tags-field_type": "~2.4.0",
"anomaly/slug-field_type": "~2.1.0",
"anomaly/text-field_type": "~2.2.0",
"anomaly/file-field_type": "~2.2.0",
"anomaly/url-field_type": "~2.2.0",
"anomaly/configuration-module": "~2.1.0",
"anomaly/preferences-module": "~2.2.0",
"anomaly/navigation-module": "~2.4.0",
"anomaly/dashboard-module": "~2.2.0",
"anomaly/redirects-module": "~2.3.0",
"anomaly/settings-module": "~2.4.0",
"anomaly/search-module": "~3.0.0",
"anomaly/users-module": "~2.5.0",
"anomaly/pages-module": "~2.6.0",
"anomaly/posts-module": "~2.6.0",
"anomaly/files-module": "~2.6.0",
"visiosoft/contact-plugin": "*",
"anomaly/helper-plugin": "~2.1.0",
"anomaly/robots-extension": "~2.1.0",
"anomaly/html_block-extension": "~1.0.0",
"anomaly/wysiwyg_block-extension": "~1.0.0",
"visiosoft/decimal-field_type": "~2.1.0",
"visiosoft/integer-field_type": "~2.1.0",
"visiosoft/list-field_type": "*",
"visiosoft/addblock-extension": "^1.1",
"google/recaptcha": "1.2.*",
"sentry/sentry-laravel": "2.3.1",
"composer/composer": "2.*",
"visiosoft/composer-merge-plugin": "2.*",
"guzzlehttp/guzzle": "^7.3",
"visiosoft/connect-module": "^1.0",
"visiosoft/singlefile-field_type": "^1.0",
"visiosoft/profile-module": "^1.0",
"visiosoft/multiple-field_type": "^1.0",
"visiosoft/media-field_type": "^1.0",
"visiosoft/location-module": "^1.0",
"visiosoft/input_file-field_type": "^1.0",
"visiosoft/defaultadmin-theme": "^1.0",
"visiosoft/cats-module": "^1.0",
"visiosoft/base-theme": "^1.0",
"visiosoft/advs-module": "^1.0",
"visiosoft/json-field_type": "^1.0",
"visiosoft/language_switcher-plugin": "^1.0",
"visiosoft/global_helper-extension": "*"
},
"replace": {
"anomaly/streams-platform": "*"
}, },
"require-dev": { "require-dev": {
"filp/whoops": "~2.0", "fakerphp/faker": "^1.23",
"fzaninotto/faker": "~1.4", "laravel/breeze": "*",
"symfony/css-selector": "3.1.*", "laravel/pail": "^1.2.2",
"symfony/dom-crawler": "3.1.*" "laravel/pint": "^1.24",
"laravel/sail": "^1.41",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpunit/phpunit": "^11.5.3"
}, },
"repositories": [
{
"type": "composer",
"url": "https://packages.pyrocms.com"
},
{
"type": "composer",
"url": "https://community.pyrocms.com"
}
],
"autoload": { "autoload": {
"classmap": [
"database"
],
"psr-4": { "psr-4": {
"App\\": "app/", "App\\": "app/",
"Modules\\": "Modules/",
"Database\\Factories\\": "database/factories/", "Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/" "Database\\Seeders\\": "database/seeders/"
} }
}, },
"autoload-dev": { "autoload-dev": {
"classmap": [ "psr-4": {
"tests/TestCase.php" "Tests\\": "tests/"
}
},
"scripts": {
"setup": [
"composer install",
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php artisan key:generate",
"@php artisan migrate --force",
"npm install",
"npm run build"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
],
"test": [
"@php artisan config:clear --ansi",
"@php artisan test"
],
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"pre-package-uninstall": [
"Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
] ]
}, },
"extra": { "extra": {
"merge-plugin": {
"include": [
"addons/*/*/*/composer.json",
"core/*/*/composer.json"
],
"recurse": true,
"replace": false
},
"laravel": { "laravel": {
"dont-discover": [ "dont-discover": []
"*"
]
} }
}, },
"config": { "config": {
"bin-dir": "bin",
"preferred-install": "dist",
"optimize-autoloader": true, "optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": { "allow-plugins": {
"visiosoft/composer-merge-plugin": true "pestphp/pest-plugin": true,
"php-http/discovery": true,
"wikimedia/composer-merge-plugin": true
} }
} },
"minimum-stability": "stable",
"prefer-stable": true
} }

8603
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More