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/en-GB/plg_media-action_crop.ini language/en-GB/plg_media-action_crop.sys.ini + + +
+ +
+ + + + + + + + + +
+
+
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" - > - + default="10/9" + > + - - - - + + + + - - - - + + + + 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(''); + $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();