Skip to content

[6.1] make media action crop aspect ratio configurable #3661

@jgerman-bot

Description

@jgerman-bot

New language relevant PR in upstream repo: joomla/joomla-cms#46421 Here are the upstream changes:

Click to expand the diff!
diff --git a/administrator/language/en-GB/plg_media-action_crop.ini b/administrator/language/en-GB/plg_media-action_crop.ini
index 57c9846ad67a0..9c5c13dd7d82c 100644
--- a/administrator/language/en-GB/plg_media-action_crop.ini
+++ b/administrator/language/en-GB/plg_media-action_crop.ini
@@ -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"
@@ -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."
diff --git a/build/media_source/plg_media-action_crop/js/crop.es6.js b/build/media_source/plg_media-action_crop/js/crop.es6.js
index ddeb2649181d3..68a9c3be071d6 100644
--- a/build/media_source/plg_media-action_crop/js/crop.es6.js
+++ b/build/media_source/plg_media-action_crop/js/crop.es6.js
@@ -8,6 +8,23 @@ 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 (typeof value !== 'string' || !value.trim()) {
+    return NaN;
+  }
+  if (value.includes('/')) {
+    const [numerator, denominator] = value.split('/').map(parseFloat);
+    return isNaN(numerator) || isNaN(denominator) || denominator === 0 ? NaN : numerator / denominator;
+  }
+  return parseFloat(value);
+};
+
 const addListeners = () => {
   formElements.cropX.addEventListener('change', ({ currentTarget }) => {
     instance.setData({ x: parseInt(currentTarget.value, 10) });
@@ -22,7 +39,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;
 };
@@ -66,7 +83,7 @@ const init = (image) => {
     addListeners();
   }
 
-  instance.setAspectRatio(formElements.cropAspectRatioOption.value);
+  instance.setAspectRatio(parseAspectRatio(formElements.cropAspectRatioOption.value));
 };
 
 // Register the Events
diff --git a/plugins/media-action/crop/crop.xml b/plugins/media-action/crop/crop.xml
index 4b914e679a0aa..88708674ab74b 100644
--- a/plugins/media-action/crop/crop.xml
+++ b/plugins/media-action/crop/crop.xml
@@ -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*)"
+							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>
diff --git a/plugins/media-action/crop/form/crop.xml b/plugins/media-action/crop/form/crop.xml
index 696bde1336c20..e2c8daee7f4ef 100644
--- a/plugins/media-action/crop/form/crop.xml
+++ b/plugins/media-action/crop/form/crop.xml
@@ -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>
diff --git a/plugins/media-action/crop/src/Extension/Crop.php b/plugins/media-action/crop/src/Extension/Crop.php
index 8f56d62876889..1ef09b18c8904 100644
--- a/plugins/media-action/crop/src/Extension/Crop.php
+++ b/plugins/media-action/crop/src/Extension/Crop.php
@@ -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;
 
@@ -25,6 +26,159 @@
  */
 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.
      *
@@ -32,7 +186,7 @@ final class Crop extends MediaActionPlugin implements SubscriberInterface
      *
      * @since   4.0.0
      */
-    protected function loadJs()
+    protected function loadJs(): void
     {
         parent::loadJs();
 
@@ -50,7 +204,7 @@ protected function loadJs()
      *
      * @since   4.0.0
      */
-    protected function loadCss()
+    protected function loadCss(): void
     {
         parent::loadCss();
 

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions