PHPackages                             aboleon/metaframework-mediaclass - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [File &amp; Storage](/categories/file-storage)
4. /
5. aboleon/metaframework-mediaclass

ActiveLibrary[File &amp; Storage](/categories/file-storage)

aboleon/metaframework-mediaclass
================================

Mediaclass media management components for MetaFramework

1.1.4(yesterday)0761MITPHPPHP ^8.3

Since Jan 12Pushed yesterdayCompare

[ Source](https://github.com/aboleon/metaframework-mediaclass)[ Packagist](https://packagist.org/packages/aboleon/metaframework-mediaclass)[ RSS](/packages/aboleon-metaframework-mediaclass/feed)WikiDiscussions 1.x Synced today

READMEChangelogDependencies (76)Versions (51)Used By (1)

MetaFramework Mediaclass
========================

[](#metaframework-mediaclass)

[![Tests](https://github.com/aboleon/metaframework-mediaclass/actions/workflows/tests.yml/badge.svg)](https://github.com/aboleon/metaframework-mediaclass/actions)[![codecov](https://camo.githubusercontent.com/dcba6aa2185c83431cbdcb2879b328cc9514733bf46ee1cc73d59c13dbffe61c/68747470733a2f2f636f6465636f762e696f2f67682f61626f6c656f6e2f6d6574616672616d65776f726b2d6d65646961636c6173732f67726170682f62616467652e737667)](https://codecov.io/gh/aboleon/metaframework-mediaclass)[![Latest Version on Packagist](https://camo.githubusercontent.com/efce5c25947bfb1a8e3cf85c347e4868938d679b8e7ab3a99183e55648bd8356/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f61626f6c656f6e2f6d6574616672616d65776f726b2d6d65646961636c6173732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/aboleon/metaframework-mediaclass)[![Total Downloads](https://camo.githubusercontent.com/ef2448aa3b3403a498ffb65880469722976dae10418f3f721b97ef758301fa9c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f61626f6c656f6e2f6d6574616672616d65776f726b2d6d65646961636c6173732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/aboleon/metaframework-mediaclass)[![PHP Version](https://camo.githubusercontent.com/bceafd85b548dcfb5bb1b9834d9c333ffdbfb36fba65cf400ddf7e800f0152b6/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f61626f6c656f6e2f6d6574616672616d65776f726b2d6d65646961636c6173732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/aboleon/metaframework-mediaclass)[![License](https://camo.githubusercontent.com/d0e99fe8c995a8ce67db1d82931e9f3ca1e71aee70a9c03c9fe0cfe26ae3c5ca/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f61626f6c656f6e2f6d6574616672616d65776f726b2d6d65646961636c6173732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/aboleon/metaframework-mediaclass)

Media management components for Laravel applications. This package provides upload UI, database persistence, image resizing, optional cropping, and helpers to retrieve and render media for Eloquent models.

Quick Start
-----------

[](#quick-start)

```
// Get image URL
$url = $post->img('cover')->url();

// Get img tag
{!! $post->img('cover')->class('rounded')->lazy()->img() !!}

// In Blade

```

Installation
------------

[](#installation)

```
composer require aboleon/metaframework-mediaclass
```

Publish package resources after install or update:

```
php artisan mediaclass:update --force
```

The update command replaces the package-owned `public/vendor/mfw-mediaclass` directory before publishing. This removes assets deleted by newer package versions instead of leaving stale files from older releases. Do not customize files inside that published asset directory.

On first install, publish the config without `--force`, then set its `disk` to the application filesystem disk that owns the media paths:

```
php artisan mediaclass:update --config
```

Normal package updates never publish the config, including when `--force` is used. This preserves application-owned disk, dimensions, subgroup, and path settings. Use `--config --force` only when intentionally replacing the application config with package defaults.

When a release adds migrations, publish and run them explicitly:

```
php artisan mediaclass:update --force --migrate
```

Use `--views` only when the application needs to customize the package Blade views. Views are not published by default so package updates can keep improving the upstream UI.

The stored-media component loads LightGallery `2.8.3` from `https://cdnjs.cloudflare.com`, including its bundled CSS and the core, zoom, thumbnail, and video scripts. The package does not publish a local LightGallery distribution. Applications with a Content Security Policy must allow this host in `script-src` and `style-src`.

Version Tracks
--------------

[](#version-tracks)

Mediaclass `0.x` is the jQuery uploader line. Applications that want the existing jQuery UI and do not want the v1 Svelte UI should require the `0.x`track explicitly:

```
composer require aboleon/metaframework-mediaclass:"0.*"
```

For the current stable jQuery release line only:

```
composer require aboleon/metaframework-mediaclass:"^0.16"
```

Mediaclass `1.x` is the Svelte UI line. The Svelte assets are built and shipped inside the Composer package, so consuming Laravel applications should not need Node, npm, or a Svelte build step just to use the package. After updating the Composer dependency, run:

```
php artisan mediaclass:update --force
```

The `1.x` package does not ship or load Blueimp jQuery File Upload. Its uploader, video URL form, queue, progress state, and validation UI are Svelte. jQuery is still required by the current media-management bridge for stored-media sorting, deletion, descriptions, cropping, subgroups, and LightGallery integration.

During v1 development, applications can test the branch with Composer's dev constraint:

```
composer require aboleon/metaframework-mediaclass:"1.x-dev"
```

### Frontend Asset Development

[](#frontend-asset-development)

The v1 frontend source lives in `resources/svelte`. `Uploadable.svelte` owns the upload UI and `media-manager.js` owns stored-media interactions. Vite compiles both into the single shipped `public/vendor/mfw-mediaclass/mediaclass-uploader.js` bundle. Package maintainers must run the frontend checks and rebuild the shipped bundle before tagging a release:

```
npm install
npm run check
npm run build
```

Uploader styles live in `public/vendor/mfw-mediaclass/css/styles.css` and are processed by the existing CSSCrush integration. Svelte components must not contain component-level style blocks or inject CSS into the compiled JavaScript bundle.

The Vite build disables `publicDir` intentionally because the bundle output is inside the package `public` tree. Do not re-enable public copying for this build.

### Blade Components

[](#blade-components)

The active v1 Blade surface is:

- `` for the Svelte mount point and media context.
- `` for server-rendered existing media.
- `` and `` for frontend rendering.
- Internal crop and confirmation modal components used by `uploadable`.

The old Blueimp upload-template component was removed in `1.x`; it is not a supported Blade API.

The small published `jcrop` directory remains because the active crop editor still loads Jcrop. It is independent from the removed Blueimp uploader.

Applications installing the package through Composer do not run these npm commands. They receive the precompiled bundle and only need:

```
composer update aboleon/metaframework-mediaclass
php artisan mediaclass:update --force
```

Laravel Compatibility
---------------------

[](#laravel-compatibility)

The package supports Laravel majors through explicit Illuminate constraints in `composer.json`, currently `^11.0|^12.0|^13.0`. A future Laravel major does not automatically require a Mediaclass major release. The package should widen its Laravel constraints and tag a minor or patch release when the public API and published assets remain compatible.

Use a new Mediaclass major only when the package drops an older Laravel major, changes the public PHP or Blade contract, changes installation behavior in a breaking way, or replaces an implementation detail that applications can reasonably depend on.

The current v1 direction keeps Mediaclass Laravel-bound with a shipped Svelte bundle. If Mediaclass later needs to live as a framework-neutral uploader, the better split is a Lit + TypeScript web-component package with a Laravel bridge package around routes, persistence, configuration, and Blade helpers. That split is larger than the v1 Svelte migration and should be treated as a separate product boundary.

Model Setup
-----------

[](#model-setup)

```
use Illuminate\Database\Eloquent\Model;
use MetaFramework\Mediaclass\Contracts\MediaclassInterface;
use MetaFramework\Mediaclass\Concerns\Mediaclass as MediaclassTrait;

class Post extends Model implements MediaclassInterface
{
    use MediaclassTrait;

    public function mediaclassSettings(): array
    {
        return [
            'cover' => [
                'label' => 'Cover',
                'width' => 1600,
                'height' => 900,
                'cropable' => true,
            ],
            'gallery' => [
                'label' => 'Gallery',
                'width' => 1200,
                'height' => 800,
            ],
        ];
    }
}
```

---

Two Groups Example (Cover + Gallery)
------------------------------------

[](#two-groups-example-cover--gallery)

Use group keys in `mediaclassSettings()` to define the required dimensions per group:

```
public function mediaclassSettings(): array
{
    return [
        'cover' => [
            'label' => 'Cover',
            'width' => 1600,
            'height' => 900,
            'cropable' => true, // single crop using the group dimensions
        ],
        'gallery' => [
            'label' => 'Gallery',
            'width' => 1200,
            'height' => 800,
            // 'cropable' => ['thumb' => [400, 300]] // optional extra crops
        ],
    ];
}
```

If no group is defined, the package falls back to the default sizes defined in `config/mfw-mediaclass.php` under `dimensions`.

---

Group-Specific Sizes
--------------------

[](#group-specific-sizes)

You can define multiple sizes for a single group using a `sizes` array. These sizes will be used for resizing and for size keys when calling `url('key')`:

```
public function mediaclassSettings(): array
{
    return [
        'cover' => [
            'label' => 'Cover',
            'sizes' => [
                'xl' => ['width' => 1600, 'height' => 900],
                'sm' => ['width' => 1200, 'height' => 500],
            ],
            'cropable' => true, // uses the largest size as the crop target
        ],
    ];
}
```

If `sizes` is not provided for a group, the package uses the single `width` / `height` pair for that group, or falls back to the global `dimensions` defaults.

**Note:** Upload processing relies on Intervention Image. If Intervention Image is not installed, upload tests that hit the upload controller will be skipped.

---

Displaying Images
-----------------

[](#displaying-images)

### Fluent API (Recommended)

[](#fluent-api-recommended)

The simplest way to display images from your models:

```
// Get URL
$url = $post->img('cover')->url();
$url = $post->img('cover')->url('lg');    // specific size

// Get img tag
$html = $post->img('cover')->img();
$html = $post->img('cover')
    ->class('rounded-lg shadow')
    ->alt('Product photo')
    ->lazy()
    ->img();

// Get cropped version
$url = $post->img('cover')->crop('banner')->url();

// Check if media exists
if ($post->img('cover')->exists()) {
    // ...
}

// Multiple images
foreach ($post->imgs('gallery') as $img) {
    echo $img->url();
}
```

### Available Methods

[](#available-methods)

MethodDescription`->url(?string $size)`Get URL (default: 'sm')`->img(?string $size)`Get `` tag`->picture(?array $breakpoints)`Get `` element`->background()`Get CSS background-image style`->urls()`Get all available URLs as array**Size Methods:**

```
->size('lg')      // Set size
->sm() / ->md() / ->lg() / ->xl()  // Shorthand
```

**Crop Methods:**

```
->crop('banner')  // Use specific crop
->hasCrop('banner')  // Check if crop exists
```

**HTML Attributes:**

```
->class('rounded')   // CSS classes
->addClass('shadow') // Add to existing classes
->alt('Description') // Alt text
->id('hero-image')   // ID attribute
->lazy()             // loading="lazy"
->eager()            // loading="eager"
->width(800)         // Width attribute
->height(600)        // Height attribute
->attr('data-id', 1) // Any attribute
->attrs(['class' => 'rounded', 'id' => 'img'])
->data('gallery', 'main')  // data-* attributes
```

**Fallback:**

```
->default('/img/fallback.png')  // Custom fallback URL
->noDefault()                    // No fallback image
```

### Blade Component

[](#blade-component)

```
{{-- Basic usage --}}

{{-- With attributes --}}

{{-- From model directly --}}

{{-- As URL only --}}

{{-- As picture element --}}

{{-- With specific crop --}}

```

**Component Attributes:**

AttributeTypeDescription`src`MediaBuilderFrom `$model->img('group')``model`objectModel instance (with `group`)`group`stringMedia group name`subgroup`stringMedia subgroup filter`size`stringImage size (sm, md, lg, xl)`type`stringOutput: img, url, picture, background`class`stringCSS classes`alt`stringAlt text`id`stringHTML id`lazy`boolEnable lazy loading`crop`stringSpecific crop key`default`stringFallback URL`noDefault`boolDisable fallback`data`arrayData attributes`breakpoints`arrayFor picture element### Direct Media Model Usage

[](#direct-media-model-usage)

```
// If you have a Media model directly
$media = Media::find(1);

$url = $media->url('lg');
$url = $media->crop('banner');
$html = $media->img('md', ['class' => 'rounded']);

// Fluent builder
$html = $media->builder()
    ->class('rounded')
    ->lazy()
    ->img();
```

---

Uploading Media
---------------

[](#uploading-media)

### Upload Component

[](#upload-component)

```

```

With options:

```

```

### Stored Media Display (Admin)

[](#stored-media-display-admin)

```

```

### Dynamic Subgroups in the Upload UI

[](#dynamic-subgroups-in-the-upload-ui)

Mediaclass stores an optional `subgroup` on each media row. You can enable an admin-side subgroup selector for an uploadable group so editors can assign each uploaded image to a preset subgroup without creating separate upload slots.

Configure presets globally:

```
// config/mfw-mediaclass.php
'subgroups' => [
    'count' => 5,
    'label' => 'Group',
    'empty_label' => 'Normal flow',
    'key_prefix' => 'group_',
    'groups' => [
        'gallery' => true,
    ],
],
```

Or define explicit labels:

```
'subgroups' => [
    'groups' => [
        'gallery' => [
            'options' => [
                'featured' => 'Featured',
                'flow' => 'Flow',
            ],
        ],
    ],
],
```

You can also define subgroup presets on a model group:

```
public function mediaclassSettings(): array
{
    return [
        'gallery' => [
            'label' => 'Gallery',
            'width' => 1200,
            'height' => 800,
            'subgroups' => [
                'count' => 5,
                'label' => 'Group',
            ],
        ],
    ];
}
```

When subgroups are enabled, `` injects a select into each native uploaded image row. The select saves through the package AJAX route:

```
POST /mediaclass-ajax
action=saveSubgroup

```

The response triggers a jQuery document event:

```
$(document).on('mediaclass:subgroup-saved', function (event, result, uploadable, select) {
    // result.group, result.media_id, result.subgroup, result.uses_subgroups
});
```

Frontend rendering remains application-owned. A common pattern is to render media with `subgroup = null` in normal flow, and render media sharing the same subgroup as a grid.

### Processing After Save

[](#processing-after-save)

```
$post = Post::create($payload);
$post->processMedia();
```

---

Configuration
-------------

[](#configuration)

Published to `config/mfw-mediaclass.php`:

```
return [
    'disk' => 'public',
    'dimensions' => [
        'xl' => ['width' => 1920, 'height' => 1080],
        'lg' => ['width' => 1400, 'height' => 788],
        'md' => ['width' => 700,  'height' => 394],
        'sm' => ['width' => 400,  'height' => 225],
    ],
];
```

---

Cropping
--------

[](#cropping)

Define cropable settings in your model:

```
public function mediaclassSettings(): array
{
    return [
        'cover' => [
            'width' => 1600,
            'height' => 900,
            'cropable' => true,  // Single crop using group dimensions
        ],
        'banner' => [
            'width' => 1920,
            'height' => 400,
            'cropable' => [
                'desktop' => [1920, 400],
                'mobile' => [800, 400],
            ],
        ],
    ];
}
```

Access cropped versions:

```
// Single crop
$url = $post->img('cover')->crop('cover')->url();

// Multiple crops
$desktop = $post->img('banner')->crop('desktop')->url();
$mobile = $post->img('banner')->crop('mobile')->url();

// Check if crop exists
if ($post->img('cover')->hasCrop('cover')) {
    // ...
}
```

---

Ghost Media
-----------

[](#ghost-media)

For media not attached to a specific model instance:

```

```

Retrieve ghost media:

```
use MetaFramework\Mediaclass\Mediaclass;

$url = Mediaclass::ghostUrl(Post::class, 'cover', 'sm', '/fallback.png');
```

---

External Video Embeds
---------------------

[](#external-video-embeds)

External video media and supported oEmbed URLs can be rendered through the Mediaclass facade or helper:

```
use MetaFramework\Mediaclass\Facades\MediaclassFacade;

$html = MediaclassFacade::embed($media, ['loading' => 'lazy']);
$html = mediaclass_embed('https://www.youtube.com/watch?v=...');
```

Embeds default to `560 × 315`. External video media store their display dimensions in the media `storable` data. The uploader UI supports a pixel width or a responsive `100%` width:

```
$media->storable = [
    'url' => 'https://www.youtube.com/watch?v=...',
    'embed_width' => '100%',
    'embed_height' => 315,
];
```

Explicit helper options override the stored dimensions.

The back-office uploader stores the provider thumbnail when available. Existing external videos resolve and cache their oEmbed thumbnail on first display. Video previews open in the CDN-hosted LightGallery viewer and autoplay through its video plugin.

Unsupported URLs and provider failures return an empty `HtmlString`.

---

Legacy API
----------

[](#legacy-api)

The original Parser/Printer classes are still available for backward compatibility:

```
use MetaFramework\Mediaclass\Mediaclass;
use MetaFramework\Mediaclass\Printer;

// Fetch and parse
$parser = (new Mediaclass())->forModel($post, 'cover')->first();
$url = $parser->url;

// Render with Printer
$html = (new Printer($parser))
    ->setClass('rounded')
    ->setLoading('lazy')
    ->img('md');
```

---

Storage Layout
--------------

[](#storage-layout)

- Regular: `{model}/{id}/{width}_{filename}.{ext}`
- Ghost: `{model}/{width}_{filename}.{ext}`
- Crops: `{model}/{id}/cropped_{key}_{filename}.{ext}`

Routes
------

[](#routes)

- `POST /mediaclass-ajax` - Upload/delete/crop actions
- `GET /mediaclass/cropable/{media}` - Crop UI

Testing
-------

[](#testing)

```
composer install
composer test
```

Requirements
------------

[](#requirements)

- PHP 8.3+
- Laravel 11+
- Intervention Image

###  Health Score

49

—

FairBetter than 94% of packages

Maintenance100

Actively maintained with recent releases

Popularity12

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity63

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~3 days

Total

51

Last Release

1d ago

Major Versions

0.x-dev → 1.0.02026-06-14

### Community

Maintainers

![](https://www.gravatar.com/avatar/bd427eef37726c93700471f509dcb5bd190be25cfa3aa01e388e117d6a353b71?d=identicon)[aboleon](/maintainers/aboleon)

---

Top Contributors

[![aboleon](https://avatars.githubusercontent.com/u/86931678?v=4)](https://github.com/aboleon "aboleon (56 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/aboleon-metaframework-mediaclass/health.svg)

```
[![Health](https://phpackages.com/badges/aboleon-metaframework-mediaclass/health.svg)](https://phpackages.com/packages/aboleon-metaframework-mediaclass)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M346](/packages/psalm-plugin-laravel)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9762.4M131](/packages/roots-acorn)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k15.1M132](/packages/laravel-pulse)[pressbooks/pressbooks

Pressbooks is an open source book publishing tool built on a WordPress multisite platform. Pressbooks outputs books in multiple formats, including PDF, EPUB, web, and a variety of XML flavours, using a theming/templating system, driven by CSS.

45444.2k1](/packages/pressbooks-pressbooks)[flarum/core

Delightfully simple forum software.

201.4M2.3k](/packages/flarum-core)[api-platform/laravel

API Platform support for Laravel

58171.6k14](/packages/api-platform-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
