Coverage Report

Created: 2025-12-08 09:28

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