Skip to main content

Media Handling & Protection

Sociable provides a comprehensive media management system for handling user-uploaded images and videos. This guide covers upload settings, validation, compression, storage options, and the optional protected media access feature.

Architecture Overview

Media in Sociable follows a single source of truth architecture:

  1. Database Table: All media metadata is stored in #__sociable_media table
  2. Storage Service: Files are stored via the configurable StorageService (local or S3)
  3. Media Service: Handles uploads, validation, URL generation, and retrieval
  4. Media Controller: Serves protected files with signature verification (when enabled)
┌─────────────────┐     ┌───────────────────┐     ┌─────────────────┐
│ React SPA │────▶│ API Endpoint │────▶│ MediaService │
│ (Frontend) │ │ (ActivityCtrl) │ │ │
└─────────────────┘ └───────────────────┘ └────────┬────────┘

┌──────────────────────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ StorageService │ │ #__sociable_ │ │ Settings │
│ (Local / S3) │ │ media │ │ Service │
└─────────────────┘ └─────────────────┘ └─────────────────┘

Media Upload Settings

Configure media uploads in Components → Sociable → Settings → Activity.

Image Settings

SettingDefaultDescription
Allow ImagesYesEnable/disable image uploads
Max Image Size10 MBMaximum file size per image
Allowed Image TypesJPEG, PNG, GIF, WebPPermitted MIME types
Max Image Width1920 pxAuto-resize images wider than this
Max Image Height1920 pxAuto-resize images taller than this
Compress ImagesYesEnable automatic compression
Image Quality85%JPEG/WebP compression quality (1-100)

Video Settings

SettingDefaultDescription
Allow VideosYesEnable/disable video uploads
Max Video Size50 MBMaximum file size per video
Allowed Video TypesMP4, WebM, QuickTimePermitted MIME types

General Settings

SettingDefaultDescription
Max Media Per Post10Maximum number of files per post

Storage Configuration

Configure storage in Components → Sociable → Settings → Storage.

Local Storage

Files are stored on your web server within the Joomla installation.

media/com_sociable/uploads/
└── posts/
└── {user_id}/
└── {year}/
└── {month}/
└── {unique_filename}.{ext}

Settings:

  • Local Upload Path: Relative path from Joomla root (default: media/com_sociable/uploads)

Amazon S3 Storage

Store files in an Amazon S3 bucket for scalability and CDN integration.

Settings:

  • S3 Bucket Name: Your S3 bucket identifier
  • S3 Region: AWS region (e.g., us-east-1, eu-west-1)
  • AWS Access Key ID: Your IAM access key
  • AWS Secret Access Key: Your IAM secret key
  • Use AWS CLI: Use installed AWS CLI instead of API keys

For S3 storage, files are stored with the path:

uploads/posts/{user_id}/{year}/{month}/{unique_filename}.{ext}

Validation Process

When users upload media, the system performs multi-stage validation:

Stage 1: Global Checks

  • Is media upload enabled globally?
  • Is the specific media type (images/videos) allowed?

Stage 2: Per-File Validation

For each file:

  1. PHP Upload Error Check: Validate no PHP-level upload errors occurred
  2. MIME Type Check: Verify file type is in the allowed list
  3. File Size Check: Ensure file doesn't exceed size limits
  4. Media Type Authorization: Confirm the specific type (image/video) is permitted

Stage 3: Limit Enforcement

  • Check if total files exceed "Max Media Per Post" limit
  • If exceeded, only first N files are processed; warning returned

Error Handling

The system returns detailed, per-file error messages:

{
"mediaIds": [1, 2, 3],
"errors": [
"image4.jpg: File exceeds the maximum allowed size (15.2 MB, max: 10 MB)",
"video.avi: File type not allowed"
],
"warnings": [
"12 files submitted, only first 10 were uploaded (max media per post limit)"
]
}

Image Processing

When enabled, images are automatically processed before storage:

Auto-Resize

Images exceeding the configured maximum dimensions are resized while preserving aspect ratio:

  • Uses Joomla's Image class for processing
  • Maintains original aspect ratio
  • GIF files are skipped to preserve animation

Compression

  • JPEG files: Compressed at configured quality (1-100%)
  • PNG files: Optimized PNG encoding
  • WebP files: Compressed at configured quality
  • GIF files: Skipped (preserves animation)

Processing Flow

Original Upload → Dimension Check → Resize (if needed) → Compress → Store
↓ ↓
[Temp File] [Final Storage]
↓ ↓
(Cleanup) [DB Metadata]

Protected Media Access

By default, uploaded media is publicly accessible via direct URLs. The Protected Media Access feature adds signature-based access control, preventing unauthorized direct URL access.

How It Works

When protection is enabled:

  1. URL Generation: Instead of direct file URLs, the system generates signed URLs:

    # Without protection (direct access):
    https://example.com/media/com_sociable/uploads/posts/123/2024/03/image.jpg

    # With protection (signed URL):
    https://example.com/index.php?option=com_sociable&task=media.serve
    &path=posts/123/2024/03/image.jpg
    &expires=1710456000
    &signature=a3f2b9c7e1d4...
  2. Signature Verification: The MediaController validates:

    • Required parameters are present (path, signature, expires)
    • URL hasn't expired (timestamp check)
    • Signature matches (HMAC-SHA256 verification)
  3. File Serving: If valid, PHP reads and serves the file with proper headers

Signature Generation

The signature uses HMAC-SHA256:

$data = $path . '|' . $expires;  // e.g., "posts/123/2024/03/image.jpg|1710456000"
$signature = hash_hmac('sha256', $data, $signingSecret);

The signing secret can be:

  • A custom secret configured in settings
  • Joomla's global secret key (fallback)

URL Caching

To avoid performance overhead, generated URLs are cached in-memory during each request:

  • Cache key: {adapter}:{path}:{protectMedia}
  • Same path returns cached URL within a single request

Enabling Protection

  1. Navigate to Components → Sociable → Settings → Storage

  2. Enable Protect Media Files toggle

  3. Optionally set:

    • Signed URL Expiry: How long URLs remain valid (60-86400 seconds, default: 3600)
    • Signing Secret: Custom HMAC secret (leave empty to use Joomla's secret)
  4. Configure .htaccess (Apache):

    cd media/com_sociable/uploads/
    mv .htaccess .htaccess.backup
    mv .htaccess.protected .htaccess
  5. Configure Nginx (if applicable):

    location /media/com_sociable/uploads/ {
    deny all;
    return 403;
    }

Video Streaming Support

The MediaController supports HTTP Range requests for video streaming:

HTTP/1.1 206 Partial Content
Content-Type: video/mp4
Content-Range: bytes 0-999999/5000000
Accept-Ranges: bytes

This allows:

  • Seeking within videos
  • Progressive playback
  • Bandwidth-efficient streaming

Settings Reference

SettingDefaultDescription
Protect MediaNoEnable signature-based access control
Signed URL Expiry3600URL validity period in seconds
Signing Secret(empty)Custom HMAC secret (uses Joomla secret if empty)

Security Considerations

Default Security (.htaccess)

Even without protection enabled, the default .htaccess includes:

# Block PHP execution in uploads
<FilesMatch "\.(?:php[1-7]?|phtml|phar)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
</FilesMatch>
php_flag engine Off

# Disable directory listing
Options -Indexes

Protected Mode (.htaccess.protected)

When protection is enabled:

# Deny all direct access
<IfModule mod_authz_core.c>
Require all denied
</IfModule>

# Disable directory listing
Options -Indexes

Recommendations

  1. Always use HTTPS - Signed URLs contain sensitive parameters
  2. Keep expiry times reasonable - Balance usability vs. security
  3. Monitor access patterns - Check server logs for unauthorized attempts
  4. Use custom signing secret - Replace Joomla's shared secret for isolation

Database Schema

The #__sociable_media table stores all media metadata:

ColumnTypeDescription
idINTPrimary key
user_idINTUploader's user ID
target_typeVARCHAREntity type (activity, comment, etc.)
target_idINTEntity ID
orderingINTSort order within target
media_typeVARCHARType: image or video
file_nameVARCHAROriginal filename
file_pathVARCHARStorage path
file_sizeINTSize in bytes
mime_typeVARCHARMIME type
dimensionsJSONWidth/height for images
hashVARCHARMD5 file hash
storage_adapterVARCHARAdapter used: local or s3
visibilityVARCHARAccess level
publishedTINYINTPublished state
createdDATETIMEUpload timestamp

API Reference

MediaService Methods

// Upload with validation (recommended)
$result = $mediaService->validateAndUploadFiles($userId, $files);
// Returns: ['mediaIds' => [...], 'errors' => [...], 'warnings' => [...]]

// Attach media to entity
$mediaService->attachToTarget($mediaIds, 'activity', $activityId);

// Retrieve media by target
$media = $mediaService->getByTarget('activity', $activityId);

// Batch load for feed
$mediaMap = $mediaService->getByTargetBatch('activity', $activityIds);
// Returns: [activityId => [mediaItems], ...]

// Get URL (handles protection automatically)
$url = $mediaService->getMediaUrl($path, $adapter);

// Delete media
$mediaService->deleteMedia($mediaId, $userId, $isAdmin);

REST API Endpoints

Upload media (as part of activity creation):

POST /index.php?option=com_sociable&task=activities.create
Content-Type: multipart/form-data
X-CSRF-Token: {token}

FormData:
- content: "Post text"
- files[]: (binary)
- files[]: (binary)

Retrieve activity with media:

GET /index.php?option=com_sociable&task=activities.item&id={id}

Response:
{
"activity": {
"id": 123,
"content": "...",
"media": [
{
"id": 1,
"type": "image",
"url": "https://...",
"width": 1920,
"height": 1080
}
]
}
}

Troubleshooting

Common Issues

Files not uploading:

  • Check PHP upload_max_filesize and post_max_size in php.ini
  • Verify directory permissions (755 for folders, 644 for files)
  • Check available disk space

Protected URLs not working:

  • Ensure .htaccess.protected is renamed to .htaccess
  • For Nginx, verify the deny rule is configured
  • Check system clock is accurate (for expiry validation)

Signature verification failing:

  • Verify signing secret is consistent
  • Check for URL encoding issues
  • Ensure system time is synchronized

Video not streaming:

  • Check video codec compatibility (H.264 for MP4)
  • Verify Range header support in web server
  • Test with smaller files first

Debug Tips

Enable Joomla debug mode to see detailed error messages. Check:

  • Joomla error log (administrator/logs/)
  • Server error log (/var/log/apache2/error.log or equivalent)
  • Browser Network tab for failed requests