From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- sfx2/source/appl/sfxhelp.cxx | 1406 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1406 insertions(+) create mode 100644 sfx2/source/appl/sfxhelp.cxx (limited to 'sfx2/source/appl/sfxhelp.cxx') diff --git a/sfx2/source/appl/sfxhelp.cxx b/sfx2/source/appl/sfxhelp.cxx new file mode 100644 index 000000000..8295e0392 --- /dev/null +++ b/sfx2/source/appl/sfxhelp.cxx @@ -0,0 +1,1406 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include +#include +#ifdef MACOSX +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "newhelp.hxx" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::lang; + +namespace { + +class NoHelpErrorBox +{ +private: + std::unique_ptr m_xErrBox; +public: + DECL_STATIC_LINK(NoHelpErrorBox, HelpRequestHdl, weld::Widget&, bool); +public: + explicit NoHelpErrorBox(weld::Widget* pParent) + : m_xErrBox(Application::CreateMessageDialog(pParent, VclMessageType::Error, VclButtonsType::Ok, + SfxResId(RID_STR_HLPFILENOTEXIST))) + { + // Error message: "No help available" + m_xErrBox->connect_help(LINK(nullptr, NoHelpErrorBox, HelpRequestHdl)); + } + void run() + { + m_xErrBox->run(); + } +}; + +} + +IMPL_STATIC_LINK_NOARG(NoHelpErrorBox, HelpRequestHdl, weld::Widget&, bool) +{ + // do nothing, because no help available + return false; +} + +static OUString const & HelpLocaleString(); + +namespace { + +/// Root path of the help. +OUString const & getHelpRootURL() +{ + static OUString const s_instURL = []() + { + OUString tmp = officecfg::Office::Common::Path::Current::Help::get(); + if (tmp.isEmpty()) + { + // try to determine path from default + tmp = "$(instpath)/" LIBO_SHARE_HELP_FOLDER; + } + + // replace anything like $(instpath); + SvtPathOptions aOptions; + tmp = aOptions.SubstituteVariable(tmp); + + OUString url; + if (osl::FileBase::getFileURLFromSystemPath(tmp, url) == osl::FileBase::E_None) + tmp = url; + return tmp; + }(); + return s_instURL; +} + +bool impl_checkHelpLocalePath(OUString const & rpPath) +{ + osl::DirectoryItem directoryItem; + bool bOK = false; + + osl::FileStatus fileStatus(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL | osl_FileStatus_Mask_FileName); + if (osl::DirectoryItem::get(rpPath, directoryItem) == osl::FileBase::E_None && + directoryItem.getFileStatus(fileStatus) == osl::FileBase::E_None && + fileStatus.isDirectory()) + { + bOK = true; + } + return bOK; +} + +/// Check for built-in help +/// Check if help//err.html file exist +bool impl_hasHelpInstalled() +{ + if (comphelper::LibreOfficeKit::isActive()) + return false; + + // detect installed locale + static OUString const aLocaleStr = HelpLocaleString(); + + OUString helpRootURL = getHelpRootURL() + "/" + aLocaleStr + "/err.html"; + bool bOK = false; + osl::DirectoryItem directoryItem; + if(osl::DirectoryItem::get(helpRootURL, directoryItem) == osl::FileBase::E_None){ + bOK=true; + } + + SAL_INFO( "sfx.appl", "Checking old help installed " << bOK); + return bOK; +} + +/// Check for html built-in help +/// Check if help/lang/text folder exist. Only html has it. +bool impl_hasHTMLHelpInstalled() +{ + if (comphelper::LibreOfficeKit::isActive()) + return false; + + // detect installed locale + static OUString const aLocaleStr = HelpLocaleString(); + + OUString helpRootURL = getHelpRootURL() + "/" + aLocaleStr + "/text"; + bool bOK = impl_checkHelpLocalePath( helpRootURL ); + SAL_INFO( "sfx.appl", "Checking new help (html) installed " << bOK); + return bOK; +} + +} // namespace + +/// Return the locale we prefer for displaying help +static OUString const & HelpLocaleString() +{ + if (comphelper::LibreOfficeKit::isActive()) + return comphelper::LibreOfficeKit::getLanguageTag().getBcp47(); + + static OUString aLocaleStr; + if (!aLocaleStr.isEmpty()) + return aLocaleStr; + + static const OUStringLiteral aEnglish(u"en-US"); + // detect installed locale + aLocaleStr = utl::ConfigManager::getUILocale(); + + if ( aLocaleStr.isEmpty() ) + { + aLocaleStr = aEnglish; + return aLocaleStr; + } + + // get fall-back language (country) + OUString sLang = aLocaleStr; + sal_Int32 nSepPos = sLang.indexOf( '-' ); + if (nSepPos != -1) + { + sLang = sLang.copy( 0, nSepPos ); + } + OUString sHelpPath(""); + sHelpPath = getHelpRootURL() + "/" + utl::ConfigManager::getProductVersion() + "/" + aLocaleStr; + if (impl_checkHelpLocalePath(sHelpPath)) + { + return aLocaleStr; + } + sHelpPath = getHelpRootURL() + "/" + utl::ConfigManager::getProductVersion() + "/" + sLang; + if (impl_checkHelpLocalePath(sHelpPath)) + { + aLocaleStr = sLang; + return aLocaleStr; + } + sHelpPath = getHelpRootURL() + "/" + aLocaleStr; + if (impl_checkHelpLocalePath(sHelpPath)) + { + return aLocaleStr; + } + sHelpPath = getHelpRootURL() + "/" + sLang; + if (impl_checkHelpLocalePath(sHelpPath)) + { + aLocaleStr = sLang; + return aLocaleStr; + } + sHelpPath = getHelpRootURL() + "/" + utl::ConfigManager::getProductVersion() + "/" + aEnglish; + if (impl_checkHelpLocalePath(sHelpPath)) + { + aLocaleStr = aEnglish; + return aLocaleStr; + } + sHelpPath = getHelpRootURL() + "/" + aEnglish; + if (impl_checkHelpLocalePath(sHelpPath)) + { + aLocaleStr = aEnglish; + return aLocaleStr; + } + return aLocaleStr; +} + + + +void AppendConfigToken( OUStringBuffer& rURL, bool bQuestionMark ) +{ + OUString aLocaleStr = HelpLocaleString(); + + // query part exists? + if ( bQuestionMark ) + // no, so start with '?' + rURL.append('?'); + else + // yes, so only append with '&' + rURL.append('&'); + + // set parameters + rURL.append("Language="); + rURL.append(aLocaleStr); + rURL.append("&System="); + rURL.append(officecfg::Office::Common::Help::System::get()); + rURL.append("&Version="); + rURL.append(utl::ConfigManager::getProductVersion()); +} + +static bool GetHelpAnchor_Impl( std::u16string_view _rURL, OUString& _rAnchor ) +{ + bool bRet = false; + + try + { + ::ucbhelper::Content aCnt( INetURLObject( _rURL ).GetMainURL( INetURLObject::DecodeMechanism::NONE ), + Reference< css::ucb::XCommandEnvironment >(), + comphelper::getProcessComponentContext() ); + OUString sAnchor; + if ( aCnt.getPropertyValue("AnchorName") >>= sAnchor ) + { + + if ( !sAnchor.isEmpty() ) + { + _rAnchor = sAnchor; + bRet = true; + } + } + else + { + SAL_WARN( "sfx.appl", "Property 'AnchorName' is missing" ); + } + } + catch (const css::uno::Exception&) + { + } + + return bRet; +} + +namespace { + +class SfxHelp_Impl +{ +public: + static OUString GetHelpText( const OUString& aCommandURL, const OUString& rModule ); +}; + +} + +OUString SfxHelp_Impl::GetHelpText( const OUString& aCommandURL, const OUString& rModule ) +{ + // create help url + OUStringBuffer aHelpURL( SfxHelp::CreateHelpURL( aCommandURL, rModule ) ); + // added 'active' parameter + sal_Int32 nIndex = aHelpURL.lastIndexOf( '#' ); + if ( nIndex < 0 ) + nIndex = aHelpURL.getLength(); + aHelpURL.insert( nIndex, "&Active=true" ); + // load help string + return SfxContentHelper::GetActiveHelpString( aHelpURL.makeStringAndClear() ); +} + +SfxHelp::SfxHelp() + : bIsDebug(false) + , bLaunchingHelp(false) +{ + // read the environment variable "HELP_DEBUG" + // if it's set, you will see debug output on active help + OUString sHelpDebug; + OUString sEnvVarName( "HELP_DEBUG" ); + osl_getEnvironment( sEnvVarName.pData, &sHelpDebug.pData ); + bIsDebug = !sHelpDebug.isEmpty(); +} + +SfxHelp::~SfxHelp() +{ +} + +static OUString getDefaultModule_Impl() +{ + OUString sDefaultModule; + SvtModuleOptions aModOpt; + if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) ) + sDefaultModule = "swriter"; + else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) ) + sDefaultModule = "scalc"; + else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) ) + sDefaultModule = "simpress"; + else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) ) + sDefaultModule = "sdraw"; + else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::MATH ) ) + sDefaultModule = "smath"; + else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::CHART ) ) + sDefaultModule = "schart"; + else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::BASIC ) ) + sDefaultModule = "sbasic"; + else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::DATABASE ) ) + sDefaultModule = "sdatabase"; + else + { + SAL_WARN( "sfx.appl", "getDefaultModule_Impl(): no module installed" ); + } + return sDefaultModule; +} + +static OUString getCurrentModuleIdentifier_Impl() +{ + OUString sIdentifier; + Reference < XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + Reference < XModuleManager2 > xModuleManager = ModuleManager::create(xContext); + Reference < XDesktop2 > xDesktop = Desktop::create(xContext); + Reference < XFrame > xCurrentFrame = xDesktop->getCurrentFrame(); + + if ( xCurrentFrame.is() ) + { + try + { + sIdentifier = xModuleManager->identify( xCurrentFrame ); + } + catch (const css::frame::UnknownModuleException&) + { + SAL_INFO( "sfx.appl", "SfxHelp::getCurrentModuleIdentifier_Impl(): unknown module (help in help?)" ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelp::getCurrentModuleIdentifier_Impl(): exception of XModuleManager::identify()" ); + } + } + + return sIdentifier; +} + +namespace +{ + OUString MapModuleIdentifier(const OUString &rFactoryShortName) + { + OUString aFactoryShortName(rFactoryShortName); + + // Map some module identifiers to their "real" help module string. + if ( aFactoryShortName == "chart2" ) + aFactoryShortName = "schart" ; + else if ( aFactoryShortName == "BasicIDE" ) + aFactoryShortName = "sbasic"; + else if ( aFactoryShortName == "sweb" + || aFactoryShortName == "sglobal" + || aFactoryShortName == "swxform" ) + aFactoryShortName = "swriter" ; + else if ( aFactoryShortName == "dbquery" + || aFactoryShortName == "dbbrowser" + || aFactoryShortName == "dbrelation" + || aFactoryShortName == "dbtable" + || aFactoryShortName == "dbapp" + || aFactoryShortName == "dbreport" + || aFactoryShortName == "dbtdata" + || aFactoryShortName == "swreport" + || aFactoryShortName == "swform" ) + aFactoryShortName = "sdatabase"; + else if ( aFactoryShortName == "sbibliography" + || aFactoryShortName == "sabpilot" + || aFactoryShortName == "scanner" + || aFactoryShortName == "spropctrlr" + || aFactoryShortName == "StartModule" ) + aFactoryShortName.clear(); + + return aFactoryShortName; + } +} + +OUString SfxHelp::GetHelpModuleName_Impl(std::u16string_view rHelpID) +{ + OUString aFactoryShortName; + + //rhbz#1438876 detect preferred module for this help id, e.g. csv dialog + //for calc import before any toplevel is created and so context is + //otherwise unknown. Cosmetic, same help is shown in any case because its + //in the shared section, but title bar would state "Writer" when context is + //expected to be "Calc" + std::u16string_view sRemainder; + if (o3tl::starts_with(rHelpID, u"modules/", &sRemainder)) + { + std::size_t nEndModule = sRemainder.find(u'/'); + aFactoryShortName = nEndModule != std::u16string_view::npos + ? sRemainder.substr(0, nEndModule) : sRemainder; + } + + if (aFactoryShortName.isEmpty()) + { + OUString aModuleIdentifier = getCurrentModuleIdentifier_Impl(); + if (!aModuleIdentifier.isEmpty()) + { + try + { + Reference < XModuleManager2 > xModuleManager( + ModuleManager::create(::comphelper::getProcessComponentContext()) ); + Sequence< PropertyValue > lProps; + xModuleManager->getByName( aModuleIdentifier ) >>= lProps; + auto pProp = std::find_if(std::cbegin(lProps), std::cend(lProps), + [](const PropertyValue& rProp) { return rProp.Name == "ooSetupFactoryShortName"; }); + if (pProp != std::cend(lProps)) + pProp->Value >>= aFactoryShortName; + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelp::GetHelpModuleName_Impl()" ); + } + } + } + + if (!aFactoryShortName.isEmpty()) + aFactoryShortName = MapModuleIdentifier(aFactoryShortName); + if (aFactoryShortName.isEmpty()) + aFactoryShortName = getDefaultModule_Impl(); + + return aFactoryShortName; +} + +OUString SfxHelp::CreateHelpURL_Impl( const OUString& aCommandURL, const OUString& rModuleName ) +{ + // build up the help URL + OUStringBuffer aHelpURL("vnd.sun.star.help://"); + bool bHasAnchor = false; + OUString aAnchor; + + OUString aModuleName( rModuleName ); + if (aModuleName.isEmpty()) + aModuleName = getDefaultModule_Impl(); + + aHelpURL.append(aModuleName); + + if ( aCommandURL.isEmpty() ) + aHelpURL.append("/start"); + else + { + aHelpURL.append('/'); + aHelpURL.append(rtl::Uri::encode(aCommandURL, + rtl_UriCharClassRelSegment, + rtl_UriEncodeKeepEscapes, + RTL_TEXTENCODING_UTF8)); + + OUStringBuffer aTempURL = aHelpURL; + AppendConfigToken( aTempURL, true ); + bHasAnchor = GetHelpAnchor_Impl(aTempURL.makeStringAndClear(), aAnchor); + } + + AppendConfigToken( aHelpURL, true ); + + if ( bHasAnchor ) + { + aHelpURL.append('#'); + aHelpURL.append(aAnchor); + } + + return aHelpURL.makeStringAndClear(); +} + +static SfxHelpWindow_Impl* impl_createHelp(Reference< XFrame2 >& rHelpTask , + Reference< XFrame >& rHelpContent) +{ + Reference < XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() ); + + // otherwise - create new help task + Reference< XFrame2 > xHelpTask( + xDesktop->findFrame( "OFFICE_HELP_TASK", FrameSearchFlag::TASKS | FrameSearchFlag::CREATE), + UNO_QUERY); + if (!xHelpTask.is()) + return nullptr; + + // create all internal windows and sub frames ... + Reference< css::awt::XWindow > xParentWindow = xHelpTask->getContainerWindow(); + VclPtr pParentWindow = VCLUnoHelper::GetWindow( xParentWindow ); + VclPtrInstance pHelpWindow( xHelpTask, pParentWindow ); + Reference< css::awt::XWindow > xHelpWindow = VCLUnoHelper::GetInterface( pHelpWindow ); + + Reference< XFrame > xHelpContent; + if (xHelpTask->setComponent( xHelpWindow, Reference< XController >() )) + { + // Customize UI ... + xHelpTask->setName("OFFICE_HELP_TASK"); + + Reference< XPropertySet > xProps(xHelpTask, UNO_QUERY); + if (xProps.is()) + xProps->setPropertyValue( + "Title", + Any(SfxResId(STR_HELP_WINDOW_TITLE))); + + pHelpWindow->setContainerWindow( xParentWindow ); + xParentWindow->setVisible(true); + xHelpWindow->setVisible(true); + + // This sub frame is created internally (if we called new SfxHelpWindow_Impl() ...) + // It should exist :-) + xHelpContent = xHelpTask->findFrame("OFFICE_HELP", FrameSearchFlag::CHILDREN); + } + + if (!xHelpContent.is()) + { + pHelpWindow.disposeAndClear(); + return nullptr; + } + + xHelpContent->setName("OFFICE_HELP"); + + rHelpTask = xHelpTask; + rHelpContent = xHelpContent; + return pHelpWindow; +} + +OUString SfxHelp::GetHelpText( const OUString& aCommandURL, const vcl::Window* pWindow ) +{ + OUString sModuleName = GetHelpModuleName_Impl(aCommandURL); + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommandURL, getCurrentModuleIdentifier_Impl()); + OUString sRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties); + OUString sHelpText = SfxHelp_Impl::GetHelpText( sRealCommand.isEmpty() ? aCommandURL : sRealCommand, sModuleName ); + + OString aNewHelpId; + + if (pWindow && sHelpText.isEmpty()) + { + // no help text found -> try with parent help id. + vcl::Window* pParent = pWindow->GetParent(); + while ( pParent ) + { + aNewHelpId = pParent->GetHelpId(); + sHelpText = SfxHelp_Impl::GetHelpText( OStringToOUString(aNewHelpId, RTL_TEXTENCODING_UTF8), sModuleName ); + if (!sHelpText.isEmpty()) + pParent = nullptr; + else + pParent = pParent->GetParent(); + } + + if (bIsDebug && sHelpText.isEmpty()) + aNewHelpId.clear(); + } + + // add some debug information? + if ( bIsDebug ) + { + sHelpText += "\n-------------\n" + + sModuleName + ": " + aCommandURL; + if ( !aNewHelpId.isEmpty() ) + { + sHelpText += " - " + + OStringToOUString(aNewHelpId, RTL_TEXTENCODING_UTF8); + } + } + + return sHelpText; +} + +OUString SfxHelp::GetHelpText(const OUString& aCommandURL, const weld::Widget* pWidget) +{ + OUString sModuleName = GetHelpModuleName_Impl(aCommandURL); + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommandURL, getCurrentModuleIdentifier_Impl()); + OUString sRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties); + OUString sHelpText = SfxHelp_Impl::GetHelpText( sRealCommand.isEmpty() ? aCommandURL : sRealCommand, sModuleName ); + + OString aNewHelpId; + + if (pWidget && sHelpText.isEmpty()) + { + // no help text found -> try with parent help id. + std::unique_ptr xParent(pWidget->weld_parent()); + while (xParent) + { + aNewHelpId = xParent->get_help_id(); + sHelpText = SfxHelp_Impl::GetHelpText( OStringToOUString(aNewHelpId, RTL_TEXTENCODING_UTF8), sModuleName ); + if (!sHelpText.isEmpty()) + xParent.reset(); + else + xParent = xParent->weld_parent(); + } + + if (bIsDebug && sHelpText.isEmpty()) + aNewHelpId.clear(); + } + + // add some debug information? + if ( bIsDebug ) + { + sHelpText += "\n-------------\n" + + sModuleName + ": " + aCommandURL; + if ( !aNewHelpId.isEmpty() ) + { + sHelpText += " - " + + OStringToOUString(aNewHelpId, RTL_TEXTENCODING_UTF8); + } + } + + return sHelpText; +} + +OUString SfxHelp::GetURLHelpText(std::u16string_view aURL) +{ + bool bCtrlClickHlink = SvtSecurityOptions::IsOptionSet(SvtSecurityOptions::EOption::CtrlClickHyperlink); + + // "ctrl-click to follow link:" for not MacOS + // "⌘-click to follow link:" for MacOs + vcl::KeyCode aCode(KEY_SPACE); + vcl::KeyCode aModifiedCode(KEY_SPACE, KEY_MOD1); + OUString aModStr(aModifiedCode.GetName()); + aModStr = aModStr.replaceFirst(aCode.GetName(), ""); + aModStr = aModStr.replaceAll("+", ""); + OUString aHelpStr + = bCtrlClickHlink ? SfxResId(STR_CTRLCLICKHYPERLINK) : SfxResId(STR_CLICKHYPERLINK); + aHelpStr = aHelpStr.replaceFirst("%{key}", aModStr); + aHelpStr = aHelpStr.replaceFirst("%{link}", aURL); + return aHelpStr; +} + +void SfxHelp::SearchKeyword( const OUString& rKeyword ) +{ + Start_Impl(OUString(), static_cast(nullptr), rKeyword); +} + +bool SfxHelp::Start( const OUString& rURL, const vcl::Window* pWindow ) +{ + if (bLaunchingHelp) + return true; + bLaunchingHelp = true; + bool bRet = Start_Impl( rURL, pWindow ); + bLaunchingHelp = false; + return bRet; +} + +bool SfxHelp::Start(const OUString& rURL, weld::Widget* pWidget) +{ + if (bLaunchingHelp) + return true; + bLaunchingHelp = true; + bool bRet = Start_Impl(rURL, pWidget, OUString()); + bLaunchingHelp = false; + return bRet; +} + +/// Redirect the vnd.sun.star.help:// urls to http://help.libreoffice.org +static bool impl_showOnlineHelp(const OUString& rURL, weld::Widget* pDialogParent) +{ + static constexpr OUStringLiteral aInternal(u"vnd.sun.star.help://"); + if ( rURL.getLength() <= aInternal.getLength() || !rURL.startsWith(aInternal) ) + return false; + + OUString aHelpLink = officecfg::Office::Common::Help::HelpRootURL::get(); + OUString aTarget = OUString::Concat("Target=") + rURL.subView(aInternal.getLength()); + aTarget = aTarget.replaceAll("%2F", "/").replaceAll("?", "&"); + aHelpLink += aTarget; + + if (comphelper::LibreOfficeKit::isActive()) + { + if(SfxViewShell* pViewShell = SfxViewShell::Current()) + { + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_HYPERLINK_CLICKED, + aHelpLink.toUtf8().getStr()); + return true; + } + else if (GetpApp()) + { + GetpApp()->libreOfficeKitViewCallback(LOK_CALLBACK_HYPERLINK_CLICKED, + aHelpLink.toUtf8().getStr()); + return true; + } + + return false; + } + + try + { +#ifdef MACOSX + LSOpenCFURLRef(CFURLCreateWithString(kCFAllocatorDefault, + CFStringCreateWithCString(kCFAllocatorDefault, + aHelpLink.toUtf8().getStr(), + kCFStringEncodingUTF8), + nullptr), + nullptr); + (void)pDialogParent; +#else + sfx2::openUriExternally(aHelpLink, false, pDialogParent); +#endif + return true; + } + catch (const Exception&) + { + } + return false; +} + +namespace { + +bool rewriteFlatpakHelpRootUrl(OUString * helpRootUrl) { + assert(helpRootUrl != nullptr); + //TODO: this function for now assumes that the passed-in *helpRootUrl references + // /app/libreoffice/help (which belongs to the org.libreoffice.LibreOffice.Help + // extension); it replaces it with the corresponding file URL as seen outside the flatpak + // sandbox: + struct Failure: public std::exception {}; + try { + static auto const url = [] { + // From /.flatpak-info [Instance] section, read + // app-path= + // app-extensions=...;org.libreoffice.LibreOffice.Help=;... + // lines: + osl::File ini("file:///.flatpak-info"); + auto err = ini.open(osl_File_OpenFlag_Read); + if (err != osl::FileBase::E_None) { + SAL_WARN("sfx.appl", "LIBO_FLATPAK mode failure opening /.flatpak-info: " << err); + throw Failure(); + } + OUString path; + OUString extensions; + bool havePath = false; + bool haveExtensions = false; + for (bool instance = false; !(havePath && haveExtensions);) { + rtl::ByteSequence bytes; + err = ini.readLine(bytes); + if (err != osl::FileBase::E_None) { + SAL_WARN( + "sfx.appl", + "LIBO_FLATPAK mode reading /.flatpak-info fails with " << err + << " before [Instance] app-path"); + throw Failure(); + } + std::string_view const line( + reinterpret_cast(bytes.getConstArray()), bytes.getLength()); + if (instance) { + static constexpr auto keyPath = std::string_view("app-path="); + static constexpr auto keyExtensions = std::string_view("app-extensions="); + if (!havePath && line.length() >= keyPath.size() + && line.substr(0, keyPath.size()) == keyPath.data()) + { + auto const value = line.substr(keyPath.size()); + if (!rtl_convertStringToUString( + &path.pData, value.data(), value.length(), + osl_getThreadTextEncoding(), + (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR))) + { + SAL_WARN( + "sfx.appl", + "LIBO_FLATPAK mode failure converting app-path \"" << value + << "\" encoding"); + throw Failure(); + } + havePath = true; + } else if (!haveExtensions && line.length() >= keyExtensions.size() + && line.substr(0, keyExtensions.size()) == keyExtensions.data()) + { + auto const value = line.substr(keyExtensions.size()); + if (!rtl_convertStringToUString( + &extensions.pData, value.data(), value.length(), + osl_getThreadTextEncoding(), + (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR + | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR))) + { + SAL_WARN( + "sfx.appl", + "LIBO_FLATPAK mode failure converting app-extensions \"" << value + << "\" encoding"); + throw Failure(); + } + haveExtensions = true; + } else if (line.length() > 0 && line[0] == '[') { + SAL_WARN( + "sfx.appl", + "LIBO_FLATPAK mode /.flatpak-info lacks [Instance] app-path and" + " app-extensions"); + throw Failure(); + } + } else if (line == "[Instance]") { + instance = true; + } + } + ini.close(); + // Extract from ...;org.libreoffice.LibreOffice.Help=;...: + OUString sha; + for (sal_Int32 i = 0;;) { + OUString elem = extensions.getToken(0, ';', i); + if (elem.startsWith("org.libreoffice.LibreOffice.Help=", &sha)) { + break; + } + if (i == -1) { + SAL_WARN( + "sfx.appl", + "LIBO_FLATPAK mode /.flatpak-info [Instance] app-extensions \"" + << extensions << "\" org.libreoffice.LibreOffice.Help"); + throw Failure(); + } + } + // Assuming that is of the form + // /.../app/org.libreoffice.LibreOffice////files + // rewrite it as + // /.../runtime/org.libreoffice.LibreOffice.Help////files + // because the extension's files are stored at a different place than the app's files, + // so use this hack until flatpak itself provides a better solution: + static constexpr OUStringLiteral segments = u"/app/org.libreoffice.LibreOffice/"; + auto const i1 = path.lastIndexOf(segments); + // use lastIndexOf instead of indexOf, in case the user-controlled prefix /.../ + // happens to contain such segments + if (i1 == -1) { + SAL_WARN( + "sfx.appl", + "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path + << "\" doesn't contain /app/org.libreoffice.LibreOffice/"); + throw Failure(); + } + auto const i2 = i1 + segments.getLength(); + auto i3 = path.indexOf('/', i2); + if (i3 == -1) { + SAL_WARN( + "sfx.appl", + "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path + << "\" doesn't contain branch segment"); + throw Failure(); + } + i3 = path.indexOf('/', i3 + 1); + if (i3 == -1) { + SAL_WARN( + "sfx.appl", + "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path + << "\" doesn't contain sha segment"); + throw Failure(); + } + ++i3; + auto const i4 = path.indexOf('/', i3); + if (i4 == -1) { + SAL_WARN( + "sfx.appl", + "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path + << "\" doesn't contain files segment"); + throw Failure(); + } + path = path.subView(0, i1) + OUString::Concat("/runtime/org.libreoffice.LibreOffice.Help/") + + path.subView(i2, i3 - i2) + sha + path.subView(i4); + // Turn into a file URL: + OUString url_; + err = osl::FileBase::getFileURLFromSystemPath(path, url_); + if (err != osl::FileBase::E_None) { + SAL_WARN( + "sfx.appl", + "LIBO_FLATPAK mode failure converting app-path \"" << path << "\" to URL: " + << err); + throw Failure(); + } + return url_; + }(); + *helpRootUrl = url; + return true; + } catch (Failure &) { + return false; + } +} + +} + +// add