diff options
Diffstat (limited to 'sfx2/source/doc/doctemplates.cxx')
-rw-r--r-- | sfx2/source/doc/doctemplates.cxx | 2734 |
1 files changed, 2734 insertions, 0 deletions
diff --git a/sfx2/source/doc/doctemplates.cxx b/sfx2/source/doc/doctemplates.cxx new file mode 100644 index 000000000..6005aa5fd --- /dev/null +++ b/sfx2/source/doc/doctemplates.cxx @@ -0,0 +1,2734 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <osl/mutex.hxx> +#include <tools/diagnose_ex.h> +#include <tools/urlobj.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <vcl/svapp.hxx> +#include <vcl/wrkwin.hxx> +#include <unotools/pathoptions.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/string.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <com/sun/star/beans/IllegalTypeException.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyExistException.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/beans/XPropertyContainer.hpp> +#include <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <com/sun/star/util/theMacroExpander.hpp> +#include <com/sun/star/util/theOfficeInstallationDirectories.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/document/XTypeDetection.hpp> +#include <com/sun/star/document/DocumentProperties.hpp> +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/ucb/NameClashException.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/ucb/XContentAccess.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/ucb/XProgressHandler.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/frame/XDocumentTemplates.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/lang/XLocalizable.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/ucb/XContent.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/util/thePathSettings.hpp> + +#include <svtools/templatefoldercache.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/ucbhelper.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <ucbhelper/content.hxx> +#include <o3tl/string_view.hxx> + +#include <sfx2/sfxresid.hxx> +#include <sfxurlrelocator.hxx> +#include "doctemplateslocal.hxx" +#include <sfx2/docfac.hxx> +#include <sfx2/strings.hrc> +#include <doctempl.hrc> + +#include <memory> +#include <vector> + +constexpr OUStringLiteral SERVICENAME_TYPEDETECTION = u"com.sun.star.document.TypeDetection"; + +constexpr OUStringLiteral TEMPLATE_ROOT_URL = u"vnd.sun.star.hier:/templates"; +constexpr OUStringLiteral TITLE = u"Title"; +constexpr OUStringLiteral IS_FOLDER = u"IsFolder"; +constexpr OUStringLiteral IS_DOCUMENT = u"IsDocument"; +constexpr OUStringLiteral TARGET_URL = u"TargetURL"; +constexpr OUStringLiteral TEMPLATE_VERSION = u"TemplateComponentVersion"; +constexpr OUStringLiteral TEMPLATE_VERSION_VALUE = u"2"; +constexpr OUStringLiteral TYPE_FOLDER = u"application/vnd.sun.star.hier-folder"; +constexpr OUStringLiteral TYPE_LINK = u"application/vnd.sun.star.hier-link"; +constexpr OUStringLiteral TYPE_FSYS_FOLDER = u"application/vnd.sun.staroffice.fsys-folder"; +constexpr OUStringLiteral TYPE_FSYS_FILE = u"application/vnd.sun.staroffice.fsys-file"; + +constexpr OUStringLiteral PROPERTY_DIRLIST = u"DirectoryList"; +constexpr OUStringLiteral PROPERTY_NEEDSUPDATE = u"NeedsUpdate"; +constexpr OUStringLiteral PROPERTY_TYPE = u"TypeDescription"; + +constexpr OUStringLiteral TARGET_DIR_URL = u"TargetDirURL"; +constexpr OUStringLiteral COMMAND_DELETE = u"delete"; + +constexpr OUStringLiteral STANDARD_FOLDER = u"standard"; + +#define C_DELIM ';' + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::document; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::util; + +using namespace ::ucbhelper; +using namespace ::comphelper; + +using ::std::vector; + +namespace { + +class WaitWindow_Impl : public WorkWindow +{ + tools::Rectangle maRect; + OUString maText; + static constexpr DrawTextFlags gnTextStyle = DrawTextFlags::Center | DrawTextFlags::VCenter | DrawTextFlags::WordBreak | DrawTextFlags::MultiLine; + +public: + WaitWindow_Impl(); + virtual ~WaitWindow_Impl() override; + virtual void dispose() override; + virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override; +}; + +#define X_OFFSET 15 +#define Y_OFFSET 15 + + +struct NamePair_Impl +{ + OUString maShortName; + OUString maLongName; +}; + +class DocTemplates_EntryData_Impl; +class GroupData_Impl; + +typedef vector< std::unique_ptr<GroupData_Impl> > GroupList_Impl; + + +class TplTaskEnvironment : public ::cppu::WeakImplHelper< ucb::XCommandEnvironment > +{ + uno::Reference< task::XInteractionHandler > m_xInteractionHandler; + +public: + explicit TplTaskEnvironment( const uno::Reference< task::XInteractionHandler>& rxInteractionHandler ) + : m_xInteractionHandler( rxInteractionHandler ) + {} + + virtual uno::Reference<task::XInteractionHandler> SAL_CALL getInteractionHandler() override + { return m_xInteractionHandler; } + + virtual uno::Reference<ucb::XProgressHandler> SAL_CALL getProgressHandler() override + { return uno::Reference<ucb::XProgressHandler>(); } +}; + +class SfxDocTplService_Impl +{ + uno::Reference< XComponentContext > mxContext; + uno::Reference< XCommandEnvironment > maCmdEnv; + uno::Reference< XDocumentProperties> m_xDocProps; + uno::Reference< XTypeDetection > mxType; + + ::osl::Mutex maMutex; + Sequence< OUString > maTemplateDirs; + Sequence< OUString > maInternalTemplateDirs; + OUString maRootURL; + std::vector< NamePair_Impl > maNames; + lang::Locale maLocale; + Content maRootContent; + bool mbIsInitialized : 1; + bool mbLocaleSet : 1; + + SfxURLRelocator_Impl maRelocator; + + void init_Impl(); + void getDefaultLocale(); + void getDirList(); + void readFolderList(); + bool needsUpdate(); + OUString getLongName( const OUString& rShortName ); + bool setTitleForURL( const OUString& rURL, const OUString& aTitle ); + void getTitleFromURL( const OUString& rURL, OUString& aTitle, OUString& aType, bool& bDocHasTitle ); + + bool addEntry( Content& rParentFolder, + const OUString& rTitle, + const OUString& rTargetURL, + const OUString& rType ); + + bool createFolder( const OUString& rNewFolderURL, + bool bCreateParent, + bool bFsysFolder, + Content &rNewFolder ); + + static bool CreateNewUniqueFolderWithPrefix( std::u16string_view aPath, + const OUString& aPrefix, + OUString& aNewFolderName, + OUString& aNewFolderURL, + Content& aNewFolder ); + static OUString CreateNewUniqueFileWithPrefix( std::u16string_view aPath, + const OUString& aPrefix, + const OUString& aExt ); + + std::vector< beans::StringPair > ReadUINamesForTemplateDir_Impl( std::u16string_view aUserPath ); + bool UpdateUINamesForTemplateDir_Impl( std::u16string_view aUserPath, + const OUString& aGroupName, + const OUString& aNewFolderName ); + bool ReplaceUINamesForTemplateDir_Impl( std::u16string_view aUserPath, + const OUString& aFsysGroupName, + std::u16string_view aOldGroupName, + const OUString& aNewGroupName ); + void RemoveUINamesForTemplateDir_Impl( std::u16string_view aUserPath, + std::u16string_view aGroupName ); + bool WriteUINamesForTemplateDir_Impl( std::u16string_view aUserPath, + const std::vector< beans::StringPair >& aUINames ); + + OUString CreateNewGroupFsys( const OUString& rGroupName, Content& aGroup ); + + static bool removeContent( Content& rContent ); + bool removeContent( const OUString& rContentURL ); + + bool setProperty( Content& rContent, + const OUString& rPropName, + const Any& rPropValue ); + bool getProperty( Content& rContent, + const OUString& rPropName, + Any& rPropValue ); + + void createFromContent( GroupList_Impl& rList, + Content &rContent, + bool bHierarchy, + bool bWriteableContent ); + void addHierGroup( GroupList_Impl& rList, + const OUString& rTitle, + const OUString& rOwnURL ); + void addFsysGroup( GroupList_Impl& rList, + const OUString& rTitle, + const OUString& rUITitle, + const OUString& rOwnURL, + bool bWriteableGroup ); + void removeFromHierarchy( DocTemplates_EntryData_Impl const *pData ); + void addToHierarchy( GroupData_Impl const *pGroup, + DocTemplates_EntryData_Impl const *pData ); + + void removeFromHierarchy( GroupData_Impl const *pGroup ); + void addGroupToHierarchy( GroupData_Impl *pGroup ); + + void updateData( DocTemplates_EntryData_Impl const *pData ); + + //See: #i66157# and rhbz#1065807 + //return which template dir the rURL is a subpath of + OUString findParentTemplateDir(const OUString& rURL) const; + + //See: #i66157# and rhbz#1065807 + //return true if rURL is a path (or subpath of) a dir which is not a user path + //which implies neither it or its contents can be removed + bool isInternalTemplateDir(const OUString& rURL) const; +public: + explicit SfxDocTplService_Impl( const uno::Reference< XComponentContext > & xContext ); + ~SfxDocTplService_Impl(); + + bool init() { if ( !mbIsInitialized ) init_Impl(); return mbIsInitialized; } + const Content& getContent() const { return maRootContent; } + + void setLocale( const lang::Locale & rLocale ); + lang::Locale getLocale(); + + bool storeTemplate( const OUString& rGroupName, + const OUString& rTemplateName, + const uno::Reference< frame::XStorable >& rStorable ); + + bool addTemplate( const OUString& rGroupName, + const OUString& rTemplateName, + const OUString& rSourceURL ); + bool removeTemplate( std::u16string_view rGroupName, + std::u16string_view rTemplateName ); + bool renameTemplate( std::u16string_view rGroupName, + std::u16string_view rOldName, + const OUString& rNewName ); + + bool addGroup( const OUString& rGroupName ); + bool removeGroup( std::u16string_view rGroupName ); + bool renameGroup( std::u16string_view rOldName, + const OUString& rNewName ); + + void update(); + void doUpdate(); +}; + + +class DocTemplates_EntryData_Impl +{ + OUString maTitle; + OUString maType; + OUString maTargetURL; + OUString maHierarchyURL; + + bool mbInHierarchy : 1; + bool mbInUse : 1; + bool mbUpdateType : 1; + bool mbUpdateLink : 1; + +public: + explicit DocTemplates_EntryData_Impl( const OUString& rTitle ); + + void setInUse() { mbInUse = true; } + void setHierarchy( bool bInHierarchy ) { mbInHierarchy = bInHierarchy; } + void setUpdateLink( bool bUpdateLink ) { mbUpdateLink = bUpdateLink; } + void setUpdateType( bool bUpdateType ) { mbUpdateType = bUpdateType; } + + bool getInUse() const { return mbInUse; } + bool getInHierarchy() const { return mbInHierarchy; } + bool getUpdateLink() const { return mbUpdateLink; } + bool getUpdateType() const { return mbUpdateType; } + + const OUString& getHierarchyURL() const { return maHierarchyURL; } + const OUString& getTargetURL() const { return maTargetURL; } + const OUString& getTitle() const { return maTitle; } + const OUString& getType() const { return maType; } + + void setHierarchyURL( const OUString& rURL ) { maHierarchyURL = rURL; } + void setTargetURL( const OUString& rURL ) { maTargetURL = rURL; } + void setType( const OUString& rType ) { maType = rType; } +}; + + +class GroupData_Impl +{ + std::vector< std::unique_ptr<DocTemplates_EntryData_Impl> > maEntries; + OUString maTitle; + OUString maHierarchyURL; + OUString maTargetURL; + bool mbInUse : 1; + bool mbInHierarchy : 1; + +public: + explicit GroupData_Impl( const OUString& rTitle ); + + void setInUse() { mbInUse = true; } + void setHierarchy( bool bInHierarchy ) { mbInHierarchy = bInHierarchy; } + void setHierarchyURL( const OUString& rURL ) { maHierarchyURL = rURL; } + void setTargetURL( const OUString& rURL ) { maTargetURL = rURL; } + + bool getInUse() const { return mbInUse; } + bool getInHierarchy() const { return mbInHierarchy; } + const OUString& getHierarchyURL() const { return maHierarchyURL; } + const OUString& getTargetURL() const { return maTargetURL; } + const OUString& getTitle() const { return maTitle; } + + DocTemplates_EntryData_Impl* addEntry( const OUString& rTitle, + const OUString& rTargetURL, + const OUString& rType, + const OUString& rHierURL ); + size_t count() { return maEntries.size(); } + DocTemplates_EntryData_Impl* getEntry( size_t nPos ) { return maEntries[ nPos ].get(); } +}; + + +// private SfxDocTplService_Impl + +void SfxDocTplService_Impl::init_Impl() +{ + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + uno::Reference < task::XInteractionHandler > xInteractionHandler( + task::InteractionHandler::createWithParent(xContext, nullptr), uno::UNO_QUERY_THROW ); + maCmdEnv = new TplTaskEnvironment( xInteractionHandler ); + + ::osl::ClearableMutexGuard aGuard( maMutex ); + bool bIsInitialized = false; + bool bNeedsUpdate = false; + + if ( !mbLocaleSet ) + getDefaultLocale(); + + // convert locale to string + // set maRootContent to the root of the templates hierarchy. Create the + // entry if necessary + + maRootURL = TEMPLATE_ROOT_URL + "/" + LanguageTag::convertToBcp47(maLocale); + + const OUString aTemplVersPropName( TEMPLATE_VERSION ); + const OUString aTemplVers( TEMPLATE_VERSION_VALUE ); + if ( Content::create( maRootURL, maCmdEnv, comphelper::getProcessComponentContext(), maRootContent ) ) + { + uno::Any aValue; + OUString aPropValue; + if ( getProperty( maRootContent, aTemplVersPropName, aValue ) + && ( aValue >>= aPropValue ) + && aPropValue == aTemplVers ) + { + bIsInitialized = true; + } + else + removeContent( maRootContent ); + } + + if ( !bIsInitialized ) + { + if ( createFolder( maRootURL, true, false, maRootContent ) + && setProperty( maRootContent, aTemplVersPropName, uno::Any( aTemplVers ) ) ) + bIsInitialized = true; + + bNeedsUpdate = true; + } + + if ( bIsInitialized ) + { + try { + m_xDocProps.set(document::DocumentProperties::create( + ::comphelper::getProcessComponentContext())); + } catch (uno::RuntimeException const&) { + TOOLS_WARN_EXCEPTION("sfx.doc", "SfxDocTplService_Impl::init_Impl: cannot create DocumentProperties service:"); + } + + mxType.set( mxContext->getServiceManager()->createInstanceWithContext(SERVICENAME_TYPEDETECTION, mxContext), UNO_QUERY ); + + getDirList(); + readFolderList(); + + if ( bNeedsUpdate ) + { + aGuard.clear(); + SolarMutexClearableGuard aSolarGuard; + + VclPtrInstance< WaitWindow_Impl > pWin; + aSolarGuard.clear(); + { + osl::MutexGuard anotherGuard(maMutex); + update(); + } + SolarMutexGuard aSecondSolarGuard; + + pWin.disposeAndClear(); + } + else if ( needsUpdate() ) + // the UI should be shown only on the first update + update(); + } + else + { + SAL_WARN( "sfx.doc", "init_Impl(): Could not create root" ); + } + + mbIsInitialized = bIsInitialized; +} + + +void SfxDocTplService_Impl::getDefaultLocale() +{ + if ( !mbLocaleSet ) + { + ::osl::MutexGuard aGuard( maMutex ); + if ( !mbLocaleSet ) + { + maLocale = LanguageTag::convertToLocale( utl::ConfigManager::getUILocale(), false); + mbLocaleSet = true; + } + } +} + +const char* TEMPLATE_SHORT_NAMES_ARY[] = +{ + "standard", + "styles", + "officorr", + "offimisc", + "personal", + "forms", + "finance", + "educate", + "layout", + "presnt", + "misc", + "labels" +}; + +void SfxDocTplService_Impl::readFolderList() +{ + SolarMutexGuard aGuard; + + static_assert( SAL_N_ELEMENTS(TEMPLATE_SHORT_NAMES_ARY) == SAL_N_ELEMENTS(TEMPLATE_LONG_NAMES_ARY), "mismatch array lengths" ); + const size_t nCount = std::min(SAL_N_ELEMENTS(TEMPLATE_SHORT_NAMES_ARY), SAL_N_ELEMENTS(TEMPLATE_LONG_NAMES_ARY)); + for (size_t i = 0; i < nCount; ++i) + { + NamePair_Impl aPair; + aPair.maShortName = OUString::createFromAscii(TEMPLATE_SHORT_NAMES_ARY[i]); + aPair.maLongName = SfxResId(TEMPLATE_LONG_NAMES_ARY[i]); + + maNames.push_back( aPair ); + } +} + + +OUString SfxDocTplService_Impl::getLongName( const OUString& rShortName ) +{ + OUString aRet; + + for (auto const & rPair : maNames) + { + if ( rPair.maShortName == rShortName ) + { + aRet = rPair.maLongName; + break; + } + } + + if ( aRet.isEmpty() ) + aRet = rShortName; + + return aRet; +} + + +void SfxDocTplService_Impl::getDirList() +{ + Any aValue; + + // Get the template dir list + // TODO/LATER: let use service, register listener + INetURLObject aURL; + OUString aDirs = SvtPathOptions().GetTemplatePath(); + sal_Int32 nCount = comphelper::string::getTokenCount(aDirs, C_DELIM); + + maTemplateDirs = Sequence< OUString >( nCount ); + + uno::Reference< util::XMacroExpander > xExpander = util::theMacroExpander::get(mxContext); + static const OUStringLiteral aPrefix( + u"vnd.sun.star.expand:" ); + + sal_Int32 nIdx{ 0 }; + for (auto& rTemplateDir : asNonConstRange(maTemplateDirs)) + { + aURL.SetSmartProtocol( INetProtocol::File ); + aURL.SetURL( o3tl::getToken(aDirs, 0, C_DELIM, nIdx ) ); + rTemplateDir = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + if ( xExpander.is() ) + { + const sal_Int32 nIndex{ rTemplateDir.indexOf( aPrefix ) }; + if (nIndex<0) + continue; + + rTemplateDir = rTemplateDir.replaceAt(nIndex, aPrefix.getLength(), u""); + rTemplateDir = xExpander->expandMacros( rTemplateDir ); + } + } + + aValue <<= maTemplateDirs; + + css::uno::Reference< css::util::XPathSettings > xPathSettings = + css::util::thePathSettings::get(mxContext); + + // load internal paths + Any aAny = xPathSettings->getPropertyValue( "Template_internal" ); + aAny >>= maInternalTemplateDirs; + + for (auto& rInternalTemplateDir : asNonConstRange(maInternalTemplateDirs)) + { + //expand vnd.sun.star.expand: and remove "..." from them + //to normalize into the expected url patterns + maRelocator.makeRelocatableURL(rInternalTemplateDir); + maRelocator.makeAbsoluteURL(rInternalTemplateDir); + } + + // Store the template dir list + setProperty( maRootContent, PROPERTY_DIRLIST, aValue ); +} + + +bool SfxDocTplService_Impl::needsUpdate() +{ + bool bNeedsUpdate = true; + Any aValue; + + // Get the template dir list + bool bHasProperty = getProperty( maRootContent, PROPERTY_NEEDSUPDATE, aValue ); + + if ( bHasProperty ) + aValue >>= bNeedsUpdate; + + // the old template component also checks this state, but it is initialized from this component + // so if this component was already updated the old component does not need such an update + ::svt::TemplateFolderCache aTempCache; + if ( !bNeedsUpdate ) + bNeedsUpdate = aTempCache.needsUpdate(); + + if ( bNeedsUpdate ) + aTempCache.storeState(); + + return bNeedsUpdate; +} + + +bool SfxDocTplService_Impl::setTitleForURL( const OUString& rURL, const OUString& aTitle ) +{ + if (m_xDocProps.is()) + { + try + { + m_xDocProps->loadFromMedium(rURL, Sequence<PropertyValue>()); + m_xDocProps->setTitle(aTitle); + + uno::Reference< embed::XStorage > xStorage = ::comphelper::OStorageHelper::GetStorageFromURL( + rURL, embed::ElementModes::READWRITE); + + uno::Sequence<beans::PropertyValue> medium( comphelper::InitPropertySequence({ + { "DocumentBaseURL", Any(rURL) }, + { "URL", Any(rURL) } + })); + + m_xDocProps->storeToStorage(xStorage, medium); + return true; + } + catch ( Exception& ) + { + } + } + return false; +} + + +void SfxDocTplService_Impl::getTitleFromURL( const OUString& rURL, OUString& aTitle, OUString& aType, bool& bDocHasTitle ) +{ + bDocHasTitle = false; + + if (m_xDocProps.is()) + { + try + { + m_xDocProps->loadFromMedium(rURL, Sequence<PropertyValue>()); + aTitle = m_xDocProps->getTitle(); + } + catch ( Exception& ) + { + } + } + + if ( aType.isEmpty() && mxType.is() ) + { + const OUString aDocType {mxType->queryTypeByURL( rURL )}; + if ( !aDocType.isEmpty() ) + try + { + uno::Reference< container::XNameAccess > xTypeDetection( mxType, uno::UNO_QUERY_THROW ); + SequenceAsHashMap aTypeProps( xTypeDetection->getByName( aDocType ) ); + aType = aTypeProps.getUnpackedValueOrDefault( + "MediaType", + OUString() ); + } + catch( uno::Exception& ) + {} + } + + if ( aTitle.isEmpty() ) + { + INetURLObject aURL( rURL ); + aURL.CutExtension(); + aTitle = aURL.getName( INetURLObject::LAST_SEGMENT, true, + INetURLObject::DecodeMechanism::WithCharset ); + } + else + bDocHasTitle = true; +} + + +bool SfxDocTplService_Impl::addEntry( Content& rParentFolder, + const OUString& rTitle, + const OUString& rTargetURL, + const OUString& rType ) +{ + bool bAddedEntry = false; + + INetURLObject aLinkObj( rParentFolder.getURL() ); + aLinkObj.insertName( rTitle, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + const OUString aLinkURL {aLinkObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + Content aLink; + + if ( ! Content::create( aLinkURL, maCmdEnv, comphelper::getProcessComponentContext(), aLink ) ) + { + Sequence< Any > aValues{ Any(rTitle), Any(false), Any(rTargetURL) }; + + try + { + rParentFolder.insertNewContent( TYPE_LINK, { TITLE, IS_FOLDER, TARGET_URL }, aValues, aLink ); + setProperty( aLink, PROPERTY_TYPE, Any( rType ) ); + bAddedEntry = true; + } + catch( Exception& ) + {} + } + return bAddedEntry; +} + + +bool SfxDocTplService_Impl::createFolder( const OUString& rNewFolderURL, + bool bCreateParent, + bool bFsysFolder, + Content &rNewFolder ) +{ + Content aParent; + bool bCreatedFolder = false; + INetURLObject aParentURL( rNewFolderURL ); + const OUString aFolderName {aParentURL.getName( INetURLObject::LAST_SEGMENT, true, + INetURLObject::DecodeMechanism::WithCharset )}; + + // compute the parent folder url from the new folder url + // and remove the final slash, because Content::create doesn't + // like it + aParentURL.removeSegment(); + if ( aParentURL.getSegmentCount() >= 1 ) + aParentURL.removeFinalSlash(); + + // if the parent exists, we can continue with the creation of the + // new folder, we have to create the parent otherwise ( as long as + // bCreateParent is set to true ) + if ( Content::create( aParentURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), maCmdEnv, comphelper::getProcessComponentContext(), aParent ) ) + { + try + { + Sequence< Any > aValues{ Any(aFolderName), Any(true) }; + OUString aType; + + if ( bFsysFolder ) + aType = TYPE_FSYS_FOLDER; + else + aType = TYPE_FOLDER; + + aParent.insertNewContent( aType, { TITLE, IS_FOLDER }, aValues, rNewFolder ); + bCreatedFolder = true; + } + catch( Exception const & ) + { + TOOLS_WARN_EXCEPTION( "sfx.doc", "createFolder(): Could not create new folder" ); + } + } + else if ( bCreateParent ) + { + // if the parent doesn't exists and bCreateParent is set to true, + // we try to create the parent and if this was successful, we + // try to create the new folder again ( but this time, we set + // bCreateParent to false to avoid endless recursions ) + if ( ( aParentURL.getSegmentCount() >= 1 ) && + createFolder( aParentURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), bCreateParent, bFsysFolder, aParent ) ) + { + bCreatedFolder = createFolder( rNewFolderURL, false, bFsysFolder, rNewFolder ); + } + } + + return bCreatedFolder; +} + + +bool SfxDocTplService_Impl::CreateNewUniqueFolderWithPrefix( std::u16string_view aPath, + const OUString& aPrefix, + OUString& aNewFolderName, + OUString& aNewFolderURL, + Content& aNewFolder ) +{ + bool bCreated = false; + INetURLObject aDirPath( aPath ); + + Content aParent; + uno::Reference< XCommandEnvironment > aQuietEnv; + if ( Content::create( aDirPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aQuietEnv, comphelper::getProcessComponentContext(), aParent ) ) + { + for ( sal_Int32 nInd = 0; nInd < 32000; nInd++ ) + { + OUString aTryName = aPrefix; + if ( nInd ) + aTryName += OUString::number( nInd ); + + try + { + Sequence< Any > aValues{ Any(aTryName), Any(true) }; + bCreated = aParent.insertNewContent( TYPE_FSYS_FOLDER, { TITLE, IS_FOLDER }, aValues, aNewFolder ); + } + catch( ucb::NameClashException& ) + { + // if there is already an element, retry + } + catch( Exception& ) + { + INetURLObject aObjPath( aDirPath ); + aObjPath.insertName( aTryName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + // if there is already an element, retry + // if there was another error, do not try any more + if ( !::utl::UCBContentHelper::Exists( aObjPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) ) + break; + } + + if ( bCreated ) + { + aNewFolderName = aTryName; + aNewFolderURL = aNewFolder.get()->getIdentifier()->getContentIdentifier(); + break; + } + } + } + + return bCreated; +} + + +OUString SfxDocTplService_Impl::CreateNewUniqueFileWithPrefix( std::u16string_view aPath, + const OUString& aPrefix, + const OUString& aExt ) +{ + OUString aNewFileURL; + INetURLObject aDirPath( aPath ); + + Content aParent; + + uno::Reference< XCommandEnvironment > aQuietEnv; + if ( Content::create( aDirPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aQuietEnv, comphelper::getProcessComponentContext(), aParent ) ) + { + for ( sal_Int32 nInd = 0; nInd < 32000; nInd++ ) + { + Content aNewFile; + bool bCreated = false; + OUString aTryName = aPrefix; + if ( nInd ) + aTryName += OUString::number( nInd ); + if ( aExt.toChar() != '.' ) + aTryName += "."; + aTryName += aExt; + + try + { + Sequence< Any > aValues{ Any(aTryName), Any(true) }; + bCreated = aParent.insertNewContent( TYPE_FSYS_FILE, { TITLE, IS_DOCUMENT }, aValues, aNewFile ); + } + catch( ucb::NameClashException& ) + { + // if there is already an element, retry + } + catch( Exception& ) + { + INetURLObject aObjPath( aPath ); + aObjPath.insertName( aTryName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + // if there is already an element, retry + // if there was another error, do not try any more + if ( !::utl::UCBContentHelper::Exists( aObjPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) ) + break; + } + + if ( bCreated ) + { + aNewFileURL = aNewFile.get()->getIdentifier()->getContentIdentifier(); + break; + } + } + } + + return aNewFileURL; +} + + +bool SfxDocTplService_Impl::removeContent( Content& rContent ) +{ + bool bRemoved = false; + try + { + Any aArg( true ); + + rContent.executeCommand( COMMAND_DELETE, aArg ); + bRemoved = true; + } + catch ( RuntimeException& ) {} + catch ( Exception& ) {} + + return bRemoved; +} + + +bool SfxDocTplService_Impl::removeContent( const OUString& rContentURL ) +{ + Content aContent; + + if ( Content::create( rContentURL, maCmdEnv, comphelper::getProcessComponentContext(), aContent ) ) + return removeContent( aContent ); + return false; +} + + +bool SfxDocTplService_Impl::setProperty( Content& rContent, + const OUString& rPropName, + const Any& rPropValue ) +{ + bool bPropertySet = false; + + // Store the property + try + { + Any aPropValue( rPropValue ); + uno::Reference< XPropertySetInfo > aPropInfo = rContent.getProperties(); + + // check, whether or not the property exists, create it, when not + if ( !aPropInfo.is() || !aPropInfo->hasPropertyByName( rPropName ) ) + { + uno::Reference< XPropertyContainer > xProperties( rContent.get(), UNO_QUERY ); + if ( xProperties.is() ) + { + try + { + xProperties->addProperty( rPropName, PropertyAttribute::MAYBEVOID, rPropValue ); + } + catch( PropertyExistException& ) {} + catch( IllegalTypeException& ) { + TOOLS_WARN_EXCEPTION( "sfx.doc", "" ); + } + catch( IllegalArgumentException& ) { + TOOLS_WARN_EXCEPTION( "sfx.doc", "" ); + } + } + } + + // To ensure a reloctable office installation, the path to the + // office installation directory must never be stored directly. + if ( SfxURLRelocator_Impl::propertyCanContainOfficeDir( rPropName ) ) + { + OUString aValue; + if ( rPropValue >>= aValue ) + { + maRelocator.makeRelocatableURL( aValue ); + aPropValue <<= aValue; + } + else + { + Sequence< OUString > aValues; + if ( rPropValue >>= aValues ) + { + for ( auto& rValue : asNonConstRange(aValues) ) + { + maRelocator.makeRelocatableURL( rValue ); + } + aPropValue <<= aValues; + } + else + { + OSL_FAIL( "Unsupported property value type" ); + } + } + } + + // now set the property + + rContent.setPropertyValue( rPropName, aPropValue ); + bPropertySet = true; + } + catch ( RuntimeException& ) {} + catch ( Exception& ) {} + + return bPropertySet; +} + + +bool SfxDocTplService_Impl::getProperty(Content& rContent, const OUString& rPropName, Any& rPropValue) +{ + bool bGotProperty = false; + + // Get the property + try + { + uno::Reference< XPropertySetInfo > aPropInfo = rContent.getProperties(); + + // check, whether or not the property exists + if ( !aPropInfo.is() || !aPropInfo->hasPropertyByName( rPropName ) ) + { + return false; + } + + // now get the property + + rPropValue = rContent.getPropertyValue( rPropName ); + + // To ensure a reloctable office installation, the path to the + // office installation directory must never be stored directly. + if ( SfxURLRelocator_Impl::propertyCanContainOfficeDir( rPropName ) ) + { + OUString aValue; + if ( rPropValue >>= aValue ) + { + maRelocator.makeAbsoluteURL( aValue ); + rPropValue <<= aValue; + } + else + { + Sequence< OUString > aValues; + if ( rPropValue >>= aValues ) + { + for ( auto& rValue : asNonConstRange(aValues) ) + { + maRelocator.makeAbsoluteURL( rValue ); + } + rPropValue <<= aValues; + } + else + { + OSL_FAIL( "Unsupported property value type" ); + } + } + } + + bGotProperty = true; + } + catch ( RuntimeException& ) {} + catch ( Exception& ) {} + + return bGotProperty; +} + +SfxDocTplService_Impl::SfxDocTplService_Impl( const uno::Reference< XComponentContext > & xContext ) + : mxContext(xContext), mbIsInitialized(false), mbLocaleSet(false), maRelocator(xContext) +{ +} + + +SfxDocTplService_Impl::~SfxDocTplService_Impl() +{ + ::osl::MutexGuard aGuard( maMutex ); + maNames.clear(); +} + + +lang::Locale SfxDocTplService_Impl::getLocale() +{ + ::osl::MutexGuard aGuard( maMutex ); + + if ( !mbLocaleSet ) + getDefaultLocale(); + + return maLocale; +} + + +void SfxDocTplService_Impl::setLocale( const lang::Locale &rLocale ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + if ( mbLocaleSet && ( + ( maLocale.Language != rLocale.Language ) || + ( maLocale.Country != rLocale.Country ) || + ( maLocale.Variant != rLocale.Variant ) ) ) + mbIsInitialized = false; + + maLocale = rLocale; + mbLocaleSet = true; +} + + +void SfxDocTplService_Impl::update() +{ + ::osl::MutexGuard aGuard( maMutex ); + + doUpdate(); +} + + +void SfxDocTplService_Impl::doUpdate() +{ + ::osl::MutexGuard aGuard( maMutex ); + + const OUString aPropName( PROPERTY_NEEDSUPDATE ); + Any aValue; + + aValue <<= true; + setProperty( maRootContent, aPropName, aValue ); + + GroupList_Impl aGroupList; + + // get the entries from the hierarchy + createFromContent( aGroupList, maRootContent, true, false ); + + // get the entries from the template directories + sal_Int32 nCountDir = maTemplateDirs.getLength(); + const OUString* pDirs = maTemplateDirs.getConstArray(); + Content aDirContent; + + // the last directory in the list must be writable + bool bWriteableDirectory = true; + + // the target folder might not exist, for this reason no interaction handler should be used + uno::Reference< XCommandEnvironment > aQuietEnv; + + while ( nCountDir ) + { + nCountDir--; + if ( Content::create( pDirs[ nCountDir ], aQuietEnv, comphelper::getProcessComponentContext(), aDirContent ) ) + { + createFromContent( aGroupList, aDirContent, false, bWriteableDirectory ); + } + + bWriteableDirectory = false; + } + + // now check the list + for(std::unique_ptr<GroupData_Impl>& pGroup : aGroupList) + { + if ( pGroup->getInUse() ) + { + if ( pGroup->getInHierarchy() ) + { + Content aGroup; + if ( Content::create( pGroup->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + setProperty( aGroup, + TARGET_DIR_URL, + Any( pGroup->getTargetURL() ) ); + + size_t nCount = pGroup->count(); + for ( size_t i=0; i<nCount; i++ ) + { + DocTemplates_EntryData_Impl *pData = pGroup->getEntry( i ); + if ( ! pData->getInUse() ) + { + if ( pData->getInHierarchy() ) + removeFromHierarchy( pData ); // delete entry in hierarchy + else + addToHierarchy( pGroup.get(), pData ); // add entry to hierarchy + } + else if ( pData->getUpdateType() || + pData->getUpdateLink() ) + { + updateData( pData ); + } + } + } + else + { + addGroupToHierarchy( pGroup.get() ); // add group to hierarchy + } + } + else + removeFromHierarchy( pGroup.get() ); // delete group from hierarchy + } + aGroupList.clear(); + + aValue <<= false; + setProperty( maRootContent, aPropName, aValue ); +} + + +std::vector< beans::StringPair > SfxDocTplService_Impl::ReadUINamesForTemplateDir_Impl( std::u16string_view aUserPath ) +{ + INetURLObject aLocObj( aUserPath ); + aLocObj.insertName( u"groupuinames.xml", false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + Content aLocContent; + + // TODO/LATER: Use hashmap in future + std::vector< beans::StringPair > aUINames; + if ( Content::create( aLocObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), uno::Reference < ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext(), aLocContent ) ) + { + try + { + uno::Reference< io::XInputStream > xLocStream = aLocContent.openStream(); + if ( xLocStream.is() ) + aUINames = DocTemplLocaleHelper::ReadGroupLocalizationSequence( xLocStream, mxContext ); + } + catch( uno::Exception& ) + {} + } + + return aUINames; +} + + +bool SfxDocTplService_Impl::UpdateUINamesForTemplateDir_Impl( std::u16string_view aUserPath, + const OUString& aGroupName, + const OUString& aNewFolderName ) +{ + std::vector< beans::StringPair > aUINames = ReadUINamesForTemplateDir_Impl( aUserPath ); + sal_Int32 nLen = aUINames.size(); + + // it is possible that the name is used already, but it should be checked before + for ( sal_Int32 nInd = 0; nInd < nLen; nInd++ ) + if ( aUINames[nInd].First == aNewFolderName ) + return false; + + aUINames.resize( ++nLen ); + aUINames[nLen-1].First = aNewFolderName; + aUINames[nLen-1].Second = aGroupName; + + return WriteUINamesForTemplateDir_Impl( aUserPath, aUINames ); +} + + +bool SfxDocTplService_Impl::ReplaceUINamesForTemplateDir_Impl( std::u16string_view aUserPath, + const OUString& aDefaultFsysGroupName, + std::u16string_view aOldGroupName, + const OUString& aNewGroupName ) +{ + std::vector< beans::StringPair > aUINames = ReadUINamesForTemplateDir_Impl( aUserPath ); + sal_Int32 nLen = aUINames.size(); + + bool bChanged = false; + for ( sal_Int32 nInd = 0; nInd < nLen; nInd++ ) + if ( aUINames[nInd].Second == aOldGroupName ) + { + aUINames[nInd].Second = aNewGroupName; + bChanged = true; + } + + if ( !bChanged ) + { + aUINames.resize( ++nLen ); + aUINames[nLen-1].First = aDefaultFsysGroupName; + aUINames[nLen-1].Second = aNewGroupName; + } + return WriteUINamesForTemplateDir_Impl( aUserPath, aUINames ); +} + + +void SfxDocTplService_Impl::RemoveUINamesForTemplateDir_Impl( std::u16string_view aUserPath, + std::u16string_view aGroupName ) +{ + std::vector< beans::StringPair > aUINames = ReadUINamesForTemplateDir_Impl( aUserPath ); + sal_Int32 nLen = aUINames.size(); + std::vector< beans::StringPair > aNewUINames( nLen ); + sal_Int32 nNewLen = 0; + + bool bChanged = false; + for ( sal_Int32 nInd = 0; nInd < nLen; nInd++ ) + if ( aUINames[nInd].Second == aGroupName ) + bChanged = true; + else + { + nNewLen++; + aNewUINames[nNewLen-1].First = aUINames[nInd].First; + aNewUINames[nNewLen-1].Second = aUINames[nInd].Second; + } + + aNewUINames.resize( nNewLen ); + + if (bChanged) + WriteUINamesForTemplateDir_Impl( aUserPath, aNewUINames ); +} + + +bool SfxDocTplService_Impl::WriteUINamesForTemplateDir_Impl( std::u16string_view aUserPath, + const std::vector< beans::StringPair >& aUINames ) +{ + bool bResult = false; + try { + uno::Reference< io::XTempFile > xTempFile( + io::TempFile::create(mxContext), + uno::UNO_SET_THROW ); + + uno::Reference< io::XOutputStream > xOutStream = xTempFile->getOutputStream(); + if ( !xOutStream.is() ) + throw uno::RuntimeException(); + + DocTemplLocaleHelper::WriteGroupLocalizationSequence( xOutStream, aUINames, mxContext); + try { + // the SAX writer might close the stream + xOutStream->closeOutput(); + } catch( uno::Exception& ) + {} + + Content aTargetContent( OUString(aUserPath), maCmdEnv, comphelper::getProcessComponentContext() ); + Content aSourceContent( xTempFile->getUri(), maCmdEnv, comphelper::getProcessComponentContext() ); + aTargetContent.transferContent( aSourceContent, + InsertOperation::Copy, + "groupuinames.xml", + ucb::NameClash::OVERWRITE, + "text/xml" ); + + bResult = true; + } + catch ( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("sfx.doc", ""); + } + + return bResult; +} + + +OUString SfxDocTplService_Impl::CreateNewGroupFsys( const OUString& rGroupName, Content& aGroup ) +{ + OUString aResultURL; + + if ( maTemplateDirs.hasElements() ) + { + OUString aTargetPath = maTemplateDirs[ maTemplateDirs.getLength() - 1 ]; + + // create a new folder with the given name + Content aNewFolder; + OUString aNewFolderName; + + // the Fsys name instead of GroupName should be used, the groupuinames must be added also + if ( !CreateNewUniqueFolderWithPrefix( aTargetPath, + rGroupName, + aNewFolderName, + aResultURL, + aNewFolder ) + && !CreateNewUniqueFolderWithPrefix( aTargetPath, + "UserGroup", + aNewFolderName, + aResultURL, + aNewFolder ) ) + + return OUString(); + + if ( !UpdateUINamesForTemplateDir_Impl( aTargetPath, rGroupName, aNewFolderName ) ) + { + // we could not create the groupuinames for the folder, so we delete the group in + // the folder and return + removeContent( aNewFolder ); + return OUString(); + } + + // Now set the target url for this group and we are done + Any aValue( aResultURL ); + + if ( ! setProperty( aGroup, TARGET_DIR_URL, aValue ) ) + { + removeContent( aNewFolder ); + return OUString(); + } + } + + return aResultURL; +} + + +bool SfxDocTplService_Impl::addGroup( const OUString& rGroupName ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + // Check, whether or not there is a group with this name + Content aNewGroup; + OUString aNewGroupURL; + INetURLObject aNewGroupObj( maRootURL ); + + aNewGroupObj.insertName( rGroupName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + + aNewGroupURL = aNewGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + if ( Content::create( aNewGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aNewGroup ) || + ! createFolder( aNewGroupURL, false, false, aNewGroup ) ) + { + // if there already was a group with this name or the new group + // could not be created, we return here + return false; + } + + // Get the user template path entry ( new group will always + // be added in the user template path ) + sal_Int32 nIndex; + OUString aUserPath; + + nIndex = maTemplateDirs.getLength(); + if ( nIndex ) + nIndex--; + else + return false; // We don't know where to add the group + + aUserPath = maTemplateDirs[ nIndex ]; + + // create a new folder with the given name + Content aNewFolder; + OUString aNewFolderName; + OUString aNewFolderURL; + + // the Fsys name instead of GroupName should be used, the groupuinames must be added also + if ( !CreateNewUniqueFolderWithPrefix( aUserPath, + rGroupName, + aNewFolderName, + aNewFolderURL, + aNewFolder ) + && !CreateNewUniqueFolderWithPrefix( aUserPath, + "UserGroup", + aNewFolderName, + aNewFolderURL, + aNewFolder ) ) + { + // we could not create the folder, so we delete the group in the + // hierarchy and return + removeContent( aNewGroup ); + return false; + } + + if ( !UpdateUINamesForTemplateDir_Impl( aUserPath, rGroupName, aNewFolderName ) ) + { + // we could not create the groupuinames for the folder, so we delete the group in the + // hierarchy, the folder and return + removeContent( aNewGroup ); + removeContent( aNewFolder ); + return false; + } + + // Now set the target url for this group and we are done + Any aValue( aNewFolderURL ); + + if ( ! setProperty( aNewGroup, TARGET_DIR_URL, aValue ) ) + { + removeContent( aNewGroup ); + removeContent( aNewFolder ); + return false; + } + + return true; +} + + +bool SfxDocTplService_Impl::removeGroup( std::u16string_view rGroupName ) +{ + // remove all the elements that have the prefix aTargetURL + // if the group does not have other elements remove it + + ::osl::MutexGuard aGuard( maMutex ); + + bool bResult = false; + + // create the group url + INetURLObject aGroupObj( maRootURL ); + aGroupObj.insertName( rGroupName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + + // Get the target url + Content aGroup; + const OUString aGroupURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + if ( Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + { + const OUString aPropName( TARGET_DIR_URL ); + Any aValue; + + OUString aGroupTargetURL; + if ( getProperty( aGroup, aPropName, aValue ) ) + aValue >>= aGroupTargetURL; + + if ( aGroupTargetURL.isEmpty() ) + return false; // nothing is allowed to be removed + + if ( !maTemplateDirs.hasElements() ) + return false; + + // check that the fs location is in writable folder and this is not a "My templates" folder + INetURLObject aGroupParentFolder( aGroupTargetURL ); + if (!aGroupParentFolder.removeSegment()) + return false; + + OUString aGeneralTempPath = findParentTemplateDir( + aGroupParentFolder.GetMainURL(INetURLObject::DecodeMechanism::NONE)); + + if (aGeneralTempPath.isEmpty()) + return false; + + // now get the content of the Group + uno::Reference< XResultSet > xResultSet; + Sequence< OUString > aProps { TARGET_URL }; + + try + { + xResultSet = aGroup.createCursor( aProps, INCLUDE_DOCUMENTS_ONLY ); + + if ( xResultSet.is() ) + { + bool bHasNonRemovable = false; + bool bHasShared = false; + + uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY_THROW ); + uno::Reference< XRow > xRow( xResultSet, UNO_QUERY_THROW ); + + while ( xResultSet->next() ) + { + OUString aTemplTargetURL( xRow->getString( 1 ) ); + OUString aHierURL = xContentAccess->queryContentIdentifierString(); + + if ( ::utl::UCBContentHelper::IsSubPath( aGroupTargetURL, aTemplTargetURL ) ) + { + // this is a user template, and it can be removed + if ( removeContent( aTemplTargetURL ) ) + removeContent( aHierURL ); + else + bHasNonRemovable = true; + } + else + bHasShared = true; + } + + if ( !bHasNonRemovable && !bHasShared ) + { + if ( removeContent( aGroupTargetURL ) + || !::utl::UCBContentHelper::Exists( aGroupTargetURL ) ) + { + removeContent( aGroupURL ); + RemoveUINamesForTemplateDir_Impl( aGeneralTempPath, rGroupName ); + bResult = true; // the operation is successful only if the whole group is removed + } + } + else if ( !bHasNonRemovable ) + { + if ( removeContent( aGroupTargetURL ) + || !::utl::UCBContentHelper::Exists( aGroupTargetURL ) ) + { + RemoveUINamesForTemplateDir_Impl( aGeneralTempPath, rGroupName ); + setProperty( aGroup, aPropName, uno::Any( OUString() ) ); + } + } + } + } + catch ( Exception& ) {} + } + + return bResult; +} + + +bool SfxDocTplService_Impl::renameGroup( std::u16string_view rOldName, + const OUString& rNewName ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + // create the group url + Content aGroup; + INetURLObject aGroupObj( maRootURL ); + aGroupObj.insertName( rNewName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + OUString aGroupURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + // Check, if there is a group with the new name, return false + // if there is one. + if ( Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + return false; + + aGroupObj.removeSegment(); + aGroupObj.insertName( rOldName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + aGroupURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + // When there is no group with the old name, we can't rename it + if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + return false; + + OUString aGroupTargetURL; + // there is no need to check whether target dir url is in target path, since if the target path is changed + // the target dir url should be already generated new + Any aValue; + if ( getProperty( aGroup, TARGET_DIR_URL, aValue ) ) + aValue >>= aGroupTargetURL; + + if ( aGroupTargetURL.isEmpty() ) + return false; + + if ( !maTemplateDirs.hasElements() ) + return false; + + // check that the fs location is in writable folder and this is not a "My templates" folder + INetURLObject aGroupParentFolder( aGroupTargetURL ); + if (!aGroupParentFolder.removeSegment() || + isInternalTemplateDir(aGroupParentFolder.GetMainURL(INetURLObject::DecodeMechanism::NONE))) + { + return false; + } + + // check that the group can be renamed ( all the contents must be in target location ) + bool bCanBeRenamed = false; + try + { + uno::Reference< XResultSet > xResultSet; + Sequence< OUString > aProps { TARGET_URL }; + xResultSet = aGroup.createCursor( aProps, INCLUDE_DOCUMENTS_ONLY ); + + if ( xResultSet.is() ) + { + uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY_THROW ); + uno::Reference< XRow > xRow( xResultSet, UNO_QUERY_THROW ); + + while ( xResultSet->next() ) + { + if ( !::utl::UCBContentHelper::IsSubPath( aGroupTargetURL, xRow->getString( 1 ) ) ) + throw uno::Exception("not sub path", nullptr); + } + + bCanBeRenamed = true; + } + } + catch ( Exception& ) {} + + if ( bCanBeRenamed ) + { + INetURLObject aGroupTargetObj( aGroupTargetURL ); + const OUString aFsysName = aGroupTargetObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ); + + if ( aGroupTargetObj.removeSegment() + && ReplaceUINamesForTemplateDir_Impl( aGroupTargetObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), + aFsysName, + rOldName, + rNewName ) ) + { + // rename the group in the hierarchy + Any aTitleValue; + aTitleValue <<= rNewName; + + return setProperty( aGroup, TITLE, aTitleValue ); + } + } + + return false; +} + + +bool SfxDocTplService_Impl::storeTemplate( const OUString& rGroupName, + const OUString& rTemplateName, + const uno::Reference< frame::XStorable >& rStorable ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + // Check, whether or not there is a group with this name + // Return false, if there is no group with the given name + Content aGroup, aTemplateToRemove; + INetURLObject aGroupObj( maRootURL ); + bool bRemoveOldTemplateContent = false; + + aGroupObj.insertName( rGroupName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + const OUString aGroupURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + return false; + + OUString aGroupTargetURL; + Any aValue; + if ( getProperty( aGroup, TARGET_DIR_URL, aValue ) ) + aValue >>= aGroupTargetURL; + + + // Check, if there's a template with the given name in this group + // the target template should be overwritten if it is imported by user + // in case the template is installed by office installation of by an add-in + // it can not be replaced + aGroupObj.insertName( rTemplateName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + const OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + OUString aTemplateToRemoveTargetURL; + + if ( Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplateToRemove ) ) + { + bRemoveOldTemplateContent = true; + if ( getProperty( aTemplateToRemove, TARGET_URL, aValue ) ) + aValue >>= aTemplateToRemoveTargetURL; + + if ( aGroupTargetURL.isEmpty() || !maTemplateDirs.hasElements() + || (!aTemplateToRemoveTargetURL.isEmpty() && isInternalTemplateDir(aTemplateToRemoveTargetURL)) ) + return false; // it is not allowed to remove the template + } + + try + { + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + + // get document service name + uno::Reference< frame::XModuleManager2 > xModuleManager( frame::ModuleManager::create(xContext) ); + const OUString sDocServiceName {xModuleManager->identify( uno::Reference< uno::XInterface >( rStorable, uno::UNO_QUERY ) )}; + if ( sDocServiceName.isEmpty() ) + throw uno::RuntimeException(); + + // get the actual filter name + uno::Reference< lang::XMultiServiceFactory > xConfigProvider = + configuration::theDefaultProvider::get( xContext ); + + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"nodepath", uno::Any(OUString( "/org.openoffice.Setup/Office/Factories/" ))} + })); + uno::Reference< container::XNameAccess > xSOFConfig( + xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", + aArgs ), + uno::UNO_QUERY_THROW ); + + uno::Reference< container::XNameAccess > xApplConfig; + xSOFConfig->getByName( sDocServiceName ) >>= xApplConfig; + if ( !xApplConfig.is() ) + throw uno::RuntimeException(); + + OUString aFilterName; + xApplConfig->getByName("ooSetupFactoryActualTemplateFilter") >>= aFilterName; + if ( aFilterName.isEmpty() ) + throw uno::RuntimeException(); + + // find the related type name + uno::Reference< container::XNameAccess > xFilterFactory( + mxContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.FilterFactory", mxContext), + uno::UNO_QUERY_THROW ); + + uno::Sequence< beans::PropertyValue > aFilterData; + xFilterFactory->getByName( aFilterName ) >>= aFilterData; + OUString aTypeName; + for ( const auto& rProp : std::as_const(aFilterData) ) + if ( rProp.Name == "Type" ) + rProp.Value >>= aTypeName; + + if ( aTypeName.isEmpty() ) + throw uno::RuntimeException(); + + // find the mediatype and extension + uno::Reference< container::XNameAccess > xTypeDetection = + mxType.is() ? + uno::Reference< container::XNameAccess >( mxType, uno::UNO_QUERY_THROW ) : + uno::Reference< container::XNameAccess >( + mxContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.TypeDetection", mxContext), + uno::UNO_QUERY_THROW ); + + SequenceAsHashMap aTypeProps( xTypeDetection->getByName( aTypeName ) ); + uno::Sequence< OUString > aAllExt = + aTypeProps.getUnpackedValueOrDefault("Extensions", Sequence< OUString >() ); + if ( !aAllExt.hasElements() ) + throw uno::RuntimeException(); + + const OUString aMediaType {aTypeProps.getUnpackedValueOrDefault("MediaType", OUString() )}; + const OUString aExt {aAllExt[0]}; + + if ( aMediaType.isEmpty() || aExt.isEmpty() ) + throw uno::RuntimeException(); + + // construct destination url + if ( aGroupTargetURL.isEmpty() ) + { + aGroupTargetURL = CreateNewGroupFsys( rGroupName, aGroup ); + + if ( aGroupTargetURL.isEmpty() ) + throw uno::RuntimeException(); + } + + OUString aNewTemplateTargetURL = CreateNewUniqueFileWithPrefix( aGroupTargetURL, rTemplateName, aExt ); + if ( aNewTemplateTargetURL.isEmpty() ) + { + aNewTemplateTargetURL = CreateNewUniqueFileWithPrefix( aGroupTargetURL, "UserTemplate", aExt ); + + if ( aNewTemplateTargetURL.isEmpty() ) + throw uno::RuntimeException(); + } + + // store template + uno::Sequence< PropertyValue > aStoreArgs{ + comphelper::makePropertyValue("FilterName", aFilterName), + comphelper::makePropertyValue("DocumentTitle", rTemplateName) + }; + + if( !::utl::UCBContentHelper::EqualURLs( aNewTemplateTargetURL, rStorable->getLocation() )) + rStorable->storeToURL( aNewTemplateTargetURL, aStoreArgs ); + else + rStorable->store(); + + // the storing was successful, now the old template with the same name can be removed if it existed + if ( !aTemplateToRemoveTargetURL.isEmpty() ) + { + removeContent( aTemplateToRemoveTargetURL ); + + /* + * pb: #i79496# + * if the old template was the standard template + * it is necessary to change the standard template with the new file name + */ + const OUString sStdTmplFile = SfxObjectFactory::GetStandardTemplate( sDocServiceName ); + if ( INetURLObject( sStdTmplFile ) == INetURLObject( aTemplateToRemoveTargetURL ) ) + { + SfxObjectFactory::SetStandardTemplate( sDocServiceName, aNewTemplateTargetURL ); + } + } + + if ( bRemoveOldTemplateContent ) + removeContent( aTemplateToRemove ); + + // add the template to hierarchy + return addEntry( aGroup, rTemplateName, aNewTemplateTargetURL, aMediaType ); + } + catch( Exception& ) + { + // the template was not stored + return false; + } +} + + +bool SfxDocTplService_Impl::addTemplate( const OUString& rGroupName, + const OUString& rTemplateName, + const OUString& rSourceURL ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + // Check, whether or not there is a group with this name + // Return false, if there is no group with the given name + Content aGroup, aTemplate, aTargetGroup; + INetURLObject aGroupObj( maRootURL ); + + aGroupObj.insertName( rGroupName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + const OUString aGroupURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + return false; + + // Check, if there's a template with the given name in this group + // Return false, if there already is a template + aGroupObj.insertName( rTemplateName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + const OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + if ( Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) ) + return false; + + // get the target url of the group + OUString aTargetURL; + Any aValue; + + if ( getProperty( aGroup, TARGET_DIR_URL, aValue ) ) + aValue >>= aTargetURL; + + if ( aTargetURL.isEmpty() ) + { + aTargetURL = CreateNewGroupFsys( rGroupName, aGroup ); + + if ( aTargetURL.isEmpty() ) + return false; + } + + // Get the content type + OUString aTitle, aType; + + bool bDocHasTitle = false; + getTitleFromURL( rSourceURL, aTitle, aType, bDocHasTitle ); + + INetURLObject aSourceObj( rSourceURL ); + if ( rTemplateName == aTitle ) + { + // addTemplate will sometimes be called just to add an entry in the + // hierarchy; the target URL and the source URL will be the same in + // this scenario + // TODO/LATER: get rid of this old hack + + INetURLObject aTargetObj( aTargetURL ); + + aTargetObj.insertName( rTemplateName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + aTargetObj.setExtension( aSourceObj.getExtension() ); + + const OUString aTargetURL2 = aTargetObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + if ( aTargetURL2 == rSourceURL ) + return addEntry( aGroup, rTemplateName, aTargetURL2, aType ); + } + + // copy the template into the new group (targeturl) + + INetURLObject aTmpURL( aSourceObj ); + aTmpURL.CutExtension(); + const OUString aPattern {aTmpURL.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset )}; + + const OUString aNewTemplateTargetURL {CreateNewUniqueFileWithPrefix( aTargetURL, aPattern, aSourceObj.getExtension() )}; + INetURLObject aNewTemplateTargetObj( aNewTemplateTargetURL ); + const OUString aNewTemplateTargetName {aNewTemplateTargetObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset )}; + if ( aNewTemplateTargetURL.isEmpty() || aNewTemplateTargetName.isEmpty() ) + return false; + + // get access to source file + Content aSourceContent; + uno::Reference < ucb::XCommandEnvironment > xEnv; + INetURLObject aSourceURL( rSourceURL ); + if( ! Content::create( aSourceURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aSourceContent ) ) + return false; + + if( ! Content::create( aTargetURL, xEnv, comphelper::getProcessComponentContext(), aTargetGroup ) ) + return false; + + // transfer source file + try + { + aTargetGroup.transferContent( aSourceContent, + InsertOperation::Copy, + aNewTemplateTargetName, + NameClash::OVERWRITE, + aType ); + + // allow to edit the added template + Content aResultContent; + if ( Content::create( aNewTemplateTargetURL, xEnv, comphelper::getProcessComponentContext(), aResultContent ) ) + { + static const OUStringLiteral aPropertyName( u"IsReadOnly" ); + uno::Any aProperty; + bool bReadOnly = false; + if ( getProperty( aResultContent, aPropertyName, aProperty ) && ( aProperty >>= bReadOnly ) && bReadOnly ) + setProperty( aResultContent, aPropertyName, uno::Any( false ) ); + } + } + catch ( ContentCreationException& ) + { return false; } + catch ( Exception& ) + { return false; } + + + // either the document has title and it is the same as requested, or we have to set it + bool bCorrectTitle = ( bDocHasTitle && aTitle == rTemplateName ); + if ( !bCorrectTitle ) + { + if ( !bDocHasTitle ) + { + INetURLObject aNewTmpObj( aNewTemplateTargetObj ); + aNewTmpObj.CutExtension(); + bCorrectTitle = ( aNewTmpObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ) == rTemplateName ); + } + + if ( !bCorrectTitle ) + bCorrectTitle = setTitleForURL( aNewTemplateTargetURL, rTemplateName ); + } + + if ( bCorrectTitle ) + { + // create a new entry in the hierarchy + return addEntry( aGroup, rTemplateName, aNewTemplateTargetURL, aType ); + } + + // TODO/LATER: The user could be notified here that the renaming has failed + // create a new entry in the hierarchy + addEntry( aGroup, aTitle, aNewTemplateTargetURL, aType ); + return false; +} + +bool SfxDocTplService_Impl::isInternalTemplateDir(const OUString& rURL) const +{ + return std::any_of(maInternalTemplateDirs.begin(), maInternalTemplateDirs.end(), + [&rURL](const OUString& rDir) { return ::utl::UCBContentHelper::IsSubPath(rDir, rURL); }); +} + +OUString SfxDocTplService_Impl::findParentTemplateDir(const OUString& rURL) const +{ + const OUString* pDirs = std::find_if(maTemplateDirs.begin(), maTemplateDirs.end(), + [&rURL](const OUString& rDir) { return ::utl::UCBContentHelper::IsSubPath(rDir, rURL); }); + if (pDirs != maTemplateDirs.end()) + return *pDirs; + return OUString(); +} + +bool SfxDocTplService_Impl::removeTemplate( std::u16string_view rGroupName, + std::u16string_view rTemplateName ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + // Check, whether or not there is a group with this name + // Return false, if there is no group with the given name + Content aGroup, aTemplate; + INetURLObject aGroupObj( maRootURL ); + + aGroupObj.insertName( rGroupName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + const OUString aGroupURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + return false; + + // Check, if there's a template with the given name in this group + // Return false, if there is no template + aGroupObj.insertName( rTemplateName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + const OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + if ( !Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) ) + return false; + + // get the target URL from the template + OUString aTargetURL; + Any aValue; + + if ( getProperty( aTemplate, TARGET_URL, aValue ) ) + aValue >>= aTargetURL; + + // delete the target template + if ( !aTargetURL.isEmpty() ) + { + if (isInternalTemplateDir(aTargetURL)) + return false; + + removeContent( aTargetURL ); + } + + // delete the template entry + return removeContent( aTemplate ); +} + + +bool SfxDocTplService_Impl::renameTemplate( std::u16string_view rGroupName, + std::u16string_view rOldName, + const OUString& rNewName ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + // Check, whether or not there is a group with this name + // Return false, if there is no group with the given name + Content aGroup, aTemplate; + INetURLObject aGroupObj( maRootURL ); + + aGroupObj.insertName( rGroupName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + const OUString aGroupURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + return false; + + // Check, if there's a template with the new name in this group + // Return false, if there is one + aGroupObj.insertName( rNewName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + if ( Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) ) + return false; + + // Check, if there's a template with the old name in this group + // Return false, if there is no template + aGroupObj.removeSegment(); + aGroupObj.insertName( rOldName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + aTemplateURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + if ( !Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) ) + return false; + + OUString aTemplateTargetURL; + Any aTargetValue; + + if ( getProperty( aTemplate, TARGET_URL, aTargetValue ) ) + aTargetValue >>= aTemplateTargetURL; + + if ( !setTitleForURL( aTemplateTargetURL, rNewName ) ) + return false; + + // rename the template entry in the cache + Any aTitleValue; + aTitleValue <<= rNewName; + + return setProperty( aTemplate, TITLE, aTitleValue ); +} + + +class SfxDocTplService: public ::cppu::WeakImplHelper< css::lang::XLocalizable, css::frame::XDocumentTemplates, css::lang::XServiceInfo > +{ + std::unique_ptr<SfxDocTplService_Impl> pImp; + +public: + explicit SfxDocTplService( const css::uno::Reference < uno::XComponentContext >& xContext ); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.sfx2.DocumentTemplates"; + } + + virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override + { + return cppu::supportsService(this, ServiceName); + } + + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override + { + css::uno::Sequence< OUString > aSeq { "com.sun.star.frame.DocumentTemplates" }; + return aSeq; + } + + + // --- XLocalizable --- + void SAL_CALL setLocale( const css::lang::Locale & eLocale ) override; + css::lang::Locale SAL_CALL getLocale() override; + + // --- XDocumentTemplates --- + css::uno::Reference< css::ucb::XContent > SAL_CALL getContent() override; + sal_Bool SAL_CALL storeTemplate( const OUString& GroupName, + const OUString& TemplateName, + const css::uno::Reference< css::frame::XStorable >& Storable ) override; + sal_Bool SAL_CALL addTemplate( const OUString& GroupName, + const OUString& TemplateName, + const OUString& SourceURL ) override; + sal_Bool SAL_CALL removeTemplate( const OUString& GroupName, + const OUString& TemplateName ) override; + sal_Bool SAL_CALL renameTemplate( const OUString& GroupName, + const OUString& OldTemplateName, + const OUString& NewTemplateName ) override; + sal_Bool SAL_CALL addGroup( const OUString& GroupName ) override; + sal_Bool SAL_CALL removeGroup( const OUString& GroupName ) override; + sal_Bool SAL_CALL renameGroup( const OUString& OldGroupName, + const OUString& NewGroupName ) override; + void SAL_CALL update() override; +}; + + +SfxDocTplService::SfxDocTplService( const uno::Reference< XComponentContext >& xContext ) +{ + pImp.reset( new SfxDocTplService_Impl(xContext) ); +} + + + +//--- XLocalizable --- + + +lang::Locale SAL_CALL SfxDocTplService::getLocale() +{ + return pImp->getLocale(); +} + + +void SAL_CALL SfxDocTplService::setLocale( const lang::Locale & rLocale ) +{ + pImp->setLocale( rLocale ); +} + + +//--- XDocumentTemplates --- + +uno::Reference< ucb::XContent > SAL_CALL SfxDocTplService::getContent() +{ + if ( pImp->init() ) + return pImp->getContent().get(); + return nullptr; +} + + +sal_Bool SAL_CALL SfxDocTplService::storeTemplate( const OUString& GroupName, + const OUString& TemplateName, + const uno::Reference< frame::XStorable >& Storable ) +{ + return pImp->init() && pImp->storeTemplate( GroupName, TemplateName, Storable ); +} + + +sal_Bool SAL_CALL SfxDocTplService::addTemplate( const OUString& rGroupName, + const OUString& rTemplateName, + const OUString& rSourceURL ) +{ + return pImp->init() && pImp->addTemplate( rGroupName, rTemplateName, rSourceURL ); +} + + +sal_Bool SAL_CALL SfxDocTplService::removeTemplate( const OUString& rGroupName, + const OUString& rTemplateName ) +{ + return pImp->init() && pImp->removeTemplate( rGroupName, rTemplateName ); +} + + +sal_Bool SAL_CALL SfxDocTplService::renameTemplate( const OUString& rGroupName, + const OUString& rOldName, + const OUString& rNewName ) +{ + if ( rOldName == rNewName ) + return true; + + return pImp->init() && pImp->renameTemplate( rGroupName, rOldName, rNewName ); +} + + +sal_Bool SAL_CALL SfxDocTplService::addGroup( const OUString& rGroupName ) +{ + return pImp->init() && pImp->addGroup( rGroupName ); +} + + +sal_Bool SAL_CALL SfxDocTplService::removeGroup( const OUString& rGroupName ) +{ + return pImp->init() && pImp->removeGroup( rGroupName ); +} + + +sal_Bool SAL_CALL SfxDocTplService::renameGroup( const OUString& rOldName, + const OUString& rNewName ) +{ + if ( rOldName == rNewName ) + return true; + + return pImp->init() && pImp->renameGroup( rOldName, rNewName ); +} + + +void SAL_CALL SfxDocTplService::update() +{ + if ( pImp->init() ) + pImp->update(); +} + +WaitWindow_Impl::WaitWindow_Impl() : WorkWindow(nullptr, WB_BORDER | WB_3DLOOK) +{ + tools::Rectangle aRect(0, 0, 300, 30000); + maText = SfxResId(RID_CNT_STR_WAITING); + maRect = GetTextRect(aRect, maText, gnTextStyle); + aRect = maRect; + aRect.AdjustRight(2 * X_OFFSET ); + aRect.AdjustBottom(2 * Y_OFFSET ); + maRect.SetPos(Point(X_OFFSET, Y_OFFSET)); + SetOutputSizePixel(aRect.GetSize()); + + Show(); + PaintImmediately(); + GetOutDev()->Flush(); +} + + +WaitWindow_Impl::~WaitWindow_Impl() +{ + disposeOnce(); +} + +void WaitWindow_Impl::dispose() +{ + Hide(); + WorkWindow::dispose(); +} + + +void WaitWindow_Impl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/) +{ + rRenderContext.DrawText(maRect, maText, gnTextStyle); +} + +void SfxDocTplService_Impl::addHierGroup( GroupList_Impl& rList, + const OUString& rTitle, + const OUString& rOwnURL ) +{ + // now get the content of the Group + Content aContent; + uno::Reference<XResultSet> xResultSet; + + try + { + aContent = Content(rOwnURL, maCmdEnv, comphelper::getProcessComponentContext()); + xResultSet = aContent.createCursor( { TITLE, TARGET_URL, PROPERTY_TYPE }, INCLUDE_DOCUMENTS_ONLY ); + } + catch (ContentCreationException&) + { + TOOLS_WARN_EXCEPTION( "sfx.doc", "" ); + } + catch (Exception&) {} + + if ( !xResultSet.is() ) + return; + + GroupData_Impl *pGroup = new GroupData_Impl( rTitle ); + pGroup->setHierarchy( true ); + pGroup->setHierarchyURL( rOwnURL ); + rList.push_back( std::unique_ptr<GroupData_Impl>(pGroup) ); + + uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY ); + uno::Reference< XRow > xRow( xResultSet, UNO_QUERY ); + + try + { + while ( xResultSet->next() ) + { + bool bUpdateType = false; + DocTemplates_EntryData_Impl *pData; + + const OUString aTitle( xRow->getString( 1 ) ); + const OUString aTargetDir( xRow->getString( 2 ) ); + OUString aType( xRow->getString( 3 ) ); + const OUString aHierURL {xContentAccess->queryContentIdentifierString()}; + + if ( aType.isEmpty() ) + { + OUString aTmpTitle; + + bool bDocHasTitle = false; + getTitleFromURL( aTargetDir, aTmpTitle, aType, bDocHasTitle ); + + if ( !aType.isEmpty() ) + bUpdateType = true; + } + + pData = pGroup->addEntry( aTitle, aTargetDir, aType, aHierURL ); + pData->setUpdateType( bUpdateType ); + } + } + catch ( Exception& ) {} +} + + +void SfxDocTplService_Impl::addFsysGroup( GroupList_Impl& rList, + const OUString& rTitle, + const OUString& rUITitle, + const OUString& rOwnURL, + bool bWriteableGroup ) +{ + OUString aTitle; + + if ( rUITitle.isEmpty() ) + { + // reserved FS names that should not be used + if ( rTitle == "wizard" ) + return; + else if ( rTitle == "internal" ) + return; + + aTitle = getLongName( rTitle ); + } + else + aTitle = rUITitle; + + if ( aTitle.isEmpty() ) + return; + + GroupData_Impl* pGroup = nullptr; + for (const std::unique_ptr<GroupData_Impl>& i : rList) + { + if ( i->getTitle() == aTitle ) + { + pGroup = i.get(); + break; + } + } + + if ( !pGroup ) + { + pGroup = new GroupData_Impl( aTitle ); + rList.push_back( std::unique_ptr<GroupData_Impl>(pGroup) ); + } + + if ( bWriteableGroup ) + pGroup->setTargetURL( rOwnURL ); + + pGroup->setInUse(); + + // now get the content of the Group + Content aContent; + uno::Reference< XResultSet > xResultSet; + Sequence< OUString > aProps { TITLE }; + + try + { + // this method is only used during checking of the available template-folders + // that should happen quietly + uno::Reference< XCommandEnvironment > aQuietEnv; + aContent = Content( rOwnURL, aQuietEnv, comphelper::getProcessComponentContext() ); + xResultSet = aContent.createCursor( aProps, INCLUDE_DOCUMENTS_ONLY ); + } + catch ( Exception& ) {} + + if ( !xResultSet.is() ) + return; + + uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY ); + uno::Reference< XRow > xRow( xResultSet, UNO_QUERY ); + + try + { + while ( xResultSet->next() ) + { + OUString aChildTitle( xRow->getString( 1 ) ); + const OUString aTargetURL {xContentAccess->queryContentIdentifierString()}; + OUString aType; + + if ( aChildTitle == "sfx.tlx" || aChildTitle == "groupuinames.xml" ) + continue; + + bool bDocHasTitle = false; + getTitleFromURL( aTargetURL, aChildTitle, aType, bDocHasTitle ); + + pGroup->addEntry( aChildTitle, aTargetURL, aType, OUString() ); + } + } + catch ( Exception& ) {} +} + + +void SfxDocTplService_Impl::createFromContent( GroupList_Impl& rList, + Content &rContent, + bool bHierarchy, + bool bWriteableContent ) +{ + const OUString aTargetURL {rContent.get()->getIdentifier()->getContentIdentifier()}; + + // when scanning the file system, we have to add the 'standard' group, too + if ( ! bHierarchy ) + { + const OUString aUIStdTitle {getLongName( STANDARD_FOLDER )}; + addFsysGroup( rList, OUString(), aUIStdTitle, aTargetURL, bWriteableContent ); + } + + // search for predefined UI names + INetURLObject aLayerObj( aTargetURL ); + + // TODO/LATER: Use hashmap in future + std::vector< beans::StringPair > aUINames; + if ( !bHierarchy ) + aUINames = ReadUINamesForTemplateDir_Impl( aLayerObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + + uno::Reference< XResultSet > xResultSet; + Sequence< OUString > aProps { TITLE }; + + try + { + xResultSet = rContent.createCursor( aProps, INCLUDE_FOLDERS_ONLY ); + } + catch ( Exception& ) {} + + if ( !xResultSet.is() ) + return; + + uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY ); + uno::Reference< XRow > xRow( xResultSet, UNO_QUERY ); + + try + { + while ( xResultSet->next() ) + { + // TODO/LATER: clarify the encoding of the Title + const OUString aTitle( xRow->getString( 1 ) ); + const OUString aTargetSubfolderURL( xContentAccess->queryContentIdentifierString() ); + + if ( bHierarchy ) + addHierGroup( rList, aTitle, aTargetSubfolderURL ); + else + { + OUString aUITitle; + for (const beans::StringPair & rUIName : aUINames) + if ( rUIName.First == aTitle ) + { + aUITitle = rUIName.Second; + break; + } + + addFsysGroup( rList, aTitle, aUITitle, aTargetSubfolderURL, bWriteableContent ); + } + } + } + catch ( Exception& ) {} +} + + +void SfxDocTplService_Impl::removeFromHierarchy( DocTemplates_EntryData_Impl const *pData ) +{ + Content aTemplate; + + if ( Content::create( pData->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) ) + { + removeContent( aTemplate ); + } +} + + +void SfxDocTplService_Impl::addToHierarchy( GroupData_Impl const *pGroup, + DocTemplates_EntryData_Impl const *pData ) +{ + Content aGroup, aTemplate; + + if ( ! Content::create( pGroup->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + return; + + // Check, if there's a template with the given name in this group + // Return if there is already a template + INetURLObject aGroupObj( pGroup->getHierarchyURL() ); + + aGroupObj.insertName( pData->getTitle(), false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + + const OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + if ( Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) ) + return; + + addEntry( aGroup, pData->getTitle(), + pData->getTargetURL(), + pData->getType() ); +} + + +void SfxDocTplService_Impl::updateData( DocTemplates_EntryData_Impl const *pData ) +{ + Content aTemplate; + + if ( ! Content::create( pData->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) ) + return; + + if ( pData->getUpdateType() ) + { + setProperty( aTemplate, PROPERTY_TYPE, Any( pData->getType() ) ); + } + + if ( pData->getUpdateLink() ) + { + setProperty( aTemplate, TARGET_URL, Any( pData->getTargetURL() ) ); + } +} + + +void SfxDocTplService_Impl::addGroupToHierarchy( GroupData_Impl *pGroup ) +{ + Content aGroup; + + INetURLObject aNewGroupObj( maRootURL ); + aNewGroupObj.insertName( pGroup->getTitle(), false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + + const OUString aNewGroupURL {aNewGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + if ( createFolder( aNewGroupURL, false, false, aGroup ) ) + { + setProperty( aGroup, TARGET_DIR_URL, Any( pGroup->getTargetURL() ) ); + pGroup->setHierarchyURL( aNewGroupURL ); + + size_t nCount = pGroup->count(); + for ( size_t i = 0; i < nCount; i++ ) + { + DocTemplates_EntryData_Impl *pData = pGroup->getEntry( i ); + addToHierarchy( pGroup, pData ); // add entry to hierarchy + } + } +} + + +void SfxDocTplService_Impl::removeFromHierarchy( GroupData_Impl const *pGroup ) +{ + Content aGroup; + + if ( Content::create( pGroup->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + { + removeContent( aGroup ); + } +} + + +GroupData_Impl::GroupData_Impl( const OUString& rTitle ) + : maTitle(rTitle), mbInUse(false), mbInHierarchy(false) +{ +} + + +DocTemplates_EntryData_Impl* GroupData_Impl::addEntry( const OUString& rTitle, + const OUString& rTargetURL, + const OUString& rType, + const OUString& rHierURL ) +{ + DocTemplates_EntryData_Impl* pData = nullptr; + bool EntryFound = false; + + for (auto const & p : maEntries) + { + pData = p.get(); + if ( pData->getTitle() == rTitle ) + { + EntryFound = true; + break; + } + } + + if ( !EntryFound ) + { + pData = new DocTemplates_EntryData_Impl( rTitle ); + pData->setTargetURL( rTargetURL ); + pData->setType( rType ); + if ( !rHierURL.isEmpty() ) + { + pData->setHierarchyURL( rHierURL ); + pData->setHierarchy( true ); + } + maEntries.emplace_back( pData ); + } + else + { + if ( !rHierURL.isEmpty() ) + { + pData->setHierarchyURL( rHierURL ); + pData->setHierarchy( true ); + } + + if ( pData->getInHierarchy() ) + pData->setInUse(); + + if ( rTargetURL != pData->getTargetURL() ) + { + pData->setTargetURL( rTargetURL ); + pData->setUpdateLink( true ); + } + } + + return pData; +} + + +DocTemplates_EntryData_Impl::DocTemplates_EntryData_Impl( const OUString& rTitle ) + : maTitle(rTitle), mbInHierarchy(false), mbInUse(false), mbUpdateType(false), mbUpdateLink(false) +{ +} + +} + +// static +bool SfxURLRelocator_Impl::propertyCanContainOfficeDir( + std::u16string_view rPropName ) +{ + // Note: TargetURL is handled by UCB itself (because it is a property + // with a predefined semantic). Additional Core properties introduced + // be a client app must be handled by the client app itself, because + // the UCB does not know the semantics of those properties. + return ( rPropName == TARGET_DIR_URL || rPropName == PROPERTY_DIRLIST ); +} + + +SfxURLRelocator_Impl::SfxURLRelocator_Impl( const uno::Reference< XComponentContext > & xContext ) +: mxContext( xContext ) +{ +} + + +SfxURLRelocator_Impl::~SfxURLRelocator_Impl() +{ +} + + +void SfxURLRelocator_Impl::initOfficeInstDirs() +{ + if ( !mxOfficeInstDirs.is() ) + { + std::scoped_lock aGuard( maMutex ); + if ( !mxOfficeInstDirs.is() ) + { + OSL_ENSURE( mxContext.is(), "No service manager!" ); + + mxOfficeInstDirs = theOfficeInstallationDirectories::get(mxContext); + } + } +} + + +void SfxURLRelocator_Impl::implExpandURL( OUString& io_url ) +{ + const INetURLObject aParser( io_url ); + if ( aParser.GetProtocol() != INetProtocol::VndSunStarExpand ) + return; + + io_url = aParser.GetURLPath( INetURLObject::DecodeMechanism::WithCharset ); + try + { + if ( !mxMacroExpander.is() ) + { + mxMacroExpander.set( theMacroExpander::get(mxContext), UNO_SET_THROW ); + } + io_url = mxMacroExpander->expandMacros( io_url ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("sfx.doc"); + } +} + + +void SfxURLRelocator_Impl::makeRelocatableURL( OUString & rURL ) +{ + if ( !rURL.isEmpty() ) + { + initOfficeInstDirs(); + implExpandURL( rURL ); + rURL = mxOfficeInstDirs->makeRelocatableURL( rURL ); + } +} + + +void SfxURLRelocator_Impl::makeAbsoluteURL( OUString & rURL ) +{ + if ( !rURL.isEmpty() ) + { + initOfficeInstDirs(); + implExpandURL( rURL ); + rURL = mxOfficeInstDirs->makeAbsoluteURL( rURL ); + } +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * +com_sun_star_comp_sfx2_DocumentTemplates_get_implementation( + css::uno::XComponentContext *context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new SfxDocTplService(context)); +} + +OUString DocTemplLocaleHelper::GetStandardGroupString() +{ + return SfxResId(TEMPLATE_LONG_NAMES_ARY[0]); +} + +std::vector<OUString> DocTemplLocaleHelper::GetBuiltInGroupNames() +{ + std::vector<OUString> aGroups; + for(auto const & aGroupName : TEMPLATE_LONG_NAMES_ARY) + aGroups.push_back(SfxResId(aGroupName)); + return aGroups; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |