install.php

15.68 KB
30/07/2025 14:03
PHP
install.php
<?php

/**
 * Photo Gallery Installation Script
 *
 * This script initializes the photo gallery system with proper error handling,
 * system requirements validation, and database setup.
 *
 * Usage: php install.php [--force] [--config-only]
 */

require_once 'classes/DataManager.php';
require_once 'classes/ErrorHandler.php';

class PhotoGalleryInstaller
{
    /**
     * @var mixed
     */
    private $dataManager;
    /**
     * @var mixed
     */
    private $errorHandler;
    /**
     * @var mixed
     */
    private $force = false;
    /**
     * @var mixed
     */
    private $configOnly = false;

    public function __construct()
    {
        $this->errorHandler = new ErrorHandler();
        $this->parseArguments();
    }

    /**
     * Parse command line arguments
     */
    private function parseArguments()
    {
        global $argv;
        if (isset($argv)) {
            $this->force = in_array('--force', $argv);
            $this->configOnly = in_array('--config-only', $argv);
        }
    }

    /**
     * Main installation process
     */
    public function install()
    {
        try {
            $this->printHeader();

            // Step 1: Validate system requirements
            $this->validateSystemRequirements();

            // Step 2: Check existing installation
            if (!$this->force) {
                $this->checkExistingInstallation();
            }

            // Step 3: Create directory structure
            $this->createDirectoryStructure();

            // Step 4: Initialize data files
            $this->initializeDataFiles();

            // Step 5: Scan and create albums from existing files
            if (!$this->configOnly) {
                $this->scanAndCreateAlbums();
            }

            // Step 6: Set proper permissions
            $this->setPermissions();

            // Step 7: Validate installation
            if (!$this->configOnly) {
                $this->validateInstallation();
            }

            $this->printSuccess();

        } catch (Exception $e) {
            $this->handleError($e);
            exit(1);
        }
    }

    /**
     * Print installation header
     */
    private function printHeader()
    {
        echo "\n";
        echo "=====================================\n";
        echo "  Photo Gallery Installation Script  \n";
        echo "=====================================\n\n";

        if ($this->force) {
            echo "⚠️  FORCE MODE: Existing data will be overwritten\n\n";
        }

        if ($this->configOnly) {
            echo "📝 CONFIG ONLY: Only configuration files will be created\n\n";
        }
    }

    /**
     * Validate system requirements
     */
    private function validateSystemRequirements()
    {
        echo "🔍 Validating system requirements...\n";

        $requirements = [
            'PHP Version' => [
                'check' => version_compare(PHP_VERSION, '7.4.0', '>='),
                'message' => 'PHP 7.4.0 or higher required. Current: '.PHP_VERSION
            ],
            'GD Extension' => [
                'check' => extension_loaded('gd'),
                'message' => 'GD extension is required for image processing'
            ],
            'JSON Extension' => [
                'check' => extension_loaded('json'),
                'message' => 'JSON extension is required for data storage'
            ],
            'File Upload Support' => [
                'check' => ini_get('file_uploads'),
                'message' => 'File uploads must be enabled in PHP configuration'
            ],
            'Write Permissions' => [
                'check' => is_writable('.'),
                'message' => 'Current directory must be writable'
            ]
        ];

        $failed = [];

        foreach ($requirements as $name => $requirement) {
            if ($requirement['check']) {
                echo "  ✓ $name\n";
            } else {
                echo "  ✗ $name: {$requirement['message']}\n";
                $failed[] = $name;
            }
        }

        if (!empty($failed)) {
            throw new Exception("System requirements not met: ".implode(', ', $failed));
        }

        echo "✅ All system requirements satisfied\n\n";
    }

    /**
     * Check for existing installation
     */
    private function checkExistingInstallation()
    {
        echo "🔍 Checking for existing installation...\n";

        $existingFiles = [];
        $checkFiles = [
            'data/config.json',
            'data/albums.json',
            'data/tags.json',
            'data/counters.json'
        ];

        foreach ($checkFiles as $file) {
            if (file_exists($file)) {
                $existingFiles[] = $file;
            }
        }

        if (!empty($existingFiles)) {
            echo "⚠️  Existing installation detected:\n";
            foreach ($existingFiles as $file) {
                echo "    - $file\n";
            }
            echo "\nUse --force to overwrite existing installation\n";
            throw new Exception("Installation already exists. Use --force to overwrite.");
        }

        echo "✅ No existing installation found\n\n";
    }

    /**
     * Create directory structure
     */
    private function createDirectoryStructure()
    {
        echo "📁 Creating directory structure...\n";

        $directories = [
            'data',
            'data/cache',
            'logs',
            'albums'
        ];

        foreach ($directories as $dir) {
            if (!is_dir($dir)) {
                if (!mkdir($dir, 0755, true)) {
                    throw new Exception("Failed to create directory: $dir");
                }
                echo "  ✓ Created: $dir\n";
            } else {
                echo "  ✓ Exists: $dir\n";
            }
        }

        echo "✅ Directory structure created\n\n";
    }

    /**
     * Initialize data files
     */
    private function initializeDataFiles()
    {
        echo "📄 Initializing data files...\n";

        try {
            $this->dataManager = new DataManager();
            $this->dataManager->initializeData();

            echo "  ✓ albums.json initialized\n";
            echo "  ✓ tags.json initialized\n";
            echo "  ✓ counters.json initialized\n";
            echo "  ✓ config.json initialized\n";

        } catch (Exception $e) {
            throw new Exception("Failed to initialize data files: ".$e->getMessage());
        }

        echo "✅ Data files initialized\n\n";
    }

    /**
     * Scan existing albums directory and create/update album entries
     */
    private function scanAndCreateAlbums()
    {
        echo "🔍 Scanning for existing albums and photos...\n";

        if (!is_dir('albums')) {
            echo "  ⚠️  Albums directory not found, skipping scan\n";
            return;
        }

        $albumDirs = glob('albums/*', GLOB_ONLYDIR);
        $createdAlbums = 0;
        $updatedAlbums = 0;
        $totalPhotos = 0;

        // Get current albums data
        $albumsData = $this->dataManager->getAlbums();
        $nextId = empty($albumsData) ? 1 : max(array_keys($albumsData)) + 1;

        // Create mapping of existing albums by folder name for quick lookup
        // Map folder names to album IDs based on the order they appear
        $albumDirNames = [];
        foreach ($albumDirs as $dir) {
            $albumDirNames[] = basename($dir);
        }
        sort($albumDirNames, SORT_NATURAL); // Sort naturally (1, 2, 10, 11, etc.)

        $existingAlbumsByFolder = [];
        $albumIndex = 0;

        foreach ($albumsData as $id => $album) {
            if ($albumIndex < count($albumDirNames)) {
                $folderName = $albumDirNames[$albumIndex];
                $existingAlbumsByFolder[$folderName] = $id;
                $albumIndex++;
            }
        }

        foreach ($albumDirs as $dir) {
            $albumId = basename($dir);
            $photos = glob($dir.'/*.{jpg,jpeg,png,gif,webp,bmp,tiff}', GLOB_BRACE);

            if (empty($photos)) {
                echo "  ⚠️  No photos found in $albumId, skipping\n";
                continue;
            }

            // Create photo data
            $photoData = [];
            foreach ($photos as $index => $photo) {
                $filename = basename($photo);
                $filesize = filesize($photo);

                // Get image dimensions
                $imageInfo = @getimagesize($photo);
                $width = $imageInfo ? $imageInfo[0] : 1200;
                $height = $imageInfo ? $imageInfo[1] : 800;

                $photoData[] = [
                    'id' => $index + 1,
                    'filename' => $filename,
                    'original_filename' => $filename,
                    'file_size' => $filesize,
                    'width' => $width,
                    'height' => $height,
                    'uploaded_at' => date('c', filemtime($photo))
                ];
            }

            // Check if album already exists
            if (isset($existingAlbumsByFolder[$albumId])) {
                // Update existing album - keep existing data, update only photos
                $existingId = $existingAlbumsByFolder[$albumId];
                $existingAlbum = $albumsData[$existingId];

                $albumsData[$existingId] = [
                    'id' => $existingAlbum['id'],
                    'title' => $existingAlbum['title'], // Keep existing title
                    'description' => $existingAlbum['description'], // Keep existing description
                    'created_at' => $existingAlbum['created_at'], // Keep original creation date
                    'updated_at' => date('c'), // Update modification date
                    'photo_count' => count($photoData),
                    'is_rss_enabled' => $existingAlbum['is_rss_enabled'], // Keep existing RSS setting
                    'tags' => $existingAlbum['tags'], // Keep existing tags
                    'photos' => $photoData // Update with current photos
                ];

                echo "  ✓ Updated album '{$existingAlbum['title']}' with ".count($photoData)." photos\n";
                $updatedAlbums++;
            } else {
                // Create new album
                $albumsData[$albumId] = [
                    'id' => $albumId,
                    'title' => "Album $albumId",
                    'description' => "Photo collection $albumId",
                    'created_at' => date('c'),
                    'updated_at' => date('c'),
                    'photo_count' => count($photoData),
                    'is_rss_enabled' => true,
                    'tags' => [],
                    'photos' => $photoData
                ];

                echo "  ✓ Created album 'Album $albumId' with ".count($photoData)." photos\n";
                $createdAlbums++;
                $nextId++;
            }

            $totalPhotos += count($photoData);
        }

        // Save updated albums data
        if ($createdAlbums > 0 || $updatedAlbums > 0) {
            // Use the private method through reflection
            $reflection = new ReflectionClass($this->dataManager);
            $method = $reflection->getMethod('saveJsonFile');
            $method->setAccessible(true);
            $method->invoke($this->dataManager, 'albums.json', $albumsData);

            echo "✅ Processed albums: $createdAlbums created, $updatedAlbums updated ($totalPhotos total photos)\n\n";
        } else {
            echo "  ℹ️  No albums created or updated from existing files\n\n";
        }
    }

    /**
     * Set proper file permissions
     */
    private function setPermissions()
    {
        echo "🔐 Setting file permissions...\n";

        $permissions = [
            'data' => 0755,
            'data/cache' => 0755,
            'logs' => 0755,
            'albums' => 0755,
            'data/config.json' => 0644,
            'data/albums.json' => 0644,
            'data/tags.json' => 0644,
            'data/counters.json' => 0644
        ];

        foreach ($permissions as $path => $permission) {
            if (file_exists($path)) {
                if (!chmod($path, $permission)) {
                    echo "  ⚠️  Warning: Could not set permissions for $path\n";
                } else {
                    echo "  ✓ Set permissions for $path\n";
                }
            }
        }

        echo "✅ Permissions configured\n\n";
    }

    /**
     * Validate installation
     */
    private function validateInstallation()
    {
        echo "✅ Validating installation...\n";

        // Test DataManager functionality
        try {
            $config = $this->dataManager->getAllConfig();
            echo "  ✓ Configuration loaded successfully\n";

            $albums = $this->dataManager->getAlbums();
            echo "  ✓ Albums data accessible\n";

            $tags = $this->dataManager->getTags();
            echo "  ✓ Tags data accessible\n";

        } catch (Exception $e) {
            throw new Exception("Installation validation failed: ".$e->getMessage());
        }

        // Test API endpoint
        if (file_exists('api.php')) {
            echo "  ✓ API endpoint available\n";
        } else {
            echo "  ⚠️  Warning: api.php not found\n";
        }

        echo "✅ Installation validated\n\n";
    }

    /**
     * Print success message
     */
    private function printSuccess()
    {
        echo "🎉 Installation completed successfully!\n\n";
        echo "Next steps:\n";
        echo "1. Configure your web server to serve the gallery\n";
        echo "2. Access the gallery through your web browser\n";
        echo "3. Start uploading photos and creating albums\n\n";

        if ($this->dataManager) {
            echo "Default configuration:\n";
            $config = $this->dataManager->getAllConfig();
            $importantSettings = [
                'upload_max_file_size',
                'image_webp_quality',
                'storage_max_total_size',
                'rss_title'
            ];

            foreach ($importantSettings as $setting) {
                if (isset($config[$setting])) {
                    $value = $config[$setting];
                    if ($setting === 'upload_max_file_size' || $setting === 'storage_max_total_size') {
                        $value = $this->formatBytes($value);
                    }
                    echo "  $setting: $value\n";
                }
            }
        }

        echo "\n";
    }

    /**
     * Handle installation errors
     */
    private function handleError($exception)
    {
        echo "\n❌ Installation failed!\n";
        echo "Error: ".$exception->getMessage()."\n";

        if ($this->errorHandler) {
            $this->errorHandler->logError('INSTALLATION_ERROR', $exception->getMessage(), [
                'file' => $exception->getFile(),
                'line' => $exception->getLine(),
                'trace' => $exception->getTraceAsString()
            ]);
        }

        echo "\nTroubleshooting:\n";
        echo "1. Check file permissions on the installation directory\n";
        echo "2. Ensure PHP has write access to create directories and files\n";
        echo "3. Verify all system requirements are met\n";
        echo "4. Check the error log for detailed information\n";
    }

    /**
     * Format bytes for human readable output
     */
    private function formatBytes($bytes)
    {
        $units = ['B', 'KB', 'MB', 'GB'];
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);

        $bytes /= (1 << (10 * $pow));

        return round($bytes, 2).' '.$units[$pow];
    }
}

// Run installation if called directly
if (basename(__FILE__) == basename($_SERVER['SCRIPT_NAME'])) {
    $installer = new PhotoGalleryInstaller();
    $installer->install();
}