LanguageTool: don't crash if REST protocol isn't set
[LibreOffice.git] / sfx2 / source / appl / sfxhelp.cxx
blob889c669241d0a8be1af7377b9eda37919ac46565
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <config_folders.h>
21 #include <sfx2/sfxhelp.hxx>
23 #include <string_view>
24 #include <algorithm>
25 #include <cassert>
26 #include <cstddef>
27 #ifdef MACOSX
28 #include <premac.h>
29 #include <Foundation/NSString.h>
30 #include <CoreFoundation/CFURL.h>
31 #include <CoreServices/CoreServices.h>
32 #include <postmac.h>
33 #endif
35 #include <sal/log.hxx>
36 #include <com/sun/star/uno/Reference.h>
37 #include <com/sun/star/frame/Desktop.hpp>
38 #include <com/sun/star/frame/UnknownModuleException.hpp>
39 #include <com/sun/star/frame/XFrame2.hpp>
40 #include <comphelper/processfactory.hxx>
41 #include <com/sun/star/awt/XWindow.hpp>
42 #include <com/sun/star/awt/XTopWindow.hpp>
43 #include <com/sun/star/beans/XPropertySet.hpp>
44 #include <com/sun/star/frame/FrameSearchFlag.hpp>
45 #include <toolkit/helper/vclunohelper.hxx>
46 #include <com/sun/star/frame/ModuleManager.hpp>
47 #include <unotools/configmgr.hxx>
48 #include <unotools/moduleoptions.hxx>
49 #include <tools/urlobj.hxx>
50 #include <ucbhelper/content.hxx>
51 #include <unotools/pathoptions.hxx>
52 #include <rtl/byteseq.hxx>
53 #include <rtl/ustring.hxx>
54 #include <o3tl/string_view.hxx>
55 #include <officecfg/Office/Common.hxx>
56 #include <osl/process.h>
57 #include <osl/file.hxx>
58 #include <unotools/tempfile.hxx>
59 #include <unotools/securityoptions.hxx>
60 #include <rtl/uri.hxx>
61 #include <vcl/commandinfoprovider.hxx>
62 #include <vcl/keycod.hxx>
63 #include <vcl/settings.hxx>
64 #include <vcl/locktoplevels.hxx>
65 #include <vcl/weld.hxx>
66 #include <openuriexternally.hxx>
68 #include <comphelper/lok.hxx>
69 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
70 #include <sfx2/viewsh.hxx>
72 #include "newhelp.hxx"
73 #include <sfx2/flatpak.hxx>
74 #include <sfx2/sfxresid.hxx>
75 #include <helper.hxx>
76 #include <sfx2/strings.hrc>
77 #include <vcl/svapp.hxx>
78 #include <rtl/string.hxx>
79 #include <svtools/langtab.hxx>
80 #include <tools/diagnose_ex.h>
82 using namespace ::com::sun::star::beans;
83 using namespace ::com::sun::star::frame;
84 using namespace ::com::sun::star::uno;
85 using namespace ::com::sun::star::util;
86 using namespace ::com::sun::star::lang;
88 namespace {
90 class NoHelpErrorBox
92 private:
93 std::unique_ptr<weld::MessageDialog> m_xErrBox;
94 public:
95 DECL_STATIC_LINK(NoHelpErrorBox, HelpRequestHdl, weld::Widget&, bool);
96 public:
97 explicit NoHelpErrorBox(weld::Widget* pParent)
98 : m_xErrBox(Application::CreateMessageDialog(pParent, VclMessageType::Error, VclButtonsType::Ok,
99 SfxResId(RID_STR_HLPFILENOTEXIST)))
101 // Error message: "No help available"
102 m_xErrBox->connect_help(LINK(nullptr, NoHelpErrorBox, HelpRequestHdl));
104 void run()
106 m_xErrBox->run();
112 IMPL_STATIC_LINK_NOARG(NoHelpErrorBox, HelpRequestHdl, weld::Widget&, bool)
114 // do nothing, because no help available
115 return false;
118 static OUString const & HelpLocaleString();
120 namespace {
122 /// Root path of the help.
123 OUString const & getHelpRootURL()
125 static OUString const s_instURL = []()
127 OUString tmp = officecfg::Office::Common::Path::Current::Help::get(comphelper::getProcessComponentContext());
128 if (tmp.isEmpty())
130 // try to determine path from default
131 tmp = "$(instpath)/" LIBO_SHARE_HELP_FOLDER;
134 // replace anything like $(instpath);
135 SvtPathOptions aOptions;
136 tmp = aOptions.SubstituteVariable(tmp);
138 OUString url;
139 if (osl::FileBase::getFileURLFromSystemPath(tmp, url) == osl::FileBase::E_None)
140 tmp = url;
141 return tmp;
142 }();
143 return s_instURL;
146 bool impl_checkHelpLocalePath(OUString const & rpPath)
148 osl::DirectoryItem directoryItem;
149 bool bOK = false;
151 osl::FileStatus fileStatus(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL | osl_FileStatus_Mask_FileName);
152 if (osl::DirectoryItem::get(rpPath, directoryItem) == osl::FileBase::E_None &&
153 directoryItem.getFileStatus(fileStatus) == osl::FileBase::E_None &&
154 fileStatus.isDirectory())
156 bOK = true;
158 return bOK;
161 /// Check for built-in help
162 /// Check if help/<lang>/err.html file exist
163 bool impl_hasHelpInstalled()
165 if (comphelper::LibreOfficeKit::isActive())
166 return false;
168 // detect installed locale
169 static OUString const aLocaleStr = HelpLocaleString();
171 OUString helpRootURL = getHelpRootURL() + "/" + aLocaleStr + "/err.html";
172 bool bOK = false;
173 osl::DirectoryItem directoryItem;
174 if(osl::DirectoryItem::get(helpRootURL, directoryItem) == osl::FileBase::E_None){
175 bOK=true;
178 SAL_INFO( "sfx.appl", "Checking old help installed " << bOK);
179 return bOK;
182 /// Check for html built-in help
183 /// Check if help/lang/text folder exist. Only html has it.
184 bool impl_hasHTMLHelpInstalled()
186 if (comphelper::LibreOfficeKit::isActive())
187 return false;
189 // detect installed locale
190 static OUString const aLocaleStr = HelpLocaleString();
192 OUString helpRootURL = getHelpRootURL() + "/" + aLocaleStr + "/text";
193 bool bOK = impl_checkHelpLocalePath( helpRootURL );
194 SAL_INFO( "sfx.appl", "Checking new help (html) installed " << bOK);
195 return bOK;
198 } // namespace
200 /// Return the locale we prefer for displaying help
201 static OUString const & HelpLocaleString()
203 if (comphelper::LibreOfficeKit::isActive())
204 return comphelper::LibreOfficeKit::getLanguageTag().getBcp47();
206 static OUString aLocaleStr;
207 if (!aLocaleStr.isEmpty())
208 return aLocaleStr;
210 static const OUStringLiteral aEnglish(u"en-US");
211 // detect installed locale
212 aLocaleStr = utl::ConfigManager::getUILocale();
214 if ( aLocaleStr.isEmpty() )
216 aLocaleStr = aEnglish;
217 return aLocaleStr;
220 // get fall-back language (country)
221 OUString sLang = aLocaleStr;
222 sal_Int32 nSepPos = sLang.indexOf( '-' );
223 if (nSepPos != -1)
225 sLang = sLang.copy( 0, nSepPos );
227 OUString sHelpPath("");
228 sHelpPath = getHelpRootURL() + "/" + utl::ConfigManager::getProductVersion() + "/" + aLocaleStr;
229 if (impl_checkHelpLocalePath(sHelpPath))
231 return aLocaleStr;
233 sHelpPath = getHelpRootURL() + "/" + utl::ConfigManager::getProductVersion() + "/" + sLang;
234 if (impl_checkHelpLocalePath(sHelpPath))
236 aLocaleStr = sLang;
237 return aLocaleStr;
239 sHelpPath = getHelpRootURL() + "/" + aLocaleStr;
240 if (impl_checkHelpLocalePath(sHelpPath))
242 return aLocaleStr;
244 sHelpPath = getHelpRootURL() + "/" + sLang;
245 if (impl_checkHelpLocalePath(sHelpPath))
247 aLocaleStr = sLang;
248 return aLocaleStr;
250 sHelpPath = getHelpRootURL() + "/" + utl::ConfigManager::getProductVersion() + "/" + aEnglish;
251 if (impl_checkHelpLocalePath(sHelpPath))
253 aLocaleStr = aEnglish;
254 return aLocaleStr;
256 sHelpPath = getHelpRootURL() + "/" + aEnglish;
257 if (impl_checkHelpLocalePath(sHelpPath))
259 aLocaleStr = aEnglish;
260 return aLocaleStr;
262 return aLocaleStr;
267 void AppendConfigToken( OUStringBuffer& rURL, bool bQuestionMark )
269 OUString aLocaleStr = HelpLocaleString();
271 // query part exists?
272 if ( bQuestionMark )
273 // no, so start with '?'
274 rURL.append('?');
275 else
276 // yes, so only append with '&'
277 rURL.append('&');
279 // set parameters
280 rURL.append("Language=");
281 rURL.append(aLocaleStr);
282 rURL.append("&System=");
283 rURL.append(officecfg::Office::Common::Help::System::get());
284 rURL.append("&Version=");
285 rURL.append(utl::ConfigManager::getProductVersion());
288 static bool GetHelpAnchor_Impl( const OUString& _rURL, OUString& _rAnchor )
290 bool bRet = false;
294 ::ucbhelper::Content aCnt( INetURLObject( _rURL ).GetMainURL( INetURLObject::DecodeMechanism::NONE ),
295 Reference< css::ucb::XCommandEnvironment >(),
296 comphelper::getProcessComponentContext() );
297 OUString sAnchor;
298 if ( aCnt.getPropertyValue("AnchorName") >>= sAnchor )
301 if ( !sAnchor.isEmpty() )
303 _rAnchor = sAnchor;
304 bRet = true;
307 else
309 SAL_WARN( "sfx.appl", "Property 'AnchorName' is missing" );
312 catch (const css::uno::Exception&)
316 return bRet;
319 namespace {
321 class SfxHelp_Impl
323 public:
324 static OUString GetHelpText( const OUString& aCommandURL, const OUString& rModule );
329 OUString SfxHelp_Impl::GetHelpText( const OUString& aCommandURL, const OUString& rModule )
331 // create help url
332 OUStringBuffer aHelpURL( SfxHelp::CreateHelpURL( aCommandURL, rModule ) );
333 // added 'active' parameter
334 sal_Int32 nIndex = aHelpURL.lastIndexOf( '#' );
335 if ( nIndex < 0 )
336 nIndex = aHelpURL.getLength();
337 aHelpURL.insert( nIndex, "&Active=true" );
338 // load help string
339 return SfxContentHelper::GetActiveHelpString( aHelpURL.makeStringAndClear() );
342 SfxHelp::SfxHelp()
343 : bIsDebug(false)
344 , bLaunchingHelp(false)
346 // read the environment variable "HELP_DEBUG"
347 // if it's set, you will see debug output on active help
348 OUString sHelpDebug;
349 OUString sEnvVarName( "HELP_DEBUG" );
350 osl_getEnvironment( sEnvVarName.pData, &sHelpDebug.pData );
351 bIsDebug = !sHelpDebug.isEmpty();
354 SfxHelp::~SfxHelp()
358 static OUString getDefaultModule_Impl()
360 OUString sDefaultModule;
361 SvtModuleOptions aModOpt;
362 if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
363 sDefaultModule = "swriter";
364 else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) )
365 sDefaultModule = "scalc";
366 else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) )
367 sDefaultModule = "simpress";
368 else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) )
369 sDefaultModule = "sdraw";
370 else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::MATH ) )
371 sDefaultModule = "smath";
372 else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::CHART ) )
373 sDefaultModule = "schart";
374 else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::BASIC ) )
375 sDefaultModule = "sbasic";
376 else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::DATABASE ) )
377 sDefaultModule = "sdatabase";
378 else
380 SAL_WARN( "sfx.appl", "getDefaultModule_Impl(): no module installed" );
382 return sDefaultModule;
385 static OUString getCurrentModuleIdentifier_Impl()
387 OUString sIdentifier;
388 Reference < XComponentContext > xContext = ::comphelper::getProcessComponentContext();
389 Reference < XModuleManager2 > xModuleManager = ModuleManager::create(xContext);
390 Reference < XDesktop2 > xDesktop = Desktop::create(xContext);
391 Reference < XFrame > xCurrentFrame = xDesktop->getCurrentFrame();
393 if ( xCurrentFrame.is() )
397 sIdentifier = xModuleManager->identify( xCurrentFrame );
399 catch (const css::frame::UnknownModuleException&)
401 SAL_INFO( "sfx.appl", "SfxHelp::getCurrentModuleIdentifier_Impl(): unknown module (help in help?)" );
403 catch (const Exception&)
405 TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelp::getCurrentModuleIdentifier_Impl(): exception of XModuleManager::identify()" );
409 return sIdentifier;
412 namespace
414 OUString MapModuleIdentifier(const OUString &rFactoryShortName)
416 OUString aFactoryShortName(rFactoryShortName);
418 // Map some module identifiers to their "real" help module string.
419 if ( aFactoryShortName == "chart2" )
420 aFactoryShortName = "schart" ;
421 else if ( aFactoryShortName == "BasicIDE" )
422 aFactoryShortName = "sbasic";
423 else if ( aFactoryShortName == "sweb"
424 || aFactoryShortName == "sglobal"
425 || aFactoryShortName == "swxform" )
426 aFactoryShortName = "swriter" ;
427 else if ( aFactoryShortName == "dbquery"
428 || aFactoryShortName == "dbbrowser"
429 || aFactoryShortName == "dbrelation"
430 || aFactoryShortName == "dbtable"
431 || aFactoryShortName == "dbapp"
432 || aFactoryShortName == "dbreport"
433 || aFactoryShortName == "dbtdata"
434 || aFactoryShortName == "swreport"
435 || aFactoryShortName == "swform" )
436 aFactoryShortName = "sdatabase";
437 else if ( aFactoryShortName == "sbibliography"
438 || aFactoryShortName == "sabpilot"
439 || aFactoryShortName == "scanner"
440 || aFactoryShortName == "spropctrlr"
441 || aFactoryShortName == "StartModule" )
442 aFactoryShortName.clear();
444 return aFactoryShortName;
448 OUString SfxHelp::GetHelpModuleName_Impl(std::u16string_view rHelpID)
450 OUString aFactoryShortName;
452 //rhbz#1438876 detect preferred module for this help id, e.g. csv dialog
453 //for calc import before any toplevel is created and so context is
454 //otherwise unknown. Cosmetic, same help is shown in any case because its
455 //in the shared section, but title bar would state "Writer" when context is
456 //expected to be "Calc"
457 std::u16string_view sRemainder;
458 if (o3tl::starts_with(rHelpID, u"modules/", &sRemainder))
460 std::size_t nEndModule = sRemainder.find(u'/');
461 aFactoryShortName = nEndModule != std::u16string_view::npos
462 ? sRemainder.substr(0, nEndModule) : sRemainder;
465 if (aFactoryShortName.isEmpty())
467 OUString aModuleIdentifier = getCurrentModuleIdentifier_Impl();
468 if (!aModuleIdentifier.isEmpty())
472 Reference < XModuleManager2 > xModuleManager(
473 ModuleManager::create(::comphelper::getProcessComponentContext()) );
474 Sequence< PropertyValue > lProps;
475 xModuleManager->getByName( aModuleIdentifier ) >>= lProps;
476 auto pProp = std::find_if(std::cbegin(lProps), std::cend(lProps),
477 [](const PropertyValue& rProp) { return rProp.Name == "ooSetupFactoryShortName"; });
478 if (pProp != std::cend(lProps))
479 pProp->Value >>= aFactoryShortName;
481 catch (const Exception&)
483 TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelp::GetHelpModuleName_Impl()" );
488 if (!aFactoryShortName.isEmpty())
489 aFactoryShortName = MapModuleIdentifier(aFactoryShortName);
490 if (aFactoryShortName.isEmpty())
491 aFactoryShortName = getDefaultModule_Impl();
493 return aFactoryShortName;
496 OUString SfxHelp::CreateHelpURL_Impl( const OUString& aCommandURL, const OUString& rModuleName )
498 // build up the help URL
499 OUStringBuffer aHelpURL("vnd.sun.star.help://");
500 bool bHasAnchor = false;
501 OUString aAnchor;
503 OUString aModuleName( rModuleName );
504 if (aModuleName.isEmpty())
505 aModuleName = getDefaultModule_Impl();
507 aHelpURL.append(aModuleName);
509 if ( aCommandURL.isEmpty() )
510 aHelpURL.append("/start");
511 else
513 aHelpURL.append('/');
514 aHelpURL.append(rtl::Uri::encode(aCommandURL,
515 rtl_UriCharClassRelSegment,
516 rtl_UriEncodeKeepEscapes,
517 RTL_TEXTENCODING_UTF8));
519 OUStringBuffer aTempURL = aHelpURL;
520 AppendConfigToken( aTempURL, true );
521 bHasAnchor = GetHelpAnchor_Impl(aTempURL.makeStringAndClear(), aAnchor);
524 AppendConfigToken( aHelpURL, true );
526 if ( bHasAnchor )
528 aHelpURL.append('#');
529 aHelpURL.append(aAnchor);
532 return aHelpURL.makeStringAndClear();
535 static SfxHelpWindow_Impl* impl_createHelp(Reference< XFrame2 >& rHelpTask ,
536 Reference< XFrame >& rHelpContent)
538 Reference < XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
540 // otherwise - create new help task
541 Reference< XFrame2 > xHelpTask(
542 xDesktop->findFrame( "OFFICE_HELP_TASK", FrameSearchFlag::TASKS | FrameSearchFlag::CREATE),
543 UNO_QUERY);
544 if (!xHelpTask.is())
545 return nullptr;
547 // create all internal windows and sub frames ...
548 Reference< css::awt::XWindow > xParentWindow = xHelpTask->getContainerWindow();
549 VclPtr<vcl::Window> pParentWindow = VCLUnoHelper::GetWindow( xParentWindow );
550 VclPtrInstance<SfxHelpWindow_Impl> pHelpWindow( xHelpTask, pParentWindow );
551 Reference< css::awt::XWindow > xHelpWindow = VCLUnoHelper::GetInterface( pHelpWindow );
553 Reference< XFrame > xHelpContent;
554 if (xHelpTask->setComponent( xHelpWindow, Reference< XController >() ))
556 // Customize UI ...
557 xHelpTask->setName("OFFICE_HELP_TASK");
559 Reference< XPropertySet > xProps(xHelpTask, UNO_QUERY);
560 if (xProps.is())
561 xProps->setPropertyValue(
562 "Title",
563 makeAny(SfxResId(STR_HELP_WINDOW_TITLE)));
565 pHelpWindow->setContainerWindow( xParentWindow );
566 xParentWindow->setVisible(true);
567 xHelpWindow->setVisible(true);
569 // This sub frame is created internally (if we called new SfxHelpWindow_Impl() ...)
570 // It should exist :-)
571 xHelpContent = xHelpTask->findFrame("OFFICE_HELP", FrameSearchFlag::CHILDREN);
574 if (!xHelpContent.is())
576 pHelpWindow.disposeAndClear();
577 return nullptr;
580 xHelpContent->setName("OFFICE_HELP");
582 rHelpTask = xHelpTask;
583 rHelpContent = xHelpContent;
584 return pHelpWindow;
587 OUString SfxHelp::GetHelpText( const OUString& aCommandURL, const vcl::Window* pWindow )
589 OUString sModuleName = GetHelpModuleName_Impl(aCommandURL);
590 auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommandURL, getCurrentModuleIdentifier_Impl());
591 OUString sRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties);
592 OUString sHelpText = SfxHelp_Impl::GetHelpText( sRealCommand.isEmpty() ? aCommandURL : sRealCommand, sModuleName );
594 OString aNewHelpId;
596 if (pWindow && sHelpText.isEmpty())
598 // no help text found -> try with parent help id.
599 vcl::Window* pParent = pWindow->GetParent();
600 while ( pParent )
602 aNewHelpId = pParent->GetHelpId();
603 sHelpText = SfxHelp_Impl::GetHelpText( OStringToOUString(aNewHelpId, RTL_TEXTENCODING_UTF8), sModuleName );
604 if (!sHelpText.isEmpty())
605 pParent = nullptr;
606 else
607 pParent = pParent->GetParent();
610 if (bIsDebug && sHelpText.isEmpty())
611 aNewHelpId.clear();
614 // add some debug information?
615 if ( bIsDebug )
617 sHelpText += "\n-------------\n" +
618 sModuleName + ": " + aCommandURL;
619 if ( !aNewHelpId.isEmpty() )
621 sHelpText += " - " +
622 OStringToOUString(aNewHelpId, RTL_TEXTENCODING_UTF8);
626 return sHelpText;
629 OUString SfxHelp::GetHelpText(const OUString& aCommandURL, const weld::Widget* pWidget)
631 OUString sModuleName = GetHelpModuleName_Impl(aCommandURL);
632 auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommandURL, getCurrentModuleIdentifier_Impl());
633 OUString sRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties);
634 OUString sHelpText = SfxHelp_Impl::GetHelpText( sRealCommand.isEmpty() ? aCommandURL : sRealCommand, sModuleName );
636 OString aNewHelpId;
638 if (pWidget && sHelpText.isEmpty())
640 // no help text found -> try with parent help id.
641 std::unique_ptr<weld::Widget> xParent(pWidget->weld_parent());
642 while (xParent)
644 aNewHelpId = xParent->get_help_id();
645 sHelpText = SfxHelp_Impl::GetHelpText( OStringToOUString(aNewHelpId, RTL_TEXTENCODING_UTF8), sModuleName );
646 if (!sHelpText.isEmpty())
647 xParent.reset();
648 else
649 xParent = xParent->weld_parent();
652 if (bIsDebug && sHelpText.isEmpty())
653 aNewHelpId.clear();
656 // add some debug information?
657 if ( bIsDebug )
659 sHelpText += "\n-------------\n" +
660 sModuleName + ": " + aCommandURL;
661 if ( !aNewHelpId.isEmpty() )
663 sHelpText += " - " +
664 OStringToOUString(aNewHelpId, RTL_TEXTENCODING_UTF8);
668 return sHelpText;
671 OUString SfxHelp::GetURLHelpText(std::u16string_view aURL)
673 bool bCtrlClickHlink = SvtSecurityOptions::IsOptionSet(SvtSecurityOptions::EOption::CtrlClickHyperlink);
675 // "ctrl-click to follow link:" for not MacOS
676 // "⌘-click to follow link:" for MacOs
677 vcl::KeyCode aCode(KEY_SPACE);
678 vcl::KeyCode aModifiedCode(KEY_SPACE, KEY_MOD1);
679 OUString aModStr(aModifiedCode.GetName());
680 aModStr = aModStr.replaceFirst(aCode.GetName(), "");
681 aModStr = aModStr.replaceAll("+", "");
682 OUString aHelpStr
683 = bCtrlClickHlink ? SfxResId(STR_CTRLCLICKHYPERLINK) : SfxResId(STR_CLICKHYPERLINK);
684 aHelpStr = aHelpStr.replaceFirst("%{key}", aModStr);
685 aHelpStr = aHelpStr.replaceFirst("%{link}", aURL);
686 return aHelpStr;
689 void SfxHelp::SearchKeyword( const OUString& rKeyword )
691 Start_Impl(OUString(), static_cast<weld::Widget*>(nullptr), rKeyword);
694 bool SfxHelp::Start( const OUString& rURL, const vcl::Window* pWindow )
696 if (bLaunchingHelp)
697 return true;
698 bLaunchingHelp = true;
699 bool bRet = Start_Impl( rURL, pWindow );
700 bLaunchingHelp = false;
701 return bRet;
704 bool SfxHelp::Start(const OUString& rURL, weld::Widget* pWidget)
706 if (bLaunchingHelp)
707 return true;
708 bLaunchingHelp = true;
709 bool bRet = Start_Impl(rURL, pWidget, OUString());
710 bLaunchingHelp = false;
711 return bRet;
714 /// Redirect the vnd.sun.star.help:// urls to http://help.libreoffice.org
715 static bool impl_showOnlineHelp(const OUString& rURL, weld::Widget* pDialogParent)
717 static constexpr OUStringLiteral aInternal(u"vnd.sun.star.help://");
718 if ( rURL.getLength() <= aInternal.getLength() || !rURL.startsWith(aInternal) )
719 return false;
721 OUString aHelpLink = officecfg::Office::Common::Help::HelpRootURL::get();
722 OUString aTarget = OUString::Concat("Target=") + rURL.subView(aInternal.getLength());
723 aTarget = aTarget.replaceAll("%2F", "/").replaceAll("?", "&");
724 aHelpLink += aTarget;
726 if (comphelper::LibreOfficeKit::isActive())
728 if(SfxViewShell* pViewShell = SfxViewShell::Current())
730 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_HYPERLINK_CLICKED,
731 aHelpLink.toUtf8().getStr());
732 return true;
734 else if (GetpApp())
736 GetpApp()->libreOfficeKitViewCallback(LOK_CALLBACK_HYPERLINK_CLICKED,
737 aHelpLink.toUtf8().getStr());
738 return true;
741 return false;
746 #ifdef MACOSX
747 LSOpenCFURLRef(CFURLCreateWithString(kCFAllocatorDefault,
748 CFStringCreateWithCString(kCFAllocatorDefault,
749 aHelpLink.toUtf8().getStr(),
750 kCFStringEncodingUTF8),
751 nullptr),
752 nullptr);
753 (void)pDialogParent;
754 #else
755 sfx2::openUriExternally(aHelpLink, false, pDialogParent);
756 #endif
757 return true;
759 catch (const Exception&)
762 return false;
765 namespace {
767 bool rewriteFlatpakHelpRootUrl(OUString * helpRootUrl) {
768 assert(helpRootUrl != nullptr);
769 //TODO: this function for now assumes that the passed-in *helpRootUrl references
770 // /app/libreoffice/help (which belongs to the org.libreoffice.LibreOffice.Help
771 // extension); it replaces it with the corresponding file URL as seen outside the flatpak
772 // sandbox:
773 struct Failure: public std::exception {};
774 try {
775 static auto const url = [] {
776 // From /.flatpak-info [Instance] section, read
777 // app-path=<path>
778 // app-extensions=...;org.libreoffice.LibreOffice.Help=<sha>;...
779 // lines:
780 osl::File ini("file:///.flatpak-info");
781 auto err = ini.open(osl_File_OpenFlag_Read);
782 if (err != osl::FileBase::E_None) {
783 SAL_WARN("sfx.appl", "LIBO_FLATPAK mode failure opening /.flatpak-info: " << err);
784 throw Failure();
786 OUString path;
787 OUString extensions;
788 bool havePath = false;
789 bool haveExtensions = false;
790 for (bool instance = false; !(havePath && haveExtensions);) {
791 rtl::ByteSequence bytes;
792 err = ini.readLine(bytes);
793 if (err != osl::FileBase::E_None) {
794 SAL_WARN(
795 "sfx.appl",
796 "LIBO_FLATPAK mode reading /.flatpak-info fails with " << err
797 << " before [Instance] app-path");
798 throw Failure();
800 std::string_view const line(
801 reinterpret_cast<char const *>(bytes.getConstArray()), bytes.getLength());
802 if (instance) {
803 static constexpr auto keyPath = std::string_view("app-path=");
804 static constexpr auto keyExtensions = std::string_view("app-extensions=");
805 if (!havePath && line.length() >= keyPath.size()
806 && line.substr(0, keyPath.size()) == keyPath.data())
808 auto const value = line.substr(keyPath.size());
809 if (!rtl_convertStringToUString(
810 &path.pData, value.data(), value.length(),
811 osl_getThreadTextEncoding(),
812 (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR
813 | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR
814 | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
816 SAL_WARN(
817 "sfx.appl",
818 "LIBO_FLATPAK mode failure converting app-path \"" << value
819 << "\" encoding");
820 throw Failure();
822 havePath = true;
823 } else if (!haveExtensions && line.length() >= keyExtensions.size()
824 && line.substr(0, keyExtensions.size()) == keyExtensions.data())
826 auto const value = line.substr(keyExtensions.size());
827 if (!rtl_convertStringToUString(
828 &extensions.pData, value.data(), value.length(),
829 osl_getThreadTextEncoding(),
830 (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR
831 | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR
832 | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
834 SAL_WARN(
835 "sfx.appl",
836 "LIBO_FLATPAK mode failure converting app-extensions \"" << value
837 << "\" encoding");
838 throw Failure();
840 haveExtensions = true;
841 } else if (line.length() > 0 && line[0] == '[') {
842 SAL_WARN(
843 "sfx.appl",
844 "LIBO_FLATPAK mode /.flatpak-info lacks [Instance] app-path and"
845 " app-extensions");
846 throw Failure();
848 } else if (line == "[Instance]") {
849 instance = true;
852 ini.close();
853 // Extract <sha> from ...;org.libreoffice.LibreOffice.Help=<sha>;...:
854 OUString sha;
855 for (sal_Int32 i = 0;;) {
856 OUString elem = extensions.getToken(0, ';', i);
857 if (elem.startsWith("org.libreoffice.LibreOffice.Help=", &sha)) {
858 break;
860 if (i == -1) {
861 SAL_WARN(
862 "sfx.appl",
863 "LIBO_FLATPAK mode /.flatpak-info [Instance] app-extensions \""
864 << extensions << "\" org.libreoffice.LibreOffice.Help");
865 throw Failure();
868 // Assuming that <path> is of the form
869 // /.../app/org.libreoffice.LibreOffice/<arch>/<branch>/<sha'>/files
870 // rewrite it as
871 // /.../runtime/org.libreoffice.LibreOffice.Help/<arch>/<branch>/<sha>/files
872 // because the extension's files are stored at a different place than the app's files,
873 // so use this hack until flatpak itself provides a better solution:
874 static constexpr OUStringLiteral segments = u"/app/org.libreoffice.LibreOffice/";
875 auto const i1 = path.lastIndexOf(segments);
876 // use lastIndexOf instead of indexOf, in case the user-controlled prefix /.../
877 // happens to contain such segments
878 if (i1 == -1) {
879 SAL_WARN(
880 "sfx.appl",
881 "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path
882 << "\" doesn't contain /app/org.libreoffice.LibreOffice/");
883 throw Failure();
885 auto const i2 = i1 + segments.getLength();
886 auto i3 = path.indexOf('/', i2);
887 if (i3 == -1) {
888 SAL_WARN(
889 "sfx.appl",
890 "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path
891 << "\" doesn't contain branch segment");
892 throw Failure();
894 i3 = path.indexOf('/', i3 + 1);
895 if (i3 == -1) {
896 SAL_WARN(
897 "sfx.appl",
898 "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path
899 << "\" doesn't contain sha segment");
900 throw Failure();
902 ++i3;
903 auto const i4 = path.indexOf('/', i3);
904 if (i4 == -1) {
905 SAL_WARN(
906 "sfx.appl",
907 "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path
908 << "\" doesn't contain files segment");
909 throw Failure();
911 path = path.subView(0, i1) + OUString::Concat("/runtime/org.libreoffice.LibreOffice.Help/")
912 + path.subView(i2, i3 - i2) + sha + path.subView(i4);
913 // Turn <path> into a file URL:
914 OUString url_;
915 err = osl::FileBase::getFileURLFromSystemPath(path, url_);
916 if (err != osl::FileBase::E_None) {
917 SAL_WARN(
918 "sfx.appl",
919 "LIBO_FLATPAK mode failure converting app-path \"" << path << "\" to URL: "
920 << err);
921 throw Failure();
923 return url_;
924 }();
925 *helpRootUrl = url;
926 return true;
927 } catch (Failure &) {
928 return false;
934 // add <noscript> meta for browsers without javascript
936 constexpr OUStringLiteral SHTML1 = u"<!DOCTYPE HTML><html lang=\"en-US\"><head><meta charset=\"UTF-8\">";
937 constexpr OUStringLiteral SHTML2 = u"<noscript><meta http-equiv=\"refresh\" content=\"0; url='";
938 constexpr OUStringLiteral SHTML3 = u"/noscript.html'\"></noscript><meta http-equiv=\"refresh\" content=\"1; url='";
939 constexpr OUStringLiteral SHTML4 = u"'\"><script type=\"text/javascript\"> window.location.href = \"";
940 constexpr OUStringLiteral SHTML5 = u"\";</script><title>Help Page Redirection</title></head><body></body></html>";
942 // use a tempfile since e.g. xdg-open doesn't support URL-parameters with file:// URLs
943 static bool impl_showOfflineHelp(const OUString& rURL, weld::Widget* pDialogParent)
945 OUString aBaseInstallPath = getHelpRootURL();
946 // For the flatpak case, find the pathname outside the flatpak sandbox that corresponds to
947 // aBaseInstallPath, because that is what needs to be stored in aTempFile below:
948 if (flatpak::isFlatpak() && !rewriteFlatpakHelpRootUrl(&aBaseInstallPath)) {
949 return false;
952 OUString aHelpLink( aBaseInstallPath + "/index.html?" );
953 OUString aTarget = OUString::Concat("Target=") + rURL.subView(RTL_CONSTASCII_LENGTH("vnd.sun.star.help://"));
954 aTarget = aTarget.replaceAll("%2F","/").replaceAll("?","&");
955 aHelpLink += aTarget;
957 // Get a html tempfile (for the flatpak case, create it in XDG_CACHE_HOME instead of /tmp for
958 // technical reasons, so that it can be accessed by the browser running outside the sandbox):
959 OUString const aExtension(".html");
960 OUString * parent = nullptr;
961 if (flatpak::isFlatpak() && !flatpak::createTemporaryHtmlDirectory(&parent)) {
962 return false;
964 ::utl::TempFile aTempFile("NewHelp", true, &aExtension, parent, false );
966 SvStream* pStream = aTempFile.GetStream(StreamMode::WRITE);
967 pStream->SetStreamCharSet(RTL_TEXTENCODING_UTF8);
969 OUString aTempStr = SHTML1 + SHTML2 +
970 aBaseInstallPath + "/" + HelpLocaleString() + SHTML3 +
971 aHelpLink + SHTML4 +
972 aHelpLink + SHTML5;
974 pStream->WriteUnicodeOrByteText(aTempStr);
976 aTempFile.CloseStream();
979 #ifdef MACOSX
980 LSOpenCFURLRef(CFURLCreateWithString(kCFAllocatorDefault,
981 CFStringCreateWithCString(kCFAllocatorDefault,
982 aTempFile.GetURL().toUtf8().getStr(),
983 kCFStringEncodingUTF8),
984 nullptr),
985 nullptr);
986 (void)pDialogParent;
987 #else
988 sfx2::openUriExternally(aTempFile.GetURL(), false, pDialogParent);
989 #endif
990 return true;
992 catch (const Exception&)
995 aTempFile.EnableKillingFile();
996 return false;
999 namespace
1001 // tdf#119579 skip floating windows as potential parent for missing help dialog
1002 const vcl::Window* GetBestParent(const vcl::Window* pWindow)
1004 while (pWindow)
1006 if (pWindow->IsSystemWindow() && pWindow->GetType() != WindowType::FLOATINGWINDOW)
1007 break;
1008 pWindow = pWindow->GetParent();
1010 return pWindow;
1014 namespace {
1016 class HelpManualMessage : public weld::MessageDialogController
1018 private:
1019 std::unique_ptr<weld::CheckButton> m_xHideOfflineHelpCB;
1021 public:
1022 HelpManualMessage(weld::Widget* pParent)
1023 : MessageDialogController(pParent, "sfx/ui/helpmanual.ui", "onlinehelpmanual", "hidedialog")
1024 , m_xHideOfflineHelpCB(m_xBuilder->weld_check_button("hidedialog"))
1026 LanguageType aLangType = Application::GetSettings().GetUILanguageTag().getLanguageType();
1027 OUString sLocaleString = SvtLanguageTable::GetLanguageString(aLangType);
1028 OUString sPrimText = get_primary_text();
1029 set_primary_text(sPrimText.replaceAll("$UILOCALE", sLocaleString));
1032 bool GetOfflineHelpPopUp() const { return !m_xHideOfflineHelpCB->get_active(); }
1037 bool SfxHelp::Start_Impl(const OUString& rURL, const vcl::Window* pWindow)
1039 OUStringBuffer aHelpRootURL("vnd.sun.star.help://");
1040 AppendConfigToken(aHelpRootURL, true);
1041 SfxContentHelper::GetResultSet(aHelpRootURL.makeStringAndClear());
1043 /* rURL may be
1044 * - a "real" URL
1045 * - a HelpID (formerly a long, now a string)
1046 * If rURL is a URL, CreateHelpURL should be called for this URL
1047 * If rURL is an arbitrary string, the same should happen, but the URL should be tried out
1048 * if it delivers real help content. In case only the Help Error Document is returned, the
1049 * parent of the window for that help was called, is asked for its HelpID.
1050 * For compatibility reasons this upward search is not implemented for "real" URLs.
1051 * Help keyword search now is implemented as own method; in former versions it
1052 * was done via Help::Start, but this implementation conflicted with the upward search.
1054 OUString aHelpURL;
1055 INetURLObject aParser( rURL );
1056 INetProtocol nProtocol = aParser.GetProtocol();
1058 switch ( nProtocol )
1060 case INetProtocol::VndSunStarHelp:
1061 // already a vnd.sun.star.help URL -> nothing to do
1062 aHelpURL = rURL;
1063 break;
1064 default:
1066 OUString aHelpModuleName(GetHelpModuleName_Impl(rURL));
1067 OUString aRealCommand;
1069 if ( nProtocol == INetProtocol::Uno )
1071 // Command can be just an alias to another command.
1072 auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rURL, getCurrentModuleIdentifier_Impl());
1073 aRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties);
1076 // no URL, just a HelpID (maybe empty in case of keyword search)
1077 aHelpURL = CreateHelpURL_Impl( aRealCommand.isEmpty() ? rURL : aRealCommand, aHelpModuleName );
1079 if ( impl_hasHelpInstalled() && pWindow && SfxContentHelper::IsHelpErrorDocument( aHelpURL ) )
1081 // no help found -> try with parent help id.
1082 vcl::Window* pParent = pWindow->GetParent();
1083 while ( pParent )
1085 OString aHelpId = pParent->GetHelpId();
1086 aHelpURL = CreateHelpURL( OStringToOUString(aHelpId, RTL_TEXTENCODING_UTF8), aHelpModuleName );
1088 if ( !SfxContentHelper::IsHelpErrorDocument( aHelpURL ) )
1090 break;
1092 else
1094 pParent = pParent->GetParent();
1095 if (!pParent)
1097 // create help url of start page ( helpid == 0 -> start page)
1098 aHelpURL = CreateHelpURL( OUString(), aHelpModuleName );
1103 break;
1107 pWindow = GetBestParent(pWindow);
1108 weld::Window* pWeldWindow = pWindow ? pWindow->GetFrameWeld() : nullptr;
1110 if ( comphelper::LibreOfficeKit::isActive() )
1112 impl_showOnlineHelp(aHelpURL, pWeldWindow);
1113 return true;
1115 #ifdef MACOSX
1116 if (@available(macOS 10.14, *)) {
1117 // Workaround: Safari sandboxing prevents it from accessing files in the LibreOffice.app folder
1118 // force online-help instead if Safari is default browser.
1119 CFURLRef pBrowser = LSCopyDefaultApplicationURLForURL(
1120 CFURLCreateWithString(
1121 kCFAllocatorDefault,
1122 static_cast<CFStringRef>(@"https://www.libreoffice.org"),
1123 nullptr),
1124 kLSRolesAll, nullptr);
1125 if([static_cast<NSString*>(CFURLGetString(pBrowser)) isEqualToString:@"file:///Applications/Safari.app/"]) {
1126 impl_showOnlineHelp(aHelpURL, pWeldWindow);
1127 return true;
1130 #endif
1132 // If the HTML or no help is installed, but aHelpURL nevertheless references valid help content,
1133 // that implies that this help content belongs to an extension (and thus would not be available
1134 // in neither the offline nor online HTML help); in that case, fall through to the "old-help to
1135 // display" code below:
1136 if (SfxContentHelper::IsHelpErrorDocument(aHelpURL))
1138 if ( impl_hasHTMLHelpInstalled() && impl_showOfflineHelp(aHelpURL, pWeldWindow) )
1140 return true;
1143 if ( !impl_hasHelpInstalled() )
1145 bool bShowOfflineHelpPopUp = officecfg::Office::Common::Help::BuiltInHelpNotInstalledPopUp::get();
1147 TopLevelWindowLocker aBusy;
1149 if(bShowOfflineHelpPopUp)
1151 aBusy.incBusy(pWeldWindow);
1152 HelpManualMessage aQueryBox(pWeldWindow);
1153 short OnlineHelpBox = aQueryBox.run();
1154 bShowOfflineHelpPopUp = OnlineHelpBox != RET_OK;
1155 auto xChanges = comphelper::ConfigurationChanges::create();
1156 officecfg::Office::Common::Help::BuiltInHelpNotInstalledPopUp::set(aQueryBox.GetOfflineHelpPopUp(), xChanges);
1157 xChanges->commit();
1158 aBusy.decBusy();
1160 if(!bShowOfflineHelpPopUp)
1162 if ( impl_showOnlineHelp(aHelpURL, pWeldWindow) )
1163 return true;
1164 else
1166 aBusy.incBusy(pWeldWindow);
1167 NoHelpErrorBox aErrBox(pWeldWindow);
1168 aErrBox.run();
1169 aBusy.decBusy();
1170 return false;
1173 else
1175 return false;
1180 // old-help to display
1181 Reference < XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
1183 // check if help window is still open
1184 // If not, create a new one and return access directly to the internal sub frame showing the help content
1185 // search must be done here; search one desktop level could return an arbitrary frame
1186 Reference< XFrame2 > xHelp(
1187 xDesktop->findFrame( "OFFICE_HELP_TASK", FrameSearchFlag::CHILDREN),
1188 UNO_QUERY);
1189 Reference< XFrame > xHelpContent = xDesktop->findFrame(
1190 "OFFICE_HELP",
1191 FrameSearchFlag::CHILDREN);
1193 SfxHelpWindow_Impl* pHelpWindow = nullptr;
1194 if (!xHelp.is())
1195 pHelpWindow = impl_createHelp(xHelp, xHelpContent);
1196 else
1197 pHelpWindow = static_cast<SfxHelpWindow_Impl*>(VCLUnoHelper::GetWindow(xHelp->getComponentWindow()));
1198 if (!xHelp.is() || !xHelpContent.is() || !pHelpWindow)
1199 return false;
1201 SAL_INFO("sfx.appl", "HelpId = " << aHelpURL);
1203 pHelpWindow->SetHelpURL( aHelpURL );
1204 pHelpWindow->loadHelpContent(aHelpURL);
1206 Reference < css::awt::XTopWindow > xTopWindow( xHelp->getContainerWindow(), UNO_QUERY );
1207 if ( xTopWindow.is() )
1208 xTopWindow->toFront();
1210 return true;
1213 bool SfxHelp::Start_Impl(const OUString& rURL, weld::Widget* pWidget, const OUString& rKeyword)
1215 OUStringBuffer aHelpRootURL("vnd.sun.star.help://");
1216 AppendConfigToken(aHelpRootURL, true);
1217 SfxContentHelper::GetResultSet(aHelpRootURL.makeStringAndClear());
1219 /* rURL may be
1220 * - a "real" URL
1221 * - a HelpID (formerly a long, now a string)
1222 * If rURL is a URL, CreateHelpURL should be called for this URL
1223 * If rURL is an arbitrary string, the same should happen, but the URL should be tried out
1224 * if it delivers real help content. In case only the Help Error Document is returned, the
1225 * parent of the window for that help was called, is asked for its HelpID.
1226 * For compatibility reasons this upward search is not implemented for "real" URLs.
1227 * Help keyword search now is implemented as own method; in former versions it
1228 * was done via Help::Start, but this implementation conflicted with the upward search.
1230 OUString aHelpURL;
1231 INetURLObject aParser( rURL );
1232 INetProtocol nProtocol = aParser.GetProtocol();
1234 switch ( nProtocol )
1236 case INetProtocol::VndSunStarHelp:
1237 // already a vnd.sun.star.help URL -> nothing to do
1238 aHelpURL = rURL;
1239 break;
1240 default:
1242 OUString aHelpModuleName(GetHelpModuleName_Impl(rURL));
1243 OUString aRealCommand;
1245 if ( nProtocol == INetProtocol::Uno )
1247 // Command can be just an alias to another command.
1248 auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rURL, getCurrentModuleIdentifier_Impl());
1249 aRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties);
1252 // no URL, just a HelpID (maybe empty in case of keyword search)
1253 aHelpURL = CreateHelpURL_Impl( aRealCommand.isEmpty() ? rURL : aRealCommand, aHelpModuleName );
1255 if ( impl_hasHelpInstalled() && pWidget && SfxContentHelper::IsHelpErrorDocument( aHelpURL ) )
1257 bool bUseFinalFallback = true;
1258 // no help found -> try ids of parents.
1259 pWidget->help_hierarchy_foreach([&aHelpModuleName, &aHelpURL, &bUseFinalFallback](const OString& rHelpId){
1260 if (rHelpId.isEmpty())
1261 return false;
1262 aHelpURL = CreateHelpURL( OStringToOUString(rHelpId, RTL_TEXTENCODING_UTF8), aHelpModuleName);
1263 bool bFinished = !SfxContentHelper::IsHelpErrorDocument(aHelpURL);
1264 if (bFinished)
1265 bUseFinalFallback = false;
1266 return bFinished;
1269 if (bUseFinalFallback)
1271 // create help url of start page ( helpid == 0 -> start page)
1272 aHelpURL = CreateHelpURL( OUString(), aHelpModuleName );
1275 break;
1279 if ( comphelper::LibreOfficeKit::isActive() )
1281 impl_showOnlineHelp(aHelpURL, pWidget);
1282 return true;
1284 #ifdef MACOSX
1285 if (@available(macOS 10.14, *)) {
1286 // Workaround: Safari sandboxing prevents it from accessing files in the LibreOffice.app folder
1287 // force online-help instead if Safari is default browser.
1288 CFURLRef pBrowser = LSCopyDefaultApplicationURLForURL(
1289 CFURLCreateWithString(
1290 kCFAllocatorDefault,
1291 static_cast<CFStringRef>(@"https://www.libreoffice.org"),
1292 nullptr),
1293 kLSRolesAll, nullptr);
1294 if([static_cast<NSString*>(CFURLGetString(pBrowser)) isEqualToString:@"file:///Applications/Safari.app/"]) {
1295 impl_showOnlineHelp(aHelpURL, pWidget);
1296 return true;
1299 #endif
1301 // If the HTML or no help is installed, but aHelpURL nevertheless references valid help content,
1302 // that implies that help content belongs to an extension (and thus would not be available
1303 // in neither the offline nor online HTML help); in that case, fall through to the "old-help to
1304 // display" code below:
1305 if (SfxContentHelper::IsHelpErrorDocument(aHelpURL))
1307 if ( impl_hasHTMLHelpInstalled() && impl_showOfflineHelp(aHelpURL, pWidget) )
1309 return true;
1312 if ( !impl_hasHelpInstalled() )
1314 bool bShowOfflineHelpPopUp = officecfg::Office::Common::Help::BuiltInHelpNotInstalledPopUp::get();
1316 TopLevelWindowLocker aBusy;
1318 if(bShowOfflineHelpPopUp)
1320 aBusy.incBusy(pWidget);
1321 HelpManualMessage aQueryBox(pWidget);
1322 short OnlineHelpBox = aQueryBox.run();
1323 bShowOfflineHelpPopUp = OnlineHelpBox != RET_OK;
1324 auto xChanges = comphelper::ConfigurationChanges::create();
1325 officecfg::Office::Common::Help::BuiltInHelpNotInstalledPopUp::set(aQueryBox.GetOfflineHelpPopUp(), xChanges);
1326 xChanges->commit();
1327 aBusy.decBusy();
1329 if(!bShowOfflineHelpPopUp)
1331 if ( impl_showOnlineHelp(aHelpURL, pWidget) )
1332 return true;
1333 else
1335 aBusy.incBusy(pWidget);
1336 NoHelpErrorBox aErrBox(pWidget);
1337 aErrBox.run();
1338 aBusy.decBusy();
1339 return false;
1342 else
1344 return false;
1350 // old-help to display
1351 Reference < XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
1353 // check if help window is still open
1354 // If not, create a new one and return access directly to the internal sub frame showing the help content
1355 // search must be done here; search one desktop level could return an arbitrary frame
1356 Reference< XFrame2 > xHelp(
1357 xDesktop->findFrame( "OFFICE_HELP_TASK", FrameSearchFlag::CHILDREN),
1358 UNO_QUERY);
1359 Reference< XFrame > xHelpContent = xDesktop->findFrame(
1360 "OFFICE_HELP",
1361 FrameSearchFlag::CHILDREN);
1363 SfxHelpWindow_Impl* pHelpWindow = nullptr;
1364 if (!xHelp.is())
1365 pHelpWindow = impl_createHelp(xHelp, xHelpContent);
1366 else
1367 pHelpWindow = static_cast<SfxHelpWindow_Impl*>(VCLUnoHelper::GetWindow(xHelp->getComponentWindow()));
1368 if (!xHelp.is() || !xHelpContent.is() || !pHelpWindow)
1369 return false;
1371 SAL_INFO("sfx.appl", "HelpId = " << aHelpURL);
1373 pHelpWindow->SetHelpURL( aHelpURL );
1374 pHelpWindow->loadHelpContent(aHelpURL);
1375 if (!rKeyword.isEmpty())
1376 pHelpWindow->OpenKeyword( rKeyword );
1378 Reference < css::awt::XTopWindow > xTopWindow( xHelp->getContainerWindow(), UNO_QUERY );
1379 if ( xTopWindow.is() )
1380 xTopWindow->toFront();
1382 return true;
1385 OUString SfxHelp::CreateHelpURL(const OUString& aCommandURL, const OUString& rModuleName)
1387 SfxHelp* pHelp = static_cast< SfxHelp* >(Application::GetHelp());
1388 return pHelp ? SfxHelp::CreateHelpURL_Impl( aCommandURL, rModuleName ) : OUString();
1391 OUString SfxHelp::GetDefaultHelpModule()
1393 return getDefaultModule_Impl();
1396 OUString SfxHelp::GetCurrentModuleIdentifier()
1398 return getCurrentModuleIdentifier_Impl();
1401 bool SfxHelp::IsHelpInstalled()
1403 return impl_hasHelpInstalled();
1406 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */