feat(migrations): implement Phinx database migration system and update installation process

- Introduced a modern database migration system using Phinx, replacing the legacy SQL import method.
- Updated `install.php` to run migrations instead of importing SQL dumps.
- Added migration configuration file `phinx.php` and initial migration files for schema and data seeding.
- Created a new admin panel for migration status management.
- Updated UPGRADE_GUIDE.md to include migration setup instructions and benefits.
- Ensured backward compatibility for existing installations while facilitating a smoother transition to the new system.
This commit is contained in:
Yury Pikhtarev 2025-06-19 18:29:33 +04:00
commit e5ebf855ba
No known key found for this signature in database
13 changed files with 3630 additions and 25 deletions

129
CLAUDE.md Normal file
View file

@ -0,0 +1,129 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
TorrentPier is a BitTorrent tracker engine written in PHP, designed for hosting BitTorrent communities with forum functionality. The project is in active modernization, transitioning from legacy code to modern PHP practices while maintaining backward compatibility.
## Technology Stack & Architecture
- **PHP 8.1+** with modern features
- **MySQL/MariaDB/Percona** database
- **Nette Database** with backward-compatible wrapper
- **Composer** for dependency management
- **Custom BitTorrent tracker** implementation
## Key Directory Structure
- `/src/` - Modern PHP classes (PSR-4 autoloaded as `TorrentPier\`)
- `/library/` - Core application logic and legacy code
- `/admin/` - Administrative interface
- `/bt/` - BitTorrent tracker functionality (announce.php, scrape.php)
- `/styles/` - Templates, CSS, JS, images
- `/internal_data/` - Cache, logs, compiled templates
- `/install/` - Installation scripts and SQL schema
## Entry Points & Key Files
- `index.php` - Main forum homepage
- `tracker.php` - Torrent search/browse interface
- `bt/announce.php` - BitTorrent announce endpoint
- `bt/scrape.php` - BitTorrent scrape endpoint
- `admin/index.php` - Administrative panel
- `cron.php` - Background task runner (CLI only)
- `install.php` - Installation script (CLI only)
## Development Commands
### Installation & Setup
```bash
# Automated installation (CLI)
php install.php
# Install dependencies
composer install
# Update dependencies
composer update
```
### Maintenance & Operations
```bash
# Run background maintenance tasks
php cron.php
```
### Code Quality
The project uses **StyleCI** with PSR-2 preset for code style enforcement. StyleCI configuration is in `.styleci.yml` targeting `src/` directory.
## Modern Architecture Components
### Database Layer (`/src/Database/`)
- **Nette Database** with full old SqlDb backward compatibility
- Singleton pattern accessible via `DB()` function
- Support for multiple database connections and debug functionality
- Migration path to ORM-style Explorer queries
### Cache System (`/src/Cache/`)
- **Unified caching** using Nette Caching internally
- 100% backward compatibility with existing `CACHE()` and $datastore calls
- Supports file, SQLite, memory, and Memcached storage
- Advanced features: memoization, cache dependencies
### Configuration Management
- Environment-based config with `.env` files
- Singleton `Config` class accessible via `config()` function
- Local overrides supported via `library/config.local.php`
## Configuration Files
- `.env` - Environment variables (copy from `.env.example`)
- `library/config.php` - Main application configuration
- `library/config.local.php` - Local configuration overrides
- `composer.json` - Dependencies and PSR-4 autoloading
## Development Workflow
### CI/CD Pipeline
- **GitHub Actions** for automated testing and deployment
- **StyleCI** for code style enforcement
- **Dependabot** for dependency updates
- **FTP deployment** to demo environment
### Installation Methods
1. **Automated**: `php install.php` (recommended)
2. **Composer**: `composer create-project torrentpier/torrentpier`
3. **Manual**: Git clone + `composer install` + database setup
## Database & Schema
- MySQL schema: `/install/sql/mysql.sql`
- UTF-8 (utf8mb4) character set required
- Multiple database alias support for different components
## Legacy Compatibility Strategy
The codebase maintains 100% backward compatibility while introducing modern alternatives:
- **Database layer**: Existing old SqlDb calls work while new code can use Nette Database
- **Cache system**: All existing `CACHE()` and $datastore calls preserved while adding modern features
- **Configuration**: Legacy config access maintained alongside new singleton pattern
This approach allows gradual modernization without breaking existing functionality - critical for a mature application with existing deployments.
## Security & Performance
- **Environment-based secrets** management via `.env`
- **CDN/proxy support** (Cloudflare, Fastly)
- **Input sanitization** and CSRF protection
- **Advanced caching** with multiple storage backends
- **Rate limiting** and IP-based restrictions
## BitTorrent Tracker Features
- **BitTorrent v1 & v2** support
- **TorrServer integration** capability
- **Client ban system** for problematic torrent clients
- **Scrape support** for tracker statistics
When working with this codebase, prioritize understanding the legacy compatibility approach and modern architecture patterns. Always test both legacy and modern code paths when making changes to core systems.

View file

@ -4,6 +4,7 @@ This guide helps you upgrade your TorrentPier installation to the latest version
## 📖 Table of Contents
- [Database Migration System](#database-migration-system)
- [Database Layer Migration](#database-layer-migration)
- [Unified Cache System Migration](#unified-cache-system-migration)
- [Configuration System Migration](#configuration-system-migration)
@ -14,6 +15,357 @@ This guide helps you upgrade your TorrentPier installation to the latest version
- [Breaking Changes](#breaking-changes)
- [Best Practices](#best-practices)
## 🗄️ Database Migration System
TorrentPier now includes a modern database migration system using **Phinx** (from CakePHP), replacing the legacy direct SQL import approach. This provides version-controlled database schema management with rollback capabilities.
### Key Benefits
- **Version Control**: Database schema changes are tracked in code
- **Environment Consistency**: Same database structure across development, staging, and production
- **Safe Rollbacks**: Ability to safely revert schema changes
- **Team Collaboration**: No more merge conflicts on database changes
- **Automated Deployments**: Database updates as part of deployment process
### Migration Architecture
#### Engine Strategy
- **InnoDB**: Used for data integrity (users, posts, configuration, attachments)
- **MyISAM**: Used for performance-critical tracker tables (`bb_bt_tracker`, snapshots)
- **Expendable Tables**: Buffer and snapshot tables (`*_snap`, `buf_*`) can be dropped/recreated
#### Directory Structure
```
/migrations/
├── 20250619000001_initial_schema.php # Complete database schema
├── 20250619000002_seed_initial_data.php # Essential data seeding
└── future_migrations... # Your custom migrations
/phinx.php # Migration configuration
```
### For New Installations
New installations automatically use migrations instead of the legacy SQL dump:
```bash
# Fresh installation now uses migrations
php install.php
```
The installer will:
1. Set up environment configuration
2. Create the database
3. Run all migrations automatically
4. Seed initial data (admin user, configuration, etc.)
### For Existing Installations
Existing installations continue to work without changes. The migration system is designed for new installations and development workflows.
**Important**: Existing installations should **not** attempt to migrate to the new system without proper backup and testing procedures.
### Developer Workflow
#### Creating Migrations
```bash
# Create a new migration
php vendor/bin/phinx create AddNewFeatureTable
# Edit the generated migration file
# /migrations/YYYYMMDDHHMMSS_add_new_feature_table.php
```
#### Running Migrations
```bash
# Run all pending migrations
php vendor/bin/phinx migrate
# Check migration status
php vendor/bin/phinx status
# Rollback last migration
php vendor/bin/phinx rollback
```
#### Migration Template
```php
<?php
use Phinx\Migration\AbstractMigration;
class AddNewFeatureTable extends AbstractMigration
{
public function change()
{
// InnoDB for data integrity
$table = $this->table('bb_new_feature', [
'engine' => 'InnoDB',
'collation' => 'utf8mb4_unicode_ci'
]);
$table->addColumn('name', 'string', ['limit' => 100])
->addColumn('created_at', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
->addIndex('name')
->create();
}
// Optional: explicit up/down methods for complex operations
public function up()
{
// Complex data migration logic
}
public function down()
{
// Rollback logic
}
}
```
#### Engine Guidelines
```php
// Use InnoDB for data integrity
$table = $this->table('bb_user_posts', [
'engine' => 'InnoDB',
'collation' => 'utf8mb4_unicode_ci'
]);
// Use MyISAM for high-performance tracker tables
$table = $this->table('bb_bt_peer_stats', [
'engine' => 'MyISAM',
'collation' => 'utf8mb4_unicode_ci'
]);
// Expendable tables can be dropped without backup
public function up() {
$this->execute('DROP TABLE IF EXISTS buf_temp_data');
// Recreate with new structure
}
```
### Admin Panel Integration
The admin panel includes a read-only migration status page at `/admin/admin_migrations.php`:
- **Current migration version**
- **Applied migrations history**
- **Pending migrations list**
- **Database statistics**
- **Clear instructions for CLI operations**
**Important**: The admin panel is **read-only** for security. All migration operations must be performed via CLI.
### Complex Migration Handling
For complex data transformations, create external scripts:
```php
// migrations/YYYYMMDDHHMMSS_complex_data_migration.php
class ComplexDataMigration extends AbstractMigration
{
public function up()
{
$this->output->writeln('Running complex data migration...');
// Call external script for complex operations
$result = shell_exec('php ' . __DIR__ . '/../scripts/migrate_torrent_data.php');
$this->output->writeln($result);
if (strpos($result, 'ERROR') !== false) {
throw new Exception('Complex migration failed');
}
}
}
```
### Best Practices
#### Migration Development
```bash
# 1. Create migration
php vendor/bin/phinx create MyFeature
# 2. Edit migration file
# 3. Test locally
php vendor/bin/phinx migrate -e development
# 4. Test rollback
php vendor/bin/phinx rollback -e development
# 5. Commit to version control
git add migrations/
git commit -m "Add MyFeature migration"
```
#### Production Deployment
```bash
# Always backup database first
mysqldump tracker_db > backup_$(date +%Y%m%d_%H%M%S).sql
# Run migrations
php vendor/bin/phinx migrate -e production
# Verify application functionality
# Monitor error logs
```
#### Team Collaboration
- **Never modify existing migrations** that have been deployed
- **Always create new migrations** for schema changes
- **Test migrations on production-like data** before deployment
- **Coordinate with team** before major schema changes
### Configuration
The migration system uses your existing `.env` configuration:
```php
// phinx.php automatically reads from .env
'production' => [
'adapter' => 'mysql',
'host' => env('DB_HOST', 'localhost'),
'port' => (int) env('DB_PORT', 3306),
'name' => env('DB_DATABASE'),
'user' => env('DB_USERNAME'),
'pass' => env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci'
]
```
### Troubleshooting
#### Common Issues
```bash
# Migration table doesn't exist
php vendor/bin/phinx init # Re-run if needed
# Migration fails mid-way
php vendor/bin/phinx rollback # Rollback to previous state
# Check what would be applied
php vendor/bin/phinx status # See pending migrations
```
#### Migration Recovery
```bash
# If migration fails, check status first
php vendor/bin/phinx status
# Rollback to known good state
php vendor/bin/phinx rollback -t 20250619000002
# Fix the migration code and re-run
php vendor/bin/phinx migrate
```
### Legacy SQL Import Removal
The legacy `install/sql/mysql.sql` approach has been replaced by migrations:
- ✅ **New installations**: Use migrations automatically
- ✅ **Development workflow**: Create migrations for all schema changes
- ✅ **Version control**: All schema changes tracked in Git
- ❌ **Direct SQL imports**: No longer used for new installations
### Security Considerations
- **CLI-only execution**: Migrations run via command line only
- **Read-only admin interface**: Web interface shows status only
- **Backup requirements**: Always backup before production migrations
- **Access control**: Restrict migration command access to authorized personnel
### Migration Setup for Existing Installations
If you have an **existing TorrentPier installation** and want to adopt the migration system, you need to mark the initial migrations as already applied to avoid recreating your existing database schema.
#### Detection: Do You Need This?
You need migration setup if:
- ✅ You have an existing TorrentPier installation with data
- ✅ Your database already has tables like `bb_users`, `bb_forums`, etc.
- ✅ The admin migration panel shows "Migration System: ✗ Not Initialized"
#### Step-by-Step Setup Process
**1. Backup Your Database**
```bash
mysqldump -u username -p database_name > backup_$(date +%Y%m%d_%H%M%S).sql
```
**2. Initialize Migration Table**
```bash
# This creates the bb_migrations table without running any migrations
php vendor/bin/phinx init
```
**3. Mark Initial Migrations as Applied (Fake Run)**
```bash
# Mark the schema migration as applied without running it
php vendor/bin/phinx migrate --fake --target=20250619000001
# Mark the data seeding migration as applied without running it
php vendor/bin/phinx migrate --fake --target=20250619000002
```
**4. Verify Setup**
```bash
# Check migration status
php vendor/bin/phinx status
```
You should see both initial migrations marked as "up" (applied).
#### Alternative: Manual SQL Method
If you prefer manual control, you can directly insert migration records:
```sql
-- Create migration table (if phinx init didn't work)
CREATE TABLE IF NOT EXISTS bb_migrations (
version bigint(20) NOT NULL,
migration_name varchar(100) DEFAULT NULL,
start_time timestamp NULL DEFAULT NULL,
end_time timestamp NULL DEFAULT NULL,
breakpoint tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (version)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Mark initial migrations as applied
INSERT INTO bb_migrations (version, migration_name, start_time, end_time, breakpoint)
VALUES
('20250619000001', 'InitialSchema', NOW(), NOW(), 0),
('20250619000002', 'SeedInitialData', NOW(), NOW(), 0);
```
#### Post-Setup Workflow
After setup, your existing installation will work exactly like a fresh installation:
```bash
# Create new migrations
php vendor/bin/phinx create AddNewFeature
# Run new migrations
php vendor/bin/phinx migrate
# Check status
php vendor/bin/phinx status
```
#### Troubleshooting
**Migration table already exists:**
- Check if you've already set up migrations: `php vendor/bin/phinx status`
- If it shows errors, you may need to recreate: `DROP TABLE bb_migrations;` then restart
**"Nothing to migrate" message:**
- This is normal after fake runs - it means setup was successful
- New migrations will appear when you create them
**Admin panel shows "Needs Setup":**
- Follow the setup process above
- Refresh the admin panel after completion
## 🗄️ Database Layer Migration
TorrentPier has completely replaced its legacy database layer (SqlDb/Dbs) with a modern implementation using Nette Database while maintaining 100% backward compatibility.

View file

@ -0,0 +1,79 @@
<?php
/**
* TorrentPier Bull-powered BitTorrent tracker engine
*
* @copyright Copyright (c) 2005-2025 TorrentPier (https://torrentpier.com)
* @link https://github.com/torrentpier/torrentpier for the canonical source repository
* @license https://github.com/torrentpier/torrentpier/blob/master/LICENSE MIT License
*/
if (!empty($setmodules)) {
if (IS_SUPER_ADMIN) {
$module['GENERAL']['MIGRATIONS_STATUS'] = basename(__FILE__);
}
return;
}
require __DIR__ . '/pagestart.php';
if (!IS_SUPER_ADMIN) {
bb_die(__('ONLY_FOR_SUPER_ADMIN'));
}
use TorrentPier\Database\MigrationStatus;
// Initialize migration status
$migrationStatus = new MigrationStatus();
$status = $migrationStatus->getMigrationStatus();
$schemaInfo = $migrationStatus->getSchemaInfo();
// Template variables
$template->assign_vars([
'PAGE_TITLE' => __('MIGRATIONS_STATUS'),
'CURRENT_TIME' => date('Y-m-d H:i:s'),
// Migration status individual fields
'MIGRATION_TABLE_EXISTS' => $status['table_exists'],
'MIGRATION_CURRENT_VERSION' => $status['current_version'],
'MIGRATION_APPLIED_COUNT' => count($status['applied_migrations']),
'MIGRATION_PENDING_COUNT' => count($status['pending_migrations']),
// Setup status fields
'SETUP_REQUIRES_SETUP' => $status['requires_setup'] ?? false,
'SETUP_TYPE' => $status['setup_status']['type'] ?? __('UNKNOWN'),
'SETUP_MESSAGE' => $status['setup_status']['message'] ?? '',
'SETUP_ACTION_REQUIRED' => $status['setup_status']['action_required'] ?? false,
'SETUP_INSTRUCTIONS' => $status['setup_status']['instructions'] ?? '',
// Schema info individual fields
'SCHEMA_DATABASE_NAME' => $schemaInfo['database_name'],
'SCHEMA_TABLE_COUNT' => $schemaInfo['table_count'],
'SCHEMA_SIZE_MB' => $schemaInfo['size_mb'],
]);
// Assign migration data for template
if (!empty($status['applied_migrations'])) {
foreach ($status['applied_migrations'] as $i => $migration) {
$template->assign_block_vars('applied_migrations', [
'VERSION' => $migration['version'],
'NAME' => $migration['migration_name'] ?? __('UNKNOWN'),
'START_TIME' => $migration['start_time'] ?? __('UNKNOWN'),
'END_TIME' => $migration['end_time'] ?? __('UNKNOWN'),
'ROW_CLASS' => ($i % 2) ? 'row1' : 'row2'
]);
}
}
if (!empty($status['pending_migrations'])) {
foreach ($status['pending_migrations'] as $i => $migration) {
$template->assign_block_vars('pending_migrations', [
'VERSION' => $migration['version'],
'NAME' => $migration['name'],
'FILENAME' => $migration['filename'],
'ROW_CLASS' => ($i % 2) ? 'row1' : 'row2'
]);
}
}
// Output template using standard admin pattern
print_page('admin_migrations.tpl', 'admin');

View file

@ -68,6 +68,7 @@
"nette/caching": "^3.3",
"nette/database": "^3.2",
"php-curl-class/php-curl-class": "^12.0.0",
"robmorgan/phinx": "^0.16.9",
"samdark/sitemap": "2.4.1",
"symfony/event-dispatcher": "^6.4",
"symfony/filesystem": "^6.4",

799
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "61d990417a4943d9986cef7fc3c0f382",
"content-hash": "1c6ed0e1507a53ce5784c929c38e84cf",
"packages": [
{
"name": "arokettu/bencode",
@ -544,6 +544,330 @@
},
"time": "2025-03-06T12:03:07+00:00"
},
{
"name": "cakephp/chronos",
"version": "3.1.0",
"source": {
"type": "git",
"url": "https://github.com/cakephp/chronos.git",
"reference": "786d69e1ee4b735765cbdb5521b9603e9b98d650"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/chronos/zipball/786d69e1ee4b735765cbdb5521b9603e9b98d650",
"reference": "786d69e1ee4b735765cbdb5521b9603e9b98d650",
"shasum": ""
},
"require": {
"php": ">=8.1",
"psr/clock": "^1.0"
},
"provide": {
"psr/clock-implementation": "1.0"
},
"require-dev": {
"cakephp/cakephp-codesniffer": "^5.0",
"phpunit/phpunit": "^10.1.0 || ^11.1.3"
},
"type": "library",
"autoload": {
"psr-4": {
"Cake\\Chronos\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Brian Nesbitt",
"email": "brian@nesbot.com",
"homepage": "http://nesbot.com"
},
{
"name": "The CakePHP Team",
"homepage": "https://cakephp.org"
}
],
"description": "A simple API extension for DateTime.",
"homepage": "https://cakephp.org",
"keywords": [
"date",
"datetime",
"time"
],
"support": {
"issues": "https://github.com/cakephp/chronos/issues",
"source": "https://github.com/cakephp/chronos"
},
"time": "2024-07-18T03:18:04+00:00"
},
{
"name": "cakephp/core",
"version": "5.2.4",
"source": {
"type": "git",
"url": "https://github.com/cakephp/core.git",
"reference": "a0a92ee7fbb7b7555dbf4ea7ff3fd4e779693da6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/core/zipball/a0a92ee7fbb7b7555dbf4ea7ff3fd4e779693da6",
"reference": "a0a92ee7fbb7b7555dbf4ea7ff3fd4e779693da6",
"shasum": ""
},
"require": {
"cakephp/utility": "5.2.*@dev",
"league/container": "^4.2",
"php": ">=8.1",
"psr/container": "^1.1 || ^2.0"
},
"provide": {
"psr/container-implementation": "^2.0"
},
"suggest": {
"cakephp/cache": "To use Configure::store() and restore().",
"cakephp/event": "To use PluginApplicationInterface or plugin applications.",
"league/container": "To use Container and ServiceProvider classes"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-5.x": "5.2.x-dev"
}
},
"autoload": {
"files": [
"functions.php"
],
"psr-4": {
"Cake\\Core\\": "."
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/core/graphs/contributors"
}
],
"description": "CakePHP Framework Core classes",
"homepage": "https://cakephp.org",
"keywords": [
"cakephp",
"core",
"framework"
],
"support": {
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"issues": "https://github.com/cakephp/cakephp/issues",
"source": "https://github.com/cakephp/core"
},
"time": "2025-04-19T12:34:03+00:00"
},
{
"name": "cakephp/database",
"version": "5.2.4",
"source": {
"type": "git",
"url": "https://github.com/cakephp/database.git",
"reference": "8c4eaecf6612274b445172b680dc47a2dad681a9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/database/zipball/8c4eaecf6612274b445172b680dc47a2dad681a9",
"reference": "8c4eaecf6612274b445172b680dc47a2dad681a9",
"shasum": ""
},
"require": {
"cakephp/chronos": "^3.1",
"cakephp/core": "5.2.*@dev",
"cakephp/datasource": "5.2.*@dev",
"php": ">=8.1",
"psr/log": "^3.0"
},
"require-dev": {
"cakephp/i18n": "5.2.*@dev",
"cakephp/log": "5.2.*@dev"
},
"suggest": {
"cakephp/i18n": "If you are using locale-aware datetime formats.",
"cakephp/log": "If you want to use query logging without providing a logger yourself."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-5.x": "5.2.x-dev"
}
},
"autoload": {
"psr-4": {
"Cake\\Database\\": "."
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/database/graphs/contributors"
}
],
"description": "Flexible and powerful Database abstraction library with a familiar PDO-like API",
"homepage": "https://cakephp.org",
"keywords": [
"abstraction",
"cakephp",
"database",
"database abstraction",
"pdo"
],
"support": {
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"issues": "https://github.com/cakephp/cakephp/issues",
"source": "https://github.com/cakephp/database"
},
"time": "2025-05-09T15:08:51+00:00"
},
{
"name": "cakephp/datasource",
"version": "5.2.4",
"source": {
"type": "git",
"url": "https://github.com/cakephp/datasource.git",
"reference": "f7dc4292bec0ec746db3200a5b18bb371d50dab3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/datasource/zipball/f7dc4292bec0ec746db3200a5b18bb371d50dab3",
"reference": "f7dc4292bec0ec746db3200a5b18bb371d50dab3",
"shasum": ""
},
"require": {
"cakephp/core": "5.2.*@dev",
"php": ">=8.1",
"psr/simple-cache": "^2.0 || ^3.0"
},
"require-dev": {
"cakephp/cache": "5.2.*@dev",
"cakephp/collection": "5.2.*@dev",
"cakephp/utility": "5.2.*@dev"
},
"suggest": {
"cakephp/cache": "If you decide to use Query caching.",
"cakephp/collection": "If you decide to use ResultSetInterface.",
"cakephp/utility": "If you decide to use EntityTrait."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-5.x": "5.2.x-dev"
}
},
"autoload": {
"psr-4": {
"Cake\\Datasource\\": "."
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/datasource/graphs/contributors"
}
],
"description": "Provides connection managing and traits for Entities and Queries that can be reused for different datastores",
"homepage": "https://cakephp.org",
"keywords": [
"cakephp",
"connection management",
"datasource",
"entity",
"query"
],
"support": {
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"issues": "https://github.com/cakephp/cakephp/issues",
"source": "https://github.com/cakephp/datasource"
},
"time": "2025-04-26T23:00:26+00:00"
},
{
"name": "cakephp/utility",
"version": "5.2.4",
"source": {
"type": "git",
"url": "https://github.com/cakephp/utility.git",
"reference": "76dcd5c20e46aaf5bfdf9ad51e9f5313abffe104"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/utility/zipball/76dcd5c20e46aaf5bfdf9ad51e9f5313abffe104",
"reference": "76dcd5c20e46aaf5bfdf9ad51e9f5313abffe104",
"shasum": ""
},
"require": {
"cakephp/core": "5.2.*@dev",
"php": ">=8.1"
},
"suggest": {
"ext-intl": "To use Text::transliterate() or Text::slug()",
"lib-ICU": "To use Text::transliterate() or Text::slug()"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-5.x": "5.2.x-dev"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Cake\\Utility\\": "."
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/utility/graphs/contributors"
}
],
"description": "CakePHP Utility classes such as Inflector, String, Hash, and Security",
"homepage": "https://cakephp.org",
"keywords": [
"cakephp",
"hash",
"inflector",
"security",
"string",
"utility"
],
"support": {
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"issues": "https://github.com/cakephp/cakephp/issues",
"source": "https://github.com/cakephp/utility"
},
"time": "2025-04-19T12:34:03+00:00"
},
{
"name": "claviska/simpleimage",
"version": "4.2.1",
@ -1618,6 +1942,88 @@
},
"time": "2022-09-24T15:57:16+00:00"
},
{
"name": "league/container",
"version": "4.2.5",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/container.git",
"reference": "d3cebb0ff4685ff61c749e54b27db49319e2ec00"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/container/zipball/d3cebb0ff4685ff61c749e54b27db49319e2ec00",
"reference": "d3cebb0ff4685ff61c749e54b27db49319e2ec00",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"psr/container": "^1.1 || ^2.0"
},
"provide": {
"psr/container-implementation": "^1.0"
},
"replace": {
"orno/di": "~2.0"
},
"require-dev": {
"nette/php-generator": "^3.4",
"nikic/php-parser": "^4.10",
"phpstan/phpstan": "^0.12.47",
"phpunit/phpunit": "^8.5.17",
"roave/security-advisories": "dev-latest",
"scrutinizer/ocular": "^1.8",
"squizlabs/php_codesniffer": "^3.6"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-1.x": "1.x-dev",
"dev-2.x": "2.x-dev",
"dev-3.x": "3.x-dev",
"dev-4.x": "4.x-dev",
"dev-master": "4.x-dev"
}
},
"autoload": {
"psr-4": {
"League\\Container\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Phil Bennett",
"email": "mail@philbennett.co.uk",
"role": "Developer"
}
],
"description": "A fast and intuitive dependency injection container.",
"homepage": "https://github.com/thephpleague/container",
"keywords": [
"container",
"dependency",
"di",
"injection",
"league",
"provider",
"service"
],
"support": {
"issues": "https://github.com/thephpleague/container/issues",
"source": "https://github.com/thephpleague/container/tree/4.2.5"
},
"funding": [
{
"url": "https://github.com/philipobenito",
"type": "github"
}
],
"time": "2025-05-20T12:55:37+00:00"
},
{
"name": "league/flysystem",
"version": "3.29.1",
@ -2559,6 +2965,54 @@
},
"time": "2021-02-03T23:26:27+00:00"
},
{
"name": "psr/clock",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/clock.git",
"reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d",
"reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d",
"shasum": ""
},
"require": {
"php": "^7.0 || ^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Psr\\Clock\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for reading the clock.",
"homepage": "https://github.com/php-fig/clock",
"keywords": [
"clock",
"now",
"psr",
"psr-20",
"time"
],
"support": {
"issues": "https://github.com/php-fig/clock/issues",
"source": "https://github.com/php-fig/clock/tree/1.0.0"
},
"time": "2022-11-25T14:36:26+00:00"
},
{
"name": "psr/container",
"version": "2.0.2",
@ -2967,6 +3421,93 @@
},
"time": "2019-03-08T08:55:37+00:00"
},
{
"name": "robmorgan/phinx",
"version": "0.16.9",
"source": {
"type": "git",
"url": "https://github.com/cakephp/phinx.git",
"reference": "524ebdeb0e1838a845d752a3418726b38cd1e654"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/cakephp/phinx/zipball/524ebdeb0e1838a845d752a3418726b38cd1e654",
"reference": "524ebdeb0e1838a845d752a3418726b38cd1e654",
"shasum": ""
},
"require": {
"cakephp/database": "^5.0.2",
"composer-runtime-api": "^2.0",
"php-64bit": ">=8.1",
"psr/container": "^1.1|^2.0",
"symfony/config": "^4.0|^5.0|^6.0|^7.0",
"symfony/console": "^6.0|^7.0"
},
"require-dev": {
"cakephp/cakephp-codesniffer": "^5.0",
"cakephp/i18n": "^5.0",
"ext-json": "*",
"ext-pdo": "*",
"phpunit/phpunit": "^9.5.19",
"symfony/yaml": "^4.0|^5.0|^6.0|^7.0"
},
"suggest": {
"ext-json": "Install if using JSON configuration format",
"ext-pdo": "PDO extension is needed",
"symfony/yaml": "Install if using YAML configuration format"
},
"bin": [
"bin/phinx"
],
"type": "library",
"autoload": {
"psr-4": {
"Phinx\\": "src/Phinx/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Rob Morgan",
"email": "robbym@gmail.com",
"homepage": "https://robmorgan.id.au",
"role": "Lead Developer"
},
{
"name": "Woody Gilk",
"email": "woody.gilk@gmail.com",
"homepage": "https://shadowhand.me",
"role": "Developer"
},
{
"name": "Richard Quadling",
"email": "rquadling@gmail.com",
"role": "Developer"
},
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/phinx/graphs/contributors",
"role": "Developer"
}
],
"description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.",
"homepage": "https://phinx.org",
"keywords": [
"database",
"database migrations",
"db",
"migrations",
"phinx"
],
"support": {
"issues": "https://github.com/cakephp/phinx/issues",
"source": "https://github.com/cakephp/phinx/tree/0.16.9"
},
"time": "2025-05-25T16:07:44+00:00"
},
{
"name": "samdark/sitemap",
"version": "2.4.1",
@ -3026,6 +3567,175 @@
],
"time": "2023-11-01T08:41:34+00:00"
},
{
"name": "symfony/config",
"version": "v7.0.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
"reference": "f8a8fb0c2d0a188a00a2dd5af8a4eb070641ec60"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/f8a8fb0c2d0a188a00a2dd5af8a4eb070641ec60",
"reference": "f8a8fb0c2d0a188a00a2dd5af8a4eb070641ec60",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/filesystem": "^6.4|^7.0",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"symfony/finder": "<6.4",
"symfony/service-contracts": "<2.5"
},
"require-dev": {
"symfony/event-dispatcher": "^6.4|^7.0",
"symfony/finder": "^6.4|^7.0",
"symfony/messenger": "^6.4|^7.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/yaml": "^6.4|^7.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Config\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/config/tree/v7.0.8"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-05-31T14:55:39+00:00"
},
{
"name": "symfony/console",
"version": "v7.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/66c1440edf6f339fd82ed6c7caa76cb006211b44",
"reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/string": "^7.2"
},
"conflict": {
"symfony/dependency-injection": "<6.4",
"symfony/dotenv": "<6.4",
"symfony/event-dispatcher": "<6.4",
"symfony/lock": "<6.4",
"symfony/process": "<6.4"
},
"provide": {
"psr/log-implementation": "1.0|2.0|3.0"
},
"require-dev": {
"psr/log": "^1|^2|^3",
"symfony/config": "^6.4|^7.0",
"symfony/dependency-injection": "^6.4|^7.0",
"symfony/event-dispatcher": "^6.4|^7.0",
"symfony/http-foundation": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
"symfony/lock": "^6.4|^7.0",
"symfony/messenger": "^6.4|^7.0",
"symfony/process": "^6.4|^7.0",
"symfony/stopwatch": "^6.4|^7.0",
"symfony/var-dumper": "^6.4|^7.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Console\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Eases the creation of beautiful and testable command line interfaces",
"homepage": "https://symfony.com",
"keywords": [
"cli",
"command-line",
"console",
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.3.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-05-24T10:34:04+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.6.0",
@ -3744,6 +4454,93 @@
],
"time": "2025-04-25T09:37:31+00:00"
},
{
"name": "symfony/string",
"version": "v7.3.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125",
"reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-intl-grapheme": "~1.0",
"symfony/polyfill-intl-normalizer": "~1.0",
"symfony/polyfill-mbstring": "~1.0"
},
"conflict": {
"symfony/translation-contracts": "<2.5"
},
"require-dev": {
"symfony/emoji": "^7.1",
"symfony/error-handler": "^6.4|^7.0",
"symfony/http-client": "^6.4|^7.0",
"symfony/intl": "^6.4|^7.0",
"symfony/translation-contracts": "^2.5|^3.0",
"symfony/var-exporter": "^6.4|^7.0"
},
"type": "library",
"autoload": {
"files": [
"Resources/functions.php"
],
"psr-4": {
"Symfony\\Component\\String\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
"homepage": "https://symfony.com",
"keywords": [
"grapheme",
"i18n",
"string",
"unicode",
"utf-8",
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v7.3.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-04-20T20:19:01+00:00"
},
{
"name": "vlucas/phpdotenv",
"version": "v5.6.2",

View file

@ -265,35 +265,26 @@ if (!empty($DB_HOST) && !empty($DB_DATABASE) && !empty($DB_USERNAME)) {
}
$conn->select_db($DB_DATABASE);
// Checking SQL dump
$dumpPath = BB_ROOT . 'install/sql/mysql.sql';
if (is_file($dumpPath) && is_readable($dumpPath)) {
out('- SQL dump file found and readable!', 'success');
} else {
out('- SQL dump file not found / not readable', 'error');
exit;
}
// Inserting SQL dump
out('- Start importing SQL dump...', 'info');
$tempLine = '';
foreach (file($dumpPath) as $line) {
if (str_starts_with($line, '--') || $line == '') {
continue;
}
$tempLine .= $line;
if (str_ends_with(trim($line), ';')) {
if (!$conn->query($tempLine)) {
out("- Error performing query: $tempLine", 'error');
exit;
}
$tempLine = '';
}
}
// Close database connection - migrations will handle their own connections
$conn->close();
out("- Importing SQL dump completed!\n", 'success');
// Run database migrations
out('- Setting up database using migrations...', 'info');
// Check if phinx.php exists
if (!is_file(BB_ROOT . 'phinx.php')) {
out('- Migration configuration (phinx.php) not found', 'error');
exit;
}
// Run migrations
$migrationResult = runProcess('php vendor/bin/phinx migrate --configuration=' . BB_ROOT . 'phinx.php');
if ($migrationResult !== 0) {
out('- Database migration failed', 'error');
exit;
}
out("- Database setup completed!\n", 'success');
// Autofill host in robots.txt
$robots_txt_file = BB_ROOT . 'robots.txt';

View file

@ -83,6 +83,9 @@ define('CRON_RUNNING', TRIGGERS_DIR . '/cron_running');
define('GZIP_OUTPUT_ALLOWED', extension_loaded('zlib') && !ini_get('zlib.output_compression'));
define('UA_GZIP_SUPPORTED', isset($_SERVER['HTTP_ACCEPT_ENCODING']) && str_contains($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip'));
// Migrations table
define('BB_MIGRATIONS', 'bb_migrations');
// Tracker shared constants
define('BB_BT_TORRENTS', 'bb_bt_torrents');
define('BB_BT_TRACKER', 'bb_bt_tracker');

View file

@ -1946,6 +1946,17 @@ $lang['TRACKER_CONFIG'] = 'Tracker settings';
$lang['RELEASE_TEMPLATES'] = 'Release Templates';
$lang['ACTIONS_LOG'] = 'Report on action';
// Migrations
$lang['MIGRATIONS_STATUS'] = 'Database Migration Status';
$lang['MIGRATIONS_DATABASE_NAME'] = 'Database Name';
$lang['MIGRATIONS_DATABASE_TOTAL'] = 'Total Tables';
$lang['MIGRATIONS_DATABASE_SIZE'] = 'Database Size';
$lang['MIGRATIONS_DATABASE_INFO'] = 'Database Information';
$lang['MIGRATIONS_SYSTEM'] = 'Migration System';
$lang['MIGRATIONS_NEEDS_SETUP'] = 'Needs Setup';
$lang['MIGRATIONS_ACTIVE'] = 'Active';
$lang['MIGRATIONS_NOT_INITIALIZED'] = 'Not Initialized';
// Index
$lang['MAIN_INDEX'] = 'Forum Index';
$lang['FORUM_STATS'] = 'Forum Statistics';

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,668 @@
<?php
/**
* TorrentPier Initial Data Seeding Migration
* Seeds essential data for fresh installations
*/
use Phinx\Migration\AbstractMigration;
class SeedInitialData extends AbstractMigration
{
public function up()
{
// Seed all essential data
$this->seedCategories();
$this->seedForums();
$this->seedUsers();
$this->seedBtUsers();
$this->seedConfiguration();
$this->seedCronJobs();
$this->seedExtensions();
$this->seedSmilies();
$this->seedRanks();
$this->seedQuotaLimits();
$this->seedDisallowedUsernames();
$this->seedAttachmentConfig();
$this->seedTopicsAndPosts();
$this->seedTopicWatch();
}
private function seedCategories()
{
$this->table('bb_categories')->insert([
[
'cat_id' => 1,
'cat_title' => 'Your first category',
'cat_order' => 10
]
])->saveData();
}
private function seedForums()
{
$this->table('bb_forums')->insert([
[
'forum_id' => 1,
'cat_id' => 1,
'forum_name' => 'Your first forum',
'forum_desc' => 'Description of the forum.',
'forum_status' => 0,
'forum_order' => 10,
'forum_posts' => 1,
'forum_topics' => 1,
'forum_last_post_id' => 1,
'forum_tpl_id' => 0,
'prune_days' => 0,
'auth_view' => 0,
'auth_read' => 0,
'auth_post' => 1,
'auth_reply' => 1,
'auth_edit' => 1,
'auth_delete' => 1,
'auth_sticky' => 3,
'auth_announce' => 3,
'auth_vote' => 1,
'auth_pollcreate' => 1,
'auth_attachments' => 1,
'auth_download' => 1,
'allow_reg_tracker' => 0,
'allow_porno_topic' => 0,
'self_moderated' => 0,
'forum_parent' => 0,
'show_on_index' => 1,
'forum_display_sort' => 0,
'forum_display_order' => 0
]
])->saveData();
}
private function seedUsers()
{
$this->table('bb_users')->insert([
[
'user_id' => -1,
'user_active' => 0,
'username' => 'Guest',
'user_password' => '$2y$10$sfZSmqPio8mxxFQLRRXaFuVMkFKZARRz/RzqddfYByN3M53.CEe.O',
'user_session_time' => 0,
'user_lastvisit' => 0,
'user_last_ip' => '0',
'user_regdate' => time(),
'user_reg_ip' => '0',
'user_level' => 0,
'user_posts' => 0,
'user_timezone' => 0.00,
'user_lang' => 'en',
'user_new_privmsg' => 0,
'user_unread_privmsg' => 0,
'user_last_privmsg' => 0,
'user_opt' => 0,
'user_rank' => 0,
'avatar_ext_id' => 0,
'user_gender' => 0,
'user_birthday' => '1900-01-01',
'user_email' => '',
'user_skype' => '',
'user_twitter' => '',
'user_icq' => '',
'user_website' => '',
'user_from' => '',
'user_sig' => '',
'user_occ' => '',
'user_interests' => '',
'user_actkey' => '',
'user_newpasswd' => '',
'autologin_id' => '',
'user_newest_pm_id' => 0,
'user_points' => 0.00,
'tpl_name' => 'default'
],
[
'user_id' => -746,
'user_active' => 0,
'username' => 'bot',
'user_password' => '$2y$10$sfZSmqPio8mxxFQLRRXaFuVMkFKZARRz/RzqddfYByN3M53.CEe.O',
'user_session_time' => 0,
'user_lastvisit' => 0,
'user_last_ip' => '0',
'user_regdate' => time(),
'user_reg_ip' => '0',
'user_level' => 0,
'user_posts' => 0,
'user_timezone' => 0.00,
'user_lang' => 'en',
'user_new_privmsg' => 0,
'user_unread_privmsg' => 0,
'user_last_privmsg' => 0,
'user_opt' => 144,
'user_rank' => 0,
'avatar_ext_id' => 0,
'user_gender' => 0,
'user_birthday' => '1900-01-01',
'user_email' => 'bot@torrentpier.com',
'user_skype' => '',
'user_twitter' => '',
'user_icq' => '',
'user_website' => '',
'user_from' => '',
'user_sig' => '',
'user_occ' => '',
'user_interests' => '',
'user_actkey' => '',
'user_newpasswd' => '',
'autologin_id' => '',
'user_newest_pm_id' => 0,
'user_points' => 0.00,
'tpl_name' => 'default'
],
[
'user_id' => 2,
'user_active' => 1,
'username' => 'admin',
'user_password' => '$2y$10$QeekUGqdfMO0yp7AT7la8OhgbiNBoJ627BO38MdS1h5kY7oX6UUKu',
'user_session_time' => 0,
'user_lastvisit' => 0,
'user_last_ip' => '0',
'user_regdate' => time(),
'user_reg_ip' => '0',
'user_level' => 1,
'user_posts' => 1,
'user_timezone' => 0.00,
'user_lang' => 'en',
'user_new_privmsg' => 0,
'user_unread_privmsg' => 0,
'user_last_privmsg' => 0,
'user_opt' => 304,
'user_rank' => 1,
'avatar_ext_id' => 0,
'user_gender' => 0,
'user_birthday' => '1900-01-01',
'user_email' => 'admin@torrentpier.com',
'user_skype' => '',
'user_twitter' => '',
'user_icq' => '',
'user_website' => '',
'user_from' => '',
'user_sig' => '',
'user_occ' => '',
'user_interests' => '',
'user_actkey' => '',
'user_newpasswd' => '',
'autologin_id' => '',
'user_newest_pm_id' => 0,
'user_points' => 0.00,
'tpl_name' => 'default'
]
])->saveData();
}
private function seedBtUsers()
{
$this->table('bb_bt_users')->insert([
[
'user_id' => -1,
'auth_key' => substr(md5(rand()), 0, 20)
],
[
'user_id' => -746,
'auth_key' => substr(md5(rand()), 0, 20)
],
[
'user_id' => 2,
'auth_key' => substr(md5(rand()), 0, 20)
]
])->saveData();
}
private function seedConfiguration()
{
$currentTime = time();
$configs = [
['config_name' => 'allow_autologin', 'config_value' => '1'],
['config_name' => 'allow_bbcode', 'config_value' => '1'],
['config_name' => 'allow_namechange', 'config_value' => '0'],
['config_name' => 'allow_sig', 'config_value' => '1'],
['config_name' => 'allow_smilies', 'config_value' => '1'],
['config_name' => 'board_disable', 'config_value' => '0'],
['config_name' => 'board_startdate', 'config_value' => (string)$currentTime],
['config_name' => 'board_timezone', 'config_value' => '0'],
['config_name' => 'bonus_upload', 'config_value' => ''],
['config_name' => 'bonus_upload_price', 'config_value' => ''],
['config_name' => 'birthday_enabled', 'config_value' => '1'],
['config_name' => 'birthday_max_age', 'config_value' => '99'],
['config_name' => 'birthday_min_age', 'config_value' => '10'],
['config_name' => 'birthday_check_day', 'config_value' => '7'],
['config_name' => 'bt_add_auth_key', 'config_value' => '1'],
['config_name' => 'bt_allow_spmode_change', 'config_value' => '1'],
['config_name' => 'bt_announce_url', 'config_value' => 'https://localhost/bt/announce.php'],
['config_name' => 'bt_disable_dht', 'config_value' => '0'],
['config_name' => 'bt_check_announce_url', 'config_value' => '0'],
['config_name' => 'bt_del_addit_ann_urls', 'config_value' => '1'],
['config_name' => 'bt_dl_list_only_1st_page', 'config_value' => '1'],
['config_name' => 'bt_dl_list_only_count', 'config_value' => '1'],
['config_name' => 'bt_newtopic_auto_reg', 'config_value' => '1'],
['config_name' => 'bt_replace_ann_url', 'config_value' => '1'],
['config_name' => 'bt_search_bool_mode', 'config_value' => '1'],
['config_name' => 'bt_set_dltype_on_tor_reg', 'config_value' => '1'],
['config_name' => 'bt_show_dl_but_cancel', 'config_value' => '1'],
['config_name' => 'bt_show_dl_but_compl', 'config_value' => '1'],
['config_name' => 'bt_show_dl_but_down', 'config_value' => '0'],
['config_name' => 'bt_show_dl_but_will', 'config_value' => '1'],
['config_name' => 'bt_show_dl_list', 'config_value' => '0'],
['config_name' => 'bt_show_dl_list_buttons', 'config_value' => '1'],
['config_name' => 'bt_show_dl_stat_on_index', 'config_value' => '1'],
['config_name' => 'bt_show_ip_only_moder', 'config_value' => '1'],
['config_name' => 'bt_show_peers', 'config_value' => '1'],
['config_name' => 'bt_show_peers_mode', 'config_value' => '1'],
['config_name' => 'bt_show_port_only_moder', 'config_value' => '1'],
['config_name' => 'bt_tor_browse_only_reg', 'config_value' => '0'],
['config_name' => 'bt_unset_dltype_on_tor_unreg', 'config_value' => '1'],
['config_name' => 'cron_last_check', 'config_value' => '0'],
['config_name' => 'default_dateformat', 'config_value' => 'Y-m-d H:i'],
['config_name' => 'default_lang', 'config_value' => 'en'],
['config_name' => 'flood_interval', 'config_value' => '15'],
['config_name' => 'hot_threshold', 'config_value' => '300'],
['config_name' => 'login_reset_time', 'config_value' => '30'],
['config_name' => 'max_autologin_time', 'config_value' => '10'],
['config_name' => 'max_login_attempts', 'config_value' => '5'],
['config_name' => 'max_poll_options', 'config_value' => '6'],
['config_name' => 'max_sig_chars', 'config_value' => '255'],
['config_name' => 'posts_per_page', 'config_value' => '15'],
['config_name' => 'prune_enable', 'config_value' => '1'],
['config_name' => 'record_online_date', 'config_value' => (string)$currentTime],
['config_name' => 'record_online_users', 'config_value' => '0'],
['config_name' => 'seed_bonus_enabled', 'config_value' => '1'],
['config_name' => 'seed_bonus_release', 'config_value' => ''],
['config_name' => 'seed_bonus_points', 'config_value' => ''],
['config_name' => 'seed_bonus_tor_size', 'config_value' => '0'],
['config_name' => 'seed_bonus_user_regdate', 'config_value' => '0'],
['config_name' => 'site_desc', 'config_value' => 'Bull-powered BitTorrent tracker engine'],
['config_name' => 'sitemap_time', 'config_value' => ''],
['config_name' => 'sitename', 'config_value' => 'TorrentPier'],
['config_name' => 'smilies_path', 'config_value' => 'styles/images/smiles'],
['config_name' => 'static_sitemap', 'config_value' => ''],
['config_name' => 'topics_per_page', 'config_value' => '50'],
['config_name' => 'xs_use_cache', 'config_value' => '1'],
['config_name' => 'cron_check_interval', 'config_value' => '180'],
['config_name' => 'magnet_links_enabled', 'config_value' => '1'],
['config_name' => 'magnet_links_for_guests', 'config_value' => '0'],
['config_name' => 'gender', 'config_value' => '1'],
['config_name' => 'callseed', 'config_value' => '0'],
['config_name' => 'tor_stats', 'config_value' => '1'],
['config_name' => 'show_latest_news', 'config_value' => '1'],
['config_name' => 'max_news_title', 'config_value' => '50'],
['config_name' => 'latest_news_count', 'config_value' => '5'],
['config_name' => 'latest_news_forum_id', 'config_value' => '1'],
['config_name' => 'show_network_news', 'config_value' => '1'],
['config_name' => 'max_net_title', 'config_value' => '50'],
['config_name' => 'network_news_count', 'config_value' => '5'],
['config_name' => 'network_news_forum_id', 'config_value' => '2'],
['config_name' => 'whois_info', 'config_value' => 'https://whatismyipaddress.com/ip/'],
['config_name' => 'show_mod_index', 'config_value' => '0'],
['config_name' => 'premod', 'config_value' => '0'],
['config_name' => 'tor_comment', 'config_value' => '1'],
['config_name' => 'terms', 'config_value' => ''],
['config_name' => 'show_board_start_index', 'config_value' => '1']
];
$this->table('bb_config')->insert($configs)->saveData();
}
private function seedCronJobs()
{
$cronJobs = [
[
'cron_active' => 1,
'cron_title' => 'Attach maintenance',
'cron_script' => 'attach_maintenance.php',
'schedule' => 'daily',
'run_day' => null,
'run_time' => '05:00:00',
'run_order' => 40,
'last_run' => '1900-01-01 00:00:00',
'next_run' => '1900-01-01 00:00:00',
'run_interval' => null,
'log_enabled' => 0,
'log_file' => '',
'log_sql_queries' => 0,
'disable_board' => 1,
'run_counter' => 0
],
[
'cron_active' => 1,
'cron_title' => 'Board maintenance',
'cron_script' => 'board_maintenance.php',
'schedule' => 'daily',
'run_day' => null,
'run_time' => '05:00:00',
'run_order' => 40,
'last_run' => '1900-01-01 00:00:00',
'next_run' => '1900-01-01 00:00:00',
'run_interval' => null,
'log_enabled' => 0,
'log_file' => '',
'log_sql_queries' => 0,
'disable_board' => 1,
'run_counter' => 0
],
[
'cron_active' => 1,
'cron_title' => 'Prune forums',
'cron_script' => 'prune_forums.php',
'schedule' => 'daily',
'run_day' => null,
'run_time' => '05:00:00',
'run_order' => 50,
'last_run' => '1900-01-01 00:00:00',
'next_run' => '1900-01-01 00:00:00',
'run_interval' => null,
'log_enabled' => 0,
'log_file' => '',
'log_sql_queries' => 0,
'disable_board' => 1,
'run_counter' => 0
],
[
'cron_active' => 1,
'cron_title' => 'Tracker maintenance',
'cron_script' => 'tr_maintenance.php',
'schedule' => 'daily',
'run_day' => null,
'run_time' => '05:00:00',
'run_order' => 90,
'last_run' => '1900-01-01 00:00:00',
'next_run' => '1900-01-01 00:00:00',
'run_interval' => null,
'log_enabled' => 0,
'log_file' => '',
'log_sql_queries' => 0,
'disable_board' => 1,
'run_counter' => 0
],
[
'cron_active' => 1,
'cron_title' => 'Sessions cleanup',
'cron_script' => 'sessions_cleanup.php',
'schedule' => 'interval',
'run_day' => null,
'run_time' => '04:00:00',
'run_order' => 255,
'last_run' => '1900-01-01 00:00:00',
'next_run' => '1900-01-01 00:00:00',
'run_interval' => '00:03:00',
'log_enabled' => 0,
'log_file' => '',
'log_sql_queries' => 0,
'disable_board' => 0,
'run_counter' => 0
],
[
'cron_active' => 1,
'cron_title' => 'Tracker cleanup and dlstat',
'cron_script' => 'tr_cleanup_and_dlstat.php',
'schedule' => 'interval',
'run_day' => null,
'run_time' => '04:00:00',
'run_order' => 20,
'last_run' => '1900-01-01 00:00:00',
'next_run' => '1900-01-01 00:00:00',
'run_interval' => '00:15:00',
'log_enabled' => 0,
'log_file' => '',
'log_sql_queries' => 0,
'disable_board' => 0,
'run_counter' => 0
],
[
'cron_active' => 1,
'cron_title' => 'Make tracker snapshot',
'cron_script' => 'tr_make_snapshot.php',
'schedule' => 'interval',
'run_day' => null,
'run_time' => '04:00:00',
'run_order' => 10,
'last_run' => '1900-01-01 00:00:00',
'next_run' => '1900-01-01 00:00:00',
'run_interval' => '00:10:00',
'log_enabled' => 0,
'log_file' => '',
'log_sql_queries' => 0,
'disable_board' => 0,
'run_counter' => 0
]
];
$this->table('bb_cron')->insert($cronJobs)->saveData();
}
private function seedExtensions()
{
// Extension groups
$groups = [
['group_name' => 'Images', 'cat_id' => 1, 'allow_group' => 1, 'download_mode' => 1, 'upload_icon' => '', 'max_filesize' => 262144, 'forum_permissions' => ''],
['group_name' => 'Archives', 'cat_id' => 0, 'allow_group' => 1, 'download_mode' => 1, 'upload_icon' => '', 'max_filesize' => 262144, 'forum_permissions' => ''],
['group_name' => 'Plain text', 'cat_id' => 0, 'allow_group' => 1, 'download_mode' => 1, 'upload_icon' => '', 'max_filesize' => 262144, 'forum_permissions' => ''],
['group_name' => 'Documents', 'cat_id' => 0, 'allow_group' => 1, 'download_mode' => 1, 'upload_icon' => '', 'max_filesize' => 262144, 'forum_permissions' => ''],
['group_name' => 'Real media', 'cat_id' => 0, 'allow_group' => 0, 'download_mode' => 2, 'upload_icon' => '', 'max_filesize' => 262144, 'forum_permissions' => ''],
['group_name' => 'Torrent', 'cat_id' => 0, 'allow_group' => 1, 'download_mode' => 1, 'upload_icon' => '', 'max_filesize' => 262144, 'forum_permissions' => '']
];
$this->table('bb_extension_groups')->insert($groups)->saveData();
// Extensions
$extensions = [
['group_id' => 1, 'extension' => 'gif', 'comment' => ''],
['group_id' => 1, 'extension' => 'png', 'comment' => ''],
['group_id' => 1, 'extension' => 'jpeg', 'comment' => ''],
['group_id' => 1, 'extension' => 'jpg', 'comment' => ''],
['group_id' => 1, 'extension' => 'webp', 'comment' => ''],
['group_id' => 1, 'extension' => 'avif', 'comment' => ''],
['group_id' => 1, 'extension' => 'bmp', 'comment' => ''],
['group_id' => 2, 'extension' => 'gtar', 'comment' => ''],
['group_id' => 2, 'extension' => 'gz', 'comment' => ''],
['group_id' => 2, 'extension' => 'tar', 'comment' => ''],
['group_id' => 2, 'extension' => 'zip', 'comment' => ''],
['group_id' => 2, 'extension' => 'rar', 'comment' => ''],
['group_id' => 2, 'extension' => 'ace', 'comment' => ''],
['group_id' => 2, 'extension' => '7z', 'comment' => ''],
['group_id' => 3, 'extension' => 'txt', 'comment' => ''],
['group_id' => 3, 'extension' => 'c', 'comment' => ''],
['group_id' => 3, 'extension' => 'h', 'comment' => ''],
['group_id' => 3, 'extension' => 'cpp', 'comment' => ''],
['group_id' => 3, 'extension' => 'hpp', 'comment' => ''],
['group_id' => 3, 'extension' => 'diz', 'comment' => ''],
['group_id' => 3, 'extension' => 'm3u', 'comment' => ''],
['group_id' => 4, 'extension' => 'xls', 'comment' => ''],
['group_id' => 4, 'extension' => 'doc', 'comment' => ''],
['group_id' => 4, 'extension' => 'dot', 'comment' => ''],
['group_id' => 4, 'extension' => 'pdf', 'comment' => ''],
['group_id' => 4, 'extension' => 'ai', 'comment' => ''],
['group_id' => 4, 'extension' => 'ps', 'comment' => ''],
['group_id' => 4, 'extension' => 'ppt', 'comment' => ''],
['group_id' => 5, 'extension' => 'rm', 'comment' => ''],
['group_id' => 6, 'extension' => 'torrent', 'comment' => '']
];
$this->table('bb_extensions')->insert($extensions)->saveData();
}
private function seedSmilies()
{
$smilies = [
['code' => ':aa:', 'smile_url' => 'aa.gif', 'emoticon' => 'aa'],
['code' => ':ab:', 'smile_url' => 'ab.gif', 'emoticon' => 'ab'],
['code' => ':ac:', 'smile_url' => 'ac.gif', 'emoticon' => 'ac'],
['code' => ':ae:', 'smile_url' => 'ae.gif', 'emoticon' => 'ae'],
['code' => ':af:', 'smile_url' => 'af.gif', 'emoticon' => 'af'],
['code' => ':ag:', 'smile_url' => 'ag.gif', 'emoticon' => 'ag'],
['code' => ':ah:', 'smile_url' => 'ah.gif', 'emoticon' => 'ah'],
['code' => ':ai:', 'smile_url' => 'ai.gif', 'emoticon' => 'ai'],
['code' => ':aj:', 'smile_url' => 'aj.gif', 'emoticon' => 'aj'],
['code' => ':ak:', 'smile_url' => 'ak.gif', 'emoticon' => 'ak']
];
$this->table('bb_smilies')->insert($smilies)->saveData();
}
private function seedRanks()
{
$this->table('bb_ranks')->insert([
[
'rank_title' => 'Administrator',
'rank_image' => 'styles/images/ranks/admin.png',
'rank_style' => 'colorAdmin'
]
])->saveData();
}
private function seedQuotaLimits()
{
$quotas = [
['quota_desc' => 'Low', 'quota_limit' => 262144],
['quota_desc' => 'Medium', 'quota_limit' => 10485760],
['quota_desc' => 'High', 'quota_limit' => 15728640]
];
$this->table('bb_quota_limits')->insert($quotas)->saveData();
}
private function seedDisallowedUsernames()
{
$disallowed = [
['disallow_username' => 'torrentpier*'],
['disallow_username' => 'tracker*'],
['disallow_username' => 'forum*'],
['disallow_username' => 'torrent*'],
['disallow_username' => 'admin*']
];
$this->table('bb_disallow')->insert($disallowed)->saveData();
}
private function seedAttachmentConfig()
{
$attachConfig = [
['config_name' => 'upload_dir', 'config_value' => 'data/uploads'],
['config_name' => 'upload_img', 'config_value' => 'styles/images/icon_clip.gif'],
['config_name' => 'topic_icon', 'config_value' => 'styles/images/icon_clip.gif'],
['config_name' => 'display_order', 'config_value' => '0'],
['config_name' => 'max_filesize', 'config_value' => '262144'],
['config_name' => 'attachment_quota', 'config_value' => '52428800'],
['config_name' => 'max_filesize_pm', 'config_value' => '262144'],
['config_name' => 'max_attachments', 'config_value' => '1'],
['config_name' => 'max_attachments_pm', 'config_value' => '1'],
['config_name' => 'disable_mod', 'config_value' => '0'],
['config_name' => 'allow_pm_attach', 'config_value' => '1'],
['config_name' => 'default_upload_quota', 'config_value' => '0'],
['config_name' => 'default_pm_quota', 'config_value' => '0'],
['config_name' => 'img_display_inlined', 'config_value' => '1'],
['config_name' => 'img_max_width', 'config_value' => '2000'],
['config_name' => 'img_max_height', 'config_value' => '2000'],
['config_name' => 'img_link_width', 'config_value' => '600'],
['config_name' => 'img_link_height', 'config_value' => '400'],
['config_name' => 'img_create_thumbnail', 'config_value' => '1'],
['config_name' => 'img_min_thumb_filesize', 'config_value' => '12000']
];
$this->table('bb_attachments_config')->insert($attachConfig)->saveData();
}
private function seedTopicsAndPosts()
{
$currentTime = time();
// Create welcome topic
$this->table('bb_topics')->insert([
[
'topic_id' => 1,
'forum_id' => 1,
'topic_title' => 'Welcome to TorrentPier Cattle',
'topic_poster' => 2,
'topic_time' => $currentTime,
'topic_views' => 0,
'topic_replies' => 0,
'topic_status' => 0,
'topic_vote' => 0,
'topic_type' => 0,
'topic_first_post_id' => 1,
'topic_last_post_id' => 1,
'topic_moved_id' => 0,
'topic_attachment' => 0,
'topic_dl_type' => 0,
'topic_last_post_time' => $currentTime,
'topic_show_first_post' => 0,
'topic_allow_robots' => 1
]
])->saveData();
// Create welcome post
$this->table('bb_posts')->insert([
[
'post_id' => 1,
'topic_id' => 1,
'forum_id' => 1,
'poster_id' => 2,
'post_time' => $currentTime,
'poster_ip' => '0',
'poster_rg_id' => 0,
'attach_rg_sig' => 0,
'post_username' => '',
'post_edit_time' => 0,
'post_edit_count' => 0,
'post_attachment' => 0,
'user_post' => 1,
'mc_comment' => '',
'mc_type' => 0,
'mc_user_id' => 0
]
])->saveData();
// Create welcome post text
$welcomeText = "Thank you for installing the new — TorrentPier Cattle!\n\n" .
"What to do next? First of all configure your site in the administration panel (link in the bottom).\n\n" .
"Change main options: site description, number of messages per topic, time zone, language by default, seed-bonus options, birthdays etc... " .
"Create a couple of forums, delete or change this one. Change settings of categories to allow registration of torrents, change announcer url. " .
"If you will have questions or want additional modifications of the engine, [url=https://torrentpier.com/]visit our forum[/url] " .
"(you can use english, we will try to help in any case).\n\n" .
"If you want to help with the translations: [url=https://crowdin.com/project/torrentpier]Crowdin[/url].\n\n" .
"Our GitHub organization: [url=https://github.com/torrentpier]https://github.com/torrentpier[/url].\n" .
"Our SourceForge repository: [url=https://sourceforge.net/projects/torrentpier-engine]https://sourceforge.net/projects/torrentpier-engine[/url].\n" .
"Our demo website: [url=https://torrentpier.duckdns.org]https://torrentpier.duckdns.org[/url].\n\n" .
"We are sure that you will be able to create the best tracker available!\n" .
"Good luck! 😉";
$this->table('bb_posts_text')->insert([
[
'post_id' => 1,
'post_text' => $welcomeText
]
])->saveData();
}
private function seedTopicWatch()
{
$this->table('bb_topics_watch')->insert([
[
'topic_id' => 1,
'user_id' => 2,
'notify_status' => 1
]
])->saveData();
}
public function down()
{
// Clean all seeded data
$tables = [
'bb_topics_watch', 'bb_posts_text', 'bb_posts', 'bb_topics',
'bb_attachments_config', 'bb_disallow', 'bb_quota_limits',
'bb_ranks', 'bb_smilies', 'bb_extensions', 'bb_extension_groups',
'bb_cron', 'bb_config', 'bb_bt_users', 'bb_users', 'bb_forums', 'bb_categories'
];
foreach ($tables as $table) {
$this->execute("DELETE FROM {$table}");
}
}
}

74
phinx.php Normal file
View file

@ -0,0 +1,74 @@
<?php
/**
* Phinx configuration for TorrentPier
*/
if (PHP_SAPI != 'cli') {
die(basename(__FILE__));
}
// Only load what's needed for Phinx - don't bootstrap the entire application
const BB_ROOT = __DIR__ . DIRECTORY_SEPARATOR;
const BB_PATH = __DIR__;
require_once BB_ROOT . 'library/defines.php';
// Load environment variables
use Dotenv\Dotenv;
require_once __DIR__ . '/vendor/autoload.php';
if (file_exists(__DIR__ . '/.env')) {
$dotenv = Dotenv::createMutable(__DIR__);
$dotenv->load();
}
// Helper function for environment variables
function env(string $key, mixed $default = null): mixed
{
$value = $_ENV[$key] ?? getenv($key);
if ($value === false) {
return $default;
}
return $value;
}
return [
'paths' => [
'migrations' => __DIR__ . '/migrations'
],
'environments' => [
'default_migration_table' => BB_MIGRATIONS,
'default_environment' => env('APP_ENV', 'production'),
'production' => [
'adapter' => 'mysql',
'host' => env('DB_HOST', 'localhost'),
'port' => (int)env('DB_PORT', 3306),
'name' => env('DB_DATABASE'),
'user' => env('DB_USERNAME'),
'pass' => env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'table_options' => [
'ENGINE' => 'InnoDB',
'DEFAULT CHARSET' => 'utf8mb4',
'COLLATE' => 'utf8mb4_unicode_ci'
]
],
'development' => [
'adapter' => 'mysql',
'host' => env('DB_HOST', 'localhost'),
'port' => (int)env('DB_PORT', 3306),
'name' => env('DB_DATABASE'),
'user' => env('DB_USERNAME'),
'pass' => env('DB_PASSWORD', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'table_options' => [
'ENGINE' => 'InnoDB',
'DEFAULT CHARSET' => 'utf8mb4',
'COLLATE' => 'utf8mb4_unicode_ci'
]
]
],
'version_order' => 'creation',
];

View file

@ -0,0 +1,300 @@
<?php
/**
* TorrentPier Bull-powered BitTorrent tracker engine
*
* @copyright Copyright (c) 2005-2025 TorrentPier (https://torrentpier.com)
* @link https://github.com/torrentpier/torrentpier for the canonical source repository
* @license https://github.com/torrentpier/torrentpier/blob/master/LICENSE MIT License
*/
namespace TorrentPier\Database;
/**
* Migration Status Manager
*
* Provides read-only access to database migration status information
* for the admin panel. Uses TorrentPier's database singleton and config system.
*/
class MigrationStatus
{
private string $migrationTable;
private string $migrationPath;
public function __construct()
{
$this->migrationTable = BB_MIGRATIONS;
$this->migrationPath = BB_ROOT . 'migrations';
}
/**
* Get complete migration status information
*
* @return array Migration status data including applied/pending migrations
*/
public function getMigrationStatus(): array
{
try {
// Check if migration table exists using Nette Database Explorer
$tableExists = $this->checkMigrationTableExists();
$setupStatus = $this->getSetupStatus();
if (!$tableExists) {
return [
'table_exists' => false,
'current_version' => null,
'applied_migrations' => [],
'pending_migrations' => $this->getAvailableMigrations(),
'setup_status' => $setupStatus,
'requires_setup' => $setupStatus['needs_setup']
];
}
// Get applied migrations using Nette Database Explorer
$appliedMigrations = DB()->query("
SELECT version, migration_name, start_time, end_time
FROM {$this->migrationTable}
ORDER BY version ASC
")->fetchAll();
// Convert Nette Result objects to arrays
$appliedMigrationsArray = [];
foreach ($appliedMigrations as $migration) {
$appliedMigrationsArray[] = [
'version' => $migration->version,
'migration_name' => $migration->migration_name,
'start_time' => $migration->start_time,
'end_time' => $migration->end_time
];
}
// Get current version (latest applied)
$currentVersion = null;
if (!empty($appliedMigrationsArray)) {
$currentVersion = end($appliedMigrationsArray)['version'];
}
// Get pending migrations
$availableMigrations = $this->getAvailableMigrations();
$appliedVersions = array_column($appliedMigrationsArray, 'version');
$pendingMigrations = array_filter($availableMigrations, function ($migration) use ($appliedVersions) {
return !in_array($migration['version'], $appliedVersions);
});
return [
'table_exists' => true,
'current_version' => $currentVersion,
'applied_migrations' => $appliedMigrationsArray,
'pending_migrations' => array_values($pendingMigrations),
'setup_status' => $setupStatus,
'requires_setup' => $setupStatus['needs_setup']
];
} catch (\Exception $e) {
bb_die('Error checking migration status: ' . $e->getMessage());
}
}
/**
* Determine setup status for existing installations
*
* @return array Setup status information
*/
private function getSetupStatus(): array
{
try {
// Check if core TorrentPier tables exist (indicates existing installation)
$coreTablesExist = $this->checkCoreTablesExist();
$migrationTableExists = $this->checkMigrationTableExists();
if (!$coreTablesExist) {
// Fresh installation
return [
'type' => 'fresh',
'needs_setup' => false,
'message' => 'Fresh installation - migrations will run normally',
'action_required' => false
];
}
if (!$migrationTableExists) {
// Existing installation without migration system
return [
'type' => 'existing_needs_setup',
'needs_setup' => true,
'message' => 'Existing installation detected - migration setup required',
'action_required' => true,
'instructions' => 'Mark initial migrations as applied using --fake flag'
];
}
// Check if initial migrations are marked as applied
$initialMigrationsApplied = $this->checkInitialMigrationsApplied();
if (!$initialMigrationsApplied) {
return [
'type' => 'existing_partial_setup',
'needs_setup' => true,
'message' => 'Migration table exists but initial migrations not marked as applied',
'action_required' => true,
'instructions' => 'Run: php vendor/bin/phinx migrate --fake --target=20250619000002'
];
}
// Fully set up
return [
'type' => 'fully_setup',
'needs_setup' => false,
'message' => 'Migration system fully configured',
'action_required' => false
];
} catch (\Exception $e) {
return [
'type' => 'error',
'needs_setup' => false,
'message' => 'Error detecting setup status: ' . $e->getMessage(),
'action_required' => false
];
}
}
/**
* Check if core TorrentPier tables exist
*
* @return bool True if core tables exist
*/
private function checkCoreTablesExist(): bool
{
try {
$coreTable = 'bb_users'; // Key table that should exist in any TorrentPier installation
$escapedTable = DB()->escape($coreTable);
$result = DB()->query("
SELECT COUNT(*) as table_count
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name = '{$escapedTable}'
")->fetch();
return $result && $result->table_count > 0;
} catch (\Exception $e) {
return false;
}
}
/**
* Check if initial migrations are marked as applied
*
* @return bool True if initial migrations are applied
*/
private function checkInitialMigrationsApplied(): bool
{
try {
$result = DB()->query("
SELECT COUNT(*) as migration_count
FROM {$this->migrationTable}
WHERE version IN ('20250619000001', '20250619000002')
")->fetch();
return $result && $result->migration_count >= 2;
} catch (\Exception $e) {
return false;
}
}
/**
* Check if migration table exists
*
* @return bool True if table exists, false otherwise
*/
private function checkMigrationTableExists(): bool
{
try {
// Using simple query without parameters to avoid issues
$escapedTable = DB()->escape($this->migrationTable);
$result = DB()->query("
SELECT COUNT(*) as table_count
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name = '{$escapedTable}'
")->fetch();
return $result && $result->table_count > 0;
} catch (\Exception $e) {
return false;
}
}
/**
* Get available migrations from filesystem
*
* @return array List of available migration files
*/
private function getAvailableMigrations(): array
{
$migrations = [];
if (is_dir($this->migrationPath)) {
$files = glob($this->migrationPath . '/*.php');
foreach ($files as $file) {
$filename = basename($file);
if (preg_match('/^(\d+)_(.+)\.php$/', $filename, $matches)) {
$migrations[] = [
'version' => $matches[1],
'name' => $matches[2],
'filename' => $filename,
'file_path' => $file
];
}
}
}
// Sort by version
usort($migrations, function ($a, $b) {
return strcmp($a['version'], $b['version']);
});
return $migrations;
}
/**
* Get database schema information
*
* @return array Database statistics and information
*/
public function getSchemaInfo(): array
{
try {
// Get database name using Nette Database Explorer
$dbInfo = DB()->query("SELECT DATABASE() as db_name")->fetch();
// Get table count using Nette Database Explorer
$tableInfo = DB()->query("
SELECT COUNT(*) as table_count
FROM information_schema.tables
WHERE table_schema = DATABASE()
")->fetch();
// Get database size using Nette Database Explorer
$sizeInfo = DB()->query("
SELECT
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) as size_mb
FROM information_schema.tables
WHERE table_schema = DATABASE()
")->fetch();
return [
'database_name' => $dbInfo->db_name ?? 'Unknown',
'table_count' => $tableInfo->table_count ?? 0,
'size_mb' => $sizeInfo->size_mb ?? 0
];
} catch (\Exception $e) {
return [
'database_name' => 'Unknown',
'table_count' => 0,
'size_mb' => 0,
'error' => $e->getMessage()
];
}
}
}

View file

@ -0,0 +1,183 @@
<h1>{L_MIGRATIONS_STATUS}</h1>
<table class="forumline" cellpadding="4" cellspacing="1" border="0" width="100%">
<tr>
<th class="thHead" colspan="2">{L_MIGRATIONS_DATABASE_INFO}</th>
</tr>
<tr>
<td class="row1" width="35%"><b>{L_MIGRATIONS_DATABASE_NAME}:</b></td>
<td class="row2">{SCHEMA_DATABASE_NAME}</td>
</tr>
<tr>
<td class="row1"><b>{L_MIGRATIONS_DATABASE_TOTAL}:</b></td>
<td class="row2">{SCHEMA_TABLE_COUNT}</td>
</tr>
<tr>
<td class="row1"><b>{L_MIGRATIONS_DATABASE_SIZE}:</b></td>
<td class="row2">{SCHEMA_SIZE_MB} MB</td>
</tr>
<tr>
<td class="row1"><b>{L_LAST_UPDATED}:</b></td>
<td class="row2">{CURRENT_TIME}</td>
</tr>
</table>
<br/>
<table class="forumline" cellpadding="4" cellspacing="1" border="0" width="100%">
<tr>
<th class="thHead" colspan="2">{L_MIGRATIONS_STATUS}</th>
</tr>
<tr>
<td class="row1" width="35%"><b>{L_MIGRATIONS_SYSTEM}:</b></td>
<td class="row2">
<!-- IF MIGRATION_TABLE_EXISTS -->
<!-- IF SETUP_REQUIRES_SETUP -->
<span style="color: orange; font-weight: bold;">⚠ {L_MIGRATIONS_NEEDS_SETUP}</span>
<!-- ELSE -->
<span style="color: green; font-weight: bold;">✓ {L_MIGRATIONS_ACTIVE}</span>
<!-- ENDIF -->
<!-- ELSE -->
<span style="color: red; font-weight: bold;">✗ {L_MIGRATIONS_NOT_INITIALIZED}</span>
<!-- ENDIF -->
</td>
</tr>
<!-- IF SETUP_ACTION_REQUIRED -->
<tr>
<td class="row1"><b>Setup Status:</b></td>
<td class="row2">
<div
style="background: #fff3cd; padding: 8px; border: 1px solid #ffeaa7; border-radius: 4px; margin: 4px 0;">
<strong>Action Required:</strong> {SETUP_MESSAGE}<br>
<!-- IF SETUP_INSTRUCTIONS -->
<small><strong>Instructions:</strong> {SETUP_INSTRUCTIONS}</small><br>
<!-- ENDIF -->
<small><a href="#migration-setup-guide">See setup guide below</a></small>
</div>
</td>
</tr>
<!-- ENDIF -->
<tr>
<td class="row1"><b>Current Version:</b></td>
<td class="row2">
<!-- IF MIGRATION_CURRENT_VERSION -->
{MIGRATION_CURRENT_VERSION}
<!-- ELSE -->
<em>No migrations applied</em>
<!-- ENDIF -->
</td>
</tr>
<tr>
<td class="row1"><b>Applied Migrations:</b></td>
<td class="row2">{MIGRATION_APPLIED_COUNT}</td>
</tr>
<tr>
<td class="row1"><b>Pending Migrations:</b></td>
<td class="row2">
<!-- IF MIGRATION_PENDING_COUNT > 0 -->
<span style="color: orange; font-weight: bold;">{MIGRATION_PENDING_COUNT} pending</span>
<!-- ELSE -->
<span style="color: green;">All up to date</span>
<!-- ENDIF -->
</td>
</tr>
</table>
<!-- IF MIGRATION_APPLIED_COUNT > 0 -->
<br/>
<table class="forumline" cellpadding="4" cellspacing="1" border="0" width="100%">
<tr>
<th class="thHead" colspan="4">Applied Migrations</th>
</tr>
<tr>
<td class="catHead" width="15%"><b>Version</b></td>
<td class="catHead" width="35%"><b>Migration Name</b></td>
<td class="catHead" width="25%"><b>Applied At</b></td>
<td class="catHead" width="25%"><b>Completed At</b></td>
</tr>
<!-- BEGIN applied_migrations -->
<tr>
<td class="{applied_migrations.ROW_CLASS}">{applied_migrations.VERSION}</td>
<td class="{applied_migrations.ROW_CLASS}">{applied_migrations.NAME}</td>
<td class="{applied_migrations.ROW_CLASS}">{applied_migrations.START_TIME}</td>
<td class="{applied_migrations.ROW_CLASS}">{applied_migrations.END_TIME}</td>
</tr>
<!-- END applied_migrations -->
</table>
<!-- ENDIF -->
<!-- IF MIGRATION_PENDING_COUNT > 0 -->
<br/>
<table class="forumline" cellpadding="4" cellspacing="1" border="0" width="100%">
<tr>
<th class="thHead" colspan="3">Pending Migrations</th>
</tr>
<tr>
<td class="catHead" width="15%"><b>Version</b></td>
<td class="catHead" width="35%"><b>Migration Name</b></td>
<td class="catHead" width="50%"><b>File</b></td>
</tr>
<!-- BEGIN pending_migrations -->
<tr>
<td class="{pending_migrations.ROW_CLASS}">{pending_migrations.VERSION}</td>
<td class="{pending_migrations.ROW_CLASS}">{pending_migrations.NAME}</td>
<td class="{pending_migrations.ROW_CLASS}"><code>{pending_migrations.FILENAME}</code></td>
</tr>
<!-- END pending_migrations -->
</table>
<br/>
<div class="alert-warning"
style="padding: 10px; background-color: #fff3cd; border: 1px solid #ffeaa7; border-radius: 4px;">
<strong>⚠️ Pending Migrations Detected</strong><br/>
There are {MIGRATION_PENDING_COUNT} migration(s) that need to be applied.
Contact your system administrator to run:<br/>
<code style="background: #f8f9fa; padding: 2px 4px;">php vendor/bin/phinx migrate</code>
</div>
<!-- ENDIF -->
<br/>
<div class="info-box" style="padding: 15px; background-color: #e9ecef; border-radius: 4px;">
<h3>Migration Management</h3>
<p>This panel provides read-only information about the database migration status.
To manage migrations, use the command line interface:</p>
<ul>
<li><strong>Check status:</strong> <code>php vendor/bin/phinx status</code></li>
<li><strong>Run migrations:</strong> <code>php vendor/bin/phinx migrate</code></li>
<li><strong>Create new migration:</strong> <code>php vendor/bin/phinx create MigrationName</code></li>
<li><strong>Rollback last migration:</strong> <code>php vendor/bin/phinx rollback</code></li>
</ul>
<p><strong>⚠️ Important:</strong> Always backup your database before running migrations in production!</p>
</div>
<!-- IF SETUP_REQUIRES_SETUP -->
<br/>
<div id="migration-setup-guide" class="setup-guide"
style="padding: 15px; background-color: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px;">
<h3 style="color: #721c24;">🔧 Migration Setup Required</h3>
<p>Your installation has existing data but hasn't been set up for migrations yet. Follow these steps:</p>
<h4>Step 1: Backup Your Database</h4>
<pre style="background: #f8f9fa; padding: 10px; border-radius: 4px;">mysqldump -u username -p database_name > backup_$(date +%Y%m%d_%H%M%S).sql</pre>
<h4>Step 2: Initialize Migration System</h4>
<pre style="background: #f8f9fa; padding: 10px; border-radius: 4px;">php vendor/bin/phinx init</pre>
<h4>Step 3: Mark Initial Migrations as Applied</h4>
<pre style="background: #f8f9fa; padding: 10px; border-radius: 4px;">php vendor/bin/phinx migrate --fake --target=20250619000001
php vendor/bin/phinx migrate --fake --target=20250619000002</pre>
<h4>Step 4: Verify Setup</h4>
<pre style="background: #f8f9fa; padding: 10px; border-radius: 4px;">php vendor/bin/phinx status</pre>
<p><strong>What this does:</strong> The <code>--fake</code> flag marks migrations as applied without actually
running them,
since your database already has the schema. This allows future migrations to work normally.</p>
<p><strong>📖 Need help?</strong> See the complete guide in the
<a href="https://github.com/torrentpier/torrentpier/blob/master/UPGRADE_GUIDE.md#migration-setup-for-existing-installations"
target="_blank">UPGRADE_GUIDE.md</a></p>
</div>
<!-- ENDIF -->