summaryrefslogtreecommitdiffstats
path: root/sfx2/source/doc/doctemplates.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sfx2/source/doc/doctemplates.cxx')
-rw-r--r--sfx2/source/doc/doctemplates.cxx2640
1 files changed, 2640 insertions, 0 deletions
diff --git a/sfx2/source/doc/doctemplates.cxx b/sfx2/source/doc/doctemplates.cxx
new file mode 100644
index 0000000000..20aecdd028
--- /dev/null
+++ b/sfx2/source/doc/doctemplates.cxx
@@ -0,0 +1,2640 @@
+/* -*- 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 <comphelper/diagnose_ex.hxx>
+#include <tools/urlobj.hxx>
+#include <rtl/uri.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <utility>
+#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 OUString TITLE = u"Title"_ustr;
+constexpr OUString IS_FOLDER = u"IsFolder"_ustr;
+constexpr OUString IS_DOCUMENT = u"IsDocument"_ustr;
+constexpr OUString TARGET_URL = u"TargetURL"_ustr;
+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 OUString TYPE_FSYS_FOLDER = u"application/vnd.sun.staroffice.fsys-folder"_ustr;
+constexpr OUStringLiteral TYPE_FSYS_FILE = u"application/vnd.sun.staroffice.fsys-file";
+
+constexpr OUString PROPERTY_DIRLIST = u"DirectoryList"_ustr;
+constexpr OUString PROPERTY_NEEDSUPDATE = u"NeedsUpdate"_ustr;
+constexpr OUString PROPERTY_TYPE = u"TypeDescription"_ustr;
+
+constexpr OUString TARGET_DIR_URL = u"TargetDirURL"_ustr;
+constexpr OUStringLiteral COMMAND_DELETE = u"delete";
+
+constexpr OUString STANDARD_FOLDER = u"standard"_ustr;
+
+#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( uno::Reference< task::XInteractionHandler> xInteractionHandler )
+ : m_xInteractionHandler(std::move( xInteractionHandler ))
+ {}
+
+ 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 : public ::cppu::WeakImplHelper< css::lang::XLocalizable, css::frame::XDocumentTemplates, css::lang::XServiceInfo >
+{
+public:
+ explicit SfxDocTplService( const css::uno::Reference < uno::XComponentContext >& xContext );
+ virtual ~SfxDocTplService() override;
+
+ 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;
+
+private:
+ bool init() { if ( !mbIsInitialized ) init_Impl(); return mbIsInitialized; }
+
+ void doUpdate();
+
+ 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,
+ std::u16string_view 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;
+};
+
+
+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( OUString aTitle );
+
+ 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( OUString aTitle );
+
+ 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::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);
+ doUpdate();
+ }
+ SolarMutexGuard aSecondSolarGuard;
+
+ pWin.disposeAndClear();
+ }
+ else if ( needsUpdate() )
+ // the UI should be shown only on the first update
+ doUpdate();
+ }
+ else
+ {
+ SAL_WARN( "sfx.doc", "init_Impl(): Could not create root" );
+ }
+
+ mbIsInitialized = bIsInitialized;
+}
+
+
+void SfxDocTplService::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",
+ "presnt",
+ "draw",
+ "l10n",
+};
+
+void SfxDocTplService::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::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::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);
+
+ 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 && rTemplateDir.startsWithIgnoreAsciiCase("vnd.sun.star.expand:", &rTemplateDir))
+ {
+ rTemplateDir
+ = rtl::Uri::decode(rTemplateDir, rtl_UriDecodeStrict, RTL_TEXTENCODING_UTF8);
+ 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::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::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::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::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::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::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::CreateNewUniqueFileWithPrefix( std::u16string_view aPath,
+ const OUString& aPrefix,
+ std::u16string_view 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.empty() || aExt[0] != '.' )
+ 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::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::removeContent( const OUString& rContentURL )
+{
+ Content aContent;
+
+ if ( Content::create( rContentURL, maCmdEnv, comphelper::getProcessComponentContext(), aContent ) )
+ return removeContent( aContent );
+ return false;
+}
+
+
+bool SfxDocTplService::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::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::SfxDocTplService( const uno::Reference< XComponentContext > & xContext )
+ : mxContext(xContext), mbIsInitialized(false), mbLocaleSet(false), maRelocator(xContext)
+{
+}
+
+
+SfxDocTplService::~SfxDocTplService()
+{
+ ::osl::MutexGuard aGuard( maMutex );
+ maNames.clear();
+}
+
+
+lang::Locale SfxDocTplService::getLocale()
+{
+ ::osl::MutexGuard aGuard( maMutex );
+
+ if ( !mbLocaleSet )
+ getDefaultLocale();
+
+ return maLocale;
+}
+
+
+void SfxDocTplService::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::update()
+{
+ if (!init())
+ return;
+
+ doUpdate();
+}
+
+
+void SfxDocTplService::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::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::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::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::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::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::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;
+}
+
+
+sal_Bool SfxDocTplService::addGroup( const OUString& rGroupName )
+{
+ if (!init())
+ return false;
+
+ ::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;
+}
+
+
+sal_Bool SfxDocTplService::removeGroup( const OUString& rGroupName )
+{
+ // remove all the elements that have the prefix aTargetURL
+ // if the group does not have other elements remove it
+
+ if (!init())
+ return false;
+
+ ::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;
+}
+
+
+sal_Bool SfxDocTplService::renameGroup( const OUString& rOldName,
+ const OUString& rNewName )
+{
+ if ( rOldName == rNewName )
+ return true;
+
+ if (!init())
+ return false;
+
+ ::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;
+}
+
+
+sal_Bool SfxDocTplService::storeTemplate( const OUString& rGroupName,
+ const OUString& rTemplateName,
+ const uno::Reference< frame::XStorable >& rStorable )
+{
+ if (!init())
+ return false;
+
+ ::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;
+ }
+}
+
+
+sal_Bool SfxDocTplService::addTemplate( const OUString& rGroupName,
+ const OUString& rTemplateName,
+ const OUString& rSourceURL )
+{
+ if (!init())
+ return false;
+
+ ::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 constexpr OUString aPropertyName( u"IsReadOnly"_ustr );
+ 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::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::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();
+}
+
+sal_Bool SfxDocTplService::removeTemplate( const OUString& rGroupName,
+ const OUString& rTemplateName )
+{
+ if (!init())
+ return false;
+ ::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 );
+}
+
+
+sal_Bool SfxDocTplService::renameTemplate( const OUString& rGroupName,
+ const OUString& rOldName,
+ const OUString& rNewName )
+{
+ if ( rOldName == rNewName )
+ return true;
+ if (!init())
+ return false;
+
+ ::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 );
+}
+
+
+
+//--- XDocumentTemplates ---
+
+uno::Reference< ucb::XContent > SAL_CALL SfxDocTplService::getContent()
+{
+ if ( init() )
+ return maRootContent.get();
+ return nullptr;
+}
+
+
+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::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::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::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::removeFromHierarchy( DocTemplates_EntryData_Impl const *pData )
+{
+ Content aTemplate;
+
+ if ( Content::create( pData->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) )
+ {
+ removeContent( aTemplate );
+ }
+}
+
+
+void SfxDocTplService::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::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::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::removeFromHierarchy( GroupData_Impl const *pGroup )
+{
+ Content aGroup;
+
+ if ( Content::create( pGroup->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
+ {
+ removeContent( aGroup );
+ }
+}
+
+
+GroupData_Impl::GroupData_Impl( OUString aTitle )
+ : maTitle(std::move(aTitle)), 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( OUString aTitle )
+ : maTitle(std::move(aTitle)), 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( uno::Reference< XComponentContext > xContext )
+: mxContext(std::move( 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: */