xref: /core/sfx2/source/appl/sfxhelp.cxx (revision ce83d1f4)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <config_folders.h>
21 #include <sfx2/sfxhelp.hxx>
22 
23 #include <string_view>
24 #include <algorithm>
25 #include <cassert>
26 #ifdef MACOSX
27 #include <premac.h>
28 #include <Foundation/NSString.h>
29 #include <CoreFoundation/CFURL.h>
30 #include <CoreServices/CoreServices.h>
31 #include <postmac.h>
32 #endif
33 
34 #include <sal/log.hxx>
35 #include <com/sun/star/uno/Reference.h>
36 #include <com/sun/star/frame/Desktop.hpp>
37 #include <com/sun/star/frame/UnknownModuleException.hpp>
38 #include <com/sun/star/frame/XFrame2.hpp>
39 #include <comphelper/processfactory.hxx>
40 #include <com/sun/star/awt/XWindow.hpp>
41 #include <com/sun/star/awt/XTopWindow.hpp>
42 #include <com/sun/star/beans/XPropertySet.hpp>
43 #include <com/sun/star/frame/FrameSearchFlag.hpp>
44 #include <toolkit/helper/vclunohelper.hxx>
45 #include <com/sun/star/frame/ModuleManager.hpp>
46 #include <unotools/configmgr.hxx>
47 #include <svtools/helpopt.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 <officecfg/Office/Common.hxx>
55 #include <osl/process.h>
56 #include <osl/file.hxx>
57 #include <unotools/tempfile.hxx>
58 #include <unotools/securityoptions.hxx>
59 #include <rtl/uri.hxx>
60 #include <vcl/commandinfoprovider.hxx>
61 #include <vcl/keycod.hxx>
62 #include <vcl/settings.hxx>
63 #include <vcl/waitobj.hxx>
64 #include <vcl/weld.hxx>
65 #include <openuriexternally.hxx>
66 
67 #include <comphelper/lok.hxx>
68 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
69 #include <sfx2/viewsh.hxx>
70 
71 #include "newhelp.hxx"
72 #include <sfx2/flatpak.hxx>
73 #include <sfx2/sfxresid.hxx>
74 #include <helper.hxx>
75 #include <sfx2/strings.hrc>
76 #include <vcl/svapp.hxx>
77 #include <rtl/string.hxx>
78 #include <svtools/langtab.hxx>
79 #include <tools/diagnose_ex.h>
80 
81 using namespace ::com::sun::star::beans;
82 using namespace ::com::sun::star::frame;
83 using namespace ::com::sun::star::uno;
84 using namespace ::com::sun::star::util;
85 using namespace ::com::sun::star::lang;
86 
87 namespace {
88 
89 class NoHelpErrorBox
90 {
91 private:
92     std::unique_ptr<weld::MessageDialog> m_xErrBox;
93 public:
94     DECL_STATIC_LINK(NoHelpErrorBox, HelpRequestHdl, weld::Widget&, bool);
95 public:
96     explicit NoHelpErrorBox(weld::Widget* pParent)
97         : m_xErrBox(Application::CreateMessageDialog(pParent, VclMessageType::Error, VclButtonsType::Ok,
98                                                      SfxResId(RID_STR_HLPFILENOTEXIST)))
99     {
100         // Error message: "No help available"
101         m_xErrBox->connect_help(LINK(nullptr, NoHelpErrorBox, HelpRequestHdl));
102     }
103     void run()
104     {
105         m_xErrBox->run();
106     }
107 };
108 
109 }
110 
111 IMPL_STATIC_LINK_NOARG(NoHelpErrorBox, HelpRequestHdl, weld::Widget&, bool)
112 {
113     // do nothing, because no help available
114     return false;
115 }
116 
117 static OUString const & HelpLocaleString();
118 
119 namespace {
120 
121 /// Root path of the help.
122 OUString const & getHelpRootURL()
123 {
124     static OUString const s_instURL = [&]()
125     {
126         OUString tmp = officecfg::Office::Common::Path::Current::Help::get(comphelper::getProcessComponentContext());
127         if (tmp.isEmpty())
128         {
129             // try to determine path from default
130             tmp = "$(instpath)/" LIBO_SHARE_HELP_FOLDER;
131         }
132 
133         // replace anything like $(instpath);
134         SvtPathOptions aOptions;
135         tmp = aOptions.SubstituteVariable(tmp);
136 
137         OUString url;
138         if (osl::FileBase::getFileURLFromSystemPath(tmp, url) == osl::FileBase::E_None)
139             tmp = url;
140         return tmp;
141     }();
142     return s_instURL;
143 }
144 
145 bool impl_checkHelpLocalePath(OUString const & rpPath)
146 {
147     osl::DirectoryItem directoryItem;
148     bool bOK = false;
149 
150     osl::FileStatus fileStatus(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL | osl_FileStatus_Mask_FileName);
151     if (osl::DirectoryItem::get(rpPath, directoryItem) == osl::FileBase::E_None &&
152         directoryItem.getFileStatus(fileStatus) == osl::FileBase::E_None &&
153         fileStatus.isDirectory())
154     {
155         bOK = true;
156     }
157     return bOK;
158 }
159 
160 /// Check for built-in help
161 /// Check if help/<lang>/err.html file exist
162 bool impl_hasHelpInstalled()
163 {
164     if (comphelper::LibreOfficeKit::isActive())
165         return false;
166 
167         // detect installed locale
168     static OUString const aLocaleStr = HelpLocaleString();
169 
170     OUString helpRootURL = getHelpRootURL() + "/" + aLocaleStr + "/err.html";
171     bool bOK = false;
172     osl::DirectoryItem directoryItem;
173     if(osl::DirectoryItem::get(helpRootURL, directoryItem) == osl::FileBase::E_None){
174         bOK=true;
175     }
176 
177     SAL_INFO( "sfx.appl", "Checking old help installed " << bOK);
178     return bOK;
179 }
180 
181 /// Check for html built-in help
182 /// Check if help/lang/text folder exist. Only html has it.
183 bool impl_hasHTMLHelpInstalled()
184 {
185     if (comphelper::LibreOfficeKit::isActive())
186         return false;
187 
188     // detect installed locale
189     static OUString const aLocaleStr = HelpLocaleString();
190 
191     OUString helpRootURL = getHelpRootURL() + "/" + aLocaleStr + "/text";
192     bool bOK = impl_checkHelpLocalePath( helpRootURL );
193     SAL_INFO( "sfx.appl", "Checking new help (html) installed " << bOK);
194     return bOK;
195 }
196 
197 } // namespace
198 
199 /// Return the locale we prefer for displaying help
200 static OUString const & HelpLocaleString()
201 {
202     if (comphelper::LibreOfficeKit::isActive())
203         return comphelper::LibreOfficeKit::getLanguageTag().getBcp47();
204 
205     static OUString aLocaleStr;
206     if (!aLocaleStr.isEmpty())
207         return aLocaleStr;
208 
209     const OUString aEnglish("en-US");
210     // detect installed locale
211     aLocaleStr = utl::ConfigManager::getUILocale();
212 
213     if ( aLocaleStr.isEmpty() )
214     {
215         aLocaleStr = aEnglish;
216         return aLocaleStr;
217     }
218 
219     // get fall-back language (country)
220     OUString sLang = aLocaleStr;
221     sal_Int32 nSepPos = sLang.indexOf( '-' );
222     if (nSepPos != -1)
223     {
224         sLang = sLang.copy( 0, nSepPos );
225     }
226     OUString sHelpPath("");
227     sHelpPath = getHelpRootURL() + "/" + utl::ConfigManager::getProductVersion() + "/" + aLocaleStr;
228     if (impl_checkHelpLocalePath(sHelpPath))
229     {
230         return aLocaleStr;
231     }
232     sHelpPath = getHelpRootURL() + "/" + utl::ConfigManager::getProductVersion() + "/" + sLang;
233     if (impl_checkHelpLocalePath(sHelpPath))
234     {
235         aLocaleStr = sLang;
236         return aLocaleStr;
237     }
238     sHelpPath = getHelpRootURL() + "/" + aLocaleStr;
239     if (impl_checkHelpLocalePath(sHelpPath))
240     {
241         return aLocaleStr;
242     }
243     sHelpPath = getHelpRootURL() + "/" + sLang;
244     if (impl_checkHelpLocalePath(sHelpPath))
245     {
246         aLocaleStr = sLang;
247         return aLocaleStr;
248     }
249     sHelpPath = getHelpRootURL() + "/" + utl::ConfigManager::getProductVersion() + "/" + aEnglish;
250     if (impl_checkHelpLocalePath(sHelpPath))
251     {
252         aLocaleStr = aEnglish;
253         return aLocaleStr;
254     }
255     sHelpPath = getHelpRootURL() + "/" + aEnglish;
256     if (impl_checkHelpLocalePath(sHelpPath))
257     {
258         aLocaleStr = aEnglish;
259         return aLocaleStr;
260     }
261     return aLocaleStr;
262 }
263 
264 
265 
266 void AppendConfigToken( OUStringBuffer& rURL, bool bQuestionMark )
267 {
268     OUString aLocaleStr = HelpLocaleString();
269 
270     // query part exists?
271     if ( bQuestionMark )
272         // no, so start with '?'
273         rURL.append('?');
274     else
275         // yes, so only append with '&'
276         rURL.append('&');
277 
278     // set parameters
279     rURL.append("Language=");
280     rURL.append(aLocaleStr);
281     rURL.append("&System=");
282     rURL.append(SvtHelpOptions().GetSystem());
283     rURL.append("&Version=");
284     rURL.append(utl::ConfigManager::getProductVersion());
285 }
286 
287 static bool GetHelpAnchor_Impl( const OUString& _rURL, OUString& _rAnchor )
288 {
289     bool bRet = false;
290 
291     try
292     {
293         ::ucbhelper::Content aCnt( INetURLObject( _rURL ).GetMainURL( INetURLObject::DecodeMechanism::NONE ),
294                              Reference< css::ucb::XCommandEnvironment >(),
295                              comphelper::getProcessComponentContext() );
296         OUString sAnchor;
297         if ( aCnt.getPropertyValue("AnchorName") >>= sAnchor )
298         {
299 
300             if ( !sAnchor.isEmpty() )
301             {
302                 _rAnchor = sAnchor;
303                 bRet = true;
304             }
305         }
306         else
307         {
308             SAL_WARN( "sfx.appl", "Property 'AnchorName' is missing" );
309         }
310     }
311     catch (const css::uno::Exception&)
312     {
313     }
314 
315     return bRet;
316 }
317 
318 namespace {
319 
320 class SfxHelp_Impl
321 {
322 public:
323     static OUString GetHelpText( const OUString& aCommandURL, const OUString& rModule );
324 };
325 
326 }
327 
328 OUString SfxHelp_Impl::GetHelpText( const OUString& aCommandURL, const OUString& rModule )
329 {
330     // create help url
331     OUStringBuffer aHelpURL( SfxHelp::CreateHelpURL( aCommandURL, rModule ) );
332     // added 'active' parameter
333     sal_Int32 nIndex = aHelpURL.lastIndexOf( '#' );
334     if ( nIndex < 0 )
335         nIndex = aHelpURL.getLength();
336     aHelpURL.insert( nIndex, "&Active=true" );
337     // load help string
338     return SfxContentHelper::GetActiveHelpString( aHelpURL.makeStringAndClear() );
339 }
340 
341 SfxHelp::SfxHelp() :
342     bIsDebug( false )
343 {
344     // read the environment variable "HELP_DEBUG"
345     // if it's set, you will see debug output on active help
346     OUString sHelpDebug;
347     OUString sEnvVarName( "HELP_DEBUG"  );
348     osl_getEnvironment( sEnvVarName.pData, &sHelpDebug.pData );
349     bIsDebug = !sHelpDebug.isEmpty();
350 }
351 
352 SfxHelp::~SfxHelp()
353 {
354 }
355 
356 static OUString getDefaultModule_Impl()
357 {
358     OUString sDefaultModule;
359     SvtModuleOptions aModOpt;
360     if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
361         sDefaultModule = "swriter";
362     else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) )
363         sDefaultModule = "scalc";
364     else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) )
365         sDefaultModule = "simpress";
366     else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) )
367         sDefaultModule = "sdraw";
368     else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::MATH ) )
369         sDefaultModule = "smath";
370     else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::CHART ) )
371         sDefaultModule = "schart";
372     else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::BASIC ) )
373         sDefaultModule = "sbasic";
374     else if ( aModOpt.IsModuleInstalled( SvtModuleOptions::EModule::DATABASE ) )
375         sDefaultModule = "sdatabase";
376     else
377     {
378         SAL_WARN( "sfx.appl", "getDefaultModule_Impl(): no module installed" );
379     }
380     return sDefaultModule;
381 }
382 
383 static OUString getCurrentModuleIdentifier_Impl()
384 {
385     OUString sIdentifier;
386     Reference < XComponentContext > xContext = ::comphelper::getProcessComponentContext();
387     Reference < XModuleManager2 > xModuleManager = ModuleManager::create(xContext);
388     Reference < XDesktop2 > xDesktop = Desktop::create(xContext);
389     Reference < XFrame > xCurrentFrame = xDesktop->getCurrentFrame();
390 
391     if ( xCurrentFrame.is() )
392     {
393         try
394         {
395             sIdentifier = xModuleManager->identify( xCurrentFrame );
396         }
397         catch (const css::frame::UnknownModuleException&)
398         {
399             SAL_INFO( "sfx.appl", "SfxHelp::getCurrentModuleIdentifier_Impl(): unknown module (help in help?)" );
400         }
401         catch (const Exception&)
402         {
403             TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelp::getCurrentModuleIdentifier_Impl(): exception of XModuleManager::identify()" );
404         }
405     }
406 
407     return sIdentifier;
408 }
409 
410 namespace
411 {
412     OUString MapModuleIdentifier(const OUString &rFactoryShortName)
413     {
414         OUString aFactoryShortName(rFactoryShortName);
415 
416         // Map some module identifiers to their "real" help module string.
417         if ( aFactoryShortName == "chart2" )
418             aFactoryShortName = "schart" ;
419         else if ( aFactoryShortName == "BasicIDE" )
420             aFactoryShortName = "sbasic";
421         else if ( aFactoryShortName == "sweb"
422                 || aFactoryShortName == "sglobal"
423                 || aFactoryShortName == "swxform" )
424             aFactoryShortName = "swriter" ;
425         else if ( aFactoryShortName == "dbquery"
426                 || aFactoryShortName == "dbbrowser"
427                 || aFactoryShortName == "dbrelation"
428                 || aFactoryShortName == "dbtable"
429                 || aFactoryShortName == "dbapp"
430                 || aFactoryShortName == "dbreport"
431                 || aFactoryShortName == "dbtdata"
432                 || aFactoryShortName == "swreport"
433                 || aFactoryShortName == "swform" )
434             aFactoryShortName = "sdatabase";
435         else if ( aFactoryShortName == "sbibliography"
436                 || aFactoryShortName == "sabpilot"
437                 || aFactoryShortName == "scanner"
438                 || aFactoryShortName == "spropctrlr"
439                 || aFactoryShortName == "StartModule" )
440             aFactoryShortName.clear();
441 
442         return aFactoryShortName;
443     }
444 }
445 
446 OUString SfxHelp::GetHelpModuleName_Impl(const OUString& rHelpID)
447 {
448     OUString aFactoryShortName;
449 
450     //rhbz#1438876 detect preferred module for this help id, e.g. csv dialog
451     //for calc import before any toplevel is created and so context is
452     //otherwise unknown. Cosmetic, same help is shown in any case because its
453     //in the shared section, but title bar would state "Writer" when context is
454     //expected to be "Calc"
455     OUString sRemainder;
456     if (rHelpID.startsWith("modules/", &sRemainder))
457     {
458         sal_Int32 nEndModule = sRemainder.indexOf('/');
459         aFactoryShortName = nEndModule != -1 ? sRemainder.copy(0, nEndModule) : sRemainder;
460     }
461 
462     if (aFactoryShortName.isEmpty())
463     {
464         OUString aModuleIdentifier = getCurrentModuleIdentifier_Impl();
465         if (!aModuleIdentifier.isEmpty())
466         {
467             try
468             {
469                 Reference < XModuleManager2 > xModuleManager(
470                     ModuleManager::create(::comphelper::getProcessComponentContext()) );
471                 Sequence< PropertyValue > lProps;
472                 xModuleManager->getByName( aModuleIdentifier ) >>= lProps;
473                 auto pProp = std::find_if(lProps.begin(), lProps.end(),
474                     [](const PropertyValue& rProp) { return rProp.Name == "ooSetupFactoryShortName"; });
475                 if (pProp != lProps.end())
476                     pProp->Value >>= aFactoryShortName;
477             }
478             catch (const Exception&)
479             {
480                 TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelp::GetHelpModuleName_Impl()" );
481             }
482         }
483     }
484 
485     if (!aFactoryShortName.isEmpty())
486         aFactoryShortName = MapModuleIdentifier(aFactoryShortName);
487     if (aFactoryShortName.isEmpty())
488         aFactoryShortName = getDefaultModule_Impl();
489 
490     return aFactoryShortName;
491 }
492 
493 OUString SfxHelp::CreateHelpURL_Impl( const OUString& aCommandURL, const OUString& rModuleName )
494 {
495     // build up the help URL
496     OUStringBuffer aHelpURL("vnd.sun.star.help://");
497     bool bHasAnchor = false;
498     OUString aAnchor;
499 
500     OUString aModuleName( rModuleName );
501     if (aModuleName.isEmpty())
502         aModuleName = getDefaultModule_Impl();
503 
504     aHelpURL.append(aModuleName);
505 
506     if ( aCommandURL.isEmpty() )
507         aHelpURL.append("/start");
508     else
509     {
510         aHelpURL.append('/');
511         aHelpURL.append(rtl::Uri::encode(aCommandURL,
512                                               rtl_UriCharClassRelSegment,
513                                               rtl_UriEncodeKeepEscapes,
514                                               RTL_TEXTENCODING_UTF8));
515 
516         OUStringBuffer aTempURL = aHelpURL;
517         AppendConfigToken( aTempURL, true );
518         bHasAnchor = GetHelpAnchor_Impl(aTempURL.makeStringAndClear(), aAnchor);
519     }
520 
521     AppendConfigToken( aHelpURL, true );
522 
523     if ( bHasAnchor )
524     {
525         aHelpURL.append('#');
526         aHelpURL.append(aAnchor);
527     }
528 
529     return aHelpURL.makeStringAndClear();
530 }
531 
532 static SfxHelpWindow_Impl* impl_createHelp(Reference< XFrame2 >& rHelpTask   ,
533                                     Reference< XFrame >& rHelpContent)
534 {
535     Reference < XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
536 
537     // otherwise - create new help task
538     Reference< XFrame2 > xHelpTask(
539         xDesktop->findFrame(  "OFFICE_HELP_TASK", FrameSearchFlag::TASKS | FrameSearchFlag::CREATE),
540         UNO_QUERY);
541     if (!xHelpTask.is())
542         return nullptr;
543 
544     // create all internal windows and sub frames ...
545     Reference< css::awt::XWindow >      xParentWindow = xHelpTask->getContainerWindow();
546     VclPtr<vcl::Window>                 pParentWindow = VCLUnoHelper::GetWindow( xParentWindow );
547     VclPtrInstance<SfxHelpWindow_Impl>  pHelpWindow( xHelpTask, pParentWindow );
548     Reference< css::awt::XWindow >      xHelpWindow   = VCLUnoHelper::GetInterface( pHelpWindow );
549 
550     Reference< XFrame > xHelpContent;
551     if (xHelpTask->setComponent( xHelpWindow, Reference< XController >() ))
552     {
553         // Customize UI ...
554         xHelpTask->setName("OFFICE_HELP_TASK");
555 
556         Reference< XPropertySet > xProps(xHelpTask, UNO_QUERY);
557         if (xProps.is())
558             xProps->setPropertyValue(
559                 "Title",
560                 makeAny(SfxResId(STR_HELP_WINDOW_TITLE)));
561 
562         pHelpWindow->setContainerWindow( xParentWindow );
563         xParentWindow->setVisible(true);
564         xHelpWindow->setVisible(true);
565 
566         // This sub frame is created internally (if we called new SfxHelpWindow_Impl() ...)
567         // It should exist :-)
568         xHelpContent = xHelpTask->findFrame("OFFICE_HELP", FrameSearchFlag::CHILDREN);
569     }
570 
571     if (!xHelpContent.is())
572     {
573         pHelpWindow.disposeAndClear();
574         return nullptr;
575     }
576 
577     xHelpContent->setName("OFFICE_HELP");
578 
579     rHelpTask    = xHelpTask;
580     rHelpContent = xHelpContent;
581     return pHelpWindow;
582 }
583 
584 OUString SfxHelp::GetHelpText( const OUString& aCommandURL, const vcl::Window* pWindow )
585 {
586     OUString sModuleName = GetHelpModuleName_Impl(aCommandURL);
587     auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommandURL, getCurrentModuleIdentifier_Impl());
588     OUString sRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties);
589     OUString sHelpText = SfxHelp_Impl::GetHelpText( sRealCommand.isEmpty() ? aCommandURL : sRealCommand, sModuleName );
590 
591     OString aNewHelpId;
592 
593     if (pWindow && sHelpText.isEmpty())
594     {
595         // no help text found -> try with parent help id.
596         vcl::Window* pParent = pWindow->GetParent();
597         while ( pParent )
598         {
599             aNewHelpId = pParent->GetHelpId();
600             sHelpText = SfxHelp_Impl::GetHelpText( OStringToOUString(aNewHelpId, RTL_TEXTENCODING_UTF8), sModuleName );
601             if (!sHelpText.isEmpty())
602                 pParent = nullptr;
603             else
604                 pParent = pParent->GetParent();
605         }
606 
607         if (bIsDebug && sHelpText.isEmpty())
608             aNewHelpId.clear();
609     }
610 
611     // add some debug information?
612     if ( bIsDebug )
613     {
614         sHelpText += "\n-------------\n" +
615             sModuleName + ": " + aCommandURL;
616         if ( !aNewHelpId.isEmpty() )
617         {
618             sHelpText += " - " +
619                 OStringToOUString(aNewHelpId, RTL_TEXTENCODING_UTF8);
620         }
621     }
622 
623     return sHelpText;
624 }
625 
626 OUString SfxHelp::GetHelpText(const OUString& aCommandURL, const weld::Widget* pWidget)
627 {
628     OUString sModuleName = GetHelpModuleName_Impl(aCommandURL);
629     auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommandURL, getCurrentModuleIdentifier_Impl());
630     OUString sRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties);
631     OUString sHelpText = SfxHelp_Impl::GetHelpText( sRealCommand.isEmpty() ? aCommandURL : sRealCommand, sModuleName );
632 
633     OString aNewHelpId;
634 
635     if (pWidget && sHelpText.isEmpty())
636     {
637         // no help text found -> try with parent help id.
638         std::unique_ptr<weld::Widget> xParent(pWidget->weld_parent());
639         while (xParent)
640         {
641             aNewHelpId = xParent->get_help_id();
642             sHelpText = SfxHelp_Impl::GetHelpText( OStringToOUString(aNewHelpId, RTL_TEXTENCODING_UTF8), sModuleName );
643             if (!sHelpText.isEmpty())
644                 xParent.reset();
645             else
646                 xParent = xParent->weld_parent();
647         }
648 
649         if (bIsDebug && sHelpText.isEmpty())
650             aNewHelpId.clear();
651     }
652 
653     // add some debug information?
654     if ( bIsDebug )
655     {
656         sHelpText += "\n-------------\n" +
657             sModuleName + ": " + aCommandURL;
658         if ( !aNewHelpId.isEmpty() )
659         {
660             sHelpText += " - " +
661                 OStringToOUString(aNewHelpId, RTL_TEXTENCODING_UTF8);
662         }
663     }
664 
665     return sHelpText;
666 }
667 
668 OUString SfxHelp::GetURLHelpText(std::u16string_view aURL)
669 {
670     SvtSecurityOptions aSecOpt;
671     bool bCtrlClickHlink = aSecOpt.IsOptionSet(SvtSecurityOptions::EOption::CtrlClickHyperlink);
672 
673     // "ctrl-click to follow link:" for not MacOS
674     // "⌘-click to follow link:" for MacOs
675     vcl::KeyCode aCode(KEY_SPACE);
676     vcl::KeyCode aModifiedCode(KEY_SPACE, KEY_MOD1);
677     OUString aModStr(aModifiedCode.GetName());
678     aModStr = aModStr.replaceFirst(aCode.GetName(), "");
679     aModStr = aModStr.replaceAll("+", "");
680     OUString aHelpStr
681         = bCtrlClickHlink ? SfxResId(STR_CTRLCLICKHYPERLINK) : SfxResId(STR_CLICKHYPERLINK);
682     aHelpStr = aHelpStr.replaceFirst("%{key}", aModStr);
683     aHelpStr = aHelpStr.replaceFirst("%{link}", aURL);
684     return aHelpStr;
685 }
686 
687 void SfxHelp::SearchKeyword( const OUString& rKeyword )
688 {
689     Start_Impl(OUString(), static_cast<weld::Widget*>(nullptr), rKeyword);
690 }
691 
692 bool SfxHelp::Start( const OUString& rURL, const vcl::Window* pWindow )
693 {
694     return Start_Impl( rURL, pWindow, OUString() );
695 }
696 
697 bool SfxHelp::Start(const OUString& rURL, weld::Widget* pWidget)
698 {
699     return Start_Impl(rURL, pWidget, OUString());
700 }
701 
702 /// Redirect the vnd.sun.star.help:// urls to http://help.libreoffice.org
703 static bool impl_showOnlineHelp( const OUString& rURL )
704 {
705     static constexpr OUStringLiteral aInternal(u"vnd.sun.star.help://");
706     if ( rURL.getLength() <= aInternal.getLength() || !rURL.startsWith(aInternal) )
707         return false;
708 
709     OUString aHelpLink = officecfg::Office::Common::Help::HelpRootURL::get();
710     OUString aTarget = OUString::Concat("Target=") + rURL.subView(aInternal.getLength());
711     aTarget = aTarget.replaceAll("%2F", "/").replaceAll("?", "&");
712     aHelpLink += aTarget;
713 
714     if (comphelper::LibreOfficeKit::isActive())
715     {
716         if(SfxViewShell* pViewShell = SfxViewShell::Current())
717         {
718             pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_HYPERLINK_CLICKED,
719                                                    aHelpLink.toUtf8().getStr());
720             return true;
721         }
722         else if (GetpApp())
723         {
724             GetpApp()->libreOfficeKitViewCallback(LOK_CALLBACK_HYPERLINK_CLICKED,
725                                                    aHelpLink.toUtf8().getStr());
726             return true;
727         }
728 
729         return false;
730     }
731 
732     try
733     {
734 #ifdef MACOSX
735         LSOpenCFURLRef(CFURLCreateWithString(kCFAllocatorDefault,
736                            CFStringCreateWithCString(kCFAllocatorDefault,
737                                aHelpLink.toUtf8().getStr(),
738                                kCFStringEncodingUTF8),
739                            nullptr),
740             nullptr);
741 #else
742         sfx2::openUriExternally(aHelpLink, false);
743 #endif
744         return true;
745     }
746     catch (const Exception&)
747     {
748     }
749     return false;
750 }
751 
752 namespace {
753 
754 bool rewriteFlatpakHelpRootUrl(OUString * helpRootUrl) {
755     assert(helpRootUrl != nullptr);
756     //TODO: this function for now assumes that the passed-in *helpRootUrl references
757     // /app/libreoffice/help (which belongs to the org.libreoffice.LibreOffice.Help
758     // extension); it replaces it with the corresponding file URL as seen outside the flatpak
759     // sandbox:
760     struct Failure: public std::exception {};
761     try {
762         static auto const url = [] {
763             // From /.flatpak-info [Instance] section, read
764             //   app-path=<path>
765             //   app-extensions=...;org.libreoffice.LibreOffice.Help=<sha>;...
766             // lines:
767             osl::File ini("file:///.flatpak-info");
768             auto err = ini.open(osl_File_OpenFlag_Read);
769             if (err != osl::FileBase::E_None) {
770                 SAL_WARN("sfx.appl", "LIBO_FLATPAK mode failure opening /.flatpak-info: " << err);
771                 throw Failure();
772             }
773             OUString path;
774             OUString extensions;
775             bool havePath = false;
776             bool haveExtensions = false;
777             for (bool instance = false; !(havePath && haveExtensions);) {
778                 rtl::ByteSequence bytes;
779                 err = ini.readLine(bytes);
780                 if (err != osl::FileBase::E_None) {
781                     SAL_WARN(
782                         "sfx.appl",
783                         "LIBO_FLATPAK mode reading /.flatpak-info fails with " << err
784                             << " before [Instance] app-path");
785                     throw Failure();
786                 }
787                 std::string_view const line(
788                     reinterpret_cast<char const *>(bytes.getConstArray()), bytes.getLength());
789                 if (instance) {
790                     static constexpr auto keyPath = std::string_view("app-path=");
791                     static constexpr auto keyExtensions = std::string_view("app-extensions=");
792                     if (!havePath && line.length() >= keyPath.size()
793                         && line.substr(0, keyPath.size()) == keyPath.data())
794                     {
795                         auto const value = line.substr(keyPath.size());
796                         if (!rtl_convertStringToUString(
797                                 &path.pData, value.data(), value.length(),
798                                 osl_getThreadTextEncoding(),
799                                 (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR
800                                  | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR
801                                  | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
802                         {
803                             SAL_WARN(
804                                 "sfx.appl",
805                                 "LIBO_FLATPAK mode failure converting app-path \"" << value
806                                     << "\" encoding");
807                             throw Failure();
808                         }
809                         havePath = true;
810                     } else if (!haveExtensions && line.length() >= keyExtensions.size()
811                                && line.substr(0, keyExtensions.size()) == keyExtensions.data())
812                     {
813                         auto const value = line.substr(keyExtensions.size());
814                         if (!rtl_convertStringToUString(
815                                 &extensions.pData, value.data(), value.length(),
816                                 osl_getThreadTextEncoding(),
817                                 (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR
818                                  | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR
819                                  | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
820                         {
821                             SAL_WARN(
822                                 "sfx.appl",
823                                 "LIBO_FLATPAK mode failure converting app-extensions \"" << value
824                                     << "\" encoding");
825                             throw Failure();
826                         }
827                         haveExtensions = true;
828                     } else if (line.length() > 0 && line[0] == '[') {
829                         SAL_WARN(
830                             "sfx.appl",
831                             "LIBO_FLATPAK mode /.flatpak-info lacks [Instance] app-path and"
832                                 " app-extensions");
833                         throw Failure();
834                     }
835                 } else if (line == "[Instance]") {
836                     instance = true;
837                 }
838             }
839             ini.close();
840             // Extract <sha> from ...;org.libreoffice.LibreOffice.Help=<sha>;...:
841             OUString sha;
842             for (sal_Int32 i = 0;;) {
843                 OUString elem = extensions.getToken(0, ';', i);
844                 if (elem.startsWith("org.libreoffice.LibreOffice.Help=", &sha)) {
845                     break;
846                 }
847                 if (i == -1) {
848                     SAL_WARN(
849                         "sfx.appl",
850                         "LIBO_FLATPAK mode /.flatpak-info [Instance] app-extensions \""
851                             << extensions << "\" org.libreoffice.LibreOffice.Help");
852                     throw Failure();
853                 }
854             }
855             // Assuming that <path> is of the form
856             //   /.../app/org.libreoffice.LibreOffice/<arch>/<branch>/<sha'>/files
857             // rewrite it as
858             //   /.../runtime/org.libreoffice.LibreOffice.Help/<arch>/<branch>/<sha>/files
859             // because the extension's files are stored at a different place than the app's files,
860             // so use this hack until flatpak itself provides a better solution:
861             static constexpr auto segments = OUStringLiteral(u"/app/org.libreoffice.LibreOffice/");
862             auto const i1 = path.lastIndexOf(segments);
863                 // use lastIndexOf instead of indexOf, in case the user-controlled prefix /.../
864                 // happens to contain such segments
865             if (i1 == -1) {
866                 SAL_WARN(
867                     "sfx.appl",
868                     "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path
869                         << "\" doesn't contain /app/org.libreoffice.LibreOffice/");
870                 throw Failure();
871             }
872             auto const i2 = i1 + segments.getLength();
873             auto i3 = path.indexOf('/', i2);
874             if (i3 == -1) {
875                 SAL_WARN(
876                     "sfx.appl",
877                     "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path
878                         << "\" doesn't contain branch segment");
879                 throw Failure();
880             }
881             i3 = path.indexOf('/', i3 + 1);
882             if (i3 == -1) {
883                 SAL_WARN(
884                     "sfx.appl",
885                     "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path
886                         << "\" doesn't contain sha segment");
887                 throw Failure();
888             }
889             ++i3;
890             auto const i4 = path.indexOf('/', i3);
891             if (i4 == -1) {
892                 SAL_WARN(
893                     "sfx.appl",
894                     "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path
895                         << "\" doesn't contain files segment");
896                 throw Failure();
897             }
898             path = path.subView(0, i1) + OUString::Concat("/runtime/org.libreoffice.LibreOffice.Help/")
899                 + path.subView(i2, i3 - i2) + sha + path.subView(i4);
900             // Turn <path> into a file URL:
901             OUString url_;
902             err = osl::FileBase::getFileURLFromSystemPath(path, url_);
903             if (err != osl::FileBase::E_None) {
904                 SAL_WARN(
905                     "sfx.appl",
906                     "LIBO_FLATPAK mode failure converting app-path \"" << path << "\" to URL: "
907                         << err);
908                 throw Failure();
909             }
910             return url_;
911         }();
912         *helpRootUrl = url;
913         return true;
914     } catch (Failure &) {
915         return false;
916     }
917 }
918 
919 }
920 
921 // add <noscript> meta for browsers without javascript
922 
923 #define SHTML1 "<!DOCTYPE HTML><html lang=\"en-US\"><head><meta charset=\"UTF-8\">"
924 #define SHTML2 "<noscript><meta http-equiv=\"refresh\" content=\"0; url='"
925 #define SHTML3 "/noscript.html'\"></noscript><meta http-equiv=\"refresh\" content=\"1; url='"
926 #define SHTML4 "'\"><script type=\"text/javascript\"> window.location.href = \""
927 #define SHTML5 "\";</script><title>Help Page Redirection</title></head><body></body></html>"
928 
929 // use a tempfile since e.g. xdg-open doesn't support URL-parameters with file:// URLs
930 static bool impl_showOfflineHelp( const OUString& rURL )
931 {
932     OUString aBaseInstallPath = getHelpRootURL();
933     // For the flatpak case, find the pathname outside the flatpak sandbox that corresponds to
934     // aBaseInstallPath, because that is what needs to be stored in aTempFile below:
935     if (flatpak::isFlatpak() && !rewriteFlatpakHelpRootUrl(&aBaseInstallPath)) {
936         return false;
937     }
938 
939     OUString aHelpLink( aBaseInstallPath + "/index.html?" );
940     OUString aTarget = OUString::Concat("Target=") + rURL.subView(RTL_CONSTASCII_LENGTH("vnd.sun.star.help://"));
941     aTarget = aTarget.replaceAll("%2F","/").replaceAll("?","&");
942     aHelpLink += aTarget;
943 
944     // Get a html tempfile (for the flatpak case, create it in XDG_CACHE_HOME instead of /tmp for
945     // technical reasons, so that it can be accessed by the browser running outside the sandbox):
946     OUString const aExtension(".html");
947     OUString * parent = nullptr;
948     if (flatpak::isFlatpak() && !flatpak::createTemporaryHtmlDirectory(&parent)) {
949         return false;
950     }
951     ::utl::TempFile aTempFile("NewHelp", true, &aExtension, parent, false );
952 
953     SvStream* pStream = aTempFile.GetStream(StreamMode::WRITE);
954     pStream->SetStreamCharSet(RTL_TEXTENCODING_UTF8);
955 
956     OUString aTempStr = SHTML1 SHTML2 +
957         aBaseInstallPath + "/" + HelpLocaleString() + SHTML3 +
958         aHelpLink + SHTML4 +
959         aHelpLink + SHTML5;
960 
961     pStream->WriteUnicodeOrByteText(aTempStr);
962 
963     aTempFile.CloseStream();
964     try
965     {
966 #ifdef MACOSX
967         LSOpenCFURLRef(CFURLCreateWithString(kCFAllocatorDefault,
968                            CFStringCreateWithCString(kCFAllocatorDefault,
969                                aTempFile.GetURL().toUtf8().getStr(),
970                                kCFStringEncodingUTF8),
971                            nullptr),
972             nullptr);
973 #else
974         sfx2::openUriExternally(aTempFile.GetURL(), false);
975 #endif
976         return true;
977     }
978     catch (const Exception&)
979     {
980     }
981     aTempFile.EnableKillingFile();
982     return false;
983 }
984 
985 namespace
986 {
987     // tdf#119579 skip floating windows as potential parent for missing help dialog
988     const vcl::Window* GetBestParent(const vcl::Window* pWindow)
989     {
990         while (pWindow)
991         {
992             if (pWindow->IsSystemWindow() && pWindow->GetType() != WindowType::FLOATINGWINDOW)
993                 break;
994             pWindow = pWindow->GetParent();
995         }
996         return pWindow;
997     }
998 }
999 
1000 namespace {
1001 
1002 class HelpManualMessage : public weld::MessageDialogController
1003 {
1004 private:
1005     std::unique_ptr<weld::CheckButton> m_xHideOfflineHelpCB;
1006 
1007 public:
1008     HelpManualMessage(weld::Widget* pParent)
1009         : MessageDialogController(pParent, "sfx/ui/helpmanual.ui", "onlinehelpmanual", "hidedialog")
1010         , m_xHideOfflineHelpCB(m_xBuilder->weld_check_button("hidedialog"))
1011     {
1012         LanguageTag aLangTag = Application::GetSettings().GetUILanguageTag();
1013         OUString sLocaleString = SvtLanguageTable::GetLanguageString(aLangTag.getLanguageType());
1014         OUString sPrimText = get_primary_text();
1015         set_primary_text(sPrimText.replaceAll("$UILOCALE", sLocaleString));
1016     }
1017 
1018     bool GetOfflineHelpPopUp() const { return !m_xHideOfflineHelpCB->get_active(); }
1019 };
1020 
1021 }
1022 
1023 bool SfxHelp::Start_Impl(const OUString& rURL, const vcl::Window* pWindow, const OUString& rKeyword)
1024 {
1025     OUStringBuffer aHelpRootURL("vnd.sun.star.help://");
1026     AppendConfigToken(aHelpRootURL, true);
1027     SfxContentHelper::GetResultSet(aHelpRootURL.makeStringAndClear());
1028 
1029     /* rURL may be
1030      *       - a "real" URL
1031      *       - a HelpID (formerly a long, now a string)
1032      *      If rURL is a URL, CreateHelpURL should be called for this URL
1033      *      If rURL is an arbitrary string, the same should happen, but the URL should be tried out
1034      *      if it delivers real help content. In case only the Help Error Document is returned, the
1035      *      parent of the window for that help was called, is asked for its HelpID.
1036      *      For compatibility reasons this upward search is not implemented for "real" URLs.
1037      *      Help keyword search now is implemented as own method; in former versions it
1038      *      was done via Help::Start, but this implementation conflicted with the upward search.
1039      */
1040     OUString aHelpURL;
1041     INetURLObject aParser( rURL );
1042     INetProtocol nProtocol = aParser.GetProtocol();
1043 
1044     switch ( nProtocol )
1045     {
1046         case INetProtocol::VndSunStarHelp:
1047             // already a vnd.sun.star.help URL -> nothing to do
1048             aHelpURL = rURL;
1049             break;
1050         default:
1051         {
1052             OUString aHelpModuleName(GetHelpModuleName_Impl(rURL));
1053             OUString aRealCommand;
1054 
1055             if ( nProtocol == INetProtocol::Uno )
1056             {
1057                 // Command can be just an alias to another command.
1058                 auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rURL, getCurrentModuleIdentifier_Impl());
1059                 aRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties);
1060             }
1061 
1062             // no URL, just a HelpID (maybe empty in case of keyword search)
1063             aHelpURL = CreateHelpURL_Impl( aRealCommand.isEmpty() ? rURL : aRealCommand, aHelpModuleName );
1064 
1065             if ( impl_hasHelpInstalled() && pWindow && SfxContentHelper::IsHelpErrorDocument( aHelpURL ) )
1066             {
1067                 // no help found -> try with parent help id.
1068                 vcl::Window* pParent = pWindow->GetParent();
1069                 while ( pParent )
1070                 {
1071                     OString aHelpId = pParent->GetHelpId();
1072                     aHelpURL = CreateHelpURL( OStringToOUString(aHelpId, RTL_TEXTENCODING_UTF8), aHelpModuleName );
1073 
1074                     if ( !SfxContentHelper::IsHelpErrorDocument( aHelpURL ) )
1075                     {
1076                         break;
1077                     }
1078                     else
1079                     {
1080                         pParent = pParent->GetParent();
1081                         if (!pParent)
1082                         {
1083                             // create help url of start page ( helpid == 0 -> start page)
1084                             aHelpURL = CreateHelpURL( OUString(), aHelpModuleName );
1085                         }
1086                     }
1087                 }
1088             }
1089             break;
1090         }
1091     }
1092 
1093     if ( comphelper::LibreOfficeKit::isActive() )
1094     {
1095         impl_showOnlineHelp( aHelpURL );
1096         return true;
1097     }
1098 #ifdef MACOSX
1099     if (@available(macOS 10.14, *)) {
1100         // Workaround: Safari sandboxing prevents it from accessing files in the LibreOffice.app folder
1101         // force online-help instead if Safari is default browser.
1102         CFURLRef pBrowser = LSCopyDefaultApplicationURLForURL(
1103                                 CFURLCreateWithString(
1104                                     kCFAllocatorDefault,
1105                                     static_cast<CFStringRef>(@"https://www.libreoffice.org"),
1106                                     nullptr),
1107                                 kLSRolesAll, nullptr);
1108         if([static_cast<NSString*>(CFURLGetString(pBrowser)) isEqualToString:@"file:///Applications/Safari.app/"]) {
1109             impl_showOnlineHelp( aHelpURL );
1110             return true;
1111         }
1112     }
1113 #endif
1114 
1115     // If the HTML or no help is installed, but aHelpURL nevertheless references valid help content,
1116     // that implies that this help content belongs to an extension (and thus would not be available
1117     // in neither the offline nor online HTML help); in that case, fall through to the "old-help to
1118     // display" code below:
1119     if (SfxContentHelper::IsHelpErrorDocument(aHelpURL))
1120     {
1121         if ( impl_hasHTMLHelpInstalled() && impl_showOfflineHelp(aHelpURL) )
1122         {
1123             return true;
1124         }
1125 
1126         if ( !impl_hasHelpInstalled() )
1127         {
1128             SvtHelpOptions aHelpOptions;
1129             bool bShowOfflineHelpPopUp = aHelpOptions.IsOfflineHelpPopUp();
1130 
1131             pWindow = GetBestParent(pWindow);
1132 
1133             TopLevelWindowLocker aBusy;
1134 
1135             if(bShowOfflineHelpPopUp)
1136             {
1137                 weld::Window* pWeldWindow = pWindow ? pWindow->GetFrameWeld() : nullptr;
1138                 aBusy.incBusy(pWeldWindow);
1139                 HelpManualMessage aQueryBox(pWeldWindow);
1140                 short OnlineHelpBox = aQueryBox.run();
1141                 bShowOfflineHelpPopUp = OnlineHelpBox != RET_OK;
1142                 aHelpOptions.SetOfflineHelpPopUp(aQueryBox.GetOfflineHelpPopUp());
1143                 aBusy.decBusy();
1144             }
1145             if(!bShowOfflineHelpPopUp)
1146             {
1147                 if ( impl_showOnlineHelp( aHelpURL ) )
1148                     return true;
1149                 else
1150                 {
1151                     weld::Window* pWeldWindow = pWindow ? pWindow->GetFrameWeld() : nullptr;
1152                     aBusy.incBusy(pWeldWindow);
1153                     NoHelpErrorBox aErrBox(pWeldWindow);
1154                     aErrBox.run();
1155                     aBusy.decBusy();
1156                     return false;
1157                 }
1158             }
1159             else
1160             {
1161                 return false;
1162             }
1163         }
1164     }
1165 
1166     // old-help to display
1167     Reference < XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
1168 
1169     // check if help window is still open
1170     // If not, create a new one and return access directly to the internal sub frame showing the help content
1171     // search must be done here; search one desktop level could return an arbitrary frame
1172     Reference< XFrame2 > xHelp(
1173         xDesktop->findFrame( "OFFICE_HELP_TASK", FrameSearchFlag::CHILDREN),
1174                                UNO_QUERY);
1175     Reference< XFrame > xHelpContent = xDesktop->findFrame(
1176         "OFFICE_HELP",
1177         FrameSearchFlag::CHILDREN);
1178 
1179     SfxHelpWindow_Impl* pHelpWindow = nullptr;
1180     if (!xHelp.is())
1181         pHelpWindow = impl_createHelp(xHelp, xHelpContent);
1182     else
1183         pHelpWindow = static_cast<SfxHelpWindow_Impl*>(VCLUnoHelper::GetWindow(xHelp->getComponentWindow()));
1184     if (!xHelp.is() || !xHelpContent.is() || !pHelpWindow)
1185         return false;
1186 
1187     SAL_INFO("sfx.appl", "HelpId = " << aHelpURL);
1188 
1189     pHelpWindow->SetHelpURL( aHelpURL );
1190     pHelpWindow->loadHelpContent(aHelpURL);
1191     if (!rKeyword.isEmpty())
1192         pHelpWindow->OpenKeyword( rKeyword );
1193 
1194     Reference < css::awt::XTopWindow > xTopWindow( xHelp->getContainerWindow(), UNO_QUERY );
1195     if ( xTopWindow.is() )
1196         xTopWindow->toFront();
1197 
1198     return true;
1199 }
1200 
1201 bool SfxHelp::Start_Impl(const OUString& rURL, weld::Widget* pWidget, const OUString& rKeyword)
1202 {
1203     OUStringBuffer aHelpRootURL("vnd.sun.star.help://");
1204     AppendConfigToken(aHelpRootURL, true);
1205     SfxContentHelper::GetResultSet(aHelpRootURL.makeStringAndClear());
1206 
1207     /* rURL may be
1208      *       - a "real" URL
1209      *       - a HelpID (formerly a long, now a string)
1210      *      If rURL is a URL, CreateHelpURL should be called for this URL
1211      *      If rURL is an arbitrary string, the same should happen, but the URL should be tried out
1212      *      if it delivers real help content. In case only the Help Error Document is returned, the
1213      *      parent of the window for that help was called, is asked for its HelpID.
1214      *      For compatibility reasons this upward search is not implemented for "real" URLs.
1215      *      Help keyword search now is implemented as own method; in former versions it
1216      *      was done via Help::Start, but this implementation conflicted with the upward search.
1217      */
1218     OUString aHelpURL;
1219     INetURLObject aParser( rURL );
1220     INetProtocol nProtocol = aParser.GetProtocol();
1221 
1222     switch ( nProtocol )
1223     {
1224         case INetProtocol::VndSunStarHelp:
1225             // already a vnd.sun.star.help URL -> nothing to do
1226             aHelpURL = rURL;
1227             break;
1228         default:
1229         {
1230             OUString aHelpModuleName(GetHelpModuleName_Impl(rURL));
1231             OUString aRealCommand;
1232 
1233             if ( nProtocol == INetProtocol::Uno )
1234             {
1235                 // Command can be just an alias to another command.
1236                 auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rURL, getCurrentModuleIdentifier_Impl());
1237                 aRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties);
1238             }
1239 
1240             // no URL, just a HelpID (maybe empty in case of keyword search)
1241             aHelpURL = CreateHelpURL_Impl( aRealCommand.isEmpty() ? rURL : aRealCommand, aHelpModuleName );
1242 
1243             if ( impl_hasHelpInstalled() && pWidget && SfxContentHelper::IsHelpErrorDocument( aHelpURL ) )
1244             {
1245                 bool bUseFinalFallback = true;
1246                 // no help found -> try ids of parents.
1247                 pWidget->help_hierarchy_foreach([&aHelpModuleName, &aHelpURL, &bUseFinalFallback](const OString& rHelpId){
1248                     if (rHelpId.isEmpty())
1249                         return false;
1250                     aHelpURL = CreateHelpURL( OStringToOUString(rHelpId, RTL_TEXTENCODING_UTF8), aHelpModuleName);
1251                     bool bFinished = !SfxContentHelper::IsHelpErrorDocument(aHelpURL);
1252                     if (bFinished)
1253                         bUseFinalFallback = false;
1254                     return bFinished;
1255                 });
1256 
1257                 if (bUseFinalFallback)
1258                 {
1259                     // create help url of start page ( helpid == 0 -> start page)
1260                     aHelpURL = CreateHelpURL( OUString(), aHelpModuleName );
1261                 }
1262             }
1263             break;
1264         }
1265     }
1266 
1267     if ( comphelper::LibreOfficeKit::isActive() )
1268     {
1269         impl_showOnlineHelp( aHelpURL );
1270         return true;
1271     }
1272 
1273     // If the HTML or no help is installed, but aHelpURL nevertheless references valid help content,
1274     // that implies that help content belongs to an extension (and thus would not be available
1275     // in neither the offline nor online HTML help); in that case, fall through to the "old-help to
1276     // display" code below:
1277     if (SfxContentHelper::IsHelpErrorDocument(aHelpURL))
1278     {
1279         if ( impl_hasHTMLHelpInstalled() && impl_showOfflineHelp(aHelpURL) )
1280         {
1281             return true;
1282         }
1283 
1284         if ( !impl_hasHelpInstalled() )
1285         {
1286             SvtHelpOptions aHelpOptions;
1287             bool bShowOfflineHelpPopUp = aHelpOptions.IsOfflineHelpPopUp();
1288 
1289             TopLevelWindowLocker aBusy;
1290 
1291             if(bShowOfflineHelpPopUp)
1292             {
1293                 aBusy.incBusy(pWidget);
1294                 HelpManualMessage aQueryBox(pWidget);
1295                 short OnlineHelpBox = aQueryBox.run();
1296                 bShowOfflineHelpPopUp = OnlineHelpBox != RET_OK;
1297                 aHelpOptions.SetOfflineHelpPopUp(aQueryBox.GetOfflineHelpPopUp());
1298                 aBusy.decBusy();
1299             }
1300             if(!bShowOfflineHelpPopUp)
1301             {
1302                 if ( impl_showOnlineHelp( aHelpURL ) )
1303                     return true;
1304                 else
1305                 {
1306                     aBusy.incBusy(pWidget);
1307                     NoHelpErrorBox aErrBox(pWidget);
1308                     aErrBox.run();
1309                     aBusy.decBusy();
1310                     return false;
1311                 }
1312             }
1313             else
1314             {
1315                 return false;
1316             }
1317 
1318         }
1319     }
1320 
1321     // old-help to display
1322     Reference < XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
1323 
1324     // check if help window is still open
1325     // If not, create a new one and return access directly to the internal sub frame showing the help content
1326     // search must be done here; search one desktop level could return an arbitrary frame
1327     Reference< XFrame2 > xHelp(
1328         xDesktop->findFrame( "OFFICE_HELP_TASK", FrameSearchFlag::CHILDREN),
1329                                UNO_QUERY);
1330     Reference< XFrame > xHelpContent = xDesktop->findFrame(
1331         "OFFICE_HELP",
1332         FrameSearchFlag::CHILDREN);
1333 
1334     SfxHelpWindow_Impl* pHelpWindow = nullptr;
1335     if (!xHelp.is())
1336         pHelpWindow = impl_createHelp(xHelp, xHelpContent);
1337     else
1338         pHelpWindow = static_cast<SfxHelpWindow_Impl*>(VCLUnoHelper::GetWindow(xHelp->getComponentWindow()));
1339     if (!xHelp.is() || !xHelpContent.is() || !pHelpWindow)
1340         return false;
1341 
1342     SAL_INFO("sfx.appl", "HelpId = " << aHelpURL);
1343 
1344     pHelpWindow->SetHelpURL( aHelpURL );
1345     pHelpWindow->loadHelpContent(aHelpURL);
1346     if (!rKeyword.isEmpty())
1347         pHelpWindow->OpenKeyword( rKeyword );
1348 
1349     Reference < css::awt::XTopWindow > xTopWindow( xHelp->getContainerWindow(), UNO_QUERY );
1350     if ( xTopWindow.is() )
1351         xTopWindow->toFront();
1352 
1353     return true;
1354 }
1355 
1356 OUString SfxHelp::CreateHelpURL(const OUString& aCommandURL, const OUString& rModuleName)
1357 {
1358     SfxHelp* pHelp = static_cast< SfxHelp* >(Application::GetHelp());
1359     return pHelp ? SfxHelp::CreateHelpURL_Impl( aCommandURL, rModuleName ) : OUString();
1360 }
1361 
1362 OUString SfxHelp::GetDefaultHelpModule()
1363 {
1364     return getDefaultModule_Impl();
1365 }
1366 
1367 OUString SfxHelp::GetCurrentModuleIdentifier()
1368 {
1369     return getCurrentModuleIdentifier_Impl();
1370 }
1371 
1372 bool SfxHelp::IsHelpInstalled()
1373 {
1374     return impl_hasHelpInstalled();
1375 }
1376 
1377 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1378