/* -*- 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 u"com.sun.star.comp.sfx2.DocumentTemplates"_ustr;
    }
 
    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 { u"com.sun.star.frame.DocumentTemplates"_ustr };
        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;
    rtl::Reference< TplTaskEnvironment >             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()
{
    const 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( u"Template_internal"_ustr );
    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 );
                if (xTypeDetection)
                {
                    SequenceAsHashMap aTypeProps( xTypeDetection->getByName( aDocType ) );
                    aType = aTypeProps.getUnpackedValueOrDefault(
                                u"MediaType"_ustr,
                                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,
                                        u"groupuinames.xml"_ustr,
                                        ucb::NameClash::OVERWRITE,
                                        u"text/xml"_ustr );
 
        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,
                                                u"UserGroup"_ustr,
                                                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,
                                            u"UserGroup"_ustr,
                                            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(u"not sub path"_ustr, 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
    {
        const 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(u"/org.openoffice.Setup/Office/Factories/"_ustr)}
        }));
        uno::Reference< container::XNameAccess > xSOFConfig(
            xConfigProvider->createInstanceWithArguments(
                                    u"com.sun.star.configuration.ConfigurationAccess"_ustr,
                                    aArgs ),
            uno::UNO_QUERY_THROW );
 
        uno::Reference< container::XNameAccess > xApplConfig;
        xSOFConfig->getByName( sDocServiceName ) >>= xApplConfig;
        if ( !xApplConfig.is() )
            throw uno::RuntimeException();
 
        OUString aFilterName;
        xApplConfig->getByName(u"ooSetupFactoryActualTemplateFilter"_ustr) >>= aFilterName;
        if ( aFilterName.isEmpty() )
            throw uno::RuntimeException();
 
        // find the related type name
        uno::Reference< container::XNameAccess > xFilterFactory(
            mxContext->getServiceManager()->createInstanceWithContext(u"com.sun.star.document.FilterFactory"_ustr, mxContext),
            uno::UNO_QUERY_THROW );
 
        uno::Sequence< beans::PropertyValue > aFilterData;
        xFilterFactory->getByName( aFilterName ) >>= aFilterData;
        OUString aTypeName;
        for (const auto& rProp : 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(u"com.sun.star.document.TypeDetection"_ustr, mxContext),
                    uno::UNO_QUERY_THROW );
 
        SequenceAsHashMap aTypeProps( xTypeDetection->getByName( aTypeName ) );
        uno::Sequence< OUString > aAllExt =
            aTypeProps.getUnpackedValueOrDefault(u"Extensions"_ustr, Sequence< OUString >() );
        if ( !aAllExt.hasElements() )
            throw uno::RuntimeException();
 
        const OUString aMediaType {aTypeProps.getUnpackedValueOrDefault(u"MediaType"_ustr, 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, u"UserTemplate"_ustr, aExt );
 
            if ( aNewTemplateTargetURL.isEmpty() )
                throw uno::RuntimeException();
        }
 
        // store template
        uno::Sequence< PropertyValue > aStoreArgs{
            comphelper::makePropertyValue(u"FilterName"_ustr, aFilterName),
            comphelper::makePropertyValue(u"DocumentTitle"_ustr, 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(std::move(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: */

V530 The return value of function 'CutExtension' is required to be utilized.

V530 The return value of function 'CutExtension' is required to be utilized.

V530 The return value of function 'CutExtension' is required to be utilized.

V1053 Calling the 'SetOutputSizePixel' virtual function in the constructor may lead to unexpected result at runtime.

V547 Expression '!mbLocaleSet' is always true.

V1019 Compound assignment expression 'aValue >>= aPropValue' is used inside condition.

V1019 Compound assignment expression 'aProperty >>= bReadOnly' is used inside condition.