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