diff --git a/app/components/enterprise_trials/welcome_dialog_component.html.erb b/app/components/enterprise_trials/welcome_dialog_component.html.erb
index 3c7029043b5..630c3ec6a6c 100644
--- a/app/components/enterprise_trials/welcome_dialog_component.html.erb
+++ b/app/components/enterprise_trials/welcome_dialog_component.html.erb
@@ -22,7 +22,7 @@
frameborder: "0",
height: "400",
width: "100%",
- src: OpenProject::Static::Links.links[:enterprise_welcome_video][:href],
+ src: OpenProject::Static::Links.url_for(:enterprise_welcome_video),
allowfullscreen: true)
end
end
diff --git a/app/components/work_packages/progress/base_modal_component.rb b/app/components/work_packages/progress/base_modal_component.rb
index 6ba3656a1e6..94103d635f1 100644
--- a/app/components/work_packages/progress/base_modal_component.rb
+++ b/app/components/work_packages/progress/base_modal_component.rb
@@ -79,7 +79,7 @@ module WorkPackages
end
def learn_more_href
- OpenProject::Static::Links.links[:progress_tracking_docs][:href]
+ OpenProject::Static::Links.url_for(:progress_tracking_docs)
end
private
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index 661ec8f20e5..17c4276d307 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -32,11 +32,11 @@ class HelpController < ApplicationController
no_authorization_required! :keyboard_shortcuts, :text_formatting
def keyboard_shortcuts
- redirect_to OpenProject::Static::Links[:shortcuts][:href], allow_other_host: true
+ redirect_to OpenProject::Static::Links.url_for(:shortcuts), allow_other_host: true
end
def text_formatting
- default_link = OpenProject::Static::Links[:text_formatting][:href]
+ default_link = OpenProject::Static::Links.url_for(:text_formatting)
help_link = OpenProject::Configuration.force_formatting_help_link.presence || default_link
redirect_to help_link, allow_other_host: true
diff --git a/app/forms/my/look_and_feel_form.rb b/app/forms/my/look_and_feel_form.rb
index df827a3c6c1..c0439de7e7c 100644
--- a/app/forms/my/look_and_feel_form.rb
+++ b/app/forms/my/look_and_feel_form.rb
@@ -30,6 +30,7 @@
class My::LookAndFeelForm < ApplicationForm
include ApplicationHelper
+
form do |f|
f.select_list(
name: :theme,
@@ -65,7 +66,7 @@ class My::LookAndFeelForm < ApplicationForm
f.check_box name: :disable_keyboard_shortcuts,
label: I18n.t("activerecord.attributes.user_preference.disable_keyboard_shortcuts"),
caption: I18n.t("activerecord.attributes.user_preference.disable_keyboard_shortcuts_caption_html",
- href: OpenProject::Static::Links.links[:shortcuts][:href]).html_safe
+ href: OpenProject::Static::Links.url_for(:shortcuts)).html_safe
f.submit(name: :submit,
label: I18n.t("activerecord.attributes.user_preference.button_update_look_and_feel"),
diff --git a/app/helpers/static_links_helper.rb b/app/helpers/static_links_helper.rb
index 0a6b1ff952e..36b5f6283b3 100644
--- a/app/helpers/static_links_helper.rb
+++ b/app/helpers/static_links_helper.rb
@@ -31,11 +31,12 @@
module StaticLinksHelper
##
# Create a static link to the given key entry
- def static_link_to(key, label: nil)
- item = OpenProject::Static::Links.links.fetch key
+ def static_link_to(*path, label: nil)
+ href = OpenProject::Static::Links.url_for(*path)
+ label_text = label || OpenProject::Static::Links.label_for(*path)
- link_to label || t(item[:label]),
- item[:href],
+ link_to label_text,
+ href,
class: "openproject--static-link",
target: "_blank", rel: "noopener"
end
@@ -44,8 +45,7 @@ module StaticLinksHelper
# Link to the correct installation guides for the current selected method
def installation_guide_link
val = OpenProject::Configuration.installation_type
- link = OpenProject::Static::Links.links[:"#{val}_installation"] || OpenProject::Static::Links.links[:installation_guides]
-
- link[:href]
+ # Try specific installation type first, fallback to general installation guides
+ OpenProject::Static::Links.url_for(:"#{val}_installation") || OpenProject::Static::Links.url_for(:installation_guides)
end
end
diff --git a/app/views/admin/info.html.erb b/app/views/admin/info.html.erb
index 50e35fe2b7c..21d2e9821d2 100644
--- a/app/views/admin/info.html.erb
+++ b/app/views/admin/info.html.erb
@@ -73,7 +73,7 @@ See COPYRIGHT and LICENSE files for more details.
if display_security_badge_graphic?
content = content_tag :object, nil, data: security_badge_url, type: "image/svg+xml"
content += link_to "",
- ::OpenProject::Static::Links[:security_badge_documentation][:href],
+ ::OpenProject::Static::Links.url_for(:security_badge_documentation),
title: t(:label_what_is_this),
class: "security-badge--help-icon icon-context icon-help1",
target: "_blank"
diff --git a/app/views/admin/settings/api_settings/show.html.erb b/app/views/admin/settings/api_settings/show.html.erb
index 1eeeba35ca0..e1167f136f2 100644
--- a/app/views/admin/settings/api_settings/show.html.erb
+++ b/app/views/admin/settings/api_settings/show.html.erb
@@ -84,7 +84,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= t(:text_line_separated) %>
<%= t(
:setting_apiv3_cors_origins_text_html,
- origin_link: ::OpenProject::Static::Links[:origin_mdn_documentation][:href]
+ origin_link: ::OpenProject::Static::Links.url_for(:origin_mdn_documentation)
) %>
diff --git a/app/views/admin/settings/date_format_settings/show.html.erb b/app/views/admin/settings/date_format_settings/show.html.erb
index 9e6b5b9fe52..66a3ed5395b 100644
--- a/app/views/admin/settings/date_format_settings/show.html.erb
+++ b/app/views/admin/settings/date_format_settings/show.html.erb
@@ -69,7 +69,7 @@ See COPYRIGHT and LICENSE files for more details.
diff --git a/app/views/admin/settings/general_settings/show.html.erb b/app/views/admin/settings/general_settings/show.html.erb
index 6137683473a..81ac683fcb1 100644
--- a/app/views/admin/settings/general_settings/show.html.erb
+++ b/app/views/admin/settings/general_settings/show.html.erb
@@ -96,7 +96,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= t(
:text_notice_security_badge_displayed_html,
information_panel_label: t(:label_information),
- more_info_url: ::OpenProject::Static::Links[:security_badge_documentation][:href],
+ more_info_url: ::OpenProject::Static::Links.url_for(:security_badge_documentation),
information_panel_path: info_admin_index_path
) %>
diff --git a/app/views/admin/settings/icalendar_settings/show.html.erb b/app/views/admin/settings/icalendar_settings/show.html.erb
index bd1c6cc6623..ac1885f1b22 100644
--- a/app/views/admin/settings/icalendar_settings/show.html.erb
+++ b/app/views/admin/settings/icalendar_settings/show.html.erb
@@ -50,7 +50,7 @@ See COPYRIGHT and LICENSE files for more details.
diff --git a/app/views/enterprise_tokens/_info.html.erb b/app/views/enterprise_tokens/_info.html.erb
index 442f7494d26..2e93bb6bcea 100644
--- a/app/views/enterprise_tokens/_info.html.erb
+++ b/app/views/enterprise_tokens/_info.html.erb
@@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<% content_for :header_tags do %>
- <%= nonced_javascript_include_tag OpenProject::Static::Links.links[:chargebee][:href],
+ <%= nonced_javascript_include_tag OpenProject::Static::Links.url_for(:chargebee),
"data-cb-site": OpenProject::Configuration.enterprise_chargebee_site %>
<% end %>
diff --git a/app/views/homescreen/blocks/_administration.html.erb b/app/views/homescreen/blocks/_administration.html.erb
index 37d1414d433..4d6eefbf5f8 100644
--- a/app/views/homescreen/blocks/_administration.html.erb
+++ b/app/views/homescreen/blocks/_administration.html.erb
@@ -51,7 +51,7 @@
<%= content_tag :div, class: "security-badge--container" do %>
<%= content_tag :object, nil, data: security_badge_url, type: "image/svg+xml" %>
<%= link_to "",
- ::OpenProject::Static::Links[:security_badge_documentation][:href],
+ ::OpenProject::Static::Links.url_for(:security_badge_documentation),
title: t(:label_what_is_this),
class: "security-badge--help-icon icon-context icon-help1",
target: "_blank", rel: "noopener" %>
diff --git a/app/views/homescreen/blocks/_community.html.erb b/app/views/homescreen/blocks/_community.html.erb
index fa2974ce694..d0b385c9c07 100644
--- a/app/views/homescreen/blocks/_community.html.erb
+++ b/app/views/homescreen/blocks/_community.html.erb
@@ -11,30 +11,63 @@
<%= static_link_to :forums %>
- <%= static_link_to (EnterpriseToken.active? ? :enterprise_support : :enterprise_support_as_community) %>
+ <%= static_link_to(EnterpriseToken.active? ? :enterprise_support : :enterprise_support_as_community) %>
<%= link_to(
- t("label_openproject_website"), "#{OpenProject::Static::Links.links[:website][:href]}/?utm_source=unknown&utm_medium=op-instance&utm_campaign=website-home-screen",
- { aria: { label: t("label_openproject_website") },
+ t("label_openproject_website"),
+ OpenProject::Static::Links.url_for(
+ :website,
+ url_params: {
+ utm_source: "unknown",
+ utm_medium: "op-instance",
+ utm_campaign: "website-home-screen"
+ }
+ ),
+ {
+ aria: { label: t("label_openproject_website") },
target: "_blank",
- title: t("label_openproject_website"), rel: "noopener" }
+ title: t("label_openproject_website"),
+ rel: "noopener"
+ }
) %>
<%= link_to(
- t("homescreen.links.security_alerts"), "#{OpenProject::Static::Links.links[:security_alerts][:href]}/?utm_source=unknown&utm_medium=op-instance&utm_campaign=security-alerts-home-screen",
- { aria: { label: t("homescreen.links.security_alerts") },
+ t("homescreen.links.security_alerts"),
+ OpenProject::Static::Links.url_for(
+ :security_alerts,
+ url_params: {
+ utm_source: "unknown",
+ utm_medium: "op-instance",
+ utm_campaign: "security-alerts-home-screen"
+ }
+ ),
+ {
+ aria: { label: t("homescreen.links.security_alerts") },
target: "_blank",
- title: t("homescreen.links.security_alerts"), rel: "noopener" }
+ title: t("homescreen.links.security_alerts"),
+ rel: "noopener"
+ }
) %>
<%= link_to(
- t("homescreen.links.newsletter"), "#{OpenProject::Static::Links.links[:newsletter][:href]}/?utm_source=unknown&utm_medium=op-instance&utm_campaign=newsletter-home-screen",
- { aria: { label: t("homescreen.links.newsletter") },
+ t("homescreen.links.newsletter"),
+ OpenProject::Static::Links.url_for(
+ :newsletter,
+ url_params: {
+ utm_source: "unknown",
+ utm_medium: "op-instance",
+ utm_campaign: "newsletter-home-screen"
+ }
+ ),
+ {
+ aria: { label: t("homescreen.links.newsletter") },
target: "_blank",
- title: t("homescreen.links.newsletter"), rel: "noopener" }
+ title: t("homescreen.links.newsletter"),
+ rel: "noopener"
+ }
) %>
diff --git a/app/views/homescreen/index.html.erb b/app/views/homescreen/index.html.erb
index 285d42e9ca2..90bfc0324f6 100644
--- a/app/views/homescreen/index.html.erb
+++ b/app/views/homescreen/index.html.erb
@@ -51,11 +51,18 @@ See COPYRIGHT and LICENSE files for more details.
<% @homescreen[:links].each do |link| %>
- <% title = I18n.t(link[:label], scope: "homescreen.links") %>
-
- <%= op_icon(link[:icon]) %>
- <%= title %>
-
+ <% url = link[:url] || OpenProject::Static::Links.url_for(link[:url_key]) %>
+
+ <% if url %>
+ <% title = I18n.t(link[:label], scope: "homescreen.links") %>
+ <%= link_to url,
+ class: "homescreen--links--item",
+ target: "_blank",
+ aria_label: title do %>
+ <%= op_icon(link[:icon]) %>
+ <%= title %>
+ <% end %>
+ <% end %>
<% end %>
<% end %>
diff --git a/app/views/ldap_auth_sources/_form.html.erb b/app/views/ldap_auth_sources/_form.html.erb
index 8ffca274b2c..1abac258bed 100644
--- a/app/views/ldap_auth_sources/_form.html.erb
+++ b/app/views/ldap_auth_sources/_form.html.erb
@@ -73,7 +73,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= t "ldap_auth_sources.tls_mode.section_more_info_link_html",
- link: OpenProject::Static::Links[:ldap_encryption_documentation][:href] %>
+ link: OpenProject::Static::Links.url_for(:ldap_encryption_documentation) %>
diff --git a/config/initializers/homescreen.rb b/config/initializers/homescreen.rb
index 83db6d1c19d..9a5f20f9224 100644
--- a/config/initializers/homescreen.rb
+++ b/config/initializers/homescreen.rb
@@ -72,36 +72,31 @@ OpenProject::Static::Homescreen.manage :blocks do |blocks|
end
OpenProject::Static::Homescreen.manage :links do |links|
- link_hash = OpenProject::Static::Links.links
-
links.push(
{
label: :user_guides,
icon: "icon-context icon-rename",
- url: link_hash[:user_guides][:href]
+ url_key: :user_guides
},
{
label: :glossary,
icon: "icon-context icon-glossar",
- url: link_hash[:glossary][:href]
+ url_key: :glossary
},
{
label: :shortcuts,
icon: "icon-context icon-shortcuts",
- url: link_hash[:shortcuts][:href]
+ url_key: :shortcuts
},
{
label: :forums,
icon: "icon-context icon-forums",
- url: link_hash[:forums][:href]
+ url_key: :forums
+ },
+ {
+ label: :impressum,
+ icon: "icon-context icon-info1",
+ url_key: :impressum
}
)
-
- if impressum_link = link_hash[:impressum]
- links.push({
- label: :impressum,
- url: impressum_link[:href],
- icon: "icon-context icon-info1"
- })
- end
end
diff --git a/config/static_links.yml b/config/static_links.yml
index 1b43258b354..5cbae86fe57 100644
--- a/config/static_links.yml
+++ b/config/static_links.yml
@@ -81,6 +81,8 @@ get_started_videos:
glossary:
href: https://www.openproject.org/docs/glossary/
label: homescreen.links.glossary
+github:
+ href: https://github.com/opf/openproject
ical_docs:
href: https://www.openproject.org/docs/user-guide/calendar/#subscribe-to-a-calendar
installation_guides:
diff --git a/lib/open_project/journal_formatter/cause.rb b/lib/open_project/journal_formatter/cause.rb
index 6148cbf82f9..1728ebe119f 100644
--- a/lib/open_project/journal_formatter/cause.rb
+++ b/lib/open_project/journal_formatter/cause.rb
@@ -94,9 +94,9 @@ class OpenProject::JournalFormatter::Cause < JournalFormatter::Base
case feature
when "progress_calculation_adjusted_from_disabled_mode",
"progress_calculation_adjusted"
- { href: OpenProject::Static::Links.links[:blog_article_progress_changes][:href] }
+ { href: OpenProject::Static::Links.url_for(:blog_article_progress_changes) }
when "totals_removed_from_childless_work_packages"
- { href: OpenProject::Static::Links.links[:release_notes_14_0_1][:href] }
+ { href: OpenProject::Static::Links.url_for(:release_notes_14_0_1) }
else
{}
end
diff --git a/lib/open_project/static/links.rb b/lib/open_project/static/links.rb
index 47b4d4f60ea..07cff8577c6 100644
--- a/lib/open_project/static/links.rb
+++ b/lib/open_project/static/links.rb
@@ -37,22 +37,28 @@ module OpenProject
end
def help_link
- OpenProject::Configuration.force_help_link.presence || static_links[:user_guides]
+ OpenProject::Configuration.force_help_link.presence || static_links[:user_guides][:href]
end
- delegate :[], to: :links
-
- def links
- @links ||= static_links.merge(dynamic_links)
+ def cache_key
+ @cache_key ||= OpenProject::Cache::CacheKey.expand(links)
end
- def url_for(*items, localize_url: true)
- href = links.dig(*items, :href)
+ def label_for(*path)
+ key = links.dig(*path, :label)
+ return if key.nil?
- if localize_url && docs_url?(href)
- with_locale_param(href)
+ I18n.t(key)
+ end
+
+ def url_for(*path, localize_url: true, url_params: {})
+ href = links.dig(*path, :href)
+ return if href.nil?
+
+ if localize_url && website_link?(href)
+ url_with_query(href, **url_params, go_to_locale: I18n.locale)
else
- href
+ url_with_query(href, **url_params)
end
end
@@ -60,22 +66,28 @@ module OpenProject
@links.key? name
end
- def docs_url?(url)
- url&.start_with?(docs_url)
+ def website_link?(url)
+ url&.start_with?(website_url)
end
- def docs_url
- links[:openproject_docs][:href]
- end
-
- def with_locale_param(href)
- url = Addressable::URI.parse(href)
- url.query_values = (url.query_values || {}).merge(go_to_locale: I18n.locale)
- url.to_s
+ def website_url
+ links[:website][:href]
end
private
+ def links
+ @links ||= static_links.merge(dynamic_links)
+ end
+
+ def url_with_query(href, **params)
+ return href if params.empty?
+
+ url = Addressable::URI.parse(href)
+ url.query_values = (url.query_values || {}).merge(params)
+ url.to_s
+ end
+
def dynamic_links
dynamic = {
help: {
diff --git a/lib/redmine/menu_manager/top_menu/help_menu.rb b/lib/redmine/menu_manager/top_menu/help_menu.rb
index e1bb6557a01..4926bf8ea75 100644
--- a/lib/redmine/menu_manager/top_menu/help_menu.rb
+++ b/lib/redmine/menu_manager/top_menu/help_menu.rb
@@ -31,7 +31,7 @@
module Redmine::MenuManager::TopMenu::HelpMenu
def render_help_top_menu_node(item = help_menu_item)
cache_key = ["help_top_menu_node",
- OpenProject::Static::Links.links,
+ OpenProject::Static::Links.cache_key,
I18n.locale,
OpenProject::Static::Links.help_link,
EnterpriseToken.active?]
@@ -100,7 +100,11 @@ module Redmine::MenuManager::TopMenu::HelpMenu
unless EnterpriseToken.hide_banners? && EnterpriseToken.active?
menu_group.with_item(
**link_options_for(:upsell,
- href_suffix: "/?utm_source=unknown&utm_medium=op-instance&utm_campaign=ee-upsell-help-menu")
+ url_params: {
+ utm_source: "unknown",
+ utm_medium: "op-instance",
+ utm_campaign: "ee-upsell-help-menu"
+ })
)
end
menu_group.with_item(**link_options_for(:user_guides))
@@ -131,15 +135,27 @@ module Redmine::MenuManager::TopMenu::HelpMenu
menu_group.with_item(**link_options_for(:digital_accessibility))
menu_group.with_item(**link_options_for(
:website,
- href_suffix: "/?utm_source=unknown&utm_medium=op-instance&utm_campaign=website-help-menu"
+ url_params: {
+ utm_source: "unknown",
+ utm_medium: "op-instance",
+ utm_campaign: "website-help-menu"
+ }
))
menu_group.with_item(**link_options_for(
:security_alerts,
- href_suffix: "/?utm_source=unknown&utm_medium=op-instance&utm_campaign=security-help-menu"
+ url_params: {
+ utm_source: "unknown",
+ utm_medium: "op-instance",
+ utm_campaign: "security-help-menu"
+ }
))
menu_group.with_item(**link_options_for(
:newsletter,
- href_suffix: "/?utm_source=unknown&utm_medium=op-instance&utm_campaign=newsletter-help-menu"
+ url_params: {
+ utm_source: "unknown",
+ utm_medium: "op-instance",
+ utm_campaign: "newsletter-help-menu"
+ }
))
menu_group.with_item(**link_options_for(:blog))
menu_group.with_item(**link_options_for(:release_notes))
@@ -151,11 +167,11 @@ module Redmine::MenuManager::TopMenu::HelpMenu
end
def link_options_for(key, options = {})
- link = OpenProject::Static::Links.links[key]
- label = I18n.t(link[:label])
+ href = OpenProject::Static::Links.url_for(key, url_params: options[:url_params] || {})
+ label = OpenProject::Static::Links.label_for(key)
{
- href: "#{link[:href]}#{options[:href_suffix]}",
+ href: href,
label: label,
content_arguments: {
target: "_blank",
diff --git a/modules/storages/app/components/storages/admin/forms/general_info_form_component.rb b/modules/storages/app/components/storages/admin/forms/general_info_form_component.rb
index 5ef187c7fba..bf087c78006 100644
--- a/modules/storages/app/components/storages/admin/forms/general_info_form_component.rb
+++ b/modules/storages/app/components/storages/admin/forms/general_info_form_component.rb
@@ -87,7 +87,7 @@ module Storages::Admin::Forms
I18n.t(
"storages.instructions.#{provider_type}.provider_configuration",
application_link_text: application_link_text_for(
- ::OpenProject::Static::Links[:storage_docs][:"#{provider_type}_oauth_application"][:href],
+ ::OpenProject::Static::Links.url_for(:storage_docs, :"#{provider_type}_oauth_application"),
I18n.t("storages.instructions.#{provider_type}.application_link_text")
)
).html_safe
diff --git a/modules/storages/app/components/storages/admin/forms/oauth_client_form_component.rb b/modules/storages/app/components/storages/admin/forms/oauth_client_form_component.rb
index 71a8bc01232..c090e03e5dd 100644
--- a/modules/storages/app/components/storages/admin/forms/oauth_client_form_component.rb
+++ b/modules/storages/app/components/storages/admin/forms/oauth_client_form_component.rb
@@ -57,12 +57,12 @@ module Storages::Admin::Forms
end
def one_drive_integration_link(target: "_blank")
- href = ::OpenProject::Static::Links[:storage_docs][:one_drive_oauth_application][:href]
+ href = ::OpenProject::Static::Links.url_for(:storage_docs, :one_drive_oauth_application)
render(Primer::Beta::Link.new(href:, underline: true, target:)) { I18n.t("storages.instructions.one_drive.application_link_text") }
end
def sharepoint_integration_link(target: "_blank")
- href = ::OpenProject::Static::Links[:storage_docs][:sharepoint_oauth_application][:href]
+ href = ::OpenProject::Static::Links.url_for(:storage_docs, :sharepoint_oauth_application)
render(Primer::Beta::Link.new(href:, underline: true, target:)) { I18n.t("storages.instructions.sharepoint.application_link_text") }
end
diff --git a/modules/storages/app/components/storages/open_project_storage_modal_component.rb b/modules/storages/app/components/storages/open_project_storage_modal_component.rb
index 70266653e56..ec12fcad1fb 100644
--- a/modules/storages/app/components/storages/open_project_storage_modal_component.rb
+++ b/modules/storages/app/components/storages/open_project_storage_modal_component.rb
@@ -57,7 +57,7 @@ class Storages::OpenProjectStorageModalComponent < ViewComponent::Base
end
def subtitle_timeout_text
- href = OpenProject::Static::Links[:storage_docs][:health_status][:href]
+ href = OpenProject::Static::Links.url_for(:storage_docs, :health_status)
I18n.t(
"storages.open_project_storage_modal.timeout.subtitle",
storages_health_link: render(Primer::Beta::Link.new(href:, target: "_blank", underline: true)) do
diff --git a/modules/storages/app/forms/storages/admin/provider_drive_id_input_form.rb b/modules/storages/app/forms/storages/admin/provider_drive_id_input_form.rb
index 9f9b46c21fe..9a46f35a297 100644
--- a/modules/storages/app/forms/storages/admin/provider_drive_id_input_form.rb
+++ b/modules/storages/app/forms/storages/admin/provider_drive_id_input_form.rb
@@ -44,7 +44,7 @@ module Storages::Admin
private
def caption
- href = ::OpenProject::Static::Links[:storage_docs][:one_drive_drive_id_guide][:href]
+ href = ::OpenProject::Static::Links.url_for(:storage_docs, :one_drive_drive_id_guide)
I18n.t("storages.instructions.one_drive.drive_id",
drive_id_link_text: render(Primer::Beta::Link.new(href:, underline: true, target: "_blank")) do
I18n.t("storages.instructions.one_drive.documentation_link_text")
diff --git a/modules/storages/app/forms/storages/admin/provider_tenant_id_input_form.rb b/modules/storages/app/forms/storages/admin/provider_tenant_id_input_form.rb
index 82e383eef7b..8d3f09e88e4 100644
--- a/modules/storages/app/forms/storages/admin/provider_tenant_id_input_form.rb
+++ b/modules/storages/app/forms/storages/admin/provider_tenant_id_input_form.rb
@@ -45,7 +45,7 @@ module Storages::Admin
private
def caption
- href = ::OpenProject::Static::Links[:storage_docs][:one_drive_oauth_application][:href]
+ href = ::OpenProject::Static::Links.url_for(:storage_docs, :one_drive_oauth_application)
I18n.t("storages.instructions.one_drive.tenant_id",
application_link_text: render(Primer::Beta::Link.new(href:, underline: true, target: "_blank")) do
I18n.t("storages.instructions.one_drive.application_link_text")
diff --git a/modules/storages/app/views/storages/admin/storages/new.html.erb b/modules/storages/app/views/storages/admin/storages/new.html.erb
index 0c1a6243ac7..9178b323b94 100644
--- a/modules/storages/app/views/storages/admin/storages/new.html.erb
+++ b/modules/storages/app/views/storages/admin/storages/new.html.erb
@@ -18,7 +18,7 @@
<% header.with_description(test_selector: 'storage-new-page-header--description') do %>
<%=
t("storages.instructions.new_storage",
- provider_link: ::OpenProject::Static::Links[:storage_docs][:"#{@storage}_setup"][:href].html_safe,
+ provider_link: ::OpenProject::Static::Links.url_for(:storage_docs, :"#{@storage}_setup"),
provider_name: I18n.t("storages.provider_types.#{@storage}.name")
).html_safe
%>
diff --git a/modules/storages/app/views/storages/storages_mailer/_health_status_notification.html.erb b/modules/storages/app/views/storages/storages_mailer/_health_status_notification.html.erb
index c8f76f7c9a1..6d8d499ca97 100644
--- a/modules/storages/app/views/storages/storages_mailer/_health_status_notification.html.erb
+++ b/modules/storages/app/views/storages/storages_mailer/_health_status_notification.html.erb
@@ -87,7 +87,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= I18n.t("mail.storages.health.unhealthy.troubleshooting.text") %>
- <%= I18n.t("mail.storages.health.unhealthy.troubleshooting.link_text") %>.
+ <%= I18n.t("mail.storages.health.unhealthy.troubleshooting.link_text") %>.
|
<%= placeholder_cell("12px", vertical: true) %>
diff --git a/modules/two_factor_authentication/app/views/two_factor_authentication/settings.html.erb b/modules/two_factor_authentication/app/views/two_factor_authentication/settings.html.erb
index b586dfc047e..97db0435e12 100644
--- a/modules/two_factor_authentication/app/views/two_factor_authentication/settings.html.erb
+++ b/modules/two_factor_authentication/app/views/two_factor_authentication/settings.html.erb
@@ -22,9 +22,9 @@
<%= t("two_factor_authentication.settings.text_configuration") %>
- <% configuration_link = OpenProject::Static::Links.links.fetch :configuration_guide %>
+ <% configuration_link = OpenProject::Static::Links.url_for :configuration_guide %>
<%= link_to t("two_factor_authentication.settings.text_configuration_guide"),
- configuration_link[:href],
+ configuration_link,
target: "_blank" %>
<%= render(AttributeGroups::AttributeGroupComponent.new) do |component|
diff --git a/modules/webhooks/app/views/webhooks/outgoing/admin/_form.html.erb b/modules/webhooks/app/views/webhooks/outgoing/admin/_form.html.erb
index 0058b573046..99b507d8202 100644
--- a/modules/webhooks/app/views/webhooks/outgoing/admin/_form.html.erb
+++ b/modules/webhooks/app/views/webhooks/outgoing/admin/_form.html.erb
@@ -2,7 +2,7 @@
<%= t "webhooks.outgoing.form.introduction" %>
- <%= link_to t("webhooks.outgoing.form.apiv3_doc_url"), OpenProject::Static::Links.links[:api_docs][:href] %>
+ <%= link_to t("webhooks.outgoing.form.apiv3_doc_url"), OpenProject::Static::Links.url_for(:api_docs) %>
diff --git a/spec/components/enterprise_edition/banner_component_spec.rb b/spec/components/enterprise_edition/banner_component_spec.rb
index 3c66f44d5d3..25383683334 100644
--- a/spec/components/enterprise_edition/banner_component_spec.rb
+++ b/spec/components/enterprise_edition/banner_component_spec.rb
@@ -257,7 +257,7 @@ RSpec.describe EnterpriseEdition::BannerComponent, type: :component do
expect(component).to have_text(expected_title)
expect(component).to have_text(expected_description)
- expect(component).to have_link("More information", href: "https://www.openproject.org/enterprise-edition")
+ expect(component).to have_link("More information", href: "https://www.openproject.org/enterprise-edition?go_to_locale=mo")
end
end
diff --git a/spec/components/work_packages/progress/shared_modal_examples.rb b/spec/components/work_packages/progress/shared_modal_examples.rb
index 398c3ebd155..bd81d07658b 100644
--- a/spec/components/work_packages/progress/shared_modal_examples.rb
+++ b/spec/components/work_packages/progress/shared_modal_examples.rb
@@ -74,7 +74,7 @@ RSpec.shared_examples_for "progress modal help links" do
expect(page)
.to have_link("Learn more",
- href: OpenProject::Static::Links.links[:progress_tracking_docs][:href])
+ href: OpenProject::Static::Links.url_for(:progress_tracking_docs))
end
end
end
diff --git a/spec/lib/open_project/journal_formatter/cause_spec.rb b/spec/lib/open_project/journal_formatter/cause_spec.rb
index fdf249ad827..059471fd9ad 100644
--- a/spec/lib/open_project/journal_formatter/cause_spec.rb
+++ b/spec/lib/open_project/journal_formatter/cause_spec.rb
@@ -450,7 +450,7 @@ RSpec.describe OpenProject::JournalFormatter::Cause do
end
it do
- href = OpenProject::Static::Links.links[:blog_article_progress_changes][:href]
+ href = OpenProject::Static::Links.url_for(:blog_article_progress_changes)
expect(cause).to render_html_variant(
"
OpenProject system update: Progress calculation automatically " \
"
set to work-based mode and adjusted with version update."
@@ -474,7 +474,7 @@ RSpec.describe OpenProject::JournalFormatter::Cause do
end
it do
- href = OpenProject::Static::Links.links[:blog_article_progress_changes][:href]
+ href = OpenProject::Static::Links.url_for(:blog_article_progress_changes)
expect(cause).to render_html_variant(
"
OpenProject system update: Progress calculation automatically " \
"
adjusted with version update."
@@ -509,7 +509,7 @@ RSpec.describe OpenProject::JournalFormatter::Cause do
end
it do
- href = OpenProject::Static::Links.links[:release_notes_14_0_1][:href]
+ href = OpenProject::Static::Links.url_for(:release_notes_14_0_1)
expect(cause).to render_html_variant(
"
OpenProject system update: Work and progress totals " \
"automatically removed for non-parent work packages with " \
diff --git a/spec/lib/open_project/static/links_spec.rb b/spec/lib/open_project/static/links_spec.rb
index 6b35e1aedd8..86f4825c568 100644
--- a/spec/lib/open_project/static/links_spec.rb
+++ b/spec/lib/open_project/static/links_spec.rb
@@ -61,18 +61,123 @@ RSpec.describe OpenProject::Static::Links do
end
end
- context "with non-docs URLs" do
+ context "with website URL" do
let(:args) { %i[website] }
- it "does not add locale parameter to non-docs URLs" do
- expect(subject).to eq("https://www.openproject.org")
+ it "adds locale parameter to website URL" do
+ expect(subject).to eq("https://www.openproject.org?go_to_locale=en")
+ end
+ end
+
+ context "with other URLs" do
+ let(:args) { %i[github] }
+
+ it "does not add a parameter" do
+ expect(subject).to eq("https://github.com/opf/openproject")
expect(subject).not_to include("go_to_locale=")
end
end
+
+ context "with additional URL parameters" do
+ let(:args) { %i[website] }
+
+ it "adds custom URL parameters" do
+ result = described_class.url_for(*args, url_params: { utm_source: "test", utm_medium: "spec" })
+ expect(result).to include("utm_source=test")
+ expect(result).to include("utm_medium=spec")
+ end
+ end
+
+ context "with non-existent path" do
+ let(:args) { %i[non_existent_key] }
+
+ it "returns nil for non-existent paths" do
+ expect(subject).to be_nil
+ end
+ end
+
+ context "with localize_url disabled" do
+ let(:args) { %i[enterprise_features board_view] }
+
+ it "does not add locale parameter when localize_url is false" do
+ result = described_class.url_for(*args, localize_url: false)
+ expect(result).not_to include("go_to_locale=")
+ expect(result).to eq("https://www.openproject.org/docs/user-guide/agile-boards/#action-boards-enterprise-add-on")
+ end
+ end
end
- describe ".docs_url?" do
- subject { described_class.docs_url?(url) }
+ describe ".label_for" do
+ subject { described_class.label_for(*args) }
+
+ let(:args) { %i[website] }
+
+ it "returns the translated label for the given path" do
+ expect(subject).to eq(I18n.t("label_openproject_website"))
+ end
+
+ context "with single key" do
+ let(:args) { %i[shortcuts] }
+
+ it "returns the translated label for a single key" do
+ expect(subject).to eq(I18n.t("homescreen.links.shortcuts"))
+ end
+ end
+
+ context "with non-existent path" do
+ let(:args) { %i[non_existent_key] }
+
+ it "returns nil for non-existent paths" do
+ expect(subject).to be_nil
+ end
+ end
+ end
+
+ describe ".cache_key" do
+ subject { described_class.cache_key }
+
+ it "returns a cache key based on the links" do
+ expect(subject).to be_a(String)
+ expect(subject).not_to be_empty
+ end
+
+ it "returns the same key for multiple calls" do
+ first_call = described_class.cache_key
+ second_call = described_class.cache_key
+ expect(first_call).to eq(second_call)
+ end
+ end
+
+ describe ".has?" do
+ subject { described_class.has?(key) }
+
+ context "with existing key" do
+ let(:key) { :website }
+
+ it "returns true for existing keys" do
+ expect(subject).to be true
+ end
+ end
+
+ context "with non-existing key" do
+ let(:key) { :non_existent_key }
+
+ it "returns false for non-existing keys" do
+ expect(subject).to be false
+ end
+ end
+ end
+
+ describe ".website_url" do
+ subject { described_class.website_url }
+
+ it "returns the website URL" do
+ expect(subject).to eq("https://www.openproject.org")
+ end
+ end
+
+ describe ".website_link?" do
+ subject { described_class.website_link?(url) }
context "with docs URLs" do
let(:url) { "https://www.openproject.org/docs/user-guide/agile-boards/" }
@@ -83,56 +188,67 @@ RSpec.describe OpenProject::Static::Links do
end
context "with non-docs URLs" do
- let(:url) { "https://www.openproject.org/enterprise-edition" }
+ let(:url) { "https://foo.example.com" }
it "returns false for URLs that do not start with the docs base URL" do
expect(subject).to be false
end
end
- end
- describe ".with_locale_param" do
- subject { described_class.with_locale_param(href) }
+ context "with nil URL" do
+ let(:url) { nil }
- let(:href) { "https://www.openproject.org/docs/system-admin-guide/authentication/openid-providers/" }
-
- before do
- allow(I18n).to receive(:locale).and_return(:en)
- end
-
- it "adds go_to_locale parameter to the URL" do
- expect(subject).to include("go_to_locale=en")
- end
-
- it "preserves the original URL structure" do
- expect(subject).to start_with(href)
- end
-
- context "with URL that already has query parameters" do
- let(:href) { "https://www.openproject.org/docs/user-guide/agile-boards/?section=boards" }
-
- it "adds go_to_locale parameter while preserving existing parameters" do
- expect(subject).to include("section=boards")
- expect(subject).to include("go_to_locale=en")
+ it "returns false for nil URLs" do
+ expect(subject).to be_falsy
end
end
+ end
- context "with different locale" do
+ describe ".help_link_overridden?" do
+ subject { described_class.help_link_overridden? }
+
+ context "when help link is not overridden" do
before do
- allow(I18n).to receive(:locale).and_return(:de)
+ allow(OpenProject::Configuration).to receive(:force_help_link).and_return(nil)
end
- it "uses the current I18n locale" do
- expect(subject).to include("go_to_locale=de")
+ it "returns false" do
+ expect(subject).to be false
+ end
+ end
+
+ context "when help link is overridden" do
+ before do
+ allow(OpenProject::Configuration).to receive(:force_help_link).and_return("https://custom.help.com")
+ end
+
+ it "returns true" do
+ expect(subject).to be true
end
end
end
- describe ".docs_url" do
- subject { described_class.docs_url }
+ describe ".help_link" do
+ subject { described_class.help_link }
- it "returns the base docs URL" do
- expect(subject).to eq("https://www.openproject.org/docs/")
+ context "when help link is not overridden" do
+ before do
+ allow(OpenProject::Configuration).to receive(:force_help_link).and_return(nil)
+ end
+
+ it "returns the default user guides link" do
+ expect(subject).to eq("https://www.openproject.org/docs/user-guide/")
+ end
+ end
+
+ context "when help link is overridden" do
+ before do
+ allow(OpenProject::Configuration).to receive(:force_help_link).and_return("https://custom.help.com")
+ end
+
+ it "returns the overridden help link" do
+ expect(subject).to eq("https://custom.help.com")
+ end
end
end
end
diff --git a/spec/support/support_links.rb b/spec/support/support_links.rb
index f3715449984..4423716c747 100644
--- a/spec/support/support_links.rb
+++ b/spec/support/support_links.rb
@@ -30,7 +30,7 @@
# rubocop:disable RSpec/ContextWording
RSpec.shared_context "support links" do
- let(:support_link_as_community) { "https://www.openproject.org/pricing/#support" }
- let(:support_link_as_enterprise) { "https://www.openproject.org/docs/enterprise-guide/support/" }
+ let(:support_link_as_community) { "https://www.openproject.org/pricing/?go_to_locale=en#support" }
+ let(:support_link_as_enterprise) { "https://www.openproject.org/docs/enterprise-guide/support/?go_to_locale=en" }
end
# rubocop:enable RSpec/ContextWording