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

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.