Skip to content
Open
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
2378ef9
make media action crop aspect ratio configurable
hans2103 Nov 7, 2025
c5ecf44
:art: code styling
hans2103 Nov 7, 2025
0a047f2
alpha sort
hans2103 Nov 7, 2025
13e8aa0
fix year
hans2103 Nov 7, 2025
2da2336
apply __DEPLOY_VERSION__ to newly added code
hans2103 Nov 7, 2025
daedb7c
fix year for newly added code
hans2103 Nov 7, 2025
436e14b
another sort :-)
hans2103 Nov 7, 2025
9850572
remove fallback Copy
hans2103 Nov 7, 2025
59a8091
Update administrator/language/en-GB/plg_media-action_crop.ini
hans2103 Nov 7, 2025
1409e67
PHPCBF FIX
hans2103 Nov 7, 2025
3c83c9a
Joomla.sanitizeHtml() for security
hans2103 Nov 9, 2025
65fcf2e
Merge branch '6.1-dev' into feature/6.1-media-action-crop-aspect-ratio
hans2103 Nov 9, 2025
db05b82
removal obsolete title attribute
hans2103 Nov 19, 2025
8d18b13
Merge branch '6.1-dev' into feature/6.1-media-action-crop-aspect-ratio
hans2103 Nov 19, 2025
d79e1f7
Update plugins/media-action/crop/crop.xml
hans2103 Nov 19, 2025
f6fc598
Revert "Update plugins/media-action/crop/crop.xml"
hans2103 Nov 20, 2025
c76424f
:wheelchair: removing className text-success to make text readable in…
hans2103 Nov 20, 2025
361715c
solve rounding issue
hans2103 Nov 20, 2025
392019c
Revert "solve rounding issue"
hans2103 Nov 20, 2025
f751bd2
adjust default setting to meet same output as calculator
hans2103 Nov 20, 2025
0a2c785
Change button class from success to primary
hans2103 Nov 20, 2025
325034c
removal of calculator
hans2103 Nov 28, 2025
5dea148
Enhances crop aspect ratio handling
hans2103 Nov 28, 2025
aaeb7bd
Merge branch '6.1-dev' into feature/6.1-media-action-crop-aspect-ratio
hans2103 Nov 28, 2025
afbc7d9
Updates crop aspect ratio values
hans2103 Nov 28, 2025
89e061c
Merge remote-tracking branch 'origin/feature/6.1-media-action-crop-as…
hans2103 Nov 28, 2025
5d1ee29
Normalizes aspect ratio handling for wider compatibility
hans2103 Nov 30, 2025
cdc90c2
Merge branch '6.1-dev' into feature/6.1-media-action-crop-aspect-ratio
hans2103 Nov 30, 2025
92750e7
Update plugins/media-action/crop/form/crop.xml
hans2103 Dec 1, 2025
e7ff28e
Update plugins/media-action/crop/crop.xml
hans2103 Dec 1, 2025
16cdeee
Update plugins/media-action/crop/src/Extension/Crop.php
hans2103 Dec 1, 2025
e2c998a
Update plugins/media-action/crop/crop.xml
hans2103 Dec 1, 2025
3449a2d
Update build/media_source/plg_media-action_crop/js/crop.es6.js
hans2103 Dec 1, 2025
402a702
Update plugins/media-action/crop/src/Extension/Crop.php
hans2103 Dec 1, 2025
4db306f
Escapes aspect ratio values
hans2103 Dec 1, 2025
b6f6637
add _LABEL to the keys
hans2103 Dec 2, 2025
016baa5
Update plugins/media-action/crop/src/Extension/Crop.php
hans2103 Dec 2, 2025
4dd59c1
Update plugins/media-action/crop/src/Extension/Crop.php
hans2103 Dec 2, 2025
1c104a4
Update plugins/media-action/crop/src/Extension/Crop.php
hans2103 Dec 2, 2025
71ee773
reduce complexity
hans2103 Dec 2, 2025
4b1256d
compact code
hans2103 Dec 2, 2025
042a355
even more compact. :-)
hans2103 Dec 2, 2025
ed574b1
undo removed spaces
hans2103 Dec 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions administrator/language/en-GB/plg_media-action_crop.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
; Note : All ini files need to be saved as UTF-8

PLG_MEDIA-ACTION_CROP="Media Action - Crop"
PLG_MEDIA-ACTION_CROP_ASPECT_RATIOS_DESC="Configure custom aspect ratios for the crop tool. Each ratio needs a label (e.g. '16:9'), a value (e.g. '16/9' or '1'), and optionally a group (landscape/portrait)."
PLG_MEDIA-ACTION_CROP_ASPECT_RATIOS_LABEL="Aspect Ratios"
PLG_MEDIA-ACTION_CROP_LABEL="Crop"
PLG_MEDIA-ACTION_CROP_PARAM_ASPECT="Aspect Ratio"
PLG_MEDIA-ACTION_CROP_PARAM_DEFAULT_RATIO="Default aspect ratio"
Expand All @@ -15,4 +17,11 @@ PLG_MEDIA-ACTION_CROP_PARAM_WIDTH="Width"
PLG_MEDIA-ACTION_CROP_PARAM_X="X-Axis"
PLG_MEDIA-ACTION_CROP_PARAM_Y="Y-Axis"
PLG_MEDIA-ACTION_CROP_QUALITY="Quality"
PLG_MEDIA-ACTION_CROP_RATIO_GROUP_LABEL="Group"
PLG_MEDIA-ACTION_CROP_RATIO_GROUP_DESC="Optionally group this ratio under landscape or portrait"
PLG_MEDIA-ACTION_CROP_RATIO_GROUP_NONE="No group"
PLG_MEDIA-ACTION_CROP_RATIO_LABEL_LABEL="Label"
PLG_MEDIA-ACTION_CROP_RATIO_LABEL_DESC="The label to display for this ratio (e.g. '16:9', '4:3', 'thumbnail', etc.)"
PLG_MEDIA-ACTION_CROP_RATIO_VALUE_LABEL="Value"
PLG_MEDIA-ACTION_CROP_RATIO_VALUE_DESC="The aspect ratio as numerator/denominator or number (e.g. 16/9, 4/3, 1)."
PLG_MEDIA-ACTION_CROP_XML_DESCRIPTION="Adds crop functionality for images."
30 changes: 28 additions & 2 deletions build/media_source/plg_media-action_crop/js/crop.es6.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,32 @@ let formElements;
let activated = false;
let instance;

/**
* Parse aspect ratio value from string format to number
* Accepts either a plain number (e.g. "1") or numerator/denominator format (e.g. "16/9")
* @param {string} value - The aspect ratio value
* @returns {number} The calculated aspect ratio as a number
*/
const parseAspectRatio = (value) => {
if (!value || typeof value !== 'string' || value.trim() === '') {
return NaN;
}
if (value.includes('/')) {
const [numeratorStr, denominatorStr] = value.split('/');
const numerator = parseFloat(numeratorStr);
const denominator = parseFloat(denominatorStr);
if (
isNaN(numerator) ||
isNaN(denominator) ||
denominator === 0
) {
return NaN;
}
return numerator / denominator;
}
return parseFloat(value);
};

const addListeners = () => {
formElements.cropX.addEventListener('change', ({ currentTarget }) => {
instance.setData({ x: parseInt(currentTarget.value, 10) });
Expand All @@ -22,7 +48,7 @@ const addListeners = () => {
instance.setData({ height: parseInt(currentTarget.value, 10) });
});
formElements.aspectRatio.addEventListener('change', ({ currentTarget }) => {
instance.setAspectRatio(currentTarget.value);
instance.setAspectRatio(parseAspectRatio(currentTarget.value));
});
activated = true;
};
Expand Down Expand Up @@ -66,7 +92,7 @@ const init = (image) => {
addListeners();
}

instance.setAspectRatio(formElements.cropAspectRatioOption.value);
instance.setAspectRatio(parseAspectRatio(formElements.cropAspectRatioOption.value));
};

// Register the Events
Expand Down
50 changes: 50 additions & 0 deletions plugins/media-action/crop/crop.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,54 @@
<language tag="en-GB">language/en-GB/plg_media-action_crop.ini</language>
<language tag="en-GB">language/en-GB/plg_media-action_crop.sys.ini</language>
</languages>
<config>
<fields name="params">
<fieldset name="basic">
<field
name="aspect_ratios"
type="subform"
label="PLG_MEDIA-ACTION_CROP_ASPECT_RATIOS_LABEL"
description="PLG_MEDIA-ACTION_CROP_ASPECT_RATIOS_DESC"
multiple="true"
layout="joomla.form.field.subform.repeatable-table"
default='{"aspect_ratios0":{"label":"1:1","value":"1","group":""},"aspect_ratios1":{"label":"5:4","value":"5/4","group":"landscape"},"aspect_ratios2":{"label":"4:3","value":"4/3","group":"landscape"},"aspect_ratios3":{"label":"3:2","value":"3/2","group":"landscape"},"aspect_ratios4":{"label":"16:9","value":"16/9","group":"landscape"},"aspect_ratios5":{"label":"4:5","value":"4/5","group":"portrait"},"aspect_ratios6":{"label":"3:4","value":"3/4","group":"portrait"},"aspect_ratios7":{"label":"2:3","value":"2/3","group":"portrait"},"aspect_ratios8":{"label":"9:16","value":"9/16","group":"portrait"}}'
>
<form>
<field
name="label"
type="text"
label="PLG_MEDIA-ACTION_CROP_RATIO_LABEL_LABEL"
description="PLG_MEDIA-ACTION_CROP_RATIO_LABEL_DESC"
required="true"
filter="string"
/>
<field
name="value"
type="text"
label="PLG_MEDIA-ACTION_CROP_RATIO_VALUE_LABEL"
description="PLG_MEDIA-ACTION_CROP_RATIO_VALUE_DESC"
required="true"
pattern="([1-9]\d*/[1-9]\d*|[1-9]\d*)"
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern ([1-9]\d*/[1-9]\d*|[1-9]\d*) doesn't allow decimal values like "0.5" or "1.5" which the JavaScript parseAspectRatio function can handle via parseFloat(). Additionally, it doesn't allow single-digit values like "1" without additional digits. Consider updating the pattern to allow both fraction and decimal formats, e.g., ([1-9]\d*/[1-9]\d*|\d+\.?\d*) or (\d+/\d+|\d+\.?\d+) if you want to support zero-based values.

Suggested change
pattern="([1-9]\d*/[1-9]\d*|[1-9]\d*)"
pattern="(\d+/\d+|\d+\.?\d*)"

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not want to support zero-based values.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not want to support zero-based values.

that means you don't allow such values:

				<option class="crop-aspect-ratio-option" value="0.8">4:5</option>
				<option class="crop-aspect-ratio-option" value="0.75">3:4</option>
				<option class="crop-aspect-ratio-option" value="0.6666666666666667">2:3</option>
				<option class="crop-aspect-ratio-option" value="0.5625">9:16</option>

this options are all zero based.

I think we shouldn't force people to use the 1/2 since they might already get a 0.xyz value from another place and just want to copy paste it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we shouldn't force people to use the 1/2 since they might already get a 0.xyz value from another place and just want to copy paste it.

@HLeithner for real?
That is exactly what you suggested to do 4 days ago. Using a / to calculate implies adding as such too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You miss understand me, it's not a you MUST use x/y it's an optional thing. both should work. If I like to use 0.35 then that should be possible. But normally I would expect the more readable x/y variant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HLeithner at this moment Joomla does not allow to configure any aspect ratios. This PR provides a solution that people can configure aspect ratios. At first I added a calculator to create the required value for the aspect ratios.
After your suggestion, which was very valid, I have removed the calculator and moved the calculation of the required value to javascript. People just have to enter the aspect ratio as if they add it in css too..... with a slash between the two numbers.

This PR as it is right now works like a charm. If other kind of input is needed for better UI, please create a new PR when this is merged and adjust it. But please... continue with the PR as it is right now.

filter="string"
hint="9/16"
class="input-small"
size="10"
maxlength="10"
/>
<field
name="group"
type="list"
label="PLG_MEDIA-ACTION_CROP_RATIO_GROUP_LABEL"
description="PLG_MEDIA-ACTION_CROP_RATIO_GROUP_DESC"
default=""
>
<option value="">PLG_MEDIA-ACTION_CROP_RATIO_GROUP_NONE</option>
<option value="landscape">PLG_MEDIA-ACTION_CROP_PARAM_LANDSCAPE</option>
<option value="portrait">PLG_MEDIA-ACTION_CROP_PARAM_PORTRAIT</option>
</field>
</form>
</field>
</fieldset>
</fields>
</config>
</extension>
22 changes: 11 additions & 11 deletions plugins/media-action/crop/form/crop.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,22 +64,22 @@
label="PLG_MEDIA-ACTION_CROP_PARAM_ASPECT"
hiddenLabel="true"
class="crop-aspect-ratio-options"
default="1.111"
>
<option class="crop-aspect-ratio-option" value="1.111">PLG_MEDIA-ACTION_CROP_PARAM_DEFAULT_RATIO</option>
default="10/9"
>
<option class="crop-aspect-ratio-option" value="10/9">PLG_MEDIA-ACTION_CROP_PARAM_DEFAULT_RATIO</option>
<option class="crop-aspect-ratio-option" value="">PLG_MEDIA-ACTION_CROP_PARAM_NO_RATIO</option>
<option class="crop-aspect-ratio-option" value="1">1:1</option>
<group label="PLG_MEDIA-ACTION_CROP_PARAM_LANDSCAPE">
<option class="crop-aspect-ratio-option" value="1.25">5:4</option>
<option class="crop-aspect-ratio-option" value="1.3333333333333333">4:3</option>
<option class="crop-aspect-ratio-option" value="1.5">3:2</option>
<option class="crop-aspect-ratio-option" value="1.7777777777777777">16:9</option>
<option class="crop-aspect-ratio-option" value="5/4">5:4</option>
<option class="crop-aspect-ratio-option" value="4/3">4:3</option>
<option class="crop-aspect-ratio-option" value="3/2">3:2</option>
<option class="crop-aspect-ratio-option" value="16/9">16:9</option>
</group>
<group label="PLG_MEDIA-ACTION_CROP_PARAM_PORTRAIT">
<option class="crop-aspect-ratio-option" value="0.8">4:5</option>
<option class="crop-aspect-ratio-option" value="0.75">3:4</option>
<option class="crop-aspect-ratio-option" value="0.6666666666666667">2:3</option>
<option class="crop-aspect-ratio-option" value="0.5625">9:16</option>
<option class="crop-aspect-ratio-option" value="4/5">4:5</option>
<option class="crop-aspect-ratio-option" value="3/4">3:4</option>
<option class="crop-aspect-ratio-option" value="2/3">2:3</option>
<option class="crop-aspect-ratio-option" value="9/16">9:16</option>
</group>
</field>
</fieldset>
Expand Down
158 changes: 156 additions & 2 deletions plugins/media-action/crop/src/Extension/Crop.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
namespace Joomla\Plugin\MediaAction\Crop\Extension;

use Joomla\CMS\Application\CMSWebApplicationInterface;
use Joomla\CMS\Form\Form;
use Joomla\Component\Media\Administrator\Plugin\MediaActionPlugin;
use Joomla\Event\SubscriberInterface;

Expand All @@ -25,14 +26,167 @@
*/
final class Crop extends MediaActionPlugin implements SubscriberInterface
{
/**
* The form event. Load additional parameters when available into the field form.
* Override to dynamically inject aspect ratios from plugin settings.
*
* @param Form $form The form
* @param \stdClass $data The data (required by the event interface, not used in this implementation)
*
* @return void
*
* @since __DEPLOY_VERSION__
* @note The $data parameter is required by the Joomla event interface but is not used in this method.
*/
public function onContentPrepareForm(Form $form, $data): void
{
// Check if it is the right form
if ($form->getName() !== 'com_media.file') {
return;
}

$this->loadCss();
$this->loadJs();

// The file with the params for the edit view
$paramsFile = JPATH_PLUGINS . '/media-action/' . $this->_name . '/form/' . $this->_name . '.xml';

// When the file exists, load it into the form
if (file_exists($paramsFile)) {
$form->loadFile($paramsFile);

// Get the aspect ratios from plugin parameters
$aspectRatios = $this->params->get('aspect_ratios');

// Convert to array if needed (handles stdClass from Registry)
if (\is_object($aspectRatios)) {
$aspectRatios = (array) $aspectRatios;
}

// If we have custom aspect ratios, modify the form field
if (!empty($aspectRatios) && \is_array($aspectRatios)) {
$this->injectAspectRatios($form, $aspectRatios);
}
}
}

/**
* Inject custom aspect ratios into the crop form
*
* @param Form $form The form object
* @param array $aspectRatios The aspect ratios from plugin settings
*
* @return void
*
* @since __DEPLOY_VERSION__
*/
private function injectAspectRatios(Form $form, array $aspectRatios): void
{
// Get the aspectRatio field (try without group first, then with 'crop' group)
$field = $form->getField('aspectRatio');

if (!$field) {
$field = $form->getField('aspectRatio', 'crop');
}

if (!$field) {
return;
}

// Build new XML for the field with custom options
$xml = new \SimpleXMLElement('<field/>');
$xml->addAttribute('name', 'aspectRatio');
$xml->addAttribute('type', 'groupedlist');
$xml->addAttribute('label', 'PLG_MEDIA-ACTION_CROP_PARAM_ASPECT');
$xml->addAttribute('hiddenLabel', 'true');
$xml->addAttribute('class', 'crop-aspect-ratio-options');
$xml->addAttribute('default', '10/9');

// Add default options
$option = $xml->addChild('option', 'PLG_MEDIA-ACTION_CROP_PARAM_DEFAULT_RATIO');
$option->addAttribute('class', 'crop-aspect-ratio-option');
$option->addAttribute('value', '10/9');

$option = $xml->addChild('option', 'PLG_MEDIA-ACTION_CROP_PARAM_NO_RATIO');
$option->addAttribute('class', 'crop-aspect-ratio-option');
$option->addAttribute('value', '');

// Group ratios by landscape/portrait
$grouped = [
'' => [],
'landscape' => [],
'portrait' => [],
];

foreach ($aspectRatios as $ratio) {
// Convert individual ratio to array if it's an object
$ratio = (array) $ratio;

$label = $ratio['label'] ?? '';
$value = $ratio['value'] ?? '';
$group = strtolower(trim($ratio['group'] ?? ''));

if (empty($label) || $value === '' || $value === null) {
continue;
}

if (!isset($grouped[$group])) {
$grouped[$group] = [];
}

$grouped[$group][] = [
'label' => $label,
'value' => $value,
'group' => $group,
];
}

// Add ungrouped ratios
foreach ($grouped[''] as $ratio) {
$option = $xml->addChild('option', htmlspecialchars($ratio['label'], ENT_XML1, 'UTF-8'));
$option->addAttribute('class', 'crop-aspect-ratio-option');
$option->addAttribute('value', htmlspecialchars($ratio['value'], ENT_XML1, 'UTF-8'));
}

// Add landscape group
if (!empty($grouped['landscape'])) {
$group = $xml->addChild('group');
$group->addAttribute('label', 'PLG_MEDIA-ACTION_CROP_PARAM_LANDSCAPE');

foreach ($grouped['landscape'] as $ratio) {
$option = $group->addChild('option', htmlspecialchars($ratio['label'], ENT_XML1, 'UTF-8'));
$option->addAttribute('class', 'crop-aspect-ratio-option');
$option->addAttribute('value', htmlspecialchars($ratio['value'], ENT_XML1, 'UTF-8'));
}
}

// Add portrait group
if (!empty($grouped['portrait'])) {
$group = $xml->addChild('group');
$group->addAttribute('label', 'PLG_MEDIA-ACTION_CROP_PARAM_PORTRAIT');

foreach ($grouped['portrait'] as $ratio) {
$option = $group->addChild('option', htmlspecialchars($ratio['label'], ENT_XML1, 'UTF-8'));
$option->addAttribute('class', 'crop-aspect-ratio-option');
$option->addAttribute('value', htmlspecialchars($ratio['value'], ENT_XML1, 'UTF-8'));
}
}

// Replace the field in the form
// Try setting in default group first, then 'crop' group
if (!$form->setField($xml, null, true)) {
$form->setField($xml, 'crop', true);
}
}

/**
* Load the javascript files of the plugin.
*
* @return void
*
* @since 4.0.0
*/
protected function loadJs()
protected function loadJs(): void
{
parent::loadJs();

Expand All @@ -50,7 +204,7 @@ protected function loadJs()
*
* @since 4.0.0
*/
protected function loadCss()
protected function loadCss(): void
{
parent::loadCss();

Expand Down