Bump version to 24.04.3.4
[LibreOffice.git] / sfx2 / source / doc / doctemplates.cxx
blob20aecdd028a619c856fdf129c95c7075766f8542
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <osl/mutex.hxx>
21 #include <comphelper/diagnose_ex.hxx>
22 #include <tools/urlobj.hxx>
23 #include <rtl/uri.hxx>
24 #include <rtl/ustring.hxx>
25 #include <sal/log.hxx>
26 #include <utility>
27 #include <vcl/svapp.hxx>
28 #include <vcl/wrkwin.hxx>
29 #include <unotools/pathoptions.hxx>
30 #include <comphelper/processfactory.hxx>
31 #include <comphelper/propertysequence.hxx>
32 #include <comphelper/propertyvalue.hxx>
33 #include <comphelper/sequenceashashmap.hxx>
34 #include <comphelper/storagehelper.hxx>
35 #include <comphelper/string.hxx>
36 #include <cppuhelper/implbase.hxx>
37 #include <cppuhelper/supportsservice.hxx>
38 #include <com/sun/star/beans/IllegalTypeException.hpp>
39 #include <com/sun/star/beans/PropertyAttribute.hpp>
40 #include <com/sun/star/beans/PropertyExistException.hpp>
41 #include <com/sun/star/beans/XPropertySetInfo.hpp>
42 #include <com/sun/star/beans/XPropertyContainer.hpp>
43 #include <com/sun/star/beans/StringPair.hpp>
44 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
45 #include <com/sun/star/util/theMacroExpander.hpp>
46 #include <com/sun/star/util/theOfficeInstallationDirectories.hpp>
47 #include <com/sun/star/configuration/theDefaultProvider.hpp>
48 #include <com/sun/star/document/XTypeDetection.hpp>
49 #include <com/sun/star/document/DocumentProperties.hpp>
50 #include <com/sun/star/io/TempFile.hpp>
51 #include <com/sun/star/sdbc/XResultSet.hpp>
52 #include <com/sun/star/sdbc/XRow.hpp>
53 #include <com/sun/star/ucb/ContentCreationException.hpp>
54 #include <com/sun/star/ucb/NameClash.hpp>
55 #include <com/sun/star/ucb/NameClashException.hpp>
56 #include <com/sun/star/ucb/XCommandEnvironment.hpp>
57 #include <com/sun/star/ucb/XContentAccess.hpp>
58 #include <com/sun/star/frame/ModuleManager.hpp>
59 #include <com/sun/star/uno/Exception.hpp>
60 #include <com/sun/star/task/InteractionHandler.hpp>
61 #include <com/sun/star/ucb/XProgressHandler.hpp>
62 #include <com/sun/star/container/XNameAccess.hpp>
63 #include <com/sun/star/frame/XDocumentTemplates.hpp>
64 #include <com/sun/star/frame/XStorable.hpp>
65 #include <com/sun/star/lang/Locale.hpp>
66 #include <com/sun/star/lang/XLocalizable.hpp>
67 #include <com/sun/star/lang/XServiceInfo.hpp>
68 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
69 #include <com/sun/star/ucb/XContent.hpp>
70 #include <com/sun/star/beans/PropertyValue.hpp>
71 #include <com/sun/star/uno/RuntimeException.hpp>
72 #include <com/sun/star/uno/XComponentContext.hpp>
73 #include <com/sun/star/util/thePathSettings.hpp>
75 #include <svtools/templatefoldercache.hxx>
76 #include <unotools/configmgr.hxx>
77 #include <unotools/ucbhelper.hxx>
78 #include <i18nlangtag/languagetag.hxx>
79 #include <ucbhelper/content.hxx>
80 #include <o3tl/string_view.hxx>
82 #include <sfx2/sfxresid.hxx>
83 #include <sfxurlrelocator.hxx>
84 #include "doctemplateslocal.hxx"
85 #include <sfx2/docfac.hxx>
86 #include <sfx2/strings.hrc>
87 #include <doctempl.hrc>
89 #include <memory>
90 #include <vector>
92 constexpr OUStringLiteral SERVICENAME_TYPEDETECTION = u"com.sun.star.document.TypeDetection";
94 constexpr OUStringLiteral TEMPLATE_ROOT_URL = u"vnd.sun.star.hier:/templates";
95 constexpr OUString TITLE = u"Title"_ustr;
96 constexpr OUString IS_FOLDER = u"IsFolder"_ustr;
97 constexpr OUString IS_DOCUMENT = u"IsDocument"_ustr;
98 constexpr OUString TARGET_URL = u"TargetURL"_ustr;
99 constexpr OUStringLiteral TEMPLATE_VERSION = u"TemplateComponentVersion";
100 constexpr OUStringLiteral TEMPLATE_VERSION_VALUE = u"2";
101 constexpr OUStringLiteral TYPE_FOLDER = u"application/vnd.sun.star.hier-folder";
102 constexpr OUStringLiteral TYPE_LINK = u"application/vnd.sun.star.hier-link";
103 constexpr OUString TYPE_FSYS_FOLDER = u"application/vnd.sun.staroffice.fsys-folder"_ustr;
104 constexpr OUStringLiteral TYPE_FSYS_FILE = u"application/vnd.sun.staroffice.fsys-file";
106 constexpr OUString PROPERTY_DIRLIST = u"DirectoryList"_ustr;
107 constexpr OUString PROPERTY_NEEDSUPDATE = u"NeedsUpdate"_ustr;
108 constexpr OUString PROPERTY_TYPE = u"TypeDescription"_ustr;
110 constexpr OUString TARGET_DIR_URL = u"TargetDirURL"_ustr;
111 constexpr OUStringLiteral COMMAND_DELETE = u"delete";
113 constexpr OUString STANDARD_FOLDER = u"standard"_ustr;
115 #define C_DELIM ';'
117 using namespace ::com::sun::star;
118 using namespace ::com::sun::star::beans;
119 using namespace ::com::sun::star::document;
120 using namespace ::com::sun::star::io;
121 using namespace ::com::sun::star::lang;
122 using namespace ::com::sun::star::sdbc;
123 using namespace ::com::sun::star::ucb;
124 using namespace ::com::sun::star::uno;
125 using namespace ::com::sun::star::container;
126 using namespace ::com::sun::star::util;
128 using namespace ::ucbhelper;
129 using namespace ::comphelper;
131 using ::std::vector;
133 namespace {
135 class WaitWindow_Impl : public WorkWindow
137 tools::Rectangle maRect;
138 OUString maText;
139 static constexpr DrawTextFlags gnTextStyle = DrawTextFlags::Center | DrawTextFlags::VCenter | DrawTextFlags::WordBreak | DrawTextFlags::MultiLine;
141 public:
142 WaitWindow_Impl();
143 virtual ~WaitWindow_Impl() override;
144 virtual void dispose() override;
145 virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override;
148 #define X_OFFSET 15
149 #define Y_OFFSET 15
152 struct NamePair_Impl
154 OUString maShortName;
155 OUString maLongName;
158 class DocTemplates_EntryData_Impl;
159 class GroupData_Impl;
161 typedef vector< std::unique_ptr<GroupData_Impl> > GroupList_Impl;
164 class TplTaskEnvironment : public ::cppu::WeakImplHelper< ucb::XCommandEnvironment >
166 uno::Reference< task::XInteractionHandler > m_xInteractionHandler;
168 public:
169 explicit TplTaskEnvironment( uno::Reference< task::XInteractionHandler> xInteractionHandler )
170 : m_xInteractionHandler(std::move( xInteractionHandler ))
173 virtual uno::Reference<task::XInteractionHandler> SAL_CALL getInteractionHandler() override
174 { return m_xInteractionHandler; }
176 virtual uno::Reference<ucb::XProgressHandler> SAL_CALL getProgressHandler() override
177 { return uno::Reference<ucb::XProgressHandler>(); }
180 class SfxDocTplService : public ::cppu::WeakImplHelper< css::lang::XLocalizable, css::frame::XDocumentTemplates, css::lang::XServiceInfo >
182 public:
183 explicit SfxDocTplService( const css::uno::Reference < uno::XComponentContext >& xContext );
184 virtual ~SfxDocTplService() override;
186 virtual OUString SAL_CALL getImplementationName() override
188 return "com.sun.star.comp.sfx2.DocumentTemplates";
191 virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
193 return cppu::supportsService(this, ServiceName);
196 virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
198 css::uno::Sequence< OUString > aSeq { "com.sun.star.frame.DocumentTemplates" };
199 return aSeq;
203 // --- XLocalizable ---
204 void SAL_CALL setLocale( const css::lang::Locale & eLocale ) override;
205 css::lang::Locale SAL_CALL getLocale() override;
207 // --- XDocumentTemplates ---
208 css::uno::Reference< css::ucb::XContent > SAL_CALL getContent() override;
209 sal_Bool SAL_CALL storeTemplate( const OUString& GroupName,
210 const OUString& TemplateName,
211 const css::uno::Reference< css::frame::XStorable >& Storable ) override;
212 sal_Bool SAL_CALL addTemplate( const OUString& GroupName,
213 const OUString& TemplateName,
214 const OUString& SourceURL ) override;
215 sal_Bool SAL_CALL removeTemplate( const OUString& GroupName,
216 const OUString& TemplateName ) override;
217 sal_Bool SAL_CALL renameTemplate( const OUString& GroupName,
218 const OUString& OldTemplateName,
219 const OUString& NewTemplateName ) override;
220 sal_Bool SAL_CALL addGroup( const OUString& GroupName ) override;
221 sal_Bool SAL_CALL removeGroup( const OUString& GroupName ) override;
222 sal_Bool SAL_CALL renameGroup( const OUString& OldGroupName,
223 const OUString& NewGroupName ) override;
224 void SAL_CALL update() override;
226 private:
227 bool init() { if ( !mbIsInitialized ) init_Impl(); return mbIsInitialized; }
229 void doUpdate();
231 uno::Reference< XComponentContext > mxContext;
232 uno::Reference< XCommandEnvironment > maCmdEnv;
233 uno::Reference< XDocumentProperties> m_xDocProps;
234 uno::Reference< XTypeDetection > mxType;
236 ::osl::Mutex maMutex;
237 Sequence< OUString > maTemplateDirs;
238 Sequence< OUString > maInternalTemplateDirs;
239 OUString maRootURL;
240 std::vector< NamePair_Impl > maNames;
241 lang::Locale maLocale;
242 Content maRootContent;
243 bool mbIsInitialized : 1;
244 bool mbLocaleSet : 1;
246 SfxURLRelocator_Impl maRelocator;
248 void init_Impl();
249 void getDefaultLocale();
250 void getDirList();
251 void readFolderList();
252 bool needsUpdate();
253 OUString getLongName( const OUString& rShortName );
254 bool setTitleForURL( const OUString& rURL, const OUString& aTitle );
255 void getTitleFromURL( const OUString& rURL, OUString& aTitle, OUString& aType, bool& bDocHasTitle );
257 bool addEntry( Content& rParentFolder,
258 const OUString& rTitle,
259 const OUString& rTargetURL,
260 const OUString& rType );
262 bool createFolder( const OUString& rNewFolderURL,
263 bool bCreateParent,
264 bool bFsysFolder,
265 Content &rNewFolder );
267 static bool CreateNewUniqueFolderWithPrefix( std::u16string_view aPath,
268 const OUString& aPrefix,
269 OUString& aNewFolderName,
270 OUString& aNewFolderURL,
271 Content& aNewFolder );
272 static OUString CreateNewUniqueFileWithPrefix( std::u16string_view aPath,
273 const OUString& aPrefix,
274 std::u16string_view aExt );
276 std::vector< beans::StringPair > ReadUINamesForTemplateDir_Impl( std::u16string_view aUserPath );
277 bool UpdateUINamesForTemplateDir_Impl( std::u16string_view aUserPath,
278 const OUString& aGroupName,
279 const OUString& aNewFolderName );
280 bool ReplaceUINamesForTemplateDir_Impl( std::u16string_view aUserPath,
281 const OUString& aFsysGroupName,
282 std::u16string_view aOldGroupName,
283 const OUString& aNewGroupName );
284 void RemoveUINamesForTemplateDir_Impl( std::u16string_view aUserPath,
285 std::u16string_view aGroupName );
286 bool WriteUINamesForTemplateDir_Impl( std::u16string_view aUserPath,
287 const std::vector< beans::StringPair >& aUINames );
289 OUString CreateNewGroupFsys( const OUString& rGroupName, Content& aGroup );
291 static bool removeContent( Content& rContent );
292 bool removeContent( const OUString& rContentURL );
294 bool setProperty( Content& rContent,
295 const OUString& rPropName,
296 const Any& rPropValue );
297 bool getProperty( Content& rContent,
298 const OUString& rPropName,
299 Any& rPropValue );
301 void createFromContent( GroupList_Impl& rList,
302 Content &rContent,
303 bool bHierarchy,
304 bool bWriteableContent );
305 void addHierGroup( GroupList_Impl& rList,
306 const OUString& rTitle,
307 const OUString& rOwnURL );
308 void addFsysGroup( GroupList_Impl& rList,
309 const OUString& rTitle,
310 const OUString& rUITitle,
311 const OUString& rOwnURL,
312 bool bWriteableGroup );
313 void removeFromHierarchy( DocTemplates_EntryData_Impl const *pData );
314 void addToHierarchy( GroupData_Impl const *pGroup,
315 DocTemplates_EntryData_Impl const *pData );
317 void removeFromHierarchy( GroupData_Impl const *pGroup );
318 void addGroupToHierarchy( GroupData_Impl *pGroup );
320 void updateData( DocTemplates_EntryData_Impl const *pData );
322 //See: #i66157# and rhbz#1065807
323 //return which template dir the rURL is a subpath of
324 OUString findParentTemplateDir(const OUString& rURL) const;
326 //See: #i66157# and rhbz#1065807
327 //return true if rURL is a path (or subpath of) a dir which is not a user path
328 //which implies neither it or its contents can be removed
329 bool isInternalTemplateDir(const OUString& rURL) const;
333 class DocTemplates_EntryData_Impl
335 OUString maTitle;
336 OUString maType;
337 OUString maTargetURL;
338 OUString maHierarchyURL;
340 bool mbInHierarchy : 1;
341 bool mbInUse : 1;
342 bool mbUpdateType : 1;
343 bool mbUpdateLink : 1;
345 public:
346 explicit DocTemplates_EntryData_Impl( OUString aTitle );
348 void setInUse() { mbInUse = true; }
349 void setHierarchy( bool bInHierarchy ) { mbInHierarchy = bInHierarchy; }
350 void setUpdateLink( bool bUpdateLink ) { mbUpdateLink = bUpdateLink; }
351 void setUpdateType( bool bUpdateType ) { mbUpdateType = bUpdateType; }
353 bool getInUse() const { return mbInUse; }
354 bool getInHierarchy() const { return mbInHierarchy; }
355 bool getUpdateLink() const { return mbUpdateLink; }
356 bool getUpdateType() const { return mbUpdateType; }
358 const OUString& getHierarchyURL() const { return maHierarchyURL; }
359 const OUString& getTargetURL() const { return maTargetURL; }
360 const OUString& getTitle() const { return maTitle; }
361 const OUString& getType() const { return maType; }
363 void setHierarchyURL( const OUString& rURL ) { maHierarchyURL = rURL; }
364 void setTargetURL( const OUString& rURL ) { maTargetURL = rURL; }
365 void setType( const OUString& rType ) { maType = rType; }
369 class GroupData_Impl
371 std::vector< std::unique_ptr<DocTemplates_EntryData_Impl> > maEntries;
372 OUString maTitle;
373 OUString maHierarchyURL;
374 OUString maTargetURL;
375 bool mbInUse : 1;
376 bool mbInHierarchy : 1;
378 public:
379 explicit GroupData_Impl( OUString aTitle );
381 void setInUse() { mbInUse = true; }
382 void setHierarchy( bool bInHierarchy ) { mbInHierarchy = bInHierarchy; }
383 void setHierarchyURL( const OUString& rURL ) { maHierarchyURL = rURL; }
384 void setTargetURL( const OUString& rURL ) { maTargetURL = rURL; }
386 bool getInUse() const { return mbInUse; }
387 bool getInHierarchy() const { return mbInHierarchy; }
388 const OUString& getHierarchyURL() const { return maHierarchyURL; }
389 const OUString& getTargetURL() const { return maTargetURL; }
390 const OUString& getTitle() const { return maTitle; }
392 DocTemplates_EntryData_Impl* addEntry( const OUString& rTitle,
393 const OUString& rTargetURL,
394 const OUString& rType,
395 const OUString& rHierURL );
396 size_t count() { return maEntries.size(); }
397 DocTemplates_EntryData_Impl* getEntry( size_t nPos ) { return maEntries[ nPos ].get(); }
401 // private SfxDocTplService_Impl
403 void SfxDocTplService::init_Impl()
405 uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
406 uno::Reference < task::XInteractionHandler > xInteractionHandler(
407 task::InteractionHandler::createWithParent(xContext, nullptr), uno::UNO_QUERY_THROW );
408 maCmdEnv = new TplTaskEnvironment( xInteractionHandler );
410 ::osl::ClearableMutexGuard aGuard( maMutex );
411 bool bIsInitialized = false;
412 bool bNeedsUpdate = false;
414 if ( !mbLocaleSet )
415 getDefaultLocale();
417 // convert locale to string
418 // set maRootContent to the root of the templates hierarchy. Create the
419 // entry if necessary
421 maRootURL = TEMPLATE_ROOT_URL + "/" + LanguageTag::convertToBcp47(maLocale);
423 const OUString aTemplVersPropName( TEMPLATE_VERSION );
424 const OUString aTemplVers( TEMPLATE_VERSION_VALUE );
425 if ( Content::create( maRootURL, maCmdEnv, comphelper::getProcessComponentContext(), maRootContent ) )
427 uno::Any aValue;
428 OUString aPropValue;
429 if ( getProperty( maRootContent, aTemplVersPropName, aValue )
430 && ( aValue >>= aPropValue )
431 && aPropValue == aTemplVers )
433 bIsInitialized = true;
435 else
436 removeContent( maRootContent );
439 if ( !bIsInitialized )
441 if ( createFolder( maRootURL, true, false, maRootContent )
442 && setProperty( maRootContent, aTemplVersPropName, uno::Any( aTemplVers ) ) )
443 bIsInitialized = true;
445 bNeedsUpdate = true;
448 if ( bIsInitialized )
450 try {
451 m_xDocProps.set(document::DocumentProperties::create(
452 ::comphelper::getProcessComponentContext()));
453 } catch (uno::RuntimeException const&) {
454 TOOLS_WARN_EXCEPTION("sfx.doc", "SfxDocTplService_Impl::init_Impl: cannot create DocumentProperties service:");
457 mxType.set( mxContext->getServiceManager()->createInstanceWithContext(SERVICENAME_TYPEDETECTION, mxContext), UNO_QUERY );
459 getDirList();
460 readFolderList();
462 if ( bNeedsUpdate )
464 aGuard.clear();
465 SolarMutexClearableGuard aSolarGuard;
467 VclPtrInstance< WaitWindow_Impl > pWin;
468 aSolarGuard.clear();
470 osl::MutexGuard anotherGuard(maMutex);
471 doUpdate();
473 SolarMutexGuard aSecondSolarGuard;
475 pWin.disposeAndClear();
477 else if ( needsUpdate() )
478 // the UI should be shown only on the first update
479 doUpdate();
481 else
483 SAL_WARN( "sfx.doc", "init_Impl(): Could not create root" );
486 mbIsInitialized = bIsInitialized;
490 void SfxDocTplService::getDefaultLocale()
492 if ( !mbLocaleSet )
494 ::osl::MutexGuard aGuard( maMutex );
495 if ( !mbLocaleSet )
497 maLocale = LanguageTag::convertToLocale( utl::ConfigManager::getUILocale(), false);
498 mbLocaleSet = true;
503 const char* TEMPLATE_SHORT_NAMES_ARY[] =
505 "standard",
506 "styles",
507 "officorr",
508 "offimisc",
509 "personal",
510 "presnt",
511 "draw",
512 "l10n",
515 void SfxDocTplService::readFolderList()
517 SolarMutexGuard aGuard;
519 static_assert( SAL_N_ELEMENTS(TEMPLATE_SHORT_NAMES_ARY) == SAL_N_ELEMENTS(TEMPLATE_LONG_NAMES_ARY), "mismatch array lengths" );
520 const size_t nCount = std::min(SAL_N_ELEMENTS(TEMPLATE_SHORT_NAMES_ARY), SAL_N_ELEMENTS(TEMPLATE_LONG_NAMES_ARY));
521 for (size_t i = 0; i < nCount; ++i)
523 NamePair_Impl aPair;
524 aPair.maShortName = OUString::createFromAscii(TEMPLATE_SHORT_NAMES_ARY[i]);
525 aPair.maLongName = SfxResId(TEMPLATE_LONG_NAMES_ARY[i]);
527 maNames.push_back( aPair );
532 OUString SfxDocTplService::getLongName( const OUString& rShortName )
534 OUString aRet;
536 for (auto const & rPair : maNames)
538 if ( rPair.maShortName == rShortName )
540 aRet = rPair.maLongName;
541 break;
545 if ( aRet.isEmpty() )
546 aRet = rShortName;
548 return aRet;
552 void SfxDocTplService::getDirList()
554 Any aValue;
556 // Get the template dir list
557 // TODO/LATER: let use service, register listener
558 INetURLObject aURL;
559 OUString aDirs = SvtPathOptions().GetTemplatePath();
560 sal_Int32 nCount = comphelper::string::getTokenCount(aDirs, C_DELIM);
562 maTemplateDirs = Sequence< OUString >( nCount );
564 uno::Reference< util::XMacroExpander > xExpander = util::theMacroExpander::get(mxContext);
566 sal_Int32 nIdx{ 0 };
567 for (auto& rTemplateDir : asNonConstRange(maTemplateDirs))
569 aURL.SetSmartProtocol( INetProtocol::File );
570 aURL.SetURL( o3tl::getToken(aDirs, 0, C_DELIM, nIdx ) );
571 rTemplateDir = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE );
573 if (xExpander && rTemplateDir.startsWithIgnoreAsciiCase("vnd.sun.star.expand:", &rTemplateDir))
575 rTemplateDir
576 = rtl::Uri::decode(rTemplateDir, rtl_UriDecodeStrict, RTL_TEXTENCODING_UTF8);
577 rTemplateDir = xExpander->expandMacros( rTemplateDir );
581 aValue <<= maTemplateDirs;
583 css::uno::Reference< css::util::XPathSettings > xPathSettings =
584 css::util::thePathSettings::get(mxContext);
586 // load internal paths
587 Any aAny = xPathSettings->getPropertyValue( "Template_internal" );
588 aAny >>= maInternalTemplateDirs;
590 for (auto& rInternalTemplateDir : asNonConstRange(maInternalTemplateDirs))
592 //expand vnd.sun.star.expand: and remove "..." from them
593 //to normalize into the expected url patterns
594 maRelocator.makeRelocatableURL(rInternalTemplateDir);
595 maRelocator.makeAbsoluteURL(rInternalTemplateDir);
598 // Store the template dir list
599 setProperty( maRootContent, PROPERTY_DIRLIST, aValue );
603 bool SfxDocTplService::needsUpdate()
605 bool bNeedsUpdate = true;
606 Any aValue;
608 // Get the template dir list
609 bool bHasProperty = getProperty( maRootContent, PROPERTY_NEEDSUPDATE, aValue );
611 if ( bHasProperty )
612 aValue >>= bNeedsUpdate;
614 // the old template component also checks this state, but it is initialized from this component
615 // so if this component was already updated the old component does not need such an update
616 ::svt::TemplateFolderCache aTempCache;
617 if ( !bNeedsUpdate )
618 bNeedsUpdate = aTempCache.needsUpdate();
620 if ( bNeedsUpdate )
621 aTempCache.storeState();
623 return bNeedsUpdate;
627 bool SfxDocTplService::setTitleForURL( const OUString& rURL, const OUString& aTitle )
629 if (m_xDocProps.is())
633 m_xDocProps->loadFromMedium(rURL, Sequence<PropertyValue>());
634 m_xDocProps->setTitle(aTitle);
636 uno::Reference< embed::XStorage > xStorage = ::comphelper::OStorageHelper::GetStorageFromURL(
637 rURL, embed::ElementModes::READWRITE);
639 uno::Sequence<beans::PropertyValue> medium( comphelper::InitPropertySequence({
640 { "DocumentBaseURL", Any(rURL) },
641 { "URL", Any(rURL) }
642 }));
644 m_xDocProps->storeToStorage(xStorage, medium);
645 return true;
647 catch ( Exception& )
651 return false;
655 void SfxDocTplService::getTitleFromURL( const OUString& rURL, OUString& aTitle, OUString& aType, bool& bDocHasTitle )
657 bDocHasTitle = false;
659 if (m_xDocProps.is())
663 m_xDocProps->loadFromMedium(rURL, Sequence<PropertyValue>());
664 aTitle = m_xDocProps->getTitle();
666 catch ( Exception& )
671 if ( aType.isEmpty() && mxType.is() )
673 const OUString aDocType {mxType->queryTypeByURL( rURL )};
674 if ( !aDocType.isEmpty() )
677 uno::Reference< container::XNameAccess > xTypeDetection( mxType, uno::UNO_QUERY_THROW );
678 SequenceAsHashMap aTypeProps( xTypeDetection->getByName( aDocType ) );
679 aType = aTypeProps.getUnpackedValueOrDefault(
680 "MediaType",
681 OUString() );
683 catch( uno::Exception& )
687 if ( aTitle.isEmpty() )
689 INetURLObject aURL( rURL );
690 aURL.CutExtension();
691 aTitle = aURL.getName( INetURLObject::LAST_SEGMENT, true,
692 INetURLObject::DecodeMechanism::WithCharset );
694 else
695 bDocHasTitle = true;
699 bool SfxDocTplService::addEntry( Content& rParentFolder,
700 const OUString& rTitle,
701 const OUString& rTargetURL,
702 const OUString& rType )
704 bool bAddedEntry = false;
706 INetURLObject aLinkObj( rParentFolder.getURL() );
707 aLinkObj.insertName( rTitle, false,
708 INetURLObject::LAST_SEGMENT,
709 INetURLObject::EncodeMechanism::All );
710 const OUString aLinkURL {aLinkObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
712 Content aLink;
714 if ( ! Content::create( aLinkURL, maCmdEnv, comphelper::getProcessComponentContext(), aLink ) )
716 Sequence< Any > aValues{ Any(rTitle), Any(false), Any(rTargetURL) };
720 rParentFolder.insertNewContent( TYPE_LINK, { TITLE, IS_FOLDER, TARGET_URL }, aValues, aLink );
721 setProperty( aLink, PROPERTY_TYPE, Any( rType ) );
722 bAddedEntry = true;
724 catch( Exception& )
727 return bAddedEntry;
731 bool SfxDocTplService::createFolder( const OUString& rNewFolderURL,
732 bool bCreateParent,
733 bool bFsysFolder,
734 Content &rNewFolder )
736 Content aParent;
737 bool bCreatedFolder = false;
738 INetURLObject aParentURL( rNewFolderURL );
739 const OUString aFolderName {aParentURL.getName( INetURLObject::LAST_SEGMENT, true,
740 INetURLObject::DecodeMechanism::WithCharset )};
742 // compute the parent folder url from the new folder url
743 // and remove the final slash, because Content::create doesn't
744 // like it
745 aParentURL.removeSegment();
746 if ( aParentURL.getSegmentCount() >= 1 )
747 aParentURL.removeFinalSlash();
749 // if the parent exists, we can continue with the creation of the
750 // new folder, we have to create the parent otherwise ( as long as
751 // bCreateParent is set to true )
752 if ( Content::create( aParentURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), maCmdEnv, comphelper::getProcessComponentContext(), aParent ) )
756 Sequence< Any > aValues{ Any(aFolderName), Any(true) };
757 OUString aType;
759 if ( bFsysFolder )
760 aType = TYPE_FSYS_FOLDER;
761 else
762 aType = TYPE_FOLDER;
764 aParent.insertNewContent( aType, { TITLE, IS_FOLDER }, aValues, rNewFolder );
765 bCreatedFolder = true;
767 catch( Exception const & )
769 TOOLS_WARN_EXCEPTION( "sfx.doc", "createFolder(): Could not create new folder" );
772 else if ( bCreateParent )
774 // if the parent doesn't exists and bCreateParent is set to true,
775 // we try to create the parent and if this was successful, we
776 // try to create the new folder again ( but this time, we set
777 // bCreateParent to false to avoid endless recursions )
778 if ( ( aParentURL.getSegmentCount() >= 1 ) &&
779 createFolder( aParentURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), bCreateParent, bFsysFolder, aParent ) )
781 bCreatedFolder = createFolder( rNewFolderURL, false, bFsysFolder, rNewFolder );
785 return bCreatedFolder;
789 bool SfxDocTplService::CreateNewUniqueFolderWithPrefix( std::u16string_view aPath,
790 const OUString& aPrefix,
791 OUString& aNewFolderName,
792 OUString& aNewFolderURL,
793 Content& aNewFolder )
795 bool bCreated = false;
796 INetURLObject aDirPath( aPath );
798 Content aParent;
799 uno::Reference< XCommandEnvironment > aQuietEnv;
800 if ( Content::create( aDirPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aQuietEnv, comphelper::getProcessComponentContext(), aParent ) )
802 for ( sal_Int32 nInd = 0; nInd < 32000; nInd++ )
804 OUString aTryName = aPrefix;
805 if ( nInd )
806 aTryName += OUString::number( nInd );
810 Sequence< Any > aValues{ Any(aTryName), Any(true) };
811 bCreated = aParent.insertNewContent( TYPE_FSYS_FOLDER, { TITLE, IS_FOLDER }, aValues, aNewFolder );
813 catch( ucb::NameClashException& )
815 // if there is already an element, retry
817 catch( Exception& )
819 INetURLObject aObjPath( aDirPath );
820 aObjPath.insertName( aTryName, false,
821 INetURLObject::LAST_SEGMENT,
822 INetURLObject::EncodeMechanism::All );
823 // if there is already an element, retry
824 // if there was another error, do not try any more
825 if ( !::utl::UCBContentHelper::Exists( aObjPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
826 break;
829 if ( bCreated )
831 aNewFolderName = aTryName;
832 aNewFolderURL = aNewFolder.get()->getIdentifier()->getContentIdentifier();
833 break;
838 return bCreated;
842 OUString SfxDocTplService::CreateNewUniqueFileWithPrefix( std::u16string_view aPath,
843 const OUString& aPrefix,
844 std::u16string_view aExt )
846 OUString aNewFileURL;
847 INetURLObject aDirPath( aPath );
849 Content aParent;
851 uno::Reference< XCommandEnvironment > aQuietEnv;
852 if ( Content::create( aDirPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aQuietEnv, comphelper::getProcessComponentContext(), aParent ) )
854 for ( sal_Int32 nInd = 0; nInd < 32000; nInd++ )
856 Content aNewFile;
857 bool bCreated = false;
858 OUString aTryName = aPrefix;
859 if ( nInd )
860 aTryName += OUString::number( nInd );
861 if ( aExt.empty() || aExt[0] != '.' )
862 aTryName += ".";
863 aTryName += aExt;
867 Sequence< Any > aValues{ Any(aTryName), Any(true) };
868 bCreated = aParent.insertNewContent( TYPE_FSYS_FILE, { TITLE, IS_DOCUMENT }, aValues, aNewFile );
870 catch( ucb::NameClashException& )
872 // if there is already an element, retry
874 catch( Exception& )
876 INetURLObject aObjPath( aPath );
877 aObjPath.insertName( aTryName, false,
878 INetURLObject::LAST_SEGMENT,
879 INetURLObject::EncodeMechanism::All );
880 // if there is already an element, retry
881 // if there was another error, do not try any more
882 if ( !::utl::UCBContentHelper::Exists( aObjPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
883 break;
886 if ( bCreated )
888 aNewFileURL = aNewFile.get()->getIdentifier()->getContentIdentifier();
889 break;
894 return aNewFileURL;
898 bool SfxDocTplService::removeContent( Content& rContent )
900 bool bRemoved = false;
903 Any aArg( true );
905 rContent.executeCommand( COMMAND_DELETE, aArg );
906 bRemoved = true;
908 catch ( RuntimeException& ) {}
909 catch ( Exception& ) {}
911 return bRemoved;
915 bool SfxDocTplService::removeContent( const OUString& rContentURL )
917 Content aContent;
919 if ( Content::create( rContentURL, maCmdEnv, comphelper::getProcessComponentContext(), aContent ) )
920 return removeContent( aContent );
921 return false;
925 bool SfxDocTplService::setProperty( Content& rContent,
926 const OUString& rPropName,
927 const Any& rPropValue )
929 bool bPropertySet = false;
931 // Store the property
934 Any aPropValue( rPropValue );
935 uno::Reference< XPropertySetInfo > aPropInfo = rContent.getProperties();
937 // check, whether or not the property exists, create it, when not
938 if ( !aPropInfo.is() || !aPropInfo->hasPropertyByName( rPropName ) )
940 uno::Reference< XPropertyContainer > xProperties( rContent.get(), UNO_QUERY );
941 if ( xProperties.is() )
945 xProperties->addProperty( rPropName, PropertyAttribute::MAYBEVOID, rPropValue );
947 catch( PropertyExistException& ) {}
948 catch( IllegalTypeException& ) {
949 TOOLS_WARN_EXCEPTION( "sfx.doc", "" );
951 catch( IllegalArgumentException& ) {
952 TOOLS_WARN_EXCEPTION( "sfx.doc", "" );
957 // To ensure a reloctable office installation, the path to the
958 // office installation directory must never be stored directly.
959 if ( SfxURLRelocator_Impl::propertyCanContainOfficeDir( rPropName ) )
961 OUString aValue;
962 if ( rPropValue >>= aValue )
964 maRelocator.makeRelocatableURL( aValue );
965 aPropValue <<= aValue;
967 else
969 Sequence< OUString > aValues;
970 if ( rPropValue >>= aValues )
972 for ( auto& rValue : asNonConstRange(aValues) )
974 maRelocator.makeRelocatableURL( rValue );
976 aPropValue <<= aValues;
978 else
980 OSL_FAIL( "Unsupported property value type" );
985 // now set the property
987 rContent.setPropertyValue( rPropName, aPropValue );
988 bPropertySet = true;
990 catch ( RuntimeException& ) {}
991 catch ( Exception& ) {}
993 return bPropertySet;
997 bool SfxDocTplService::getProperty(Content& rContent, const OUString& rPropName, Any& rPropValue)
999 bool bGotProperty = false;
1001 // Get the property
1004 uno::Reference< XPropertySetInfo > aPropInfo = rContent.getProperties();
1006 // check, whether or not the property exists
1007 if ( !aPropInfo.is() || !aPropInfo->hasPropertyByName( rPropName ) )
1009 return false;
1012 // now get the property
1014 rPropValue = rContent.getPropertyValue( rPropName );
1016 // To ensure a reloctable office installation, the path to the
1017 // office installation directory must never be stored directly.
1018 if ( SfxURLRelocator_Impl::propertyCanContainOfficeDir( rPropName ) )
1020 OUString aValue;
1021 if ( rPropValue >>= aValue )
1023 maRelocator.makeAbsoluteURL( aValue );
1024 rPropValue <<= aValue;
1026 else
1028 Sequence< OUString > aValues;
1029 if ( rPropValue >>= aValues )
1031 for ( auto& rValue : asNonConstRange(aValues) )
1033 maRelocator.makeAbsoluteURL( rValue );
1035 rPropValue <<= aValues;
1037 else
1039 OSL_FAIL( "Unsupported property value type" );
1044 bGotProperty = true;
1046 catch ( RuntimeException& ) {}
1047 catch ( Exception& ) {}
1049 return bGotProperty;
1052 SfxDocTplService::SfxDocTplService( const uno::Reference< XComponentContext > & xContext )
1053 : mxContext(xContext), mbIsInitialized(false), mbLocaleSet(false), maRelocator(xContext)
1058 SfxDocTplService::~SfxDocTplService()
1060 ::osl::MutexGuard aGuard( maMutex );
1061 maNames.clear();
1065 lang::Locale SfxDocTplService::getLocale()
1067 ::osl::MutexGuard aGuard( maMutex );
1069 if ( !mbLocaleSet )
1070 getDefaultLocale();
1072 return maLocale;
1076 void SfxDocTplService::setLocale( const lang::Locale &rLocale )
1078 ::osl::MutexGuard aGuard( maMutex );
1080 if ( mbLocaleSet && (
1081 ( maLocale.Language != rLocale.Language ) ||
1082 ( maLocale.Country != rLocale.Country ) ||
1083 ( maLocale.Variant != rLocale.Variant ) ) )
1084 mbIsInitialized = false;
1086 maLocale = rLocale;
1087 mbLocaleSet = true;
1091 void SfxDocTplService::update()
1093 if (!init())
1094 return;
1096 doUpdate();
1100 void SfxDocTplService::doUpdate()
1102 ::osl::MutexGuard aGuard( maMutex );
1104 const OUString aPropName( PROPERTY_NEEDSUPDATE );
1105 Any aValue;
1107 aValue <<= true;
1108 setProperty( maRootContent, aPropName, aValue );
1110 GroupList_Impl aGroupList;
1112 // get the entries from the hierarchy
1113 createFromContent( aGroupList, maRootContent, true, false );
1115 // get the entries from the template directories
1116 sal_Int32 nCountDir = maTemplateDirs.getLength();
1117 const OUString* pDirs = maTemplateDirs.getConstArray();
1118 Content aDirContent;
1120 // the last directory in the list must be writable
1121 bool bWriteableDirectory = true;
1123 // the target folder might not exist, for this reason no interaction handler should be used
1124 uno::Reference< XCommandEnvironment > aQuietEnv;
1126 while ( nCountDir )
1128 nCountDir--;
1129 if ( Content::create( pDirs[ nCountDir ], aQuietEnv, comphelper::getProcessComponentContext(), aDirContent ) )
1131 createFromContent( aGroupList, aDirContent, false, bWriteableDirectory );
1134 bWriteableDirectory = false;
1137 // now check the list
1138 for(std::unique_ptr<GroupData_Impl>& pGroup : aGroupList)
1140 if ( pGroup->getInUse() )
1142 if ( pGroup->getInHierarchy() )
1144 Content aGroup;
1145 if ( Content::create( pGroup->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
1146 setProperty( aGroup,
1147 TARGET_DIR_URL,
1148 Any( pGroup->getTargetURL() ) );
1150 size_t nCount = pGroup->count();
1151 for ( size_t i=0; i<nCount; i++ )
1153 DocTemplates_EntryData_Impl *pData = pGroup->getEntry( i );
1154 if ( ! pData->getInUse() )
1156 if ( pData->getInHierarchy() )
1157 removeFromHierarchy( pData ); // delete entry in hierarchy
1158 else
1159 addToHierarchy( pGroup.get(), pData ); // add entry to hierarchy
1161 else if ( pData->getUpdateType() ||
1162 pData->getUpdateLink() )
1164 updateData( pData );
1168 else
1170 addGroupToHierarchy( pGroup.get() ); // add group to hierarchy
1173 else
1174 removeFromHierarchy( pGroup.get() ); // delete group from hierarchy
1176 aGroupList.clear();
1178 aValue <<= false;
1179 setProperty( maRootContent, aPropName, aValue );
1183 std::vector< beans::StringPair > SfxDocTplService::ReadUINamesForTemplateDir_Impl( std::u16string_view aUserPath )
1185 INetURLObject aLocObj( aUserPath );
1186 aLocObj.insertName( u"groupuinames.xml", false,
1187 INetURLObject::LAST_SEGMENT,
1188 INetURLObject::EncodeMechanism::All );
1189 Content aLocContent;
1191 // TODO/LATER: Use hashmap in future
1192 std::vector< beans::StringPair > aUINames;
1193 if ( Content::create( aLocObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), uno::Reference < ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext(), aLocContent ) )
1197 uno::Reference< io::XInputStream > xLocStream = aLocContent.openStream();
1198 if ( xLocStream.is() )
1199 aUINames = DocTemplLocaleHelper::ReadGroupLocalizationSequence( xLocStream, mxContext );
1201 catch( uno::Exception& )
1205 return aUINames;
1209 bool SfxDocTplService::UpdateUINamesForTemplateDir_Impl( std::u16string_view aUserPath,
1210 const OUString& aGroupName,
1211 const OUString& aNewFolderName )
1213 std::vector< beans::StringPair > aUINames = ReadUINamesForTemplateDir_Impl( aUserPath );
1214 sal_Int32 nLen = aUINames.size();
1216 // it is possible that the name is used already, but it should be checked before
1217 for ( sal_Int32 nInd = 0; nInd < nLen; nInd++ )
1218 if ( aUINames[nInd].First == aNewFolderName )
1219 return false;
1221 aUINames.resize( ++nLen );
1222 aUINames[nLen-1].First = aNewFolderName;
1223 aUINames[nLen-1].Second = aGroupName;
1225 return WriteUINamesForTemplateDir_Impl( aUserPath, aUINames );
1229 bool SfxDocTplService::ReplaceUINamesForTemplateDir_Impl( std::u16string_view aUserPath,
1230 const OUString& aDefaultFsysGroupName,
1231 std::u16string_view aOldGroupName,
1232 const OUString& aNewGroupName )
1234 std::vector< beans::StringPair > aUINames = ReadUINamesForTemplateDir_Impl( aUserPath );
1235 sal_Int32 nLen = aUINames.size();
1237 bool bChanged = false;
1238 for ( sal_Int32 nInd = 0; nInd < nLen; nInd++ )
1239 if ( aUINames[nInd].Second == aOldGroupName )
1241 aUINames[nInd].Second = aNewGroupName;
1242 bChanged = true;
1245 if ( !bChanged )
1247 aUINames.resize( ++nLen );
1248 aUINames[nLen-1].First = aDefaultFsysGroupName;
1249 aUINames[nLen-1].Second = aNewGroupName;
1251 return WriteUINamesForTemplateDir_Impl( aUserPath, aUINames );
1255 void SfxDocTplService::RemoveUINamesForTemplateDir_Impl( std::u16string_view aUserPath,
1256 std::u16string_view aGroupName )
1258 std::vector< beans::StringPair > aUINames = ReadUINamesForTemplateDir_Impl( aUserPath );
1259 sal_Int32 nLen = aUINames.size();
1260 std::vector< beans::StringPair > aNewUINames( nLen );
1261 sal_Int32 nNewLen = 0;
1263 bool bChanged = false;
1264 for ( sal_Int32 nInd = 0; nInd < nLen; nInd++ )
1265 if ( aUINames[nInd].Second == aGroupName )
1266 bChanged = true;
1267 else
1269 nNewLen++;
1270 aNewUINames[nNewLen-1].First = aUINames[nInd].First;
1271 aNewUINames[nNewLen-1].Second = aUINames[nInd].Second;
1274 aNewUINames.resize( nNewLen );
1276 if (bChanged)
1277 WriteUINamesForTemplateDir_Impl( aUserPath, aNewUINames );
1281 bool SfxDocTplService::WriteUINamesForTemplateDir_Impl( std::u16string_view aUserPath,
1282 const std::vector< beans::StringPair >& aUINames )
1284 bool bResult = false;
1285 try {
1286 uno::Reference< io::XTempFile > xTempFile(
1287 io::TempFile::create(mxContext),
1288 uno::UNO_SET_THROW );
1290 uno::Reference< io::XOutputStream > xOutStream = xTempFile->getOutputStream();
1291 if ( !xOutStream.is() )
1292 throw uno::RuntimeException();
1294 DocTemplLocaleHelper::WriteGroupLocalizationSequence( xOutStream, aUINames, mxContext);
1295 try {
1296 // the SAX writer might close the stream
1297 xOutStream->closeOutput();
1298 } catch( uno::Exception& )
1301 Content aTargetContent( OUString(aUserPath), maCmdEnv, comphelper::getProcessComponentContext() );
1302 Content aSourceContent( xTempFile->getUri(), maCmdEnv, comphelper::getProcessComponentContext() );
1303 aTargetContent.transferContent( aSourceContent,
1304 InsertOperation::Copy,
1305 "groupuinames.xml",
1306 ucb::NameClash::OVERWRITE,
1307 "text/xml" );
1309 bResult = true;
1311 catch ( uno::Exception& )
1313 TOOLS_WARN_EXCEPTION("sfx.doc", "");
1316 return bResult;
1320 OUString SfxDocTplService::CreateNewGroupFsys( const OUString& rGroupName, Content& aGroup )
1322 OUString aResultURL;
1324 if ( maTemplateDirs.hasElements() )
1326 OUString aTargetPath = maTemplateDirs[ maTemplateDirs.getLength() - 1 ];
1328 // create a new folder with the given name
1329 Content aNewFolder;
1330 OUString aNewFolderName;
1332 // the Fsys name instead of GroupName should be used, the groupuinames must be added also
1333 if ( !CreateNewUniqueFolderWithPrefix( aTargetPath,
1334 rGroupName,
1335 aNewFolderName,
1336 aResultURL,
1337 aNewFolder )
1338 && !CreateNewUniqueFolderWithPrefix( aTargetPath,
1339 "UserGroup",
1340 aNewFolderName,
1341 aResultURL,
1342 aNewFolder ) )
1344 return OUString();
1346 if ( !UpdateUINamesForTemplateDir_Impl( aTargetPath, rGroupName, aNewFolderName ) )
1348 // we could not create the groupuinames for the folder, so we delete the group in
1349 // the folder and return
1350 removeContent( aNewFolder );
1351 return OUString();
1354 // Now set the target url for this group and we are done
1355 Any aValue( aResultURL );
1357 if ( ! setProperty( aGroup, TARGET_DIR_URL, aValue ) )
1359 removeContent( aNewFolder );
1360 return OUString();
1364 return aResultURL;
1368 sal_Bool SfxDocTplService::addGroup( const OUString& rGroupName )
1370 if (!init())
1371 return false;
1373 ::osl::MutexGuard aGuard( maMutex );
1375 // Check, whether or not there is a group with this name
1376 Content aNewGroup;
1377 OUString aNewGroupURL;
1378 INetURLObject aNewGroupObj( maRootURL );
1380 aNewGroupObj.insertName( rGroupName, false,
1381 INetURLObject::LAST_SEGMENT,
1382 INetURLObject::EncodeMechanism::All );
1384 aNewGroupURL = aNewGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1386 if ( Content::create( aNewGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aNewGroup ) ||
1387 ! createFolder( aNewGroupURL, false, false, aNewGroup ) )
1389 // if there already was a group with this name or the new group
1390 // could not be created, we return here
1391 return false;
1394 // Get the user template path entry ( new group will always
1395 // be added in the user template path )
1396 sal_Int32 nIndex;
1397 OUString aUserPath;
1399 nIndex = maTemplateDirs.getLength();
1400 if ( nIndex )
1401 nIndex--;
1402 else
1403 return false; // We don't know where to add the group
1405 aUserPath = maTemplateDirs[ nIndex ];
1407 // create a new folder with the given name
1408 Content aNewFolder;
1409 OUString aNewFolderName;
1410 OUString aNewFolderURL;
1412 // the Fsys name instead of GroupName should be used, the groupuinames must be added also
1413 if ( !CreateNewUniqueFolderWithPrefix( aUserPath,
1414 rGroupName,
1415 aNewFolderName,
1416 aNewFolderURL,
1417 aNewFolder )
1418 && !CreateNewUniqueFolderWithPrefix( aUserPath,
1419 "UserGroup",
1420 aNewFolderName,
1421 aNewFolderURL,
1422 aNewFolder ) )
1424 // we could not create the folder, so we delete the group in the
1425 // hierarchy and return
1426 removeContent( aNewGroup );
1427 return false;
1430 if ( !UpdateUINamesForTemplateDir_Impl( aUserPath, rGroupName, aNewFolderName ) )
1432 // we could not create the groupuinames for the folder, so we delete the group in the
1433 // hierarchy, the folder and return
1434 removeContent( aNewGroup );
1435 removeContent( aNewFolder );
1436 return false;
1439 // Now set the target url for this group and we are done
1440 Any aValue( aNewFolderURL );
1442 if ( ! setProperty( aNewGroup, TARGET_DIR_URL, aValue ) )
1444 removeContent( aNewGroup );
1445 removeContent( aNewFolder );
1446 return false;
1449 return true;
1453 sal_Bool SfxDocTplService::removeGroup( const OUString& rGroupName )
1455 // remove all the elements that have the prefix aTargetURL
1456 // if the group does not have other elements remove it
1458 if (!init())
1459 return false;
1461 ::osl::MutexGuard aGuard( maMutex );
1463 bool bResult = false;
1465 // create the group url
1466 INetURLObject aGroupObj( maRootURL );
1467 aGroupObj.insertName( rGroupName, false,
1468 INetURLObject::LAST_SEGMENT,
1469 INetURLObject::EncodeMechanism::All );
1471 // Get the target url
1472 Content aGroup;
1473 const OUString aGroupURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1475 if ( Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
1477 const OUString aPropName( TARGET_DIR_URL );
1478 Any aValue;
1480 OUString aGroupTargetURL;
1481 if ( getProperty( aGroup, aPropName, aValue ) )
1482 aValue >>= aGroupTargetURL;
1484 if ( aGroupTargetURL.isEmpty() )
1485 return false; // nothing is allowed to be removed
1487 if ( !maTemplateDirs.hasElements() )
1488 return false;
1490 // check that the fs location is in writable folder and this is not a "My templates" folder
1491 INetURLObject aGroupParentFolder( aGroupTargetURL );
1492 if (!aGroupParentFolder.removeSegment())
1493 return false;
1495 OUString aGeneralTempPath = findParentTemplateDir(
1496 aGroupParentFolder.GetMainURL(INetURLObject::DecodeMechanism::NONE));
1498 if (aGeneralTempPath.isEmpty())
1499 return false;
1501 // now get the content of the Group
1502 uno::Reference< XResultSet > xResultSet;
1503 Sequence< OUString > aProps { TARGET_URL };
1507 xResultSet = aGroup.createCursor( aProps, INCLUDE_DOCUMENTS_ONLY );
1509 if ( xResultSet.is() )
1511 bool bHasNonRemovable = false;
1512 bool bHasShared = false;
1514 uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY_THROW );
1515 uno::Reference< XRow > xRow( xResultSet, UNO_QUERY_THROW );
1517 while ( xResultSet->next() )
1519 OUString aTemplTargetURL( xRow->getString( 1 ) );
1520 OUString aHierURL = xContentAccess->queryContentIdentifierString();
1522 if ( ::utl::UCBContentHelper::IsSubPath( aGroupTargetURL, aTemplTargetURL ) )
1524 // this is a user template, and it can be removed
1525 if ( removeContent( aTemplTargetURL ) )
1526 removeContent( aHierURL );
1527 else
1528 bHasNonRemovable = true;
1530 else
1531 bHasShared = true;
1534 if ( !bHasNonRemovable && !bHasShared )
1536 if ( removeContent( aGroupTargetURL )
1537 || !::utl::UCBContentHelper::Exists( aGroupTargetURL ) )
1539 removeContent( aGroupURL );
1540 RemoveUINamesForTemplateDir_Impl( aGeneralTempPath, rGroupName );
1541 bResult = true; // the operation is successful only if the whole group is removed
1544 else if ( !bHasNonRemovable )
1546 if ( removeContent( aGroupTargetURL )
1547 || !::utl::UCBContentHelper::Exists( aGroupTargetURL ) )
1549 RemoveUINamesForTemplateDir_Impl( aGeneralTempPath, rGroupName );
1550 setProperty( aGroup, aPropName, uno::Any( OUString() ) );
1555 catch ( Exception& ) {}
1558 return bResult;
1562 sal_Bool SfxDocTplService::renameGroup( const OUString& rOldName,
1563 const OUString& rNewName )
1565 if ( rOldName == rNewName )
1566 return true;
1568 if (!init())
1569 return false;
1571 ::osl::MutexGuard aGuard( maMutex );
1573 // create the group url
1574 Content aGroup;
1575 INetURLObject aGroupObj( maRootURL );
1576 aGroupObj.insertName( rNewName, false,
1577 INetURLObject::LAST_SEGMENT,
1578 INetURLObject::EncodeMechanism::All );
1579 OUString aGroupURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1581 // Check, if there is a group with the new name, return false
1582 // if there is one.
1583 if ( Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
1584 return false;
1586 aGroupObj.removeSegment();
1587 aGroupObj.insertName( rOldName, false,
1588 INetURLObject::LAST_SEGMENT,
1589 INetURLObject::EncodeMechanism::All );
1590 aGroupURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1592 // When there is no group with the old name, we can't rename it
1593 if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
1594 return false;
1596 OUString aGroupTargetURL;
1597 // there is no need to check whether target dir url is in target path, since if the target path is changed
1598 // the target dir url should be already generated new
1599 Any aValue;
1600 if ( getProperty( aGroup, TARGET_DIR_URL, aValue ) )
1601 aValue >>= aGroupTargetURL;
1603 if ( aGroupTargetURL.isEmpty() )
1604 return false;
1606 if ( !maTemplateDirs.hasElements() )
1607 return false;
1609 // check that the fs location is in writable folder and this is not a "My templates" folder
1610 INetURLObject aGroupParentFolder( aGroupTargetURL );
1611 if (!aGroupParentFolder.removeSegment() ||
1612 isInternalTemplateDir(aGroupParentFolder.GetMainURL(INetURLObject::DecodeMechanism::NONE)))
1614 return false;
1617 // check that the group can be renamed ( all the contents must be in target location )
1618 bool bCanBeRenamed = false;
1621 uno::Reference< XResultSet > xResultSet;
1622 Sequence< OUString > aProps { TARGET_URL };
1623 xResultSet = aGroup.createCursor( aProps, INCLUDE_DOCUMENTS_ONLY );
1625 if ( xResultSet.is() )
1627 uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY_THROW );
1628 uno::Reference< XRow > xRow( xResultSet, UNO_QUERY_THROW );
1630 while ( xResultSet->next() )
1632 if ( !::utl::UCBContentHelper::IsSubPath( aGroupTargetURL, xRow->getString( 1 ) ) )
1633 throw uno::Exception("not sub path", nullptr);
1636 bCanBeRenamed = true;
1639 catch ( Exception& ) {}
1641 if ( bCanBeRenamed )
1643 INetURLObject aGroupTargetObj( aGroupTargetURL );
1644 const OUString aFsysName = aGroupTargetObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
1646 if ( aGroupTargetObj.removeSegment()
1647 && ReplaceUINamesForTemplateDir_Impl( aGroupTargetObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ),
1648 aFsysName,
1649 rOldName,
1650 rNewName ) )
1652 // rename the group in the hierarchy
1653 Any aTitleValue;
1654 aTitleValue <<= rNewName;
1656 return setProperty( aGroup, TITLE, aTitleValue );
1660 return false;
1664 sal_Bool SfxDocTplService::storeTemplate( const OUString& rGroupName,
1665 const OUString& rTemplateName,
1666 const uno::Reference< frame::XStorable >& rStorable )
1668 if (!init())
1669 return false;
1671 ::osl::MutexGuard aGuard( maMutex );
1673 // Check, whether or not there is a group with this name
1674 // Return false, if there is no group with the given name
1675 Content aGroup, aTemplateToRemove;
1676 INetURLObject aGroupObj( maRootURL );
1677 bool bRemoveOldTemplateContent = false;
1679 aGroupObj.insertName( rGroupName, false,
1680 INetURLObject::LAST_SEGMENT,
1681 INetURLObject::EncodeMechanism::All );
1682 const OUString aGroupURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
1684 if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
1685 return false;
1687 OUString aGroupTargetURL;
1688 Any aValue;
1689 if ( getProperty( aGroup, TARGET_DIR_URL, aValue ) )
1690 aValue >>= aGroupTargetURL;
1693 // Check, if there's a template with the given name in this group
1694 // the target template should be overwritten if it is imported by user
1695 // in case the template is installed by office installation of by an add-in
1696 // it can not be replaced
1697 aGroupObj.insertName( rTemplateName, false,
1698 INetURLObject::LAST_SEGMENT,
1699 INetURLObject::EncodeMechanism::All );
1700 const OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
1702 OUString aTemplateToRemoveTargetURL;
1704 if ( Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplateToRemove ) )
1706 bRemoveOldTemplateContent = true;
1707 if ( getProperty( aTemplateToRemove, TARGET_URL, aValue ) )
1708 aValue >>= aTemplateToRemoveTargetURL;
1710 if ( aGroupTargetURL.isEmpty() || !maTemplateDirs.hasElements()
1711 || (!aTemplateToRemoveTargetURL.isEmpty() && isInternalTemplateDir(aTemplateToRemoveTargetURL)) )
1712 return false; // it is not allowed to remove the template
1717 uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
1719 // get document service name
1720 uno::Reference< frame::XModuleManager2 > xModuleManager( frame::ModuleManager::create(xContext) );
1721 const OUString sDocServiceName {xModuleManager->identify( uno::Reference< uno::XInterface >( rStorable, uno::UNO_QUERY ) )};
1722 if ( sDocServiceName.isEmpty() )
1723 throw uno::RuntimeException();
1725 // get the actual filter name
1726 uno::Reference< lang::XMultiServiceFactory > xConfigProvider =
1727 configuration::theDefaultProvider::get( xContext );
1729 uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
1731 {"nodepath", uno::Any(OUString( "/org.openoffice.Setup/Office/Factories/" ))}
1732 }));
1733 uno::Reference< container::XNameAccess > xSOFConfig(
1734 xConfigProvider->createInstanceWithArguments(
1735 "com.sun.star.configuration.ConfigurationAccess",
1736 aArgs ),
1737 uno::UNO_QUERY_THROW );
1739 uno::Reference< container::XNameAccess > xApplConfig;
1740 xSOFConfig->getByName( sDocServiceName ) >>= xApplConfig;
1741 if ( !xApplConfig.is() )
1742 throw uno::RuntimeException();
1744 OUString aFilterName;
1745 xApplConfig->getByName("ooSetupFactoryActualTemplateFilter") >>= aFilterName;
1746 if ( aFilterName.isEmpty() )
1747 throw uno::RuntimeException();
1749 // find the related type name
1750 uno::Reference< container::XNameAccess > xFilterFactory(
1751 mxContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.FilterFactory", mxContext),
1752 uno::UNO_QUERY_THROW );
1754 uno::Sequence< beans::PropertyValue > aFilterData;
1755 xFilterFactory->getByName( aFilterName ) >>= aFilterData;
1756 OUString aTypeName;
1757 for ( const auto& rProp : std::as_const(aFilterData) )
1758 if ( rProp.Name == "Type" )
1759 rProp.Value >>= aTypeName;
1761 if ( aTypeName.isEmpty() )
1762 throw uno::RuntimeException();
1764 // find the mediatype and extension
1765 uno::Reference< container::XNameAccess > xTypeDetection =
1766 mxType.is() ?
1767 uno::Reference< container::XNameAccess >( mxType, uno::UNO_QUERY_THROW ) :
1768 uno::Reference< container::XNameAccess >(
1769 mxContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.TypeDetection", mxContext),
1770 uno::UNO_QUERY_THROW );
1772 SequenceAsHashMap aTypeProps( xTypeDetection->getByName( aTypeName ) );
1773 uno::Sequence< OUString > aAllExt =
1774 aTypeProps.getUnpackedValueOrDefault("Extensions", Sequence< OUString >() );
1775 if ( !aAllExt.hasElements() )
1776 throw uno::RuntimeException();
1778 const OUString aMediaType {aTypeProps.getUnpackedValueOrDefault("MediaType", OUString() )};
1779 const OUString aExt {aAllExt[0]};
1781 if ( aMediaType.isEmpty() || aExt.isEmpty() )
1782 throw uno::RuntimeException();
1784 // construct destination url
1785 if ( aGroupTargetURL.isEmpty() )
1787 aGroupTargetURL = CreateNewGroupFsys( rGroupName, aGroup );
1789 if ( aGroupTargetURL.isEmpty() )
1790 throw uno::RuntimeException();
1793 OUString aNewTemplateTargetURL = CreateNewUniqueFileWithPrefix( aGroupTargetURL, rTemplateName, aExt );
1794 if ( aNewTemplateTargetURL.isEmpty() )
1796 aNewTemplateTargetURL = CreateNewUniqueFileWithPrefix( aGroupTargetURL, "UserTemplate", aExt );
1798 if ( aNewTemplateTargetURL.isEmpty() )
1799 throw uno::RuntimeException();
1802 // store template
1803 uno::Sequence< PropertyValue > aStoreArgs{
1804 comphelper::makePropertyValue("FilterName", aFilterName),
1805 comphelper::makePropertyValue("DocumentTitle", rTemplateName)
1808 if( !::utl::UCBContentHelper::EqualURLs( aNewTemplateTargetURL, rStorable->getLocation() ))
1809 rStorable->storeToURL( aNewTemplateTargetURL, aStoreArgs );
1810 else
1811 rStorable->store();
1813 // the storing was successful, now the old template with the same name can be removed if it existed
1814 if ( !aTemplateToRemoveTargetURL.isEmpty() )
1816 removeContent( aTemplateToRemoveTargetURL );
1819 * pb: #i79496#
1820 * if the old template was the standard template
1821 * it is necessary to change the standard template with the new file name
1823 const OUString sStdTmplFile = SfxObjectFactory::GetStandardTemplate( sDocServiceName );
1824 if ( INetURLObject( sStdTmplFile ) == INetURLObject( aTemplateToRemoveTargetURL ) )
1826 SfxObjectFactory::SetStandardTemplate( sDocServiceName, aNewTemplateTargetURL );
1830 if ( bRemoveOldTemplateContent )
1831 removeContent( aTemplateToRemove );
1833 // add the template to hierarchy
1834 return addEntry( aGroup, rTemplateName, aNewTemplateTargetURL, aMediaType );
1836 catch( Exception& )
1838 // the template was not stored
1839 return false;
1844 sal_Bool SfxDocTplService::addTemplate( const OUString& rGroupName,
1845 const OUString& rTemplateName,
1846 const OUString& rSourceURL )
1848 if (!init())
1849 return false;
1851 ::osl::MutexGuard aGuard( maMutex );
1853 // Check, whether or not there is a group with this name
1854 // Return false, if there is no group with the given name
1855 Content aGroup, aTemplate, aTargetGroup;
1856 INetURLObject aGroupObj( maRootURL );
1858 aGroupObj.insertName( rGroupName, false,
1859 INetURLObject::LAST_SEGMENT,
1860 INetURLObject::EncodeMechanism::All );
1861 const OUString aGroupURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1863 if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
1864 return false;
1866 // Check, if there's a template with the given name in this group
1867 // Return false, if there already is a template
1868 aGroupObj.insertName( rTemplateName, false,
1869 INetURLObject::LAST_SEGMENT,
1870 INetURLObject::EncodeMechanism::All );
1871 const OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
1873 if ( Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) )
1874 return false;
1876 // get the target url of the group
1877 OUString aTargetURL;
1878 Any aValue;
1880 if ( getProperty( aGroup, TARGET_DIR_URL, aValue ) )
1881 aValue >>= aTargetURL;
1883 if ( aTargetURL.isEmpty() )
1885 aTargetURL = CreateNewGroupFsys( rGroupName, aGroup );
1887 if ( aTargetURL.isEmpty() )
1888 return false;
1891 // Get the content type
1892 OUString aTitle, aType;
1894 bool bDocHasTitle = false;
1895 getTitleFromURL( rSourceURL, aTitle, aType, bDocHasTitle );
1897 INetURLObject aSourceObj( rSourceURL );
1898 if ( rTemplateName == aTitle )
1900 // addTemplate will sometimes be called just to add an entry in the
1901 // hierarchy; the target URL and the source URL will be the same in
1902 // this scenario
1903 // TODO/LATER: get rid of this old hack
1905 INetURLObject aTargetObj( aTargetURL );
1907 aTargetObj.insertName( rTemplateName, false,
1908 INetURLObject::LAST_SEGMENT,
1909 INetURLObject::EncodeMechanism::All );
1910 aTargetObj.setExtension( aSourceObj.getExtension() );
1912 const OUString aTargetURL2 = aTargetObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
1914 if ( aTargetURL2 == rSourceURL )
1915 return addEntry( aGroup, rTemplateName, aTargetURL2, aType );
1918 // copy the template into the new group (targeturl)
1920 INetURLObject aTmpURL( aSourceObj );
1921 aTmpURL.CutExtension();
1922 const OUString aPattern {aTmpURL.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset )};
1924 const OUString aNewTemplateTargetURL {CreateNewUniqueFileWithPrefix( aTargetURL, aPattern, aSourceObj.getExtension() )};
1925 INetURLObject aNewTemplateTargetObj( aNewTemplateTargetURL );
1926 const OUString aNewTemplateTargetName {aNewTemplateTargetObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset )};
1927 if ( aNewTemplateTargetURL.isEmpty() || aNewTemplateTargetName.isEmpty() )
1928 return false;
1930 // get access to source file
1931 Content aSourceContent;
1932 uno::Reference < ucb::XCommandEnvironment > xEnv;
1933 INetURLObject aSourceURL( rSourceURL );
1934 if( ! Content::create( aSourceURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aSourceContent ) )
1935 return false;
1937 if( ! Content::create( aTargetURL, xEnv, comphelper::getProcessComponentContext(), aTargetGroup ) )
1938 return false;
1940 // transfer source file
1943 aTargetGroup.transferContent( aSourceContent,
1944 InsertOperation::Copy,
1945 aNewTemplateTargetName,
1946 NameClash::OVERWRITE,
1947 aType );
1949 // allow to edit the added template
1950 Content aResultContent;
1951 if ( Content::create( aNewTemplateTargetURL, xEnv, comphelper::getProcessComponentContext(), aResultContent ) )
1953 static constexpr OUString aPropertyName( u"IsReadOnly"_ustr );
1954 uno::Any aProperty;
1955 bool bReadOnly = false;
1956 if ( getProperty( aResultContent, aPropertyName, aProperty ) && ( aProperty >>= bReadOnly ) && bReadOnly )
1957 setProperty( aResultContent, aPropertyName, uno::Any( false ) );
1960 catch ( ContentCreationException& )
1961 { return false; }
1962 catch ( Exception& )
1963 { return false; }
1966 // either the document has title and it is the same as requested, or we have to set it
1967 bool bCorrectTitle = ( bDocHasTitle && aTitle == rTemplateName );
1968 if ( !bCorrectTitle )
1970 if ( !bDocHasTitle )
1972 INetURLObject aNewTmpObj( aNewTemplateTargetObj );
1973 aNewTmpObj.CutExtension();
1974 bCorrectTitle = ( aNewTmpObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ) == rTemplateName );
1977 if ( !bCorrectTitle )
1978 bCorrectTitle = setTitleForURL( aNewTemplateTargetURL, rTemplateName );
1981 if ( bCorrectTitle )
1983 // create a new entry in the hierarchy
1984 return addEntry( aGroup, rTemplateName, aNewTemplateTargetURL, aType );
1987 // TODO/LATER: The user could be notified here that the renaming has failed
1988 // create a new entry in the hierarchy
1989 addEntry( aGroup, aTitle, aNewTemplateTargetURL, aType );
1990 return false;
1993 bool SfxDocTplService::isInternalTemplateDir(const OUString& rURL) const
1995 return std::any_of(maInternalTemplateDirs.begin(), maInternalTemplateDirs.end(),
1996 [&rURL](const OUString& rDir) { return ::utl::UCBContentHelper::IsSubPath(rDir, rURL); });
1999 OUString SfxDocTplService::findParentTemplateDir(const OUString& rURL) const
2001 const OUString* pDirs = std::find_if(maTemplateDirs.begin(), maTemplateDirs.end(),
2002 [&rURL](const OUString& rDir) { return ::utl::UCBContentHelper::IsSubPath(rDir, rURL); });
2003 if (pDirs != maTemplateDirs.end())
2004 return *pDirs;
2005 return OUString();
2008 sal_Bool SfxDocTplService::removeTemplate( const OUString& rGroupName,
2009 const OUString& rTemplateName )
2011 if (!init())
2012 return false;
2013 ::osl::MutexGuard aGuard( maMutex );
2015 // Check, whether or not there is a group with this name
2016 // Return false, if there is no group with the given name
2017 Content aGroup, aTemplate;
2018 INetURLObject aGroupObj( maRootURL );
2020 aGroupObj.insertName( rGroupName, false,
2021 INetURLObject::LAST_SEGMENT,
2022 INetURLObject::EncodeMechanism::All );
2023 const OUString aGroupURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
2025 if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
2026 return false;
2028 // Check, if there's a template with the given name in this group
2029 // Return false, if there is no template
2030 aGroupObj.insertName( rTemplateName, false,
2031 INetURLObject::LAST_SEGMENT,
2032 INetURLObject::EncodeMechanism::All );
2033 const OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
2035 if ( !Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) )
2036 return false;
2038 // get the target URL from the template
2039 OUString aTargetURL;
2040 Any aValue;
2042 if ( getProperty( aTemplate, TARGET_URL, aValue ) )
2043 aValue >>= aTargetURL;
2045 // delete the target template
2046 if ( !aTargetURL.isEmpty() )
2048 if (isInternalTemplateDir(aTargetURL))
2049 return false;
2051 removeContent( aTargetURL );
2054 // delete the template entry
2055 return removeContent( aTemplate );
2059 sal_Bool SfxDocTplService::renameTemplate( const OUString& rGroupName,
2060 const OUString& rOldName,
2061 const OUString& rNewName )
2063 if ( rOldName == rNewName )
2064 return true;
2065 if (!init())
2066 return false;
2068 ::osl::MutexGuard aGuard( maMutex );
2070 // Check, whether or not there is a group with this name
2071 // Return false, if there is no group with the given name
2072 Content aGroup, aTemplate;
2073 INetURLObject aGroupObj( maRootURL );
2075 aGroupObj.insertName( rGroupName, false,
2076 INetURLObject::LAST_SEGMENT,
2077 INetURLObject::EncodeMechanism::All );
2078 const OUString aGroupURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
2080 if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
2081 return false;
2083 // Check, if there's a template with the new name in this group
2084 // Return false, if there is one
2085 aGroupObj.insertName( rNewName, false,
2086 INetURLObject::LAST_SEGMENT,
2087 INetURLObject::EncodeMechanism::All );
2088 OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
2090 if ( Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) )
2091 return false;
2093 // Check, if there's a template with the old name in this group
2094 // Return false, if there is no template
2095 aGroupObj.removeSegment();
2096 aGroupObj.insertName( rOldName, false,
2097 INetURLObject::LAST_SEGMENT,
2098 INetURLObject::EncodeMechanism::All );
2099 aTemplateURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
2101 if ( !Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) )
2102 return false;
2104 OUString aTemplateTargetURL;
2105 Any aTargetValue;
2107 if ( getProperty( aTemplate, TARGET_URL, aTargetValue ) )
2108 aTargetValue >>= aTemplateTargetURL;
2110 if ( !setTitleForURL( aTemplateTargetURL, rNewName ) )
2111 return false;
2113 // rename the template entry in the cache
2114 Any aTitleValue;
2115 aTitleValue <<= rNewName;
2117 return setProperty( aTemplate, TITLE, aTitleValue );
2122 //--- XDocumentTemplates ---
2124 uno::Reference< ucb::XContent > SAL_CALL SfxDocTplService::getContent()
2126 if ( init() )
2127 return maRootContent.get();
2128 return nullptr;
2132 WaitWindow_Impl::WaitWindow_Impl() : WorkWindow(nullptr, WB_BORDER | WB_3DLOOK)
2134 tools::Rectangle aRect(0, 0, 300, 30000);
2135 maText = SfxResId(RID_CNT_STR_WAITING);
2136 maRect = GetTextRect(aRect, maText, gnTextStyle);
2137 aRect = maRect;
2138 aRect.AdjustRight(2 * X_OFFSET );
2139 aRect.AdjustBottom(2 * Y_OFFSET );
2140 maRect.SetPos(Point(X_OFFSET, Y_OFFSET));
2141 SetOutputSizePixel(aRect.GetSize());
2143 Show();
2144 PaintImmediately();
2145 GetOutDev()->Flush();
2149 WaitWindow_Impl::~WaitWindow_Impl()
2151 disposeOnce();
2154 void WaitWindow_Impl::dispose()
2156 Hide();
2157 WorkWindow::dispose();
2161 void WaitWindow_Impl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
2163 rRenderContext.DrawText(maRect, maText, gnTextStyle);
2166 void SfxDocTplService::addHierGroup( GroupList_Impl& rList,
2167 const OUString& rTitle,
2168 const OUString& rOwnURL )
2170 // now get the content of the Group
2171 Content aContent;
2172 uno::Reference<XResultSet> xResultSet;
2176 aContent = Content(rOwnURL, maCmdEnv, comphelper::getProcessComponentContext());
2177 xResultSet = aContent.createCursor( { TITLE, TARGET_URL, PROPERTY_TYPE }, INCLUDE_DOCUMENTS_ONLY );
2179 catch (ContentCreationException&)
2181 TOOLS_WARN_EXCEPTION( "sfx.doc", "" );
2183 catch (Exception&) {}
2185 if ( !xResultSet.is() )
2186 return;
2188 GroupData_Impl *pGroup = new GroupData_Impl( rTitle );
2189 pGroup->setHierarchy( true );
2190 pGroup->setHierarchyURL( rOwnURL );
2191 rList.push_back( std::unique_ptr<GroupData_Impl>(pGroup) );
2193 uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY );
2194 uno::Reference< XRow > xRow( xResultSet, UNO_QUERY );
2198 while ( xResultSet->next() )
2200 bool bUpdateType = false;
2201 DocTemplates_EntryData_Impl *pData;
2203 const OUString aTitle( xRow->getString( 1 ) );
2204 const OUString aTargetDir( xRow->getString( 2 ) );
2205 OUString aType( xRow->getString( 3 ) );
2206 const OUString aHierURL {xContentAccess->queryContentIdentifierString()};
2208 if ( aType.isEmpty() )
2210 OUString aTmpTitle;
2212 bool bDocHasTitle = false;
2213 getTitleFromURL( aTargetDir, aTmpTitle, aType, bDocHasTitle );
2215 if ( !aType.isEmpty() )
2216 bUpdateType = true;
2219 pData = pGroup->addEntry( aTitle, aTargetDir, aType, aHierURL );
2220 pData->setUpdateType( bUpdateType );
2223 catch ( Exception& ) {}
2227 void SfxDocTplService::addFsysGroup( GroupList_Impl& rList,
2228 const OUString& rTitle,
2229 const OUString& rUITitle,
2230 const OUString& rOwnURL,
2231 bool bWriteableGroup )
2233 OUString aTitle;
2235 if ( rUITitle.isEmpty() )
2237 // reserved FS names that should not be used
2238 if ( rTitle == "wizard" )
2239 return;
2240 else if ( rTitle == "internal" )
2241 return;
2243 aTitle = getLongName( rTitle );
2245 else
2246 aTitle = rUITitle;
2248 if ( aTitle.isEmpty() )
2249 return;
2251 GroupData_Impl* pGroup = nullptr;
2252 for (const std::unique_ptr<GroupData_Impl>& i : rList)
2254 if ( i->getTitle() == aTitle )
2256 pGroup = i.get();
2257 break;
2261 if ( !pGroup )
2263 pGroup = new GroupData_Impl( aTitle );
2264 rList.push_back( std::unique_ptr<GroupData_Impl>(pGroup) );
2267 if ( bWriteableGroup )
2268 pGroup->setTargetURL( rOwnURL );
2270 pGroup->setInUse();
2272 // now get the content of the Group
2273 Content aContent;
2274 uno::Reference< XResultSet > xResultSet;
2275 Sequence< OUString > aProps { TITLE };
2279 // this method is only used during checking of the available template-folders
2280 // that should happen quietly
2281 uno::Reference< XCommandEnvironment > aQuietEnv;
2282 aContent = Content( rOwnURL, aQuietEnv, comphelper::getProcessComponentContext() );
2283 xResultSet = aContent.createCursor( aProps, INCLUDE_DOCUMENTS_ONLY );
2285 catch ( Exception& ) {}
2287 if ( !xResultSet.is() )
2288 return;
2290 uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY );
2291 uno::Reference< XRow > xRow( xResultSet, UNO_QUERY );
2295 while ( xResultSet->next() )
2297 OUString aChildTitle( xRow->getString( 1 ) );
2298 const OUString aTargetURL {xContentAccess->queryContentIdentifierString()};
2299 OUString aType;
2301 if ( aChildTitle == "sfx.tlx" || aChildTitle == "groupuinames.xml" )
2302 continue;
2304 bool bDocHasTitle = false;
2305 getTitleFromURL( aTargetURL, aChildTitle, aType, bDocHasTitle );
2307 pGroup->addEntry( aChildTitle, aTargetURL, aType, OUString() );
2310 catch ( Exception& ) {}
2314 void SfxDocTplService::createFromContent( GroupList_Impl& rList,
2315 Content &rContent,
2316 bool bHierarchy,
2317 bool bWriteableContent )
2319 const OUString aTargetURL {rContent.get()->getIdentifier()->getContentIdentifier()};
2321 // when scanning the file system, we have to add the 'standard' group, too
2322 if ( ! bHierarchy )
2324 const OUString aUIStdTitle {getLongName( STANDARD_FOLDER )};
2325 addFsysGroup( rList, OUString(), aUIStdTitle, aTargetURL, bWriteableContent );
2328 // search for predefined UI names
2329 INetURLObject aLayerObj( aTargetURL );
2331 // TODO/LATER: Use hashmap in future
2332 std::vector< beans::StringPair > aUINames;
2333 if ( !bHierarchy )
2334 aUINames = ReadUINamesForTemplateDir_Impl( aLayerObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
2336 uno::Reference< XResultSet > xResultSet;
2337 Sequence< OUString > aProps { TITLE };
2341 xResultSet = rContent.createCursor( aProps, INCLUDE_FOLDERS_ONLY );
2343 catch ( Exception& ) {}
2345 if ( !xResultSet.is() )
2346 return;
2348 uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY );
2349 uno::Reference< XRow > xRow( xResultSet, UNO_QUERY );
2353 while ( xResultSet->next() )
2355 // TODO/LATER: clarify the encoding of the Title
2356 const OUString aTitle( xRow->getString( 1 ) );
2357 const OUString aTargetSubfolderURL( xContentAccess->queryContentIdentifierString() );
2359 if ( bHierarchy )
2360 addHierGroup( rList, aTitle, aTargetSubfolderURL );
2361 else
2363 OUString aUITitle;
2364 for (const beans::StringPair & rUIName : aUINames)
2365 if ( rUIName.First == aTitle )
2367 aUITitle = rUIName.Second;
2368 break;
2371 addFsysGroup( rList, aTitle, aUITitle, aTargetSubfolderURL, bWriteableContent );
2375 catch ( Exception& ) {}
2379 void SfxDocTplService::removeFromHierarchy( DocTemplates_EntryData_Impl const *pData )
2381 Content aTemplate;
2383 if ( Content::create( pData->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) )
2385 removeContent( aTemplate );
2390 void SfxDocTplService::addToHierarchy( GroupData_Impl const *pGroup,
2391 DocTemplates_EntryData_Impl const *pData )
2393 Content aGroup, aTemplate;
2395 if ( ! Content::create( pGroup->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
2396 return;
2398 // Check, if there's a template with the given name in this group
2399 // Return if there is already a template
2400 INetURLObject aGroupObj( pGroup->getHierarchyURL() );
2402 aGroupObj.insertName( pData->getTitle(), false,
2403 INetURLObject::LAST_SEGMENT,
2404 INetURLObject::EncodeMechanism::All );
2406 const OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
2408 if ( Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) )
2409 return;
2411 addEntry( aGroup, pData->getTitle(),
2412 pData->getTargetURL(),
2413 pData->getType() );
2417 void SfxDocTplService::updateData( DocTemplates_EntryData_Impl const *pData )
2419 Content aTemplate;
2421 if ( ! Content::create( pData->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) )
2422 return;
2424 if ( pData->getUpdateType() )
2426 setProperty( aTemplate, PROPERTY_TYPE, Any( pData->getType() ) );
2429 if ( pData->getUpdateLink() )
2431 setProperty( aTemplate, TARGET_URL, Any( pData->getTargetURL() ) );
2436 void SfxDocTplService::addGroupToHierarchy( GroupData_Impl *pGroup )
2438 Content aGroup;
2440 INetURLObject aNewGroupObj( maRootURL );
2441 aNewGroupObj.insertName( pGroup->getTitle(), false,
2442 INetURLObject::LAST_SEGMENT,
2443 INetURLObject::EncodeMechanism::All );
2445 const OUString aNewGroupURL {aNewGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
2447 if ( createFolder( aNewGroupURL, false, false, aGroup ) )
2449 setProperty( aGroup, TARGET_DIR_URL, Any( pGroup->getTargetURL() ) );
2450 pGroup->setHierarchyURL( aNewGroupURL );
2452 size_t nCount = pGroup->count();
2453 for ( size_t i = 0; i < nCount; i++ )
2455 DocTemplates_EntryData_Impl *pData = pGroup->getEntry( i );
2456 addToHierarchy( pGroup, pData ); // add entry to hierarchy
2462 void SfxDocTplService::removeFromHierarchy( GroupData_Impl const *pGroup )
2464 Content aGroup;
2466 if ( Content::create( pGroup->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
2468 removeContent( aGroup );
2473 GroupData_Impl::GroupData_Impl( OUString aTitle )
2474 : maTitle(std::move(aTitle)), mbInUse(false), mbInHierarchy(false)
2479 DocTemplates_EntryData_Impl* GroupData_Impl::addEntry( const OUString& rTitle,
2480 const OUString& rTargetURL,
2481 const OUString& rType,
2482 const OUString& rHierURL )
2484 DocTemplates_EntryData_Impl* pData = nullptr;
2485 bool EntryFound = false;
2487 for (auto const & p : maEntries)
2489 pData = p.get();
2490 if ( pData->getTitle() == rTitle )
2492 EntryFound = true;
2493 break;
2497 if ( !EntryFound )
2499 pData = new DocTemplates_EntryData_Impl( rTitle );
2500 pData->setTargetURL( rTargetURL );
2501 pData->setType( rType );
2502 if ( !rHierURL.isEmpty() )
2504 pData->setHierarchyURL( rHierURL );
2505 pData->setHierarchy( true );
2507 maEntries.emplace_back( pData );
2509 else
2511 if ( !rHierURL.isEmpty() )
2513 pData->setHierarchyURL( rHierURL );
2514 pData->setHierarchy( true );
2517 if ( pData->getInHierarchy() )
2518 pData->setInUse();
2520 if ( rTargetURL != pData->getTargetURL() )
2522 pData->setTargetURL( rTargetURL );
2523 pData->setUpdateLink( true );
2527 return pData;
2531 DocTemplates_EntryData_Impl::DocTemplates_EntryData_Impl( OUString aTitle )
2532 : maTitle(std::move(aTitle)), mbInHierarchy(false), mbInUse(false), mbUpdateType(false), mbUpdateLink(false)
2538 // static
2539 bool SfxURLRelocator_Impl::propertyCanContainOfficeDir(
2540 std::u16string_view rPropName )
2542 // Note: TargetURL is handled by UCB itself (because it is a property
2543 // with a predefined semantic). Additional Core properties introduced
2544 // be a client app must be handled by the client app itself, because
2545 // the UCB does not know the semantics of those properties.
2546 return ( rPropName == TARGET_DIR_URL || rPropName == PROPERTY_DIRLIST );
2550 SfxURLRelocator_Impl::SfxURLRelocator_Impl( uno::Reference< XComponentContext > xContext )
2551 : mxContext(std::move( xContext ))
2556 SfxURLRelocator_Impl::~SfxURLRelocator_Impl()
2561 void SfxURLRelocator_Impl::initOfficeInstDirs()
2563 if ( !mxOfficeInstDirs.is() )
2565 std::scoped_lock aGuard( maMutex );
2566 if ( !mxOfficeInstDirs.is() )
2568 OSL_ENSURE( mxContext.is(), "No service manager!" );
2570 mxOfficeInstDirs = theOfficeInstallationDirectories::get(mxContext);
2576 void SfxURLRelocator_Impl::implExpandURL( OUString& io_url )
2578 const INetURLObject aParser( io_url );
2579 if ( aParser.GetProtocol() != INetProtocol::VndSunStarExpand )
2580 return;
2582 io_url = aParser.GetURLPath( INetURLObject::DecodeMechanism::WithCharset );
2585 if ( !mxMacroExpander.is() )
2587 mxMacroExpander.set( theMacroExpander::get(mxContext), UNO_SET_THROW );
2589 io_url = mxMacroExpander->expandMacros( io_url );
2591 catch( const Exception& )
2593 DBG_UNHANDLED_EXCEPTION("sfx.doc");
2598 void SfxURLRelocator_Impl::makeRelocatableURL( OUString & rURL )
2600 if ( !rURL.isEmpty() )
2602 initOfficeInstDirs();
2603 implExpandURL( rURL );
2604 rURL = mxOfficeInstDirs->makeRelocatableURL( rURL );
2609 void SfxURLRelocator_Impl::makeAbsoluteURL( OUString & rURL )
2611 if ( !rURL.isEmpty() )
2613 initOfficeInstDirs();
2614 implExpandURL( rURL );
2615 rURL = mxOfficeInstDirs->makeAbsoluteURL( rURL );
2619 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
2620 com_sun_star_comp_sfx2_DocumentTemplates_get_implementation(
2621 css::uno::XComponentContext *context,
2622 css::uno::Sequence<css::uno::Any> const &)
2624 return cppu::acquire(new SfxDocTplService(context));
2627 OUString DocTemplLocaleHelper::GetStandardGroupString()
2629 return SfxResId(TEMPLATE_LONG_NAMES_ARY[0]);
2632 std::vector<OUString> DocTemplLocaleHelper::GetBuiltInGroupNames()
2634 std::vector<OUString> aGroups;
2635 for(auto const & aGroupName : TEMPLATE_LONG_NAMES_ARY)
2636 aGroups.push_back(SfxResId(aGroupName));
2637 return aGroups;
2640 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */