From 77a6f1839811b6ac714fe459d76edd8fd31e6afd Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Mon, 2 Jun 2025 18:49:35 +0530 Subject: [PATCH 01/47] initial commit for jacademyseo_opengraph plugin for joomla seo improvements --- plugins/system/jacademyseo_opengraph/jacademyseo_opengraph.xml | 0 plugins/system/jacademyseo_opengraph/services/provider.php | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 plugins/system/jacademyseo_opengraph/jacademyseo_opengraph.xml create mode 100644 plugins/system/jacademyseo_opengraph/services/provider.php diff --git a/plugins/system/jacademyseo_opengraph/jacademyseo_opengraph.xml b/plugins/system/jacademyseo_opengraph/jacademyseo_opengraph.xml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/plugins/system/jacademyseo_opengraph/services/provider.php b/plugins/system/jacademyseo_opengraph/services/provider.php new file mode 100644 index 00000000000..e69de29bb2d From eb2262704983ab91a87edd8b9cf9a57241b49837 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Tue, 3 Jun 2025 20:19:17 +0530 Subject: [PATCH 02/47] feat : added manifest and language files for jacademyseo_opengraph plugin --- .../jacademyseo_opengraph.php | 14 ++ .../jacademyseo_opengraph.xml | 134 ++++++++++++++++++ .../plg_system_jacademyseo_opengraph.ini | 67 +++++++++ .../plg_system_jacademyseo_opengraph.sys.ini | 16 +++ 4 files changed, 231 insertions(+) create mode 100644 plugins/system/jacademyseo_opengraph/jacademyseo_opengraph.php create mode 100644 plugins/system/jacademyseo_opengraph/language/en-GB/plg_system_jacademyseo_opengraph.ini create mode 100644 plugins/system/jacademyseo_opengraph/language/en-GB/plg_system_jacademyseo_opengraph.sys.ini diff --git a/plugins/system/jacademyseo_opengraph/jacademyseo_opengraph.php b/plugins/system/jacademyseo_opengraph/jacademyseo_opengraph.php new file mode 100644 index 00000000000..859074c4e2c --- /dev/null +++ b/plugins/system/jacademyseo_opengraph/jacademyseo_opengraph.php @@ -0,0 +1,14 @@ + + + PLG_SYSTEM_JACADEMYSEO_OPENGRAPH + JacademySEO Team + 2025-06 + (C) 2025 JacademySEO. All rights reserved. + GNU General Public License version 2 or later + support@jacademyseo.com + https://jacademyseo.com + 1.0.0 + PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_DESCRIPTION + + JacademySEO\Plugin\System\Jacademyseo_Opengraph + + + src + jacademyseo_opengraph.php + jacademyseo_opengraph.xml + + + + language/en-GB/plg_system_jacademyseo_opengraph.ini + language/en-GB/plg_system_jacademyseo_opengraph.sys.ini + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + +
+ + +
+
+ + + + + + +
diff --git a/plugins/system/jacademyseo_opengraph/language/en-GB/plg_system_jacademyseo_opengraph.ini b/plugins/system/jacademyseo_opengraph/language/en-GB/plg_system_jacademyseo_opengraph.ini new file mode 100644 index 00000000000..c7e14470dc7 --- /dev/null +++ b/plugins/system/jacademyseo_opengraph/language/en-GB/plg_system_jacademyseo_opengraph.ini @@ -0,0 +1,67 @@ +; JacademySEO Open Graph Plugin Language File +; Copyright (C) 2025 JacademySEO. All rights reserved. +; License GNU General Public License version 2 or later + +; Plugin Description +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_DESCRIPTION="JacademySEO Open Graph Plugin automatically generates Open Graph tags for improved social media sharing. Features hierarchical override system supporting global, category, and article-level metadata configuration." + +; Global OG Settings +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_GLOBAL_OG_SETTINGS="Global Open Graph Settings" + +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_ENABLE_OG_GENERATION="Enable OG Tag Generation" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_ENABLE_OG_GENERATION_DESC="Enable automatic generation of Open Graph meta tags for improved social media sharing." + +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_GLOBAL_OG_TITLE="Global OG Title" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_GLOBAL_OG_TITLE_DESC="Default Open Graph title used as fallback when no article or category-specific title is available. Leave empty to use site name." + +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_GLOBAL_OG_DESCRIPTION="Global OG Description" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_GLOBAL_OG_DESCRIPTION_DESC="Default Open Graph description used as fallback when no article or category-specific description is available. Maximum 200 characters." + +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_GLOBAL_OG_IMAGE="Global OG Image" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_GLOBAL_OG_IMAGE_DESC="Default Open Graph image used as fallback when no article or category-specific image is available. Recommended size: 1200x630 pixels." + +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_GLOBAL_OG_TYPE="Global OG Type" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_GLOBAL_OG_TYPE_DESC="Default Open Graph type for your website. 'Website' is recommended for most sites." + +; Override Settings +PLG_SYSTEM_JACADEMYSEO_OVERRIDE_SETTINGS="Override Settings" + +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_ENABLE_ARTICLE_OVERRIDE="Enable Article-Level Override" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_ENABLE_ARTICLE_OVERRIDE_DESC="Allow articles to have custom Open Graph metadata via custom fields. When enabled, article OG fields will take priority over category and global settings." + +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_ENABLE_CATEGORY_OVERRIDE="Enable Category-Level Override" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_ENABLE_CATEGORY_OVERRIDE_DESC="Allow categories to have custom Open Graph metadata. When enabled, category OG settings will take priority over global settings but will be overridden by article-specific settings." + +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_FALLBACK_TO_SITE_LOGO="Use Site Logo as Fallback" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_FALLBACK_TO_SITE_LOGO_DESC="When no OG image is specified, use the site's logo as fallback image. If disabled, no image tag will be generated." + +; Custom Fields Labels (for reference) +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_FIELD_OG_TITLE="Open Graph Title" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_FIELD_OG_TITLE_DESC="Custom title for Open Graph sharing. Leave empty to use article title." + +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_FIELD_OG_DESCRIPTION="Open Graph Description" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_FIELD_OG_DESCRIPTION_DESC="Custom description for Open Graph sharing. Leave empty to use article meta description or intro text." + +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_FIELD_OG_IMAGE="Open Graph Image" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_FIELD_OG_IMAGE_DESC="Custom image for Open Graph sharing. Leave empty to use article intro image or site logo." + +; Error Messages +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_ERROR_INVALID_CATEGORY="Invalid category specified for OG metadata" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_ERROR_SAVE_FAILED="Failed to save OG metadata" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_ERROR_DELETE_FAILED="Failed to delete OG metadata" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_ERROR_FIELD_TOO_LONG="Field value exceeds maximum length" + +; Success Messages +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_SUCCESS_SAVED="OG metadata saved successfully" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_SUCCESS_DELETED="OG metadata deleted successfully" + +; Information Messages +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_INFO_NO_ARTICLE="No article context available for OG tag generation" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_INFO_USING_FALLBACK="Using fallback values for OG tag generation" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_INFO_HIERARCHY_APPLIED="OG metadata hierarchy applied: %s level used" + +; Priority Levels +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_PRIORITY_ARTICLE="Article" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_PRIORITY_CATEGORY="Category" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_PRIORITY_GLOBAL="Global" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_PRIORITY_AUTO="Auto-generated" diff --git a/plugins/system/jacademyseo_opengraph/language/en-GB/plg_system_jacademyseo_opengraph.sys.ini b/plugins/system/jacademyseo_opengraph/language/en-GB/plg_system_jacademyseo_opengraph.sys.ini new file mode 100644 index 00000000000..0bc1ad0cee6 --- /dev/null +++ b/plugins/system/jacademyseo_opengraph/language/en-GB/plg_system_jacademyseo_opengraph.sys.ini @@ -0,0 +1,16 @@ +; JacademySEO Open Graph Plugin System Language File +; Copyright (C) 2025 JacademySEO. All rights reserved. +; License GNU General Public License version 2 or later + +; Plugin Name and Description for Extension Manager +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH="System - JacademySEO Open Graph" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_DESCRIPTION="Automatic Open Graph tag generation plugin for Joomla 6.x with hierarchical metadata override system. Improves social media sharing with intelligent fallback mechanisms." + +; Installation Messages +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_INSTALL_SUCCESS="JacademySEO Open Graph Plugin installed successfully!" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_UPDATE_SUCCESS="JacademySEO Open Graph Plugin updated successfully!" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_UNINSTALL_SUCCESS="JacademySEO Open Graph Plugin uninstalled successfully!" + +; Custom Fields Group +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_FIELDS_GROUP="Open Graph Metadata" +PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_FIELDS_GROUP_DESC="Custom fields for Open Graph metadata configuration" From ece1af59e1ba53a68871791f53d50adde8b35942 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Wed, 4 Jun 2025 10:20:12 +0530 Subject: [PATCH 03/47] fix : rename plugin to opengraph and update language files --- .../jacademyseo_opengraph.php | 14 ---- .../plg_system_jacademyseo_opengraph.ini | 67 ------------------- .../plg_system_jacademyseo_opengraph.sys.ini | 16 ----- .../language/en-GB/plg_system_opengraph.ini | 67 +++++++++++++++++++ .../en-GB/plg_system_opengraph.sys.ini | 16 +++++ .../opengraph.xml} | 63 ++++++++--------- .../services/provider.php | 0 .../src/Extension/jacademyseo_opengraph.php | 38 +++++++++++ 8 files changed, 149 insertions(+), 132 deletions(-) delete mode 100644 plugins/system/jacademyseo_opengraph/jacademyseo_opengraph.php delete mode 100644 plugins/system/jacademyseo_opengraph/language/en-GB/plg_system_jacademyseo_opengraph.ini delete mode 100644 plugins/system/jacademyseo_opengraph/language/en-GB/plg_system_jacademyseo_opengraph.sys.ini create mode 100644 plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini create mode 100644 plugins/system/opengraph/language/en-GB/plg_system_opengraph.sys.ini rename plugins/system/{jacademyseo_opengraph/jacademyseo_opengraph.xml => opengraph/opengraph.xml} (56%) rename plugins/system/{jacademyseo_opengraph => opengraph}/services/provider.php (100%) create mode 100644 plugins/system/opengraph/src/Extension/jacademyseo_opengraph.php diff --git a/plugins/system/jacademyseo_opengraph/jacademyseo_opengraph.php b/plugins/system/jacademyseo_opengraph/jacademyseo_opengraph.php deleted file mode 100644 index 859074c4e2c..00000000000 --- a/plugins/system/jacademyseo_opengraph/jacademyseo_opengraph.php +++ /dev/null @@ -1,14 +0,0 @@ - - PLG_SYSTEM_JACADEMYSEO_OPENGRAPH - JacademySEO Team + PLG_SYSTEM_OPENGRAPH + Joomla! Project 2025-06 - (C) 2025 JacademySEO. All rights reserved. + (C) 2025 Open Source Matters, Inc. GNU General Public License version 2 or later - support@jacademyseo.com - https://jacademyseo.com - 1.0.0 - PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_DESCRIPTION - - JacademySEO\Plugin\System\Jacademyseo_Opengraph + support@joomla.org + https://joomla.org + __DEPLOY_VERSION__ + PLG_SYSTEM_OPENGRAPH_DESCRIPTION + Joomla\Plugin\System\Opengraph - src - jacademyseo_opengraph.php - jacademyseo_opengraph.xml + src + opengraph.xml - language/en-GB/plg_system_jacademyseo_opengraph.ini - language/en-GB/plg_system_jacademyseo_opengraph.sys.ini + language/en-GB/plg_system_opengraph.ini + language/en-GB/plg_system_opengraph.sys.ini @@ -31,8 +29,8 @@ type="radio" class="btn-group btn-group-yesno" default="1" - label="PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_ENABLE_OG_GENERATION" - description="PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_ENABLE_OG_GENERATION_DESC" + label="PLG_SYSTEM_OPENGRAPH_ENABLE_OG_GENERATION" + description="PLG_SYSTEM_OPENGRAPH_ENABLE_OG_GENERATION_DESC" > @@ -41,8 +39,8 @@ @@ -50,8 +48,8 @@ @@ -69,8 +67,8 @@ name="global_og_type" type="list" default="website" - label="PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_GLOBAL_OG_TYPE" - description="PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_GLOBAL_OG_TYPE_DESC" + label="PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_TYPE" + description="PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_TYPE_DESC" > @@ -90,8 +88,8 @@ type="radio" class="btn-group btn-group-yesno" default="1" - label="PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_ENABLE_ARTICLE_OVERRIDE" - description="PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_ENABLE_ARTICLE_OVERRIDE_DESC" + label="PLG_SYSTEM_OPENGRAPH_ENABLE_ARTICLE_OVERRIDE" + description="PLG_SYSTEM_OPENGRAPH_ENABLE_ARTICLE_OVERRIDE_DESC" > @@ -102,8 +100,8 @@ type="radio" class="btn-group btn-group-yesno" default="1" - label="PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_ENABLE_CATEGORY_OVERRIDE" - description="PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_ENABLE_CATEGORY_OVERRIDE_DESC" + label="PLG_SYSTEM_OPENGRAPH_ENABLE_CATEGORY_OVERRIDE" + description="PLG_SYSTEM_OPENGRAPH_ENABLE_CATEGORY_OVERRIDE_DESC" > @@ -114,8 +112,8 @@ type="radio" class="btn-group btn-group-yesno" default="1" - label="PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_FALLBACK_TO_SITE_LOGO" - description="PLG_SYSTEM_JACADEMYSEO_OPENGRAPH_FALLBACK_TO_SITE_LOGO_DESC" + label="PLG_SYSTEM_OPENGRAPH_FALLBACK_TO_SITE_LOGO" + description="PLG_SYSTEM_OPENGRAPH_FALLBACK_TO_SITE_LOGO_DESC" > @@ -126,9 +124,4 @@ - - - - - diff --git a/plugins/system/jacademyseo_opengraph/services/provider.php b/plugins/system/opengraph/services/provider.php similarity index 100% rename from plugins/system/jacademyseo_opengraph/services/provider.php rename to plugins/system/opengraph/services/provider.php diff --git a/plugins/system/opengraph/src/Extension/jacademyseo_opengraph.php b/plugins/system/opengraph/src/Extension/jacademyseo_opengraph.php new file mode 100644 index 00000000000..43c48f38f7d --- /dev/null +++ b/plugins/system/opengraph/src/Extension/jacademyseo_opengraph.php @@ -0,0 +1,38 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Plugin\System\Opengraph\Extension; + +use Joomla\CMS\Event\Application\BeforeCompileHeadEvent; +use Joomla\CMS\Plugin\CMSPlugin; +use Joomla\Event\SubscriberInterface; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + + +/** + * OpenGraph Metadata plugin. + * + * @since __DEPLOY_VERSION__ + */ + +final class PlgSystemOpengraph extends CMSPlugin implements SubscriberInterface +{ + + + public static function getSubscribedEvents(): array + { + return [ + 'onBeforeCompileHead' => 'onBeforeCompileHead', + ]; + } + +} From 905cca3c2c1f5945942ff6a3695f5e4de0e9080f Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Wed, 4 Jun 2025 10:22:32 +0530 Subject: [PATCH 04/47] fix : file name changed to opengraph.php --- .../src/Extension/{jacademyseo_opengraph.php => opengraph.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugins/system/opengraph/src/Extension/{jacademyseo_opengraph.php => opengraph.php} (100%) diff --git a/plugins/system/opengraph/src/Extension/jacademyseo_opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php similarity index 100% rename from plugins/system/opengraph/src/Extension/jacademyseo_opengraph.php rename to plugins/system/opengraph/src/Extension/opengraph.php From 085ff659d62b626370bb66871754c84de0c39a01 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Thu, 5 Jun 2025 19:37:10 +0530 Subject: [PATCH 05/47] feat: added forms for article and category to opengraph plugin --- .../language/en-GB/plg_system_opengraph.ini | 109 +++++++++++++ plugins/system/opengraph/opengraph.xml | 1 + .../system/opengraph/services/provider.php | 46 ++++++ .../opengraph/src/Extension/opengraph.php | 122 ++++++++++++++- .../opengraph/src/Forms/Article/Article.xml | 104 +++++++++++++ .../opengraph/src/Forms/Category/Category.xml | 143 ++++++++++++++++++ 6 files changed, 522 insertions(+), 3 deletions(-) create mode 100644 plugins/system/opengraph/src/Forms/Article/Article.xml create mode 100644 plugins/system/opengraph/src/Forms/Category/Category.xml diff --git a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini index 8f1a6afcf95..46daa1e3c40 100644 --- a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini +++ b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini @@ -65,3 +65,112 @@ PLG_SYSTEM_OPENGRAPH_PRIORITY_ARTICLE="Article" PLG_SYSTEM_OPENGRAPH_PRIORITY_CATEGORY="Category" PLG_SYSTEM_OPENGRAPH_PRIORITY_GLOBAL="Global" PLG_SYSTEM_OPENGRAPH_PRIORITY_AUTO="Auto-generated" + +; Form Fieldsets +PLG_SYSTEM_OPENGRAPH_FIELDSET_ARTICLE="OpenGraph Settings" +PLG_SYSTEM_OPENGRAPH_FIELDSET_ARTICLE_DESC="Configure OpenGraph metadata for social media sharing. These settings override global defaults." + +PLG_SYSTEM_OPENGRAPH_FIELDSET_CATEGORY="OpenGraph Category Settings" +PLG_SYSTEM_OPENGRAPH_FIELDSET_CATEGORY_DESC="Configure OpenGraph metadata for this category. These settings will be used for articles in this category when no article-specific settings are defined." + +; Common Field Labels +PLG_SYSTEM_OPENGRAPH_OG_TITLE_LABEL="OpenGraph Title" +PLG_SYSTEM_OPENGRAPH_OG_TITLE_DESC="Custom title for social media sharing. Recommended length: 40-60 characters." +PLG_SYSTEM_OPENGRAPH_OG_TITLE_HINT="Enter a compelling title for social sharing" + +PLG_SYSTEM_OPENGRAPH_OG_DESCRIPTION_LABEL="OpenGraph Description" +PLG_SYSTEM_OPENGRAPH_OG_DESCRIPTION_DESC="Brief description for social media sharing. Recommended length: 120-160 characters." +PLG_SYSTEM_OPENGRAPH_OG_DESCRIPTION_HINT="Enter a brief, engaging description" + +PLG_SYSTEM_OPENGRAPH_OG_IMAGE_LABEL="OpenGraph Image" +PLG_SYSTEM_OPENGRAPH_OG_IMAGE_DESC="Image for social media sharing. Recommended size: 1200x630 pixels." + +PLG_SYSTEM_OPENGRAPH_OG_IMAGE_ALT_LABEL="OpenGraph Image Alt Text" +PLG_SYSTEM_OPENGRAPH_OG_IMAGE_ALT_DESC="Alternative text for the OpenGraph image for accessibility." + +PLG_SYSTEM_OPENGRAPH_OG_TYPE_LABEL="OpenGraph Type" +PLG_SYSTEM_OPENGRAPH_OG_TYPE_DESC="Content type for social media platforms." + +PLG_SYSTEM_OPENGRAPH_OG_URL_LABEL="Custom URL" +PLG_SYSTEM_OPENGRAPH_OG_URL_DESC="Override the canonical URL for sharing." +PLG_SYSTEM_OPENGRAPH_OG_URL_HINT="Leave empty to use the article's URL" + +PLG_SYSTEM_OPENGRAPH_ENABLE_LABEL="Enable OpenGraph" +PLG_SYSTEM_OPENGRAPH_ENABLE_DESC="Enable OpenGraph tags for this content." + +; OpenGraph Types +PLG_SYSTEM_OPENGRAPH_USE_DEFAULT="Use Default" +PLG_SYSTEM_OPENGRAPH_TYPE_ARTICLE="Article" +PLG_SYSTEM_OPENGRAPH_TYPE_WEBSITE="Website" +PLG_SYSTEM_OPENGRAPH_TYPE_BLOG="Blog" +PLG_SYSTEM_OPENGRAPH_TYPE_VIDEO="Video" +PLG_SYSTEM_OPENGRAPH_TYPE_MUSIC="Music" +PLG_SYSTEM_OPENGRAPH_TYPE_BOOK="Book" +PLG_SYSTEM_OPENGRAPH_TYPE_PROFILE="Profile" +PLG_SYSTEM_OPENGRAPH_TYPE_PRODUCT="Product" +PLG_SYSTEM_OPENGRAPH_TYPE_EVENT="Event" + +; Article Specific +PLG_SYSTEM_OPENGRAPH_ARTICLE_SECTION_LABEL="Article Section" +PLG_SYSTEM_OPENGRAPH_ARTICLE_SECTION_DESC="Section or category tag for the article." +PLG_SYSTEM_OPENGRAPH_ARTICLE_SECTION_HINT="e.g., Technology, Sports, News" + +PLG_SYSTEM_OPENGRAPH_PRIORITY_NOTE_LABEL="Priority Information" +PLG_SYSTEM_OPENGRAPH_PRIORITY_NOTE_DESC="These article settings take priority over category and global settings. Leave fields empty to inherit from higher levels." + +; Category Specific +PLG_SYSTEM_OPENGRAPH_CATEGORY_TITLE_DESC="Custom title for this category's social media sharing." +PLG_SYSTEM_OPENGRAPH_CATEGORY_TITLE_HINT="Enter a title that represents this category" + +PLG_SYSTEM_OPENGRAPH_CATEGORY_DESCRIPTION_DESC="Description for articles in this category when no article-specific description is set." +PLG_SYSTEM_OPENGRAPH_CATEGORY_DESCRIPTION_HINT="Describe what this category contains" + +PLG_SYSTEM_OPENGRAPH_CATEGORY_IMAGE_DESC="Default image for articles in this category." +PLG_SYSTEM_OPENGRAPH_CATEGORY_TYPE_DESC="Default content type for articles in this category." +PLG_SYSTEM_OPENGRAPH_CATEGORY_URL_DESC="Custom URL for this category page." +PLG_SYSTEM_OPENGRAPH_CATEGORY_URL_HINT="Leave empty to use the category's URL" +PLG_SYSTEM_OPENGRAPH_CATEGORY_ENABLE_DESC="Enable OpenGraph tags for this category and its articles." + +PLG_SYSTEM_OPENGRAPH_INHERIT_PARENT_LABEL="Inherit from Parent" +PLG_SYSTEM_OPENGRAPH_INHERIT_PARENT_DESC="Inherit OpenGraph settings from parent category when available." + +PLG_SYSTEM_OPENGRAPH_DEFAULT_ARTICLES_LABEL="Default for Articles" +PLG_SYSTEM_OPENGRAPH_DEFAULT_ARTICLES_DESC="Use these settings as defaults for articles in this category." + +PLG_SYSTEM_OPENGRAPH_CATEGORY_SPECIFIC_LABEL="Category-Specific Options" + +PLG_SYSTEM_OPENGRAPH_SHOW_CATEGORY_LABEL="Show Category in Articles" +PLG_SYSTEM_OPENGRAPH_SHOW_CATEGORY_DESC="Include category name in article OpenGraph metadata." + +PLG_SYSTEM_OPENGRAPH_USE_KEYWORDS_LABEL="Use Category Keywords" +PLG_SYSTEM_OPENGRAPH_USE_KEYWORDS_DESC="Include category keywords in OpenGraph metadata." + +PLG_SYSTEM_OPENGRAPH_INHERITANCE_NOTE_LABEL="Inheritance Information" +PLG_SYSTEM_OPENGRAPH_INHERITANCE_NOTE_DESC="Category settings are inherited by articles unless overridden at the article level. Global settings serve as the final fallback." + +; Character Counters +PLG_SYSTEM_OPENGRAPH_TITLE_COUNTER_DESC="Character counter for title optimization" +PLG_SYSTEM_OPENGRAPH_DESCRIPTION_COUNTER_DESC="Character counter for description optimization" + +; Note Fields +PLG_SYSTEM_OPENGRAPH_CATEGORY_NOTE_LABEL="Category Settings Note" +PLG_SYSTEM_OPENGRAPH_CATEGORY_NOTE_DESC="These settings apply to this category and can be inherited by articles within it." + +; Additional Missing Constants +PLG_SYSTEM_OPENGRAPH_CATEGORY_SETTINGS="Category OpenGraph Settings" +PLG_SYSTEM_OPENGRAPH_GLOBAL_SETTINGS="Global OpenGraph Settings" +PLG_SYSTEM_OPENGRAPH_ARTICLE_SETTINGS="Article OpenGraph Settings" + +; Default values text +PLG_SYSTEM_OPENGRAPH_DEFAULT_TITLE="Use site title" +PLG_SYSTEM_OPENGRAPH_DEFAULT_DESCRIPTION="Use site description" +PLG_SYSTEM_OPENGRAPH_DEFAULT_IMAGE="Use site logo" + +; Status messages for category fields +PLG_SYSTEM_OPENGRAPH_CATEGORY_ENABLED="OpenGraph enabled for this category" +PLG_SYSTEM_OPENGRAPH_CATEGORY_DISABLED="OpenGraph disabled for this category" +PLG_SYSTEM_OPENGRAPH_CATEGORY_INHERIT="Inherit from global settings" + +; Help text for complex fields +PLG_SYSTEM_OPENGRAPH_TYPE_HELP="Select the content type that best describes this category. This affects how social media platforms display shared links." +PLG_SYSTEM_OPENGRAPH_IMAGE_HELP="Upload an image that represents this category. Recommended dimensions: 1200x630 pixels for optimal display on social media." diff --git a/plugins/system/opengraph/opengraph.xml b/plugins/system/opengraph/opengraph.xml index ca60deaa0a3..d8b05d5f00f 100644 --- a/plugins/system/opengraph/opengraph.xml +++ b/plugins/system/opengraph/opengraph.xml @@ -13,6 +13,7 @@ src + services opengraph.xml diff --git a/plugins/system/opengraph/services/provider.php b/plugins/system/opengraph/services/provider.php index e69de29bb2d..12732800dc6 100644 --- a/plugins/system/opengraph/services/provider.php +++ b/plugins/system/opengraph/services/provider.php @@ -0,0 +1,46 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +\defined('_JEXEC') or die; + +use Joomla\CMS\Extension\PluginInterface; +use Joomla\CMS\Factory; +use Joomla\CMS\Plugin\PluginHelper; +use Joomla\DI\Container; +use Joomla\DI\ServiceProviderInterface; +use Joomla\Event\DispatcherInterface; +use Joomla\Plugin\System\Opengraph\Extension\Opengraph; + +return new class () implements ServiceProviderInterface { + /** + * Registers the service provider with a DI container. + * + * @param Container $container The DI container. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function register(Container $container) + { + $container->set( + PluginInterface::class, + function (Container $container) { + $plugin = new Opengraph( + $container->get(DispatcherInterface::class), + (array) PluginHelper::getPlugin('system', 'opengraph') + ); + $plugin->setApplication(Factory::getApplication()); + + return $plugin; + } + ); + } +}; diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 43c48f38f7d..193b077cac2 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -9,9 +9,12 @@ namespace Joomla\Plugin\System\Opengraph\Extension; +use Joomla\CMS\Application\CMSApplication; use Joomla\CMS\Event\Application\BeforeCompileHeadEvent; +use Joomla\CMS\Event\Model\PrepareFormEvent; use Joomla\CMS\Plugin\CMSPlugin; use Joomla\Event\SubscriberInterface; +use Joomla\CMS\Form\Form; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -24,15 +27,128 @@ * @since __DEPLOY_VERSION__ */ -final class PlgSystemOpengraph extends CMSPlugin implements SubscriberInterface +final class Opengraph extends CMSPlugin implements SubscriberInterface { - - + /** + * Returns an array of events this subscriber will listen to. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ public static function getSubscribedEvents(): array { return [ 'onBeforeCompileHead' => 'onBeforeCompileHead', + 'onContentPrepareForm' => 'onContentPrepareForm', ]; } + /** + * Application object + * + * @var CMSApplication + * @since __DEPLOY_VERSION__ + */ + protected $app; + + /** + * Load the language file on instantiation. + * + * @var boolean + * @since __DEPLOY_VERSION__ + */ + protected $autoloadLanguage = true; + + /** + * Method to handle the onBeforeCompileHead event + * + * @param BeforeCompileHeadEvent $event The event object + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function onBeforeCompileHead(BeforeCompileHeadEvent $event) + { + $app = $this->getApplication(); + + // Only run in frontend + if (!$app->isClient('site')) { + return; + } + + // Will add OpenGraph meta tags logic here in the future + // This method will be called to generate OpenGraph tags + } + + /** + * Method to add OpenGraph fields to content forms + * + * @param PrepareFormEvent $event The event object. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function onContentPrepareForm(PrepareFormEvent $event): void + { + $app = $this->getApplication(); + + if (!$app->isClient('administrator')) { + return; + } + + $form = $event->getForm(); + $data = $event->getData(); + + $name = $form->getName(); + + + + // Check if it's the article form + if ($name === 'com_content.article') { + $xmlFile = __DIR__ . '/../Forms/Article/Article.xml'; + if (file_exists($xmlFile)) { + $result = $form->loadFile($xmlFile, false); + + } + } + + // Check if it's the category form + if (strpos($name, 'com_categories.category') === 0) { + $extension = ''; + + // Extract extension from form name if it's concatenated + if ($name === 'com_categories.categorycom_content') { + $extension = 'com_content'; + } else { + // Try to get extension from different sources + if (is_object($data) && isset($data->extension)) { + $extension = $data->extension; + } elseif (is_array($data) && isset($data['extension'])) { + $extension = $data['extension']; + } else { + $extension = $form->getValue('extension'); + } + + // If still empty, try from request + if (empty($extension)) { + $input = $this->getApplication()->input; + $extension = $input->get('extension', '', 'cmd'); + } + } + + + + // Load form only for content categories + if ($extension === 'com_content') { + $xmlFile = __DIR__ . '/../Forms/Category/Category.xml'; + if (file_exists($xmlFile)) { + $result = $form->loadFile($xmlFile, false); + + } + } + } + } } diff --git a/plugins/system/opengraph/src/Forms/Article/Article.xml b/plugins/system/opengraph/src/Forms/Article/Article.xml new file mode 100644 index 00000000000..b684f7b79f7 --- /dev/null +++ b/plugins/system/opengraph/src/Forms/Article/Article.xml @@ -0,0 +1,104 @@ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
diff --git a/plugins/system/opengraph/src/Forms/Category/Category.xml b/plugins/system/opengraph/src/Forms/Category/Category.xml new file mode 100644 index 00000000000..a3af4538e22 --- /dev/null +++ b/plugins/system/opengraph/src/Forms/Category/Category.xml @@ -0,0 +1,143 @@ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
From 8cb7864768aa8be31676770ec56f956281745e51 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Thu, 12 Jun 2025 13:30:33 +0530 Subject: [PATCH 06/47] feat : add og tag injection into html --- .gitignore | 3 + .../language/en-GB/plg_system_opengraph.ini | 112 ++--------- plugins/system/opengraph/opengraph.xml | 18 +- .../opengraph/src/Extension/opengraph.php | 177 +++++++++--------- .../opengraph/src/Forms/Category/Category.xml | 143 -------------- .../Article.xml => forms/opengraph.xml} | 105 +++++++---- 6 files changed, 183 insertions(+), 375 deletions(-) delete mode 100644 plugins/system/opengraph/src/Forms/Category/Category.xml rename plugins/system/opengraph/src/{Forms/Article/Article.xml => forms/opengraph.xml} (55%) diff --git a/.gitignore b/.gitignore index b936fdbaa9b..8c0822a22e6 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,6 @@ cypress.config.mjs # WebAuthn FIDO metadata cache /plugins/system/webauthn/fido.jwt +cccsocialmedia +administrator/language/en-GB/en-GB.plg_system_cccsocialmedia.ini +administrator/language/en-GB/en-GB.plg_system_cccsocialmedia.sys.ini diff --git a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini index 46daa1e3c40..44cf6d67cc0 100644 --- a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini +++ b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini @@ -35,43 +35,10 @@ PLG_SYSTEM_OPENGRAPH_ENABLE_CATEGORY_OVERRIDE_DESC="Allow categories to have cus PLG_SYSTEM_OPENGRAPH_FALLBACK_TO_SITE_LOGO="Use Site Logo as Fallback" PLG_SYSTEM_OPENGRAPH_FALLBACK_TO_SITE_LOGO_DESC="When no OG image is specified, use the site's logo as fallback image. If disabled, no image tag will be generated." -; Custom Fields Labels (for reference) -PLG_SYSTEM_OPENGRAPH_FIELD_OG_TITLE="Open Graph Title" -PLG_SYSTEM_OPENGRAPH_FIELD_OG_TITLE_DESC="Custom title for Open Graph sharing. Leave empty to use article title." - -PLG_SYSTEM_OPENGRAPH_FIELD_OG_DESCRIPTION="Open Graph Description" -PLG_SYSTEM_OPENGRAPH_FIELD_OG_DESCRIPTION_DESC="Custom description for Open Graph sharing. Leave empty to use article meta description or intro text." - -PLG_SYSTEM_OPENGRAPH_FIELD_OG_IMAGE="Open Graph Image" -PLG_SYSTEM_OPENGRAPH_FIELD_OG_IMAGE_DESC="Custom image for Open Graph sharing. Leave empty to use article intro image or site logo." - -; Error Messages -PLG_SYSTEM_OPENGRAPH_ERROR_INVALID_CATEGORY="Invalid category specified for OG metadata" -PLG_SYSTEM_OPENGRAPH_ERROR_SAVE_FAILED="Failed to save OG metadata" -PLG_SYSTEM_OPENGRAPH_ERROR_DELETE_FAILED="Failed to delete OG metadata" -PLG_SYSTEM_OPENGRAPH_ERROR_FIELD_TOO_LONG="Field value exceeds maximum length" - -; Success Messages -PLG_SYSTEM_OPENGRAPH_SUCCESS_SAVED="OG metadata saved successfully" -PLG_SYSTEM_OPENGRAPH_SUCCESS_DELETED="OG metadata deleted successfully" - -; Information Messages -PLG_SYSTEM_OPENGRAPH_INFO_NO_ARTICLE="No article context available for OG tag generation" -PLG_SYSTEM_OPENGRAPH_INFO_USING_FALLBACK="Using fallback values for OG tag generation" -PLG_SYSTEM_OPENGRAPH_INFO_HIERARCHY_APPLIED="OG metadata hierarchy applied: %s level used" - -; Priority Levels -PLG_SYSTEM_OPENGRAPH_PRIORITY_ARTICLE="Article" -PLG_SYSTEM_OPENGRAPH_PRIORITY_CATEGORY="Category" -PLG_SYSTEM_OPENGRAPH_PRIORITY_GLOBAL="Global" -PLG_SYSTEM_OPENGRAPH_PRIORITY_AUTO="Auto-generated" - -; Form Fieldsets -PLG_SYSTEM_OPENGRAPH_FIELDSET_ARTICLE="OpenGraph Settings" +;fieldsets +PLG_SYSTEM_OPENGRAPH_FIELDSET_ARTICLE="OpenGraph" PLG_SYSTEM_OPENGRAPH_FIELDSET_ARTICLE_DESC="Configure OpenGraph metadata for social media sharing. These settings override global defaults." -PLG_SYSTEM_OPENGRAPH_FIELDSET_CATEGORY="OpenGraph Category Settings" -PLG_SYSTEM_OPENGRAPH_FIELDSET_CATEGORY_DESC="Configure OpenGraph metadata for this category. These settings will be used for articles in this category when no article-specific settings are defined." ; Common Field Labels PLG_SYSTEM_OPENGRAPH_OG_TITLE_LABEL="OpenGraph Title" @@ -87,6 +54,7 @@ PLG_SYSTEM_OPENGRAPH_OG_IMAGE_DESC="Image for social media sharing. Recommended PLG_SYSTEM_OPENGRAPH_OG_IMAGE_ALT_LABEL="OpenGraph Image Alt Text" PLG_SYSTEM_OPENGRAPH_OG_IMAGE_ALT_DESC="Alternative text for the OpenGraph image for accessibility." +PLG_SYSTEM_OPENGRAPH_OG_IMAGE_ALT_HINT="Enter a brief description of the image for accessibility" PLG_SYSTEM_OPENGRAPH_OG_TYPE_LABEL="OpenGraph Type" PLG_SYSTEM_OPENGRAPH_OG_TYPE_DESC="Content type for social media platforms." @@ -110,67 +78,25 @@ PLG_SYSTEM_OPENGRAPH_TYPE_PROFILE="Profile" PLG_SYSTEM_OPENGRAPH_TYPE_PRODUCT="Product" PLG_SYSTEM_OPENGRAPH_TYPE_EVENT="Event" -; Article Specific -PLG_SYSTEM_OPENGRAPH_ARTICLE_SECTION_LABEL="Article Section" -PLG_SYSTEM_OPENGRAPH_ARTICLE_SECTION_DESC="Section or category tag for the article." -PLG_SYSTEM_OPENGRAPH_ARTICLE_SECTION_HINT="e.g., Technology, Sports, News" +;advanced settings +PLG_SYSTEM_OPENGRAPH_SHOW_ADVANCED_LABEL="Show Advanced Settings" +PLG_SYSTEM_OPENGRAPH_SHOW_ADVANCED_DESC="Show advanced settings for OpenGraph metadata." -PLG_SYSTEM_OPENGRAPH_PRIORITY_NOTE_LABEL="Priority Information" -PLG_SYSTEM_OPENGRAPH_PRIORITY_NOTE_DESC="These article settings take priority over category and global settings. Leave fields empty to inherit from higher levels." +PLG_SYSTEM_OPENGRAPH_TWITTER_CARD_LABEL="Twitter Card Type" +PLG_SYSTEM_OPENGRAPH_TWITTER_CARD_DESC="Select the type of Twitter card to use for this content." -; Category Specific -PLG_SYSTEM_OPENGRAPH_CATEGORY_TITLE_DESC="Custom title for this category's social media sharing." -PLG_SYSTEM_OPENGRAPH_CATEGORY_TITLE_HINT="Enter a title that represents this category" +PLG_SYSTEM_OPENGRAPH_TWITTER_TITLE_LABEL="Twitter Title" +PLG_SYSTEM_OPENGRAPH_TWITTER_TITLE_DESC="Custom title for Twitter sharing. Recommended length: 70 characters." +PLG_SYSTEM_OPENGRAPH_TWITTER_TITLE_HINT="Enter a compelling title for Twitter sharing" -PLG_SYSTEM_OPENGRAPH_CATEGORY_DESCRIPTION_DESC="Description for articles in this category when no article-specific description is set." -PLG_SYSTEM_OPENGRAPH_CATEGORY_DESCRIPTION_HINT="Describe what this category contains" +PLG_SYSTEM_OPENGRAPH_TWITTER_DESC_LABEL="Twitter Description" +PLG_SYSTEM_OPENGRAPH_TWITTER_DESC_DESC="Custom description for Twitter sharing. Recommended length: 120-160 characters." +PLG_SYSTEM_OPENGRAPH_TWITTER_DESC_HINT="Enter a brief, engaging description" -PLG_SYSTEM_OPENGRAPH_CATEGORY_IMAGE_DESC="Default image for articles in this category." -PLG_SYSTEM_OPENGRAPH_CATEGORY_TYPE_DESC="Default content type for articles in this category." -PLG_SYSTEM_OPENGRAPH_CATEGORY_URL_DESC="Custom URL for this category page." -PLG_SYSTEM_OPENGRAPH_CATEGORY_URL_HINT="Leave empty to use the category's URL" -PLG_SYSTEM_OPENGRAPH_CATEGORY_ENABLE_DESC="Enable OpenGraph tags for this category and its articles." +PLG_SYSTEM_OPENGRAPH_TWITTER_IMAGE_LABEL="Twitter Image" +PLG_SYSTEM_OPENGRAPH_TWITTER_IMAGE_DESC="Image for Twitter sharing. Recommended size: 1200x630 pixels." -PLG_SYSTEM_OPENGRAPH_INHERIT_PARENT_LABEL="Inherit from Parent" -PLG_SYSTEM_OPENGRAPH_INHERIT_PARENT_DESC="Inherit OpenGraph settings from parent category when available." +PLG_SYSTEM_OPENGRAPH_FB_APP_ID_LABEL="Facebook App ID" +PLG_SYSTEM_OPENGRAPH_FB_APP_ID_DESC="Enter your Facebook App ID to enable Facebook Open Graph debugging." +PLG_SYSTEM_OPENGRAPH_FB_APP_ID_HINT="Enter your Facebook App ID to enable Facebook Open Graph debugging." -PLG_SYSTEM_OPENGRAPH_DEFAULT_ARTICLES_LABEL="Default for Articles" -PLG_SYSTEM_OPENGRAPH_DEFAULT_ARTICLES_DESC="Use these settings as defaults for articles in this category." - -PLG_SYSTEM_OPENGRAPH_CATEGORY_SPECIFIC_LABEL="Category-Specific Options" - -PLG_SYSTEM_OPENGRAPH_SHOW_CATEGORY_LABEL="Show Category in Articles" -PLG_SYSTEM_OPENGRAPH_SHOW_CATEGORY_DESC="Include category name in article OpenGraph metadata." - -PLG_SYSTEM_OPENGRAPH_USE_KEYWORDS_LABEL="Use Category Keywords" -PLG_SYSTEM_OPENGRAPH_USE_KEYWORDS_DESC="Include category keywords in OpenGraph metadata." - -PLG_SYSTEM_OPENGRAPH_INHERITANCE_NOTE_LABEL="Inheritance Information" -PLG_SYSTEM_OPENGRAPH_INHERITANCE_NOTE_DESC="Category settings are inherited by articles unless overridden at the article level. Global settings serve as the final fallback." - -; Character Counters -PLG_SYSTEM_OPENGRAPH_TITLE_COUNTER_DESC="Character counter for title optimization" -PLG_SYSTEM_OPENGRAPH_DESCRIPTION_COUNTER_DESC="Character counter for description optimization" - -; Note Fields -PLG_SYSTEM_OPENGRAPH_CATEGORY_NOTE_LABEL="Category Settings Note" -PLG_SYSTEM_OPENGRAPH_CATEGORY_NOTE_DESC="These settings apply to this category and can be inherited by articles within it." - -; Additional Missing Constants -PLG_SYSTEM_OPENGRAPH_CATEGORY_SETTINGS="Category OpenGraph Settings" -PLG_SYSTEM_OPENGRAPH_GLOBAL_SETTINGS="Global OpenGraph Settings" -PLG_SYSTEM_OPENGRAPH_ARTICLE_SETTINGS="Article OpenGraph Settings" - -; Default values text -PLG_SYSTEM_OPENGRAPH_DEFAULT_TITLE="Use site title" -PLG_SYSTEM_OPENGRAPH_DEFAULT_DESCRIPTION="Use site description" -PLG_SYSTEM_OPENGRAPH_DEFAULT_IMAGE="Use site logo" - -; Status messages for category fields -PLG_SYSTEM_OPENGRAPH_CATEGORY_ENABLED="OpenGraph enabled for this category" -PLG_SYSTEM_OPENGRAPH_CATEGORY_DISABLED="OpenGraph disabled for this category" -PLG_SYSTEM_OPENGRAPH_CATEGORY_INHERIT="Inherit from global settings" - -; Help text for complex fields -PLG_SYSTEM_OPENGRAPH_TYPE_HELP="Select the content type that best describes this category. This affects how social media platforms display shared links." -PLG_SYSTEM_OPENGRAPH_IMAGE_HELP="Upload an image that represents this category. Recommended dimensions: 1200x630 pixels for optimal display on social media." diff --git a/plugins/system/opengraph/opengraph.xml b/plugins/system/opengraph/opengraph.xml index d8b05d5f00f..7fd9fa83880 100644 --- a/plugins/system/opengraph/opengraph.xml +++ b/plugins/system/opengraph/opengraph.xml @@ -71,15 +71,15 @@ label="PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_TYPE" description="PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_TYPE_DESC" > - - - - - - - - - + + + + + + + + + diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 193b077cac2..f3e7ead558b 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -15,27 +15,22 @@ use Joomla\CMS\Plugin\CMSPlugin; use Joomla\Event\SubscriberInterface; use Joomla\CMS\Form\Form; +use Joomla\CMS\Document\HtmlDocument; +use Joomla\CMS\Table\Table; +use Joomla\Registry\Registry; +use Joomla\CMS\Document\Document; +use Joomla\CMS\Uri\Uri; -// phpcs:disable PSR1.Files.SideEffects -\defined('_JEXEC') or die; -// phpcs:enable PSR1.Files.SideEffects -/** - * OpenGraph Metadata plugin. - * - * @since __DEPLOY_VERSION__ - */ + +\defined('_JEXEC') or die; final class Opengraph extends CMSPlugin implements SubscriberInterface { - /** - * Returns an array of events this subscriber will listen to. - * - * @return array - * - * @since __DEPLOY_VERSION__ - */ + protected $app; + protected $autoloadLanguage = true; + public static function getSubscribedEvents(): array { return [ @@ -44,53 +39,43 @@ public static function getSubscribedEvents(): array ]; } - /** - * Application object - * - * @var CMSApplication - * @since __DEPLOY_VERSION__ - */ - protected $app; - - /** - * Load the language file on instantiation. - * - * @var boolean - * @since __DEPLOY_VERSION__ - */ - protected $autoloadLanguage = true; - - /** - * Method to handle the onBeforeCompileHead event - * - * @param BeforeCompileHeadEvent $event The event object - * - * @return void - * - * @since __DEPLOY_VERSION__ - */ - public function onBeforeCompileHead(BeforeCompileHeadEvent $event) + public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void { $app = $this->getApplication(); - // Only run in frontend if (!$app->isClient('site')) { return; } - // Will add OpenGraph meta tags logic here in the future - // This method will be called to generate OpenGraph tags + $input = $app->input; + $option = $input->get('option', '', 'cmd'); + $view = $input->get('view', '', 'cmd'); + $id = $input->getInt('id'); + + if ($option !== 'com_content' || $view !== 'article' || !$id) { + return; + } + + $table = Table::getInstance('Content'); + if (!$table->load($id)) { + return; + } + + $attribs = new Registry($table->attribs); + + if ($attribs->get('og_enabled', '') === '0') { + return; + } + + $document = $this->app->getDocument(); + + if (!$document instanceof HtmlDocument) { + return; + } + + $this->injectOpenGraphData($document, $attribs); } - /** - * Method to add OpenGraph fields to content forms - * - * @param PrepareFormEvent $event The event object. - * - * @return void - * - * @since __DEPLOY_VERSION__ - */ public function onContentPrepareForm(PrepareFormEvent $event): void { $app = $this->getApplication(); @@ -100,55 +85,61 @@ public function onContentPrepareForm(PrepareFormEvent $event): void } $form = $event->getForm(); - $data = $event->getData(); - $name = $form->getName(); - - - // Check if it's the article form if ($name === 'com_content.article') { - $xmlFile = __DIR__ . '/../Forms/Article/Article.xml'; - if (file_exists($xmlFile)) { - $result = $form->loadFile($xmlFile, false); - + $xml = __DIR__ . '/../forms/opengraph.xml'; + if (file_exists($xml)) { + $form->loadFile($xml, false); } } + } - // Check if it's the category form - if (strpos($name, 'com_categories.category') === 0) { - $extension = ''; - - // Extract extension from form name if it's concatenated - if ($name === 'com_categories.categorycom_content') { - $extension = 'com_content'; - } else { - // Try to get extension from different sources - if (is_object($data) && isset($data->extension)) { - $extension = $data->extension; - } elseif (is_array($data) && isset($data['extension'])) { - $extension = $data['extension']; - } else { - $extension = $form->getValue('extension'); - } - - // If still empty, try from request - if (empty($extension)) { - $input = $this->getApplication()->input; - $extension = $input->get('extension', '', 'cmd'); - } - } + private function injectOpenGraphData(Document $document, Registry $params): void + { + $this->setMetaData($document, 'og:title', $params->get('og_title'), 'property'); + $this->setMetaData($document, 'og:description', $params->get('og_description'), 'property'); + $this->setMetaData($document, 'og:type', $params->get('og_type'), 'property'); - // Load form only for content categories - if ($extension === 'com_content') { - $xmlFile = __DIR__ . '/../Forms/Category/Category.xml'; - if (file_exists($xmlFile)) { - $result = $form->loadFile($xmlFile, false); - } - } + $this->setMetaData($document, 'twitter:card', $params->get('twitter_card'), 'name'); + $this->setMetaData($document, 'twitter:title', $params->get('twitter_title'), 'name'); + $this->setMetaData($document, 'twitter:description', $params->get('twitter_description'), 'name'); + + $this->setMetaData($document, 'fb:app_id', $params->get('fb_app_id'), 'property'); + + $this->setOpenGraphImage( + $document, + $params->get('og_image'), + $params->get('og_image_alt'), + Uri::base() + ); + + + } + + private function setMetaData(Document $document, string $name, ?string $value, string $attributeType): void + { + if (!empty($value)) { + $document->setMetaData($name, $value, $attributeType); } } -} + + private function setOpenGraphImage(Document $document, ?string $image, ?string $alt = '', ?string $baseUrl = ''): void + { + if (empty($image)) { + return; + } + + $url = rtrim((string) $baseUrl, '/') . '/' . ltrim($image, '/'); + + $this->setMetaData($document, 'og:image', $url, 'property'); + $this->setMetaData($document, 'og:image:alt', $alt, 'property'); + $this->setMetaData($document, 'twitter:image', $url, 'name'); + $this->setMetaData($document, 'twitter:image:alt', $alt, 'name'); + } + + +} \ No newline at end of file diff --git a/plugins/system/opengraph/src/Forms/Category/Category.xml b/plugins/system/opengraph/src/Forms/Category/Category.xml deleted file mode 100644 index a3af4538e22..00000000000 --- a/plugins/system/opengraph/src/Forms/Category/Category.xml +++ /dev/null @@ -1,143 +0,0 @@ - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
diff --git a/plugins/system/opengraph/src/Forms/Article/Article.xml b/plugins/system/opengraph/src/forms/opengraph.xml similarity index 55% rename from plugins/system/opengraph/src/Forms/Article/Article.xml rename to plugins/system/opengraph/src/forms/opengraph.xml index b684f7b79f7..2f4ba1e85a3 100644 --- a/plugins/system/opengraph/src/Forms/Article/Article.xml +++ b/plugins/system/opengraph/src/forms/opengraph.xml @@ -1,37 +1,37 @@
-
+ + + + + + - - + hint="PLG_SYSTEM_OPENGRAPH_OG_TITLE_HINT" + class="form-control" + showon="og_override:1" /> + hint="PLG_SYSTEM_OPENGRAPH_OG_DESCRIPTION_HINT" + class="form-control" + showon="og_override:1" /> - + PLG_SYSTEM_OPENGRAPH_TYPE_EVENT - - + hint="PLG_SYSTEM_OPENGRAPH_OG_URL_HINT" + class="form-control" /> + JNO - + + + + + + + + + + + + + + + + + + + + + +
From ce47b06e7b4a4d66fd47ba37c3b68d16a965ab50 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Mon, 16 Jun 2025 16:46:32 +0530 Subject: [PATCH 07/47] fix : added docblocks and comments --- .gitignore | 1 - .../opengraph/src/Extension/opengraph.php | 81 +++++++++++++++++-- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 8c0822a22e6..78192e8a2cb 100644 --- a/.gitignore +++ b/.gitignore @@ -107,6 +107,5 @@ cypress.config.mjs # WebAuthn FIDO metadata cache /plugins/system/webauthn/fido.jwt -cccsocialmedia administrator/language/en-GB/en-GB.plg_system_cccsocialmedia.ini administrator/language/en-GB/en-GB.plg_system_cccsocialmedia.sys.ini diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index f3e7ead558b..79cf9b8affc 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -1,4 +1,5 @@ 'onContentPrepareForm', ]; } - + /** + * Handle the beforeCompileHead event. + * + * @param BeforeCompileHeadEvent $event + * + * @return void + */ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void { $app = $this->getApplication(); @@ -52,6 +87,9 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void $view = $input->get('view', '', 'cmd'); $id = $input->getInt('id'); + + // @todo: will be changed in future to support other components + if ($option !== 'com_content' || $view !== 'article' || !$id) { return; } @@ -76,6 +114,13 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void $this->injectOpenGraphData($document, $attribs); } + /** + * Handle the onContentPrepareForm event. + * + * @param PrepareFormEvent $event + * + * @return void + */ public function onContentPrepareForm(PrepareFormEvent $event): void { $app = $this->getApplication(); @@ -95,6 +140,14 @@ public function onContentPrepareForm(PrepareFormEvent $event): void } } + /** + * Inject the OpenGraph data into the document. + * + * @param Document $document + * @param Registry $params + * + * @return void + */ private function injectOpenGraphData(Document $document, Registry $params): void { @@ -116,10 +169,18 @@ private function injectOpenGraphData(Document $document, Registry $params): void $params->get('og_image_alt'), Uri::base() ); - - } + /** + * Set metadata tag in document. + * + * @param Document $document + * @param string $name + * @param string|null $value + * @param string $attributeType + * + * @return void + */ private function setMetaData(Document $document, string $name, ?string $value, string $attributeType): void { if (!empty($value)) { @@ -127,6 +188,16 @@ private function setMetaData(Document $document, string $name, ?string $value, s } } + /** + * Set OpenGraph Image tag in document. + * + * @param Document $document + * @param string|null $image + * @param string|null $alt + * @param string|null $baseUrl + * + * @return void + */ private function setOpenGraphImage(Document $document, ?string $image, ?string $alt = '', ?string $baseUrl = ''): void { if (empty($image)) { @@ -140,6 +211,4 @@ private function setOpenGraphImage(Document $document, ?string $image, ?string $ $this->setMetaData($document, 'twitter:image', $url, 'name'); $this->setMetaData($document, 'twitter:image:alt', $alt, 'name'); } - - -} \ No newline at end of file +} From 7ba727ce57b94ae1ff3e5d9867b7fa25e3bc1c42 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Wed, 18 Jun 2025 23:27:54 +0530 Subject: [PATCH 08/47] feat : form improvements - custom fields mapping and manual override fields --- .../language/en-GB/plg_system_opengraph.ini | 23 ++++ .../system/opengraph/src/forms/opengraph.xml | 123 ++++++++++-------- 2 files changed, 91 insertions(+), 55 deletions(-) diff --git a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini index 44cf6d67cc0..734c3c42e43 100644 --- a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini +++ b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini @@ -41,6 +41,9 @@ PLG_SYSTEM_OPENGRAPH_FIELDSET_ARTICLE_DESC="Configure OpenGraph metadata for soc ; Common Field Labels +PLG_SYSTEM_OPENGRAPH_SHOW_MANUAL_OVERRIDE_LABEL="Show Manual Override Settings" +PLG_SYSTEM_OPENGRAPH_SHOW_MANUAL_OVERRIDE_DESC="Enable this to manually set OG tags like title, description, and image." + PLG_SYSTEM_OPENGRAPH_OG_TITLE_LABEL="OpenGraph Title" PLG_SYSTEM_OPENGRAPH_OG_TITLE_DESC="Custom title for social media sharing. Recommended length: 40-60 characters." PLG_SYSTEM_OPENGRAPH_OG_TITLE_HINT="Enter a compelling title for social sharing" @@ -79,6 +82,8 @@ PLG_SYSTEM_OPENGRAPH_TYPE_PRODUCT="Product" PLG_SYSTEM_OPENGRAPH_TYPE_EVENT="Event" ;advanced settings +PLG_SYSTEM_OPENGRAPH_ADVANCED_SECTION ="Advanced Settings" + PLG_SYSTEM_OPENGRAPH_SHOW_ADVANCED_LABEL="Show Advanced Settings" PLG_SYSTEM_OPENGRAPH_SHOW_ADVANCED_DESC="Show advanced settings for OpenGraph metadata." @@ -100,3 +105,21 @@ PLG_SYSTEM_OPENGRAPH_FB_APP_ID_LABEL="Facebook App ID" PLG_SYSTEM_OPENGRAPH_FB_APP_ID_DESC="Enter your Facebook App ID to enable Facebook Open Graph debugging." PLG_SYSTEM_OPENGRAPH_FB_APP_ID_HINT="Enter your Facebook App ID to enable Facebook Open Graph debugging." + +; Field Selection (New Section) +PLG_SYSTEM_OPENGRAPH_FIELD_MAPPING_SECTION="Custom Field Mapping" +PLG_SYSTEM_OPENGRAPH_MANUAL_OVERRIDE_SECTION="Manual Override Values" + +PLG_SYSTEM_OPENGRAPH_TITLE_FIELD_LABEL="Title Custom Field" +PLG_SYSTEM_OPENGRAPH_TITLE_FIELD_DESC="Select a custom field to use as the OpenGraph title source." + +PLG_SYSTEM_OPENGRAPH_DESCRIPTION_FIELD_LABEL="Description Custom Field" +PLG_SYSTEM_OPENGRAPH_DESCRIPTION_FIELD_DESC="Select a custom field to use as the OpenGraph description source." + +PLG_SYSTEM_OPENGRAPH_IMAGE_FIELD_LABEL="Image Custom Field" +PLG_SYSTEM_OPENGRAPH_IMAGE_FIELD_DESC="Select a custom field to use as the OpenGraph image source." + +PLG_SYSTEM_OPENGRAPH_TYPE_FIELD_LABEL="Type Custom Field" +PLG_SYSTEM_OPENGRAPH_TYPE_FIELD_DESC="Select a custom field to use as the OpenGraph type source." + +PLG_SYSTEM_OPENGRAPH_NO_FIELD_SELECTED="No custom field selected" diff --git a/plugins/system/opengraph/src/forms/opengraph.xml b/plugins/system/opengraph/src/forms/opengraph.xml index 2f4ba1e85a3..48555fe422f 100644 --- a/plugins/system/opengraph/src/forms/opengraph.xml +++ b/plugins/system/opengraph/src/forms/opengraph.xml @@ -1,59 +1,82 @@ - -
- - - +
+ + + + + + + + + + + + +
+
+ - - + size="50" + showon="show_manual_override:1" /> - - + rows="3" + cols="50" + showon="show_manual_override:1" /> - + showon="show_manual_override:1" /> - - + hint="PLG_SYSTEM_OPENGRAPH_OG_IMAGE_ALT_HINT" + showon="show_manual_override:1" /> + class="form-select" + showon="show_manual_override:1"> @@ -65,71 +88,61 @@ - - - - + - - + default="1" + layout="joomla.form.field.radio.switcher" + showon="show_manual_override:1"> + - - + default="0" + showon="show_manual_override:1"> - - + + showon="show_manual_override:1[AND]show_advanced:1"> - - + hint="PLG_SYSTEM_OPENGRAPH_TWITTER_TITLE_HINT" + showon="show_manual_override:1[AND]show_advanced:1" /> - + showon="show_manual_override:1[AND]show_advanced:1" /> - + showon="show_manual_override:1[AND]show_advanced:1" /> - - - - + showon="show_manual_override:1[AND]show_advanced:1" />
- - +
+ \ No newline at end of file From 768a2bfee1fb013e08c34a94ac23451aa3fe4d4f Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Thu, 19 Jun 2025 19:08:14 +0530 Subject: [PATCH 09/47] feat : created opengraph service interface and field type for opengraph plugin --- .../src/Extension/ContentComponent.php | 29 +++++++- .../Opengraph/OpengraphServiceInterface.php | 31 ++++++++ .../opengraph/src/Extension/opengraph.php | 19 +++-- .../opengraph/src/Field/OpengraphField.php | 70 +++++++++++++++++++ .../system/opengraph/src/forms/opengraph.xml | 5 +- 5 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 libraries/src/Opengraph/OpengraphServiceInterface.php create mode 100644 plugins/system/opengraph/src/Field/OpengraphField.php diff --git a/administrator/components/com_content/src/Extension/ContentComponent.php b/administrator/components/com_content/src/Extension/ContentComponent.php index bd2861e1fa4..c65943a7a39 100644 --- a/administrator/components/com_content/src/Extension/ContentComponent.php +++ b/administrator/components/com_content/src/Extension/ContentComponent.php @@ -25,6 +25,7 @@ use Joomla\CMS\Helper\ContentHelper as LibraryContentHelper; use Joomla\CMS\HTML\HTMLRegistryAwareTrait; use Joomla\CMS\Language\Text; +use Joomla\CMS\Opengraph\OpengraphServiceInterface; use Joomla\CMS\Schemaorg\SchemaorgServiceInterface; use Joomla\CMS\Schemaorg\SchemaorgServiceTrait; use Joomla\CMS\Tag\TagServiceInterface; @@ -53,7 +54,8 @@ class ContentComponent extends MVCComponent implements SchemaorgServiceInterface, WorkflowServiceInterface, RouterServiceInterface, - TagServiceInterface + TagServiceInterface, + OpengraphServiceInterface { use AssociationServiceTrait; use RouterServiceTrait; @@ -204,6 +206,30 @@ public function getSchemaorgContexts(): array return $contexts; } + + /** + * Returns valid contexts for opengraph + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getOpengraphFields(): array + { + Factory::getLanguage()->load('com_content', JPATH_ADMINISTRATOR); + + $fields = [ + 'title' => Text::_('JGLOBAL_TITLE'), + 'articletext' => Text::_('COM_CONTENT_FIELD_ARTICLETEXT_LABEL'), + + // 'image_intro' => Text::_('COM_CONTENT_FIELD_INTRO_LABEL'), + // 'image_intro_alt' => Text::_('COM_CONTENT_FIELD_ARTICLETEXT_LABEL'), + ]; + + return $fields; + } + + /** * Returns valid contexts * @@ -295,7 +321,6 @@ public function getModelName($context): string return ucfirst($modelname); } - /** * Method to filter transitions by given id of state. * diff --git a/libraries/src/Opengraph/OpengraphServiceInterface.php b/libraries/src/Opengraph/OpengraphServiceInterface.php new file mode 100644 index 00000000000..ab2e6e284c4 --- /dev/null +++ b/libraries/src/Opengraph/OpengraphServiceInterface.php @@ -0,0 +1,31 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Opengraph; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * The Opengraph service. + * + * @since __DEPLOY_VERSION__ + */ +interface OpengraphServiceInterface +{ + /** + * Returns valid contexts. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getOpengraphFields(): array; +} diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 79cf9b8affc..bbb65553ae3 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -132,11 +132,20 @@ public function onContentPrepareForm(PrepareFormEvent $event): void $form = $event->getForm(); $name = $form->getName(); - if ($name === 'com_content.article') { - $xml = __DIR__ . '/../forms/opengraph.xml'; - if (file_exists($xml)) { - $form->loadFile($xml, false); - } + //todo : replace with interface check + $supportedForms = [ + 'com_content.article', + 'com_categories.categorycom_content', + 'com_menus.item' + ]; + + if (!in_array($name, $supportedForms, true)) { + return; + } + + $xml = __DIR__ . '/../forms/opengraph.xml'; + if (file_exists($xml)) { + $form->loadFile($xml, false); } } diff --git a/plugins/system/opengraph/src/Field/OpengraphField.php b/plugins/system/opengraph/src/Field/OpengraphField.php new file mode 100644 index 00000000000..6bb755992fb --- /dev/null +++ b/plugins/system/opengraph/src/Field/OpengraphField.php @@ -0,0 +1,70 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Plugin\System\Opengraph\Field; + +use Joomla\CMS\Extension\Component; +use Joomla\CMS\Factory; +use Joomla\CMS\Form\Field\ListField; +use Joomla\CMS\Opengraph\OpengraphServiceInterface; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Form Field class for the Joomla Platform. + * Supports a generic list of options. + * + * @since 1.7.0 + */ + +class OpengraphField extends ListField +{ + /** + * The form field type. + * + * @var string + * @since 1.7.0 + */ + protected $type = 'Opengraph'; + + + /** + * Method to get the field options. + * + * @return object[] The field option objects. + * + * @since 3.7.0 + */ + protected function getOptions() + { + + $app = Factory::getApplication(); + $options = parent::getOptions(); + //todo : make this more modular or flexible + $component = $app->bootComponent('com_content'); + if (!$component instanceof OpengraphServiceInterface) { + return $options; + } + + $fields = $component->getOpengraphFields(); + foreach ($fields as $value => $text) { + $tmp = [ + 'value' => $value, + 'text' => $text + + ]; + //todo : ordering of the fields + array_unshift($options, $tmp); + } + + return $options; + } +} diff --git a/plugins/system/opengraph/src/forms/opengraph.xml b/plugins/system/opengraph/src/forms/opengraph.xml index 48555fe422f..a863e1bdc18 100644 --- a/plugins/system/opengraph/src/forms/opengraph.xml +++ b/plugins/system/opengraph/src/forms/opengraph.xml @@ -3,9 +3,10 @@
+ label="PLG_SYSTEM_OPENGRAPH_FIELD_MAPPING_SECTION" + addfieldprefix="Joomla\Plugin\System\Opengraph\Field"> From 6b71fbfd4beb4fea6dd584e5b878e6f485e49f62 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Tue, 24 Jun 2025 10:24:14 +0530 Subject: [PATCH 10/47] feat: add opengraph field mapping and form changes --- .../src/Extension/ContentComponent.php | 33 +++++++++++++++++-- .../opengraph/src/Field/OpengraphField.php | 9 ++--- .../system/opengraph/src/forms/opengraph.xml | 13 ++++++-- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/administrator/components/com_content/src/Extension/ContentComponent.php b/administrator/components/com_content/src/Extension/ContentComponent.php index c65943a7a39..c16c1308f4a 100644 --- a/administrator/components/com_content/src/Extension/ContentComponent.php +++ b/administrator/components/com_content/src/Extension/ContentComponent.php @@ -219,11 +219,40 @@ public function getOpengraphFields(): array Factory::getLanguage()->load('com_content', JPATH_ADMINISTRATOR); $fields = [ + // Core Content Fields 'title' => Text::_('JGLOBAL_TITLE'), 'articletext' => Text::_('COM_CONTENT_FIELD_ARTICLETEXT_LABEL'), + 'alias' => Text::_('JFIELD_ALIAS_LABEL'), - // 'image_intro' => Text::_('COM_CONTENT_FIELD_INTRO_LABEL'), - // 'image_intro_alt' => Text::_('COM_CONTENT_FIELD_ARTICLETEXT_LABEL'), + // Image Fields + 'image_intro' => Text::_('COM_CONTENT_FIELD_INTRO_LABEL'), + 'image_intro_alt' => Text::_('COM_CONTENT_FIELD_IMAGE_ALT_LABEL'), + 'image_fulltext' => Text::_('COM_CONTENT_FIELD_FULL_LABEL'), + 'image_fulltext_alt' => Text::_('COM_CONTENT_FIELD_IMAGE_ALT_LABEL'), + + // Meta Fields for OpenGraph + 'metadesc' => Text::_('JFIELD_META_DESCRIPTION_LABEL'), + 'metakey' => Text::_('JFIELD_META_KEYWORDS_LABEL'), + + + // Date/Time Fields + 'created' => Text::_('COM_CONTENT_FIELD_CREATED_LABEL'), + 'modified' => Text::_('JGLOBAL_FIELD_MODIFIED_LABEL'), + + // Author Fields + 'created_by' => Text::_('COM_CONTENT_FIELD_CREATED_BY_LABEL'), + 'created_by_alias' => Text::_('COM_CONTENT_FIELD_CREATED_BY_ALIAS_LABEL'), + + // Category & Organization + 'catid' => Text::_('JCATEGORY'), + 'tags' => Text::_('JTAG'), + + // Localization + 'language' => Text::_('JFIELD_LANGUAGE_LABEL'), + + // Publishing Fields + 'publish_up' => Text::_('COM_CONTENT_FIELD_PUBLISH_UP_LABEL'), + 'publish_down' => Text::_('COM_CONTENT_FIELD_PUBLISH_DOWN_LABEL'), ]; return $fields; diff --git a/plugins/system/opengraph/src/Field/OpengraphField.php b/plugins/system/opengraph/src/Field/OpengraphField.php index 6bb755992fb..9d1a1cee37b 100644 --- a/plugins/system/opengraph/src/Field/OpengraphField.php +++ b/plugins/system/opengraph/src/Field/OpengraphField.php @@ -12,6 +12,7 @@ use Joomla\CMS\Extension\Component; use Joomla\CMS\Factory; use Joomla\CMS\Form\Field\ListField; +use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Opengraph\OpengraphServiceInterface; // phpcs:disable PSR1.Files.SideEffects @@ -56,13 +57,7 @@ protected function getOptions() $fields = $component->getOpengraphFields(); foreach ($fields as $value => $text) { - $tmp = [ - 'value' => $value, - 'text' => $text - - ]; - //todo : ordering of the fields - array_unshift($options, $tmp); + $options[] = HTMLHelper::_('select.option', $value, $text); } return $options; diff --git a/plugins/system/opengraph/src/forms/opengraph.xml b/plugins/system/opengraph/src/forms/opengraph.xml index a863e1bdc18..5394fd04ffc 100644 --- a/plugins/system/opengraph/src/forms/opengraph.xml +++ b/plugins/system/opengraph/src/forms/opengraph.xml @@ -13,21 +13,28 @@ + + + From 2fac558d034b83d520966ad66e16fe07f2923ffd Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Tue, 24 Jun 2025 16:23:09 +0530 Subject: [PATCH 11/47] fix : updated form and used the interface check to render form --- .../language/en-GB/plg_system_opengraph.ini | 24 +++++++++++-------- .../opengraph/src/Extension/opengraph.php | 12 +++------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini index 734c3c42e43..80f8b75c993 100644 --- a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini +++ b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini @@ -107,19 +107,23 @@ PLG_SYSTEM_OPENGRAPH_FB_APP_ID_HINT="Enter your Facebook App ID to enable Facebo ; Field Selection (New Section) -PLG_SYSTEM_OPENGRAPH_FIELD_MAPPING_SECTION="Custom Field Mapping" -PLG_SYSTEM_OPENGRAPH_MANUAL_OVERRIDE_SECTION="Manual Override Values" +PLG_SYSTEM_OPENGRAPH_FIELD_MAPPING_SECTION="Field Mapping Configuration" +PLG_SYSTEM_OPENGRAPH_MANUAL_OVERRIDE_SECTION="Manual Override Options" -PLG_SYSTEM_OPENGRAPH_TITLE_FIELD_LABEL="Title Custom Field" -PLG_SYSTEM_OPENGRAPH_TITLE_FIELD_DESC="Select a custom field to use as the OpenGraph title source." +PLG_SYSTEM_OPENGRAPH_TITLE_FIELD_LABEL="Title Source" +PLG_SYSTEM_OPENGRAPH_TITLE_FIELD_DESC="Select which article field to use for og:title meta tag" -PLG_SYSTEM_OPENGRAPH_DESCRIPTION_FIELD_LABEL="Description Custom Field" -PLG_SYSTEM_OPENGRAPH_DESCRIPTION_FIELD_DESC="Select a custom field to use as the OpenGraph description source." +PLG_SYSTEM_OPENGRAPH_DESCRIPTION_FIELD_LABEL="Description Source" +PLG_SYSTEM_OPENGRAPH_DESCRIPTION_FIELD_DESC="Select which article field to use for og:description meta tag. Meta description is preferred over article text." -PLG_SYSTEM_OPENGRAPH_IMAGE_FIELD_LABEL="Image Custom Field" -PLG_SYSTEM_OPENGRAPH_IMAGE_FIELD_DESC="Select a custom field to use as the OpenGraph image source." +PLG_SYSTEM_OPENGRAPH_IMAGE_FIELD_LABEL="Image Source" +PLG_SYSTEM_OPENGRAPH_IMAGE_FIELD_DESC="Select which article field to use for og:image meta tag" -PLG_SYSTEM_OPENGRAPH_TYPE_FIELD_LABEL="Type Custom Field" +PLG_SYSTEM_OPENGRAPH_IMAGE_ALT_FIELD_LABEL="Image Alt Text Source" +PLG_SYSTEM_OPENGRAPH_IMAGE_ALT_FIELD_DESC="Select which article field to use for og:image:alt meta tag for accessibility" + +PLG_SYSTEM_OPENGRAPH_TYPE_FIELD_LABEL="Type Field" PLG_SYSTEM_OPENGRAPH_TYPE_FIELD_DESC="Select a custom field to use as the OpenGraph type source." -PLG_SYSTEM_OPENGRAPH_NO_FIELD_SELECTED="No custom field selected" +PLG_SYSTEM_OPENGRAPH_NO_FIELD_SELECTED="-- No Field Selected --" + diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index bbb65553ae3..860099abe3c 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -20,6 +20,7 @@ use Joomla\CMS\Table\Table; use Joomla\Registry\Registry; use Joomla\CMS\Document\Document; +use Joomla\CMS\Opengraph\OpengraphServiceInterface; use Joomla\CMS\Uri\Uri; @@ -131,15 +132,8 @@ public function onContentPrepareForm(PrepareFormEvent $event): void $form = $event->getForm(); $name = $form->getName(); - - //todo : replace with interface check - $supportedForms = [ - 'com_content.article', - 'com_categories.categorycom_content', - 'com_menus.item' - ]; - - if (!in_array($name, $supportedForms, true)) { + $component = $app->bootComponent('com_content'); + if (!$component instanceof OpengraphServiceInterface) { return; } From 4b65016bb3be9e474c47b75f94a3fd7efb0aebd1 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Thu, 26 Jun 2025 13:10:27 +0530 Subject: [PATCH 12/47] Segregate fields for opengraph into different form dropdowns --- .../src/Extension/ContentComponent.php | 62 +++++++++++-------- .../opengraph/src/Field/OpengraphField.php | 18 +++--- .../system/opengraph/src/forms/opengraph.xml | 19 +++--- 3 files changed, 55 insertions(+), 44 deletions(-) diff --git a/administrator/components/com_content/src/Extension/ContentComponent.php b/administrator/components/com_content/src/Extension/ContentComponent.php index c16c1308f4a..bb11d5b4fea 100644 --- a/administrator/components/com_content/src/Extension/ContentComponent.php +++ b/administrator/components/com_content/src/Extension/ContentComponent.php @@ -219,40 +219,50 @@ public function getOpengraphFields(): array Factory::getLanguage()->load('com_content', JPATH_ADMINISTRATOR); $fields = [ - // Core Content Fields - 'title' => Text::_('JGLOBAL_TITLE'), - 'articletext' => Text::_('COM_CONTENT_FIELD_ARTICLETEXT_LABEL'), - 'alias' => Text::_('JFIELD_ALIAS_LABEL'), + 'text-fields' => [ + 'title' => Text::_('JGLOBAL_TITLE'), + 'articletext' => Text::_('COM_CONTENT_FIELD_ARTICLETEXT_LABEL'), + 'alias' => Text::_('JFIELD_ALIAS_LABEL'), + 'metadesc' => Text::_('JFIELD_META_DESCRIPTION_LABEL'), + ], - // Image Fields - 'image_intro' => Text::_('COM_CONTENT_FIELD_INTRO_LABEL'), - 'image_intro_alt' => Text::_('COM_CONTENT_FIELD_IMAGE_ALT_LABEL'), - 'image_fulltext' => Text::_('COM_CONTENT_FIELD_FULL_LABEL'), - 'image_fulltext_alt' => Text::_('COM_CONTENT_FIELD_IMAGE_ALT_LABEL'), + 'image-fields' => [ + 'image_intro' => Text::_('COM_CONTENT_FIELD_INTRO_LABEL'), + 'image_fulltext' => Text::_('COM_CONTENT_FIELD_FULL_LABEL'), - // Meta Fields for OpenGraph - 'metadesc' => Text::_('JFIELD_META_DESCRIPTION_LABEL'), - 'metakey' => Text::_('JFIELD_META_KEYWORDS_LABEL'), + ], + 'image-alt-fields' => [ + 'image_intro_alt' => Text::_('COM_CONTENT_FIELD_INTRO_LABEL') . ' - ' . Text::_('COM_CONTENT_FIELD_IMAGE_ALT_LABEL'), + 'image_fulltext_alt' => Text::_('COM_CONTENT_FIELD_FULL_LABEL') . ' - ' . Text::_('COM_CONTENT_FIELD_IMAGE_ALT_LABEL'), + ], - // Date/Time Fields - 'created' => Text::_('COM_CONTENT_FIELD_CREATED_LABEL'), - 'modified' => Text::_('JGLOBAL_FIELD_MODIFIED_LABEL'), + 'meta-fields' => [ + 'metadesc' => Text::_('JFIELD_META_DESCRIPTION_LABEL'), + 'metakey' => Text::_('JFIELD_META_KEYWORDS_LABEL'), + ], + + + 'locale-fields' => [ + 'language' => Text::_('JFIELD_LANGUAGE_LABEL'), + ], + + 'author-fields' => [ + 'created_by' => Text::_('COM_CONTENT_FIELD_CREATED_BY_LABEL'), + 'created_by_alias' => Text::_('COM_CONTENT_FIELD_CREATED_BY_ALIAS_LABEL'), + 'modified_by' => Text::_('JGLOBAL_FIELD_MODIFIED_BY_LABEL'), + + ], + 'date-fields' => [ + 'created' => Text::_('COM_CONTENT_FIELD_CREATED_LABEL'), + 'modified' => Text::_('JGLOBAL_FIELD_MODIFIED_LABEL'), + 'publish_up' => Text::_('COM_CONTENT_FIELD_PUBLISH_UP_LABEL'), + 'publish_down' => Text::_('COM_CONTENT_FIELD_PUBLISH_DOWN_LABEL'), + ], - // Author Fields - 'created_by' => Text::_('COM_CONTENT_FIELD_CREATED_BY_LABEL'), - 'created_by_alias' => Text::_('COM_CONTENT_FIELD_CREATED_BY_ALIAS_LABEL'), - // Category & Organization - 'catid' => Text::_('JCATEGORY'), - 'tags' => Text::_('JTAG'), - // Localization - 'language' => Text::_('JFIELD_LANGUAGE_LABEL'), - // Publishing Fields - 'publish_up' => Text::_('COM_CONTENT_FIELD_PUBLISH_UP_LABEL'), - 'publish_down' => Text::_('COM_CONTENT_FIELD_PUBLISH_DOWN_LABEL'), ]; return $fields; diff --git a/plugins/system/opengraph/src/Field/OpengraphField.php b/plugins/system/opengraph/src/Field/OpengraphField.php index 9d1a1cee37b..68c9816764e 100644 --- a/plugins/system/opengraph/src/Field/OpengraphField.php +++ b/plugins/system/opengraph/src/Field/OpengraphField.php @@ -9,7 +9,7 @@ namespace Joomla\Plugin\System\Opengraph\Field; -use Joomla\CMS\Extension\Component; + use Joomla\CMS\Factory; use Joomla\CMS\Form\Field\ListField; use Joomla\CMS\HTML\HTMLHelper; @@ -46,20 +46,24 @@ class OpengraphField extends ListField */ protected function getOptions() { + $app = Factory::getApplication(); + $options = parent::getOptions(); + - $app = Factory::getApplication(); - $options = parent::getOptions(); - //todo : make this more modular or flexible $component = $app->bootComponent('com_content'); + if (!$component instanceof OpengraphServiceInterface) { return $options; } $fields = $component->getOpengraphFields(); - foreach ($fields as $value => $text) { - $options[] = HTMLHelper::_('select.option', $value, $text); - } + $fieldType = $this->getAttribute('field-type'); + if (isset($fields[$fieldType])) { + foreach ($fields[$fieldType] as $value => $text) { + $options[] = HTMLHelper::_('select.option', $value, $text); + } + } return $options; } } diff --git a/plugins/system/opengraph/src/forms/opengraph.xml b/plugins/system/opengraph/src/forms/opengraph.xml index 5394fd04ffc..aa7adef9b21 100644 --- a/plugins/system/opengraph/src/forms/opengraph.xml +++ b/plugins/system/opengraph/src/forms/opengraph.xml @@ -9,35 +9,32 @@ type="opengraph" label="PLG_SYSTEM_OPENGRAPH_TITLE_FIELD_LABEL" description="PLG_SYSTEM_OPENGRAPH_TITLE_FIELD_DESC" - class="form-select"> + class="form-select" + field-type="text-fields"> + class="form-select" + field-type="text-fields"> + class="form-select" + field-type="image-fields"> - - - + class="form-select" + field-type="image-alt-fields">
From 89149f9d4de6a6a8cca4b294bba0d0d60eccc5ee Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Tue, 1 Jul 2025 18:20:54 +0530 Subject: [PATCH 13/47] fix : Separated the mapping form and conditionally combined them only in the category. --- .../opengraph/src/Extension/opengraph.php | 37 ++- .../system/opengraph/src/forms/opengraph.xml | 264 ++++++++---------- .../opengraph/src/forms/opengraphmappings.xml | 44 +++ 3 files changed, 192 insertions(+), 153 deletions(-) create mode 100644 plugins/system/opengraph/src/forms/opengraphmappings.xml diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 860099abe3c..a2320bdcbd9 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -131,15 +131,24 @@ public function onContentPrepareForm(PrepareFormEvent $event): void } $form = $event->getForm(); - $name = $form->getName(); + $context = $form->getName(); $component = $app->bootComponent('com_content'); if (!$component instanceof OpengraphServiceInterface) { return; } - $xml = __DIR__ . '/../forms/opengraph.xml'; - if (file_exists($xml)) { - $form->loadFile($xml, false); + // Check if this is a category context - load mappings form first for categories + if ($this->isCategoryContext($context)) { + $mappingsXml = __DIR__ . '/../forms/opengraphmappings.xml'; + if (file_exists($mappingsXml)) { + $form->loadFile($mappingsXml, false); + } + } + + // Load the main OpenGraph form for all supported contexts + $mainXml = __DIR__ . '/../forms/opengraph.xml'; + if (file_exists($mainXml)) { + $form->loadFile($mainXml, false); } } @@ -214,4 +223,24 @@ private function setOpenGraphImage(Document $document, ?string $image, ?string $ $this->setMetaData($document, 'twitter:image', $url, 'name'); $this->setMetaData($document, 'twitter:image:alt', $alt, 'name'); } + + + + /** + * Check if the context is a category context. + * + * @param string $context + * + * @return bool + * + * @since __DEPLOY_VERSION__ + */ + private function isCategoryContext(string $context): bool + { + $categoryContexts = [ + 'com_categories.categorycom_content', + ]; + + return in_array($context, $categoryContexts, true); + } } diff --git a/plugins/system/opengraph/src/forms/opengraph.xml b/plugins/system/opengraph/src/forms/opengraph.xml index aa7adef9b21..c83c217d4d7 100644 --- a/plugins/system/opengraph/src/forms/opengraph.xml +++ b/plugins/system/opengraph/src/forms/opengraph.xml @@ -1,153 +1,119 @@
-
-
- - - - - - - - - - - - + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
+ \ No newline at end of file diff --git a/plugins/system/opengraph/src/forms/opengraphmappings.xml b/plugins/system/opengraph/src/forms/opengraphmappings.xml new file mode 100644 index 00000000000..3b7bd70a22f --- /dev/null +++ b/plugins/system/opengraph/src/forms/opengraphmappings.xml @@ -0,0 +1,44 @@ + +
+ +
+
+ + + + + + + + + + + + +
+
+
+
\ No newline at end of file From b05a861d2cdf4ce59c6f9ea9724a4d59bb4e07df Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Wed, 2 Jul 2025 22:29:58 +0530 Subject: [PATCH 14/47] fix : forms now correclty save values in both category and article --- .../opengraph/src/Extension/opengraph.php | 87 ++++++++++++++++--- 1 file changed, 74 insertions(+), 13 deletions(-) diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index a2320bdcbd9..458d018af6b 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -22,6 +22,7 @@ use Joomla\CMS\Document\Document; use Joomla\CMS\Opengraph\OpengraphServiceInterface; use Joomla\CMS\Uri\Uri; +use Exception; @@ -124,34 +125,43 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void */ public function onContentPrepareForm(PrepareFormEvent $event): void { - $app = $this->getApplication(); + $form = $event->getForm(); + $context = $form->getName(); + $app = $this->getApplication(); - if (!$app->isClient('administrator')) { + if (!$app->isClient('administrator') || !$this->isSupported($context)) { return; } - $form = $event->getForm(); - $context = $form->getName(); - $component = $app->bootComponent('com_content'); - if (!$component instanceof OpengraphServiceInterface) { - return; - } + $isCategory = $this->isCategoryContext($context); + $groupName = $isCategory ? 'params' : 'attribs'; - // Check if this is a category context - load mappings form first for categories - if ($this->isCategoryContext($context)) { + // Load and modify opengraphmappings.xml (only for categories) + if ($isCategory) { $mappingsXml = __DIR__ . '/../forms/opengraphmappings.xml'; if (file_exists($mappingsXml)) { - $form->loadFile($mappingsXml, false); + try { + $modifiedXml = $this->adjustFieldsGroup($mappingsXml, $groupName); + $form->load($modifiedXml, false); + } catch (Exception $e) { + error_log('OpenGraph Plugin: Failed to load mappings form: ' . $e->getMessage()); + } } } - // Load the main OpenGraph form for all supported contexts + // Load and modify opengraph.xml $mainXml = __DIR__ . '/../forms/opengraph.xml'; if (file_exists($mainXml)) { - $form->loadFile($mainXml, false); + try { + $modifiedXml = $this->adjustFieldsGroup($mainXml, $groupName); + $form->load($modifiedXml, false); + } catch (Exception $e) { + error_log('OpenGraph Plugin: Failed to load main form: ' . $e->getMessage()); + } } } + /** * Inject the OpenGraph data into the document. * @@ -225,6 +235,40 @@ private function setOpenGraphImage(Document $document, ?string $image, ?string $ } + /** + * Check if the current plugin should execute opengraph related activities + * + * @param string $context + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + protected function isSupported($context): bool + { + // @todo: will be changed in future to support other components + + + + $supportedContexts = [ + 'com_content.article', + 'com_categories.categorycom_content', + ]; + + if (!in_array($context, $supportedContexts, true)) { + return false; + } + + //todo : currently we have interface in com_content only but we need to have it in other components + + // $parts = explode('.', $context, 2); + + // $component = $this->getApplication()->bootComponent($parts[0]); + + // return $component instanceof OpengraphServiceInterface; + + return true; + } /** * Check if the context is a category context. @@ -243,4 +287,21 @@ private function isCategoryContext(string $context): bool return in_array($context, $categoryContexts, true); } + + private function adjustFieldsGroup(string $filePath, string $newGroup): string + { + $xmlContent = file_get_contents($filePath); + $xml = simplexml_load_string($xmlContent); + + if ($xml === false) { + throw new Exception("Could not load XML file: {$filePath}"); + } + + // Adjust all nodes to use the desired group + foreach ($xml->xpath('//fields') as $fields) { + $fields['name'] = $newGroup; + } + + return $xml->asXML(); + } } From b41f698c580dd48bf73bf3a804a89baa7a2bf084 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Thu, 3 Jul 2025 18:33:24 +0530 Subject: [PATCH 15/47] feat : added custom fields options to opengraph mappings and used group field also. --- .../opengraph/src/Field/OpengraphField.php | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/plugins/system/opengraph/src/Field/OpengraphField.php b/plugins/system/opengraph/src/Field/OpengraphField.php index 68c9816764e..00b55153f86 100644 --- a/plugins/system/opengraph/src/Field/OpengraphField.php +++ b/plugins/system/opengraph/src/Field/OpengraphField.php @@ -3,17 +3,19 @@ /** * Joomla! Content Management System * - * @copyright (C) 2009 Open Source Matters, Inc. + * @copyright (C) 2025 Open Source Matters, Inc. * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Plugin\System\Opengraph\Field; - use Joomla\CMS\Factory; -use Joomla\CMS\Form\Field\ListField; +use Joomla\CMS\Fields\FieldsServiceInterface; +use Joomla\CMS\Form\Field\GroupedlistField; use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Opengraph\OpengraphServiceInterface; +use Joomla\Component\Fields\Administrator\Helper\FieldsHelper; +use Joomla\CMS\Language\Text; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -26,7 +28,7 @@ * @since 1.7.0 */ -class OpengraphField extends ListField +class OpengraphField extends GroupedlistField { /** * The form field type. @@ -44,16 +46,21 @@ class OpengraphField extends ListField * * @since 3.7.0 */ - protected function getOptions() + protected function getGroups() { $app = Factory::getApplication(); - $options = parent::getOptions(); + $groups = []; + + $groups[''] = [ + HTMLHelper::_('select.option', '', Text::_('PLG_SYSTEM_OPENGRAPH_NO_FIELD_SELECTED')) + ]; + $ogOptions = []; $component = $app->bootComponent('com_content'); if (!$component instanceof OpengraphServiceInterface) { - return $options; + return $groups; } $fields = $component->getOpengraphFields(); @@ -61,9 +68,36 @@ protected function getOptions() if (isset($fields[$fieldType])) { foreach ($fields[$fieldType] as $value => $text) { - $options[] = HTMLHelper::_('select.option', $value, $text); + $ogOptions[] = HTMLHelper::_('select.option', $value, $text); } } - return $options; + + if (!empty($ogOptions)) { + $groups['Default Fields'] = $ogOptions; + } + + + if (!$component instanceof FieldsServiceInterface) { + return $groups; + } + + $customOptions = []; + + + $customFields = FieldsHelper::getFields('com_content.article', null); + + foreach ($customFields as $field) { + // todo : will be filtered by type or group in the future + + $label = $field->title . ' (' . $field->name . ')'; + $customOptions[] = HTMLHelper::_('select.option', $field->name, $label); + } + + + if (!empty($customOptions)) { + $groups['Custom Fields'] = $customOptions; + } + + return $groups; } } From a6e28cd891bc3a16f2b1599074b2ab2104ad0ba1 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Tue, 8 Jul 2025 16:05:54 +0530 Subject: [PATCH 16/47] fix : made some changes to the form and made isSupported function more modular using the OGinterface --- .../opengraph/src/Extension/opengraph.php | 79 ++++++++----------- .../system/opengraph/src/forms/opengraph.xml | 47 ++++------- .../opengraph/src/forms/opengraphmappings.xml | 4 +- 3 files changed, 49 insertions(+), 81 deletions(-) diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 458d018af6b..886366cec5f 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -128,28 +128,28 @@ public function onContentPrepareForm(PrepareFormEvent $event): void $form = $event->getForm(); $context = $form->getName(); $app = $this->getApplication(); - if (!$app->isClient('administrator') || !$this->isSupported($context)) { return; } - $isCategory = $this->isCategoryContext($context); - $groupName = $isCategory ? 'params' : 'attribs'; + $isCategory = $context === 'com_categories.categorycom_content'; + $isMenu = $context === 'com_menus.item'; + + $groupName = $isMenu ? 'params' : 'attribs'; - // Load and modify opengraphmappings.xml (only for categories) + // Load opengraphmappings.xml for categories directly no need to adjust fields group if ($isCategory) { - $mappingsXml = __DIR__ . '/../forms/opengraphmappings.xml'; - if (file_exists($mappingsXml)) { - try { - $modifiedXml = $this->adjustFieldsGroup($mappingsXml, $groupName); - $form->load($modifiedXml, false); - } catch (Exception $e) { - error_log('OpenGraph Plugin: Failed to load mappings form: ' . $e->getMessage()); - } + try { + $form::addFormPath(__DIR__ . '/../forms'); + $form->loadFile('opengraphmappings', false); + } catch (Exception $e) { + error_log('OpenGraph Plugin: Failed to load mappings form: ' . $e->getMessage()); } + + return; } - // Load and modify opengraph.xml + // Load and modify opengraph.xml for articles and menus $mainXml = __DIR__ . '/../forms/opengraph.xml'; if (file_exists($mainXml)) { try { @@ -246,47 +246,32 @@ private function setOpenGraphImage(Document $document, ?string $image, ?string $ */ protected function isSupported($context): bool { - // @todo: will be changed in future to support other components - - - - $supportedContexts = [ - 'com_content.article', - 'com_categories.categorycom_content', - ]; - - if (!in_array($context, $supportedContexts, true)) { + $parts = explode('.', $context, 2); + if (empty($parts)) { return false; } - //todo : currently we have interface in com_content only but we need to have it in other components - - // $parts = explode('.', $context, 2); - - // $component = $this->getApplication()->bootComponent($parts[0]); + if ($parts[0] === 'com_categories' && isset($parts[1])) { + // Extract true component name from com_categories context + if (preg_match('/com_[a-zA-Z0-9_]+$/', $parts[1], $matches)) { + $componentName = $matches[0]; + } else { + return false; + } + } else { + $componentName = $parts[0]; + } - // return $component instanceof OpengraphServiceInterface; + try { + $component = $this->getApplication()->bootComponent($componentName); + } catch (Exception $e) { + error_log('OpenGraph Plugin: Failed to boot component: ' . $e->getMessage()); + return false; + } - return true; + return $component instanceof OpengraphServiceInterface; } - /** - * Check if the context is a category context. - * - * @param string $context - * - * @return bool - * - * @since __DEPLOY_VERSION__ - */ - private function isCategoryContext(string $context): bool - { - $categoryContexts = [ - 'com_categories.categorycom_content', - ]; - - return in_array($context, $categoryContexts, true); - } private function adjustFieldsGroup(string $filePath, string $newGroup): string { diff --git a/plugins/system/opengraph/src/forms/opengraph.xml b/plugins/system/opengraph/src/forms/opengraph.xml index c83c217d4d7..e2281605b58 100644 --- a/plugins/system/opengraph/src/forms/opengraph.xml +++ b/plugins/system/opengraph/src/forms/opengraph.xml @@ -5,48 +5,34 @@ label="PLG_SYSTEM_OPENGRAPH_FIELDSET_ARTICLE">
- - - - + size="50" /> + cols="50" /> + description="PLG_SYSTEM_OPENGRAPH_OG_IMAGE_DESC" /> + hint="PLG_SYSTEM_OPENGRAPH_OG_IMAGE_ALT_HINT" /> + class="form-select"> @@ -62,15 +48,13 @@ type="url" label="PLG_SYSTEM_OPENGRAPH_OG_URL_LABEL" description="PLG_SYSTEM_OPENGRAPH_OG_URL_DESC" - hint="PLG_SYSTEM_OPENGRAPH_OG_URL_HINT" - showon="show_manual_override:1" /> + hint="PLG_SYSTEM_OPENGRAPH_OG_URL_HINT" /> + layout="joomla.form.field.radio.switcher"> @@ -79,19 +63,18 @@ label="PLG_SYSTEM_OPENGRAPH_SHOW_ADVANCED_LABEL" description="PLG_SYSTEM_OPENGRAPH_SHOW_ADVANCED_DESC" class="btn-group btn-group-yesno" - default="0" - showon="show_manual_override:1"> + default="0"> + showon="show_advanced:1" /> + showon="show_advanced:1"> @@ -99,21 +82,21 @@ type="text" label="PLG_SYSTEM_OPENGRAPH_TWITTER_TITLE_LABEL" hint="PLG_SYSTEM_OPENGRAPH_TWITTER_TITLE_HINT" - showon="show_manual_override:1[AND]show_advanced:1" /> + showon="show_advanced:1" /> + showon="show_advanced:1" /> + showon="show_advanced:1" /> + showon="show_advanced:1" />
- \ No newline at end of file + diff --git a/plugins/system/opengraph/src/forms/opengraphmappings.xml b/plugins/system/opengraph/src/forms/opengraphmappings.xml index 3b7bd70a22f..97c766cbab2 100644 --- a/plugins/system/opengraph/src/forms/opengraphmappings.xml +++ b/plugins/system/opengraph/src/forms/opengraphmappings.xml @@ -1,6 +1,6 @@
- +
- \ No newline at end of file + From fea4185ba7ddaf0f02ac9cc098427a0e3928d351 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Tue, 8 Jul 2025 16:14:51 +0530 Subject: [PATCH 17/47] feat : implemented the OGInterface in com_menus --- .../src/Extension/MenusComponent.php | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/administrator/components/com_menus/src/Extension/MenusComponent.php b/administrator/components/com_menus/src/Extension/MenusComponent.php index 084ca80159e..6338ad5b89e 100644 --- a/administrator/components/com_menus/src/Extension/MenusComponent.php +++ b/administrator/components/com_menus/src/Extension/MenusComponent.php @@ -15,6 +15,7 @@ use Joomla\CMS\Extension\BootableExtensionInterface; use Joomla\CMS\Extension\MVCComponent; use Joomla\CMS\HTML\HTMLRegistryAwareTrait; +use Joomla\CMS\Opengraph\OpengraphServiceInterface; use Joomla\Component\Menus\Administrator\Service\HTML\Menus; use Psr\Container\ContainerInterface; @@ -29,7 +30,8 @@ */ class MenusComponent extends MVCComponent implements BootableExtensionInterface, - AssociationServiceInterface + AssociationServiceInterface, + OpengraphServiceInterface { use AssociationServiceTrait; use HTMLRegistryAwareTrait; @@ -51,4 +53,20 @@ public function boot(ContainerInterface $container) { $this->getRegistry()->register('menus', new Menus()); } + + + /** + * Returns valid contexts for opengraph + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public function getOpengraphFields(): array + { + + $fields = []; + + return $fields; + } } From 6d37b9bfab615d04d9cd26004a7f09a2350b3587 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Mon, 14 Jul 2025 13:23:08 +0530 Subject: [PATCH 18/47] feat: add custom fields filtering to opengraph field --- .../opengraph/src/Field/OpengraphField.php | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/plugins/system/opengraph/src/Field/OpengraphField.php b/plugins/system/opengraph/src/Field/OpengraphField.php index 00b55153f86..18b43304149 100644 --- a/plugins/system/opengraph/src/Field/OpengraphField.php +++ b/plugins/system/opengraph/src/Field/OpengraphField.php @@ -25,7 +25,7 @@ * Form Field class for the Joomla Platform. * Supports a generic list of options. * - * @since 1.7.0 + * @since __DEPLOY_VERSION__ */ class OpengraphField extends GroupedlistField @@ -34,7 +34,7 @@ class OpengraphField extends GroupedlistField * The form field type. * * @var string - * @since 1.7.0 + * @since __DEPLOY_VERSION__ */ protected $type = 'Opengraph'; @@ -44,7 +44,7 @@ class OpengraphField extends GroupedlistField * * @return object[] The field option objects. * - * @since 3.7.0 + * @since __DEPLOY_VERSION__ */ protected function getGroups() { @@ -81,19 +81,27 @@ protected function getGroups() return $groups; } - $customOptions = []; + // Allowed field types for each OpenGraph group + $allowedFieldTypes = [ + 'text-fields' => ['text', 'textarea'], + 'image-fields' => ['media', 'imagelist'], + 'image-alt-fields' => ['text'], + ]; + $allowedTypes = $allowedFieldTypes[$fieldType] ?? []; $customFields = FieldsHelper::getFields('com_content.article', null); + $customOptions = []; foreach ($customFields as $field) { - // todo : will be filtered by type or group in the future + if (!in_array($field->type, $allowedTypes, true)) { + continue; + } $label = $field->title . ' (' . $field->name . ')'; - $customOptions[] = HTMLHelper::_('select.option', $field->name, $label); + $customOptions[] = HTMLHelper::_('select.option', 'field.' . $field->name, $label); } - if (!empty($customOptions)) { $groups['Custom Fields'] = $customOptions; } From 7cfde557e6162549c7851ad8963630ac3113550e Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Thu, 17 Jul 2025 23:14:08 +0530 Subject: [PATCH 19/47] feat : og tag injection into html --- .../opengraph/src/Extension/opengraph.php | 474 +++++++++++++++--- 1 file changed, 396 insertions(+), 78 deletions(-) diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 886366cec5f..e3339f32845 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -4,7 +4,7 @@ * @package Joomla.Plugin * @subpackage System.Opengraph * - * @copyright (C) 2024 Open Source Matters, Inc. + * @copyright (C) 2025 Open Source Matters, Inc. * @license GNU General Public License version 2 or later; see LICENSE.txt */ @@ -23,9 +23,10 @@ use Joomla\CMS\Opengraph\OpengraphServiceInterface; use Joomla\CMS\Uri\Uri; use Exception; - - - +use Joomla\CMS\Table\Content; +use Joomla\Database\DatabaseInterface; +use Joomla\CMS\Menu\MenuItem; +use Joomla\Component\Fields\Administrator\Helper\FieldsHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -55,6 +56,17 @@ final class Opengraph extends CMSPlugin implements SubscriberInterface */ protected $autoloadLanguage = true; + /** + * Database object + * + * The database is injected by parent constructor + * + * @var DatabaseInterface + * @since __DEPLOY_VERSION__ + */ + protected $db; + + /** * Returns an array of events this subscriber will listen to. * @@ -69,6 +81,54 @@ public static function getSubscribedEvents(): array 'onContentPrepareForm' => 'onContentPrepareForm', ]; } + + + /** + * Add fields for the OpenGraph data to the form + * + * @param PrepareFormEvent $event + * + * @return void + */ + public function onContentPrepareForm(PrepareFormEvent $event): void + { + $form = $event->getForm(); + $context = $form->getName(); + $app = $this->getApplication(); + if (!$app->isClient('administrator') || !$this->isSupported($context)) { + return; + } + + $isCategory = $context === 'com_categories.categorycom_content'; + $isMenu = $context === 'com_menus.item'; + + $groupName = $isMenu ? 'params' : 'attribs'; + + // Load opengraphmappings.xml for categories directly no need to adjust fields group + if ($isCategory) { + try { + $form::addFormPath(__DIR__ . '/../forms'); + $form->loadFile('opengraphmappings', false); + } catch (Exception $e) { + error_log('OpenGraph Plugin: Failed to load mappings form: ' . $e->getMessage()); + } + + return; + } + + // Load and modify opengraph.xml for articles and menus + $mainXml = __DIR__ . '/../forms/opengraph.xml'; + if (file_exists($mainXml)) { + try { + $modifiedXml = $this->adjustFieldsGroup($mainXml, $groupName); + $form->load($modifiedXml, false); + } catch (Exception $e) { + error_log('OpenGraph Plugin: Failed to load main form: ' . $e->getMessage()); + } + } + } + + /** * Handle the beforeCompileHead event. * @@ -78,119 +138,322 @@ public static function getSubscribedEvents(): array */ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void { - $app = $this->getApplication(); + $app = $this->app; if (!$app->isClient('site')) { return; } + $document = $app->getDocument(); + + // Only process HTML documents + if (!($document instanceof HtmlDocument)) { + return; + } + $input = $app->input; $option = $input->get('option', '', 'cmd'); $view = $input->get('view', '', 'cmd'); $id = $input->getInt('id'); - - // @todo: will be changed in future to support other components - - if ($option !== 'com_content' || $view !== 'article' || !$id) { + // Skip indexer and feed formats + if (($option . '.' . $view) === 'com_finder.indexer') { return; } - $table = Table::getInstance('Content'); - if (!$table->load($id)) { + if ($input->get('format', '', 'cmd') === 'feed') { return; } + // Check if plugin is enabled globally + if (!$this->params->get('enable_og_generation', 1)) { + return; + } + $uniqueArticle = true; + if (is_array($input->get('id', 0, 'int'))) { + $uniqueArticle = false; + } - $attribs = new Registry($table->attribs); + if ($uniqueArticle) { + $article = new Content($this->db); + $article->load($id); + $category = Table::getInstance('Category'); + $category->load($article->catid); + } - if ($attribs->get('og_enabled', '') === '0') { - return; + + + + // Get menu parameters + $menuParams = $this->getMenuParams(); + $articleAttribs = new Registry($article->attribs ?? '{}'); + $categoryParams = new Registry($category->params ?? '{}'); + $articleImages = $this->getAllArticleImages(new Registry($article->images ?? '{}')); + + + + $ogTags = [ + 'og_title' => '', + 'og_description' => '', + 'og_image' => '', + 'og_image_alt' => '', + 'og_type' => '', + 'og_url' => '', + 'twitter_card' => '', + 'twitter_title' => '', + 'twitter_description' => '', + 'twitter_image' => '', + 'twitter_image_alt' => '', + 'fb_app_id' => '', + 'site_name' => '', + 'url' => '', + 'base_url' => '' + ]; + + // Get Global settings + + $config = $this->app->getConfig(); + + $ogTags['fb_app_id'] = $this->params->get('fb_app_id'); + $ogTags['site_name'] = $config->get('sitename'); + $ogTags['base_url'] = Uri::base(); + $ogTags['url'] = Uri::getInstance()->toString(); + + // get OG tags from category mappings + $this->getOgTagsFromCategoryMappings($categoryParams, $article, $articleImages, $ogTags); + + // get OG tags from article form + $this->getOgTagsFromParams($articleAttribs, $ogTags); + + // get OG tags from menu form + $this->getOgTagsFromParams($menuParams, $ogTags); + + // get Twitter tags + $this->getTwitterOgTags($ogTags); + + // Inject the OpenGraph data into the document + $this->injectOpenGraphData($document, $ogTags); + } + + + /** + * Generates OG metadata values based on category field mapping and article data. + * + * @param Registry $categoryParams The category params containing OG field mappings. + * @param object $article The article object. + * @param array $articleImages The array of article images. + * @param array $ogTags The array of OG tags. + */ + private function getOgTagsFromCategoryMappings(Registry $categoryParams, Content $article, array $articleImages, array &$ogTags): void + { + + foreach ($categoryParams as $key => $fieldName) { + // Only process keys that start with 'og_' + if (strpos($key, 'og_') === 0 && str_ends_with($key, '_field')) { + $ogTagName = substr($key, 0, -6); // Remove "_field" from the end + + $ogTags[$ogTagName] = $this->getFieldValue($article, $fieldName, $articleImages); + } } + } - $document = $this->app->getDocument(); - if (!$document instanceof HtmlDocument) { - return; + + /** + * Get value from article field or custom field + * + * @param Content $article + * @param string $fieldName + * @param array $articleImages + * + * @return string + */ + private function getFieldValue(Content $article, string $fieldName, array $articleImages): string + { + // Check if it's a custom field + if (strpos($fieldName, 'field.') === 0) { + $customFieldName = substr($fieldName, 6); + // Load custom fields for the article + $customFields = FieldsHelper::getFields('com_content.article', $article, true); + + foreach ($customFields as $field) { + + if ($field->name == $customFieldName) { + + return $field->value ?? ''; + } + } + + return ''; + } + + // Handle standard article fields + $value = ''; + + switch ($fieldName) { + case 'title': + $value = $article->title; + break; + + case 'alias': + $value = $article->alias; + break; + + case 'articletext': + $value = $article->introtext . $article->fulltext; + break; + + case 'metadesc': + $value = $article->metadesc; + break; + + case 'metakey': + $value = $article->metakey; + break; + + case 'image_intro': + $value = $articleImages['image_intro']; + break; + + case 'image_fulltext': + $value = $articleImages['image_fulltext']; + break; + + case 'image_intro_alt': + $value = $articleImages['image_intro_alt']; + break; + + case 'image_fulltext_alt': + $value = $articleImages['image_fulltext_alt']; + break; + + case 'created_by_alias': + $value = $article->created_by_alias; + break; + + default: + if (property_exists($article, $fieldName)) { + $value = $article->$fieldName; + } } - $this->injectOpenGraphData($document, $attribs); + return (string) $value; } + + /** - * Handle the onContentPrepareForm event. + * @param Registry $articleImages * - * @param PrepareFormEvent $event + * @return array + * @since __DEPLOY_VERSION__ + */ + private function getAllArticleImages(Registry $articleImages): array + { + $image_intro = $image_intro_alt = ''; + $image_fulltext = $image_fulltext_alt = ''; + + // Handle image_intro + if ($articleImages->get('image_intro') > '') { + // get part before # in image if # exists + $image_intro = strpos($articleImages->get('image_intro'), '#') !== false + ? substr($articleImages->get('image_intro'), 0, strpos($articleImages->get('image_intro'), '#')) + : $articleImages->get('image_intro'); + + $image_intro_alt = $articleImages->get('image_intro_alt', ''); + } + + // Handle image_fulltext + if ($articleImages->get('image_fulltext') > '') { + $image_fulltext = strpos($articleImages->get('image_fulltext'), '#') !== false + ? substr($articleImages->get('image_fulltext'), 0, strpos($articleImages->get('image_fulltext'), '#')) + : $articleImages->get('image_fulltext'); + + $image_fulltext_alt = $articleImages->get('image_fulltext_alt', ''); + } + + return [ + 'image_intro' => $image_intro, + 'image_intro_alt' => $image_intro_alt, + 'image_fulltext' => $image_fulltext, + 'image_fulltext_alt' => $image_fulltext_alt, + ]; + } + + + + /** + * Extract OG tags from a parameter source (article, menu) + * + * @param Registry $params The source of OG field mappings (e.g. article attribs, menu params) + * @param array &$ogTags Reference to the OG tags array to populate * * @return void */ - public function onContentPrepareForm(PrepareFormEvent $event): void + private function getOgTagsFromParams(Registry $params, array &$ogTags): void { - $form = $event->getForm(); - $context = $form->getName(); - $app = $this->getApplication(); - if (!$app->isClient('administrator') || !$this->isSupported($context)) { - return; - } - $isCategory = $context === 'com_categories.categorycom_content'; - $isMenu = $context === 'com_menus.item'; - $groupName = $isMenu ? 'params' : 'attribs'; + foreach (array_keys($ogTags) as $ogTagName) { - // Load opengraphmappings.xml for categories directly no need to adjust fields group - if ($isCategory) { - try { - $form::addFormPath(__DIR__ . '/../forms'); - $form->loadFile('opengraphmappings', false); - } catch (Exception $e) { - error_log('OpenGraph Plugin: Failed to load mappings form: ' . $e->getMessage()); - } + if ($params->exists($ogTagName)) { - return; - } + $value = $params->get($ogTagName); - // Load and modify opengraph.xml for articles and menus - $mainXml = __DIR__ . '/../forms/opengraph.xml'; - if (file_exists($mainXml)) { - try { - $modifiedXml = $this->adjustFieldsGroup($mainXml, $groupName); - $form->load($modifiedXml, false); - } catch (Exception $e) { - error_log('OpenGraph Plugin: Failed to load main form: ' . $e->getMessage()); + + if ($value) { + $ogTags[$ogTagName] = $value; + } } } } + /** + * Get Twitter tags if not set use OG value + * @param array &$ogTags + * + * @return void + */ + private function getTwitterOgTags(array &$ogTags): void + { + $twitterTags = [ + 'twitter_title' => $ogTags['twitter_title'], + 'twitter_description' => $ogTags['twitter_description'], + 'twitter_image' => $ogTags['twitter_image'], + 'twitter_image_alt' => $ogTags['twitter_image_alt'] + ]; + foreach ($twitterTags as $key => $value) { + // If the value is not set, use the OG value + if (!$value || $value === '') { + $ogTags[$key] = $ogTags['og_' . substr($key, 8)]; + } + } + } /** * Inject the OpenGraph data into the document. * * @param Document $document - * @param Registry $params + * @param array $ogTags * * @return void */ - private function injectOpenGraphData(Document $document, Registry $params): void + private function injectOpenGraphData(Document $document, array $ogTags): void { + // OpenGraph tags + $this->setMetaData($document, 'og:title', $ogTags['og_title'], 'property'); + $this->setMetaData($document, 'og:description', $ogTags['og_description'], 'property'); + $this->setMetaData($document, 'og:type', $ogTags['og_type'], 'property'); + $this->setMetaData($document, 'og:url', $ogTags['og_url'], 'property'); + // Twitter tags + $this->setMetaData($document, 'twitter:card', $ogTags['twitter_card'], 'name'); + $this->setMetaData($document, 'twitter:title', $ogTags['twitter_title'], 'name'); + $this->setMetaData($document, 'twitter:description', $ogTags['twitter_description'], 'name'); - $this->setMetaData($document, 'og:title', $params->get('og_title'), 'property'); - $this->setMetaData($document, 'og:description', $params->get('og_description'), 'property'); - $this->setMetaData($document, 'og:type', $params->get('og_type'), 'property'); - - - $this->setMetaData($document, 'twitter:card', $params->get('twitter_card'), 'name'); - $this->setMetaData($document, 'twitter:title', $params->get('twitter_title'), 'name'); - $this->setMetaData($document, 'twitter:description', $params->get('twitter_description'), 'name'); + // Facebook App ID + $this->setMetaData($document, 'fb:app_id', $ogTags['fb_app_id'], 'property'); - $this->setMetaData($document, 'fb:app_id', $params->get('fb_app_id'), 'property'); - - $this->setOpenGraphImage( - $document, - $params->get('og_image'), - $params->get('og_image_alt'), - Uri::base() - ); + $this->setOpenGraphImage($document, $ogTags); } /** @@ -211,27 +474,53 @@ private function setMetaData(Document $document, string $name, ?string $value, s } /** - * Set OpenGraph Image tag in document. + * Add OpenGraph image to document * - * @param Document $document - * @param string|null $image - * @param string|null $alt - * @param string|null $baseUrl + * @param Document $document + * @param array $ogTags * * @return void + * @since __DEPLOY_VERSION__ */ - private function setOpenGraphImage(Document $document, ?string $image, ?string $alt = '', ?string $baseUrl = ''): void - { - if (empty($image)) { + private function setOpenGraphImage( + Document $document, + array $ogTags + ): void { + $image = $ogTags['og_image']; + $alt = $ogTags['og_image_alt']; + $baseUrl = $ogTags['base_url']; + $twitterImage = $ogTags['twitter_image']; + $twitterImageAlt = $ogTags['twitter_image_alt']; + + if (empty($image) || !empty($document->getMetaData('og:image'))) { return; } - $url = rtrim((string) $baseUrl, '/') . '/' . ltrim($image, '/'); + $image = preg_replace('~^([\w\-./\\\]+).*$~', '$1', $image); + + if (!file_exists($image)) { + return; + } - $this->setMetaData($document, 'og:image', $url, 'property'); + $ogImageUrl = empty($baseUrl) ? '' : rtrim($baseUrl, '/') . '/'; + $ogImageUrl .= $image; + + $twitterImageUrl = empty($baseUrl) ? '' : rtrim($baseUrl, '/') . '/'; + $twitterImageUrl .= $twitterImage; + + $this->setMetaData($document, 'og:image', $ogImageUrl, 'property'); + $this->setMetaData($document, 'og:image:secure_url', $ogImageUrl, 'property'); $this->setMetaData($document, 'og:image:alt', $alt, 'property'); - $this->setMetaData($document, 'twitter:image', $url, 'name'); - $this->setMetaData($document, 'twitter:image:alt', $alt, 'name'); + $this->setMetaData($document, 'twitter:image', $twitterImageUrl, 'name'); + $this->setMetaData($document, 'twitter:image:alt', $twitterImageAlt, 'name'); + + $info = getimagesize($image); + + if (\is_array($info)) { + $this->setMetaData($document, 'og:image:type', $info['mime'], 'property'); + $this->setMetaData($document, 'og:image:height', $info[1], 'property'); + $this->setMetaData($document, 'og:image:width', $info[0], 'property'); + } } @@ -273,6 +562,15 @@ protected function isSupported($context): bool } + /** + * Adjust the fields group in the XML file + * + * @param string $filePath + * @param string $newGroup + * + * @return string + * @since __DEPLOY_VERSION__ + */ private function adjustFieldsGroup(string $filePath, string $newGroup): string { $xmlContent = file_get_contents($filePath); @@ -289,4 +587,24 @@ private function adjustFieldsGroup(string $filePath, string $newGroup): string return $xml->asXML(); } + + + /** + * Get the parameters associated with the active menu item + * + * @return Registry + * @since __DEPLOY_VERSION__ + */ + private function getMenuParams(): Registry + { + $menu = $this->app->getMenu(); + + $active = $menu?->getActive(); + + if (!$active instanceof MenuItem) { + return new Registry(); + } + + return $menu->getParams($active->id); + } } From 1f95b29a3bc1613573e4accd77c56d31be93125f Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Tue, 22 Jul 2025 19:24:06 +0530 Subject: [PATCH 20/47] fix : used the component helper to get the article and category --- .../opengraph/src/Extension/opengraph.php | 81 +++++++++++-------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index e3339f32845..035edcbcfdc 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -23,10 +23,16 @@ use Joomla\CMS\Opengraph\OpengraphServiceInterface; use Joomla\CMS\Uri\Uri; use Exception; +use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Table\Content; use Joomla\Database\DatabaseInterface; use Joomla\CMS\Menu\MenuItem; use Joomla\Component\Fields\Administrator\Helper\FieldsHelper; +use Joomla\Component\Content\Site\Model\ArticleModel; +use Joomla\Component\Content\Site\Model\CategoryModel; +use Joomla\CMS\MVC\Factory\MVCFactoryInterface; + + // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -56,16 +62,6 @@ final class Opengraph extends CMSPlugin implements SubscriberInterface */ protected $autoloadLanguage = true; - /** - * Database object - * - * The database is injected by parent constructor - * - * @var DatabaseInterface - * @since __DEPLOY_VERSION__ - */ - protected $db; - /** * Returns an array of events this subscriber will listen to. @@ -140,10 +136,12 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void { $app = $this->app; + if (!$app->isClient('site')) { return; } + /** @var HtmlDocument $document */ $document = $app->getDocument(); // Only process HTML documents @@ -151,35 +149,51 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void return; } - $input = $app->input; - $option = $input->get('option', '', 'cmd'); - $view = $input->get('view', '', 'cmd'); - $id = $input->getInt('id'); - - // Skip indexer and feed formats - if (($option . '.' . $view) === 'com_finder.indexer') { - return; - } - - if ($input->get('format', '', 'cmd') === 'feed') { + $input = $app->input; + if ( + $input->getCmd('option') !== 'com_content' + || $input->getCmd('view') !== 'article' + || ! $id = $input->getInt('id') + ) { return; } - // Check if plugin is enabled globally + // Plugin globally disabled? if (!$this->params->get('enable_og_generation', 1)) { return; } - $uniqueArticle = true; - if (is_array($input->get('id', 0, 'int'))) { - $uniqueArticle = false; + + /** @var MVCComponent $component */ + $component = $app->bootComponent('com_content'); + + /** @var MVCFactoryInterface $mvcFactory */ + $mvcFactory = $component->getMVCFactory(); + + $params = ComponentHelper::getParams('com_content'); + // Fallback if for some reason it isn’t an object + if (! $params instanceof Registry) { + $params = new Registry; } - if ($uniqueArticle) { - $article = new Content($this->db); - $article->load($id); - $category = Table::getInstance('Category'); - $category->load($article->catid); + /** @var ArticleModel $articleModel */ + $articleModel = $mvcFactory->createModel('Article', 'Site', ['ignore_request' => true]); + + $articleModel->setState('params', clone $params); + $articleModel->setState('article.id', $id); + + $article = $articleModel->getItem($id); + if (! $article) { + return; } + /** @var CategoryModel $categoryModel */ + $categoryModel = $mvcFactory->createModel( + 'Category', + 'Site', + ['ignore_request' => true] + ); + $categoryModel->setState('category.id', $article->catid); + $category = $categoryModel->getCategory(); + @@ -243,7 +257,7 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void * @param array $articleImages The array of article images. * @param array $ogTags The array of OG tags. */ - private function getOgTagsFromCategoryMappings(Registry $categoryParams, Content $article, array $articleImages, array &$ogTags): void + private function getOgTagsFromCategoryMappings(Registry $categoryParams, object $article, array $articleImages, array &$ogTags): void { foreach ($categoryParams as $key => $fieldName) { @@ -251,7 +265,8 @@ private function getOgTagsFromCategoryMappings(Registry $categoryParams, Content if (strpos($key, 'og_') === 0 && str_ends_with($key, '_field')) { $ogTagName = substr($key, 0, -6); // Remove "_field" from the end - $ogTags[$ogTagName] = $this->getFieldValue($article, $fieldName, $articleImages); + $value = $this->getFieldValue($article, $fieldName, $articleImages); + $ogTags[$ogTagName] = $value; } } } @@ -267,7 +282,7 @@ private function getOgTagsFromCategoryMappings(Registry $categoryParams, Content * * @return string */ - private function getFieldValue(Content $article, string $fieldName, array $articleImages): string + private function getFieldValue(object $article, string $fieldName, array $articleImages): string { // Check if it's a custom field if (strpos($fieldName, 'field.') === 0) { From 19fe1f7a6b6507490cb95a8a8ba10d66888d6235 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Wed, 23 Jul 2025 16:14:12 +0530 Subject: [PATCH 21/47] fix : get custom fields for the respective category in dropdown --- .../system/opengraph/src/Extension/opengraph.php | 4 ---- .../system/opengraph/src/Field/OpengraphField.php | 13 ++++++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 035edcbcfdc..3f5a2cdba2c 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -15,17 +15,13 @@ use Joomla\CMS\Event\Model\PrepareFormEvent; use Joomla\CMS\Plugin\CMSPlugin; use Joomla\Event\SubscriberInterface; -use Joomla\CMS\Form\Form; use Joomla\CMS\Document\HtmlDocument; -use Joomla\CMS\Table\Table; use Joomla\Registry\Registry; use Joomla\CMS\Document\Document; use Joomla\CMS\Opengraph\OpengraphServiceInterface; use Joomla\CMS\Uri\Uri; use Exception; use Joomla\CMS\Component\ComponentHelper; -use Joomla\CMS\Table\Content; -use Joomla\Database\DatabaseInterface; use Joomla\CMS\Menu\MenuItem; use Joomla\Component\Fields\Administrator\Helper\FieldsHelper; use Joomla\Component\Content\Site\Model\ArticleModel; diff --git a/plugins/system/opengraph/src/Field/OpengraphField.php b/plugins/system/opengraph/src/Field/OpengraphField.php index 18b43304149..67c4dd30e84 100644 --- a/plugins/system/opengraph/src/Field/OpengraphField.php +++ b/plugins/system/opengraph/src/Field/OpengraphField.php @@ -90,7 +90,18 @@ protected function getGroups() $allowedTypes = $allowedFieldTypes[$fieldType] ?? []; - $customFields = FieldsHelper::getFields('com_content.article', null); + + + $catId = (int) $this->form->getValue('id'); // editing existing cat + if (!$catId) { + // Creating a new category: use the chosen parent so assignments still work + $catId = (int) $this->form->getValue('parent_id'); + } + + // Dummy item with catid so FieldsService filters by assignment + $scopeItem = $catId ? (object) ['catid' => $catId] : null; + + $customFields = FieldsHelper::getFields('com_content.article', $scopeItem); $customOptions = []; foreach ($customFields as $field) { From cbbff6cba62c019a3bd8d95911255f5487d9cb2b Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Wed, 23 Jul 2025 16:49:42 +0530 Subject: [PATCH 22/47] Chore: Apply PHP-CS-Fixer formatting across the codebase --- .../src/Extension/ContentComponent.php | 22 ++-- .../opengraph/src/Extension/opengraph.php | 100 +++++++++--------- .../opengraph/src/Field/OpengraphField.php | 18 ++-- 3 files changed, 68 insertions(+), 72 deletions(-) diff --git a/administrator/components/com_content/src/Extension/ContentComponent.php b/administrator/components/com_content/src/Extension/ContentComponent.php index bb11d5b4fea..12837192df0 100644 --- a/administrator/components/com_content/src/Extension/ContentComponent.php +++ b/administrator/components/com_content/src/Extension/ContentComponent.php @@ -220,26 +220,26 @@ public function getOpengraphFields(): array $fields = [ 'text-fields' => [ - 'title' => Text::_('JGLOBAL_TITLE'), + 'title' => Text::_('JGLOBAL_TITLE'), 'articletext' => Text::_('COM_CONTENT_FIELD_ARTICLETEXT_LABEL'), - 'alias' => Text::_('JFIELD_ALIAS_LABEL'), - 'metadesc' => Text::_('JFIELD_META_DESCRIPTION_LABEL'), + 'alias' => Text::_('JFIELD_ALIAS_LABEL'), + 'metadesc' => Text::_('JFIELD_META_DESCRIPTION_LABEL'), ], 'image-fields' => [ - 'image_intro' => Text::_('COM_CONTENT_FIELD_INTRO_LABEL'), + 'image_intro' => Text::_('COM_CONTENT_FIELD_INTRO_LABEL'), 'image_fulltext' => Text::_('COM_CONTENT_FIELD_FULL_LABEL'), ], 'image-alt-fields' => [ - 'image_intro_alt' => Text::_('COM_CONTENT_FIELD_INTRO_LABEL') . ' - ' . Text::_('COM_CONTENT_FIELD_IMAGE_ALT_LABEL'), + 'image_intro_alt' => Text::_('COM_CONTENT_FIELD_INTRO_LABEL') . ' - ' . Text::_('COM_CONTENT_FIELD_IMAGE_ALT_LABEL'), 'image_fulltext_alt' => Text::_('COM_CONTENT_FIELD_FULL_LABEL') . ' - ' . Text::_('COM_CONTENT_FIELD_IMAGE_ALT_LABEL'), ], 'meta-fields' => [ 'metadesc' => Text::_('JFIELD_META_DESCRIPTION_LABEL'), - 'metakey' => Text::_('JFIELD_META_KEYWORDS_LABEL'), + 'metakey' => Text::_('JFIELD_META_KEYWORDS_LABEL'), ], @@ -248,15 +248,15 @@ public function getOpengraphFields(): array ], 'author-fields' => [ - 'created_by' => Text::_('COM_CONTENT_FIELD_CREATED_BY_LABEL'), + 'created_by' => Text::_('COM_CONTENT_FIELD_CREATED_BY_LABEL'), 'created_by_alias' => Text::_('COM_CONTENT_FIELD_CREATED_BY_ALIAS_LABEL'), - 'modified_by' => Text::_('JGLOBAL_FIELD_MODIFIED_BY_LABEL'), + 'modified_by' => Text::_('JGLOBAL_FIELD_MODIFIED_BY_LABEL'), ], 'date-fields' => [ - 'created' => Text::_('COM_CONTENT_FIELD_CREATED_LABEL'), - 'modified' => Text::_('JGLOBAL_FIELD_MODIFIED_LABEL'), - 'publish_up' => Text::_('COM_CONTENT_FIELD_PUBLISH_UP_LABEL'), + 'created' => Text::_('COM_CONTENT_FIELD_CREATED_LABEL'), + 'modified' => Text::_('JGLOBAL_FIELD_MODIFIED_LABEL'), + 'publish_up' => Text::_('COM_CONTENT_FIELD_PUBLISH_UP_LABEL'), 'publish_down' => Text::_('COM_CONTENT_FIELD_PUBLISH_DOWN_LABEL'), ], diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 3f5a2cdba2c..f6900d0cae1 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -11,24 +11,21 @@ namespace Joomla\Plugin\System\Opengraph\Extension; use Joomla\CMS\Application\CMSApplication; +use Joomla\CMS\Component\ComponentHelper; +use Joomla\CMS\Document\Document; +use Joomla\CMS\Document\HtmlDocument; use Joomla\CMS\Event\Application\BeforeCompileHeadEvent; use Joomla\CMS\Event\Model\PrepareFormEvent; -use Joomla\CMS\Plugin\CMSPlugin; -use Joomla\Event\SubscriberInterface; -use Joomla\CMS\Document\HtmlDocument; -use Joomla\Registry\Registry; -use Joomla\CMS\Document\Document; +use Joomla\CMS\Menu\MenuItem; +use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\Opengraph\OpengraphServiceInterface; +use Joomla\CMS\Plugin\CMSPlugin; use Joomla\CMS\Uri\Uri; -use Exception; -use Joomla\CMS\Component\ComponentHelper; -use Joomla\CMS\Menu\MenuItem; -use Joomla\Component\Fields\Administrator\Helper\FieldsHelper; use Joomla\Component\Content\Site\Model\ArticleModel; use Joomla\Component\Content\Site\Model\CategoryModel; -use Joomla\CMS\MVC\Factory\MVCFactoryInterface; - - +use Joomla\Component\Fields\Administrator\Helper\FieldsHelper; +use Joomla\Event\SubscriberInterface; +use Joomla\Registry\Registry; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -43,7 +40,6 @@ final class Opengraph extends CMSPlugin implements SubscriberInterface { - /** * The application object. * @@ -69,7 +65,7 @@ final class Opengraph extends CMSPlugin implements SubscriberInterface public static function getSubscribedEvents(): array { return [ - 'onBeforeCompileHead' => 'onBeforeCompileHead', + 'onBeforeCompileHead' => 'onBeforeCompileHead', 'onContentPrepareForm' => 'onContentPrepareForm', ]; } @@ -92,7 +88,7 @@ public function onContentPrepareForm(PrepareFormEvent $event): void } $isCategory = $context === 'com_categories.categorycom_content'; - $isMenu = $context === 'com_menus.item'; + $isMenu = $context === 'com_menus.item'; $groupName = $isMenu ? 'params' : 'attribs'; @@ -101,7 +97,7 @@ public function onContentPrepareForm(PrepareFormEvent $event): void try { $form::addFormPath(__DIR__ . '/../forms'); $form->loadFile('opengraphmappings', false); - } catch (Exception $e) { + } catch (\Exception $e) { error_log('OpenGraph Plugin: Failed to load mappings form: ' . $e->getMessage()); } @@ -114,7 +110,7 @@ public function onContentPrepareForm(PrepareFormEvent $event): void try { $modifiedXml = $this->adjustFieldsGroup($mainXml, $groupName); $form->load($modifiedXml, false); - } catch (Exception $e) { + } catch (\Exception $e) { error_log('OpenGraph Plugin: Failed to load main form: ' . $e->getMessage()); } } @@ -148,7 +144,7 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void $input = $app->input; if ( $input->getCmd('option') !== 'com_content' - || $input->getCmd('view') !== 'article' + || $input->getCmd('view') !== 'article' || ! $id = $input->getInt('id') ) { return; @@ -167,7 +163,7 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void $params = ComponentHelper::getParams('com_content'); // Fallback if for some reason it isn’t an object if (! $params instanceof Registry) { - $params = new Registry; + $params = new Registry(); } /** @var ArticleModel $articleModel */ @@ -194,29 +190,29 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void // Get menu parameters - $menuParams = $this->getMenuParams(); + $menuParams = $this->getMenuParams(); $articleAttribs = new Registry($article->attribs ?? '{}'); $categoryParams = new Registry($category->params ?? '{}'); - $articleImages = $this->getAllArticleImages(new Registry($article->images ?? '{}')); + $articleImages = $this->getAllArticleImages(new Registry($article->images ?? '{}')); $ogTags = [ - 'og_title' => '', - 'og_description' => '', - 'og_image' => '', - 'og_image_alt' => '', - 'og_type' => '', - 'og_url' => '', - 'twitter_card' => '', - 'twitter_title' => '', + 'og_title' => '', + 'og_description' => '', + 'og_image' => '', + 'og_image_alt' => '', + 'og_type' => '', + 'og_url' => '', + 'twitter_card' => '', + 'twitter_title' => '', 'twitter_description' => '', - 'twitter_image' => '', - 'twitter_image_alt' => '', - 'fb_app_id' => '', - 'site_name' => '', - 'url' => '', - 'base_url' => '' + 'twitter_image' => '', + 'twitter_image_alt' => '', + 'fb_app_id' => '', + 'site_name' => '', + 'url' => '', + 'base_url' => '', ]; // Get Global settings @@ -225,8 +221,8 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void $ogTags['fb_app_id'] = $this->params->get('fb_app_id'); $ogTags['site_name'] = $config->get('sitename'); - $ogTags['base_url'] = Uri::base(); - $ogTags['url'] = Uri::getInstance()->toString(); + $ogTags['base_url'] = Uri::base(); + $ogTags['url'] = Uri::getInstance()->toString(); // get OG tags from category mappings $this->getOgTagsFromCategoryMappings($categoryParams, $article, $articleImages, $ogTags); @@ -261,7 +257,7 @@ private function getOgTagsFromCategoryMappings(Registry $categoryParams, object if (strpos($key, 'og_') === 0 && str_ends_with($key, '_field')) { $ogTagName = substr($key, 0, -6); // Remove "_field" from the end - $value = $this->getFieldValue($article, $fieldName, $articleImages); + $value = $this->getFieldValue($article, $fieldName, $articleImages); $ogTags[$ogTagName] = $value; } } @@ -360,7 +356,7 @@ private function getFieldValue(object $article, string $fieldName, array $articl */ private function getAllArticleImages(Registry $articleImages): array { - $image_intro = $image_intro_alt = ''; + $image_intro = $image_intro_alt = ''; $image_fulltext = $image_fulltext_alt = ''; // Handle image_intro @@ -383,9 +379,9 @@ private function getAllArticleImages(Registry $articleImages): array } return [ - 'image_intro' => $image_intro, - 'image_intro_alt' => $image_intro_alt, - 'image_fulltext' => $image_fulltext, + 'image_intro' => $image_intro, + 'image_intro_alt' => $image_intro_alt, + 'image_fulltext' => $image_fulltext, 'image_fulltext_alt' => $image_fulltext_alt, ]; } @@ -427,10 +423,10 @@ private function getOgTagsFromParams(Registry $params, array &$ogTags): void private function getTwitterOgTags(array &$ogTags): void { $twitterTags = [ - 'twitter_title' => $ogTags['twitter_title'], + 'twitter_title' => $ogTags['twitter_title'], 'twitter_description' => $ogTags['twitter_description'], - 'twitter_image' => $ogTags['twitter_image'], - 'twitter_image_alt' => $ogTags['twitter_image_alt'] + 'twitter_image' => $ogTags['twitter_image'], + 'twitter_image_alt' => $ogTags['twitter_image_alt'], ]; foreach ($twitterTags as $key => $value) { // If the value is not set, use the OG value @@ -497,10 +493,10 @@ private function setOpenGraphImage( Document $document, array $ogTags ): void { - $image = $ogTags['og_image']; - $alt = $ogTags['og_image_alt']; - $baseUrl = $ogTags['base_url']; - $twitterImage = $ogTags['twitter_image']; + $image = $ogTags['og_image']; + $alt = $ogTags['og_image_alt']; + $baseUrl = $ogTags['base_url']; + $twitterImage = $ogTags['twitter_image']; $twitterImageAlt = $ogTags['twitter_image_alt']; if (empty($image) || !empty($document->getMetaData('og:image'))) { @@ -564,7 +560,7 @@ protected function isSupported($context): bool try { $component = $this->getApplication()->bootComponent($componentName); - } catch (Exception $e) { + } catch (\Exception $e) { error_log('OpenGraph Plugin: Failed to boot component: ' . $e->getMessage()); return false; } @@ -585,10 +581,10 @@ protected function isSupported($context): bool private function adjustFieldsGroup(string $filePath, string $newGroup): string { $xmlContent = file_get_contents($filePath); - $xml = simplexml_load_string($xmlContent); + $xml = simplexml_load_string($xmlContent); if ($xml === false) { - throw new Exception("Could not load XML file: {$filePath}"); + throw new \Exception("Could not load XML file: {$filePath}"); } // Adjust all nodes to use the desired group diff --git a/plugins/system/opengraph/src/Field/OpengraphField.php b/plugins/system/opengraph/src/Field/OpengraphField.php index 67c4dd30e84..230bf8aeebc 100644 --- a/plugins/system/opengraph/src/Field/OpengraphField.php +++ b/plugins/system/opengraph/src/Field/OpengraphField.php @@ -13,9 +13,9 @@ use Joomla\CMS\Fields\FieldsServiceInterface; use Joomla\CMS\Form\Field\GroupedlistField; use Joomla\CMS\HTML\HTMLHelper; +use Joomla\CMS\Language\Text; use Joomla\CMS\Opengraph\OpengraphServiceInterface; use Joomla\Component\Fields\Administrator\Helper\FieldsHelper; -use Joomla\CMS\Language\Text; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -48,11 +48,11 @@ class OpengraphField extends GroupedlistField */ protected function getGroups() { - $app = Factory::getApplication(); + $app = Factory::getApplication(); $groups = []; $groups[''] = [ - HTMLHelper::_('select.option', '', Text::_('PLG_SYSTEM_OPENGRAPH_NO_FIELD_SELECTED')) + HTMLHelper::_('select.option', '', Text::_('PLG_SYSTEM_OPENGRAPH_NO_FIELD_SELECTED')), ]; @@ -63,7 +63,7 @@ protected function getGroups() return $groups; } - $fields = $component->getOpengraphFields(); + $fields = $component->getOpengraphFields(); $fieldType = $this->getAttribute('field-type'); if (isset($fields[$fieldType])) { @@ -83,8 +83,8 @@ protected function getGroups() // Allowed field types for each OpenGraph group $allowedFieldTypes = [ - 'text-fields' => ['text', 'textarea'], - 'image-fields' => ['media', 'imagelist'], + 'text-fields' => ['text', 'textarea'], + 'image-fields' => ['media', 'imagelist'], 'image-alt-fields' => ['text'], ]; @@ -101,15 +101,15 @@ protected function getGroups() // Dummy item with catid so FieldsService filters by assignment $scopeItem = $catId ? (object) ['catid' => $catId] : null; - $customFields = FieldsHelper::getFields('com_content.article', $scopeItem); + $customFields = FieldsHelper::getFields('com_content.article', $scopeItem); $customOptions = []; foreach ($customFields as $field) { - if (!in_array($field->type, $allowedTypes, true)) { + if (!\in_array($field->type, $allowedTypes, true)) { continue; } - $label = $field->title . ' (' . $field->name . ')'; + $label = $field->title . ' (' . $field->name . ')'; $customOptions[] = HTMLHelper::_('select.option', 'field.' . $field->name, $label); } From 419dfa71173c5b367893e70dc03a9c433bcb0004 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Wed, 23 Jul 2025 16:57:54 +0530 Subject: [PATCH 23/47] Chore: Normalize line endings and fix code style via PHPCBF --- cli/joomla.php | 8 -------- plugins/system/opengraph/src/Extension/opengraph.php | 4 ---- 2 files changed, 12 deletions(-) diff --git a/cli/joomla.php b/cli/joomla.php index dc3f1f9b9c7..c13b949d5d0 100755 --- a/cli/joomla.php +++ b/cli/joomla.php @@ -10,17 +10,14 @@ // We are a valid entry point. const _JEXEC = 1; - // Define the application's minimum supported PHP version as a constant so it can be referenced within the application. const JOOMLA_MINIMUM_PHP = '8.1.0'; - if (version_compare(PHP_VERSION, JOOMLA_MINIMUM_PHP, '<')) { echo 'Sorry, your PHP version is not supported.' . PHP_EOL; echo 'Your command line php needs to be version ' . JOOMLA_MINIMUM_PHP . ' or newer to run the Joomla! CLI Tools' . PHP_EOL; echo 'The version of PHP currently running this code, at the command line, is PHP version ' . PHP_VERSION . '.' . PHP_EOL; echo 'Please note, the version of PHP running your commands here, may be different to the version that is used by '; echo 'your web server to run the Joomla! Web Application' . PHP_EOL; - exit; } @@ -39,7 +36,6 @@ echo 'It looks like you are trying to run Joomla! from our git repository.' . PHP_EOL; echo 'To do so requires you complete a couple of extra steps first.' . PHP_EOL; echo 'Please see https://docs.joomla.org/Special:MyLanguage/J5.x:Setting_Up_Your_Local_Environment for further details.' . PHP_EOL; - exit; } @@ -49,16 +45,13 @@ || (filesize(JPATH_CONFIGURATION . '/configuration.php') < 10) ) { echo 'Install Joomla to run cli commands' . PHP_EOL; - exit; } // Get the framework. require_once JPATH_BASE . '/includes/framework.php'; - // Boot the DI container $container = \Joomla\CMS\Factory::getContainer(); - /* * Alias the session service keys to the CLI session service as that is the primary session backend for this application * @@ -71,7 +64,6 @@ ->alias(\Joomla\CMS\Session\Session::class, 'session.cli') ->alias(\Joomla\Session\Session::class, 'session.cli') ->alias(\Joomla\Session\SessionInterface::class, 'session.cli'); - $app = \Joomla\CMS\Factory::getContainer()->get(\Joomla\Console\Application::class); \Joomla\CMS\Factory::$application = $app; $app->execute(); diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index f6900d0cae1..cdce7e4258e 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -283,9 +283,7 @@ private function getFieldValue(object $article, string $fieldName, array $articl $customFields = FieldsHelper::getFields('com_content.article', $article, true); foreach ($customFields as $field) { - if ($field->name == $customFieldName) { - return $field->value ?? ''; } } @@ -401,9 +399,7 @@ private function getOgTagsFromParams(Registry $params, array &$ogTags): void foreach (array_keys($ogTags) as $ogTagName) { - if ($params->exists($ogTagName)) { - $value = $params->get($ogTagName); From 928ad4be9f1c6848b208f34f6e67261577102b3e Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Tue, 29 Jul 2025 13:15:20 +0530 Subject: [PATCH 24/47] feat : add string sanitization and truncate to the plugin --- .../language/en-GB/plg_system_opengraph.ini | 41 +++-- plugins/system/opengraph/opengraph.xml | 155 +++++++----------- .../opengraph/src/Extension/opengraph.php | 81 +++++++++ .../system/opengraph/src/forms/opengraph.xml | 4 +- 4 files changed, 167 insertions(+), 114 deletions(-) diff --git a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini index 80f8b75c993..28270c4ffd3 100644 --- a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini +++ b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini @@ -8,20 +8,11 @@ PLG_SYSTEM_OPENGRAPH_DESCRIPTION="Open Graph Plugin automatically generates Open ; Global OG Settings PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_SETTINGS="Global Open Graph Settings" -PLG_SYSTEM_OPENGRAPH_ENABLE_OG_GENERATION="Enable OG Tag Generation" -PLG_SYSTEM_OPENGRAPH_ENABLE_OG_GENERATION_DESC="Enable automatic generation of Open Graph meta tags for improved social media sharing." +PLG_SYSTEM_OPENGRAPH_GLOBAL_DESC="Global defaults used when no article / menu overrides are present." +PLG_SYSTEM_OPENGRAPH_OVERVIEW="Overview & Help" -PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_TITLE="Global OG Title" -PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_TITLE_DESC="Default Open Graph title used as fallback when no article or category-specific title is available. Leave empty to use site name." - -PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_DESCRIPTION="Global OG Description" -PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_DESCRIPTION_DESC="Default Open Graph description used as fallback when no article or category-specific description is available. Maximum 200 characters." - -PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_IMAGE="Global OG Image" -PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_IMAGE_DESC="Default Open Graph image used as fallback when no article or category-specific image is available. Recommended size: 1200x630 pixels." - -PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_TYPE="Global OG Type" -PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_TYPE_DESC="Default Open Graph type for your website. 'Website' is recommended for most sites." +PLG_SYSTEM_OPENGRAPH_ENABLE_OG_GENERATION="Enable OpenGraph" +PLG_SYSTEM_OPENGRAPH_ENABLE_OG_GENERATION_DESC="Enable OpenGraph tags generation." ; Override Settings PLG_SYSTEM_OPENGRAPH_OVERRIDE_SETTINGS="Override Settings" @@ -90,6 +81,9 @@ PLG_SYSTEM_OPENGRAPH_SHOW_ADVANCED_DESC="Show advanced settings for OpenGraph me PLG_SYSTEM_OPENGRAPH_TWITTER_CARD_LABEL="Twitter Card Type" PLG_SYSTEM_OPENGRAPH_TWITTER_CARD_DESC="Select the type of Twitter card to use for this content." +PLG_SYSTEM_OPENGRAPH_TWITTER_CARD_SUMMARY="Summary" +PLG_SYSTEM_OPENGRAPH_TWITTER_CARD_SUMMARY_LARGE_IMAGE="Summary Large Image" + PLG_SYSTEM_OPENGRAPH_TWITTER_TITLE_LABEL="Twitter Title" PLG_SYSTEM_OPENGRAPH_TWITTER_TITLE_DESC="Custom title for Twitter sharing. Recommended length: 70 characters." PLG_SYSTEM_OPENGRAPH_TWITTER_TITLE_HINT="Enter a compelling title for Twitter sharing" @@ -127,3 +121,24 @@ PLG_SYSTEM_OPENGRAPH_TYPE_FIELD_DESC="Select a custom field to use as the OpenGr PLG_SYSTEM_OPENGRAPH_NO_FIELD_SELECTED="-- No Field Selected --" +PLG_SYSTEM_OPENGRAPH_FB_APP_ID_LABEL="Facebook App ID" +PLG_SYSTEM_OPENGRAPH_FB_APP_ID_DESC="Needed only for Facebook Insights." + + +PLG_SYSTEM_OPENGRAPH_FEATURES_LABEL="What this plugin does" +PLG_SYSTEM_OPENGRAPH_FEATURES_DESC="• Generates complete Open Graph and Twitter Card metadata on the fly. • Hierarchical override system: Menu → Article → Category → Global. • Auto‑picks intro/full images and alt text. • Smart sanitisation & trimming prevents broken previews." + +PLG_SYSTEM_OPENGRAPH_QUICKSTART_LABEL="How to use" +PLG_SYSTEM_OPENGRAPH_QUICKSTART_DESC="1. Enable the plugin. 2. Optional: enter your Facebook App ID above. 3. Edit any article → ‘Open Graph’ tab to override title, description, or image. 4. Clear cache, share your URL and enjoy rich previews!" + +PLG_SYSTEM_OPENGRAPH_TIPS_LABEL="Troubleshooting" +PLG_SYSTEM_OPENGRAPH_TIPS_DESC="• Changed an image but Facebook still shows the old one? Clear Joomla & CDN caches, then run Facebook Sharing Debugger. • Large images (>5 MB) may be ignored by Twitter. • Verify that og:image is an absolute URL (use the built‑in sanitiser)." + +PLG_SYSTEM_OPENGRAPH_MAX_TITLE_LABEL="Max Title Length" +PLG_SYSTEM_OPENGRAPH_MAX_TITLE_DESC="Recommended: ≤60 characters for best display in Facebook and Twitter cards." + +PLG_SYSTEM_OPENGRAPH_MAX_DESC_LABEL="Max Description Length" +PLG_SYSTEM_OPENGRAPH_MAX_DESC_DESC="Recommended: ≤160 characters (Twitter truncates beyond this)." + +PLG_SYSTEM_OPENGRAPH_MAX_ALT_LABEL="Max Alt Text Length" +PLG_SYSTEM_OPENGRAPH_MAX_ALT_DESC="Recommended: ≤125 characters (based on WCAG guidelines)." diff --git a/plugins/system/opengraph/opengraph.xml b/plugins/system/opengraph/opengraph.xml index 7fd9fa83880..1eec4bf21d5 100644 --- a/plugins/system/opengraph/opengraph.xml +++ b/plugins/system/opengraph/opengraph.xml @@ -1,5 +1,8 @@ - + PLG_SYSTEM_OPENGRAPH Joomla! Project 2025-06 @@ -8,121 +11,75 @@ support@joomla.org https://joomla.org __DEPLOY_VERSION__ - PLG_SYSTEM_OPENGRAPH_DESCRIPTION + PLG_SYSTEM_OPENGRAPH_DESCRIPTION Joomla\Plugin\System\Opengraph - src services opengraph.xml - language/en-GB/plg_system_opengraph.ini language/en-GB/plg_system_opengraph.sys.ini - -
- +
+ - - - - - - - - - - - - - - - - - - + + + +
- -
- - - - - - - - - - - - - - +
+ + +
- - - diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index cdce7e4258e..01711400762 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -26,6 +26,8 @@ use Joomla\Component\Fields\Administrator\Helper\FieldsHelper; use Joomla\Event\SubscriberInterface; use Joomla\Registry\Registry; +use Joomla\CMS\Filter\OutputFilter; +use Joomla\CMS\HTML\HTMLHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -77,6 +79,8 @@ public static function getSubscribedEvents(): array * @param PrepareFormEvent $event * * @return void + * + * @since __DEPLOY_VERSION__ */ public function onContentPrepareForm(PrepareFormEvent $event): void { @@ -123,6 +127,8 @@ public function onContentPrepareForm(PrepareFormEvent $event): void * @param BeforeCompileHeadEvent $event * * @return void + * + * @since __DEPLOY_VERSION__ */ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void { @@ -236,6 +242,9 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void // get Twitter tags $this->getTwitterOgTags($ogTags); + // sanitize OG tags + $this->sanitizeOgTags($ogTags); + // Inject the OpenGraph data into the document $this->injectOpenGraphData($document, $ogTags); } @@ -248,6 +257,10 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void * @param object $article The article object. * @param array $articleImages The array of article images. * @param array $ogTags The array of OG tags. + * + * @return void + * + * @since __DEPLOY_VERSION__ */ private function getOgTagsFromCategoryMappings(Registry $categoryParams, object $article, array $articleImages, array &$ogTags): void { @@ -393,6 +406,8 @@ private function getAllArticleImages(Registry $articleImages): array * @param array &$ogTags Reference to the OG tags array to populate * * @return void + * + * @since __DEPLOY_VERSION__ */ private function getOgTagsFromParams(Registry $params, array &$ogTags): void { @@ -415,6 +430,8 @@ private function getOgTagsFromParams(Registry $params, array &$ogTags): void * @param array &$ogTags * * @return void + * + * @since __DEPLOY_VERSION__ */ private function getTwitterOgTags(array &$ogTags): void { @@ -439,6 +456,8 @@ private function getTwitterOgTags(array &$ogTags): void * @param array $ogTags * * @return void + * + * @since __DEPLOY_VERSION__ */ private function injectOpenGraphData(Document $document, array $ogTags): void { @@ -468,6 +487,8 @@ private function injectOpenGraphData(Document $document, array $ogTags): void * @param string $attributeType * * @return void + * + * @since __DEPLOY_VERSION__ */ private function setMetaData(Document $document, string $name, ?string $value, string $attributeType): void { @@ -572,6 +593,7 @@ protected function isSupported($context): bool * @param string $newGroup * * @return string + * * @since __DEPLOY_VERSION__ */ private function adjustFieldsGroup(string $filePath, string $newGroup): string @@ -596,6 +618,7 @@ private function adjustFieldsGroup(string $filePath, string $newGroup): string * Get the parameters associated with the active menu item * * @return Registry + * * @since __DEPLOY_VERSION__ */ private function getMenuParams(): Registry @@ -610,4 +633,62 @@ private function getMenuParams(): Registry return $menu->getParams($active->id); } + + /** + * Clean up and normalise all OG / Twitter tag values. + * + * @param array &$ogTags Reference to the tag array created earlier. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + private function sanitizeOgTags(array &$ogTags): void + { + // TODO : will be configurable in the future from the plugin settings + + $maxTitleLen = $this->params->get('max_title_length', 60); // Facebook shows ~55–60 chars, Twitter ~70 + $maxDescLen = $this->params->get('max_description_length', 160); // Twitter summary cards truncate after ~160 + $maxAltLen = $this->params->get('max_alt_length', 125); // WCAG recommendation for alt text + + foreach (['og_title', 'twitter_title'] as $key) { + $ogTags[$key] = $this->cleanText($ogTags[$key] ?? '', $maxTitleLen); + } + + foreach (['og_description', 'twitter_description'] as $key) { + $ogTags[$key] = $this->cleanText($ogTags[$key] ?? '', $maxDescLen); + } + + foreach (['og_image_alt', 'twitter_image_alt'] as $key) { + $ogTags[$key] = $this->cleanText($ogTags[$key] ?? '', $maxAltLen); + } + + // Make sure og:url is absolute + if (!empty($ogTags['og_url']) && !preg_match('~^https?://~i', $ogTags['og_url'])) { + $ogTags['og_url'] = Uri::root() . ltrim($ogTags['og_url'], '/'); + } + } + + + /** + * Helper: strip HTML, decode entities, collapse whitespace, then truncate on a word boundary and add an ellipsis if needed. + * + * @param string $text + * @param int $maxLen + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + private function cleanText(string $text, int $maxLen): string + { + // Remove tags and entities, normalise whitespace + $plain = OutputFilter::cleanText($text); + + // Truncate the text on a word boundary and add an ellipsis if needed + $truncated = HTMLHelper::_('string.truncate', $plain, $maxLen, true, false); + + // Replace the three-dot ellipsis with a single Unicode one + return preg_replace('/\.\.\.$/', '…', $truncated); + } } diff --git a/plugins/system/opengraph/src/forms/opengraph.xml b/plugins/system/opengraph/src/forms/opengraph.xml index e2281605b58..74f0cd269c6 100644 --- a/plugins/system/opengraph/src/forms/opengraph.xml +++ b/plugins/system/opengraph/src/forms/opengraph.xml @@ -75,8 +75,8 @@ type="list" label="PLG_SYSTEM_OPENGRAPH_TWITTER_CARD_LABEL" showon="show_advanced:1"> - - + + Date: Tue, 29 Jul 2025 13:25:44 +0530 Subject: [PATCH 25/47] fix : sorted language strings --- .../language/en-GB/plg_system_opengraph.ini | 181 ++++++------------ 1 file changed, 62 insertions(+), 119 deletions(-) diff --git a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini index 28270c4ffd3..7e9fcee4efc 100644 --- a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini +++ b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini @@ -2,143 +2,86 @@ ; Copyright (C) 2025 Open Source Matters, Inc. ; License GNU General Public License version 2 or later -; Plugin Description -PLG_SYSTEM_OPENGRAPH_DESCRIPTION="Open Graph Plugin automatically generates Open Graph tags for improved social media sharing. Features hierarchical override system supporting global, category, and article-level metadata configuration." - -; Global OG Settings -PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_SETTINGS="Global Open Graph Settings" -PLG_SYSTEM_OPENGRAPH_GLOBAL_DESC="Global defaults used when no article / menu overrides are present." -PLG_SYSTEM_OPENGRAPH_OVERVIEW="Overview & Help" +PLG_SYSTEM_OPENGRAPH_ADVANCED_SECTION ="Advanced Settings" +PLG_SYSTEM_OPENGRAPH_DESCRIPTION="Open Graph Plugin automatically generates Open Graph tags for improved social media sharing. Features hierarchical override system supporting global, category, and article-level metadata configuration." +PLG_SYSTEM_OPENGRAPH_DESCRIPTION_FIELD_DESC="Select which article field to use for og:description meta tag. Meta description is preferred over article text." +PLG_SYSTEM_OPENGRAPH_DESCRIPTION_FIELD_LABEL="Description Source" +PLG_SYSTEM_OPENGRAPH_ENABLE_DESC="Enable OpenGraph tags for this content." +PLG_SYSTEM_OPENGRAPH_ENABLE_LABEL="Enable OpenGraph" PLG_SYSTEM_OPENGRAPH_ENABLE_OG_GENERATION="Enable OpenGraph" PLG_SYSTEM_OPENGRAPH_ENABLE_OG_GENERATION_DESC="Enable OpenGraph tags generation." - -; Override Settings -PLG_SYSTEM_OPENGRAPH_OVERRIDE_SETTINGS="Override Settings" - -PLG_SYSTEM_OPENGRAPH_ENABLE_ARTICLE_OVERRIDE="Enable Article-Level Override" -PLG_SYSTEM_OPENGRAPH_ENABLE_ARTICLE_OVERRIDE_DESC="Allow articles to have custom Open Graph metadata via custom fields. When enabled, article OG fields will take priority over category and global settings." - -PLG_SYSTEM_OPENGRAPH_ENABLE_CATEGORY_OVERRIDE="Enable Category-Level Override" -PLG_SYSTEM_OPENGRAPH_ENABLE_CATEGORY_OVERRIDE_DESC="Allow categories to have custom Open Graph metadata. When enabled, category OG settings will take priority over global settings but will be overridden by article-specific settings." - -PLG_SYSTEM_OPENGRAPH_FALLBACK_TO_SITE_LOGO="Use Site Logo as Fallback" -PLG_SYSTEM_OPENGRAPH_FALLBACK_TO_SITE_LOGO_DESC="When no OG image is specified, use the site's logo as fallback image. If disabled, no image tag will be generated." - -;fieldsets +PLG_SYSTEM_OPENGRAPH_FB_APP_ID_DESC="Needed only for Facebook Insights." +PLG_SYSTEM_OPENGRAPH_FB_APP_ID_LABEL="Facebook App ID" +PLG_SYSTEM_OPENGRAPH_FEATURES_DESC="• Generates complete Open Graph and Twitter Card metadata on the fly. • Hierarchical override system: Menu → Article → Category → Global. • Auto‑picks intro/full images and alt text. • Smart sanitisation & trimming prevents broken previews." +PLG_SYSTEM_OPENGRAPH_FEATURES_LABEL="What this plugin does" PLG_SYSTEM_OPENGRAPH_FIELDSET_ARTICLE="OpenGraph" PLG_SYSTEM_OPENGRAPH_FIELDSET_ARTICLE_DESC="Configure OpenGraph metadata for social media sharing. These settings override global defaults." - - -; Common Field Labels -PLG_SYSTEM_OPENGRAPH_SHOW_MANUAL_OVERRIDE_LABEL="Show Manual Override Settings" -PLG_SYSTEM_OPENGRAPH_SHOW_MANUAL_OVERRIDE_DESC="Enable this to manually set OG tags like title, description, and image." - -PLG_SYSTEM_OPENGRAPH_OG_TITLE_LABEL="OpenGraph Title" -PLG_SYSTEM_OPENGRAPH_OG_TITLE_DESC="Custom title for social media sharing. Recommended length: 40-60 characters." -PLG_SYSTEM_OPENGRAPH_OG_TITLE_HINT="Enter a compelling title for social sharing" - -PLG_SYSTEM_OPENGRAPH_OG_DESCRIPTION_LABEL="OpenGraph Description" +PLG_SYSTEM_OPENGRAPH_FIELD_MAPPING_SECTION="Field Mapping Configuration" +PLG_SYSTEM_OPENGRAPH_GLOBAL_DESC="Global defaults used when no article / menu overrides are present." +PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_SETTINGS="Global Open Graph Settings" +PLG_SYSTEM_OPENGRAPH_IMAGE_ALT_FIELD_DESC="Select which article field to use for og:image:alt meta tag for accessibility" +PLG_SYSTEM_OPENGRAPH_IMAGE_ALT_FIELD_LABEL="Image Alt Text Source" +PLG_SYSTEM_OPENGRAPH_IMAGE_FIELD_DESC="Select which article field to use for og:image meta tag" +PLG_SYSTEM_OPENGRAPH_IMAGE_FIELD_LABEL="Image Source" +PLG_SYSTEM_OPENGRAPH_MANUAL_OVERRIDE_SECTION="Manual Override Options" +PLG_SYSTEM_OPENGRAPH_MAX_ALT_DESC="Recommended: ≤125 characters (based on WCAG guidelines)." +PLG_SYSTEM_OPENGRAPH_MAX_ALT_LABEL="Max Alt Text Length" +PLG_SYSTEM_OPENGRAPH_MAX_DESC_DESC="Recommended: ≤160 characters (Twitter truncates beyond this)." +PLG_SYSTEM_OPENGRAPH_MAX_DESC_LABEL="Max Description Length" +PLG_SYSTEM_OPENGRAPH_MAX_TITLE_DESC="Recommended: ≤60 characters for best display in Facebook and Twitter cards." +PLG_SYSTEM_OPENGRAPH_MAX_TITLE_LABEL="Max Title Length" +PLG_SYSTEM_OPENGRAPH_NO_FIELD_SELECTED="-- No Field Selected --" PLG_SYSTEM_OPENGRAPH_OG_DESCRIPTION_DESC="Brief description for social media sharing. Recommended length: 120-160 characters." PLG_SYSTEM_OPENGRAPH_OG_DESCRIPTION_HINT="Enter a brief, engaging description" - -PLG_SYSTEM_OPENGRAPH_OG_IMAGE_LABEL="OpenGraph Image" -PLG_SYSTEM_OPENGRAPH_OG_IMAGE_DESC="Image for social media sharing. Recommended size: 1200x630 pixels." - -PLG_SYSTEM_OPENGRAPH_OG_IMAGE_ALT_LABEL="OpenGraph Image Alt Text" +PLG_SYSTEM_OPENGRAPH_OG_DESCRIPTION_LABEL="OpenGraph Description" PLG_SYSTEM_OPENGRAPH_OG_IMAGE_ALT_DESC="Alternative text for the OpenGraph image for accessibility." PLG_SYSTEM_OPENGRAPH_OG_IMAGE_ALT_HINT="Enter a brief description of the image for accessibility" - -PLG_SYSTEM_OPENGRAPH_OG_TYPE_LABEL="OpenGraph Type" +PLG_SYSTEM_OPENGRAPH_OG_IMAGE_ALT_LABEL="OpenGraph Image Alt Text" +PLG_SYSTEM_OPENGRAPH_OG_IMAGE_DESC="Image for social media sharing. Recommended size: 1200x630 pixels." +PLG_SYSTEM_OPENGRAPH_OG_IMAGE_LABEL="OpenGraph Image" +PLG_SYSTEM_OPENGRAPH_OG_TITLE_DESC="Custom title for social media sharing. Recommended length: 40-60 characters." +PLG_SYSTEM_OPENGRAPH_OG_TITLE_HINT="Enter a compelling title for social sharing" +PLG_SYSTEM_OPENGRAPH_OG_TITLE_LABEL="OpenGraph Title" PLG_SYSTEM_OPENGRAPH_OG_TYPE_DESC="Content type for social media platforms." - -PLG_SYSTEM_OPENGRAPH_OG_URL_LABEL="Custom URL" +PLG_SYSTEM_OPENGRAPH_OG_TYPE_LABEL="OpenGraph Type" PLG_SYSTEM_OPENGRAPH_OG_URL_DESC="Override the canonical URL for sharing." PLG_SYSTEM_OPENGRAPH_OG_URL_HINT="Leave empty to use the article's URL" - -PLG_SYSTEM_OPENGRAPH_ENABLE_LABEL="Enable OpenGraph" -PLG_SYSTEM_OPENGRAPH_ENABLE_DESC="Enable OpenGraph tags for this content." - -; OpenGraph Types -PLG_SYSTEM_OPENGRAPH_USE_DEFAULT="Use Default" -PLG_SYSTEM_OPENGRAPH_TYPE_ARTICLE="Article" -PLG_SYSTEM_OPENGRAPH_TYPE_WEBSITE="Website" -PLG_SYSTEM_OPENGRAPH_TYPE_BLOG="Blog" -PLG_SYSTEM_OPENGRAPH_TYPE_VIDEO="Video" -PLG_SYSTEM_OPENGRAPH_TYPE_MUSIC="Music" -PLG_SYSTEM_OPENGRAPH_TYPE_BOOK="Book" -PLG_SYSTEM_OPENGRAPH_TYPE_PROFILE="Profile" -PLG_SYSTEM_OPENGRAPH_TYPE_PRODUCT="Product" -PLG_SYSTEM_OPENGRAPH_TYPE_EVENT="Event" - -;advanced settings -PLG_SYSTEM_OPENGRAPH_ADVANCED_SECTION ="Advanced Settings" - -PLG_SYSTEM_OPENGRAPH_SHOW_ADVANCED_LABEL="Show Advanced Settings" +PLG_SYSTEM_OPENGRAPH_OG_URL_LABEL="Custom URL" +PLG_SYSTEM_OPENGRAPH_OVERRIDE_SETTINGS="Override Settings" +PLG_SYSTEM_OPENGRAPH_OVERVIEW="Overview & Help" +PLG_SYSTEM_OPENGRAPH_QUICKSTART_DESC="1. Enable the plugin. 2. Optional: enter your Facebook App ID above. 3. Edit any article → ‘Open Graph’ tab to override title, description, or image. 4. Clear cache, share your URL and enjoy rich previews!" +PLG_SYSTEM_OPENGRAPH_QUICKSTART_LABEL="How to use" PLG_SYSTEM_OPENGRAPH_SHOW_ADVANCED_DESC="Show advanced settings for OpenGraph metadata." - -PLG_SYSTEM_OPENGRAPH_TWITTER_CARD_LABEL="Twitter Card Type" +PLG_SYSTEM_OPENGRAPH_SHOW_ADVANCED_LABEL="Show Advanced Settings" +PLG_SYSTEM_OPENGRAPH_SHOW_MANUAL_OVERRIDE_DESC="Enable this to manually set OG tags like title, description, and image." +PLG_SYSTEM_OPENGRAPH_SHOW_MANUAL_OVERRIDE_LABEL="Show Manual Override Settings" +PLG_SYSTEM_OPENGRAPH_TIPS_DESC="• Changed an image but Facebook still shows the old one? Clear Joomla & CDN caches, then run Facebook Sharing Debugger. • Large images (>5 MB) may be ignored by Twitter. • Verify that og:image is an absolute URL (use the built‑in sanitiser)." +PLG_SYSTEM_OPENGRAPH_TIPS_LABEL="Troubleshooting" +PLG_SYSTEM_OPENGRAPH_TITLE_FIELD_DESC="Select which article field to use for og:title meta tag" +PLG_SYSTEM_OPENGRAPH_TITLE_FIELD_LABEL="Title Source" PLG_SYSTEM_OPENGRAPH_TWITTER_CARD_DESC="Select the type of Twitter card to use for this content." - +PLG_SYSTEM_OPENGRAPH_TWITTER_CARD_LABEL="Twitter Card Type" PLG_SYSTEM_OPENGRAPH_TWITTER_CARD_SUMMARY="Summary" PLG_SYSTEM_OPENGRAPH_TWITTER_CARD_SUMMARY_LARGE_IMAGE="Summary Large Image" - -PLG_SYSTEM_OPENGRAPH_TWITTER_TITLE_LABEL="Twitter Title" -PLG_SYSTEM_OPENGRAPH_TWITTER_TITLE_DESC="Custom title for Twitter sharing. Recommended length: 70 characters." -PLG_SYSTEM_OPENGRAPH_TWITTER_TITLE_HINT="Enter a compelling title for Twitter sharing" - -PLG_SYSTEM_OPENGRAPH_TWITTER_DESC_LABEL="Twitter Description" PLG_SYSTEM_OPENGRAPH_TWITTER_DESC_DESC="Custom description for Twitter sharing. Recommended length: 120-160 characters." PLG_SYSTEM_OPENGRAPH_TWITTER_DESC_HINT="Enter a brief, engaging description" - -PLG_SYSTEM_OPENGRAPH_TWITTER_IMAGE_LABEL="Twitter Image" +PLG_SYSTEM_OPENGRAPH_TWITTER_DESC_LABEL="Twitter Description" PLG_SYSTEM_OPENGRAPH_TWITTER_IMAGE_DESC="Image for Twitter sharing. Recommended size: 1200x630 pixels." - -PLG_SYSTEM_OPENGRAPH_FB_APP_ID_LABEL="Facebook App ID" -PLG_SYSTEM_OPENGRAPH_FB_APP_ID_DESC="Enter your Facebook App ID to enable Facebook Open Graph debugging." -PLG_SYSTEM_OPENGRAPH_FB_APP_ID_HINT="Enter your Facebook App ID to enable Facebook Open Graph debugging." - - -; Field Selection (New Section) -PLG_SYSTEM_OPENGRAPH_FIELD_MAPPING_SECTION="Field Mapping Configuration" -PLG_SYSTEM_OPENGRAPH_MANUAL_OVERRIDE_SECTION="Manual Override Options" - -PLG_SYSTEM_OPENGRAPH_TITLE_FIELD_LABEL="Title Source" -PLG_SYSTEM_OPENGRAPH_TITLE_FIELD_DESC="Select which article field to use for og:title meta tag" - -PLG_SYSTEM_OPENGRAPH_DESCRIPTION_FIELD_LABEL="Description Source" -PLG_SYSTEM_OPENGRAPH_DESCRIPTION_FIELD_DESC="Select which article field to use for og:description meta tag. Meta description is preferred over article text." - -PLG_SYSTEM_OPENGRAPH_IMAGE_FIELD_LABEL="Image Source" -PLG_SYSTEM_OPENGRAPH_IMAGE_FIELD_DESC="Select which article field to use for og:image meta tag" - -PLG_SYSTEM_OPENGRAPH_IMAGE_ALT_FIELD_LABEL="Image Alt Text Source" -PLG_SYSTEM_OPENGRAPH_IMAGE_ALT_FIELD_DESC="Select which article field to use for og:image:alt meta tag for accessibility" - -PLG_SYSTEM_OPENGRAPH_TYPE_FIELD_LABEL="Type Field" +PLG_SYSTEM_OPENGRAPH_TWITTER_IMAGE_LABEL="Twitter Image" +PLG_SYSTEM_OPENGRAPH_TWITTER_TITLE_DESC="Custom title for Twitter sharing. Recommended length: 70 characters." +PLG_SYSTEM_OPENGRAPH_TWITTER_TITLE_HINT="Enter a compelling title for Twitter sharing" +PLG_SYSTEM_OPENGRAPH_TWITTER_TITLE_LABEL="Twitter Title" +PLG_SYSTEM_OPENGRAPH_TYPE_ARTICLE="Article" +PLG_SYSTEM_OPENGRAPH_TYPE_BLOG="Blog" +PLG_SYSTEM_OPENGRAPH_TYPE_BOOK="Book" +PLG_SYSTEM_OPENGRAPH_TYPE_EVENT="Event" PLG_SYSTEM_OPENGRAPH_TYPE_FIELD_DESC="Select a custom field to use as the OpenGraph type source." - -PLG_SYSTEM_OPENGRAPH_NO_FIELD_SELECTED="-- No Field Selected --" - -PLG_SYSTEM_OPENGRAPH_FB_APP_ID_LABEL="Facebook App ID" -PLG_SYSTEM_OPENGRAPH_FB_APP_ID_DESC="Needed only for Facebook Insights." - - -PLG_SYSTEM_OPENGRAPH_FEATURES_LABEL="What this plugin does" -PLG_SYSTEM_OPENGRAPH_FEATURES_DESC="• Generates complete Open Graph and Twitter Card metadata on the fly. • Hierarchical override system: Menu → Article → Category → Global. • Auto‑picks intro/full images and alt text. • Smart sanitisation & trimming prevents broken previews." - -PLG_SYSTEM_OPENGRAPH_QUICKSTART_LABEL="How to use" -PLG_SYSTEM_OPENGRAPH_QUICKSTART_DESC="1. Enable the plugin. 2. Optional: enter your Facebook App ID above. 3. Edit any article → ‘Open Graph’ tab to override title, description, or image. 4. Clear cache, share your URL and enjoy rich previews!" - -PLG_SYSTEM_OPENGRAPH_TIPS_LABEL="Troubleshooting" -PLG_SYSTEM_OPENGRAPH_TIPS_DESC="• Changed an image but Facebook still shows the old one? Clear Joomla & CDN caches, then run Facebook Sharing Debugger. • Large images (>5 MB) may be ignored by Twitter. • Verify that og:image is an absolute URL (use the built‑in sanitiser)." - -PLG_SYSTEM_OPENGRAPH_MAX_TITLE_LABEL="Max Title Length" -PLG_SYSTEM_OPENGRAPH_MAX_TITLE_DESC="Recommended: ≤60 characters for best display in Facebook and Twitter cards." - -PLG_SYSTEM_OPENGRAPH_MAX_DESC_LABEL="Max Description Length" -PLG_SYSTEM_OPENGRAPH_MAX_DESC_DESC="Recommended: ≤160 characters (Twitter truncates beyond this)." - -PLG_SYSTEM_OPENGRAPH_MAX_ALT_LABEL="Max Alt Text Length" -PLG_SYSTEM_OPENGRAPH_MAX_ALT_DESC="Recommended: ≤125 characters (based on WCAG guidelines)." +PLG_SYSTEM_OPENGRAPH_TYPE_FIELD_LABEL="Type Field" +PLG_SYSTEM_OPENGRAPH_TYPE_MUSIC="Music" +PLG_SYSTEM_OPENGRAPH_TYPE_PRODUCT="Product" +PLG_SYSTEM_OPENGRAPH_TYPE_PROFILE="Profile" +PLG_SYSTEM_OPENGRAPH_TYPE_VIDEO="Video" +PLG_SYSTEM_OPENGRAPH_TYPE_WEBSITE="Website" +PLG_SYSTEM_OPENGRAPH_USE_DEFAULT="Use Default" From 8a9441bc63dadcd6ffcdacdd29cec7178025550a Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Thu, 31 Jul 2025 17:59:04 +0530 Subject: [PATCH 26/47] feat : added the javascript to auto update the placeholder values in the form --- .../language/en-GB/plg_system_opengraph.ini | 1 + .../opengraph/src/Extension/opengraph.php | 92 +++++++++++++++++-- .../system/opengraph/src/forms/opengraph.xml | 3 +- 3 files changed, 88 insertions(+), 8 deletions(-) diff --git a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini index 7e9fcee4efc..bc7bd79464f 100644 --- a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini +++ b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini @@ -85,3 +85,4 @@ PLG_SYSTEM_OPENGRAPH_TYPE_PROFILE="Profile" PLG_SYSTEM_OPENGRAPH_TYPE_VIDEO="Video" PLG_SYSTEM_OPENGRAPH_TYPE_WEBSITE="Website" PLG_SYSTEM_OPENGRAPH_USE_DEFAULT="Use Default" +PLG_SYSTEM_OPENGRAPH_INHERITED="inherited" diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 01711400762..a1cb0a7719e 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -21,14 +21,14 @@ use Joomla\CMS\Opengraph\OpengraphServiceInterface; use Joomla\CMS\Plugin\CMSPlugin; use Joomla\CMS\Uri\Uri; -use Joomla\Component\Content\Site\Model\ArticleModel; -use Joomla\Component\Content\Site\Model\CategoryModel; +use Joomla\Component\Categories\Administrator\Model\CategoryModel; +use Joomla\Component\Content\Administrator\Model\ArticleModel; use Joomla\Component\Fields\Administrator\Helper\FieldsHelper; use Joomla\Event\SubscriberInterface; use Joomla\Registry\Registry; use Joomla\CMS\Filter\OutputFilter; use Joomla\CMS\HTML\HTMLHelper; - +use Joomla\CMS\Language\Text; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects @@ -118,6 +118,88 @@ public function onContentPrepareForm(PrepareFormEvent $event): void error_log('OpenGraph Plugin: Failed to load main form: ' . $e->getMessage()); } } + + // if the form is a menu item, we don't need to change placeholder values + if ($isMenu) { + return; + } + + // Get the article id and category id + $input = $app->input; + $articleId = (int) ($input->getInt('id') ?: $form->getValue('id')); + $categoryId = 0; + + if ($articleId > 0) { + /** @var MVCComponent $articleComponent */ + $articleComponent = $app->bootComponent('com_content'); + /** @var MVCFactoryInterface $articleFactory */ + $articleFactory = $articleComponent->getMVCFactory(); + + /** @var ArticleModel $articleModel */ + $articleModel = $articleFactory->createModel('Article', 'Administrator', ['ignore_request' => true]); + $article = $articleModel->getItem($articleId); + + if ($article && !empty($article->catid)) { + $categoryId = (int) $article->catid; + } + } + + if ($categoryId > 0) { + /** @var MVCComponent $catComponent */ + $catComponent = $app->bootComponent('com_categories'); + /** @var MVCFactoryInterface $catFactory */ + $catFactory = $catComponent->getMVCFactory(); + + /** @var CategoryModel $catModel */ + $catModel = $catFactory->createModel('Category', 'Administrator', ['ignore_request' => true]); + $catModel->setState('category.id', $categoryId); + + $category = $catModel->getItem($categoryId); // JTable row + $catParams = new Registry($category->params ?? '{}'); + } + + + // Get the mappings from the category params + $mappings = []; + foreach ($catParams as $paramKey => $fieldName) { + if (str_starts_with($paramKey, 'og_') && str_ends_with($paramKey, '_field')) { + $ogTag = substr($paramKey, 0, -6); // strip "_field" + $mappings[$ogTag] = $fieldName; + } + } + + + if (!$mappings) { + return; // category has no mappings + } + + $maxTitleLen = $this->params->get('max_title_length', 60); + $maxDescLen = $this->params->get('max_description_length', 160); + $maxAltLen = $this->params->get('max_alt_length', 125); + $mappings["maxTitleLen"] = $maxTitleLen; + $mappings["maxDescLen"] = $maxDescLen; + $mappings["maxAltLen"] = $maxAltLen; + $mappings['twitter_title'] = $mappings['og_title'] ?? ''; + $mappings['twitter_description'] = $mappings['og_description'] ?? ''; + + $document = $app->getDocument(); + + $document->addScriptOptions('plgOgMappings', $mappings); + + Text::script('PLG_SYSTEM_OPENGRAPH_INHERITED'); + + /** @var WebAssetManager $wa */ + $wa = $document->getWebAssetManager(); + + $wa->registerAndUseScript( + 'plg.opengraph.placeholder', + 'media/plg_system_opengraph/js/opengraph-placeholder.js', + [ + 'type' => 'module', + 'version' => 'auto', + 'dependencies' => ['core'], + ] + ); } @@ -190,7 +272,7 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void ['ignore_request' => true] ); $categoryModel->setState('category.id', $article->catid); - $category = $categoryModel->getCategory(); + $category = $categoryModel->getItem($article->catid); @@ -412,7 +494,6 @@ private function getAllArticleImages(Registry $articleImages): array private function getOgTagsFromParams(Registry $params, array &$ogTags): void { - foreach (array_keys($ogTags) as $ogTagName) { if ($params->exists($ogTagName)) { $value = $params->get($ogTagName); @@ -613,7 +694,6 @@ private function adjustFieldsGroup(string $filePath, string $newGroup): string return $xml->asXML(); } - /** * Get the parameters associated with the active menu item * diff --git a/plugins/system/opengraph/src/forms/opengraph.xml b/plugins/system/opengraph/src/forms/opengraph.xml index 74f0cd269c6..bfb16829044 100644 --- a/plugins/system/opengraph/src/forms/opengraph.xml +++ b/plugins/system/opengraph/src/forms/opengraph.xml @@ -31,9 +31,8 @@ type="list" label="PLG_SYSTEM_OPENGRAPH_OG_TYPE_LABEL" description="PLG_SYSTEM_OPENGRAPH_OG_TYPE_DESC" - default="" + default="article" class="form-select"> - From 30689d6febb8d8ffe0e635e5a1ca04d159dfa4ea Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Tue, 5 Aug 2025 13:41:34 +0530 Subject: [PATCH 27/47] feat : added script for OpenGraph placeholder in build folder --- .gitignore | 2 - .../plg_system_opengraph/joomla.asset.json | 18 ++++ .../js/opengraph-placeholder.es6.js | 94 +++++++++++++++++++ .../Opengraph/OpengraphServiceInterface.php | 54 +++++++++++ plugins/system/opengraph/opengraph.xml | 7 +- .../opengraph/src/Extension/opengraph.php | 11 +-- .../opengraph/src/Field/OpengraphField.php | 36 ++++++- 7 files changed, 205 insertions(+), 17 deletions(-) create mode 100644 build/media_source/plg_system_opengraph/joomla.asset.json create mode 100644 build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js diff --git a/.gitignore b/.gitignore index 78192e8a2cb..b936fdbaa9b 100644 --- a/.gitignore +++ b/.gitignore @@ -107,5 +107,3 @@ cypress.config.mjs # WebAuthn FIDO metadata cache /plugins/system/webauthn/fido.jwt -administrator/language/en-GB/en-GB.plg_system_cccsocialmedia.ini -administrator/language/en-GB/en-GB.plg_system_cccsocialmedia.sys.ini diff --git a/build/media_source/plg_system_opengraph/joomla.asset.json b/build/media_source/plg_system_opengraph/joomla.asset.json new file mode 100644 index 00000000000..ba01c79bd2a --- /dev/null +++ b/build/media_source/plg_system_opengraph/joomla.asset.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json", + "name": "plg_system_opengraph", + "version": " __DEPLOY_VERSION__", + "description": "Joomla CMS", + "license": "GPL-2.0-or-later", + "assets": [ + { + "name": "plg_system_opengraph.opengraph-placeholder", + "type": "script", + "uri": "plg_system_opengraph/opengraph-placeholder.min.js", + "dependencies": ["core"], + "attributes": { + "type": "module" + } + } + ] +} diff --git a/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js b/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js new file mode 100644 index 00000000000..b54076629a9 --- /dev/null +++ b/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js @@ -0,0 +1,94 @@ +document.addEventListener("DOMContentLoaded", () => { + const maps = Joomla.getOptions("plgOgMappings", {}); + + const selectorFor = (token) => { + if (token.startsWith("field.")) { + const n = CSS.escape(token.slice(6)); + return `[name="jform[com_fields][${n}]"]`; + } + if (token.startsWith("image_")) { + return `[name="jform[images][${CSS.escape(token)}]"]`; + } + return `[name="jform[${CSS.escape(token)}]"]`; + }; + + function getSourceName(token) { + if (token.startsWith("field.")) { + const fieldKey = token.slice(6).replace(/_/g, " "); + return `Custom Field: ${fieldKey}`; + } + + const mapCoreNames = { + title: "Title", + alias: "Alias", + metadesc: "Meta Description", + metakey: "Meta Keywords", + articletext: "Article Text", + image_intro: "Intro Image", + image_intro_alt: "Intro Image Alt", + image_fulltext: "Fulltext Image", + image_fulltext_alt: "Fulltext Image Alt", + created_by_alias: "Author Alias", + }; + + return mapCoreNames[token] || token; + } + + function sanitizeText(input, maxLen = 60) { + if (typeof input !== "string" || !input.trim()) return ""; + + // Remove HTML tags manually (just in case innerText fails) + const noTags = input.replace(/<[^>]*>/g, " "); + + // Decode HTML entities using a temporary div + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = noTags; + const decoded = tempDiv.textContent || tempDiv.innerText || ""; + + // Normalize whitespace + const cleaned = decoded.replace(/\s+/g, " ").trim(); + + // Truncate with word boundary safety + if (cleaned.length <= maxLen) return cleaned; + + const cut = cleaned.lastIndexOf(" ", maxLen - 1); + const safeCut = cut > maxLen * 0.6 ? cut : maxLen - 1; + return cleaned.slice(0, safeCut).replace(/[.,;:\-\s]+$/, "") + "…"; + } + + const maxLen = { + og_title: Number(maps.maxTitleLen) || 60, + og_description: Number(maps.maxDescLen) || 160, + og_image_alt: Number(maps.maxAltLen) || 125, + }; + + Object.entries(maps).forEach(([ogKey, token]) => { + const ogInput = document.getElementById(`jform_attribs_${ogKey}`); + const srcInput = document.querySelector(selectorFor(token)); + + if (!ogInput || !srcInput) return; + + const originalPh = ogInput.placeholder; + const inherited = Joomla.Text._("PLG_SYSTEM_OPENGRAPH_INHERITED"); + const paint = () => { + if (ogInput.value.trim()) return; // user override + let v = srcInput.value.trim(); + + if (ogKey !== "og_image") { + v = sanitizeText(v, maxLen[ogKey]); + } + const sourceLabel = getSourceName(token); + ogInput.placeholder = v + ? `${v} — ${inherited} from ${sourceLabel}` + : originalPh; + }; + + paint(); // initial + srcInput.addEventListener("input", paint); + + ogInput.addEventListener("input", () => { + ogInput.placeholder = originalPh; // detach + srcInput.removeEventListener("input", paint); + }); + }); +}); diff --git a/libraries/src/Opengraph/OpengraphServiceInterface.php b/libraries/src/Opengraph/OpengraphServiceInterface.php index ab2e6e284c4..e5dccb0e251 100644 --- a/libraries/src/Opengraph/OpengraphServiceInterface.php +++ b/libraries/src/Opengraph/OpengraphServiceInterface.php @@ -13,6 +13,44 @@ \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects + + +/** + * Enumerates the logical OpenGraph groups that a custom field-type can + * register itself under. + * + * Third-party field-type plugins should return one of these cases from + * {@see MappableFieldInterface::getOpengraphGroup()} so the System – OpenGraph + * plugin knows how to categorise the field inside its mapping drop-down. + * + * @since __DEPLOY_VERSION__ + */ +enum OpengraphGroup: string +{ +/** Standard textual content (e.g. single-line, multi-line). */ + case TEXT = 'text-fields'; + +/** Image or media file (intro/full images, custom media fields, …). */ + case IMAGE = 'image-fields'; + +/** Alternate-text associated with an image (accessibility / SEO). */ + case IMAGE_ALT = 'image-alt-fields'; + + // TODO : implement these cases in the future, if needed + + // /** Meta-information such as description or keywords. */ + // case META = 'meta-fields'; + + // /** Locale or language codes (e.g. `en-US`). */ + // case LOCALE = 'locale-fields'; + + // /** Author-related data (creator, modifier, alias, …). */ + // case AUTHOR = 'author-fields'; + + // /** Date-time values (created, modified, publish-up/down, …). */ + // case DATE = 'date-fields'; +} + /** * The Opengraph service. * @@ -26,6 +64,22 @@ interface OpengraphServiceInterface * @return array * * @since __DEPLOY_VERSION__ + * */ public function getOpengraphFields(): array; } + + + + +interface MappableFieldInterface +{ + /** + * Returns the OpenGraph group this field should be listed under. + * + * @return OpengraphGroup One of the enum cases defined in {@see OpengraphGroup}. + * + * @since __DEPLOY_VERSION__ + */ + public function getOpengraphGroup(): OpengraphGroup; +} diff --git a/plugins/system/opengraph/opengraph.xml b/plugins/system/opengraph/opengraph.xml index 1eec4bf21d5..206522bfdb8 100644 --- a/plugins/system/opengraph/opengraph.xml +++ b/plugins/system/opengraph/opengraph.xml @@ -13,6 +13,11 @@ __DEPLOY_VERSION__ PLG_SYSTEM_OPENGRAPH_DESCRIPTION Joomla\Plugin\System\Opengraph + + js + joomla.asset.json + src services @@ -82,4 +87,4 @@
- + \ No newline at end of file diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index a1cb0a7719e..80b16da8915 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -191,15 +191,8 @@ public function onContentPrepareForm(PrepareFormEvent $event): void /** @var WebAssetManager $wa */ $wa = $document->getWebAssetManager(); - $wa->registerAndUseScript( - 'plg.opengraph.placeholder', - 'media/plg_system_opengraph/js/opengraph-placeholder.js', - [ - 'type' => 'module', - 'version' => 'auto', - 'dependencies' => ['core'], - ] - ); + $wa->getRegistry()->addExtensionRegistryFile('plg_system_opengraph'); + $wa->useScript('plg_system_opengraph.opengraph-placeholder'); } diff --git a/plugins/system/opengraph/src/Field/OpengraphField.php b/plugins/system/opengraph/src/Field/OpengraphField.php index 230bf8aeebc..1e26ee34cd8 100644 --- a/plugins/system/opengraph/src/Field/OpengraphField.php +++ b/plugins/system/opengraph/src/Field/OpengraphField.php @@ -14,7 +14,10 @@ use Joomla\CMS\Form\Field\GroupedlistField; use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Language\Text; +use Joomla\CMS\Opengraph\MappableFieldInterface; +use Joomla\CMS\Opengraph\OpengraphGroup; use Joomla\CMS\Opengraph\OpengraphServiceInterface; +use Joomla\CMS\Plugin\PluginHelper; use Joomla\Component\Fields\Administrator\Helper\FieldsHelper; // phpcs:disable PSR1.Files.SideEffects @@ -83,12 +86,12 @@ protected function getGroups() // Allowed field types for each OpenGraph group $allowedFieldTypes = [ - 'text-fields' => ['text', 'textarea'], - 'image-fields' => ['media', 'imagelist'], - 'image-alt-fields' => ['text'], + OpengraphGroup::TEXT->value => ['text', 'textarea'], + OpengraphGroup::IMAGE->value => ['media', 'imagelist'], + OpengraphGroup::IMAGE_ALT->value => ['text'], ]; - $allowedTypes = $allowedFieldTypes[$fieldType] ?? []; + $nativeTypes = $allowedFieldTypes[$fieldType] ?? []; @@ -105,10 +108,33 @@ protected function getGroups() $customOptions = []; foreach ($customFields as $field) { - if (!\in_array($field->type, $allowedTypes, true)) { + $accept = \in_array($field->type, $nativeTypes, true); + + + // If not native-allowed, see if the field’s plugin implements our interface + if (!$accept) { + // Class name convention: PlgFields{Type} + $class = 'PlgFields' . ucfirst($field->type); + + // Ensure plugin autoloaded + PluginHelper::importPlugin('fields'); + + if ( + \class_exists($class) + && \is_subclass_of($class, MappableFieldInterface::class) + && $class::getOpengraphGroup()->value === $fieldType + ) { + $accept = true; + } + } + + + if (!$accept) { continue; } + + $label = $field->title . ' (' . $field->name . ')'; $customOptions[] = HTMLHelper::_('select.option', 'field.' . $field->name, $label); } From 8f94056e4389560fc729690f4901eb77520b9434 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Tue, 5 Aug 2025 13:54:46 +0530 Subject: [PATCH 28/47] removed the custom field logic from this branch --- .../Opengraph/OpengraphServiceInterface.php | 52 -------------- .../opengraph/src/Extension/opengraph.php | 16 ----- .../opengraph/src/Field/OpengraphField.php | 68 ------------------- 3 files changed, 136 deletions(-) diff --git a/libraries/src/Opengraph/OpengraphServiceInterface.php b/libraries/src/Opengraph/OpengraphServiceInterface.php index e5dccb0e251..d8cc2eca98e 100644 --- a/libraries/src/Opengraph/OpengraphServiceInterface.php +++ b/libraries/src/Opengraph/OpengraphServiceInterface.php @@ -14,43 +14,6 @@ // phpcs:enable PSR1.Files.SideEffects - -/** - * Enumerates the logical OpenGraph groups that a custom field-type can - * register itself under. - * - * Third-party field-type plugins should return one of these cases from - * {@see MappableFieldInterface::getOpengraphGroup()} so the System – OpenGraph - * plugin knows how to categorise the field inside its mapping drop-down. - * - * @since __DEPLOY_VERSION__ - */ -enum OpengraphGroup: string -{ -/** Standard textual content (e.g. single-line, multi-line). */ - case TEXT = 'text-fields'; - -/** Image or media file (intro/full images, custom media fields, …). */ - case IMAGE = 'image-fields'; - -/** Alternate-text associated with an image (accessibility / SEO). */ - case IMAGE_ALT = 'image-alt-fields'; - - // TODO : implement these cases in the future, if needed - - // /** Meta-information such as description or keywords. */ - // case META = 'meta-fields'; - - // /** Locale or language codes (e.g. `en-US`). */ - // case LOCALE = 'locale-fields'; - - // /** Author-related data (creator, modifier, alias, …). */ - // case AUTHOR = 'author-fields'; - - // /** Date-time values (created, modified, publish-up/down, …). */ - // case DATE = 'date-fields'; -} - /** * The Opengraph service. * @@ -68,18 +31,3 @@ interface OpengraphServiceInterface */ public function getOpengraphFields(): array; } - - - - -interface MappableFieldInterface -{ - /** - * Returns the OpenGraph group this field should be listed under. - * - * @return OpengraphGroup One of the enum cases defined in {@see OpengraphGroup}. - * - * @since __DEPLOY_VERSION__ - */ - public function getOpengraphGroup(): OpengraphGroup; -} diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 80b16da8915..c0f46f04173 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -364,22 +364,6 @@ private function getOgTagsFromCategoryMappings(Registry $categoryParams, object */ private function getFieldValue(object $article, string $fieldName, array $articleImages): string { - // Check if it's a custom field - if (strpos($fieldName, 'field.') === 0) { - $customFieldName = substr($fieldName, 6); - // Load custom fields for the article - $customFields = FieldsHelper::getFields('com_content.article', $article, true); - - foreach ($customFields as $field) { - if ($field->name == $customFieldName) { - return $field->value ?? ''; - } - } - - return ''; - } - - // Handle standard article fields $value = ''; switch ($fieldName) { diff --git a/plugins/system/opengraph/src/Field/OpengraphField.php b/plugins/system/opengraph/src/Field/OpengraphField.php index 1e26ee34cd8..fd60a7beec4 100644 --- a/plugins/system/opengraph/src/Field/OpengraphField.php +++ b/plugins/system/opengraph/src/Field/OpengraphField.php @@ -10,15 +10,10 @@ namespace Joomla\Plugin\System\Opengraph\Field; use Joomla\CMS\Factory; -use Joomla\CMS\Fields\FieldsServiceInterface; use Joomla\CMS\Form\Field\GroupedlistField; use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\Language\Text; -use Joomla\CMS\Opengraph\MappableFieldInterface; -use Joomla\CMS\Opengraph\OpengraphGroup; use Joomla\CMS\Opengraph\OpengraphServiceInterface; -use Joomla\CMS\Plugin\PluginHelper; -use Joomla\Component\Fields\Administrator\Helper\FieldsHelper; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -80,69 +75,6 @@ protected function getGroups() } - if (!$component instanceof FieldsServiceInterface) { - return $groups; - } - - // Allowed field types for each OpenGraph group - $allowedFieldTypes = [ - OpengraphGroup::TEXT->value => ['text', 'textarea'], - OpengraphGroup::IMAGE->value => ['media', 'imagelist'], - OpengraphGroup::IMAGE_ALT->value => ['text'], - ]; - - $nativeTypes = $allowedFieldTypes[$fieldType] ?? []; - - - - $catId = (int) $this->form->getValue('id'); // editing existing cat - if (!$catId) { - // Creating a new category: use the chosen parent so assignments still work - $catId = (int) $this->form->getValue('parent_id'); - } - - // Dummy item with catid so FieldsService filters by assignment - $scopeItem = $catId ? (object) ['catid' => $catId] : null; - - $customFields = FieldsHelper::getFields('com_content.article', $scopeItem); - $customOptions = []; - - foreach ($customFields as $field) { - $accept = \in_array($field->type, $nativeTypes, true); - - - // If not native-allowed, see if the field’s plugin implements our interface - if (!$accept) { - // Class name convention: PlgFields{Type} - $class = 'PlgFields' . ucfirst($field->type); - - // Ensure plugin autoloaded - PluginHelper::importPlugin('fields'); - - if ( - \class_exists($class) - && \is_subclass_of($class, MappableFieldInterface::class) - && $class::getOpengraphGroup()->value === $fieldType - ) { - $accept = true; - } - } - - - if (!$accept) { - continue; - } - - - - $label = $field->title . ' (' . $field->name . ')'; - $customOptions[] = HTMLHelper::_('select.option', 'field.' . $field->name, $label); - } - - if (!empty($customOptions)) { - $groups['Custom Fields'] = $customOptions; - } - return $groups; } } From c215b7a97cad5b5aafae3c857fd0cf38ed2c3133 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Thu, 7 Aug 2025 20:18:54 +0530 Subject: [PATCH 29/47] fix : fix some bugs and modified the plugins settings page --- plugins/system/opengraph/opengraph.xml | 28 +++---------------- .../opengraph/src/Extension/opengraph.php | 10 +++---- 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/plugins/system/opengraph/opengraph.xml b/plugins/system/opengraph/opengraph.xml index 206522bfdb8..926ab251705 100644 --- a/plugins/system/opengraph/opengraph.xml +++ b/plugins/system/opengraph/opengraph.xml @@ -29,17 +29,15 @@ -
+
- - + +
-
- - - -
\ No newline at end of file diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index c0f46f04173..896aa52cdff 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -143,7 +143,7 @@ public function onContentPrepareForm(PrepareFormEvent $event): void $categoryId = (int) $article->catid; } } - + $catParams = new Registry(); if ($categoryId > 0) { /** @var MVCComponent $catComponent */ $catComponent = $app->bootComponent('com_categories'); @@ -158,7 +158,9 @@ public function onContentPrepareForm(PrepareFormEvent $event): void $catParams = new Registry($category->params ?? '{}'); } - + if (!$catParams) { + return; + } // Get the mappings from the category params $mappings = []; foreach ($catParams as $paramKey => $fieldName) { @@ -265,9 +267,7 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void ['ignore_request' => true] ); $categoryModel->setState('category.id', $article->catid); - $category = $categoryModel->getItem($article->catid); - - + $category = $categoryModel->getCategory(); // Get menu parameters From 53b3576b91b76c8e43fc05c20d02a089fa18d1ab Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Thu, 7 Aug 2025 20:26:36 +0530 Subject: [PATCH 30/47] chore : changed opengraph.xml to use JYES/JNO instead of JENABLED/JDISABLED --- plugins/system/opengraph/opengraph.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/system/opengraph/opengraph.xml b/plugins/system/opengraph/opengraph.xml index 926ab251705..6c6f66685cc 100644 --- a/plugins/system/opengraph/opengraph.xml +++ b/plugins/system/opengraph/opengraph.xml @@ -36,8 +36,8 @@ default="1" label="PLG_SYSTEM_OPENGRAPH_ENABLE_OG_GENERATION" description="PLG_SYSTEM_OPENGRAPH_ENABLE_OG_GENERATION_DESC"> - - + + Date: Mon, 11 Aug 2025 19:06:47 +0530 Subject: [PATCH 31/47] feat : added global default option in the settings and refactored OpenGraph code to use them --- .../language/en-GB/plg_system_opengraph.ini | 18 +++++++++ plugins/system/opengraph/opengraph.xml | 39 +++++++++++++++++-- .../opengraph/src/Extension/opengraph.php | 37 ++++++++++++++++++ .../system/opengraph/src/forms/opengraph.xml | 9 +++-- 4 files changed, 95 insertions(+), 8 deletions(-) diff --git a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini index bc7bd79464f..f1b729b37b3 100644 --- a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini +++ b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini @@ -86,3 +86,21 @@ PLG_SYSTEM_OPENGRAPH_TYPE_VIDEO="Video" PLG_SYSTEM_OPENGRAPH_TYPE_WEBSITE="Website" PLG_SYSTEM_OPENGRAPH_USE_DEFAULT="Use Default" PLG_SYSTEM_OPENGRAPH_INHERITED="inherited" + +PLG_SYSTEM_OPENGRAPH_FIELDSET_GLOBAL_DEFAULTS="Global Defaults" +PLG_SYSTEM_OPENGRAPH_FIELDSET_GLOBAL_DEFAULTS_DESC="Set site-wide OpenGraph fallback values. Menu and item-level settings will override these." + +PLG_SYSTEM_OPENGRAPH_DEFAULT_TITLE_LABEL="Default OG Title" +PLG_SYSTEM_OPENGRAPH_DEFAULT_TITLE_DESC="Used when no title is mapped or provided by Menu/Category/Item." + +PLG_SYSTEM_OPENGRAPH_DEFAULT_DESC_LABEL="Default OG Description" +PLG_SYSTEM_OPENGRAPH_DEFAULT_DESC_DESC="Fallback description for OpenGraph tags." + +PLG_SYSTEM_OPENGRAPH_DEFAULT_IMAGE_LABEL="Default OG Image" +PLG_SYSTEM_OPENGRAPH_DEFAULT_IMAGE_DESC="Fallback image for OpenGraph (recommended 1200×630)." + +PLG_SYSTEM_OPENGRAPH_DEFAULT_IMAGE_ALT_LABEL="Default OG Image Alt" +PLG_SYSTEM_OPENGRAPH_DEFAULT_IMAGE_ALT_DESC="Alt text for the default OpenGraph image." + +PLG_SYSTEM_OPENGRAPH_DEFAULT_SITENAME_LABEL="Default Site Name" +PLG_SYSTEM_OPENGRAPH_DEFAULT_SITENAME_DESC="Fallback for og:site_name if you choose to include it." diff --git a/plugins/system/opengraph/opengraph.xml b/plugins/system/opengraph/opengraph.xml index 6c6f66685cc..0082c0c4d88 100644 --- a/plugins/system/opengraph/opengraph.xml +++ b/plugins/system/opengraph/opengraph.xml @@ -39,10 +39,6 @@ -
+
+ + + + + + +
\ No newline at end of file diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 896aa52cdff..655d9fad455 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -314,6 +314,9 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void // get OG tags from menu form $this->getOgTagsFromParams($menuParams, $ogTags); + // get Default OG tags + $this->getDefaultOgTags($ogTags); + // get Twitter tags $this->getTwitterOgTags($ogTags); @@ -483,6 +486,38 @@ private function getOgTagsFromParams(Registry $params, array &$ogTags): void } } + + + /** + * Get Global Default OG tags if not till not set + * @param array &$ogTags + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + private function getDefaultOgTags(array &$ogTags): void + { + // Get Global Default OG tags if not set + $defaultOgTags = [ + 'og_title' => $this->params->get('default_og_title'), + 'og_description' => $this->params->get('default_og_description'), + 'og_image' => $this->params->get('default_og_image'), + 'og_image_alt' => $this->params->get('default_og_image_alt'), + 'site_name' => $this->params->get('default_og_site_name'), + 'fb_app_id' => $this->params->get('fb_app_id'), + ]; + + foreach ($defaultOgTags as $key => $value) { + if ($ogTags[$key] === '') { + $ogTags[$key] = $value; + } + } + } + + + + /** * Get Twitter tags if not set use OG value * @param array &$ogTags @@ -533,6 +568,8 @@ private function injectOpenGraphData(Document $document, array $ogTags): void // Facebook App ID $this->setMetaData($document, 'fb:app_id', $ogTags['fb_app_id'], 'property'); + $this->setMetaData($document, 'og:site_name', $ogTags['site_name'], 'property'); + $this->setOpenGraphImage($document, $ogTags); } diff --git a/plugins/system/opengraph/src/forms/opengraph.xml b/plugins/system/opengraph/src/forms/opengraph.xml index bfb16829044..58996266907 100644 --- a/plugins/system/opengraph/src/forms/opengraph.xml +++ b/plugins/system/opengraph/src/forms/opengraph.xml @@ -52,19 +52,20 @@ type="radio" label="PLG_SYSTEM_OPENGRAPH_ENABLE_LABEL" description="PLG_SYSTEM_OPENGRAPH_ENABLE_DESC" - default="1" - layout="joomla.form.field.radio.switcher"> + layout="joomla.form.field.radio.switcher" + default="1">
- +
- + \ No newline at end of file From ff3a89112c79135c9c750b5d4973f59ad87de3e8 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Fri, 15 Aug 2025 09:18:22 +0530 Subject: [PATCH 32/47] feat : made the OpenGraph field more flexible , removed hardcoded component name --- cli/joomla.php | 8 +++++ .../en-GB/plg_system_opengraph.sys.ini | 3 -- .../opengraph/src/Field/OpengraphField.php | 30 ++++++++++++++++--- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/cli/joomla.php b/cli/joomla.php index c13b949d5d0..dc3f1f9b9c7 100755 --- a/cli/joomla.php +++ b/cli/joomla.php @@ -10,14 +10,17 @@ // We are a valid entry point. const _JEXEC = 1; + // Define the application's minimum supported PHP version as a constant so it can be referenced within the application. const JOOMLA_MINIMUM_PHP = '8.1.0'; + if (version_compare(PHP_VERSION, JOOMLA_MINIMUM_PHP, '<')) { echo 'Sorry, your PHP version is not supported.' . PHP_EOL; echo 'Your command line php needs to be version ' . JOOMLA_MINIMUM_PHP . ' or newer to run the Joomla! CLI Tools' . PHP_EOL; echo 'The version of PHP currently running this code, at the command line, is PHP version ' . PHP_VERSION . '.' . PHP_EOL; echo 'Please note, the version of PHP running your commands here, may be different to the version that is used by '; echo 'your web server to run the Joomla! Web Application' . PHP_EOL; + exit; } @@ -36,6 +39,7 @@ echo 'It looks like you are trying to run Joomla! from our git repository.' . PHP_EOL; echo 'To do so requires you complete a couple of extra steps first.' . PHP_EOL; echo 'Please see https://docs.joomla.org/Special:MyLanguage/J5.x:Setting_Up_Your_Local_Environment for further details.' . PHP_EOL; + exit; } @@ -45,13 +49,16 @@ || (filesize(JPATH_CONFIGURATION . '/configuration.php') < 10) ) { echo 'Install Joomla to run cli commands' . PHP_EOL; + exit; } // Get the framework. require_once JPATH_BASE . '/includes/framework.php'; + // Boot the DI container $container = \Joomla\CMS\Factory::getContainer(); + /* * Alias the session service keys to the CLI session service as that is the primary session backend for this application * @@ -64,6 +71,7 @@ ->alias(\Joomla\CMS\Session\Session::class, 'session.cli') ->alias(\Joomla\Session\Session::class, 'session.cli') ->alias(\Joomla\Session\SessionInterface::class, 'session.cli'); + $app = \Joomla\CMS\Factory::getContainer()->get(\Joomla\Console\Application::class); \Joomla\CMS\Factory::$application = $app; $app->execute(); diff --git a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.sys.ini b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.sys.ini index df8c49a3e5d..ec37663b11d 100644 --- a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.sys.ini +++ b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.sys.ini @@ -1,16 +1,13 @@ ; Open Graph Plugin System Language File ; Copyright (C) 2025 Open Source Matters, Inc. ; License GNU General Public License version 2 or later - ; Plugin Name and Description for Extension Manager PLG_SYSTEM_OPENGRAPH="System - Open Graph" PLG_SYSTEM_OPENGRAPH_DESCRIPTION="Automatic Open Graph tag generation plugin for Joomla 6.x with hierarchical metadata override system. Improves social media sharing with intelligent fallback mechanisms." - ; Installation Messages PLG_SYSTEM_OPENGRAPH_INSTALL_SUCCESS="Open Graph Plugin installed successfully!" PLG_SYSTEM_OPENGRAPH_UPDATE_SUCCESS="Open Graph Plugin updated successfully!" PLG_SYSTEM_OPENGRAPH_UNINSTALL_SUCCESS="Open Graph Plugin uninstalled successfully!" - ; Custom Fields Group PLG_SYSTEM_OPENGRAPH_FIELDS_GROUP="Open Graph Metadata" PLG_SYSTEM_OPENGRAPH_FIELDS_GROUP_DESC="Custom fields for Open Graph metadata configuration" diff --git a/plugins/system/opengraph/src/Field/OpengraphField.php b/plugins/system/opengraph/src/Field/OpengraphField.php index fd60a7beec4..d6fa43a77e6 100644 --- a/plugins/system/opengraph/src/Field/OpengraphField.php +++ b/plugins/system/opengraph/src/Field/OpengraphField.php @@ -54,14 +54,36 @@ protected function getGroups() ]; - $ogOptions = []; - $component = $app->bootComponent('com_content'); + $component = ''; + if ($this->form) { + $component = (string) ($this->form->getValue('extension') + ?: $this->form->getData()->get('extension')); + } + if (!$component) { + $context = (string) ($this->form ? $this->form->getName() : ''); + $component = $context ? explode('.', $context, 2)[0] ?? '' : ''; + if (!$component) { + $component = (string) $app->input->getCmd('option', ''); + } + } + if (!$component) { + return $groups; + } - if (!$component instanceof OpengraphServiceInterface) { + try { + $cmp = $app->bootComponent($component); + } catch (\Throwable $e) { return $groups; } - $fields = $component->getOpengraphFields(); + if (!$cmp instanceof OpengraphServiceInterface) { + return $groups; + } + + + $ogOptions = []; + + $fields = $cmp->getOpengraphFields(); $fieldType = $this->getAttribute('field-type'); if (isset($fields[$fieldType])) { From 3e479610008fc98da900980cc71af0088cb713c9 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Mon, 1 Sep 2025 19:22:32 +0530 Subject: [PATCH 33/47] feat : added support for multi article page view --- .../opengraph/src/Extension/opengraph.php | 303 +++++++++++++----- 1 file changed, 218 insertions(+), 85 deletions(-) diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 655d9fad455..7a424985f62 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -16,7 +16,9 @@ use Joomla\CMS\Document\HtmlDocument; use Joomla\CMS\Event\Application\BeforeCompileHeadEvent; use Joomla\CMS\Event\Model\PrepareFormEvent; -use Joomla\CMS\Menu\MenuItem; +use Joomla\CMS\Filter\OutputFilter; +use Joomla\CMS\HTML\HTMLHelper; +use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\Opengraph\OpengraphServiceInterface; use Joomla\CMS\Plugin\CMSPlugin; @@ -26,9 +28,7 @@ use Joomla\Component\Fields\Administrator\Helper\FieldsHelper; use Joomla\Event\SubscriberInterface; use Joomla\Registry\Registry; -use Joomla\CMS\Filter\OutputFilter; -use Joomla\CMS\HTML\HTMLHelper; -use Joomla\CMS\Language\Text; + // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; // phpcs:enable PSR1.Files.SideEffects @@ -125,8 +125,8 @@ public function onContentPrepareForm(PrepareFormEvent $event): void } // Get the article id and category id - $input = $app->input; - $articleId = (int) ($input->getInt('id') ?: $form->getValue('id')); + $input = $app->input; + $articleId = (int) ($input->getInt('id') ?: $form->getValue('id')); $categoryId = 0; if ($articleId > 0) { @@ -165,7 +165,7 @@ public function onContentPrepareForm(PrepareFormEvent $event): void $mappings = []; foreach ($catParams as $paramKey => $fieldName) { if (str_starts_with($paramKey, 'og_') && str_ends_with($paramKey, '_field')) { - $ogTag = substr($paramKey, 0, -6); // strip "_field" + $ogTag = substr($paramKey, 0, -6); // strip "_field" $mappings[$ogTag] = $fieldName; } } @@ -175,12 +175,12 @@ public function onContentPrepareForm(PrepareFormEvent $event): void return; // category has no mappings } - $maxTitleLen = $this->params->get('max_title_length', 60); - $maxDescLen = $this->params->get('max_description_length', 160); - $maxAltLen = $this->params->get('max_alt_length', 125); - $mappings["maxTitleLen"] = $maxTitleLen; - $mappings["maxDescLen"] = $maxDescLen; - $mappings["maxAltLen"] = $maxAltLen; + $maxTitleLen = $this->params->get('max_title_length', 60); + $maxDescLen = $this->params->get('max_description_length', 160); + $maxAltLen = $this->params->get('max_alt_length', 125); + $mappings["maxTitleLen"] = $maxTitleLen; + $mappings["maxDescLen"] = $maxDescLen; + $mappings["maxAltLen"] = $maxAltLen; $mappings['twitter_title'] = $mappings['og_title'] ?? ''; $mappings['twitter_description'] = $mappings['og_description'] ?? ''; @@ -225,27 +225,55 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void } $input = $app->input; - if ( - $input->getCmd('option') !== 'com_content' - || $input->getCmd('view') !== 'article' - || ! $id = $input->getInt('id') - ) { + $view = $input->getCmd('view'); + $id = $input->getInt('id'); + $option = $input->getCmd('option'); + + if ($option !== 'com_content') { return; } - // Plugin globally disabled? + + // Plugin disabled? if (!$this->params->get('enable_og_generation', 1)) { return; } + $ogTags = $this->initializeOgTags(); + + if ($view === 'article' && $id > 0) { + $this->handleSingleArticle($document, $ogTags, $id, $option, $view); + return; + } + $this->handleMultipleArticleView($document, $ogTags, $option, $view, $id); + } + + + + + /** + * Handle Single Article + * Priority: Category Mappings → Article Form → Single Article Menu (if available) + * + * @param HtmlDocument $document + * @param array $ogTags + * @param int $id + * @param string $option + * @param string $view + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + private function handleSingleArticle(HtmlDocument $document, array $ogTags, int $id, string $option, string $view): void + { /** @var MVCComponent $component */ - $component = $app->bootComponent('com_content'); + $component = $this->app->bootComponent('com_content'); /** @var MVCFactoryInterface $mvcFactory */ $mvcFactory = $component->getMVCFactory(); $params = ComponentHelper::getParams('com_content'); - // Fallback if for some reason it isn’t an object - if (! $params instanceof Registry) { + if (!$params instanceof Registry) { $params = new Registry(); } @@ -256,77 +284,198 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void $articleModel->setState('article.id', $id); $article = $articleModel->getItem($id); - if (! $article) { + if (!$article) { return; } /** @var CategoryModel $categoryModel */ - $categoryModel = $mvcFactory->createModel( - 'Category', - 'Site', - ['ignore_request' => true] - ); + $categoryModel = $mvcFactory->createModel('Category', 'Site', ['ignore_request' => true]); $categoryModel->setState('category.id', $article->catid); $category = $categoryModel->getCategory(); - - // Get menu parameters - $menuParams = $this->getMenuParams(); $articleAttribs = new Registry($article->attribs ?? '{}'); $categoryParams = new Registry($category->params ?? '{}'); $articleImages = $this->getAllArticleImages(new Registry($article->images ?? '{}')); + // Set article-specific properties + $ogTags['og_type'] = 'article'; + // Step 1: Get OG tags from category mappings (Priority 1) + $this->getOgTagsFromCategoryMappings($categoryParams, $article, $articleImages, $ogTags); - $ogTags = [ - 'og_title' => '', - 'og_description' => '', - 'og_image' => '', - 'og_image_alt' => '', - 'og_type' => '', - 'og_url' => '', - 'twitter_card' => '', - 'twitter_title' => '', - 'twitter_description' => '', - 'twitter_image' => '', - 'twitter_image_alt' => '', - 'fb_app_id' => '', - 'site_name' => '', - 'url' => '', - 'base_url' => '', - ]; + // Step 2: Get OG tags from article form (Priority 2) + $this->getOgTagsFromParams($articleAttribs, $ogTags); - // Get Global settings + // Step 3: Check if there's a single article menu item and override (Priority 3 - Highest) + $singleArticleMenuParams = $this->getSingleArticleMenuParams($option, $view, $id); + $this->getOgTagsFromParams($singleArticleMenuParams, $ogTags); - $config = $this->app->getConfig(); + // Get default OG tags for any missing values + $this->getDefaultOgTags($ogTags); - $ogTags['fb_app_id'] = $this->params->get('fb_app_id'); - $ogTags['site_name'] = $config->get('sitename'); - $ogTags['base_url'] = Uri::base(); - $ogTags['url'] = Uri::getInstance()->toString(); + // Get Twitter tags + $this->getTwitterOgTags($ogTags); - // get OG tags from category mappings - $this->getOgTagsFromCategoryMappings($categoryParams, $article, $articleImages, $ogTags); + // Sanitize OG tags + $this->sanitizeOgTags($ogTags); - // get OG tags from article form - $this->getOgTagsFromParams($articleAttribs, $ogTags); + // Inject the OpenGraph data into the document + $this->injectOpenGraphData($document, $ogTags); + } - // get OG tags from menu form - $this->getOgTagsFromParams($menuParams, $ogTags); + /** + * Handle Multiple Article Views (category, blog) + * Only check menu form + * + * @param HtmlDocument $document + * @param array $ogTags + * @param string $option + * @param string $view + * @param int|null $categoryId + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + private function handleMultipleArticleView(HtmlDocument $document, array $ogTags, string $option, string $view, int|null $categoryId): void + { - // get Default OG tags - $this->getDefaultOgTags($ogTags); - // get Twitter tags + $menuParams = $this->getMultipleArticleMenuParams($option, $view, $categoryId); + + + if (!$menuParams->count()) { + return; + } + + // Apply menu parameters only + $this->getOgTagsFromParams($menuParams, $ogTags); + + // Get Twitter tags $this->getTwitterOgTags($ogTags); - // sanitize OG tags + // Sanitize OG tags $this->sanitizeOgTags($ogTags); // Inject the OpenGraph data into the document $this->injectOpenGraphData($document, $ogTags); } + /** + * Get menu parameters only for single article menu items + * + * @param string $option + * @param string $view + * @param int $id + * + * @return Registry + * + * @since __DEPLOY_VERSION__ + */ + private function getSingleArticleMenuParams(string $option, string $view, int $id): Registry + { + $menu = $this->app->getMenu(); + $active = $menu->getActive(); + + // Default empty params + $params = new Registry(); + + if (!$active || !isset($active->query['option']) || $active->query['option'] !== $option) { + return $params; + } + + // Only return params if this is a direct single article menu item + if ( + isset($active->query['view']) && $active->query['view'] === 'article' && + isset($active->query['id']) && (int) $active->query['id'] === $id + ) { + return $active->getParams(); + } + + return $params; + } + + /** + * Get menu parameters for multiple article views (category, blog, featured) + * + * @param string $option + * @param string $view + * @param int|null $categoryId + * + * @return Registry + * + * @since __DEPLOY_VERSION__ + */ + private function getMultipleArticleMenuParams(string $option, string $view, ?int $categoryId = null): Registry + { + $menu = $this->app->getMenu(); + $active = $menu->getActive(); + + // Default empty params + $params = new Registry(); + + if (!$active || !isset($active->query['option']) || $active->query['option'] !== $option) { + return $params; + } + + // Check for direct menu match based on view type + if (isset($active->query['view']) && $active->query['view'] === $view) { + // For category-based views with an ID parameter + if (\in_array($view, ['category', 'categoryblog'], true) && isset($active->query['id'])) { + if ($categoryId !== null && (int) $active->query['id'] === (int) $categoryId) { + return $active->getParams(); + } + } + + // For featured view (no category ID) + elseif ($view === 'featured') { + return $active->getParams(); + } + + // For other multi-article views, just return menu params + else { + return $active->getParams(); + } + } + + return $params; + } + + + /** + * Initialize OG tags array + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + private function initializeOgTags(): array + { + $config = $this->app->getConfig(); + + return [ + 'og_title' => '', + 'og_description' => '', + 'og_image' => '', + 'og_image_alt' => '', + 'og_type' => 'website', + 'og_url' => Uri::getInstance()->toString(), + 'twitter_card' => 'summary', + 'twitter_title' => '', + 'twitter_description' => '', + 'twitter_image' => '', + 'twitter_image_alt' => '', + 'fb_app_id' => $this->params->get('fb_app_id', ''), + 'site_name' => $config->get('sitename'), + 'url' => Uri::getInstance()->toString(), + 'base_url' => Uri::base(), + ]; + } + + + + + /** * Generates OG metadata values based on category field mapping and article data. @@ -364,6 +513,8 @@ private function getOgTagsFromCategoryMappings(Registry $categoryParams, object * @param array $articleImages * * @return string + * + * @since __DEPLOY_VERSION__ */ private function getFieldValue(object $article, string $fieldName, array $articleImages): string { @@ -425,6 +576,7 @@ private function getFieldValue(object $article, string $fieldName, array $articl * @param Registry $articleImages * * @return array + * * @since __DEPLOY_VERSION__ */ private function getAllArticleImages(Registry $articleImages): array @@ -504,8 +656,8 @@ private function getDefaultOgTags(array &$ogTags): void 'og_description' => $this->params->get('default_og_description'), 'og_image' => $this->params->get('default_og_image'), 'og_image_alt' => $this->params->get('default_og_image_alt'), - 'site_name' => $this->params->get('default_og_site_name'), - 'fb_app_id' => $this->params->get('fb_app_id'), + 'site_name' => $this->params->get('default_og_site_name'), + 'fb_app_id' => $this->params->get('fb_app_id'), ]; foreach ($defaultOgTags as $key => $value) { @@ -708,25 +860,6 @@ private function adjustFieldsGroup(string $filePath, string $newGroup): string return $xml->asXML(); } - /** - * Get the parameters associated with the active menu item - * - * @return Registry - * - * @since __DEPLOY_VERSION__ - */ - private function getMenuParams(): Registry - { - $menu = $this->app->getMenu(); - - $active = $menu?->getActive(); - - if (!$active instanceof MenuItem) { - return new Registry(); - } - - return $menu->getParams($active->id); - } /** * Clean up and normalise all OG / Twitter tag values. From e63a335f96ce5a821443cd8e431903d7c334845c Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Mon, 1 Sep 2025 19:39:47 +0530 Subject: [PATCH 34/47] fix : run php-cs-fixer and sorted language strings --- .../language/en-GB/plg_system_opengraph.ini | 33 ++++++++----------- .../opengraph/src/Extension/opengraph.php | 1 - .../opengraph/src/Field/OpengraphField.php | 2 +- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini index f1b729b37b3..2e6e2ed2e4a 100644 --- a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini +++ b/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini @@ -3,8 +3,17 @@ ; License GNU General Public License version 2 or later - PLG_SYSTEM_OPENGRAPH_ADVANCED_SECTION ="Advanced Settings" +PLG_SYSTEM_OPENGRAPH_DEFAULT_DESC_DESC="Fallback description for OpenGraph tags." +PLG_SYSTEM_OPENGRAPH_DEFAULT_DESC_LABEL="Default OG Description" +PLG_SYSTEM_OPENGRAPH_DEFAULT_IMAGE_ALT_DESC="Alt text for the default OpenGraph image." +PLG_SYSTEM_OPENGRAPH_DEFAULT_IMAGE_ALT_LABEL="Default OG Image Alt" +PLG_SYSTEM_OPENGRAPH_DEFAULT_IMAGE_DESC="Fallback image for OpenGraph (recommended 1200×630)." +PLG_SYSTEM_OPENGRAPH_DEFAULT_IMAGE_LABEL="Default OG Image" +PLG_SYSTEM_OPENGRAPH_DEFAULT_SITENAME_DESC="Fallback for og:site_name if you choose to include it." +PLG_SYSTEM_OPENGRAPH_DEFAULT_SITENAME_LABEL="Default Site Name" +PLG_SYSTEM_OPENGRAPH_DEFAULT_TITLE_DESC="Used when no title is mapped or provided by Menu/Category/Item." +PLG_SYSTEM_OPENGRAPH_DEFAULT_TITLE_LABEL="Default OG Title" PLG_SYSTEM_OPENGRAPH_DESCRIPTION="Open Graph Plugin automatically generates Open Graph tags for improved social media sharing. Features hierarchical override system supporting global, category, and article-level metadata configuration." PLG_SYSTEM_OPENGRAPH_DESCRIPTION_FIELD_DESC="Select which article field to use for og:description meta tag. Meta description is preferred over article text." PLG_SYSTEM_OPENGRAPH_DESCRIPTION_FIELD_LABEL="Description Source" @@ -18,6 +27,8 @@ PLG_SYSTEM_OPENGRAPH_FEATURES_DESC="• Generates complete Open Graph and Twitte PLG_SYSTEM_OPENGRAPH_FEATURES_LABEL="What this plugin does" PLG_SYSTEM_OPENGRAPH_FIELDSET_ARTICLE="OpenGraph" PLG_SYSTEM_OPENGRAPH_FIELDSET_ARTICLE_DESC="Configure OpenGraph metadata for social media sharing. These settings override global defaults." +PLG_SYSTEM_OPENGRAPH_FIELDSET_GLOBAL_DEFAULTS="Global Defaults" +PLG_SYSTEM_OPENGRAPH_FIELDSET_GLOBAL_DEFAULTS_DESC="Set site-wide OpenGraph fallback values. Menu and item-level settings will override these." PLG_SYSTEM_OPENGRAPH_FIELD_MAPPING_SECTION="Field Mapping Configuration" PLG_SYSTEM_OPENGRAPH_GLOBAL_DESC="Global defaults used when no article / menu overrides are present." PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_SETTINGS="Global Open Graph Settings" @@ -25,6 +36,7 @@ PLG_SYSTEM_OPENGRAPH_IMAGE_ALT_FIELD_DESC="Select which article field to use for PLG_SYSTEM_OPENGRAPH_IMAGE_ALT_FIELD_LABEL="Image Alt Text Source" PLG_SYSTEM_OPENGRAPH_IMAGE_FIELD_DESC="Select which article field to use for og:image meta tag" PLG_SYSTEM_OPENGRAPH_IMAGE_FIELD_LABEL="Image Source" +PLG_SYSTEM_OPENGRAPH_INHERITED="inherited" PLG_SYSTEM_OPENGRAPH_MANUAL_OVERRIDE_SECTION="Manual Override Options" PLG_SYSTEM_OPENGRAPH_MAX_ALT_DESC="Recommended: ≤125 characters (based on WCAG guidelines)." PLG_SYSTEM_OPENGRAPH_MAX_ALT_LABEL="Max Alt Text Length" @@ -85,22 +97,3 @@ PLG_SYSTEM_OPENGRAPH_TYPE_PROFILE="Profile" PLG_SYSTEM_OPENGRAPH_TYPE_VIDEO="Video" PLG_SYSTEM_OPENGRAPH_TYPE_WEBSITE="Website" PLG_SYSTEM_OPENGRAPH_USE_DEFAULT="Use Default" -PLG_SYSTEM_OPENGRAPH_INHERITED="inherited" - -PLG_SYSTEM_OPENGRAPH_FIELDSET_GLOBAL_DEFAULTS="Global Defaults" -PLG_SYSTEM_OPENGRAPH_FIELDSET_GLOBAL_DEFAULTS_DESC="Set site-wide OpenGraph fallback values. Menu and item-level settings will override these." - -PLG_SYSTEM_OPENGRAPH_DEFAULT_TITLE_LABEL="Default OG Title" -PLG_SYSTEM_OPENGRAPH_DEFAULT_TITLE_DESC="Used when no title is mapped or provided by Menu/Category/Item." - -PLG_SYSTEM_OPENGRAPH_DEFAULT_DESC_LABEL="Default OG Description" -PLG_SYSTEM_OPENGRAPH_DEFAULT_DESC_DESC="Fallback description for OpenGraph tags." - -PLG_SYSTEM_OPENGRAPH_DEFAULT_IMAGE_LABEL="Default OG Image" -PLG_SYSTEM_OPENGRAPH_DEFAULT_IMAGE_DESC="Fallback image for OpenGraph (recommended 1200×630)." - -PLG_SYSTEM_OPENGRAPH_DEFAULT_IMAGE_ALT_LABEL="Default OG Image Alt" -PLG_SYSTEM_OPENGRAPH_DEFAULT_IMAGE_ALT_DESC="Alt text for the default OpenGraph image." - -PLG_SYSTEM_OPENGRAPH_DEFAULT_SITENAME_LABEL="Default Site Name" -PLG_SYSTEM_OPENGRAPH_DEFAULT_SITENAME_DESC="Fallback for og:site_name if you choose to include it." diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 7a424985f62..6cc08f687b2 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -25,7 +25,6 @@ use Joomla\CMS\Uri\Uri; use Joomla\Component\Categories\Administrator\Model\CategoryModel; use Joomla\Component\Content\Administrator\Model\ArticleModel; -use Joomla\Component\Fields\Administrator\Helper\FieldsHelper; use Joomla\Event\SubscriberInterface; use Joomla\Registry\Registry; diff --git a/plugins/system/opengraph/src/Field/OpengraphField.php b/plugins/system/opengraph/src/Field/OpengraphField.php index d6fa43a77e6..88de25fd638 100644 --- a/plugins/system/opengraph/src/Field/OpengraphField.php +++ b/plugins/system/opengraph/src/Field/OpengraphField.php @@ -60,7 +60,7 @@ protected function getGroups() ?: $this->form->getData()->get('extension')); } if (!$component) { - $context = (string) ($this->form ? $this->form->getName() : ''); + $context = (string) ($this->form ? $this->form->getName() : ''); $component = $context ? explode('.', $context, 2)[0] ?? '' : ''; if (!$component) { $component = (string) $app->input->getCmd('option', ''); From 7e7aaea665fd806b420f1748f52ea48858a92ed7 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Mon, 15 Sep 2025 12:20:33 +0530 Subject: [PATCH 35/47] fix : Check PHP code style fixes --- plugins/system/opengraph/src/Extension/opengraph.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 6cc08f687b2..faaf9b91d82 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -424,15 +424,9 @@ private function getMultipleArticleMenuParams(string $option, string $view, ?int if ($categoryId !== null && (int) $active->query['id'] === (int) $categoryId) { return $active->getParams(); } - } - - // For featured view (no category ID) - elseif ($view === 'featured') { + } elseif ($view === 'featured') { return $active->getParams(); - } - - // For other multi-article views, just return menu params - else { + } else { return $active->getParams(); } } From 0f8c46743c817f2bf808bc6f7171e3017eb0e202 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Mon, 15 Sep 2025 12:50:09 +0530 Subject: [PATCH 36/47] fix : removed the phpstan error --- .../components/com_content/src/Extension/ContentComponent.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/administrator/components/com_content/src/Extension/ContentComponent.php b/administrator/components/com_content/src/Extension/ContentComponent.php index 12837192df0..299afb34128 100644 --- a/administrator/components/com_content/src/Extension/ContentComponent.php +++ b/administrator/components/com_content/src/Extension/ContentComponent.php @@ -216,7 +216,7 @@ public function getSchemaorgContexts(): array */ public function getOpengraphFields(): array { - Factory::getLanguage()->load('com_content', JPATH_ADMINISTRATOR); + Factory::getApplication()->getLanguage()->load('com_content', JPATH_ADMINISTRATOR); $fields = [ 'text-fields' => [ From 64bf434c8543b1b587e2c8de087a2e7c98ea916f Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Tue, 30 Sep 2025 21:45:50 +0530 Subject: [PATCH 37/47] fix : many minor fixes and improvements for SEO plugin --- .../src/Extension/ContentComponent.php | 4 - .../language/en-GB/plg_system_opengraph.ini | 8 +- .../en-GB/plg_system_opengraph.sys.ini | 17 +- .../plg_system_opengraph/joomla.asset.json | 4 +- .../js/opengraph-placeholder.es6.js | 17 +- plugins/system/opengraph/opengraph.xml | 226 +++++++++------- .../opengraph/src/Extension/opengraph.php | 68 +++-- .../system/opengraph/src/forms/opengraph.xml | 245 +++++++++++------- .../opengraph/src/forms/opengraphmappings.xml | 97 ++++--- 9 files changed, 389 insertions(+), 297 deletions(-) rename {plugins/system/opengraph => administrator}/language/en-GB/plg_system_opengraph.ini (97%) rename {plugins/system/opengraph => administrator}/language/en-GB/plg_system_opengraph.sys.ini (72%) diff --git a/administrator/components/com_content/src/Extension/ContentComponent.php b/administrator/components/com_content/src/Extension/ContentComponent.php index 299afb34128..c56360d7286 100644 --- a/administrator/components/com_content/src/Extension/ContentComponent.php +++ b/administrator/components/com_content/src/Extension/ContentComponent.php @@ -259,10 +259,6 @@ public function getOpengraphFields(): array 'publish_up' => Text::_('COM_CONTENT_FIELD_PUBLISH_UP_LABEL'), 'publish_down' => Text::_('COM_CONTENT_FIELD_PUBLISH_DOWN_LABEL'), ], - - - - ]; return $fields; diff --git a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini b/administrator/language/en-GB/plg_system_opengraph.ini similarity index 97% rename from plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini rename to administrator/language/en-GB/plg_system_opengraph.ini index 2e6e2ed2e4a..bede244460f 100644 --- a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.ini +++ b/administrator/language/en-GB/plg_system_opengraph.ini @@ -1,7 +1,7 @@ -; Open Graph Plugin Language File -; Copyright (C) 2025 Open Source Matters, Inc. -; License GNU General Public License version 2 or later - +; Joomla! Project +; (C) 2025 Open Source Matters, Inc. +; License GNU General Public License version 2 or later; see LICENSE.txt +; Note : All ini files need to be saved as UTF-8 PLG_SYSTEM_OPENGRAPH_ADVANCED_SECTION ="Advanced Settings" PLG_SYSTEM_OPENGRAPH_DEFAULT_DESC_DESC="Fallback description for OpenGraph tags." diff --git a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.sys.ini b/administrator/language/en-GB/plg_system_opengraph.sys.ini similarity index 72% rename from plugins/system/opengraph/language/en-GB/plg_system_opengraph.sys.ini rename to administrator/language/en-GB/plg_system_opengraph.sys.ini index ec37663b11d..dc51f59166d 100644 --- a/plugins/system/opengraph/language/en-GB/plg_system_opengraph.sys.ini +++ b/administrator/language/en-GB/plg_system_opengraph.sys.ini @@ -1,13 +1,12 @@ -; Open Graph Plugin System Language File -; Copyright (C) 2025 Open Source Matters, Inc. -; License GNU General Public License version 2 or later -; Plugin Name and Description for Extension Manager +; Joomla! Project +; (C) 2025 Open Source Matters, Inc. +; License GNU General Public License version 2 or later; see LICENSE.txt +; Note : All ini files need to be saved as UTF-8 + PLG_SYSTEM_OPENGRAPH="System - Open Graph" PLG_SYSTEM_OPENGRAPH_DESCRIPTION="Automatic Open Graph tag generation plugin for Joomla 6.x with hierarchical metadata override system. Improves social media sharing with intelligent fallback mechanisms." -; Installation Messages -PLG_SYSTEM_OPENGRAPH_INSTALL_SUCCESS="Open Graph Plugin installed successfully!" -PLG_SYSTEM_OPENGRAPH_UPDATE_SUCCESS="Open Graph Plugin updated successfully!" -PLG_SYSTEM_OPENGRAPH_UNINSTALL_SUCCESS="Open Graph Plugin uninstalled successfully!" -; Custom Fields Group PLG_SYSTEM_OPENGRAPH_FIELDS_GROUP="Open Graph Metadata" PLG_SYSTEM_OPENGRAPH_FIELDS_GROUP_DESC="Custom fields for Open Graph metadata configuration" +PLG_SYSTEM_OPENGRAPH_INSTALL_SUCCESS="Open Graph Plugin installed successfully!" +PLG_SYSTEM_OPENGRAPH_UNINSTALL_SUCCESS="Open Graph Plugin uninstalled successfully!" +PLG_SYSTEM_OPENGRAPH_UPDATE_SUCCESS="Open Graph Plugin updated successfully!" diff --git a/build/media_source/plg_system_opengraph/joomla.asset.json b/build/media_source/plg_system_opengraph/joomla.asset.json index ba01c79bd2a..05b49b6a9b8 100644 --- a/build/media_source/plg_system_opengraph/joomla.asset.json +++ b/build/media_source/plg_system_opengraph/joomla.asset.json @@ -9,7 +9,9 @@ "name": "plg_system_opengraph.opengraph-placeholder", "type": "script", "uri": "plg_system_opengraph/opengraph-placeholder.min.js", - "dependencies": ["core"], + "dependencies": [ + "core" + ], "attributes": { "type": "module" } diff --git a/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js b/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js index b54076629a9..23e9c56a5e5 100644 --- a/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js +++ b/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js @@ -1,4 +1,15 @@ -document.addEventListener("DOMContentLoaded", () => { +/** + * @copyright (C) 2025 Open Source Matters, Inc. + * @license GNU General Public License version 2 or later; see LICENSE.txt + * @since __DEPLOY_VERSION__ + */ + + + +((document) => { + 'use strict'; + + document.addEventListener("DOMContentLoaded", () => { const maps = Joomla.getOptions("plgOgMappings", {}); const selectorFor = (token) => { @@ -92,3 +103,7 @@ document.addEventListener("DOMContentLoaded", () => { }); }); }); +})(document, Joomla); + + + diff --git a/plugins/system/opengraph/opengraph.xml b/plugins/system/opengraph/opengraph.xml index 0082c0c4d88..65c4c24df1b 100644 --- a/plugins/system/opengraph/opengraph.xml +++ b/plugins/system/opengraph/opengraph.xml @@ -1,101 +1,127 @@ - - PLG_SYSTEM_OPENGRAPH - Joomla! Project - 2025-06 - (C) 2025 Open Source Matters, Inc. - GNU General Public License version 2 or later - support@joomla.org - https://joomla.org - __DEPLOY_VERSION__ - PLG_SYSTEM_OPENGRAPH_DESCRIPTION - Joomla\Plugin\System\Opengraph - - js - joomla.asset.json - - - src - services - opengraph.xml - - - language/en-GB/plg_system_opengraph.ini - language/en-GB/plg_system_opengraph.sys.ini - - - -
- - - - - - - -
-
- - - - - - -
-
-
-
\ No newline at end of file + + PLG_SYSTEM_OPENGRAPH + Joomla! Project + 2025-06 + (C) 2025 Open Source Matters, Inc. + GNU General Public License version 2 or later + support@joomla.org + https://joomla.org + __DEPLOY_VERSION__ + PLG_SYSTEM_OPENGRAPH_DESCRIPTION + Joomla\Plugin\System\Opengraph + + js + joomla.asset.json + + + src + services + opengraph.xml + + + language/en-GB/plg_system_opengraph.ini + language/en-GB/plg_system_opengraph.sys.ini + + + +
+ + + + + + + + + + +
+
+ + + + + + + + + + + +
+
+
+
diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index faaf9b91d82..3e306706761 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -41,12 +41,6 @@ final class Opengraph extends CMSPlugin implements SubscriberInterface { - /** - * The application object. - * - * @var CMSApplication - */ - protected $app; /** * Should the plugin autoload its language files. @@ -85,13 +79,16 @@ public function onContentPrepareForm(PrepareFormEvent $event): void { $form = $event->getForm(); $context = $form->getName(); - $app = $this->getApplication(); - if (!$app->isClient('administrator') || !$this->isSupported($context)) { + if (!$this->getApplication()->isClient('administrator') || !$this->isSupported($context)) { return; } $isCategory = $context === 'com_categories.categorycom_content'; $isMenu = $context === 'com_menus.item'; + $parts = explode('.', $context, 2); + $componentName = $parts[0]; + + $groupName = $isMenu ? 'params' : 'attribs'; @@ -124,13 +121,13 @@ public function onContentPrepareForm(PrepareFormEvent $event): void } // Get the article id and category id - $input = $app->input; + $input = $this->getApplication()->getInput(); $articleId = (int) ($input->getInt('id') ?: $form->getValue('id')); $categoryId = 0; if ($articleId > 0) { /** @var MVCComponent $articleComponent */ - $articleComponent = $app->bootComponent('com_content'); + $articleComponent = $this->getApplication()->bootComponent($componentName); /** @var MVCFactoryInterface $articleFactory */ $articleFactory = $articleComponent->getMVCFactory(); @@ -145,7 +142,7 @@ public function onContentPrepareForm(PrepareFormEvent $event): void $catParams = new Registry(); if ($categoryId > 0) { /** @var MVCComponent $catComponent */ - $catComponent = $app->bootComponent('com_categories'); + $catComponent = $this->getApplication()->bootComponent('com_categories'); /** @var MVCFactoryInterface $catFactory */ $catFactory = $catComponent->getMVCFactory(); @@ -177,13 +174,13 @@ public function onContentPrepareForm(PrepareFormEvent $event): void $maxTitleLen = $this->params->get('max_title_length', 60); $maxDescLen = $this->params->get('max_description_length', 160); $maxAltLen = $this->params->get('max_alt_length', 125); - $mappings["maxTitleLen"] = $maxTitleLen; - $mappings["maxDescLen"] = $maxDescLen; - $mappings["maxAltLen"] = $maxAltLen; + $mappings['maxTitleLength'] = $maxTitleLen; + $mappings['maxDescLength'] = $maxDescLen; + $mappings['maxAltLength'] = $maxAltLen; $mappings['twitter_title'] = $mappings['og_title'] ?? ''; $mappings['twitter_description'] = $mappings['og_description'] ?? ''; - $document = $app->getDocument(); + $document = $this->getApplication()->getDocument(); $document->addScriptOptions('plgOgMappings', $mappings); @@ -208,30 +205,24 @@ public function onContentPrepareForm(PrepareFormEvent $event): void */ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void { - $app = $this->app; + $app = $event->getApplication(); + $document = $event->getDocument(); - if (!$app->isClient('site')) { - return; - } + $input = $app->getInput(); + $option = $input->get('option'); + $view = $input->get('view'); + $context = $option . '.' . $view; + $id = $input->getInt('id'); - /** @var HtmlDocument $document */ - $document = $app->getDocument(); - // Only process HTML documents - if (!($document instanceof HtmlDocument)) { + if (!$app->isClient('site') || !$this->isSupported($context)) { return; } - - $input = $app->input; - $view = $input->getCmd('view'); - $id = $input->getInt('id'); - $option = $input->getCmd('option'); - - if ($option !== 'com_content') { + // Only process HTML documents + if (!($document instanceof HtmlDocument)) { return; } - // Plugin disabled? if (!$this->params->get('enable_og_generation', 1)) { return; @@ -258,15 +249,18 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void * @param int $id * @param string $option * @param string $view + * @param string $context * * @return void * * @since __DEPLOY_VERSION__ */ - private function handleSingleArticle(HtmlDocument $document, array $ogTags, int $id, string $option, string $view): void + private function handleSingleArticle(HtmlDocument $document, array $ogTags, int $id, string $option, string $view, string $context): void { + $parts = explode('.', $context, 2); + $componentName = $parts[0]; /** @var MVCComponent $component */ - $component = $this->app->bootComponent('com_content'); + $component = $this->getApplication()->bootComponent($componentName); /** @var MVCFactoryInterface $mvcFactory */ $mvcFactory = $component->getMVCFactory(); @@ -373,7 +367,7 @@ private function handleMultipleArticleView(HtmlDocument $document, array $ogTags */ private function getSingleArticleMenuParams(string $option, string $view, int $id): Registry { - $menu = $this->app->getMenu(); + $menu = $this->getApplication()->getMenu(); $active = $menu->getActive(); // Default empty params @@ -407,7 +401,7 @@ private function getSingleArticleMenuParams(string $option, string $view, int $i */ private function getMultipleArticleMenuParams(string $option, string $view, ?int $categoryId = null): Registry { - $menu = $this->app->getMenu(); + $menu = $this->getApplication()->getMenu(); $active = $menu->getActive(); // Default empty params @@ -444,7 +438,7 @@ private function getMultipleArticleMenuParams(string $option, string $view, ?int */ private function initializeOgTags(): array { - $config = $this->app->getConfig(); + $config = $this->getApplication()->getConfig(); return [ 'og_title' => '', @@ -842,7 +836,7 @@ private function adjustFieldsGroup(string $filePath, string $newGroup): string $xml = simplexml_load_string($xmlContent); if ($xml === false) { - throw new \Exception("Could not load XML file: {$filePath}"); + throw new \Exception('Could not load XML file: {$filePath}'); } // Adjust all nodes to use the desired group diff --git a/plugins/system/opengraph/src/forms/opengraph.xml b/plugins/system/opengraph/src/forms/opengraph.xml index 58996266907..be8de280f09 100644 --- a/plugins/system/opengraph/src/forms/opengraph.xml +++ b/plugins/system/opengraph/src/forms/opengraph.xml @@ -1,102 +1,147 @@
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
\ No newline at end of file + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ diff --git a/plugins/system/opengraph/src/forms/opengraphmappings.xml b/plugins/system/opengraph/src/forms/opengraphmappings.xml index 97c766cbab2..abf543e3200 100644 --- a/plugins/system/opengraph/src/forms/opengraphmappings.xml +++ b/plugins/system/opengraph/src/forms/opengraphmappings.xml @@ -1,44 +1,59 @@
- -
-
- - - - - - - - - - - - -
-
-
+ +
+
+ + + + + + + + + + + + + + + +
+
+
From a6358373cf8ff8f9bf929ce391691da35c33275f Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Tue, 30 Sep 2025 21:55:33 +0530 Subject: [PATCH 38/47] fix : PHP-CS-Fixer applied --- .../js/opengraph-placeholder.es6.js | 2 -- .../opengraph/src/Extension/opengraph.php | 26 +++++++++---------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js b/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js index 23e9c56a5e5..45a3c5467f8 100644 --- a/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js +++ b/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js @@ -4,8 +4,6 @@ * @since __DEPLOY_VERSION__ */ - - ((document) => { 'use strict'; diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 3e306706761..bea401d57fb 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -10,7 +10,6 @@ namespace Joomla\Plugin\System\Opengraph\Extension; -use Joomla\CMS\Application\CMSApplication; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Document\Document; use Joomla\CMS\Document\HtmlDocument; @@ -41,7 +40,6 @@ final class Opengraph extends CMSPlugin implements SubscriberInterface { - /** * Should the plugin autoload its language files. * @@ -83,9 +81,9 @@ public function onContentPrepareForm(PrepareFormEvent $event): void return; } - $isCategory = $context === 'com_categories.categorycom_content'; - $isMenu = $context === 'com_menus.item'; - $parts = explode('.', $context, 2); + $isCategory = $context === 'com_categories.categorycom_content'; + $isMenu = $context === 'com_menus.item'; + $parts = explode('.', $context, 2); $componentName = $parts[0]; @@ -171,14 +169,14 @@ public function onContentPrepareForm(PrepareFormEvent $event): void return; // category has no mappings } - $maxTitleLen = $this->params->get('max_title_length', 60); - $maxDescLen = $this->params->get('max_description_length', 160); - $maxAltLen = $this->params->get('max_alt_length', 125); + $maxTitleLen = $this->params->get('max_title_length', 60); + $maxDescLen = $this->params->get('max_description_length', 160); + $maxAltLen = $this->params->get('max_alt_length', 125); $mappings['maxTitleLength'] = $maxTitleLen; $mappings['maxDescLength'] = $maxDescLen; $mappings['maxAltLength'] = $maxAltLen; - $mappings['twitter_title'] = $mappings['og_title'] ?? ''; - $mappings['twitter_description'] = $mappings['og_description'] ?? ''; + $mappings['twitter_title'] = $mappings['og_title'] ?? ''; + $mappings['twitter_description'] = $mappings['og_description'] ?? ''; $document = $this->getApplication()->getDocument(); @@ -206,14 +204,14 @@ public function onContentPrepareForm(PrepareFormEvent $event): void public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void { - $app = $event->getApplication(); + $app = $event->getApplication(); $document = $event->getDocument(); - $input = $app->getInput(); + $input = $app->getInput(); $option = $input->get('option'); $view = $input->get('view'); $context = $option . '.' . $view; - $id = $input->getInt('id'); + $id = $input->getInt('id'); if (!$app->isClient('site') || !$this->isSupported($context)) { @@ -257,7 +255,7 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void */ private function handleSingleArticle(HtmlDocument $document, array $ogTags, int $id, string $option, string $view, string $context): void { - $parts = explode('.', $context, 2); + $parts = explode('.', $context, 2); $componentName = $parts[0]; /** @var MVCComponent $component */ $component = $this->getApplication()->bootComponent($componentName); From 5b335227ad4dbe90b9bcc91531e46f2f77554b44 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Tue, 30 Sep 2025 22:05:05 +0530 Subject: [PATCH 39/47] fixed the function call to include the missing parameter. --- plugins/system/opengraph/src/Extension/opengraph.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index bea401d57fb..7b32cf8e05e 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -229,7 +229,7 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void $ogTags = $this->initializeOgTags(); if ($view === 'article' && $id > 0) { - $this->handleSingleArticle($document, $ogTags, $id, $option, $view); + $this->handleSingleArticle($document, $ogTags, $id, $option, $view, $context); return; } $this->handleMultipleArticleView($document, $ogTags, $option, $view, $id); From 27b2c305c6f78b641c7fcf11f0e67eaf9b64a32f Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Tue, 30 Sep 2025 22:08:31 +0530 Subject: [PATCH 40/47] fix : removed the hardcoded component name 'com_content' to make it dynamic based on the current component. --- plugins/system/opengraph/src/Extension/opengraph.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 7b32cf8e05e..713cb82d09b 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -263,7 +263,7 @@ private function handleSingleArticle(HtmlDocument $document, array $ogTags, int /** @var MVCFactoryInterface $mvcFactory */ $mvcFactory = $component->getMVCFactory(); - $params = ComponentHelper::getParams('com_content'); + $params = ComponentHelper::getParams($componentName); if (!$params instanceof Registry) { $params = new Registry(); } From a8d59966e8bc6c0174750fb73cf91014a7930174 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Wed, 1 Oct 2025 10:51:31 +0530 Subject: [PATCH 41/47] fix : made the OpenGraph plugin more generic to support other content types --- .../language/en-GB/plg_system_opengraph.ini | 2 +- .../js/opengraph-placeholder.es6.js | 90 +++++++++------- .../system/opengraph/services/provider.php | 2 - .../opengraph/src/Extension/opengraph.php | 102 +++++++++--------- 4 files changed, 102 insertions(+), 94 deletions(-) diff --git a/administrator/language/en-GB/plg_system_opengraph.ini b/administrator/language/en-GB/plg_system_opengraph.ini index bede244460f..6b35570a79f 100644 --- a/administrator/language/en-GB/plg_system_opengraph.ini +++ b/administrator/language/en-GB/plg_system_opengraph.ini @@ -36,7 +36,7 @@ PLG_SYSTEM_OPENGRAPH_IMAGE_ALT_FIELD_DESC="Select which article field to use for PLG_SYSTEM_OPENGRAPH_IMAGE_ALT_FIELD_LABEL="Image Alt Text Source" PLG_SYSTEM_OPENGRAPH_IMAGE_FIELD_DESC="Select which article field to use for og:image meta tag" PLG_SYSTEM_OPENGRAPH_IMAGE_FIELD_LABEL="Image Source" -PLG_SYSTEM_OPENGRAPH_INHERITED="inherited" +PLG_SYSTEM_OPENGRAPH_INHERITED="inherited from" PLG_SYSTEM_OPENGRAPH_MANUAL_OVERRIDE_SECTION="Manual Override Options" PLG_SYSTEM_OPENGRAPH_MAX_ALT_DESC="Recommended: ≤125 characters (based on WCAG guidelines)." PLG_SYSTEM_OPENGRAPH_MAX_ALT_LABEL="Max Alt Text Length" diff --git a/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js b/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js index 45a3c5467f8..b3ec49cf329 100644 --- a/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js +++ b/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js @@ -4,10 +4,7 @@ * @since __DEPLOY_VERSION__ */ -((document) => { - 'use strict'; - - document.addEventListener("DOMContentLoaded", () => { +const initOpengraphPlaceholder = () => { const maps = Joomla.getOptions("plgOgMappings", {}); const selectorFor = (token) => { @@ -15,41 +12,51 @@ const n = CSS.escape(token.slice(6)); return `[name="jform[com_fields][${n}]"]`; } + if (token.startsWith("image_")) { return `[name="jform[images][${CSS.escape(token)}]"]`; } + return `[name="jform[${CSS.escape(token)}]"]`; }; - function getSourceName(token) { + const t = Joomla.Text._; + const mapCoreNames = { + title: t("JGLOBAL_TITLE"), + alias: t("JFIELD_ALIAS_LABEL"), + metadesc: t("JFIELD_META_DESCRIPTION_LABEL"), + metakey: t("JFIELD_META_KEYWORDS_LABEL"), + articletext: t("COM_CONTENT_FIELD_ARTICLETEXT_LABEL"), + image_intro: t("COM_CONTENT_FIELD_INTRO_LABEL"), + image_intro_alt: + t("COM_CONTENT_FIELD_INTRO_LABEL") + + " - " + + t("COM_CONTENT_FIELD_IMAGE_ALT_LABEL"), + image_fulltext: t("COM_CONTENT_FIELD_FULLTEXT_LABEL"), + image_fulltext_alt: + t("COM_CONTENT_FIELD_FULLTEXT_LABEL") + + " - " + + t("COM_CONTENT_FIELD_IMAGE_ALT_LABEL"), + created_by_alias: t("COM_CONTENT_FIELD_CREATED_BY_LABEL"), + }; + + const getSourceName = (token) => { if (token.startsWith("field.")) { const fieldKey = token.slice(6).replace(/_/g, " "); return `Custom Field: ${fieldKey}`; } - - const mapCoreNames = { - title: "Title", - alias: "Alias", - metadesc: "Meta Description", - metakey: "Meta Keywords", - articletext: "Article Text", - image_intro: "Intro Image", - image_intro_alt: "Intro Image Alt", - image_fulltext: "Fulltext Image", - image_fulltext_alt: "Fulltext Image Alt", - created_by_alias: "Author Alias", - }; - return mapCoreNames[token] || token; - } + }; - function sanitizeText(input, maxLen = 60) { - if (typeof input !== "string" || !input.trim()) return ""; + const sanitizeText = (input, maxLen = 60) => { + if (typeof input !== "string" || !input.trim()) { + return ""; + } - // Remove HTML tags manually (just in case innerText fails) + // Strip HTML tags const noTags = input.replace(/<[^>]*>/g, " "); - // Decode HTML entities using a temporary div + // Decode entities const tempDiv = document.createElement("div"); tempDiv.innerHTML = noTags; const decoded = tempDiv.textContent || tempDiv.innerText || ""; @@ -57,13 +64,16 @@ // Normalize whitespace const cleaned = decoded.replace(/\s+/g, " ").trim(); - // Truncate with word boundary safety - if (cleaned.length <= maxLen) return cleaned; + // Safe truncate + if (cleaned.length <= maxLen) { + return cleaned; + } const cut = cleaned.lastIndexOf(" ", maxLen - 1); const safeCut = cut > maxLen * 0.6 ? cut : maxLen - 1; + return cleaned.slice(0, safeCut).replace(/[.,;:\-\s]+$/, "") + "…"; - } + }; const maxLen = { og_title: Number(maps.maxTitleLen) || 60, @@ -75,33 +85,39 @@ const ogInput = document.getElementById(`jform_attribs_${ogKey}`); const srcInput = document.querySelector(selectorFor(token)); - if (!ogInput || !srcInput) return; + if (!ogInput || !srcInput) { + return; + } const originalPh = ogInput.placeholder; const inherited = Joomla.Text._("PLG_SYSTEM_OPENGRAPH_INHERITED"); + const paint = () => { - if (ogInput.value.trim()) return; // user override - let v = srcInput.value.trim(); + if (ogInput.value.trim()) { + return; + } // user override + let v = srcInput.value.trim(); if (ogKey !== "og_image") { v = sanitizeText(v, maxLen[ogKey]); } + const sourceLabel = getSourceName(token); ogInput.placeholder = v - ? `${v} — ${inherited} from ${sourceLabel}` + ? `${v} — ${inherited} ${sourceLabel}` : originalPh; }; - paint(); // initial + paint(); // initial render srcInput.addEventListener("input", paint); ogInput.addEventListener("input", () => { - ogInput.placeholder = originalPh; // detach + ogInput.placeholder = originalPh; // detach override srcInput.removeEventListener("input", paint); }); }); -}); -})(document, Joomla); - - +}; +((document) => { + document.addEventListener("DOMContentLoaded", initOpengraphPlaceholder); +})(document); diff --git a/plugins/system/opengraph/services/provider.php b/plugins/system/opengraph/services/provider.php index 12732800dc6..c48db300e29 100644 --- a/plugins/system/opengraph/services/provider.php +++ b/plugins/system/opengraph/services/provider.php @@ -15,7 +15,6 @@ use Joomla\CMS\Plugin\PluginHelper; use Joomla\DI\Container; use Joomla\DI\ServiceProviderInterface; -use Joomla\Event\DispatcherInterface; use Joomla\Plugin\System\Opengraph\Extension\Opengraph; return new class () implements ServiceProviderInterface { @@ -34,7 +33,6 @@ public function register(Container $container) PluginInterface::class, function (Container $container) { $plugin = new Opengraph( - $container->get(DispatcherInterface::class), (array) PluginHelper::getPlugin('system', 'opengraph') ); $plugin->setApplication(Factory::getApplication()); diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 713cb82d09b..90dee03b3b1 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -47,7 +47,6 @@ final class Opengraph extends CMSPlugin implements SubscriberInterface */ protected $autoloadLanguage = true; - /** * Returns an array of events this subscriber will listen to. * @@ -81,15 +80,11 @@ public function onContentPrepareForm(PrepareFormEvent $event): void return; } - $isCategory = $context === 'com_categories.categorycom_content'; + $isCategory = str_starts_with($context, 'com_categories.category'); $isMenu = $context === 'com_menus.item'; $parts = explode('.', $context, 2); $componentName = $parts[0]; - - - - $groupName = $isMenu ? 'params' : 'attribs'; - + $groupName = $isMenu ? 'params' : 'attribs'; // Load opengraphmappings.xml for categories directly no need to adjust fields group if ($isCategory) { try { @@ -101,7 +96,6 @@ public function onContentPrepareForm(PrepareFormEvent $event): void return; } - // Load and modify opengraph.xml for articles and menus $mainXml = __DIR__ . '/../forms/opengraph.xml'; if (file_exists($mainXml)) { @@ -112,29 +106,33 @@ public function onContentPrepareForm(PrepareFormEvent $event): void error_log('OpenGraph Plugin: Failed to load main form: ' . $e->getMessage()); } } - // if the form is a menu item, we don't need to change placeholder values if ($isMenu) { return; } - // Get the article id and category id $input = $this->getApplication()->getInput(); - $articleId = (int) ($input->getInt('id') ?: $form->getValue('id')); - $categoryId = 0; - - if ($articleId > 0) { - /** @var MVCComponent $articleComponent */ - $articleComponent = $this->getApplication()->bootComponent($componentName); - /** @var MVCFactoryInterface $articleFactory */ - $articleFactory = $articleComponent->getMVCFactory(); - - /** @var ArticleModel $articleModel */ - $articleModel = $articleFactory->createModel('Article', 'Administrator', ['ignore_request' => true]); - $article = $articleModel->getItem($articleId); + $itemId = (int) ($input->getInt('id') ?: $form->getValue('id') ?: 0); + $categoryId = (int) ($form->getValue('catid') ?: 0); - if ($article && !empty($article->catid)) { - $categoryId = (int) $article->catid; + if ($itemId > 0 && $categoryId === 0 && $componentName) { + try { + $modelMap = [ + 'com_contact' => ['contact' => 'Contact'], + ]; + /** @var MVCComponent $cmp */ + $cmp = $this->getApplication()->bootComponent($componentName); + $modelName = method_exists($cmp, 'getModelName') ? $cmp->getModelName($context) : $modelMap[$componentName][$parts[1]] ?? null; + /** @var MVCFactoryInterface $factory */ + $factory = $cmp->getMVCFactory(); + $model = $factory->createModel($modelName, 'Administrator', ['ignore_request' => true]); + if (method_exists($model, 'getItem')) { + $item = $model->getItem($itemId); + if (\is_object($item) && isset($item->catid)) { + $categoryId = (int) $item->catid; + } + } + } catch (\Exception $e) { } } $catParams = new Registry(); @@ -151,7 +149,6 @@ public function onContentPrepareForm(PrepareFormEvent $event): void $category = $catModel->getItem($categoryId); // JTable row $catParams = new Registry($category->params ?? '{}'); } - if (!$catParams) { return; } @@ -163,12 +160,9 @@ public function onContentPrepareForm(PrepareFormEvent $event): void $mappings[$ogTag] = $fieldName; } } - - if (!$mappings) { return; // category has no mappings } - $maxTitleLen = $this->params->get('max_title_length', 60); $maxDescLen = $this->params->get('max_description_length', 160); $maxAltLen = $this->params->get('max_alt_length', 125); @@ -183,6 +177,21 @@ public function onContentPrepareForm(PrepareFormEvent $event): void $document->addScriptOptions('plgOgMappings', $mappings); Text::script('PLG_SYSTEM_OPENGRAPH_INHERITED'); + foreach ( + [ + 'JGLOBAL_TITLE', + 'JFIELD_ALIAS_LABEL', + 'JFIELD_META_DESCRIPTION_LABEL', + 'JFIELD_META_KEYWORDS_LABEL', + 'COM_CONTENT_FIELD_ARTICLETEXT_LABEL', + 'COM_CONTENT_FIELD_INTRO_LABEL', + 'COM_CONTENT_FIELD_IMAGE_ALT_LABEL', + 'COM_CONTENT_FIELD_FULL_LABEL', + 'COM_CONTENT_FIELD_CREATED_BY_LABEL', + ] as $key + ) { + Text::script($key); + } /** @var WebAssetManager $wa */ $wa = $document->getWebAssetManager(); @@ -229,7 +238,7 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void $ogTags = $this->initializeOgTags(); if ($view === 'article' && $id > 0) { - $this->handleSingleArticle($document, $ogTags, $id, $option, $view, $context); + $this->handleSingleItem($document, $ogTags, $id, $option, $view, $context); return; } $this->handleMultipleArticleView($document, $ogTags, $option, $view, $id); @@ -253,13 +262,17 @@ public function onBeforeCompileHead(BeforeCompileHeadEvent $event): void * * @since __DEPLOY_VERSION__ */ - private function handleSingleArticle(HtmlDocument $document, array $ogTags, int $id, string $option, string $view, string $context): void + private function handleSingleItem(HtmlDocument $document, array $ogTags, int $id, string $option, string $view, string $context): void { $parts = explode('.', $context, 2); $componentName = $parts[0]; /** @var MVCComponent $component */ $component = $this->getApplication()->bootComponent($componentName); + $modelMap = [ + 'com_contact' => ['contact' => 'Contact'], + ]; + $modelName = $component->getModelName($context) ?? $modelMap[$componentName][$parts[1]] ?? null; /** @var MVCFactoryInterface $mvcFactory */ $mvcFactory = $component->getMVCFactory(); @@ -268,13 +281,13 @@ private function handleSingleArticle(HtmlDocument $document, array $ogTags, int $params = new Registry(); } - /** @var ArticleModel $articleModel */ - $articleModel = $mvcFactory->createModel('Article', 'Site', ['ignore_request' => true]); + /** @var ArticleModel $model */ + $model = $mvcFactory->createModel($modelName, 'Site', ['ignore_request' => true]); - $articleModel->setState('params', clone $params); - $articleModel->setState('article.id', $id); + $model->setState('params', clone $params); + $model->setState('article.id', $id); - $article = $articleModel->getItem($id); + $article = $model->getItem($id); if (!$article) { return; } @@ -457,11 +470,6 @@ private function initializeOgTags(): array ]; } - - - - - /** * Generates OG metadata values based on category field mapping and article data. * @@ -488,8 +496,6 @@ private function getOgTagsFromCategoryMappings(Registry $categoryParams, object } } - - /** * Get value from article field or custom field * @@ -555,8 +561,6 @@ private function getFieldValue(object $article, string $fieldName, array $articl return (string) $value; } - - /** * @param Registry $articleImages * @@ -596,8 +600,6 @@ private function getAllArticleImages(Registry $articleImages): array ]; } - - /** * Extract OG tags from a parameter source (article, menu) * @@ -623,8 +625,6 @@ private function getOgTagsFromParams(Registry $params, array &$ogTags): void } } - - /** * Get Global Default OG tags if not till not set * @param array &$ogTags @@ -652,9 +652,6 @@ private function getDefaultOgTags(array &$ogTags): void } } - - - /** * Get Twitter tags if not set use OG value * @param array &$ogTags @@ -817,7 +814,6 @@ protected function isSupported($context): bool return $component instanceof OpengraphServiceInterface; } - /** * Adjust the fields group in the XML file * @@ -845,7 +841,6 @@ private function adjustFieldsGroup(string $filePath, string $newGroup): string return $xml->asXML(); } - /** * Clean up and normalise all OG / Twitter tag values. * @@ -881,7 +876,6 @@ private function sanitizeOgTags(array &$ogTags): void } } - /** * Helper: strip HTML, decode entities, collapse whitespace, then truncate on a word boundary and add an ellipsis if needed. * From 10b101467e9f0a13199bd85dfb550a2df9f07831 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Wed, 1 Oct 2025 21:18:15 +0530 Subject: [PATCH 42/47] fix : added fallback for getModelName method if not exists in component --- .../opengraph/src/Extension/opengraph.php | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 90dee03b3b1..e1b4c025a4c 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -117,12 +117,18 @@ public function onContentPrepareForm(PrepareFormEvent $event): void if ($itemId > 0 && $categoryId === 0 && $componentName) { try { - $modelMap = [ - 'com_contact' => ['contact' => 'Contact'], - ]; /** @var MVCComponent $cmp */ $cmp = $this->getApplication()->bootComponent($componentName); - $modelName = method_exists($cmp, 'getModelName') ? $cmp->getModelName($context) : $modelMap[$componentName][$parts[1]] ?? null; + $modelName = null; + if (method_exists($cmp, 'getModelName')) { + $modelName = $cmp->getModelName($context); + } else { + // Fallback to plugin method + $modelName = $this->getModelName($context); + } + if (!$modelName) { + return; + } /** @var MVCFactoryInterface $factory */ $factory = $cmp->getMVCFactory(); $model = $factory->createModel($modelName, 'Administrator', ['ignore_request' => true]); @@ -269,10 +275,16 @@ private function handleSingleItem(HtmlDocument $document, array $ogTags, int $id /** @var MVCComponent $component */ $component = $this->getApplication()->bootComponent($componentName); - $modelMap = [ - 'com_contact' => ['contact' => 'Contact'], - ]; - $modelName = $component->getModelName($context) ?? $modelMap[$componentName][$parts[1]] ?? null; + $modelName = null; + if (method_exists($component, 'getModelName')) { + $modelName = $component->getModelName($context); + } else { + // Fallback to plugin method + $modelName = $this->getModelName($context); + } + if (!$modelName) { + return; + } /** @var MVCFactoryInterface $mvcFactory */ $mvcFactory = $component->getMVCFactory(); @@ -897,4 +909,26 @@ private function cleanText(string $text, int $maxLen): string // Replace the three-dot ellipsis with a single Unicode one return preg_replace('/\.\.\.$/', '…', $truncated); } + + /** + * Returns the model name, based on the context + * + * @param string $context The context of the workflow + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + public function getModelName($context): string + { + $parts = explode('.', $context); + + if (\count($parts) < 2) { + return ''; + } + + array_shift($parts); + + return ucfirst(array_shift($parts)); + } } From bdc382e79f2889baa547d90c6d176eb71a5dfad4 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Wed, 1 Oct 2025 21:21:40 +0530 Subject: [PATCH 43/47] fix : changed $cmp to $component to avoid confusion and potential issues with variable scope --- plugins/system/opengraph/src/Extension/opengraph.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index e1b4c025a4c..d4562875f8b 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -117,11 +117,11 @@ public function onContentPrepareForm(PrepareFormEvent $event): void if ($itemId > 0 && $categoryId === 0 && $componentName) { try { - /** @var MVCComponent $cmp */ - $cmp = $this->getApplication()->bootComponent($componentName); + /** @var MVCComponent $component */ + $component = $this->getApplication()->bootComponent($componentName); $modelName = null; - if (method_exists($cmp, 'getModelName')) { - $modelName = $cmp->getModelName($context); + if (method_exists($component, 'getModelName')) { + $modelName = $component->getModelName($context); } else { // Fallback to plugin method $modelName = $this->getModelName($context); @@ -130,7 +130,7 @@ public function onContentPrepareForm(PrepareFormEvent $event): void return; } /** @var MVCFactoryInterface $factory */ - $factory = $cmp->getMVCFactory(); + $factory = $component->getMVCFactory(); $model = $factory->createModel($modelName, 'Administrator', ['ignore_request' => true]); if (method_exists($model, 'getItem')) { $item = $model->getItem($itemId); From b721d72898420de3116fa9f8f1121240e368d0f0 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Thu, 2 Oct 2025 09:46:01 +0530 Subject: [PATCH 44/47] fix : minor fixes and improvements --- .../src/Extension/ContentComponent.php | 2 +- .../src/Extension/MenusComponent.php | 2 +- .../language/en-GB/plg_system_opengraph.ini | 1 + .../js/opengraph-placeholder.es6.js | 11 +++---- .../Opengraph/OpengraphServiceInterface.php | 2 +- .../opengraph/src/Extension/opengraph.php | 29 ++++++++++++------- .../opengraph/src/Field/OpengraphField.php | 24 +++++++-------- 7 files changed, 40 insertions(+), 31 deletions(-) diff --git a/administrator/components/com_content/src/Extension/ContentComponent.php b/administrator/components/com_content/src/Extension/ContentComponent.php index c56360d7286..efc9e76c6f3 100644 --- a/administrator/components/com_content/src/Extension/ContentComponent.php +++ b/administrator/components/com_content/src/Extension/ContentComponent.php @@ -208,7 +208,7 @@ public function getSchemaorgContexts(): array /** - * Returns valid contexts for opengraph + * Returns a grouped list of mappable fields used by the OpengraphField. * * @return array * diff --git a/administrator/components/com_menus/src/Extension/MenusComponent.php b/administrator/components/com_menus/src/Extension/MenusComponent.php index 6338ad5b89e..f183a7acadf 100644 --- a/administrator/components/com_menus/src/Extension/MenusComponent.php +++ b/administrator/components/com_menus/src/Extension/MenusComponent.php @@ -56,7 +56,7 @@ public function boot(ContainerInterface $container) /** - * Returns valid contexts for opengraph + * Returns a grouped list of mappable fields used by the OpengraphField. * * @return array * diff --git a/administrator/language/en-GB/plg_system_opengraph.ini b/administrator/language/en-GB/plg_system_opengraph.ini index 6b35570a79f..a1e0c08d56c 100644 --- a/administrator/language/en-GB/plg_system_opengraph.ini +++ b/administrator/language/en-GB/plg_system_opengraph.ini @@ -32,6 +32,7 @@ PLG_SYSTEM_OPENGRAPH_FIELDSET_GLOBAL_DEFAULTS_DESC="Set site-wide OpenGraph fall PLG_SYSTEM_OPENGRAPH_FIELD_MAPPING_SECTION="Field Mapping Configuration" PLG_SYSTEM_OPENGRAPH_GLOBAL_DESC="Global defaults used when no article / menu overrides are present." PLG_SYSTEM_OPENGRAPH_GLOBAL_OG_SETTINGS="Global Open Graph Settings" +PLG_SYSTEM_OPENGRAPH_GROUP_DEFAULT_FIELDS="Default Fields" PLG_SYSTEM_OPENGRAPH_IMAGE_ALT_FIELD_DESC="Select which article field to use for og:image:alt meta tag for accessibility" PLG_SYSTEM_OPENGRAPH_IMAGE_ALT_FIELD_LABEL="Image Alt Text Source" PLG_SYSTEM_OPENGRAPH_IMAGE_FIELD_DESC="Select which article field to use for og:image meta tag" diff --git a/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js b/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js index b3ec49cf329..bc8e3bfe771 100644 --- a/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js +++ b/build/media_source/plg_system_opengraph/js/opengraph-placeholder.es6.js @@ -6,6 +6,7 @@ const initOpengraphPlaceholder = () => { const maps = Joomla.getOptions("plgOgMappings", {}); + const limits = Joomla.getOptions("plgOgLimits", {}); const selectorFor = (token) => { if (token.startsWith("field.")) { @@ -32,9 +33,9 @@ const initOpengraphPlaceholder = () => { t("COM_CONTENT_FIELD_INTRO_LABEL") + " - " + t("COM_CONTENT_FIELD_IMAGE_ALT_LABEL"), - image_fulltext: t("COM_CONTENT_FIELD_FULLTEXT_LABEL"), + image_fulltext: t("COM_CONTENT_FIELD_FULL_LABEL"), image_fulltext_alt: - t("COM_CONTENT_FIELD_FULLTEXT_LABEL") + + t("COM_CONTENT_FIELD_FULL_LABEL") + " - " + t("COM_CONTENT_FIELD_IMAGE_ALT_LABEL"), created_by_alias: t("COM_CONTENT_FIELD_CREATED_BY_LABEL"), @@ -76,9 +77,9 @@ const initOpengraphPlaceholder = () => { }; const maxLen = { - og_title: Number(maps.maxTitleLen) || 60, - og_description: Number(maps.maxDescLen) || 160, - og_image_alt: Number(maps.maxAltLen) || 125, + og_title: Number(limits.maxTitleLength) || 60, + og_description: Number(limits.maxDescLength) || 160, + og_image_alt: Number(limits.maxAltLength) || 125, }; Object.entries(maps).forEach(([ogKey, token]) => { diff --git a/libraries/src/Opengraph/OpengraphServiceInterface.php b/libraries/src/Opengraph/OpengraphServiceInterface.php index d8cc2eca98e..5346295fcf3 100644 --- a/libraries/src/Opengraph/OpengraphServiceInterface.php +++ b/libraries/src/Opengraph/OpengraphServiceInterface.php @@ -22,7 +22,7 @@ interface OpengraphServiceInterface { /** - * Returns valid contexts. + * Returns a grouped list of mappable fields used by the OpengraphField. * * @return array * diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index d4562875f8b..3f333fb9b88 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -169,18 +169,22 @@ public function onContentPrepareForm(PrepareFormEvent $event): void if (!$mappings) { return; // category has no mappings } - $maxTitleLen = $this->params->get('max_title_length', 60); - $maxDescLen = $this->params->get('max_description_length', 160); - $maxAltLen = $this->params->get('max_alt_length', 125); - $mappings['maxTitleLength'] = $maxTitleLen; - $mappings['maxDescLength'] = $maxDescLen; - $mappings['maxAltLength'] = $maxAltLen; + $maxTitleLength = $this->params->get('max_title_length', 60); + $maxDescLength = $this->params->get('max_description_length', 160); + $maxAltLength = $this->params->get('max_alt_length', 125); + $limits = [ + 'maxTitleLength' => $maxTitleLength, + 'maxDescLength' => $maxDescLength, + 'maxAltLength' => $maxAltLength, + ]; + $mappings['twitter_title'] = $mappings['og_title'] ?? ''; $mappings['twitter_description'] = $mappings['og_description'] ?? ''; $document = $this->getApplication()->getDocument(); $document->addScriptOptions('plgOgMappings', $mappings); + $document->addScriptOptions('plgOgLimits', $limits); Text::script('PLG_SYSTEM_OPENGRAPH_INHERITED'); foreach ( @@ -757,7 +761,7 @@ private function setOpenGraphImage( $twitterImage = $ogTags['twitter_image']; $twitterImageAlt = $ogTags['twitter_image_alt']; - if (empty($image) || !empty($document->getMetaData('og:image'))) { + if (empty($image)) { return; } @@ -773,9 +777,12 @@ private function setOpenGraphImage( $twitterImageUrl = empty($baseUrl) ? '' : rtrim($baseUrl, '/') . '/'; $twitterImageUrl .= $twitterImage; - $this->setMetaData($document, 'og:image', $ogImageUrl, 'property'); - $this->setMetaData($document, 'og:image:secure_url', $ogImageUrl, 'property'); + if (empty($document->getMetaData('og:image'))) { + $this->setMetaData($document, 'og:image', $ogImageUrl, 'property'); + } + $this->setMetaData($document, 'og:image:alt', $alt, 'property'); + $this->setMetaData($document, 'og:image:secure_url', $ogImageUrl, 'property'); $this->setMetaData($document, 'twitter:image', $twitterImageUrl, 'name'); $this->setMetaData($document, 'twitter:image:alt', $twitterImageAlt, 'name'); @@ -842,7 +849,7 @@ private function adjustFieldsGroup(string $filePath, string $newGroup): string $xml = simplexml_load_string($xmlContent); if ($xml === false) { - throw new \Exception('Could not load XML file: {$filePath}'); + throw new \Exception("Could not load XML file: {$filePath}"); } // Adjust all nodes to use the desired group @@ -915,7 +922,7 @@ private function cleanText(string $text, int $maxLen): string * * @param string $context The context of the workflow * - * @return boolean + * @return string * * @since __DEPLOY_VERSION__ */ diff --git a/plugins/system/opengraph/src/Field/OpengraphField.php b/plugins/system/opengraph/src/Field/OpengraphField.php index 88de25fd638..846564f1d81 100644 --- a/plugins/system/opengraph/src/Field/OpengraphField.php +++ b/plugins/system/opengraph/src/Field/OpengraphField.php @@ -54,36 +54,36 @@ protected function getGroups() ]; - $component = ''; + $componentName = ''; if ($this->form) { - $component = (string) ($this->form->getValue('extension') + $componentName = (string) ($this->form->getValue('extension') ?: $this->form->getData()->get('extension')); } - if (!$component) { - $context = (string) ($this->form ? $this->form->getName() : ''); - $component = $context ? explode('.', $context, 2)[0] ?? '' : ''; - if (!$component) { - $component = (string) $app->input->getCmd('option', ''); + if (!$componentName) { + $context = (string) ($this->form ? $this->form->getName() : ''); + $componentName = $context ? explode('.', $context, 2)[0] ?? '' : ''; + if (!$componentName) { + $componentName = (string) $app->input->getCmd('option', ''); } } - if (!$component) { + if (!$componentName) { return $groups; } try { - $cmp = $app->bootComponent($component); + $component = $app->bootComponent($componentName); } catch (\Throwable $e) { return $groups; } - if (!$cmp instanceof OpengraphServiceInterface) { + if (!$component instanceof OpengraphServiceInterface) { return $groups; } $ogOptions = []; - $fields = $cmp->getOpengraphFields(); + $fields = $component->getOpengraphFields(); $fieldType = $this->getAttribute('field-type'); if (isset($fields[$fieldType])) { @@ -93,7 +93,7 @@ protected function getGroups() } if (!empty($ogOptions)) { - $groups['Default Fields'] = $ogOptions; + $groups[Text::_('PLG_SYSTEM_OPENGRAPH_GROUP_DEFAULT_FIELDS')] = $ogOptions; } From 78a4e3badd84e593d476ae7d2783a7b0b235ddf2 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Fri, 3 Oct 2025 20:25:09 +0530 Subject: [PATCH 45/47] fix : added getmodelname method to opengraph service interface and removed fallback to plugin method --- .../Opengraph/OpengraphServiceInterface.php | 9 ++++++ .../opengraph/src/Extension/opengraph.php | 28 ------------------- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/libraries/src/Opengraph/OpengraphServiceInterface.php b/libraries/src/Opengraph/OpengraphServiceInterface.php index 5346295fcf3..3b30cd0fa6b 100644 --- a/libraries/src/Opengraph/OpengraphServiceInterface.php +++ b/libraries/src/Opengraph/OpengraphServiceInterface.php @@ -30,4 +30,13 @@ interface OpengraphServiceInterface * */ public function getOpengraphFields(): array; + + /** + * Returns the model name, based on the context + * + * @param string + * + * @return boolean + */ + public function getModelName($context): string; } diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 3f333fb9b88..9206d1897a4 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -122,9 +122,6 @@ public function onContentPrepareForm(PrepareFormEvent $event): void $modelName = null; if (method_exists($component, 'getModelName')) { $modelName = $component->getModelName($context); - } else { - // Fallback to plugin method - $modelName = $this->getModelName($context); } if (!$modelName) { return; @@ -282,9 +279,6 @@ private function handleSingleItem(HtmlDocument $document, array $ogTags, int $id $modelName = null; if (method_exists($component, 'getModelName')) { $modelName = $component->getModelName($context); - } else { - // Fallback to plugin method - $modelName = $this->getModelName($context); } if (!$modelName) { return; @@ -916,26 +910,4 @@ private function cleanText(string $text, int $maxLen): string // Replace the three-dot ellipsis with a single Unicode one return preg_replace('/\.\.\.$/', '…', $truncated); } - - /** - * Returns the model name, based on the context - * - * @param string $context The context of the workflow - * - * @return string - * - * @since __DEPLOY_VERSION__ - */ - public function getModelName($context): string - { - $parts = explode('.', $context); - - if (\count($parts) < 2) { - return ''; - } - - array_shift($parts); - - return ucfirst(array_shift($parts)); - } } From 1a98cc5fa5234940365298e40c54ee38463ab522 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Fri, 3 Oct 2025 20:45:13 +0530 Subject: [PATCH 46/47] fix : add getModelName to MenusComponent --- .../src/Extension/MenusComponent.php | 22 +++++++++++++++++++ .../opengraph/src/Extension/opengraph.php | 4 ++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/administrator/components/com_menus/src/Extension/MenusComponent.php b/administrator/components/com_menus/src/Extension/MenusComponent.php index f183a7acadf..b6471744888 100644 --- a/administrator/components/com_menus/src/Extension/MenusComponent.php +++ b/administrator/components/com_menus/src/Extension/MenusComponent.php @@ -69,4 +69,26 @@ public function getOpengraphFields(): array return $fields; } + + /** + * Returns the model name, based on the context + * + * @param string $context + * + * @return string + * + * @since __DEPLOY_VERSION__ + */ + public function getModelName($context): string + { + $parts = explode('.', $context); + + if (\count($parts) < 2) { + return ''; + } + + array_shift($parts); + + return ucfirst(array_shift($parts)); + } } diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/opengraph.php index 9206d1897a4..fb56a34930b 100644 --- a/plugins/system/opengraph/src/Extension/opengraph.php +++ b/plugins/system/opengraph/src/Extension/opengraph.php @@ -120,7 +120,7 @@ public function onContentPrepareForm(PrepareFormEvent $event): void /** @var MVCComponent $component */ $component = $this->getApplication()->bootComponent($componentName); $modelName = null; - if (method_exists($component, 'getModelName')) { + if ($component instanceof OpengraphServiceInterface) { $modelName = $component->getModelName($context); } if (!$modelName) { @@ -277,7 +277,7 @@ private function handleSingleItem(HtmlDocument $document, array $ogTags, int $id $component = $this->getApplication()->bootComponent($componentName); $modelName = null; - if (method_exists($component, 'getModelName')) { + if ($component instanceof OpengraphServiceInterface) { $modelName = $component->getModelName($context); } if (!$modelName) { From edd13bf3109c3d86431b96c898244445b22d0738 Mon Sep 17 00:00:00 2001 From: shahzan01 Date: Fri, 3 Oct 2025 20:55:47 +0530 Subject: [PATCH 47/47] fix : Rename opengraph.php to Opengraph.php for consistency --- .../opengraph/src/Extension/{opengraph.php => Opengraph.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugins/system/opengraph/src/Extension/{opengraph.php => Opengraph.php} (100%) diff --git a/plugins/system/opengraph/src/Extension/opengraph.php b/plugins/system/opengraph/src/Extension/Opengraph.php similarity index 100% rename from plugins/system/opengraph/src/Extension/opengraph.php rename to plugins/system/opengraph/src/Extension/Opengraph.php