Coverage Report

Created: 2026-02-14 09:37

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