# Bongo Import Package - Cursor AI Rules

## Project Overview

This package provides import functionality for migrating legacy Bongo CMS content (pages, posts, reviews) from an old database to the new Bongo v3.0 platform. It connects to a remote MySQL database, retrieves content with chunked/nested structures, processes HTML/images, and creates new records in the target Laravel application.

**Namespace**: `Bongo\Import`
**Dependencies**: `bongo/framework` (^3.0), `bongo/image`, `bongo/page`, `bongo/post`, `bongo/review`
**Laravel**: 10+
**PHP**: 8.2+

## Architecture

### Service Provider Pattern

The package extends `Bongo\Framework\Providers\AbstractServiceProvider`:

```php
class ImportServiceProvider extends AbstractServiceProvider
{
    protected string $module = 'import';

    protected array $commands = [
        ImportCommand::class,
        ResetCommand::class,
    ];
}
```

**Auto-bootstrapped by parent class**:
- Config from `src/Config/import.php`
- Views from `src/Views/import/` (namespace: `import::`)
- Commands registered via `$commands` array

### Directory Structure

```
src/
├── Commands/
│   ├── ImportCommand.php       # Main import orchestrator (import:run)
│   └── ResetCommand.php        # Database/storage cleanup (import:reset)
├── Config/
│   └── import.php              # Default connection credentials
├── Helpers/
│   └── ImageHelper.php         # Image download/storage utilities
├── Models/                     # Source database models (connection: 'import')
│   ├── Page.php                # pages table
│   ├── Post.php                # blogposts table
│   ├── Review.php              # reviewposts table
│   ├── Chunk.php               # chunks table (page sections)
│   └── Text.php                # text table (chunk content)
├── Services/
│   ├── BongoImport.php         # Abstract base with HTML/link cleaning
│   ├── ImportPageFromBongo.php # Page import logic
│   ├── ImportPostFromBongo.php # Post import logic
│   └── ImportReviewFromBongo.php # Review import logic
├── Views/backend/              # HTML section wrappers for imported content
│   ├── open_section.blade.php
│   ├── close_section.blade.php
│   └── section_title.blade.php
└── ImportServiceProvider.php
```

## Key Classes and Responsibilities

### Commands

#### ImportCommand (`src/Commands/ImportCommand.php:16-200`)
**Signature**: `import:run`
**Purpose**: Interactive CLI for importing pages/posts/reviews from legacy Bongo

**Flow**:
1. Prompts for database credentials (host, name, username, password, site URL)
2. Creates dynamic database connection named 'import'
3. Asks user which content types to import (pages/posts/reviews)
4. Iterates through source records and delegates to service classes

**Key Methods**:
- `handle()` - Main entry point, prompts user and orchestrates import
- `setDatabaseConnection()` - Configures `database.connections.import` at runtime
- `checkDatabaseConnection()` - Validates credentials before proceeding
- `importPages()`, `importPosts()`, `importReviews()` - Type-specific import loops

#### ResetCommand (`src/Commands/ResetCommand.php:10-50`)
**Signature**: `import:reset`
**Purpose**: Truncates destination tables and deletes downloaded images

**Truncates**: `pages`, `posts`, `reviews`, `images`, `imageables`
**Deletes**: `/public/pages`, `/public/posts`, `/public/cache` storage directories

### Services

#### BongoImport (Abstract) (`src/Services/BongoImport.php:8-100`)
**Purpose**: Base class providing HTML cleanup and URL transformation

**Methods**:
- `cleanHtml(string $html): string` - Strips inline styles, classes, empty tags; wraps icons/shortcodes; removes unwanted tags
- `updateLinks(string $html, string $siteUrl): string` - Converts legacy URLs to new routes:
  - `?p=foo` → `/foo`
  - `?review=reviews/slug` → `/reviews/slug`
  - `?blog=blogs/archive/YYYY/MM/DD/name.aspx` → `/posts/name`
- `updateImageSrc(string $imageSrc, string $siteUrl): string` - Normalizes image paths by removing domain, encoding spaces

#### ImportPageFromBongo (`src/Services/ImportPageFromBongo.php:13-122`)
**Purpose**: Imports legacy page with chunks/texts structure

**Constructor Flow**:
1. `findOrCreate()` - Creates `Bongo\Page\Models\Page` with slug from title, copies meta fields
2. `saveContent()` - Iterates chunks→texts, wraps in section HTML, cleans HTML, updates links
3. `saveImages()` - Parses content for `<img>` tags, downloads from remote site, stores via `ImageHelper`, updates src paths to `/photos/{filename}`
4. `saveOpenGraphImages()` - Downloads OG image from `Storage/general/ogimage/originals` if exists

#### ImportPostFromBongo (`src/Services/ImportPostFromBongo.php:13-100`)
**Purpose**: Imports legacy blog post

**Constructor Flow**:
1. `findOrCreate()` - Creates `Bongo\Post\Models\Post` with slug from pagename, extracts meta description from content
2. `saveContent()` - Wraps in section + title HTML, cleans content, updates links
3. `saveImages()` - Same image processing as pages

#### ImportReviewFromBongo (`src/Services/ImportReviewFromBongo.php:8-24`)
**Purpose**: Imports customer review

**Simple mapping**:
- Creates `Bongo\Review\Models\Review` from legacy fields
- Converts 10-point rank to 5-star rating (`round($bongoReview->rank / 2)`)

### Helpers

#### ImageHelper (`src/Helpers/ImageHelper.php:9-65`)
**Static Methods**:
- `store($model, string $imageUrl, string $directory, string $type, int $key = 1): Image`
  - Downloads image via `file_get_contents()`
  - Stores in `/public/{directory}/{model->id}/{filename}`
  - Creates `Bongo\Image\Models\Image` record with dimensions, orientation
  - Attaches polymorphic relationship to model
- `remoteFileExists(string $fileUrl): bool` - cURL HEAD request to verify image exists (skips SSL verification)

### Models (Source Database)

All models use `protected $connection = 'import'` to query the legacy database:

- **Page** (`src/Models/Page.php:8-20`) - Maps to `pages` table, has many chunks
- **Chunk** (`src/Models/Chunk.php:9-26`) - Maps to `chunks` table, belongs to page, has many texts
- **Text** (`src/Models/Text.php:8-20`) - Maps to `text` table, belongs to chunk, contains `raw_text` column
- **Post** (`src/Models/Post.php:7-14`) - Maps to `blogposts` table
- **Review** (`src/Models/Review.php:7-14`) - Maps to `reviewposts` table

## Coding Conventions

### Namespace and Naming
- Namespace: `Bongo\Import`
- Commands: `*Command` suffix
- Services: `Import*FromBongo` pattern
- Models: Match legacy table names (singular class, plural table)

### Return Types
- Always declare return types (`:void`, `:string`, `:bool`, `): Model`)
- Use nullable return types when appropriate (`: ?Model`)

### Dependency Injection
- Services receive data objects in constructor
- Constructor performs all import logic (no separate `execute()` method needed)

### Error Handling
- Import command catches exceptions per record (continues on failure)
- Logs exceptions via `log_exception($e)` helper
- Uses `console_print()` for progress messages

### HTML Processing
- Always clean HTML via `BongoImport::cleanHtml()`
- Always update links via `BongoImport::updateLinks()` before saving
- Use `libxml_use_internal_errors(true)` before DOMDocument parsing

## Common Tasks

### Adding Support for New Content Type

1. Create source model in `src/Models/` with `protected $connection = 'import'`:
   ```php
   class Product extends Model
   {
       protected $connection = 'import';
       protected $table = 'products';
   }
   ```

2. Create service in `src/Services/`:
   ```php
   class ImportProductFromBongo extends BongoImport
   {
       public function __construct(Product $bongoProduct, string $siteUrl)
       {
           $product = $this->findOrCreate($bongoProduct);
           $this->saveContent($bongoProduct, $product, $siteUrl);
           // etc.
       }
   }
   ```

3. Add import method to `ImportCommand`:
   ```php
   if ($this->confirm('Import products?')) {
       $this->importProducts();
   }
   ```

### Customising HTML Cleaning

Override `cleanHtml()` in specific service class:

```php
class ImportPageFromBongo extends BongoImport
{
    protected function cleanHtml(string $html): string
    {
        $html = parent::cleanHtml($html);
        // Add page-specific cleaning
        return $html;
    }
}
```

### Adding New URL Transformation

Extend `updateLinks()` in `BongoImport`:

```php
// Update product links
if (Str::contains($url, '?product=')) {
    $url = str_replace('?product=', '/products/', $url);
}
```

## Testing

### Running Tests
```bash
vendor/bin/phpunit
```

### Test Structure
- Base class: `Bongo\Import\Tests\TestCase` extends Orchestra Testbench
- Registers `ImportServiceProvider` automatically
- Uses `testing` database connection

### Code Style
```bash
# Check style
vendor/bin/pint --test

# Fix style
vendor/bin/pint
```

## Configuration

### Default Settings (`src/Config/import.php`)
```php
return [
    'site_url' => 'https://designtec.co.uk',
    'db_host' => '127.0.0.1',
    'db_name' => 'designtec',
    'db_username' => 'designtec',
    'db_password' => 'designtec',
];
```

**Access in code**: `config('import.site_url')`

### Dynamic Database Connection
The `import` connection is created at runtime in `ImportCommand::setDatabaseConnection()`:
```php
config()->set('database.connections.import', [
    'driver' => 'mysql',
    'port' => '3306',
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'engine' => 'InnoDB',
    'host' => $this->dbHost,
    'database' => $this->dbName,
    'username' => $this->dbUsername,
    'password' => $this->dbPassword,
]);

DB::purge('import'); // Refresh connection
```

## Console Commands

```bash
# Run interactive import
php artisan import:run

# Reset destination data before re-importing
php artisan import:reset
```

## Dependencies and Integration

### Framework Integration
- Extends `Bongo\Framework\Providers\AbstractServiceProvider`
- Uses `Bongo\Framework\Helpers\Html` for HTML processing
- Uses `Bongo\Framework\Helpers\File` for filename generation

### Package Dependencies
- **bongo/image**: Image storage and polymorphic relationships
- **bongo/page**: Destination Page model
- **bongo/post**: Destination Post model (optional, checked via `package()->isEnabled('post')`)
- **bongo/review**: Destination Review model (optional, checked via `package()->isEnabled('review')`)

### Image Storage
- Stores images to Laravel storage: `storage/app/public/{type}/{model_id}/{filename}`
- Updates references to `/photos/{filename}` (relies on symlink or routing)

## Important Notes

### Security Considerations
- Database credentials prompted interactively (not committed to config)
- Remote image downloads use `CURLOPT_SSL_VERIFYPEER = 0` (development only!)
- No authentication required for source database access

### Data Transformation
- Legacy 10-point ratings → 5-star ratings (divide by 2)
- Slugs generated from titles (may cause collisions)
- Post dates preserved from `postdate` column, review dates from `modwhen`
- Empty/hidden content filtered (`hidden = 0`, `unpublish = 0`, `reviewstate = 1`)

### HTML Content Wrappers
Imported content wrapped in section divs with specific classes:
- `.is-section.is-box` - Outer wrapper
- `.is-boxes > .is-box-centered` - Layout containers
- `.is-container.container-fluid` with `max-width:800px` - Content container

### Limitations
- No rollback mechanism (use `import:reset` to start over)
- Imports duplicate records if run multiple times (uses `firstOrNew` but doesn't prevent duplicates)
- Image downloads synchronous (slow for large content sets)
- No progress bar or percentage indicator
