summaryrefslogtreecommitdiffstats
path: root/sfx2/source/doc
diff options
context:
space:
mode:
Diffstat (limited to 'sfx2/source/doc')
-rw-r--r--sfx2/source/doc/DocumentMetadataAccess.cxx1374
-rw-r--r--sfx2/source/doc/DocumentSigner.cxx121
-rw-r--r--sfx2/source/doc/Metadatable.cxx1603
-rw-r--r--sfx2/source/doc/QuerySaveDocument.cxx39
-rw-r--r--sfx2/source/doc/SfxDocumentMetaData.cxx2347
-rw-r--r--sfx2/source/doc/SfxRedactionHelper.cxx560
-rw-r--r--sfx2/source/doc/autoredactdialog.cxx766
-rw-r--r--sfx2/source/doc/docfac.cxx355
-rw-r--r--sfx2/source/doc/docfile.cxx4976
-rw-r--r--sfx2/source/doc/docfilt.cxx200
-rw-r--r--sfx2/source/doc/docinf.cxx324
-rw-r--r--sfx2/source/doc/docinsert.cxx293
-rw-r--r--sfx2/source/doc/docmacromode.cxx469
-rw-r--r--sfx2/source/doc/docstoragemodifylistener.cxx73
-rw-r--r--sfx2/source/doc/doctempl.cxx1751
-rw-r--r--sfx2/source/doc/doctemplates.cxx2640
-rw-r--r--sfx2/source/doc/doctemplateslocal.cxx211
-rw-r--r--sfx2/source/doc/doctemplateslocal.hxx78
-rw-r--r--sfx2/source/doc/docundomanager.cxx422
-rw-r--r--sfx2/source/doc/exoticfileloadexception.cxx32
-rw-r--r--sfx2/source/doc/exoticfileloadexception.hxx45
-rw-r--r--sfx2/source/doc/frmdescr.cxx57
-rw-r--r--sfx2/source/doc/graphhelp.cxx259
-rw-r--r--sfx2/source/doc/graphhelp.hxx71
-rw-r--r--sfx2/source/doc/guisaveas.cxx2015
-rw-r--r--sfx2/source/doc/iframe.cxx454
-rw-r--r--sfx2/source/doc/new.cxx350
-rw-r--r--sfx2/source/doc/objcont.cxx727
-rw-r--r--sfx2/source/doc/objembed.cxx224
-rw-r--r--sfx2/source/doc/objitem.cxx97
-rw-r--r--sfx2/source/doc/objmisc.cxx2037
-rw-r--r--sfx2/source/doc/objserv.cxx2285
-rw-r--r--sfx2/source/doc/objstor.cxx3968
-rw-r--r--sfx2/source/doc/objstor.hxx29
-rw-r--r--sfx2/source/doc/objxtor.cxx1128
-rw-r--r--sfx2/source/doc/oleprops.cxx1241
-rw-r--r--sfx2/source/doc/oleprops.hxx391
-rw-r--r--sfx2/source/doc/ownsubfilterservice.cxx115
-rw-r--r--sfx2/source/doc/printhelper.cxx798
-rw-r--r--sfx2/source/doc/printhelper.hxx68
-rw-r--r--sfx2/source/doc/saveastemplatedlg.cxx179
-rw-r--r--sfx2/source/doc/sfxbasemodel.cxx4618
-rw-r--r--sfx2/source/doc/sfxmodelfactory.cxx110
-rw-r--r--sfx2/source/doc/signaturestate.cxx59
-rw-r--r--sfx2/source/doc/syspath.cxx37
-rw-r--r--sfx2/source/doc/syspath.hxx32
-rw-r--r--sfx2/source/doc/syspathw32.cxx69
-rw-r--r--sfx2/source/doc/syspathw32.hxx33
-rw-r--r--sfx2/source/doc/templatedlg.cxx1394
-rw-r--r--sfx2/source/doc/watermarkitem.cxx82
-rw-r--r--sfx2/source/doc/zoomitem.cxx172
51 files changed, 41778 insertions, 0 deletions
diff --git a/sfx2/source/doc/DocumentMetadataAccess.cxx b/sfx2/source/doc/DocumentMetadataAccess.cxx
new file mode 100644
index 0000000000..51dd84baff
--- /dev/null
+++ b/sfx2/source/doc/DocumentMetadataAccess.cxx
@@ -0,0 +1,1374 @@
+/* -*- 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 <sfx2/DocumentMetadataAccess.hxx>
+
+#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/embed/ElementModes.hpp>
+#include <com/sun/star/embed/XStorage.hpp>
+#include <com/sun/star/embed/XTransactedObject.hpp>
+#include <com/sun/star/frame/XTransientDocumentsDocumentContentIdentifierFactory.hpp>
+#include <com/sun/star/task/ErrorCodeIOException.hpp>
+#include <com/sun/star/ucb/InteractiveAugmentedIOException.hpp>
+#include <com/sun/star/rdf/FileFormat.hpp>
+#include <com/sun/star/rdf/ParseException.hpp>
+#include <com/sun/star/rdf/RepositoryException.hpp>
+#include <com/sun/star/rdf/URIs.hpp>
+#include <com/sun/star/rdf/Statement.hpp>
+#include <com/sun/star/rdf/URI.hpp>
+#include <com/sun/star/rdf/Repository.hpp>
+
+#include <rtl/ustrbuf.hxx>
+#include <rtl/uri.hxx>
+#include <rtl/bootstrap.hxx>
+#include <sal/log.hxx>
+
+#include <comphelper/interaction.hxx>
+#include <unotools/mediadescriptor.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/storagehelper.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <sfx2/docfile.hxx>
+#include <sfx2/XmlIdRegistry.hxx>
+#include <sfx2/objsh.hxx>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <libxml/tree.h>
+
+#include <utility>
+#include <vector>
+#include <set>
+#include <string_view>
+
+#include <com/sun/star/uri/XUriReference.hpp>
+#include <com/sun/star/uri/UriReferenceFactory.hpp>
+
+
+/*
+ Note: in the context of this implementation, all rdf.QueryExceptions and
+ rdf.RepositoryExceptions are RuntimeExceptions, and will be reported as such.
+
+ This implementation assumes that it is only used with ODF documents, not mere
+ ODF packages. In other words, we enforce that metadata files must not be
+ called reserved names.
+ */
+
+using namespace ::com::sun::star;
+
+namespace sfx2 {
+
+
+bool isValidNCName(std::u16string_view i_rIdref)
+{
+ const OString id(
+ OUStringToOString(i_rIdref, RTL_TEXTENCODING_UTF8) );
+ return !(xmlValidateNCName(
+ reinterpret_cast<const unsigned char*>(id.getStr()), 0));
+}
+
+
+constexpr OUString s_content = u"content.xml"_ustr;
+constexpr OUString s_styles = u"styles.xml"_ustr;
+constexpr OUString s_manifest = u"manifest.rdf"_ustr;
+const char s_odfmime [] = "application/vnd.oasis.opendocument.";
+
+
+static bool isContentFile(std::u16string_view i_rPath)
+{
+ return i_rPath == s_content;
+}
+
+static bool isStylesFile (std::u16string_view i_rPath)
+{
+ return i_rPath == s_styles;
+}
+
+bool isValidXmlId(std::u16string_view i_rStreamName,
+ std::u16string_view i_rIdref)
+{
+ return isValidNCName(i_rIdref)
+ && (isContentFile(i_rStreamName) || isStylesFile(i_rStreamName));
+}
+
+static bool isReservedFile(std::u16string_view i_rPath)
+{
+ return isContentFile(i_rPath) || isStylesFile(i_rPath) || i_rPath == u"meta.xml" || i_rPath == u"settings.xml";
+}
+
+
+uno::Reference<rdf::XURI> createBaseURI(
+ uno::Reference<uno::XComponentContext> const & i_xContext,
+ uno::Reference<frame::XModel> const & i_xModel,
+ OUString const & i_rPkgURI, std::u16string_view i_rSubDocument)
+{
+ if (!i_xContext.is() || (!i_xModel.is() && i_rPkgURI.isEmpty())) {
+ throw uno::RuntimeException();
+ }
+
+ OUString pkgURI(i_rPkgURI);
+
+ // tdf#123293 chicken/egg problem when loading from stream: there is no URI,
+ // and also the model doesn't have a storage yet, so we need to get the
+ // tdoc URI without a storage...
+ if (pkgURI.isEmpty())
+ {
+ assert(i_xModel.is());
+ uno::Reference<frame::XTransientDocumentsDocumentContentIdentifierFactory>
+ const xTDDCIF(
+ i_xContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.ucb.TransientDocumentsContentProvider",
+ i_xContext),
+ uno::UNO_QUERY_THROW);
+ uno::Reference<ucb::XContentIdentifier> const xContentId(
+ xTDDCIF->createDocumentContentIdentifier(i_xModel));
+ SAL_WARN_IF(!xContentId.is(), "sfx", "createBaseURI: cannot create ContentIdentifier");
+ if (!xContentId.is())
+ {
+ throw uno::RuntimeException("createBaseURI: cannot create ContentIdentifier");
+ }
+ pkgURI = xContentId->getContentIdentifier();
+ assert(!pkgURI.isEmpty());
+ if (!pkgURI.isEmpty() && !pkgURI.endsWith("/"))
+ {
+ pkgURI += "/";
+ }
+ }
+
+ // #i108078# workaround non-hierarchical vnd.sun.star.expand URIs
+ // this really should be done somewhere else, not here.
+ if (pkgURI.startsWithIgnoreAsciiCase("vnd.sun.star.expand:", &pkgURI))
+ {
+ // expand it here (makeAbsolute requires hierarchical URI)
+ if (!pkgURI.isEmpty()) {
+ pkgURI = ::rtl::Uri::decode(
+ pkgURI, rtl_UriDecodeStrict, RTL_TEXTENCODING_UTF8);
+ if (pkgURI.isEmpty()) {
+ throw uno::RuntimeException();
+ }
+ ::rtl::Bootstrap::expandMacros(pkgURI);
+ }
+ }
+
+ const uno::Reference<uri::XUriReferenceFactory> xUriFactory =
+ uri::UriReferenceFactory::create( i_xContext);
+ uno::Reference< uri::XUriReference > xBaseURI;
+
+ const uno::Reference< uri::XUriReference > xPkgURI(
+ xUriFactory->parse(pkgURI), uno::UNO_SET_THROW );
+ xPkgURI->clearFragment();
+
+ // need to know whether the storage is a FileSystemStorage
+ // XServiceInfo would be better, but it is not implemented
+// if ( pkgURI.getLength() && ::utl::UCBContentHelper::IsFolder(pkgURI) )
+ if (true) {
+ xBaseURI.set( xPkgURI, uno::UNO_SET_THROW );
+ }
+ OUStringBuffer buf(64);
+ if (!xBaseURI->getUriReference().endsWith("/"))
+ {
+ const sal_Int32 count( xBaseURI->getPathSegmentCount() );
+ if (count > 0)
+ {
+ buf.append(xBaseURI->getPathSegment(count - 1));
+ }
+ buf.append('/');
+ }
+ if (!i_rSubDocument.empty())
+ {
+ buf.append(OUString::Concat(i_rSubDocument) + "/");
+ }
+ if (!buf.isEmpty())
+ {
+ const uno::Reference< uri::XUriReference > xPathURI(
+ xUriFactory->parse(buf.makeStringAndClear()), uno::UNO_SET_THROW );
+ xBaseURI.set(
+ xUriFactory->makeAbsolute(xBaseURI, xPathURI,
+ true, uri::RelativeUriExcessParentSegments_ERROR),
+ uno::UNO_SET_THROW);
+ }
+
+ return rdf::URI::create(i_xContext, xBaseURI->getUriReference());
+}
+
+
+struct DocumentMetadataAccess_Impl
+{
+ // note: these are all initialized in constructor, and loadFromStorage
+ const uno::Reference<uno::XComponentContext> m_xContext;
+ const SfxObjectShell & m_rXmlIdRegistrySupplier;
+ uno::Reference<rdf::XURI> m_xBaseURI;
+ uno::Reference<rdf::XRepository> m_xRepository;
+ uno::Reference<rdf::XNamedGraph> m_xManifest;
+ DocumentMetadataAccess_Impl(
+ uno::Reference<uno::XComponentContext> i_xContext,
+ SfxObjectShell const & i_rRegistrySupplier)
+ : m_xContext(std::move(i_xContext))
+ , m_rXmlIdRegistrySupplier(i_rRegistrySupplier)
+ {
+ OSL_ENSURE(m_xContext.is(), "context null");
+ }
+};
+
+// this is... a hack.
+template<sal_Int16 Constant>
+static uno::Reference<rdf::XURI> const &
+getURI(uno::Reference< uno::XComponentContext > const & i_xContext)
+{
+ static uno::Reference< rdf::XURI > xURI(
+ rdf::URI::createKnown(i_xContext, Constant), uno::UNO_SET_THROW);
+ return xURI;
+}
+
+
+/** would storing the file to a XStorage succeed? */
+static bool isFileNameValid(std::u16string_view i_rFileName)
+{
+ if (i_rFileName.empty()) return false;
+ if (i_rFileName[0] == '/') return false; // no absolute paths!
+ sal_Int32 idx(0);
+ do {
+ const OUString segment(
+ o3tl::getToken(i_rFileName, 0, u'/', idx) );
+ if (segment.isEmpty() || // no empty segments
+ segment == "." || // no . segments
+ segment == ".." || // no .. segments
+ !::comphelper::OStorageHelper::IsValidZipEntryFileName(
+ segment, false)) // no invalid characters
+ return false;
+ } while (idx >= 0);
+ return true;
+}
+
+/** split a uri hierarchy into first segment and rest */
+static bool
+splitPath(OUString const & i_rPath,
+ OUString & o_rDir, OUString& o_rRest)
+{
+ const sal_Int32 idx(i_rPath.indexOf(u'/'));
+ if (idx < 0 || idx >= i_rPath.getLength()) {
+ o_rDir.clear();
+ o_rRest = i_rPath;
+ return true;
+ } else if (idx == 0 || idx == i_rPath.getLength() - 1) {
+ // input must not start or end with '/'
+ return false;
+ } else {
+ o_rDir = i_rPath.copy(0, idx);
+ o_rRest = i_rPath.copy(idx+1);
+ return true;
+ }
+}
+
+static bool
+splitXmlId(std::u16string_view i_XmlId,
+ OUString & o_StreamName, OUString& o_Idref )
+{
+ const size_t idx(i_XmlId.find(u'#'));
+ if (idx == std::u16string_view::npos)
+ return false;
+ o_StreamName = i_XmlId.substr(0, idx);
+ o_Idref = i_XmlId.substr(idx+1);
+ return isValidXmlId(o_StreamName, o_Idref);
+}
+
+
+static uno::Reference<rdf::XURI>
+getURIForStream(struct DocumentMetadataAccess_Impl const & i_rImpl,
+ OUString const& i_rPath)
+{
+ const uno::Reference<rdf::XURI> xURI(
+ rdf::URI::createNS( i_rImpl.m_xContext,
+ i_rImpl.m_xBaseURI->getStringValue(), i_rPath),
+ uno::UNO_SET_THROW);
+ return xURI;
+}
+
+/** add statements declaring i_xResource to be a file of type i_xType with
+ path i_rPath to manifest, with optional additional types i_pTypes */
+static void
+addFile(struct DocumentMetadataAccess_Impl const & i_rImpl,
+ uno::Reference<rdf::XURI> const& i_xType,
+ OUString const & i_rPath,
+ const uno::Sequence < uno::Reference< rdf::XURI > > * i_pTypes)
+{
+ try {
+ const uno::Reference<rdf::XURI> xURI( getURIForStream(
+ i_rImpl, i_rPath) );
+
+ i_rImpl.m_xManifest->addStatement(i_rImpl.m_xBaseURI,
+ getURI<rdf::URIs::PKG_HASPART>(i_rImpl.m_xContext),
+ xURI);
+ i_rImpl.m_xManifest->addStatement(xURI,
+ getURI<rdf::URIs::RDF_TYPE>(i_rImpl.m_xContext),
+ i_xType);
+ if (i_pTypes) {
+ for (const auto& rType : *i_pTypes) {
+ i_rImpl.m_xManifest->addStatement(xURI,
+ getURI<rdf::URIs::RDF_TYPE>(i_rImpl.m_xContext),
+ rType);
+ }
+ }
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (const uno::Exception &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException(
+ "addFile: exception", /*this*/nullptr, anyEx);
+ }
+}
+
+/** add content.xml or styles.xml to manifest */
+static bool
+addContentOrStylesFileImpl(struct DocumentMetadataAccess_Impl const & i_rImpl,
+ const OUString & i_rPath)
+{
+ uno::Reference<rdf::XURI> xType;
+ if (isContentFile(i_rPath)) {
+ xType.set(getURI<rdf::URIs::ODF_CONTENTFILE>(i_rImpl.m_xContext));
+ } else if (isStylesFile(i_rPath)) {
+ xType.set(getURI<rdf::URIs::ODF_STYLESFILE>(i_rImpl.m_xContext));
+ } else {
+ return false;
+ }
+ addFile(i_rImpl, xType, i_rPath, nullptr);
+ return true;
+}
+
+/** add metadata file to manifest */
+static void
+addMetadataFileImpl(struct DocumentMetadataAccess_Impl const & i_rImpl,
+ const OUString & i_rPath,
+ const uno::Sequence < uno::Reference< rdf::XURI > > & i_rTypes)
+{
+ addFile(i_rImpl,
+ getURI<rdf::URIs::PKG_METADATAFILE>(i_rImpl.m_xContext),
+ i_rPath, &i_rTypes);
+}
+
+/** remove a file from the manifest */
+static void
+removeFile(struct DocumentMetadataAccess_Impl const & i_rImpl,
+ uno::Reference<rdf::XURI> const& i_xPart)
+{
+ if (!i_xPart.is()) throw uno::RuntimeException();
+ try {
+ i_rImpl.m_xManifest->removeStatements(i_rImpl.m_xBaseURI,
+ getURI<rdf::URIs::PKG_HASPART>(i_rImpl.m_xContext),
+ i_xPart);
+ i_rImpl.m_xManifest->removeStatements(i_xPart,
+ getURI<rdf::URIs::RDF_TYPE>(i_rImpl.m_xContext), nullptr);
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (const uno::Exception &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException(
+ "removeFile: exception",
+ nullptr, anyEx);
+ }
+}
+
+static ::std::vector< uno::Reference< rdf::XURI > >
+getAllParts(struct DocumentMetadataAccess_Impl const & i_rImpl)
+{
+ ::std::vector< uno::Reference< rdf::XURI > > ret;
+ try {
+ const uno::Reference<container::XEnumeration> xEnum(
+ i_rImpl.m_xManifest->getStatements( i_rImpl.m_xBaseURI,
+ getURI<rdf::URIs::PKG_HASPART>(i_rImpl.m_xContext), nullptr),
+ uno::UNO_SET_THROW);
+ while (xEnum->hasMoreElements()) {
+ rdf::Statement stmt;
+ if (!(xEnum->nextElement() >>= stmt)) {
+ throw uno::RuntimeException();
+ }
+ const uno::Reference<rdf::XURI> xPart(stmt.Object,
+ uno::UNO_QUERY);
+ if (!xPart.is()) continue;
+ ret.push_back(xPart);
+ }
+ return ret;
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (const uno::Exception &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException(
+ "getAllParts: exception",
+ nullptr, anyEx);
+ }
+}
+
+static bool
+isPartOfType(struct DocumentMetadataAccess_Impl const & i_rImpl,
+ uno::Reference<rdf::XURI> const & i_xPart,
+ uno::Reference<rdf::XURI> const & i_xType)
+{
+ if (!i_xPart.is() || !i_xType.is()) throw uno::RuntimeException();
+ try {
+ const uno::Reference<container::XEnumeration> xEnum(
+ i_rImpl.m_xManifest->getStatements(i_xPart,
+ getURI<rdf::URIs::RDF_TYPE>(i_rImpl.m_xContext),
+ i_xType),
+ uno::UNO_SET_THROW);
+ return xEnum->hasMoreElements();
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (const uno::Exception &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException(
+ "isPartOfType: exception",
+ nullptr, anyEx);
+ }
+}
+
+static ::std::vector<uno::Reference<rdf::XURI>>
+getAllParts(struct DocumentMetadataAccess_Impl const& i_rImpl,
+ const uno::Reference<rdf::XURI>& i_xType)
+{
+ ::std::vector<uno::Reference<rdf::XURI>> ret;
+ try
+ {
+ const uno::Reference<container::XEnumeration> xEnum(
+ i_rImpl.m_xManifest->getStatements(i_rImpl.m_xBaseURI,
+ getURI<rdf::URIs::PKG_HASPART>(i_rImpl.m_xContext),
+ nullptr),
+ uno::UNO_SET_THROW);
+ while (xEnum->hasMoreElements())
+ {
+ rdf::Statement stmt;
+ if (!(xEnum->nextElement() >>= stmt))
+ {
+ throw uno::RuntimeException();
+ }
+ const uno::Reference<rdf::XURI> xPart(stmt.Object, uno::UNO_QUERY);
+ if (!xPart.is())
+ continue;
+
+ const uno::Reference<container::XEnumeration> xEnum2(
+ i_rImpl.m_xManifest->getStatements(
+ xPart, getURI<rdf::URIs::RDF_TYPE>(i_rImpl.m_xContext), i_xType),
+ uno::UNO_SET_THROW);
+ if (xEnum2->hasMoreElements())
+ ret.emplace_back(xPart);
+ }
+ return ret;
+ }
+ catch (const uno::RuntimeException&)
+ {
+ throw;
+ }
+ catch (const uno::Exception& e)
+ {
+ throw lang::WrappedTargetRuntimeException("getAllParts: exception", nullptr,
+ uno::Any(e));
+ }
+}
+
+static ucb::InteractiveAugmentedIOException
+mkException( OUString const & i_rMessage,
+ ucb::IOErrorCode const i_ErrorCode,
+ OUString const & i_rUri, OUString const & i_rResource)
+{
+ const beans::PropertyValue uriProp("Uri",
+ -1, uno::Any(i_rUri), static_cast<beans::PropertyState>(0));
+ const beans::PropertyValue rnProp(
+ "ResourceName",
+ -1, uno::Any(i_rResource), static_cast<beans::PropertyState>(0));
+ return ucb::InteractiveAugmentedIOException(i_rMessage, {},
+ task::InteractionClassification_ERROR, i_ErrorCode,
+ { uno::Any(uriProp), uno::Any(rnProp) });
+}
+
+/** error handling policy.
+ <p>If a handler is given, ask it how to proceed:
+ <ul><li>(default:) cancel import, raise exception</li>
+ <li>ignore the error and continue</li>
+ <li>retry the action that led to the error</li></ul></p>
+ N.B.: must not be called before DMA is fully initialized!
+ @returns true iff caller should retry
+ */
+static bool
+handleError( ucb::InteractiveAugmentedIOException const & i_rException,
+ const uno::Reference<task::XInteractionHandler> & i_xHandler)
+{
+ if (!i_xHandler.is()) {
+ throw lang::WrappedTargetException(
+ "DocumentMetadataAccess::loadMetadataFromStorage: exception",
+ /* *this*/ nullptr, uno::Any(i_rException));
+ }
+
+ ::rtl::Reference< ::comphelper::OInteractionRequest > pRequest(
+ new ::comphelper::OInteractionRequest(uno::Any(i_rException)) );
+ ::rtl::Reference< ::comphelper::OInteractionRetry > pRetry(
+ new ::comphelper::OInteractionRetry );
+ ::rtl::Reference< ::comphelper::OInteractionApprove > pApprove(
+ new ::comphelper::OInteractionApprove );
+ ::rtl::Reference< ::comphelper::OInteractionAbort > pAbort(
+ new ::comphelper::OInteractionAbort );
+
+ pRequest->addContinuation( pApprove );
+ pRequest->addContinuation( pAbort );
+ // actually call the handler
+ i_xHandler->handle( pRequest );
+ if (pRetry->wasSelected()) {
+ return true;
+ } else if (pApprove->wasSelected()) {
+ return false;
+ } else {
+ OSL_ENSURE(pAbort->wasSelected(), "no continuation selected?");
+ throw lang::WrappedTargetException(
+ "DocumentMetadataAccess::loadMetadataFromStorage: exception",
+ /* *this*/ nullptr, uno::Any(i_rException));
+ }
+}
+
+/** check if storage has content.xml/styles.xml;
+ e.g. ODB files seem to only have content.xml */
+static void
+collectFilesFromStorage(uno::Reference<embed::XStorage> const& i_xStorage,
+ std::set< OUString > & o_rFiles)
+{
+ try {
+ if (i_xStorage->hasByName(s_content) &&
+ i_xStorage->isStreamElement(s_content))
+ {
+ o_rFiles.insert(s_content);
+ }
+ if (i_xStorage->hasByName(s_styles) &&
+ i_xStorage->isStreamElement(s_styles))
+ {
+ o_rFiles.insert(s_styles);
+ }
+ } catch (const uno::Exception &) {
+ TOOLS_WARN_EXCEPTION("sfx", "collectFilesFromStorage");
+ }
+}
+
+/** import a metadata file into repository */
+static void
+readStream(struct DocumentMetadataAccess_Impl & i_rImpl,
+ uno::Reference< embed::XStorage > const & i_xStorage,
+ OUString const & i_rPath,
+ OUString const & i_rBaseURI)
+{
+ try {
+ OUString dir;
+ OUString rest;
+ if (!splitPath(i_rPath, dir, rest)) throw uno::RuntimeException();
+ if (dir.isEmpty()) {
+ if (!i_xStorage->isStreamElement(i_rPath)) {
+ throw mkException(
+ "readStream: is not a stream",
+ ucb::IOErrorCode_NO_FILE, i_rBaseURI + i_rPath, i_rPath);
+ }
+ const uno::Reference<io::XStream> xStream(
+ i_xStorage->openStreamElement(i_rPath,
+ embed::ElementModes::READ), uno::UNO_SET_THROW);
+ const uno::Reference<io::XInputStream> xInStream(
+ xStream->getInputStream(), uno::UNO_SET_THROW );
+ const uno::Reference<rdf::XURI> xBaseURI(
+ rdf::URI::create(i_rImpl.m_xContext, i_rBaseURI));
+ const uno::Reference<rdf::XURI> xURI(
+ rdf::URI::createNS(i_rImpl.m_xContext,
+ i_rBaseURI, i_rPath));
+ i_rImpl.m_xRepository->importGraph(rdf::FileFormat::RDF_XML,
+ xInStream, xURI, xBaseURI);
+ } else {
+ if (!i_xStorage->isStorageElement(dir)) {
+ throw mkException(
+ "readStream: is not a directory",
+ ucb::IOErrorCode_NO_DIRECTORY, i_rBaseURI + dir, dir);
+ }
+ const uno::Reference<embed::XStorage> xDir(
+ i_xStorage->openStorageElement(dir,
+ embed::ElementModes::READ));
+ const uno::Reference< beans::XPropertySet > xDirProps(xDir,
+ uno::UNO_QUERY_THROW);
+ try {
+ OUString mimeType;
+ xDirProps->getPropertyValue(
+ utl::MediaDescriptor::PROP_MEDIATYPE )
+ >>= mimeType;
+ if (mimeType.startsWith(s_odfmime)) {
+ SAL_WARN("sfx", "readStream: refusing to recurse into embedded document");
+ return;
+ }
+ } catch (const uno::Exception &) { }
+ readStream(i_rImpl, xDir, rest, i_rBaseURI+dir+"/" );
+ }
+ } catch (const container::NoSuchElementException & e) {
+ throw mkException(e.Message, ucb::IOErrorCode_NOT_EXISTING_PATH,
+ i_rBaseURI + i_rPath, i_rPath);
+ } catch (const io::IOException & e) {
+ throw mkException(e.Message, ucb::IOErrorCode_CANT_READ,
+ i_rBaseURI + i_rPath, i_rPath);
+ } catch (const rdf::ParseException & e) {
+ throw mkException(e.Message, ucb::IOErrorCode_WRONG_FORMAT,
+ i_rBaseURI + i_rPath, i_rPath);
+ }
+}
+
+/** import a metadata file into repository */
+static void
+importFile(struct DocumentMetadataAccess_Impl & i_rImpl,
+ uno::Reference<embed::XStorage> const & i_xStorage,
+ OUString const & i_rBaseURI,
+ uno::Reference<task::XInteractionHandler> const & i_xHandler,
+ const OUString& i_rPath)
+{
+retry:
+ try {
+ readStream(i_rImpl, i_xStorage, i_rPath, i_rBaseURI);
+ } catch (const ucb::InteractiveAugmentedIOException & e) {
+ if (handleError(e, i_xHandler)) goto retry;
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (const uno::Exception &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException(
+ "importFile: exception",
+ nullptr, anyEx);
+ }
+}
+
+/** actually write a metadata file to the storage */
+static void
+exportStream(struct DocumentMetadataAccess_Impl const & i_rImpl,
+ uno::Reference< embed::XStorage > const & i_xStorage,
+ uno::Reference<rdf::XURI> const & i_xGraphName,
+ OUString const & i_rFileName,
+ OUString const & i_rBaseURI)
+{
+ const uno::Reference<io::XStream> xStream(
+ i_xStorage->openStreamElement(i_rFileName,
+ embed::ElementModes::WRITE | embed::ElementModes::TRUNCATE),
+ uno::UNO_SET_THROW);
+ const uno::Reference< beans::XPropertySet > xStreamProps(xStream,
+ uno::UNO_QUERY);
+ if (xStreamProps.is()) { // this is NOT supported in FileSystemStorage
+ xStreamProps->setPropertyValue(
+ "MediaType",
+ uno::Any(OUString("application/rdf+xml")));
+ }
+ const uno::Reference<io::XOutputStream> xOutStream(
+ xStream->getOutputStream(), uno::UNO_SET_THROW );
+ const uno::Reference<rdf::XURI> xBaseURI(
+ rdf::URI::create(i_rImpl.m_xContext, i_rBaseURI));
+ i_rImpl.m_xRepository->exportGraph(rdf::FileFormat::RDF_XML,
+ xOutStream, i_xGraphName, xBaseURI);
+}
+
+/** write a metadata file to the storage */
+static void
+writeStream(struct DocumentMetadataAccess_Impl & i_rImpl,
+ uno::Reference< embed::XStorage > const & i_xStorage,
+ uno::Reference<rdf::XURI> const & i_xGraphName,
+ OUString const & i_rPath,
+ OUString const & i_rBaseURI)
+{
+ OUString dir;
+ OUString rest;
+ if (!splitPath(i_rPath, dir, rest)) throw uno::RuntimeException();
+ try {
+ if (dir.isEmpty()) {
+ exportStream(i_rImpl, i_xStorage, i_xGraphName, i_rPath,
+ i_rBaseURI);
+ } else {
+ const uno::Reference<embed::XStorage> xDir(
+ i_xStorage->openStorageElement(dir,
+ embed::ElementModes::WRITE));
+ const uno::Reference< beans::XPropertySet > xDirProps(xDir,
+ uno::UNO_QUERY_THROW);
+ try {
+ OUString mimeType;
+ xDirProps->getPropertyValue(
+ utl::MediaDescriptor::PROP_MEDIATYPE )
+ >>= mimeType;
+ if (mimeType.startsWith(s_odfmime)) {
+ SAL_WARN("sfx", "writeStream: refusing to recurse into embedded document");
+ return;
+ }
+ } catch (const uno::Exception &) { }
+ writeStream(i_rImpl, xDir, i_xGraphName, rest, i_rBaseURI+dir+"/");
+ uno::Reference<embed::XTransactedObject> const xTransaction(
+ xDir, uno::UNO_QUERY);
+ if (xTransaction.is()) {
+ xTransaction->commit();
+ }
+ }
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (const io::IOException &) {
+ throw;
+ }
+}
+
+static void
+initLoading(struct DocumentMetadataAccess_Impl & i_rImpl,
+ const uno::Reference< embed::XStorage > & i_xStorage,
+ const uno::Reference<rdf::XURI> & i_xBaseURI,
+ const uno::Reference<task::XInteractionHandler> & i_xHandler)
+{
+retry:
+ // clear old data
+ i_rImpl.m_xManifest.clear();
+ // init BaseURI
+ i_rImpl.m_xBaseURI = i_xBaseURI;
+
+ // create repository
+ i_rImpl.m_xRepository.clear();
+ i_rImpl.m_xRepository.set(rdf::Repository::create(i_rImpl.m_xContext),
+ uno::UNO_SET_THROW);
+
+ // try to delay raising errors until after initialization is done
+ uno::Any rterr;
+ ucb::InteractiveAugmentedIOException iaioe;
+ bool err(false);
+
+ const uno::Reference <rdf::XURI> xManifest(
+ getURIForStream(i_rImpl, s_manifest));
+ try {
+ readStream(i_rImpl, i_xStorage, s_manifest, i_xBaseURI->getStringValue());
+ } catch (const ucb::InteractiveAugmentedIOException & e) {
+ // no manifest.rdf: this is not an error in ODF < 1.2
+ if (ucb::IOErrorCode_NOT_EXISTING_PATH != e.Code) {
+ iaioe = e;
+ err = true;
+ }
+ } catch (const uno::Exception & e) {
+ rterr <<= e;
+ }
+
+ // init manifest graph
+ const uno::Reference<rdf::XNamedGraph> xManifestGraph(
+ i_rImpl.m_xRepository->getGraph(xManifest));
+ i_rImpl.m_xManifest.set(xManifestGraph.is() ? xManifestGraph :
+ i_rImpl.m_xRepository->createGraph(xManifest), uno::UNO_SET_THROW);
+
+ // document statement
+ i_rImpl.m_xManifest->addStatement(i_rImpl.m_xBaseURI,
+ getURI<rdf::URIs::RDF_TYPE>(i_rImpl.m_xContext),
+ getURI<rdf::URIs::PKG_DOCUMENT>(i_rImpl.m_xContext));
+
+ OSL_ENSURE(i_rImpl.m_xBaseURI.is(), "base URI is null");
+ OSL_ENSURE(i_rImpl.m_xRepository.is(), "repository is null");
+ OSL_ENSURE(i_rImpl.m_xManifest.is(), "manifest is null");
+
+ if (rterr.hasValue()) {
+ throw lang::WrappedTargetRuntimeException(
+ "DocumentMetadataAccess::loadMetadataFromStorage: "
+ "exception", nullptr, rterr);
+ }
+
+ if (err && handleError(iaioe, i_xHandler))
+ goto retry;
+}
+
+/** init Impl struct */
+static void init(struct DocumentMetadataAccess_Impl & i_rImpl)
+{
+ try {
+
+ i_rImpl.m_xManifest.set(i_rImpl.m_xRepository->createGraph(
+ getURIForStream(i_rImpl, s_manifest)),
+ uno::UNO_SET_THROW);
+
+ // insert the document statement
+ i_rImpl.m_xManifest->addStatement(i_rImpl.m_xBaseURI,
+ getURI<rdf::URIs::RDF_TYPE>(i_rImpl.m_xContext),
+ getURI<rdf::URIs::PKG_DOCUMENT>(i_rImpl.m_xContext));
+ } catch (const uno::Exception &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException(
+ "init: unexpected exception", nullptr,
+ anyEx);
+ }
+
+ // add top-level content files
+ if (!addContentOrStylesFileImpl(i_rImpl, s_content)) {
+ throw uno::RuntimeException();
+ }
+ if (!addContentOrStylesFileImpl(i_rImpl, s_styles)) {
+ throw uno::RuntimeException();
+ }
+}
+
+
+DocumentMetadataAccess::DocumentMetadataAccess(
+ uno::Reference< uno::XComponentContext > const & i_xContext,
+ const SfxObjectShell & i_rRegistrySupplier)
+ : m_pImpl(new DocumentMetadataAccess_Impl(i_xContext, i_rRegistrySupplier))
+{
+ // no initialization: must call loadFrom...
+}
+
+DocumentMetadataAccess::DocumentMetadataAccess(
+ uno::Reference< uno::XComponentContext > const & i_xContext,
+ const SfxObjectShell & i_rRegistrySupplier,
+ OUString const & i_rURI)
+ : m_pImpl(new DocumentMetadataAccess_Impl(i_xContext, i_rRegistrySupplier))
+{
+ OSL_ENSURE(!i_rURI.isEmpty(), "DMA::DMA: no URI given!");
+ OSL_ENSURE(i_rURI.endsWith("/"), "DMA::DMA: URI without / given!");
+ if (!i_rURI.endsWith("/")) throw uno::RuntimeException();
+ m_pImpl->m_xBaseURI.set(rdf::URI::create(m_pImpl->m_xContext, i_rURI));
+ m_pImpl->m_xRepository.set(rdf::Repository::create(m_pImpl->m_xContext),
+ uno::UNO_SET_THROW);
+
+ // init repository
+ init(*m_pImpl);
+
+ OSL_ENSURE(m_pImpl->m_xBaseURI.is(), "base URI is null");
+ OSL_ENSURE(m_pImpl->m_xRepository.is(), "repository is null");
+ OSL_ENSURE(m_pImpl->m_xManifest.is(), "manifest is null");
+}
+
+DocumentMetadataAccess::~DocumentMetadataAccess()
+{
+}
+
+// css::rdf::XRepositorySupplier:
+uno::Reference< rdf::XRepository > SAL_CALL
+DocumentMetadataAccess::getRDFRepository()
+{
+ OSL_ENSURE(m_pImpl->m_xRepository.is(), "repository not initialized");
+ return m_pImpl->m_xRepository;
+}
+
+// css::rdf::XNode:
+OUString SAL_CALL
+DocumentMetadataAccess::getStringValue()
+{
+ return m_pImpl->m_xBaseURI->getStringValue();
+}
+
+// css::rdf::XURI:
+OUString SAL_CALL
+DocumentMetadataAccess::getNamespace()
+{
+ return m_pImpl->m_xBaseURI->getNamespace();
+}
+
+OUString SAL_CALL
+DocumentMetadataAccess::getLocalName()
+{
+ return m_pImpl->m_xBaseURI->getLocalName();
+}
+
+// css::rdf::XDocumentMetadataAccess:
+uno::Reference< rdf::XMetadatable > SAL_CALL
+DocumentMetadataAccess::getElementByMetadataReference(
+ const css::beans::StringPair & i_rReference)
+{
+ const IXmlIdRegistry * pReg(
+ m_pImpl->m_rXmlIdRegistrySupplier.GetXmlIdRegistry() );
+ if (!pReg) {
+ throw uno::RuntimeException(
+ "DocumentMetadataAccess::getElementByXmlId: no registry", *this);
+ }
+ return pReg->GetElementByMetadataReference(i_rReference);
+}
+
+uno::Reference< rdf::XMetadatable > SAL_CALL
+DocumentMetadataAccess::getElementByURI(
+ const uno::Reference< rdf::XURI > & i_xURI )
+{
+ if (!i_xURI.is()) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::getElementByURI: URI is null", *this, 0);
+ }
+
+ const OUString baseURI( m_pImpl->m_xBaseURI->getStringValue() );
+ const OUString name( i_xURI->getStringValue() );
+ if (!name.match(baseURI)) {
+ return nullptr;
+ }
+ OUString path;
+ OUString idref;
+ if (!splitXmlId(name.subView(baseURI.getLength()), path, idref)) {
+ return nullptr;
+ }
+
+ return getElementByMetadataReference( beans::StringPair(path, idref) );
+}
+
+uno::Sequence<uno::Reference<rdf::XURI>> SAL_CALL
+DocumentMetadataAccess::getMetadataGraphsWithType(const uno::Reference<rdf::XURI>& i_xType)
+{
+ if (!i_xType.is())
+ {
+ throw lang::IllegalArgumentException("DocumentMetadataAccess::getMetadataGraphsWithType: "
+ "type is null",
+ *this, 0);
+ }
+
+ return ::comphelper::containerToSequence(getAllParts(*m_pImpl, i_xType));
+}
+
+uno::Reference<rdf::XURI> SAL_CALL
+DocumentMetadataAccess::addMetadataFile(const OUString & i_rFileName,
+ const uno::Sequence < uno::Reference< rdf::XURI > > & i_rTypes)
+{
+ if (!isFileNameValid(i_rFileName)) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::addMetadataFile: invalid FileName",
+ *this, 0);
+ }
+ if (isReservedFile(i_rFileName)) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::addMetadataFile:"
+ "invalid FileName: reserved", *this, 0);
+ }
+ if (std::any_of(i_rTypes.begin(), i_rTypes.end(),
+ [](const uno::Reference< rdf::XURI >& rType) { return !rType.is(); })) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::addMetadataFile: "
+ "null type", *this, 2);
+ }
+
+ const uno::Reference<rdf::XURI> xGraphName(
+ getURIForStream(*m_pImpl, i_rFileName) );
+
+ try {
+ m_pImpl->m_xRepository->createGraph(xGraphName);
+ } catch (const rdf::RepositoryException &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException(
+ "DocumentMetadataAccess::addMetadataFile: exception",
+ *this, anyEx);
+ // note: all other exceptions are propagated
+ }
+
+ addMetadataFileImpl(*m_pImpl, i_rFileName, i_rTypes);
+ return xGraphName;
+}
+
+uno::Reference<rdf::XURI> SAL_CALL
+DocumentMetadataAccess::importMetadataFile(::sal_Int16 i_Format,
+ const uno::Reference< io::XInputStream > & i_xInStream,
+ const OUString & i_rFileName,
+ const uno::Reference< rdf::XURI > & i_xBaseURI,
+ const uno::Sequence < uno::Reference< rdf::XURI > > & i_rTypes)
+{
+ if (!isFileNameValid(i_rFileName)) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::importMetadataFile: invalid FileName",
+ *this, 0);
+ }
+ if (isReservedFile(i_rFileName)) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::importMetadataFile:"
+ "invalid FileName: reserved", *this, 0);
+ }
+ if (std::any_of(i_rTypes.begin(), i_rTypes.end(),
+ [](const uno::Reference< rdf::XURI >& rType) { return !rType.is(); })) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::importMetadataFile: null type",
+ *this, 5);
+ }
+
+ const uno::Reference<rdf::XURI> xGraphName(
+ getURIForStream(*m_pImpl, i_rFileName) );
+
+ try {
+ m_pImpl->m_xRepository->importGraph(
+ i_Format, i_xInStream, xGraphName, i_xBaseURI);
+ } catch (const rdf::RepositoryException &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException(
+ "DocumentMetadataAccess::importMetadataFile: "
+ "RepositoryException", *this, anyEx);
+ // note: all other exceptions are propagated
+ }
+
+ // add to manifest
+ addMetadataFileImpl(*m_pImpl, i_rFileName, i_rTypes);
+ return xGraphName;
+}
+
+void SAL_CALL
+DocumentMetadataAccess::removeMetadataFile(
+ const uno::Reference< rdf::XURI > & i_xGraphName)
+{
+ try {
+ m_pImpl->m_xRepository->destroyGraph(i_xGraphName);
+ } catch (const rdf::RepositoryException &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException(
+ "DocumentMetadataAccess::removeMetadataFile: "
+ "RepositoryException", *this, anyEx);
+ // note: all other exceptions are propagated
+ }
+
+ // remove file from manifest
+ removeFile(*m_pImpl, i_xGraphName);
+}
+
+void SAL_CALL
+DocumentMetadataAccess::addContentOrStylesFile(
+ const OUString & i_rFileName)
+{
+ if (!isFileNameValid(i_rFileName)) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::addContentOrStylesFile: "
+ "invalid FileName", *this, 0);
+ }
+
+ if (!addContentOrStylesFileImpl(*m_pImpl, i_rFileName)) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::addContentOrStylesFile: "
+ "invalid FileName: must end with content.xml or styles.xml",
+ *this, 0);
+ }
+}
+
+void SAL_CALL
+DocumentMetadataAccess::removeContentOrStylesFile(
+ const OUString & i_rFileName)
+{
+ if (!isFileNameValid(i_rFileName)) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::removeContentOrStylesFile: "
+ "invalid FileName", *this, 0);
+ }
+
+ try {
+ const uno::Reference<rdf::XURI> xPart(
+ getURIForStream(*m_pImpl, i_rFileName) );
+ const uno::Reference<container::XEnumeration> xEnum(
+ m_pImpl->m_xManifest->getStatements( m_pImpl->m_xBaseURI,
+ getURI<rdf::URIs::PKG_HASPART>(m_pImpl->m_xContext),
+ xPart),
+ uno::UNO_SET_THROW);
+ if (!xEnum->hasMoreElements()) {
+ throw container::NoSuchElementException(
+ "DocumentMetadataAccess::removeContentOrStylesFile: "
+ "cannot find stream in manifest graph: " + i_rFileName,
+ *this);
+ }
+
+ // remove file from manifest
+ removeFile(*m_pImpl, xPart);
+
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (const uno::Exception &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException(
+ "DocumentMetadataAccess::removeContentOrStylesFile: exception",
+ *this, anyEx);
+ }
+}
+
+void SAL_CALL DocumentMetadataAccess::loadMetadataFromStorage(
+ const uno::Reference< embed::XStorage > & i_xStorage,
+ const uno::Reference<rdf::XURI> & i_xBaseURI,
+ const uno::Reference<task::XInteractionHandler> & i_xHandler)
+{
+ if (!i_xStorage.is()) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::loadMetadataFromStorage: "
+ "storage is null", *this, 0);
+ }
+ if (!i_xBaseURI.is()) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::loadMetadataFromStorage: "
+ "base URI is null", *this, 1);
+ }
+ const OUString baseURI( i_xBaseURI->getStringValue());
+ if (baseURI.indexOf('#') >= 0) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::loadMetadataFromStorage: "
+ "base URI not absolute", *this, 1);
+ }
+ if (!baseURI.endsWith("/")) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::loadMetadataFromStorage: "
+ "base URI does not end with slash", *this, 1);
+ }
+
+ initLoading(*m_pImpl, i_xStorage, i_xBaseURI, i_xHandler);
+
+ std::set< OUString > StgFiles;
+ collectFilesFromStorage(i_xStorage, StgFiles);
+
+ std::vector< OUString > MfstMetadataFiles;
+
+ try {
+ const ::std::vector< uno::Reference< rdf::XURI > > parts(
+ getAllParts(*m_pImpl) );
+ const uno::Reference<rdf::XURI>& xContentFile(
+ getURI<rdf::URIs::ODF_CONTENTFILE>(m_pImpl->m_xContext));
+ const uno::Reference<rdf::XURI>& xStylesFile(
+ getURI<rdf::URIs::ODF_STYLESFILE>(m_pImpl->m_xContext));
+ const uno::Reference<rdf::XURI>& xMetadataFile(
+ getURI<rdf::URIs::PKG_METADATAFILE>(m_pImpl->m_xContext));
+ const sal_Int32 len( baseURI.getLength() );
+ for (const auto& rxPart : parts) {
+ const OUString name(rxPart->getStringValue());
+ if (!name.match(baseURI)) {
+ SAL_WARN("sfx", "loadMetadataFromStorage: graph not in document: " << name);
+ continue;
+ }
+ const OUString relName( name.copy(len) );
+ if (relName == s_manifest) {
+ SAL_WARN("sfx", "loadMetadataFromStorage: found ourselves a recursive manifest!");
+ continue;
+ }
+ // remove found items from StgFiles
+ StgFiles.erase(relName);
+ if (isContentFile(relName)) {
+ if (!isPartOfType(*m_pImpl, rxPart, xContentFile)) {
+ const uno::Reference <rdf::XURI> xName(
+ getURIForStream(*m_pImpl, relName) );
+ // add missing type statement
+ m_pImpl->m_xManifest->addStatement(xName,
+ getURI<rdf::URIs::RDF_TYPE>(m_pImpl->m_xContext),
+ xContentFile);
+ }
+ } else if (isStylesFile(relName)) {
+ if (!isPartOfType(*m_pImpl, rxPart, xStylesFile)) {
+ const uno::Reference <rdf::XURI> xName(
+ getURIForStream(*m_pImpl, relName) );
+ // add missing type statement
+ m_pImpl->m_xManifest->addStatement(xName,
+ getURI<rdf::URIs::RDF_TYPE>(m_pImpl->m_xContext),
+ xStylesFile);
+ }
+ } else if (isReservedFile(relName)) {
+ SAL_WARN("sfx", "loadMetadataFromStorage: reserved file name in manifest");
+ } else {
+ if (isPartOfType(*m_pImpl, rxPart, xMetadataFile)) {
+ MfstMetadataFiles.push_back(relName);
+ }
+ // do not add statement for MetadataFile; it could be
+ // something else! just ignore it...
+ }
+ }
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (const uno::Exception &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException(
+ "DocumentMetadataAccess::loadMetadataFromStorage: "
+ "exception", *this, anyEx);
+ }
+
+ for (const auto& aStgFile : StgFiles)
+ addContentOrStylesFileImpl(*m_pImpl, aStgFile);
+
+ for (const auto& aMfstMetadataFile : MfstMetadataFiles)
+ importFile(*m_pImpl, i_xStorage, baseURI, i_xHandler, aMfstMetadataFile);
+}
+
+void SAL_CALL DocumentMetadataAccess::storeMetadataToStorage(
+ const uno::Reference< embed::XStorage > & i_xStorage)
+{
+ if (!i_xStorage.is()) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::storeMetadataToStorage: "
+ "storage is null", *this, 0);
+ }
+
+ // export manifest
+ const uno::Reference <rdf::XURI> xManifest(
+ getURIForStream(*m_pImpl, s_manifest) );
+ const OUString baseURI( m_pImpl->m_xBaseURI->getStringValue() );
+ try {
+ writeStream(*m_pImpl, i_xStorage, xManifest, s_manifest, baseURI);
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (const io::IOException &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetException(
+ "storeMetadataToStorage: IO exception", *this, anyEx);
+ } catch (const uno::Exception &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException(
+ "storeMetadataToStorage: exception", *this, anyEx);
+ }
+
+ // export metadata streams
+ try {
+ const uno::Sequence<uno::Reference<rdf::XURI> > graphs(
+ m_pImpl->m_xRepository->getGraphNames());
+ const sal_Int32 len( baseURI.getLength() );
+ for (const uno::Reference<rdf::XURI>& xName : graphs) {
+ const OUString name(xName->getStringValue());
+ if (!name.match(baseURI)) {
+ SAL_WARN("sfx", "storeMetadataToStorage: graph not in document: " << name);
+ continue;
+ }
+ const OUString relName( name.copy(len) );
+ if (relName == s_manifest) {
+ continue;
+ }
+ if (!isFileNameValid(relName) || isReservedFile(relName)) {
+ SAL_WARN("sfx", "storeMetadataToStorage: invalid file name: " << relName);
+ continue;
+ }
+ try {
+ writeStream(*m_pImpl, i_xStorage, xName, relName, baseURI);
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (const io::IOException &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetException(
+ "storeMetadataToStorage: IO exception",
+ *this, anyEx);
+ } catch (const uno::Exception &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException(
+ "storeMetadataToStorage: exception",
+ *this, anyEx);
+ }
+ }
+ } catch (const rdf::RepositoryException &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException(
+ "storeMetadataToStorage: exception", *this, anyEx);
+ }
+}
+
+void SAL_CALL
+DocumentMetadataAccess::loadMetadataFromMedium(
+ const uno::Sequence< beans::PropertyValue > & i_rMedium)
+{
+ uno::Reference<io::XInputStream> xIn;
+ utl::MediaDescriptor md(i_rMedium);
+ OUString URL;
+ md[ utl::MediaDescriptor::PROP_URL ] >>= URL;
+ OUString BaseURL;
+ md[ utl::MediaDescriptor::PROP_DOCUMENTBASEURL ] >>= BaseURL;
+ if (md.addInputStream()) {
+ md[ utl::MediaDescriptor::PROP_INPUTSTREAM ] >>= xIn;
+ }
+ if (!xIn.is() && URL.isEmpty()) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::loadMetadataFromMedium: "
+ "invalid medium: no URL, no input stream", *this, 0);
+ }
+ uno::Reference<embed::XStorage> xStorage;
+ try {
+ if (xIn.is()) {
+ xStorage = ::comphelper::OStorageHelper::GetStorageFromInputStream(
+ xIn, m_pImpl->m_xContext);
+ } else { // fallback to url
+ xStorage = ::comphelper::OStorageHelper::GetStorageFromURL2(
+ URL, embed::ElementModes::READ, m_pImpl->m_xContext);
+ }
+ } catch (const uno::RuntimeException &) {
+ throw;
+ } catch (const io::IOException &) {
+ throw;
+ } catch (const uno::Exception &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetException(
+ "DocumentMetadataAccess::loadMetadataFromMedium: "
+ "exception", *this, anyEx);
+ }
+ if (!xStorage.is()) {
+ throw uno::RuntimeException(
+ "DocumentMetadataAccess::loadMetadataFromMedium: "
+ "cannot get Storage", *this);
+ }
+ uno::Reference<rdf::XURI> xBaseURI;
+ try {
+ xBaseURI = createBaseURI(m_pImpl->m_xContext, nullptr, BaseURL);
+ } catch (const uno::Exception &) {
+ // fall back to URL
+ try {
+ xBaseURI = createBaseURI(m_pImpl->m_xContext, nullptr, URL);
+ } catch (const uno::Exception &) {
+ OSL_FAIL("cannot create base URI");
+ }
+ }
+ uno::Reference<task::XInteractionHandler> xIH;
+ md[ utl::MediaDescriptor::PROP_INTERACTIONHANDLER ] >>= xIH;
+ loadMetadataFromStorage(xStorage, xBaseURI, xIH);
+}
+
+void SAL_CALL
+DocumentMetadataAccess::storeMetadataToMedium(
+ const uno::Sequence< beans::PropertyValue > & i_rMedium)
+{
+ utl::MediaDescriptor md(i_rMedium);
+ OUString URL;
+ md[ utl::MediaDescriptor::PROP_URL ] >>= URL;
+ if (URL.isEmpty()) {
+ throw lang::IllegalArgumentException(
+ "DocumentMetadataAccess::storeMetadataToMedium: "
+ "invalid medium: no URL", *this, 0);
+ }
+
+ SfxMedium aMedium(i_rMedium);
+ uno::Reference<embed::XStorage> xStorage(aMedium.GetOutputStorage());
+
+ bool sfx(false);
+ if (xStorage.is()) {
+ sfx = true;
+ } else {
+ xStorage = ::comphelper::OStorageHelper::GetStorageFromURL2(
+ URL, embed::ElementModes::WRITE, m_pImpl->m_xContext);
+ }
+
+ if (!xStorage.is()) {
+ throw uno::RuntimeException(
+ "DocumentMetadataAccess::storeMetadataToMedium: "
+ "cannot get Storage", *this);
+ }
+ // set MIME type of the storage
+ utl::MediaDescriptor::const_iterator iter
+ = md.find(utl::MediaDescriptor::PROP_MEDIATYPE);
+ if (iter != md.end()) {
+ uno::Reference< beans::XPropertySet > xProps(xStorage,
+ uno::UNO_QUERY_THROW);
+ try {
+ // this is NOT supported in FileSystemStorage
+ xProps->setPropertyValue(
+ utl::MediaDescriptor::PROP_MEDIATYPE,
+ iter->second);
+ } catch (const uno::Exception &) { }
+ }
+ storeMetadataToStorage(xStorage);
+
+ if (!sfx)
+ return;
+
+ const bool bOk = aMedium.Commit();
+ aMedium.Close();
+ if ( !bOk ) {
+ ErrCodeMsg nError = aMedium.GetErrorIgnoreWarning();
+ if ( nError == ERRCODE_NONE ) {
+ nError = ERRCODE_IO_GENERAL;
+ }
+ task::ErrorCodeIOException ex(
+ "DocumentMetadataAccess::storeMetadataToMedium Commit failed: " + nError.toString(),
+ uno::Reference< uno::XInterface >(), sal_uInt32(nError.GetCode()));
+ throw lang::WrappedTargetException(OUString(), *this,
+ uno::Any(ex));
+ }
+}
+
+} // namespace sfx2
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/DocumentSigner.cxx b/sfx2/source/doc/DocumentSigner.cxx
new file mode 100644
index 0000000000..0106a64777
--- /dev/null
+++ b/sfx2/source/doc/DocumentSigner.cxx
@@ -0,0 +1,121 @@
+/* -*- 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/.
+ *
+ */
+
+#include <sfx2/DocumentSigner.hxx>
+
+#include <tools/stream.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <unotools/streamwrap.hxx>
+
+#include <comphelper/storagehelper.hxx>
+#include <comphelper/processfactory.hxx>
+
+#include <com/sun/star/embed/XStorage.hpp>
+#include <com/sun/star/embed/XTransactedObject.hpp>
+#include <com/sun/star/security/DocumentDigitalSignatures.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/io/XStream.hpp>
+
+using namespace css;
+
+namespace sfx2
+{
+bool DocumentSigner::signDocument(uno::Reference<security::XCertificate> const& rxCertificate)
+{
+ std::unique_ptr<SvStream> pStream(
+ utl::UcbStreamHelper::CreateStream(m_aUrl, StreamMode::READ | StreamMode::WRITE));
+ uno::Reference<io::XStream> xInputStream(new utl::OStreamWrapper(std::move(pStream)));
+
+ bool bResult = false;
+ uno::Reference<embed::XStorage> xWriteableZipStore;
+ try
+ {
+ xWriteableZipStore = comphelper::OStorageHelper::GetStorageOfFormatFromStream(
+ ZIP_STORAGE_FORMAT_STRING, xInputStream);
+ }
+ catch (const io::IOException&)
+ {
+ }
+
+ OUString aODFVersion(comphelper::OStorageHelper::GetODFVersionFromStorage(xWriteableZipStore));
+
+ uno::Reference<security::XDocumentDigitalSignatures> xSigner(
+ security::DocumentDigitalSignatures::createWithVersionAndValidSignature(
+ comphelper::getProcessComponentContext(), aODFVersion,
+ /*bHasValidDocumentSignature*/ true));
+
+ try
+ {
+ uno::Reference<embed::XStorage> xMetaInf;
+ if (xWriteableZipStore.is() && xWriteableZipStore->hasByName("META-INF"))
+ {
+ xMetaInf = xWriteableZipStore->openStorageElement("META-INF",
+ embed::ElementModes::READWRITE);
+ if (!xMetaInf.is())
+ throw uno::RuntimeException();
+ }
+ if (xMetaInf.is())
+ {
+ uno::Reference<embed::XStorage> xStorage
+ = comphelper::OStorageHelper::GetStorageOfFormatFromStream(
+ ZIP_STORAGE_FORMAT_STRING, xInputStream);
+
+ // ODF.
+ uno::Reference<io::XStream> xStream;
+ xStream.set(
+ xMetaInf->openStreamElement(xSigner->getDocumentContentSignatureDefaultStreamName(),
+ embed::ElementModes::READWRITE),
+ uno::UNO_SET_THROW);
+ bool bSuccess = xSigner->signDocumentWithCertificate(rxCertificate, xStorage, xStream);
+ if (bSuccess)
+ {
+ uno::Reference<embed::XTransactedObject> xTransact(xMetaInf, uno::UNO_QUERY_THROW);
+ xTransact->commit();
+ xTransact.set(xWriteableZipStore, uno::UNO_QUERY_THROW);
+ xTransact->commit();
+ bResult = true;
+ }
+ }
+ else if (xWriteableZipStore.is())
+ {
+ uno::Reference<embed::XStorage> xStorage
+ = comphelper::OStorageHelper::GetStorageOfFormatFromStream(
+ ZIP_STORAGE_FORMAT_STRING, xInputStream);
+
+ // OOXML.
+ uno::Reference<io::XStream> xStream;
+
+ // We need read-write to be able to add the signature relation.
+ bool bSuccess = xSigner->signDocumentWithCertificate(rxCertificate, xStorage, xStream);
+
+ if (bSuccess)
+ {
+ uno::Reference<embed::XTransactedObject> xTransact(xWriteableZipStore,
+ uno::UNO_QUERY_THROW);
+ xTransact->commit();
+ bResult = true;
+ }
+ }
+ else
+ {
+ // Something not ZIP based: e.g. PDF.
+ bResult = xSigner->signDocumentWithCertificate(
+ rxCertificate, uno::Reference<embed::XStorage>(), xInputStream);
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ }
+ return bResult;
+}
+
+} // namespace sfx2
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/Metadatable.cxx b/sfx2/source/doc/Metadatable.cxx
new file mode 100644
index 0000000000..0952b36177
--- /dev/null
+++ b/sfx2/source/doc/Metadatable.cxx
@@ -0,0 +1,1603 @@
+/* -*- 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 <sal/config.h>
+#include <sal/log.hxx>
+
+#include <osl/diagnose.h>
+#include <sfx2/Metadatable.hxx>
+#include <sfx2/XmlIdRegistry.hxx>
+
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <comphelper/random.hxx>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <algorithm>
+#include <memory>
+#include <string_view>
+#include <unordered_map>
+#if OSL_DEBUG_LEVEL > 0
+#include <typeinfo>
+#endif
+
+
+/** XML ID handling.
+
+ There is an abstract base class <type>XmlIdRegistry</type>, with
+ 2 subclasses <type>XmlIdRegistryDocument</type> for "normal" documents,
+ and <type>XmlIdRegistryClipboard</type> for clipboard documents.
+ These classes are responsible for managing XML IDs for all elements
+ of the model. Only the implementation of the <type>Metadatable</type>
+ base class needs to know the registries, so they are not in the header.
+
+ The handling of XML IDs differs between clipboard and non-clipboard
+ documents in several aspects. Most importantly, non-clipboard documents
+ can have several elements associated with one XML ID.
+ This is necessary because of the weird undo implementation:
+ deleting a text node moves the deleted node to the undo array, but
+ executing undo will then create a <em>copy</em> of that node in the
+ document array. These 2 nodes must have the same XML ID, because
+ we cannot know whether the user will do a redo next, or something else.
+
+ Because we need to have a mechanism for several objects per XML ID anyway,
+ we use that also to enable some usability features:
+ The document registry has a list of Metadatables per XML ID.
+ This list is sorted by priority, i.e., the first element has highest
+ priority. When inserting copies, care must be taken that they are inserted
+ at the right position: either before or after the source.
+ This is done by <method>Metadatable::RegisterAsCopyOf</method>.
+ When a text node is split, then both resulting text nodes are inserted
+ into the list. If the user then deletes one text node, the other one
+ will have the XML ID.
+ Also, when a Metadatable is copied to the clipboard and then pasted,
+ the copy is inserted into the list. If the user then deletes the source,
+ the XML ID is not lost.
+ The goal is that it should be hard to lose an XML ID by accident, which
+ is especially important as long as we do not have an UI that displays them.
+
+ There are two subclasses of <type>Metadatable</type>:
+ <ul><li><type>MetadatableClipboard</type>: for copies in the clipboard</li>
+ <li><type>MetadatableUndo</type>: for undo, because a Metadatable
+ may be destroyed on delete and a new one created on undo.</li></ul>
+ These serve only to track the position in an XML ID list in a document
+ registry, so that future actions can insert objects at the right position.
+ Unfortunately, inserting dummy objects seems to be necessary:
+ <ul><li>it is not sufficient to just remember the saved id, because then
+ the relative priorities might change when executing the undo</li>
+ <li>it is not sufficient to record the position as an integer, because
+ if we delete a text node and then undo, the node will be copied(!),
+ and we will have one more node in the list.<li>
+ <li>it is not sufficient to record the pointer of the previous/next
+ Metadatable, because if we delete a text node, undo, and then
+ do something to clear the redo array, the original text node is
+ destroyed, and is replaced by the copy created by undo</li></ul>
+
+ If content from a non-clipboard document is copied into a clipboard
+ document, a dummy <type>MetadatableClipboard</type> is inserted into the
+ non-clipboard document registry in order to track the position of the
+ source element. When the clipboard content is pasted back into the source
+ document, this dummy object is used to associate the pasted element with
+ that same XML ID.
+
+ If a <type>Metadatable</type> is deleted or merged,
+ <method>Metadatable::CreateUndo</method> is called, and returns a
+ <type>MetadatableUndo<type> instance, which can be used to undo the action
+ by passing it to <method>Metadatable::RestoreMetadata</method>.
+
+ */
+
+
+using namespace ::com::sun::star;
+
+using ::sfx2::isValidXmlId;
+
+
+namespace sfx2 {
+
+constexpr OUString s_content = u"content.xml"_ustr;
+constexpr OUString s_styles = u"styles.xml"_ustr;
+
+static bool isContentFile(std::u16string_view i_rPath)
+{
+ return i_rPath == s_content;
+}
+
+static bool isStylesFile (std::u16string_view i_rPath)
+{
+ return i_rPath == s_styles;
+}
+
+
+// XML ID handling ---------------------------------------------------
+
+/** handles registration of XMetadatable.
+
+ This class is responsible for guaranteeing that XMetadatable objects
+ always have XML IDs that are unique within a stream.
+
+ This is an abstract base class; see subclasses XmlIdRegistryDocument and
+ XmlIdRegistryClipboard.
+
+ @see SwDoc::GetXmlIdRegistry
+ @see SwDocShell::GetXmlIdRegistry
+ */
+class XmlIdRegistry : public sfx2::IXmlIdRegistry
+{
+
+public:
+ XmlIdRegistry();
+
+ /** get the ODF element with the given metadata reference. */
+ virtual css::uno::Reference< css::rdf::XMetadatable >
+ GetElementByMetadataReference(
+ const css::beans::StringPair & i_rReference) const
+ override;
+
+ /** register an ODF element at a newly generated, unique metadata reference.
+
+ <p>
+ Find a fresh XML ID, and register it for the element.
+ The generated ID does not occur in any stream of the document.
+ </p>
+ */
+ virtual void RegisterMetadatableAndCreateID(Metadatable& i_xObject) = 0;
+
+ /** try to register an ODF element at a given XML ID, or update its
+ registration to a different XML ID.
+
+ <p>
+ If the given new metadata reference is not already occupied in the
+ document, unregister the element at its old metadata reference if
+ it has one, and register the new metadata reference for the element.
+ Note that this method only ensures that XML IDs are unique per stream,
+ so using the same XML ID in both content.xml and styles.xml is allowed.
+ </p>
+
+ @returns
+ true iff the element has successfully been registered
+ */
+ virtual bool TryRegisterMetadatable(Metadatable& i_xObject,
+ OUString const& i_rStreamName, OUString const& i_rIdref)
+ = 0;
+
+ /** unregister an ODF element.
+
+ <p>
+ Unregister the element at its metadata reference.
+ Does not remove the metadata reference from the element.
+ </p>
+
+ @see RemoveXmlIdForElement
+ */
+ virtual void UnregisterMetadatable(Metadatable const&) = 0;
+
+ /** get the metadata reference for the given element. */
+ css::beans::StringPair
+ GetXmlIdForElement(Metadatable const&) const;
+
+ /** remove the metadata reference for the given element. */
+ virtual void RemoveXmlIdForElement(Metadatable const&) = 0;
+
+protected:
+
+ virtual bool LookupXmlId(const Metadatable& i_xObject,
+ OUString & o_rStream, OUString & o_rIdref) const = 0;
+
+ virtual Metadatable* LookupElement(const OUString & i_rStreamName,
+ const OUString & i_rIdref) const = 0;
+};
+
+// XmlIdRegistryDocument ---------------------------------------------
+
+namespace {
+
+/** non-clipboard documents */
+class XmlIdRegistryDocument : public XmlIdRegistry
+{
+
+public:
+ XmlIdRegistryDocument();
+
+ virtual ~XmlIdRegistryDocument() override;
+
+ virtual void RegisterMetadatableAndCreateID(Metadatable& i_xObject) override;
+
+ virtual bool TryRegisterMetadatable(Metadatable& i_xObject,
+ OUString const& i_rStreamName, OUString const& i_rIdref) override;
+
+ virtual void UnregisterMetadatable(Metadatable const&) override;
+
+ virtual void RemoveXmlIdForElement(Metadatable const&) override;
+
+ /** register i_rCopy as a copy of i_rSource,
+ with precedence iff i_bCopyPrecedesSource is true */
+ void RegisterCopy(Metadatable const& i_rSource, Metadatable & i_rCopy,
+ const bool i_bCopyPrecedesSource);
+
+ /** create a Undo Metadatable for i_rObject. */
+ static std::shared_ptr<MetadatableUndo> CreateUndo(
+ Metadatable const& i_rObject);
+
+ /** merge i_rMerged and i_rOther into i_rMerged. */
+ void JoinMetadatables(Metadatable & i_rMerged, Metadatable const& i_rOther);
+
+ // unfortunately public, Metadatable::RegisterAsCopyOf needs this
+ virtual bool LookupXmlId(const Metadatable& i_xObject,
+ OUString & o_rStream, OUString & o_rIdref) const override;
+
+private:
+
+ virtual Metadatable* LookupElement(const OUString & i_rStreamName,
+ const OUString & i_rIdref) const override;
+
+ struct XmlIdRegistry_Impl;
+ ::std::unique_ptr<XmlIdRegistry_Impl> m_pImpl;
+};
+
+}
+
+// MetadatableUndo ---------------------------------------------------
+
+/** the horrible Undo Metadatable: is inserted into lists to track position */
+class MetadatableUndo : public Metadatable
+{
+ /// as determined by the stream of the source in original document
+ const bool m_isInContent;
+public:
+ explicit MetadatableUndo(const bool i_isInContent)
+ : m_isInContent(i_isInContent) { }
+ virtual ::sfx2::XmlIdRegistry& GetRegistry() override
+ {
+ // N.B. for Undo, m_pReg is initialized by registering this as copy in
+ // CreateUndo; it is never cleared
+ OSL_ENSURE(m_pReg, "no m_pReg in MetadatableUndo ?");
+ return *m_pReg;
+ }
+ virtual bool IsInClipboard() const override { return false; }
+ virtual bool IsInUndo() const override { return true; }
+ virtual bool IsInContent() const override { return m_isInContent; }
+ virtual css::uno::Reference< css::rdf::XMetadatable > MakeUnoObject() override
+ { OSL_FAIL("MetadatableUndo::MakeUnoObject"); throw; }
+};
+
+// MetadatableClipboard ----------------------------------------------
+
+/** the horrible Clipboard Metadatable: inserted into lists to track position */
+class MetadatableClipboard : public Metadatable
+{
+ /// as determined by the stream of the source in original document
+ const bool m_isInContent;
+public:
+ explicit MetadatableClipboard(const bool i_isInContent)
+ : m_isInContent(i_isInContent) { }
+ virtual ::sfx2::XmlIdRegistry& GetRegistry() override
+ {
+ // N.B. for Clipboard, m_pReg is initialized by registering this as copy in
+ // RegisterAsCopyOf; it is only cleared by OriginNoLongerInBusinessAnymore
+ assert(m_pReg && "no m_pReg in MetadatableClipboard ?");
+ return *m_pReg;
+ }
+ virtual bool IsInClipboard() const override { return true; }
+ virtual bool IsInUndo() const override { return false; }
+ virtual bool IsInContent() const override { return m_isInContent; }
+ virtual css::uno::Reference< css::rdf::XMetadatable > MakeUnoObject() override
+ { OSL_FAIL("MetadatableClipboard::MakeUnoObject"); throw; }
+ void OriginNoLongerInBusinessAnymore() { m_pReg = nullptr; }
+};
+
+// XmlIdRegistryClipboard --------------------------------------------
+
+namespace {
+
+class XmlIdRegistryClipboard : public XmlIdRegistry
+{
+
+public:
+ XmlIdRegistryClipboard();
+
+ virtual void RegisterMetadatableAndCreateID(Metadatable& i_xObject) override;
+
+ virtual bool TryRegisterMetadatable(Metadatable& i_xObject,
+ OUString const& i_rStreamName, OUString const& i_rIdref) override;
+
+ virtual void UnregisterMetadatable(Metadatable const&) override;
+
+ virtual void RemoveXmlIdForElement(Metadatable const&) override;
+
+ /** register i_rCopy as a copy of i_rSource */
+ MetadatableClipboard & RegisterCopyClipboard(Metadatable & i_rCopy,
+ beans::StringPair const & i_rReference,
+ const bool i_isLatent);
+
+ /** get the Metadatable that links i_rObject to its origin registry */
+ MetadatableClipboard const* SourceLink(Metadatable const& i_rObject);
+
+private:
+ virtual bool LookupXmlId(const Metadatable& i_xObject,
+ OUString & o_rStream, OUString & o_rIdref) const override;
+
+ virtual Metadatable* LookupElement(const OUString & i_rStreamName,
+ const OUString & i_rIdref) const override;
+
+ /** create a Clipboard Metadatable for i_rObject. */
+ static std::shared_ptr<MetadatableClipboard> CreateClipboard(
+ const bool i_isInContent);
+
+ struct XmlIdRegistry_Impl;
+ ::std::unique_ptr<XmlIdRegistry_Impl> m_pImpl;
+};
+
+}
+
+// XmlIdRegistry
+
+::sfx2::IXmlIdRegistry * createXmlIdRegistry(const bool i_DocIsClipboard)
+{
+ return i_DocIsClipboard
+ ? static_cast<XmlIdRegistry*>( new XmlIdRegistryClipboard )
+ : static_cast<XmlIdRegistry*>( new XmlIdRegistryDocument );
+}
+
+XmlIdRegistry::XmlIdRegistry()
+{
+}
+
+css::uno::Reference< css::rdf::XMetadatable >
+XmlIdRegistry::GetElementByMetadataReference(
+ const beans::StringPair & i_rReference) const
+{
+ Metadatable* pObject( LookupElement(i_rReference.First,
+ i_rReference.Second) );
+ return pObject ? pObject->MakeUnoObject() : nullptr;
+}
+
+beans::StringPair
+XmlIdRegistry::GetXmlIdForElement(const Metadatable& i_rObject) const
+{
+ OUString path;
+ OUString idref;
+ if (LookupXmlId(i_rObject, path, idref))
+ {
+ if (LookupElement(path, idref) == &i_rObject)
+ {
+ return beans::StringPair(path, idref);
+ }
+ }
+ return beans::StringPair();
+}
+
+
+/// generate unique xml:id
+template< typename T >
+static OUString create_id(const
+ std::unordered_map< OUString, T > & i_rXmlIdMap)
+{
+ static bool bHack = (getenv("LIBO_ONEWAY_STABLE_ODF_EXPORT") != nullptr);
+ static const char prefix[] = "id"; // prefix for generated xml:id
+ typename std::unordered_map< OUString, T >
+ ::const_iterator iter;
+ OUString id;
+
+ if (bHack)
+ {
+ static sal_Int64 nIdCounter = SAL_CONST_INT64(4000000000);
+ do
+ {
+ id = prefix + OUString::number(nIdCounter++);
+ iter = i_rXmlIdMap.find(id);
+ }
+ while (iter != i_rXmlIdMap.end());
+ }
+ else
+ {
+ do
+ {
+ unsigned int const n(comphelper::rng::uniform_uint_distribution(0,
+ std::numeric_limits<unsigned int>::max()));
+ id = prefix + OUString::number(n);
+ iter = i_rXmlIdMap.find(id);
+ }
+ while (iter != i_rXmlIdMap.end());
+ }
+ return id;
+}
+
+
+// Document XML ID Registry (_Impl)
+
+/// element list
+typedef ::std::vector< Metadatable* > XmlIdVector_t;
+
+/// Idref -> (content.xml element list, styles.xml element list)
+typedef std::unordered_map< OUString,
+ ::std::pair< XmlIdVector_t, XmlIdVector_t > > XmlIdMap_t;
+
+namespace {
+
+/// pointer hash template
+template<typename T> struct PtrHash
+{
+ size_t operator() (T const * i_pT) const
+ {
+ return reinterpret_cast<size_t>(i_pT);
+ }
+};
+
+}
+
+/// element -> (stream name, idref)
+typedef std::unordered_map< const Metadatable*,
+ ::std::pair< OUString, OUString>, PtrHash<Metadatable> >
+ XmlIdReverseMap_t;
+
+struct XmlIdRegistryDocument::XmlIdRegistry_Impl
+{
+ XmlIdRegistry_Impl() {}
+
+ bool TryInsertMetadatable(Metadatable& i_xObject,
+ std::u16string_view i_rStream, const OUString & i_rIdref);
+
+ bool LookupXmlId(const Metadatable& i_xObject,
+ OUString & o_rStream, OUString & o_rIdref) const;
+
+ Metadatable* LookupElement(std::u16string_view i_rStreamName,
+ const OUString & i_rIdref) const;
+
+ const XmlIdVector_t * LookupElementVector(
+ std::u16string_view i_rStreamName,
+ const OUString & i_rIdref) const;
+
+ XmlIdVector_t * LookupElementVector(
+ std::u16string_view i_rStreamName,
+ const OUString & i_rIdref)
+ {
+ return const_cast<XmlIdVector_t*>(
+ const_cast<const XmlIdRegistry_Impl*>(this)
+ ->LookupElementVector(i_rStreamName, i_rIdref));
+ }
+
+ XmlIdMap_t m_XmlIdMap;
+ XmlIdReverseMap_t m_XmlIdReverseMap;
+};
+
+
+static void
+rmIter(XmlIdMap_t & i_rXmlIdMap, XmlIdMap_t::iterator const& i_rIter,
+ std::u16string_view i_rStream, Metadatable const& i_rObject)
+{
+ if (i_rIter != i_rXmlIdMap.end())
+ {
+ XmlIdVector_t & rVector( isContentFile(i_rStream)
+ ? i_rIter->second.first : i_rIter->second.second );
+ std::erase(rVector, &const_cast<Metadatable&>(i_rObject));
+ if (i_rIter->second.first.empty() && i_rIter->second.second.empty())
+ {
+ i_rXmlIdMap.erase(i_rIter);
+ }
+ }
+}
+
+
+const XmlIdVector_t *
+XmlIdRegistryDocument::XmlIdRegistry_Impl::LookupElementVector(
+ std::u16string_view i_rStreamName,
+ const OUString & i_rIdref) const
+{
+ const XmlIdMap_t::const_iterator iter( m_XmlIdMap.find(i_rIdref) );
+ if (iter != m_XmlIdMap.end())
+ {
+ OSL_ENSURE(!iter->second.first.empty() || !iter->second.second.empty(),
+ "null entry in m_XmlIdMap");
+ return (isContentFile(i_rStreamName))
+ ? &iter->second.first
+ : &iter->second.second;
+ }
+ else
+ {
+ return nullptr;
+ }
+}
+
+Metadatable*
+XmlIdRegistryDocument::XmlIdRegistry_Impl::LookupElement(
+ std::u16string_view i_rStreamName,
+ const OUString & i_rIdref) const
+{
+ if (!isValidXmlId(i_rStreamName, i_rIdref))
+ {
+ throw lang::IllegalArgumentException("illegal XmlId", nullptr, 0);
+ }
+
+ const XmlIdVector_t * pList( LookupElementVector(i_rStreamName, i_rIdref) );
+ if (pList)
+ {
+ const XmlIdVector_t::const_iterator iter(
+ ::std::find_if(pList->begin(), pList->end(),
+ [](Metadatable* item)->bool {
+ return !(item->IsInUndo() || item->IsInClipboard());
+ } ) ) ;
+ if (iter != pList->end())
+ {
+ return *iter;
+ }
+ }
+ return nullptr;
+}
+
+bool
+XmlIdRegistryDocument::XmlIdRegistry_Impl::LookupXmlId(
+ const Metadatable& i_rObject,
+ OUString & o_rStream, OUString & o_rIdref) const
+{
+ const XmlIdReverseMap_t::const_iterator iter(
+ m_XmlIdReverseMap.find(&i_rObject) );
+ if (iter != m_XmlIdReverseMap.end())
+ {
+ OSL_ENSURE(!iter->second.first.isEmpty(),
+ "null stream in m_XmlIdReverseMap");
+ OSL_ENSURE(!iter->second.second.isEmpty(),
+ "null id in m_XmlIdReverseMap");
+ o_rStream = iter->second.first;
+ o_rIdref = iter->second.second;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool
+XmlIdRegistryDocument::XmlIdRegistry_Impl::TryInsertMetadatable(
+ Metadatable & i_rObject,
+ std::u16string_view i_rStreamName, const OUString & i_rIdref)
+{
+ const bool bContent( isContentFile(i_rStreamName) );
+ OSL_ENSURE(isContentFile(i_rStreamName) || isStylesFile(i_rStreamName),
+ "invalid stream");
+
+ XmlIdVector_t * pList( LookupElementVector(i_rStreamName, i_rIdref) );
+ if (pList)
+ {
+ if (pList->empty())
+ {
+ pList->push_back( &i_rObject );
+ return true;
+ }
+ else
+ {
+ // this is only called from TryRegister now, so check
+ // if all elements in the list are deleted (in undo) or
+ // placeholders, then "steal" the id from them
+ if ( std::none_of(pList->begin(), pList->end(),
+ [](Metadatable* item)->bool {
+ return !(item->IsInUndo() || item->IsInClipboard());
+ } ) )
+ {
+ pList->insert(pList->begin(), &i_rObject );
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+ else
+ {
+ m_XmlIdMap.insert(::std::make_pair(i_rIdref, bContent
+ ? ::std::make_pair( XmlIdVector_t( 1, &i_rObject ), XmlIdVector_t() )
+ : ::std::make_pair( XmlIdVector_t(), XmlIdVector_t( 1, &i_rObject ) )));
+ return true;
+ }
+}
+
+
+// Document XML ID Registry
+
+
+XmlIdRegistryDocument::XmlIdRegistryDocument()
+ : m_pImpl( new XmlIdRegistry_Impl )
+{
+}
+
+static void
+removeLink(Metadatable* i_pObject)
+{
+ OSL_ENSURE(i_pObject, "null in list ???");
+ if (!i_pObject) return;
+ if (i_pObject->IsInClipboard())
+ {
+ MetadatableClipboard* pLink(
+ dynamic_cast<MetadatableClipboard*>( i_pObject ) );
+ OSL_ENSURE(pLink, "IsInClipboard, but no MetadatableClipboard ?");
+ if (pLink)
+ {
+ pLink->OriginNoLongerInBusinessAnymore();
+ }
+ }
+}
+
+XmlIdRegistryDocument::~XmlIdRegistryDocument()
+{
+ // notify all list elements that are actually in the clipboard
+ for (const auto& aXmlId : m_pImpl->m_XmlIdMap) {
+ for (auto aLink : aXmlId.second.first)
+ removeLink(aLink);
+ for (auto aLink : aXmlId.second.second)
+ removeLink(aLink);
+ }
+}
+
+bool
+XmlIdRegistryDocument::LookupXmlId(
+ const Metadatable& i_rObject,
+ OUString & o_rStream, OUString & o_rIdref) const
+{
+ return m_pImpl->LookupXmlId(i_rObject, o_rStream, o_rIdref);
+}
+
+Metadatable*
+XmlIdRegistryDocument::LookupElement(
+ const OUString & i_rStreamName,
+ const OUString & i_rIdref) const
+{
+ return m_pImpl->LookupElement(i_rStreamName, i_rIdref);
+}
+
+bool
+XmlIdRegistryDocument::TryRegisterMetadatable(Metadatable & i_rObject,
+ OUString const& i_rStreamName, OUString const& i_rIdref)
+{
+ SAL_INFO("sfx", "TryRegisterMetadatable: " << &i_rObject << " (" << i_rStreamName << "#" << i_rIdref << ")");
+
+ OSL_ENSURE(!dynamic_cast<MetadatableUndo*>(&i_rObject),
+ "TryRegisterMetadatable called for MetadatableUndo?");
+ OSL_ENSURE(!dynamic_cast<MetadatableClipboard*>(&i_rObject),
+ "TryRegisterMetadatable called for MetadatableClipboard?");
+
+ if (!isValidXmlId(i_rStreamName, i_rIdref))
+ {
+ throw lang::IllegalArgumentException("illegal XmlId", nullptr, 0);
+ }
+ if (i_rObject.IsInContent()
+ ? !isContentFile(i_rStreamName)
+ : !isStylesFile(i_rStreamName))
+ {
+ throw lang::IllegalArgumentException("illegal XmlId: wrong stream", nullptr, 0);
+ }
+
+ OUString old_path;
+ OUString old_idref;
+ m_pImpl->LookupXmlId(i_rObject, old_path, old_idref);
+ if (old_path == i_rStreamName && old_idref == i_rIdref)
+ {
+ return (m_pImpl->LookupElement(old_path, old_idref) == &i_rObject);
+ }
+ XmlIdMap_t::iterator old_id( m_pImpl->m_XmlIdMap.end() );
+ if (!old_idref.isEmpty())
+ {
+ old_id = m_pImpl->m_XmlIdMap.find(old_idref);
+ OSL_ENSURE(old_id != m_pImpl->m_XmlIdMap.end(), "old id not found");
+ }
+ if (m_pImpl->TryInsertMetadatable(i_rObject, i_rStreamName, i_rIdref))
+ {
+ rmIter(m_pImpl->m_XmlIdMap, old_id, old_path, i_rObject);
+ m_pImpl->m_XmlIdReverseMap[&i_rObject] =
+ ::std::make_pair(i_rStreamName, i_rIdref);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void
+XmlIdRegistryDocument::RegisterMetadatableAndCreateID(Metadatable & i_rObject)
+{
+ SAL_INFO("sfx", "RegisterMetadatableAndCreateID: " << &i_rObject);
+
+ OSL_ENSURE(!dynamic_cast<MetadatableUndo*>(&i_rObject),
+ "RegisterMetadatableAndCreateID called for MetadatableUndo?");
+ OSL_ENSURE(!dynamic_cast<MetadatableClipboard*>(&i_rObject),
+ "RegisterMetadatableAndCreateID called for MetadatableClipboard?");
+
+ const bool isInContent( i_rObject.IsInContent() );
+ const OUString stream(
+ isInContent ? s_content : s_styles );
+ // check if we have a latent xmlid, and if yes, remove it
+ OUString old_path;
+ OUString old_idref;
+ m_pImpl->LookupXmlId(i_rObject, old_path, old_idref);
+
+ XmlIdMap_t::iterator old_id( m_pImpl->m_XmlIdMap.end() );
+ if (!old_idref.isEmpty())
+ {
+ old_id = m_pImpl->m_XmlIdMap.find(old_idref);
+ OSL_ENSURE(old_id != m_pImpl->m_XmlIdMap.end(), "old id not found");
+ if (m_pImpl->LookupElement(old_path, old_idref) == &i_rObject)
+ {
+ return;
+ }
+ else
+ {
+ // remove latent xmlid
+ rmIter(m_pImpl->m_XmlIdMap, old_id, old_path, i_rObject);
+ }
+ }
+
+ // create id
+ const OUString id( create_id(m_pImpl->m_XmlIdMap) );
+ OSL_ENSURE(m_pImpl->m_XmlIdMap.find(id) == m_pImpl->m_XmlIdMap.end(),
+ "created id is in use");
+ m_pImpl->m_XmlIdMap.insert(::std::make_pair(id, isInContent
+ ? ::std::make_pair( XmlIdVector_t( 1, &i_rObject ), XmlIdVector_t() )
+ : ::std::make_pair( XmlIdVector_t(), XmlIdVector_t( 1, &i_rObject ) )));
+ m_pImpl->m_XmlIdReverseMap[&i_rObject] = ::std::make_pair(stream, id);
+}
+
+void XmlIdRegistryDocument::UnregisterMetadatable(const Metadatable& i_rObject)
+{
+ SAL_INFO("sfx", "UnregisterMetadatable: " << &i_rObject);
+
+ OUString path;
+ OUString idref;
+ if (!m_pImpl->LookupXmlId(i_rObject, path, idref))
+ {
+ OSL_FAIL("unregister: no xml id?");
+ return;
+ }
+ const XmlIdMap_t::iterator iter( m_pImpl->m_XmlIdMap.find(idref) );
+ if (iter != m_pImpl->m_XmlIdMap.end())
+ {
+ rmIter(m_pImpl->m_XmlIdMap, iter, path, i_rObject);
+ }
+}
+
+void XmlIdRegistryDocument::RemoveXmlIdForElement(const Metadatable& i_rObject)
+{
+ SAL_INFO("sfx", "RemoveXmlIdForElement: " << &i_rObject);
+
+ const XmlIdReverseMap_t::iterator iter(
+ m_pImpl->m_XmlIdReverseMap.find(&i_rObject) );
+ if (iter != m_pImpl->m_XmlIdReverseMap.end())
+ {
+ OSL_ENSURE(!iter->second.second.isEmpty(),
+ "null id in m_XmlIdReverseMap");
+ m_pImpl->m_XmlIdReverseMap.erase(iter);
+ }
+}
+
+
+void XmlIdRegistryDocument::RegisterCopy(Metadatable const& i_rSource,
+ Metadatable & i_rCopy, const bool i_bCopyPrecedesSource)
+{
+ SAL_INFO("sfx", "RegisterCopy: " << &i_rSource << " -> " << &i_rCopy << " (" << i_bCopyPrecedesSource << ")");
+
+ // potential sources: clipboard, undo array, splitNode
+ // assumption: stream change can only happen via clipboard, and is handled
+ // by Metadatable::RegisterAsCopyOf
+ OSL_ENSURE(i_rSource.IsInUndo() || i_rCopy.IsInUndo() ||
+ (i_rSource.IsInContent() == i_rCopy.IsInContent()),
+ "RegisterCopy: not in same stream?");
+
+ OUString path;
+ OUString idref;
+ if (!m_pImpl->LookupXmlId( i_rSource, path, idref ))
+ {
+ OSL_FAIL("no xml id?");
+ return;
+ }
+ XmlIdVector_t * pList ( m_pImpl->LookupElementVector(path, idref) );
+ OSL_ENSURE( ::std::find( pList->begin(), pList->end(), &i_rCopy )
+ == pList->end(), "copy already registered???");
+ XmlIdVector_t::iterator srcpos(
+ ::std::find( pList->begin(), pList->end(), &i_rSource ) );
+ OSL_ENSURE(srcpos != pList->end(), "source not in list???");
+ if (srcpos == pList->end())
+ {
+ return;
+ }
+ if (i_bCopyPrecedesSource)
+ {
+ pList->insert( srcpos, &i_rCopy );
+ }
+ else
+ {
+ // for undo push_back does not work! must insert right after source
+ pList->insert( ++srcpos, &i_rCopy );
+ }
+ m_pImpl->m_XmlIdReverseMap.insert(::std::make_pair(&i_rCopy,
+ ::std::make_pair(path, idref)));
+}
+
+std::shared_ptr<MetadatableUndo>
+XmlIdRegistryDocument::CreateUndo(Metadatable const& i_rObject)
+{
+ SAL_INFO("sfx", "CreateUndo: " << &i_rObject);
+
+ return std::make_shared<MetadatableUndo>(
+ i_rObject.IsInContent() );
+}
+
+/*
+i_rMerged is both a source and the target node of the merge
+i_rOther is the other source, and will be deleted after the merge
+
+dimensions: none|latent|actual empty|nonempty
+i_rMerged(1) i_rOther(2) result
+ *|empty *|empty => 1|2 (arbitrary)
+ *|empty *|nonempty => 2
+ *|nonempty *|empty => 1
+ none|nonempty none|nonempty => none
+ none|nonempty latent|nonempty => 2
+latent|nonempty none|nonempty => 1
+latent|nonempty latent|nonempty => 1|2
+ *|nonempty actual|nonempty => 2
+actual|nonempty *|nonempty => 1
+actual|nonempty actual|nonempty => 1|2
+*/
+void
+XmlIdRegistryDocument::JoinMetadatables(
+ Metadatable & i_rMerged, Metadatable const & i_rOther)
+{
+ SAL_INFO("sfx", "JoinMetadatables: " << &i_rMerged << " <- " << &i_rOther);
+
+ bool mergedOwnsRef;
+ OUString path;
+ OUString idref;
+ if (m_pImpl->LookupXmlId(i_rMerged, path, idref))
+ {
+ mergedOwnsRef = (m_pImpl->LookupElement(path, idref) == &i_rMerged);
+ }
+ else
+ {
+ OSL_FAIL("JoinMetadatables: no xmlid?");
+ return;
+ }
+ if (!mergedOwnsRef)
+ {
+ i_rMerged.RemoveMetadataReference();
+ i_rMerged.RegisterAsCopyOf(i_rOther, true);
+ return;
+ }
+ // other cases: merged has actual ref and is nonempty,
+ // other has latent/actual ref and is nonempty: other loses => nothing to do
+}
+
+
+// Clipboard XML ID Registry (_Impl)
+
+namespace {
+
+struct RMapEntry
+{
+ RMapEntry() {}
+ RMapEntry(OUString i_aStream,
+ OUString i_aXmlId,
+ std::shared_ptr<MetadatableClipboard> i_pLink
+ = std::shared_ptr<MetadatableClipboard>())
+ : m_Stream(std::move(i_aStream)), m_XmlId(std::move(i_aXmlId)), m_xLink(std::move(i_pLink))
+ {}
+ OUString m_Stream;
+ OUString m_XmlId;
+ // this would have been an auto_ptr, if only that would have compiled...
+ std::shared_ptr<MetadatableClipboard> m_xLink;
+};
+
+}
+
+/// element -> (stream name, idref, source)
+typedef std::unordered_map< const Metadatable*,
+ struct RMapEntry,
+ PtrHash<Metadatable> >
+ ClipboardXmlIdReverseMap_t;
+
+/// Idref -> (content.xml element, styles.xml element)
+typedef std::unordered_map< OUString,
+ ::std::pair< Metadatable*, Metadatable* > >
+ ClipboardXmlIdMap_t;
+
+struct XmlIdRegistryClipboard::XmlIdRegistry_Impl
+{
+ XmlIdRegistry_Impl() {}
+
+ bool TryInsertMetadatable(Metadatable& i_xObject,
+ std::u16string_view i_rStream, const OUString & i_rIdref);
+
+ bool LookupXmlId(const Metadatable& i_xObject,
+ OUString & o_rStream, OUString & o_rIdref,
+ MetadatableClipboard const* &o_rpLink) const;
+
+ Metadatable* LookupElement(std::u16string_view i_rStreamName,
+ const OUString & i_rIdref) const;
+
+ Metadatable* const* LookupEntry(std::u16string_view i_rStreamName,
+ const OUString & i_rIdref) const;
+
+ ClipboardXmlIdMap_t m_XmlIdMap;
+ ClipboardXmlIdReverseMap_t m_XmlIdReverseMap;
+};
+
+
+static void
+rmIter(ClipboardXmlIdMap_t & i_rXmlIdMap,
+ ClipboardXmlIdMap_t::iterator const& i_rIter,
+ std::u16string_view i_rStream, Metadatable const& i_rObject)
+{
+ if (i_rIter == i_rXmlIdMap.end())
+ return;
+
+ Metadatable *& rMeta = isContentFile(i_rStream)
+ ? i_rIter->second.first : i_rIter->second.second;
+ if (rMeta == &i_rObject)
+ {
+ rMeta = nullptr;
+ }
+ if (!i_rIter->second.first && !i_rIter->second.second)
+ {
+ i_rXmlIdMap.erase(i_rIter);
+ }
+}
+
+
+Metadatable* const*
+XmlIdRegistryClipboard::XmlIdRegistry_Impl::LookupEntry(
+ std::u16string_view i_rStreamName,
+ const OUString & i_rIdref) const
+{
+ if (!isValidXmlId(i_rStreamName, i_rIdref))
+ {
+ throw lang::IllegalArgumentException("illegal XmlId", nullptr, 0);
+ }
+
+ const ClipboardXmlIdMap_t::const_iterator iter( m_XmlIdMap.find(i_rIdref) );
+ if (iter != m_XmlIdMap.end())
+ {
+ OSL_ENSURE(iter->second.first || iter->second.second,
+ "null entry in m_XmlIdMap");
+ return (isContentFile(i_rStreamName))
+ ? &iter->second.first
+ : &iter->second.second;
+ }
+ else
+ {
+ return nullptr;
+ }
+}
+
+Metadatable*
+XmlIdRegistryClipboard::XmlIdRegistry_Impl::LookupElement(
+ std::u16string_view i_rStreamName,
+ const OUString & i_rIdref) const
+{
+ Metadatable * const * ppEntry = LookupEntry(i_rStreamName, i_rIdref);
+ return ppEntry ? *ppEntry : nullptr;
+}
+
+bool
+XmlIdRegistryClipboard::XmlIdRegistry_Impl::LookupXmlId(
+ const Metadatable& i_rObject,
+ OUString & o_rStream, OUString & o_rIdref,
+ MetadatableClipboard const* &o_rpLink) const
+{
+ const ClipboardXmlIdReverseMap_t::const_iterator iter(
+ m_XmlIdReverseMap.find(&i_rObject) );
+ if (iter != m_XmlIdReverseMap.end())
+ {
+ OSL_ENSURE(!iter->second.m_Stream.isEmpty(),
+ "null stream in m_XmlIdReverseMap");
+ OSL_ENSURE(!iter->second.m_XmlId.isEmpty(),
+ "null id in m_XmlIdReverseMap");
+ o_rStream = iter->second.m_Stream;
+ o_rIdref = iter->second.m_XmlId;
+ o_rpLink = iter->second.m_xLink.get();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool
+XmlIdRegistryClipboard::XmlIdRegistry_Impl::TryInsertMetadatable(
+ Metadatable & i_rObject,
+ std::u16string_view i_rStreamName, const OUString & i_rIdref)
+{
+ bool bContent( isContentFile(i_rStreamName) );
+ OSL_ENSURE(isContentFile(i_rStreamName) || isStylesFile(i_rStreamName),
+ "invalid stream");
+
+ Metadatable ** ppEntry = const_cast<Metadatable**>(LookupEntry(i_rStreamName, i_rIdref));
+ if (ppEntry)
+ {
+ if (*ppEntry)
+ {
+ return false;
+ }
+ else
+ {
+ *ppEntry = &i_rObject;
+ return true;
+ }
+ }
+ else
+ {
+ m_XmlIdMap.insert(::std::make_pair(i_rIdref, bContent
+ ? ::std::make_pair( &i_rObject, static_cast<Metadatable*>(nullptr) )
+ : ::std::make_pair( static_cast<Metadatable*>(nullptr), &i_rObject )));
+ return true;
+ }
+}
+
+
+// Clipboard XML ID Registry
+
+
+XmlIdRegistryClipboard::XmlIdRegistryClipboard()
+ : m_pImpl( new XmlIdRegistry_Impl )
+{
+}
+
+bool
+XmlIdRegistryClipboard::LookupXmlId(
+ const Metadatable& i_rObject,
+ OUString & o_rStream, OUString & o_rIdref) const
+{
+ const MetadatableClipboard * pLink;
+ return m_pImpl->LookupXmlId(i_rObject, o_rStream, o_rIdref, pLink);
+}
+
+Metadatable*
+XmlIdRegistryClipboard::LookupElement(
+ const OUString & i_rStreamName,
+ const OUString & i_rIdref) const
+{
+ return m_pImpl->LookupElement(i_rStreamName, i_rIdref);
+}
+
+bool
+XmlIdRegistryClipboard::TryRegisterMetadatable(Metadatable & i_rObject,
+ OUString const& i_rStreamName, OUString const& i_rIdref)
+{
+ SAL_INFO("sfx", "TryRegisterMetadatable: " << &i_rObject << " (" << i_rStreamName << "#" << i_rIdref <<")");
+
+ OSL_ENSURE(!dynamic_cast<MetadatableUndo*>(&i_rObject),
+ "TryRegisterMetadatable called for MetadatableUndo?");
+ OSL_ENSURE(!dynamic_cast<MetadatableClipboard*>(&i_rObject),
+ "TryRegisterMetadatable called for MetadatableClipboard?");
+
+ if (!isValidXmlId(i_rStreamName, i_rIdref))
+ {
+ throw lang::IllegalArgumentException("illegal XmlId", nullptr, 0);
+ }
+ if (i_rObject.IsInContent()
+ ? !isContentFile(i_rStreamName)
+ : !isStylesFile(i_rStreamName))
+ {
+ throw lang::IllegalArgumentException("illegal XmlId: wrong stream", nullptr, 0);
+ }
+
+ OUString old_path;
+ OUString old_idref;
+ const MetadatableClipboard * pLink;
+ m_pImpl->LookupXmlId(i_rObject, old_path, old_idref, pLink);
+ if (old_path == i_rStreamName && old_idref == i_rIdref)
+ {
+ return (m_pImpl->LookupElement(old_path, old_idref) == &i_rObject);
+ }
+ ClipboardXmlIdMap_t::iterator old_id( m_pImpl->m_XmlIdMap.end() );
+ if (!old_idref.isEmpty())
+ {
+ old_id = m_pImpl->m_XmlIdMap.find(old_idref);
+ OSL_ENSURE(old_id != m_pImpl->m_XmlIdMap.end(), "old id not found");
+ }
+ if (m_pImpl->TryInsertMetadatable(i_rObject, i_rStreamName, i_rIdref))
+ {
+ rmIter(m_pImpl->m_XmlIdMap, old_id, old_path, i_rObject);
+ m_pImpl->m_XmlIdReverseMap[&i_rObject] =
+ RMapEntry(i_rStreamName, i_rIdref);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void
+XmlIdRegistryClipboard::RegisterMetadatableAndCreateID(Metadatable & i_rObject)
+{
+ SAL_INFO("sfx", "RegisterMetadatableAndCreateID: " << &i_rObject);
+
+ OSL_ENSURE(!dynamic_cast<MetadatableUndo*>(&i_rObject),
+ "RegisterMetadatableAndCreateID called for MetadatableUndo?");
+ OSL_ENSURE(!dynamic_cast<MetadatableClipboard*>(&i_rObject),
+ "RegisterMetadatableAndCreateID called for MetadatableClipboard?");
+
+ bool isInContent( i_rObject.IsInContent() );
+ OUString stream(
+ isInContent ? s_content : s_styles );
+
+ OUString old_path;
+ OUString old_idref;
+ LookupXmlId(i_rObject, old_path, old_idref);
+ if (!old_idref.isEmpty() &&
+ (m_pImpl->LookupElement(old_path, old_idref) == &i_rObject))
+ {
+ return;
+ }
+
+ // create id
+ const OUString id( create_id(m_pImpl->m_XmlIdMap) );
+ OSL_ENSURE(m_pImpl->m_XmlIdMap.find(id) == m_pImpl->m_XmlIdMap.end(),
+ "created id is in use");
+ m_pImpl->m_XmlIdMap.insert(::std::make_pair(id, isInContent
+ ? ::std::make_pair( &i_rObject, static_cast<Metadatable*>(nullptr) )
+ : ::std::make_pair( static_cast<Metadatable*>(nullptr), &i_rObject )));
+ // N.B.: if i_rObject had a latent XmlId, then we implicitly delete the
+ // MetadatableClipboard and thus the latent XmlId here
+ m_pImpl->m_XmlIdReverseMap[&i_rObject] = RMapEntry(stream, id);
+}
+
+void XmlIdRegistryClipboard::UnregisterMetadatable(const Metadatable& i_rObject)
+{
+ SAL_INFO("sfx", "UnregisterMetadatable: " << &i_rObject);
+
+ OUString path;
+ OUString idref;
+ const MetadatableClipboard * pLink;
+ if (!m_pImpl->LookupXmlId(i_rObject, path, idref, pLink))
+ {
+ OSL_FAIL("unregister: no xml id?");
+ return;
+ }
+ const ClipboardXmlIdMap_t::iterator iter( m_pImpl->m_XmlIdMap.find(idref) );
+ if (iter != m_pImpl->m_XmlIdMap.end())
+ {
+ rmIter(m_pImpl->m_XmlIdMap, iter, path, i_rObject);
+ }
+}
+
+
+void XmlIdRegistryClipboard::RemoveXmlIdForElement(const Metadatable& i_rObject)
+{
+ SAL_INFO("sfx", "RemoveXmlIdForElement: " << &i_rObject);
+
+ ClipboardXmlIdReverseMap_t::iterator iter(
+ m_pImpl->m_XmlIdReverseMap.find(&i_rObject) );
+ if (iter != m_pImpl->m_XmlIdReverseMap.end())
+ {
+ OSL_ENSURE(!iter->second.m_XmlId.isEmpty(),
+ "null id in m_XmlIdReverseMap");
+ m_pImpl->m_XmlIdReverseMap.erase(iter);
+ }
+}
+
+
+std::shared_ptr<MetadatableClipboard>
+XmlIdRegistryClipboard::CreateClipboard(const bool i_isInContent)
+{
+ SAL_INFO("sfx", "CreateClipboard:");
+
+ return std::make_shared<MetadatableClipboard>(
+ i_isInContent );
+}
+
+MetadatableClipboard &
+XmlIdRegistryClipboard::RegisterCopyClipboard(Metadatable & i_rCopy,
+ beans::StringPair const & i_rReference,
+ const bool i_isLatent)
+{
+ SAL_INFO("sfx", "RegisterCopyClipboard: " << &i_rCopy
+ << " -> (" << i_rReference.First << "#" << i_rReference.Second << ") (" << i_isLatent << ")");
+
+ // N.B.: when copying to the clipboard, the selection is always inserted
+ // into the body, even if the source is a header/footer!
+ // so we do not check whether the stream is right in this function
+
+ if (!isValidXmlId(i_rReference.First, i_rReference.Second))
+ {
+ throw lang::IllegalArgumentException("illegal XmlId", nullptr, 0);
+ }
+
+ if (!i_isLatent)
+ {
+ // this should succeed assuming clipboard has a single source document
+ const bool success( m_pImpl->TryInsertMetadatable(i_rCopy,
+ i_rReference.First, i_rReference.Second) );
+ OSL_ENSURE(success, "RegisterCopyClipboard: TryInsert failed?");
+ }
+ const std::shared_ptr<MetadatableClipboard> xLink(
+ CreateClipboard( isContentFile(i_rReference.First)) );
+ m_pImpl->m_XmlIdReverseMap.insert(::std::make_pair(&i_rCopy,
+ RMapEntry(i_rReference.First, i_rReference.Second, xLink)));
+ return *xLink;
+}
+
+MetadatableClipboard const*
+XmlIdRegistryClipboard::SourceLink(Metadatable const& i_rObject)
+{
+ OUString path;
+ OUString idref;
+ const MetadatableClipboard * pLink( nullptr );
+ m_pImpl->LookupXmlId(i_rObject, path, idref, pLink);
+ return pLink;
+}
+
+
+// Metadatable mixin
+
+
+Metadatable::~Metadatable()
+{
+ RemoveMetadataReference();
+}
+
+void Metadatable::RemoveMetadataReference()
+{
+ try
+ {
+ if (m_pReg)
+ {
+ m_pReg->UnregisterMetadatable( *this );
+ m_pReg->RemoveXmlIdForElement( *this );
+ m_pReg = nullptr;
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "Metadatable::RemoveMetadataReference");
+ }
+}
+
+// css::rdf::XMetadatable:
+beans::StringPair
+Metadatable::GetMetadataReference() const
+{
+ if (m_pReg)
+ {
+ return m_pReg->GetXmlIdForElement(*this);
+ }
+ return beans::StringPair();
+}
+
+void Metadatable::SetMetadataReference( const css::beans::StringPair & i_rReference)
+{
+ if (i_rReference.Second.isEmpty())
+ {
+ RemoveMetadataReference();
+ }
+ else
+ {
+ OUString streamName( i_rReference.First );
+ if (streamName.isEmpty())
+ {
+ // handle empty stream name as auto-detect.
+ // necessary for importing flat file format.
+ streamName = IsInContent() ? s_content : s_styles;
+ }
+ XmlIdRegistry & rReg( dynamic_cast<XmlIdRegistry&>( GetRegistry() ) );
+ if (!rReg.TryRegisterMetadatable(*this, streamName, i_rReference.Second))
+ {
+ throw lang::IllegalArgumentException(
+ "Metadatable::SetMetadataReference: argument is invalid", /*this*/nullptr, 0);
+ }
+
+ m_pReg = &rReg;
+ }
+}
+
+void Metadatable::EnsureMetadataReference()
+{
+ XmlIdRegistry& rReg(
+ m_pReg ? *m_pReg : dynamic_cast<XmlIdRegistry&>( GetRegistry() ) );
+ rReg.RegisterMetadatableAndCreateID( *this );
+ m_pReg = &rReg;
+}
+
+static const ::sfx2::IXmlIdRegistry& GetRegistryConst(Metadatable const& i_rObject)
+{
+ return const_cast< Metadatable& >( i_rObject ).GetRegistry();
+}
+
+void
+Metadatable::RegisterAsCopyOf(Metadatable const & i_rSource,
+ const bool i_bCopyPrecedesSource)
+{
+ OSL_ENSURE(typeid(*this) == typeid(i_rSource)
+ || typeid(i_rSource) == typeid(MetadatableUndo)
+ || typeid(*this) == typeid(MetadatableUndo)
+ || typeid(i_rSource) == typeid(MetadatableClipboard)
+ || typeid(*this) == typeid(MetadatableClipboard),
+ "RegisterAsCopyOf element with different class?");
+ OSL_ENSURE(!m_pReg, "RegisterAsCopyOf called on element with XmlId?");
+
+ if (m_pReg)
+ {
+ RemoveMetadataReference();
+ }
+
+ try
+ {
+ if (i_rSource.m_pReg)
+ {
+ XmlIdRegistry & rReg(
+ dynamic_cast<XmlIdRegistry&>( GetRegistry() ) );
+ if (i_rSource.m_pReg == &rReg)
+ {
+ OSL_ENSURE(!IsInClipboard(),
+ "RegisterAsCopy: both in clipboard?");
+ if (!IsInClipboard())
+ {
+ XmlIdRegistryDocument & rRegDoc(
+ dynamic_cast<XmlIdRegistryDocument&>( rReg ) );
+ rRegDoc.RegisterCopy(i_rSource, *this,
+ i_bCopyPrecedesSource);
+ m_pReg = &rRegDoc;
+ }
+ return;
+ }
+ // source is in different document
+ XmlIdRegistryDocument * pRegDoc(
+ dynamic_cast<XmlIdRegistryDocument *>(&rReg) );
+ XmlIdRegistryClipboard * pRegClp(
+ dynamic_cast<XmlIdRegistryClipboard*>(&rReg) );
+
+ if (pRegClp)
+ {
+ beans::StringPair SourceRef(
+ i_rSource.m_pReg->GetXmlIdForElement(i_rSource) );
+ bool isLatent( SourceRef.Second.isEmpty() );
+ XmlIdRegistryDocument * pSourceRegDoc(
+ dynamic_cast<XmlIdRegistryDocument*>(i_rSource.m_pReg) );
+ OSL_ENSURE(pSourceRegDoc, "RegisterAsCopyOf: 2 clipboards?");
+ if (!pSourceRegDoc) return;
+ // this is a copy _to_ the clipboard
+ if (isLatent)
+ {
+ pSourceRegDoc->LookupXmlId(i_rSource,
+ SourceRef.First, SourceRef.Second);
+ }
+ Metadatable & rLink(
+ pRegClp->RegisterCopyClipboard(*this, SourceRef, isLatent));
+ m_pReg = pRegClp;
+ // register as copy in the non-clipboard registry
+ pSourceRegDoc->RegisterCopy(i_rSource, rLink,
+ false); // i_bCopyPrecedesSource);
+ rLink.m_pReg = pSourceRegDoc;
+ }
+ else if (pRegDoc)
+ {
+ XmlIdRegistryClipboard * pSourceRegClp(
+ dynamic_cast<XmlIdRegistryClipboard*>(i_rSource.m_pReg) );
+ OSL_ENSURE(pSourceRegClp,
+ "RegisterAsCopyOf: 2 non-clipboards?");
+ if (!pSourceRegClp) return;
+ const MetadatableClipboard * pLink(
+ pSourceRegClp->SourceLink(i_rSource) );
+ // may happen if src got its id via UNO call
+ if (!pLink) return;
+ // only register copy if clipboard content is from this SwDoc!
+ if (&GetRegistryConst(*pLink) == pRegDoc)
+ {
+ // this is a copy _from_ the clipboard; check if the
+ // element is still in the same stream
+ // N.B.: we check the stream of pLink, not of i_rSource!
+ bool srcInContent( pLink->IsInContent() );
+ bool tgtInContent( IsInContent() );
+ if (srcInContent == tgtInContent)
+ {
+ pRegDoc->RegisterCopy(*pLink, *this,
+ true); // i_bCopyPrecedesSource);
+ m_pReg = pRegDoc;
+ }
+ // otherwise: stream change! do not register!
+ }
+ }
+ else
+ {
+ OSL_FAIL("neither RegDoc nor RegClp cannot happen");
+ }
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "Metadatable::RegisterAsCopyOf");
+ }
+}
+
+std::shared_ptr<MetadatableUndo> Metadatable::CreateUndo() const
+{
+ OSL_ENSURE(!IsInUndo(), "CreateUndo called for object in undo?");
+ OSL_ENSURE(!IsInClipboard(), "CreateUndo called for object in clipboard?");
+ try
+ {
+ if (!IsInClipboard() && !IsInUndo() && m_pReg)
+ {
+ XmlIdRegistryDocument * pRegDoc(
+ dynamic_cast<XmlIdRegistryDocument*>( m_pReg ) );
+ assert(pRegDoc);
+ std::shared_ptr<MetadatableUndo> xUndo(
+ sfx2::XmlIdRegistryDocument::CreateUndo(*this) );
+ pRegDoc->RegisterCopy(*this, *xUndo, false);
+ xUndo->m_pReg = pRegDoc;
+ return xUndo;
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "Metadatable::CreateUndo");
+ }
+ return std::shared_ptr<MetadatableUndo>();
+}
+
+std::shared_ptr<MetadatableUndo> Metadatable::CreateUndoForDelete()
+{
+ std::shared_ptr<MetadatableUndo> const xUndo( CreateUndo() );
+ RemoveMetadataReference();
+ return xUndo;
+}
+
+void Metadatable::RestoreMetadata(
+ std::shared_ptr<MetadatableUndo> const& i_pUndo)
+{
+ OSL_ENSURE(!IsInUndo(), "RestoreMetadata called for object in undo?");
+ OSL_ENSURE(!IsInClipboard(),
+ "RestoreMetadata called for object in clipboard?");
+ if (IsInClipboard() || IsInUndo()) return;
+ RemoveMetadataReference();
+ if (i_pUndo)
+ {
+ RegisterAsCopyOf(*i_pUndo, true);
+ }
+}
+
+void
+Metadatable::JoinMetadatable(Metadatable const & i_rOther,
+ const bool i_isMergedEmpty, const bool i_isOtherEmpty)
+{
+ OSL_ENSURE(!IsInUndo(), "JoinMetadatables called for object in undo?");
+ OSL_ENSURE(!IsInClipboard(),
+ "JoinMetadatables called for object in clipboard?");
+ if (IsInClipboard() || IsInUndo()) return;
+
+ if (i_isOtherEmpty && !i_isMergedEmpty)
+ {
+ // other is empty, thus loses => nothing to do
+ return;
+ }
+ if (i_isMergedEmpty && !i_isOtherEmpty)
+ {
+ RemoveMetadataReference();
+ RegisterAsCopyOf(i_rOther, true);
+ return;
+ }
+
+ if (!i_rOther.m_pReg)
+ {
+ // other doesn't have xmlid, thus loses => nothing to do
+ return;
+ }
+ if (!m_pReg)
+ {
+ RegisterAsCopyOf(i_rOther, true);
+ // assumption: i_rOther will be deleted, so don't unregister it here
+ return;
+ }
+ try
+ {
+ XmlIdRegistryDocument * pRegDoc(
+ dynamic_cast<XmlIdRegistryDocument*>( m_pReg ) );
+ OSL_ENSURE(pRegDoc, "JoinMetadatable: no pRegDoc?");
+ if (pRegDoc)
+ {
+ pRegDoc->JoinMetadatables(*this, i_rOther);
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "Metadatable::JoinMetadatable");
+ }
+}
+
+
+// XMetadatable mixin
+
+// css::rdf::XNode:
+OUString SAL_CALL MetadatableMixin::getStringValue()
+{
+ return getNamespace() + getLocalName();
+}
+
+// css::rdf::XURI:
+OUString SAL_CALL MetadatableMixin::getLocalName()
+{
+ SolarMutexGuard aGuard;
+ beans::StringPair mdref( getMetadataReference() );
+ if (mdref.Second.isEmpty())
+ {
+ ensureMetadataReference(); // N.B.: side effect!
+ mdref = getMetadataReference();
+ }
+ return mdref.First + "#" + mdref.Second;
+}
+
+OUString SAL_CALL MetadatableMixin::getNamespace()
+{
+ SolarMutexGuard aGuard;
+ const uno::Reference< frame::XModel > xModel( GetModel() );
+ const uno::Reference< rdf::XURI > xDMA( xModel, uno::UNO_QUERY_THROW );
+ return xDMA->getStringValue();
+}
+
+// css::rdf::XMetadatable:
+beans::StringPair SAL_CALL
+MetadatableMixin::getMetadataReference()
+{
+ SolarMutexGuard aGuard;
+
+ Metadatable *const pObject( GetCoreObject() );
+ if (!pObject)
+ {
+ throw uno::RuntimeException(
+ "MetadatableMixin: cannot get core object; not inserted?",
+ *this);
+ }
+ return pObject->GetMetadataReference();
+}
+
+void SAL_CALL
+MetadatableMixin::setMetadataReference(
+ const beans::StringPair & i_rReference)
+{
+ SolarMutexGuard aGuard;
+
+ Metadatable *const pObject( GetCoreObject() );
+ if (!pObject)
+ {
+ throw uno::RuntimeException(
+ "MetadatableMixin: cannot get core object; not inserted?",
+ *this);
+ }
+ return pObject->SetMetadataReference(i_rReference);
+}
+
+void SAL_CALL MetadatableMixin::ensureMetadataReference()
+{
+ SolarMutexGuard aGuard;
+
+ Metadatable *const pObject( GetCoreObject() );
+ if (!pObject)
+ {
+ throw uno::RuntimeException(
+ "MetadatableMixin: cannot get core object; not inserted?",
+ *this);
+ }
+ return pObject->EnsureMetadataReference();
+}
+
+} // namespace sfx2
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/QuerySaveDocument.cxx b/sfx2/source/doc/QuerySaveDocument.cxx
new file mode 100644
index 0000000000..4abc612dcf
--- /dev/null
+++ b/sfx2/source/doc/QuerySaveDocument.cxx
@@ -0,0 +1,39 @@
+/* -*- 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 <sfx2/QuerySaveDocument.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+
+short ExecuteQuerySaveDocument(weld::Widget* _pParent, std::u16string_view _rTitle)
+{
+ if (Application::IsHeadlessModeEnabled() || getenv("SAL_NO_QUERYSAVE"))
+ {
+ // don't block Desktop::terminate() if there's no user to ask
+ return RET_NO;
+ }
+
+ std::unique_ptr<weld::Builder> xBuilder(
+ Application::CreateBuilder(_pParent, "sfx/ui/querysavedialog.ui"));
+ std::unique_ptr<weld::MessageDialog> xQBox(xBuilder->weld_message_dialog("QuerySaveDialog"));
+ xQBox->set_primary_text(xQBox->get_primary_text().replaceFirst("$(DOC)", _rTitle));
+ return xQBox->run();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/SfxDocumentMetaData.cxx b/sfx2/source/doc/SfxDocumentMetaData.cxx
new file mode 100644
index 0000000000..35f769dd14
--- /dev/null
+++ b/sfx2/source/doc/SfxDocumentMetaData.cxx
@@ -0,0 +1,2347 @@
+/* -*- 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 <sal/config.h>
+#include <sal/log.hxx>
+
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/document/XDocumentProperties.hpp>
+#include <com/sun/star/document/XDocumentProperties2.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/util/XCloneable.hpp>
+#include <com/sun/star/util/XModifiable.hpp>
+#include <com/sun/star/xml/sax/SAXException.hpp>
+#include <com/sun/star/xml/sax/XSAXSerializable.hpp>
+
+#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
+#include <com/sun/star/lang/EventObject.hpp>
+#include <com/sun/star/beans/IllegalTypeException.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/PropertyAttribute.hpp>
+#include <com/sun/star/task/ErrorCodeIOException.hpp>
+#include <com/sun/star/embed/XStorage.hpp>
+#include <com/sun/star/embed/XTransactedObject.hpp>
+#include <com/sun/star/embed/ElementModes.hpp>
+#include <com/sun/star/io/WrongFormatException.hpp>
+#include <com/sun/star/io/XStream.hpp>
+#include <com/sun/star/document/XImporter.hpp>
+#include <com/sun/star/document/XExporter.hpp>
+#include <com/sun/star/document/XFilter.hpp>
+#include <com/sun/star/xml/sax/Writer.hpp>
+#include <com/sun/star/xml/sax/Parser.hpp>
+#include <com/sun/star/xml/sax/XFastParser.hpp>
+#include <com/sun/star/xml/dom/DOMException.hpp>
+#include <com/sun/star/xml/dom/XDocument.hpp>
+#include <com/sun/star/xml/dom/XElement.hpp>
+#include <com/sun/star/xml/dom/DocumentBuilder.hpp>
+#include <com/sun/star/xml/dom/NodeType.hpp>
+#include <com/sun/star/xml/xpath/XPathAPI.hpp>
+#include <com/sun/star/util/Date.hpp>
+#include <com/sun/star/util/Time.hpp>
+#include <com/sun/star/util/DateWithTimezone.hpp>
+#include <com/sun/star/util/DateTimeWithTimezone.hpp>
+#include <com/sun/star/util/Duration.hpp>
+
+#include <rtl/ustrbuf.hxx>
+#include <tools/datetime.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <osl/mutex.hxx>
+#include <comphelper/fileformat.h>
+#include <cppuhelper/basemutex.hxx>
+#include <comphelper/interfacecontainer3.hxx>
+#include <comphelper/storagehelper.hxx>
+#include <unotools/mediadescriptor.hxx>
+#include <comphelper/sequence.hxx>
+#include <sot/storage.hxx>
+#include <sfx2/docfile.hxx>
+#include <sax/tools/converter.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <optional>
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+#include <map>
+#include <cstring>
+#include <limits>
+
+
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <com/sun/star/document/XCompatWriterDocProperties.hpp>
+#include <com/sun/star/beans/PropertyBag.hpp>
+
+/**
+ * This file contains the implementation of the service
+ * com.sun.star.document.DocumentProperties.
+ * This service enables access to the meta-data stored in documents.
+ * Currently, this service only handles documents in ODF format.
+ *
+ * The implementation uses an XML DOM to store the properties.
+ * This approach was taken because it allows for preserving arbitrary XML data
+ * in loaded documents, which will be stored unmodified when saving the
+ * document again.
+ *
+ * Upon access, some properties are directly read from and updated in the DOM.
+ * Exception: it seems impossible to get notified upon addition of a property
+ * to a com.sun.star.beans.PropertyBag, which is used for storing user-defined
+ * properties; because of this, user-defined properties are updated in the
+ * XML DOM only when storing the document.
+ * Exception 2: when setting certain properties which correspond to attributes
+ * in the XML DOM, we want to remove the corresponding XML element. Detecting
+ * this condition can get messy, so we store all such properties as members,
+ * and update the DOM tree only when storing the document (in
+ * <method>updateUserDefinedAndAttributes</method>).
+ *
+ */
+
+/// anonymous implementation namespace
+namespace {
+
+/// a list of attribute-lists, where attribute means name and content
+typedef std::vector<std::vector<std::pair<OUString, OUString> > >
+ AttrVector;
+
+typedef ::cppu::WeakComponentImplHelper<
+ css::lang::XServiceInfo,
+ css::document::XDocumentProperties2,
+ css::lang::XInitialization,
+ css::util::XCloneable,
+ css::util::XModifiable,
+ css::xml::sax::XSAXSerializable>
+ SfxDocumentMetaData_Base;
+
+class SfxDocumentMetaData:
+ private ::cppu::BaseMutex,
+ public SfxDocumentMetaData_Base
+{
+public:
+ explicit SfxDocumentMetaData(
+ css::uno::Reference< css::uno::XComponentContext > const & context);
+ SfxDocumentMetaData(const SfxDocumentMetaData&) = delete;
+ SfxDocumentMetaData& operator=(const SfxDocumentMetaData&) = delete;
+
+ // css::lang::XServiceInfo:
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService(
+ const OUString & ServiceName) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL
+ getSupportedServiceNames() override;
+
+ // css::lang::XComponent:
+ virtual void SAL_CALL dispose() override;
+
+ // css::document::XDocumentProperties:
+ virtual OUString SAL_CALL getAuthor() override;
+ virtual void SAL_CALL setAuthor(const OUString & the_value) override;
+ virtual OUString SAL_CALL getGenerator() override;
+ virtual void SAL_CALL setGenerator(const OUString & the_value) override;
+ virtual css::util::DateTime SAL_CALL getCreationDate() override;
+ virtual void SAL_CALL setCreationDate(const css::util::DateTime & the_value) override;
+ virtual OUString SAL_CALL getTitle() override;
+ virtual void SAL_CALL setTitle(const OUString & the_value) override;
+ virtual OUString SAL_CALL getSubject() override;
+ virtual void SAL_CALL setSubject(const OUString & the_value) override;
+ virtual OUString SAL_CALL getDescription() override;
+ virtual void SAL_CALL setDescription(const OUString & the_value) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getKeywords() override;
+ virtual void SAL_CALL setKeywords(
+ const css::uno::Sequence< OUString > & the_value) override;
+ virtual css::lang::Locale SAL_CALL getLanguage() override;
+ virtual void SAL_CALL setLanguage(const css::lang::Locale & the_value) override;
+ virtual OUString SAL_CALL getModifiedBy() override;
+ virtual void SAL_CALL setModifiedBy(const OUString & the_value) override;
+ virtual css::util::DateTime SAL_CALL getModificationDate() override;
+ virtual void SAL_CALL setModificationDate(
+ const css::util::DateTime & the_value) override;
+ virtual OUString SAL_CALL getPrintedBy() override;
+ virtual void SAL_CALL setPrintedBy(const OUString & the_value) override;
+ virtual css::util::DateTime SAL_CALL getPrintDate() override;
+ virtual void SAL_CALL setPrintDate(const css::util::DateTime & the_value) override;
+ virtual OUString SAL_CALL getTemplateName() override;
+ virtual void SAL_CALL setTemplateName(const OUString & the_value) override;
+ virtual OUString SAL_CALL getTemplateURL() override;
+ virtual void SAL_CALL setTemplateURL(const OUString & the_value) override;
+ virtual css::util::DateTime SAL_CALL getTemplateDate() override;
+ virtual void SAL_CALL setTemplateDate(const css::util::DateTime & the_value) override;
+ virtual OUString SAL_CALL getAutoloadURL() override;
+ virtual void SAL_CALL setAutoloadURL(const OUString & the_value) override;
+ virtual ::sal_Int32 SAL_CALL getAutoloadSecs() override;
+ virtual void SAL_CALL setAutoloadSecs(::sal_Int32 the_value) override;
+ virtual OUString SAL_CALL getDefaultTarget() override;
+ virtual void SAL_CALL setDefaultTarget(const OUString & the_value) override;
+ virtual css::uno::Sequence< css::beans::NamedValue > SAL_CALL
+ getDocumentStatistics() override;
+ virtual void SAL_CALL setDocumentStatistics(
+ const css::uno::Sequence< css::beans::NamedValue > & the_value) override;
+ virtual ::sal_Int16 SAL_CALL getEditingCycles() override;
+ virtual void SAL_CALL setEditingCycles(::sal_Int16 the_value) override;
+ virtual ::sal_Int32 SAL_CALL getEditingDuration() override;
+ virtual void SAL_CALL setEditingDuration(::sal_Int32 the_value) override;
+ virtual void SAL_CALL resetUserData(const OUString & the_value) override;
+ virtual css::uno::Reference< css::beans::XPropertyContainer > SAL_CALL
+ getUserDefinedProperties() override;
+ virtual void SAL_CALL loadFromStorage(
+ const css::uno::Reference< css::embed::XStorage > & Storage,
+ const css::uno::Sequence< css::beans::PropertyValue > & Medium) override;
+ virtual void SAL_CALL loadFromMedium(const OUString & URL,
+ const css::uno::Sequence< css::beans::PropertyValue > & Medium) override;
+ virtual void SAL_CALL storeToStorage(
+ const css::uno::Reference< css::embed::XStorage > & Storage,
+ const css::uno::Sequence< css::beans::PropertyValue > & Medium) override;
+ virtual void SAL_CALL storeToMedium(const OUString & URL,
+ const css::uno::Sequence< css::beans::PropertyValue > & Medium) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getContributor() override;
+ virtual void SAL_CALL setContributor(const css::uno::Sequence< OUString >& the_value) override;
+ virtual OUString SAL_CALL getCoverage() override;
+ virtual void SAL_CALL setCoverage(const OUString & the_value) override;
+ virtual OUString SAL_CALL getIdentifier() override;
+ virtual void SAL_CALL setIdentifier(const OUString & the_value) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getPublisher() override;
+ virtual void SAL_CALL setPublisher(const css::uno::Sequence< OUString > & the_value) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getRelation() override;
+ virtual void SAL_CALL setRelation(const css::uno::Sequence< OUString > & the_value) override;
+ virtual OUString SAL_CALL getRights() override;
+ virtual void SAL_CALL setRights(const OUString & the_value) override;
+ virtual OUString SAL_CALL getSource() override;
+ virtual void SAL_CALL setSource(const OUString& the_value) override;
+ virtual OUString SAL_CALL getType() override;
+ virtual void SAL_CALL setType(const OUString& the_value) override;
+
+
+ // css::lang::XInitialization:
+ virtual void SAL_CALL initialize(
+ const css::uno::Sequence< css::uno::Any > & aArguments) override;
+
+ // css::util::XCloneable:
+ virtual css::uno::Reference<css::util::XCloneable> SAL_CALL createClone() override;
+
+ // css::util::XModifiable:
+ virtual sal_Bool SAL_CALL isModified( ) override;
+ virtual void SAL_CALL setModified( sal_Bool bModified ) override;
+
+ // css::util::XModifyBroadcaster:
+ virtual void SAL_CALL addModifyListener(
+ const css::uno::Reference< css::util::XModifyListener > & xListener) override;
+ virtual void SAL_CALL removeModifyListener(
+ const css::uno::Reference< css::util::XModifyListener > & xListener) override;
+
+ // css::xml::sax::XSAXSerializable
+ virtual void SAL_CALL serialize(
+ const css::uno::Reference<css::xml::sax::XDocumentHandler>& i_xHandler,
+ const css::uno::Sequence< css::beans::StringPair >& i_rNamespaces) override;
+
+protected:
+ virtual ~SfxDocumentMetaData() override {}
+ virtual rtl::Reference<SfxDocumentMetaData> createMe( css::uno::Reference< css::uno::XComponentContext > const & context ) { return new SfxDocumentMetaData( context ); };
+ const css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+ /// for notification
+ ::comphelper::OInterfaceContainerHelper3<css::util::XModifyListener> m_NotifyListeners;
+ /// flag: false means not initialized yet, or disposed
+ bool m_isInitialized;
+ /// flag
+ bool m_isModified;
+ /// meta-data DOM tree
+ css::uno::Reference< css::xml::dom::XDocument > m_xDoc;
+ /// meta-data super node in the meta-data DOM tree
+ css::uno::Reference< css::xml::dom::XNode> m_xParent;
+ /// standard meta data (single occurrence)
+ std::map< OUString, css::uno::Reference<css::xml::dom::XNode> >
+ m_meta;
+ /// standard meta data (multiple occurrences)
+ std::map< OUString,
+ std::vector<css::uno::Reference<css::xml::dom::XNode> > > m_metaList;
+ /// user-defined meta data (meta:user-defined) @ATTENTION may be null!
+ css::uno::Reference<css::beans::XPropertyContainer> m_xUserDefined;
+ // now for some meta-data attributes; these are not updated directly in the
+ // DOM because updates (detecting "empty" elements) would be quite messy
+ OUString m_TemplateName;
+ OUString m_TemplateURL;
+ css::util::DateTime m_TemplateDate;
+ OUString m_AutoloadURL;
+ sal_Int32 m_AutoloadSecs;
+ OUString m_DefaultTarget;
+
+ /// check if we are initialized properly
+ void checkInit() const;
+ /// initialize state from given DOM tree
+ void init(const css::uno::Reference<css::xml::dom::XDocument>& i_xDom);
+ /// update element in DOM tree
+ void updateElement(const OUString & i_name,
+ std::vector<std::pair<OUString, OUString> >* i_pAttrs = nullptr);
+ /// update user-defined meta data and attributes in DOM tree
+ void updateUserDefinedAndAttributes();
+ /// create empty DOM tree (XDocument)
+ css::uno::Reference<css::xml::dom::XDocument> createDOM() const;
+ /// extract base URL (necessary for converting relative links)
+ css::uno::Reference<css::beans::XPropertySet> getURLProperties(
+ const css::uno::Sequence<css::beans::PropertyValue> & i_rMedium) const;
+ /// get text of standard meta data element
+ OUString getMetaText(const char* i_name) const;
+ /// set text of standard meta data element iff not equal to existing text
+ bool setMetaText(const OUString& i_name,
+ const OUString & i_rValue);
+ /// set text of standard meta data element iff not equal to existing text
+ void setMetaTextAndNotify(const OUString& i_name,
+ const OUString & i_rValue);
+ /// get text of standard meta data element's attribute
+ OUString getMetaAttr(const OUString& i_name,
+ const OUString& i_attr) const;
+ /// get text of a list of standard meta data elements (multiple occ.)
+ css::uno::Sequence< OUString > getMetaList(
+ const char* i_name) const;
+ /// set text of a list of standard meta data elements (multiple occ.)
+ bool setMetaList(const OUString& i_name,
+ const css::uno::Sequence< OUString > & i_rValue,
+ AttrVector const*);
+ void createUserDefined();
+};
+
+typedef ::cppu::ImplInheritanceHelper< SfxDocumentMetaData, css::document::XCompatWriterDocProperties > CompatWriterDocPropsImpl_BASE;
+
+class CompatWriterDocPropsImpl : public CompatWriterDocPropsImpl_BASE
+{
+ OUString msManager;
+ OUString msCategory;
+ OUString msCompany;
+protected:
+ virtual rtl::Reference<SfxDocumentMetaData> createMe( css::uno::Reference< css::uno::XComponentContext > const & context ) override { return new CompatWriterDocPropsImpl( context ); };
+public:
+ explicit CompatWriterDocPropsImpl( css::uno::Reference< css::uno::XComponentContext > const & context) : CompatWriterDocPropsImpl_BASE( context ) {}
+
+// XCompatWriterDocPropsImpl
+ virtual OUString SAL_CALL getManager() override { return msManager; }
+ virtual void SAL_CALL setManager( const OUString& _manager ) override { msManager = _manager; }
+ virtual OUString SAL_CALL getCategory() override { return msCategory; }
+ virtual void SAL_CALL setCategory( const OUString& _category ) override { msCategory = _category; }
+ virtual OUString SAL_CALL getCompany() override { return msCompany; }
+ virtual void SAL_CALL setCompany( const OUString& _company ) override { msCompany = _company; }
+
+// XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) override
+ {
+ return "CompatWriterDocPropsImpl";
+ }
+
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override
+ {
+ css::uno::Sequence<OUString> aServiceNames { "com.sun.star.writer.DocumentProperties" };
+ return aServiceNames;
+ }
+};
+
+constexpr OUString sMetaPageCount = u"meta:page-count"_ustr;
+constexpr OUString sMetaTableCount = u"meta:table-count"_ustr;
+constexpr OUString sMetaDrawCount = u"meta:draw-count"_ustr;
+constexpr OUString sMetaImageCount = u"meta:image-count"_ustr;
+constexpr OUString sMetaObjectCount = u"meta:object-count"_ustr;
+constexpr OUString sMetaOleObjectCount = u"meta:ole-object-count"_ustr;
+constexpr OUString sMetaParagraphCount = u"meta:paragraph-count"_ustr;
+constexpr OUString sMetaWordCount = u"meta:word-count"_ustr;
+constexpr OUString sMetaCharacterCount = u"meta:character-count"_ustr;
+constexpr OUString sMetaRowCount = u"meta:row-count"_ustr;
+constexpr OUString sMetaFrameCount = u"meta:frame-count"_ustr;
+constexpr OUString sMetaSentenceCount = u"meta:sentence-count"_ustr;
+constexpr OUString sMetaSyllableCount = u"meta:syllable-count"_ustr;
+constexpr OUString sMetaNonWhitespaceCharacterCount = u"meta:non-whitespace-character-count"_ustr;
+constexpr OUString sMetaCellCount = u"meta:cell-count"_ustr;
+
+// NB: keep these two arrays in sync!
+constexpr OUString s_stdStatAttrs[] = {
+ sMetaPageCount,
+ sMetaTableCount,
+ sMetaDrawCount,
+ sMetaImageCount,
+ sMetaObjectCount,
+ sMetaOleObjectCount,
+ sMetaParagraphCount,
+ sMetaWordCount,
+ sMetaCharacterCount,
+ sMetaRowCount,
+ sMetaFrameCount,
+ sMetaSentenceCount,
+ sMetaSyllableCount,
+ sMetaNonWhitespaceCharacterCount,
+ sMetaCellCount
+};
+
+// NB: keep these two arrays in sync!
+const char* s_stdStats[] = {
+ "PageCount",
+ "TableCount",
+ "DrawCount",
+ "ImageCount",
+ "ObjectCount",
+ "OLEObjectCount",
+ "ParagraphCount",
+ "WordCount",
+ "CharacterCount",
+ "RowCount",
+ "FrameCount",
+ "SentenceCount",
+ "SyllableCount",
+ "NonWhitespaceCharacterCount",
+ "CellCount",
+ nullptr
+};
+
+const char* s_stdMeta[] = {
+ "meta:generator", // string
+ "dc:title", // string
+ "dc:description", // string
+ "dc:subject", // string
+ "meta:initial-creator", // string
+ "dc:creator", // string
+ "meta:printed-by", // string
+ "meta:creation-date", // dateTime
+ "dc:date", // dateTime
+ "meta:print-date", // dateTime
+ "meta:template", // XLink
+ "meta:auto-reload",
+ "meta:hyperlink-behaviour",
+ "dc:language", // language
+ "meta:editing-cycles", // nonNegativeInteger
+ "meta:editing-duration", // duration
+ "meta:document-statistic", // ... // note: statistic is singular, no s!
+ "dc:coverage",
+ "dc:identifier",
+ "dc:rights",
+ "dc:source",
+ "dc:type",
+ nullptr
+};
+
+constexpr OUString sMetaKeyword = u"meta:keyword"_ustr;
+constexpr OUString sMetaUserDefined = u"meta:user-defined"_ustr;
+constexpr OUString sDCContributor = u"dc:contributor"_ustr;
+constexpr OUString sDCPublisher = u"dc:publisher"_ustr;
+constexpr OUString sDCRelation = u"dc:relation"_ustr;
+constexpr OUString s_stdMetaList[] {
+ sMetaKeyword, // string*
+ sMetaUserDefined, // ...*
+ sDCContributor, // string*
+ sDCPublisher, // string*
+ sDCRelation, // string*
+};
+
+constexpr OUStringLiteral s_nsXLink = u"http://www.w3.org/1999/xlink";
+constexpr OUString s_nsDC = u"http://purl.org/dc/elements/1.1/"_ustr;
+constexpr OUString s_nsODF = u"urn:oasis:names:tc:opendocument:xmlns:office:1.0"_ustr;
+constexpr OUString s_nsODFMeta = u"urn:oasis:names:tc:opendocument:xmlns:meta:1.0"_ustr;
+// constexpr OUStringLiteral s_nsOOo = "http://openoffice.org/2004/office"; // not used (yet?)
+
+constexpr OUString s_meta = u"meta.xml"_ustr;
+
+bool isValidDate(const css::util::Date & i_rDate)
+{
+ return i_rDate.Month > 0;
+}
+
+bool isValidDateTime(const css::util::DateTime & i_rDateTime)
+{
+ return i_rDateTime.Month > 0;
+}
+
+std::pair< OUString, OUString >
+getQualifier(const OUString& nm) {
+ sal_Int32 ix = nm.indexOf(u':');
+ if (ix == -1) {
+ return std::make_pair(OUString(), nm);
+ } else {
+ return std::make_pair(nm.copy(0,ix), nm.copy(ix+1));
+ }
+}
+
+// get namespace for standard qualified names
+// NB: only call this with statically known strings!
+OUString getNameSpace(const OUString& i_qname) noexcept
+{
+ OUString ns;
+ OUString n = getQualifier(i_qname).first;
+ if ( n == "xlink" ) ns = s_nsXLink;
+ if ( n == "dc" ) ns = s_nsDC;
+ if ( n == "office" ) ns = s_nsODF;
+ if ( n == "meta" ) ns = s_nsODFMeta;
+ assert(!ns.isEmpty());
+ return ns;
+}
+
+bool
+textToDateOrDateTime(css::util::Date & io_rd, css::util::DateTime & io_rdt,
+ bool & o_rIsDateTime, std::optional<sal_Int16> & o_rTimeZone,
+ const OUString& i_text) noexcept
+{
+ if (::sax::Converter::parseDateOrDateTime(
+ &io_rd, io_rdt, o_rIsDateTime, &o_rTimeZone, i_text)) {
+ return true;
+ } else {
+ SAL_WARN_IF(!i_text.isEmpty(), "sfx.doc", "Invalid date: " << i_text);
+ return false;
+ }
+}
+
+// convert string to date/time
+bool
+textToDateTime(css::util::DateTime & io_rdt, const OUString& i_text) noexcept
+{
+ if (::sax::Converter::parseDateTime(io_rdt, i_text)) {
+ return true;
+ } else {
+ SAL_WARN_IF(!i_text.isEmpty(), "sfx.doc", "Invalid date: " << i_text);
+ return false;
+ }
+}
+
+// convert string to date/time with default return value
+css::util::DateTime
+textToDateTimeDefault(const OUString& i_text) noexcept
+{
+ css::util::DateTime dt;
+ static_cast<void> (textToDateTime(dt, i_text));
+ // on conversion error: return default value (unchanged)
+ return dt;
+}
+
+// convert date to string
+OUString
+dateToText(css::util::Date const& i_rd,
+ sal_Int16 const*const pTimeZone) noexcept
+{
+ if (isValidDate(i_rd)) {
+ OUStringBuffer buf;
+ ::sax::Converter::convertDate(buf, i_rd, pTimeZone);
+ return buf.makeStringAndClear();
+ } else {
+ return OUString();
+ }
+}
+
+
+// convert date/time to string
+OUString
+dateTimeToText(css::util::DateTime const& i_rdt,
+ sal_Int16 const*const pTimeZone = nullptr) noexcept
+{
+ if (isValidDateTime(i_rdt)) {
+ OUStringBuffer buf(32);
+ ::sax::Converter::convertDateTime(buf, i_rdt, pTimeZone, true);
+ return buf.makeStringAndClear();
+ } else {
+ return OUString();
+ }
+}
+
+// convert string to duration
+bool
+textToDuration(css::util::Duration& io_rDur, OUString const& i_rText)
+noexcept
+{
+ if (::sax::Converter::convertDuration(io_rDur, i_rText)) {
+ return true;
+ } else {
+ SAL_WARN_IF(!i_rText.isEmpty(), "sfx.doc", "Invalid duration: " << i_rText);
+ return false;
+ }
+}
+
+sal_Int32 textToDuration(OUString const& i_rText) noexcept
+{
+ css::util::Duration d;
+ if (textToDuration(d, i_rText)) {
+ // #i107372#: approximate years/months
+ const sal_Int32 days( (d.Years * 365) + (d.Months * 30) + d.Days );
+ return (days * (24*3600))
+ + (d.Hours * 3600) + (d.Minutes * 60) + d.Seconds;
+ } else {
+ return 0; // default
+ }
+}
+
+// convert duration to string
+OUString durationToText(css::util::Duration const& i_rDur) noexcept
+{
+ OUStringBuffer buf;
+ ::sax::Converter::convertDuration(buf, i_rDur);
+ return buf.makeStringAndClear();
+}
+
+// convert duration to string
+OUString durationToText(sal_Int32 i_value) noexcept
+{
+ css::util::Duration ud;
+ ud.Days = static_cast<sal_Int16>(i_value / (24 * 3600));
+ ud.Hours = static_cast<sal_Int16>((i_value % (24 * 3600)) / 3600);
+ ud.Minutes = static_cast<sal_Int16>((i_value % 3600) / 60);
+ ud.Seconds = static_cast<sal_Int16>(i_value % 60);
+ ud.NanoSeconds = 0;
+ return durationToText(ud);
+}
+
+// extract base URL (necessary for converting relative links)
+css::uno::Reference< css::beans::XPropertySet >
+SfxDocumentMetaData::getURLProperties(
+ const css::uno::Sequence< css::beans::PropertyValue > & i_rMedium) const
+{
+ css::uno::Reference< css::beans::XPropertyBag> xPropArg = css::beans::PropertyBag::createDefault( m_xContext );
+ try {
+ css::uno::Any baseUri;
+ for (const auto& rProp : i_rMedium) {
+ if (rProp.Name == "DocumentBaseURL") {
+ baseUri = rProp.Value;
+ } else if (rProp.Name == "URL") {
+ if (!baseUri.hasValue()) {
+ baseUri = rProp.Value;
+ }
+ } else if (rProp.Name == "HierarchicalDocumentName") {
+ xPropArg->addProperty(
+ "StreamRelPath",
+ css::beans::PropertyAttribute::MAYBEVOID,
+ rProp.Value);
+ }
+ }
+ if (baseUri.hasValue()) {
+ xPropArg->addProperty(
+ "BaseURI", css::beans::PropertyAttribute::MAYBEVOID,
+ baseUri);
+ }
+ xPropArg->addProperty("StreamName",
+ css::beans::PropertyAttribute::MAYBEVOID,
+ css::uno::Any(s_meta));
+ } catch (const css::uno::Exception &) {
+ // ignore
+ }
+ return css::uno::Reference< css::beans::XPropertySet>(xPropArg,
+ css::uno::UNO_QUERY_THROW);
+}
+
+// return the text of the (hopefully unique, i.e., normalize first!) text
+// node _below_ the given node
+/// @throws css::uno::RuntimeException
+OUString
+getNodeText(const css::uno::Reference<css::xml::dom::XNode>& i_xNode)
+{
+ if (!i_xNode.is())
+ throw css::uno::RuntimeException("SfxDocumentMetaData::getNodeText: argument is null", i_xNode);
+ for (css::uno::Reference<css::xml::dom::XNode> c = i_xNode->getFirstChild();
+ c.is();
+ c = c->getNextSibling()) {
+ if (c->getNodeType() == css::xml::dom::NodeType_TEXT_NODE) {
+ try {
+ return c->getNodeValue();
+ } catch (const css::xml::dom::DOMException &) { // too big?
+ return OUString();
+ }
+ }
+ }
+ return OUString();
+}
+
+OUString
+SfxDocumentMetaData::getMetaText(const char* i_name) const
+// throw (css::uno::RuntimeException)
+{
+ checkInit();
+
+ const OUString name( OUString::createFromAscii(i_name) );
+ assert(m_meta.find(name) != m_meta.end());
+ css::uno::Reference<css::xml::dom::XNode> xNode = m_meta.find(name)->second;
+ return (xNode.is()) ? getNodeText(xNode) : OUString();
+}
+
+bool
+SfxDocumentMetaData::setMetaText(const OUString& name,
+ const OUString & i_rValue)
+ // throw (css::uno::RuntimeException)
+{
+ checkInit();
+
+ assert(m_meta.find(name) != m_meta.end());
+ css::uno::Reference<css::xml::dom::XNode> xNode = m_meta.find(name)->second;
+
+ try {
+ if (i_rValue.isEmpty()) {
+ if (xNode.is()) { // delete
+ m_xParent->removeChild(xNode);
+ xNode.clear();
+ m_meta[name] = xNode;
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ if (xNode.is()) { // update
+ for (css::uno::Reference<css::xml::dom::XNode> c =
+ xNode->getFirstChild();
+ c.is();
+ c = c->getNextSibling()) {
+ if (c->getNodeType() == css::xml::dom::NodeType_TEXT_NODE) {
+ if (c->getNodeValue() != i_rValue) {
+ c->setNodeValue(i_rValue);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+ } else { // insert
+ xNode.set(m_xDoc->createElementNS(getNameSpace(name), name),
+ css::uno::UNO_QUERY_THROW);
+ m_xParent->appendChild(xNode);
+ m_meta[name] = xNode;
+ }
+ css::uno::Reference<css::xml::dom::XNode> xTextNode(
+ m_xDoc->createTextNode(i_rValue), css::uno::UNO_QUERY_THROW);
+ xNode->appendChild(xTextNode);
+ return true;
+ }
+ } catch (const css::xml::dom::DOMException &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw css::lang::WrappedTargetRuntimeException(
+ "SfxDocumentMetaData::setMetaText: DOM exception",
+ css::uno::Reference<css::uno::XInterface>(*this), anyEx);
+ }
+}
+
+void
+SfxDocumentMetaData::setMetaTextAndNotify(const OUString & i_name,
+ const OUString & i_rValue)
+ // throw (css::uno::RuntimeException)
+{
+ ::osl::ClearableMutexGuard g(m_aMutex);
+ if (setMetaText(i_name, i_rValue)) {
+ g.clear();
+ setModified(true);
+ }
+}
+
+OUString
+SfxDocumentMetaData::getMetaAttr(const OUString& name, const OUString& i_attr) const
+// throw (css::uno::RuntimeException)
+{
+ assert(m_meta.find(name) != m_meta.end());
+ css::uno::Reference<css::xml::dom::XNode> xNode = m_meta.find(name)->second;
+ if (xNode.is()) {
+ css::uno::Reference<css::xml::dom::XElement> xElem(xNode,
+ css::uno::UNO_QUERY_THROW);
+ return xElem->getAttributeNS(getNameSpace(i_attr),
+ getQualifier(i_attr).second);
+ } else {
+ return OUString();
+ }
+}
+
+css::uno::Sequence< OUString>
+SfxDocumentMetaData::getMetaList(const char* i_name) const
+// throw (css::uno::RuntimeException)
+{
+ checkInit();
+ OUString name = OUString::createFromAscii(i_name);
+ assert(m_metaList.find(name) != m_metaList.end());
+ std::vector<css::uno::Reference<css::xml::dom::XNode> > const & vec =
+ m_metaList.find(name)->second;
+ css::uno::Sequence< OUString> ret(vec.size());
+ std::transform(vec.begin(), vec.end(), ret.getArray(),
+ [](const auto& node) { return getNodeText(node); });
+ return ret;
+}
+
+bool
+SfxDocumentMetaData::setMetaList(const OUString& name,
+ const css::uno::Sequence<OUString> & i_rValue,
+ AttrVector const* i_pAttrs)
+ // throw (css::uno::RuntimeException)
+{
+ checkInit();
+ assert((i_pAttrs == nullptr) ||
+ (static_cast<size_t>(i_rValue.getLength()) == i_pAttrs->size()));
+
+ try {
+ assert(m_metaList.find(name) != m_metaList.end());
+ std::vector<css::uno::Reference<css::xml::dom::XNode> > & vec =
+ m_metaList[name];
+
+ // if nothing changed, do nothing
+ // alas, this does not check for permutations, or attributes...
+ if (nullptr == i_pAttrs) {
+ if (static_cast<size_t>(i_rValue.getLength()) == vec.size()) {
+ bool isEqual(true);
+ for (sal_Int32 i = 0; i < i_rValue.getLength(); ++i) {
+ css::uno::Reference<css::xml::dom::XNode> xNode(vec.at(i));
+ if (xNode.is()) {
+ OUString val = getNodeText(xNode);
+ if (val != i_rValue[i]) {
+ isEqual = false;
+ break;
+ }
+ }
+ }
+ if (isEqual) return false;
+ }
+ }
+
+ // remove old meta data nodes
+ {
+ std::vector<css::uno::Reference<css::xml::dom::XNode> >
+ ::reverse_iterator it(vec.rbegin());
+ try {
+ for ( ;it != vec.rend(); ++it)
+ {
+ m_xParent->removeChild(*it);
+ }
+ }
+ catch (...)
+ {
+ // Clean up already removed nodes
+ vec.erase(it.base(), vec.end());
+ throw;
+ }
+ vec.clear();
+ }
+
+ // insert new meta data nodes into DOM tree
+ for (sal_Int32 i = 0; i < i_rValue.getLength(); ++i) {
+ css::uno::Reference<css::xml::dom::XElement> xElem(
+ m_xDoc->createElementNS(getNameSpace(name), name),
+ css::uno::UNO_SET_THROW);
+ css::uno::Reference<css::xml::dom::XNode> xNode(xElem,
+ css::uno::UNO_QUERY_THROW);
+ css::uno::Reference<css::xml::dom::XNode> xTextNode(
+ m_xDoc->createTextNode(i_rValue[i]), css::uno::UNO_QUERY_THROW);
+ // set attributes
+ if (i_pAttrs != nullptr) {
+ for (auto const& elem : (*i_pAttrs)[i])
+ {
+ xElem->setAttributeNS(getNameSpace(elem.first),
+ elem.first, elem.second);
+ }
+ }
+ xNode->appendChild(xTextNode);
+ m_xParent->appendChild(xNode);
+ vec.push_back(xNode);
+ }
+
+ return true;
+ } catch (const css::xml::dom::DOMException &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw css::lang::WrappedTargetRuntimeException(
+ "SfxDocumentMetaData::setMetaList: DOM exception",
+ css::uno::Reference<css::uno::XInterface>(*this), anyEx);
+ }
+}
+
+// convert property list to string list and attribute list
+std::pair<css::uno::Sequence< OUString>, AttrVector>
+propsToStrings(css::uno::Reference<css::beans::XPropertySet> const & i_xPropSet)
+{
+ ::std::vector< OUString > values;
+ AttrVector attrs;
+
+ css::uno::Reference<css::beans::XPropertySetInfo> xSetInfo
+ = i_xPropSet->getPropertySetInfo();
+ css::uno::Sequence<css::beans::Property> props = xSetInfo->getProperties();
+
+ for (sal_Int32 i = 0; i < props.getLength(); ++i) {
+ if (props[i].Attributes & css::beans::PropertyAttribute::TRANSIENT) {
+ continue;
+ }
+ const OUString name = props[i].Name;
+ css::uno::Any any;
+ try {
+ any = i_xPropSet->getPropertyValue(name);
+ } catch (const css::uno::Exception &) {
+ // ignore
+ }
+ const css::uno::Type & type = any.getValueType();
+ std::vector<std::pair<OUString, OUString> > as;
+ as.emplace_back("meta:name", name);
+ static constexpr OUString vt = u"meta:value-type"_ustr;
+
+ // convert according to type
+ if (type == ::cppu::UnoType<bool>::get()) {
+ bool b = false;
+ any >>= b;
+ OUStringBuffer buf;
+ ::sax::Converter::convertBool(buf, b);
+ values.push_back(buf.makeStringAndClear());
+ as.emplace_back(vt, OUString("boolean"));
+ } else if (type == ::cppu::UnoType< OUString>::get()) {
+ OUString s;
+ any >>= s;
+ values.push_back(s);
+// #i90847# OOo 2.x does stupid things if value-type="string";
+// fortunately string is default anyway, so we can just omit it
+// #i107502#: however, OOo 2.x only reads 4 user-defined without @value-type
+// => best backward compatibility: first 4 without @value-type, rest with
+ if (4 <= i)
+ {
+ as.emplace_back(vt, OUString("string"));
+ }
+ } else if (type == ::cppu::UnoType<css::util::DateTime>::get()) {
+ css::util::DateTime dt;
+ any >>= dt;
+ values.push_back(dateTimeToText(dt));
+ as.emplace_back(vt, OUString("date"));
+ } else if (type == ::cppu::UnoType<css::util::Date>::get()) {
+ css::util::Date d;
+ any >>= d;
+ values.push_back(dateToText(d, nullptr));
+ as.emplace_back(vt,OUString("date"));
+ } else if (type == ::cppu::UnoType<css::util::DateTimeWithTimezone>::get()) {
+ css::util::DateTimeWithTimezone dttz;
+ any >>= dttz;
+ values.push_back(dateTimeToText(dttz.DateTimeInTZ, &dttz.Timezone));
+ as.emplace_back(vt, OUString("date"));
+ } else if (type == ::cppu::UnoType<css::util::DateWithTimezone>::get()) {
+ css::util::DateWithTimezone dtz;
+ any >>= dtz;
+ values.push_back(dateToText(dtz.DateInTZ, &dtz.Timezone));
+ as.emplace_back(vt, OUString("date"));
+ } else if (type == ::cppu::UnoType<css::util::Time>::get()) {
+ // #i97029#: replaced by Duration
+ // Time is supported for backward compatibility with OOo 3.x, x<=2
+ css::util::Time ut;
+ any >>= ut;
+ css::util::Duration ud;
+ ud.Hours = ut.Hours;
+ ud.Minutes = ut.Minutes;
+ ud.Seconds = ut.Seconds;
+ ud.NanoSeconds = ut.NanoSeconds;
+ values.push_back(durationToText(ud));
+ as.emplace_back(vt, OUString("time"));
+ } else if (type == ::cppu::UnoType<css::util::Duration>::get()) {
+ css::util::Duration ud;
+ any >>= ud;
+ values.push_back(durationToText(ud));
+ as.emplace_back(vt, OUString("time"));
+ } else if (::cppu::UnoType<double>::get().isAssignableFrom(type)) {
+ // support not just double, but anything that can be converted
+ double d = 0;
+ any >>= d;
+ OUStringBuffer buf;
+ ::sax::Converter::convertDouble(buf, d);
+ values.push_back(buf.makeStringAndClear());
+ as.emplace_back(vt, OUString("float"));
+ } else {
+ SAL_WARN("sfx.doc", "Unsupported property type: " << any.getValueTypeName() );
+ continue;
+ }
+ attrs.push_back(as);
+ }
+
+ return std::make_pair(comphelper::containerToSequence(values), attrs);
+}
+
+// remove the given element from the DOM, and iff i_pAttrs != 0 insert new one
+void
+SfxDocumentMetaData::updateElement(const OUString& name,
+ std::vector<std::pair<OUString, OUString> >* i_pAttrs)
+{
+ try {
+ // remove old element
+ css::uno::Reference<css::xml::dom::XNode> xNode =
+ m_meta.find(name)->second;
+ if (xNode.is()) {
+ m_xParent->removeChild(xNode);
+ xNode.clear();
+ }
+ // add new element
+ if (nullptr != i_pAttrs) {
+ css::uno::Reference<css::xml::dom::XElement> xElem(
+ m_xDoc->createElementNS(getNameSpace(name), name),
+ css::uno::UNO_SET_THROW);
+ xNode.set(xElem, css::uno::UNO_QUERY_THROW);
+ // set attributes
+ for (auto const& elem : *i_pAttrs)
+ {
+ xElem->setAttributeNS(getNameSpace(elem.first),
+ elem.first, elem.second);
+ }
+ m_xParent->appendChild(xNode);
+ }
+ m_meta[name] = xNode;
+ } catch (const css::xml::dom::DOMException &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw css::lang::WrappedTargetRuntimeException(
+ "SfxDocumentMetaData::updateElement: DOM exception",
+ css::uno::Reference<css::uno::XInterface>(*this), anyEx);
+ }
+}
+
+// update user-defined meta data in DOM tree
+void SfxDocumentMetaData::updateUserDefinedAndAttributes()
+{
+ createUserDefined();
+ const css::uno::Reference<css::beans::XPropertySet> xPSet(m_xUserDefined,
+ css::uno::UNO_QUERY_THROW);
+ const std::pair<css::uno::Sequence< OUString>, AttrVector>
+ udStringsAttrs( propsToStrings(xPSet) );
+ (void) setMetaList("meta:user-defined", udStringsAttrs.first,
+ &udStringsAttrs.second);
+
+ // update elements with attributes
+ std::vector<std::pair<OUString, OUString> > attributes;
+ if (!m_TemplateName.isEmpty() || !m_TemplateURL.isEmpty()
+ || isValidDateTime(m_TemplateDate)) {
+ attributes.emplace_back("xlink:type", OUString("simple"));
+ attributes.emplace_back("xlink:actuate", OUString("onRequest"));
+ attributes.emplace_back("xlink:title", m_TemplateName);
+ attributes.emplace_back("xlink:href", m_TemplateURL );
+ if (isValidDateTime(m_TemplateDate)) {
+ attributes.emplace_back(
+ "meta:date", dateTimeToText(m_TemplateDate));
+ }
+ updateElement("meta:template", &attributes);
+ } else {
+ updateElement("meta:template");
+ }
+ attributes.clear();
+
+ if (!m_AutoloadURL.isEmpty() || (0 != m_AutoloadSecs)) {
+ attributes.emplace_back("xlink:href", m_AutoloadURL );
+ attributes.emplace_back("meta:delay",
+ durationToText(m_AutoloadSecs));
+ updateElement("meta:auto-reload", &attributes);
+ } else {
+ updateElement("meta:auto-reload");
+ }
+ attributes.clear();
+
+ if (!m_DefaultTarget.isEmpty()) {
+ attributes.emplace_back(
+ "office:target-frame-name",
+ m_DefaultTarget);
+ // xlink:show: _blank -> new, any other value -> replace
+ const char* show = m_DefaultTarget == "_blank" ? "new" : "replace";
+ attributes.emplace_back(
+ "xlink:show",
+ OUString::createFromAscii(show));
+ updateElement("meta:hyperlink-behaviour", &attributes);
+ } else {
+ updateElement("meta:hyperlink-behaviour");
+ }
+ attributes.clear();
+}
+
+// create empty DOM tree (XDocument)
+css::uno::Reference<css::xml::dom::XDocument>
+SfxDocumentMetaData::createDOM() const // throw (css::uno::RuntimeException)
+{
+ css::uno::Reference<css::xml::dom::XDocumentBuilder> xBuilder( css::xml::dom::DocumentBuilder::create(m_xContext) );
+ css::uno::Reference<css::xml::dom::XDocument> xDoc = xBuilder->newDocument();
+ if (!xDoc.is())
+ throw css::uno::RuntimeException(
+ "SfxDocumentMetaData::createDOM: cannot create new document",
+ *const_cast<SfxDocumentMetaData*>(this));
+ return xDoc;
+}
+
+void
+SfxDocumentMetaData::checkInit() const // throw (css::uno::RuntimeException)
+{
+ if (!m_isInitialized) {
+ throw css::uno::RuntimeException(
+ "SfxDocumentMetaData::checkInit: not initialized",
+ *const_cast<SfxDocumentMetaData*>(this));
+ }
+ assert(m_xDoc.is() && m_xParent.is());
+}
+
+void extractTagAndNamespaceUri(std::u16string_view aChildNodeName,
+ std::u16string_view& rTagName, std::u16string_view& rNamespaceURI)
+{
+ size_t idx = aChildNodeName.find(':');
+ assert(idx != std::u16string_view::npos);
+ std::u16string_view aPrefix = aChildNodeName.substr(0, idx);
+ rTagName = aChildNodeName.substr(idx + 1);
+ if (aPrefix == u"dc")
+ rNamespaceURI = s_nsDC;
+ else if (aPrefix == u"meta")
+ rNamespaceURI = s_nsODFMeta;
+ else if (aPrefix == u"office")
+ rNamespaceURI = s_nsODF;
+ else
+ assert(false);
+}
+
+
+css::uno::Reference<css::xml::dom::XElement> getChildNodeByName(
+ const css::uno::Reference<css::xml::dom::XNode>& xNode,
+ std::u16string_view aChildNodeName)
+{
+ css::uno::Reference< css::xml::dom::XNodeList > xList = xNode->getChildNodes();
+ if (!xList)
+ return nullptr;
+ std::u16string_view aTagName, aNamespaceURI;
+ extractTagAndNamespaceUri(aChildNodeName, aTagName, aNamespaceURI);
+
+ const sal_Int32 nLength(xList->getLength());
+ for (sal_Int32 a(0); a < nLength; a++)
+ {
+ const css::uno::Reference< css::xml::dom::XElement > xChild(xList->item(a), css::uno::UNO_QUERY);
+ if (xChild && xChild->getNodeName() == aTagName && aNamespaceURI == xChild->getNamespaceURI())
+ return xChild;
+ }
+ return nullptr;
+}
+
+
+std::vector<css::uno::Reference<css::xml::dom::XNode> > getChildNodeListByName(
+ const css::uno::Reference<css::xml::dom::XNode>& xNode,
+ std::u16string_view aChildNodeName)
+{
+ css::uno::Reference< css::xml::dom::XNodeList > xList = xNode->getChildNodes();
+ if (!xList)
+ return {};
+ std::u16string_view aTagName, aNamespaceURI;
+ extractTagAndNamespaceUri(aChildNodeName, aTagName, aNamespaceURI);
+ std::vector<css::uno::Reference<css::xml::dom::XNode>> aList;
+ const sal_Int32 nLength(xList->getLength());
+ for (sal_Int32 a(0); a < nLength; a++)
+ {
+ const css::uno::Reference< css::xml::dom::XElement > xChild(xList->item(a), css::uno::UNO_QUERY);
+ if (xChild && xChild->getNodeName() == aTagName && aNamespaceURI == xChild->getNamespaceURI())
+ aList.push_back(xChild);
+ }
+ return aList;
+}
+
+// initialize state from DOM tree
+void SfxDocumentMetaData::init(
+ const css::uno::Reference<css::xml::dom::XDocument>& i_xDoc)
+{
+ if (!i_xDoc.is())
+ throw css::uno::RuntimeException("SfxDocumentMetaData::init: no DOM tree given", *this);
+
+ m_isInitialized = false;
+ m_xDoc = i_xDoc;
+
+ // select nodes for standard meta data stuff
+ // NB: we do not handle the single-XML-file ODF variant, which would
+ // have the root element office:document.
+ // The root of such documents must be converted in the importer!
+ css::uno::Reference<css::xml::dom::XNode> xDocNode(
+ m_xDoc, css::uno::UNO_QUERY_THROW);
+ m_xParent.clear();
+ try {
+ css::uno::Reference<css::xml::dom::XNode> xChild = getChildNodeByName(xDocNode, u"office:document-meta");
+ if (xChild)
+ m_xParent = getChildNodeByName(xChild, u"office:meta");
+ } catch (const css::uno::Exception &) {
+ }
+
+ if (!m_xParent.is()) {
+ // all this create/append stuff may throw DOMException
+ try {
+ css::uno::Reference<css::xml::dom::XElement> xRElem;
+ css::uno::Reference<css::xml::dom::XNode> xNode(
+ i_xDoc->getFirstChild());
+ while (xNode.is()) {
+ if (css::xml::dom::NodeType_ELEMENT_NODE ==xNode->getNodeType())
+ {
+ if ( xNode->getNamespaceURI() == s_nsODF && xNode->getLocalName() == "document-meta" )
+ {
+ xRElem.set(xNode, css::uno::UNO_QUERY_THROW);
+ break;
+ }
+ else
+ {
+ SAL_INFO("sfx.doc", "SfxDocumentMetaData::init(): "
+ "deleting unexpected root element: "
+ << xNode->getLocalName());
+ i_xDoc->removeChild(xNode);
+ xNode = i_xDoc->getFirstChild(); // start over
+ }
+ } else {
+ xNode = xNode->getNextSibling();
+ }
+ }
+ if (!xRElem.is()) {
+ static constexpr OUStringLiteral sOfficeDocumentMeta = u"office:document-meta";
+ xRElem = i_xDoc->createElementNS(
+ s_nsODF, sOfficeDocumentMeta);
+ css::uno::Reference<css::xml::dom::XNode> xRNode(xRElem,
+ css::uno::UNO_QUERY_THROW);
+ i_xDoc->appendChild(xRNode);
+ }
+ static constexpr OUStringLiteral sOfficeVersion = u"office:version";
+ xRElem->setAttributeNS(s_nsODF, sOfficeVersion, "1.0");
+ // does not exist, otherwise m_xParent would not be null
+ static constexpr OUStringLiteral sOfficeMeta = u"office:meta";
+ css::uno::Reference<css::xml::dom::XNode> xParent (
+ i_xDoc->createElementNS(s_nsODF, sOfficeMeta),
+ css::uno::UNO_QUERY_THROW);
+ xRElem->appendChild(xParent);
+ m_xParent = xParent;
+ } catch (const css::xml::dom::DOMException &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw css::lang::WrappedTargetRuntimeException(
+ "SfxDocumentMetaData::init: DOM exception",
+ css::uno::Reference<css::uno::XInterface>(*this), anyEx);
+ }
+ }
+
+
+ // select nodes for elements of which we only handle one occurrence
+ for (const char **pName = s_stdMeta; *pName != nullptr; ++pName) {
+ OUString name = OUString::createFromAscii(*pName);
+ // NB: If a document contains more than one occurrence of a
+ // meta-data element, we arbitrarily pick one of them here.
+ // We do not remove the others, i.e., when we write the
+ // document, it will contain the duplicates unchanged.
+ // The ODF spec says that handling multiple occurrences is
+ // application-specific.
+ css::uno::Reference<css::xml::dom::XNode> xNode =
+ getChildNodeByName(m_xParent, name);
+ // Do not create an empty element if it is missing;
+ // for certain elements, such as dateTime, this would be invalid
+ m_meta[name] = xNode;
+ }
+
+ // select nodes for elements of which we handle all occurrences
+ for (const auto & name : s_stdMetaList) {
+ std::vector<css::uno::Reference<css::xml::dom::XNode> > nodes =
+ getChildNodeListByName(m_xParent, name);
+ m_metaList[name] = nodes;
+ }
+
+ // initialize members corresponding to attributes from DOM nodes
+ static constexpr OUString sMetaTemplate = u"meta:template"_ustr;
+ static constexpr OUString sMetaAutoReload = u"meta:auto-reload"_ustr;
+ static constexpr OUStringLiteral sMetaHyperlinkBehaviour = u"meta:hyperlink-behaviour";
+ m_TemplateName = getMetaAttr(sMetaTemplate, "xlink:title");
+ m_TemplateURL = getMetaAttr(sMetaTemplate, "xlink:href");
+ m_TemplateDate =
+ textToDateTimeDefault(getMetaAttr(sMetaTemplate, "meta:date"));
+ m_AutoloadURL = getMetaAttr(sMetaAutoReload, "xlink:href");
+ m_AutoloadSecs =
+ textToDuration(getMetaAttr(sMetaAutoReload, "meta:delay"));
+ m_DefaultTarget =
+ getMetaAttr(sMetaHyperlinkBehaviour, "office:target-frame-name");
+
+
+ std::vector<css::uno::Reference<css::xml::dom::XNode> > & vec =
+ m_metaList[OUString("meta:user-defined")];
+ m_xUserDefined.clear(); // #i105826#: reset (may be re-initialization)
+ if ( !vec.empty() )
+ {
+ createUserDefined();
+ }
+
+ // user-defined meta data: initialize PropertySet from DOM nodes
+ for (auto const& elem : vec)
+ {
+ css::uno::Reference<css::xml::dom::XElement> xElem(elem,
+ css::uno::UNO_QUERY_THROW);
+ css::uno::Any any;
+ OUString name = xElem->getAttributeNS(s_nsODFMeta, "name");
+ OUString type = xElem->getAttributeNS(s_nsODFMeta, "value-type");
+ OUString text = getNodeText(elem);
+ if ( type == "float" ) {
+ double d;
+ if (::sax::Converter::convertDouble(d, text)) {
+ any <<= d;
+ } else {
+ SAL_WARN("sfx.doc", "Invalid float: " << text);
+ continue;
+ }
+ } else if ( type == "date" ) {
+ bool isDateTime;
+ css::util::Date d;
+ css::util::DateTime dt;
+ std::optional<sal_Int16> nTimeZone;
+ if (textToDateOrDateTime(d, dt, isDateTime, nTimeZone, text)) {
+ if (isDateTime) {
+ if (nTimeZone) {
+ any <<= css::util::DateTimeWithTimezone(dt,
+ *nTimeZone);
+ } else {
+ any <<= dt;
+ }
+ } else {
+ if (nTimeZone) {
+ any <<= css::util::DateWithTimezone(d, *nTimeZone);
+ } else {
+ any <<= d;
+ }
+ }
+ } else {
+ SAL_WARN("sfx.doc", "Invalid date: " << text);
+ continue;
+ }
+ } else if ( type == "time" ) {
+ css::util::Duration ud;
+ if (textToDuration(ud, text)) {
+ any <<= ud;
+ } else {
+ SAL_WARN("sfx.doc", "Invalid time: " << text);
+ continue;
+ }
+ } else if ( type == "boolean" ) {
+ bool b;
+ if (::sax::Converter::convertBool(b, text)) {
+ any <<= b;
+ } else {
+ SAL_WARN("sfx.doc", "Invalid boolean: " << text);
+ continue;
+ }
+ } else { // default
+ any <<= text;
+ }
+ try {
+ m_xUserDefined->addProperty(name,
+ css::beans::PropertyAttribute::REMOVABLE, any);
+ } catch (const css::beans::PropertyExistException &) {
+ SAL_WARN("sfx.doc", "Duplicate: " << name);
+ // ignore; duplicate
+ } catch (const css::beans::IllegalTypeException &) {
+ SAL_INFO("sfx.doc", "SfxDocumentMetaData: illegal type: " << name);
+ } catch (const css::lang::IllegalArgumentException &) {
+ SAL_INFO("sfx.doc", "SfxDocumentMetaData: illegal arg: " << name);
+ }
+ }
+
+ m_isModified = false;
+ m_isInitialized = true;
+}
+
+
+SfxDocumentMetaData::SfxDocumentMetaData(
+ css::uno::Reference< css::uno::XComponentContext > const & context)
+ : BaseMutex()
+ , SfxDocumentMetaData_Base(m_aMutex)
+ , m_xContext(context)
+ , m_NotifyListeners(m_aMutex)
+ , m_isInitialized(false)
+ , m_isModified(false)
+ , m_AutoloadSecs(0)
+{
+ assert(context.is());
+ assert(context->getServiceManager().is());
+ init(createDOM());
+}
+
+// com.sun.star.uno.XServiceInfo:
+OUString SAL_CALL
+SfxDocumentMetaData::getImplementationName()
+{
+ return "SfxDocumentMetaData";
+}
+
+sal_Bool SAL_CALL
+SfxDocumentMetaData::supportsService(OUString const & serviceName)
+{
+ return cppu::supportsService(this, serviceName);
+}
+
+css::uno::Sequence< OUString > SAL_CALL
+SfxDocumentMetaData::getSupportedServiceNames()
+{
+ css::uno::Sequence< OUString > s { "com.sun.star.document.DocumentProperties" };
+ return s;
+}
+
+
+// css::lang::XComponent:
+void SAL_CALL SfxDocumentMetaData::dispose()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ if (!m_isInitialized) {
+ return;
+ }
+ WeakComponentImplHelperBase::dispose(); // superclass
+ m_NotifyListeners.disposeAndClear(css::lang::EventObject(
+ getXWeak()));
+ m_isInitialized = false;
+ m_meta.clear();
+ m_metaList.clear();
+ m_xParent.clear();
+ m_xDoc.clear();
+ m_xUserDefined.clear();
+}
+
+
+// css::document::XDocumentProperties:
+OUString SAL_CALL
+SfxDocumentMetaData::getAuthor()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return getMetaText("meta:initial-creator");
+}
+
+void SAL_CALL SfxDocumentMetaData::setAuthor(const OUString & the_value)
+{
+ setMetaTextAndNotify("meta:initial-creator", the_value);
+}
+
+
+OUString SAL_CALL
+SfxDocumentMetaData::getGenerator()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return getMetaText("meta:generator");
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setGenerator(const OUString & the_value)
+{
+ setMetaTextAndNotify("meta:generator", the_value);
+}
+
+css::util::DateTime SAL_CALL
+SfxDocumentMetaData::getCreationDate()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return textToDateTimeDefault(getMetaText("meta:creation-date"));
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setCreationDate(const css::util::DateTime & the_value)
+{
+ setMetaTextAndNotify("meta:creation-date", dateTimeToText(the_value));
+}
+
+OUString SAL_CALL
+SfxDocumentMetaData::getTitle()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return getMetaText("dc:title");
+}
+
+void SAL_CALL SfxDocumentMetaData::setTitle(const OUString & the_value)
+{
+ setMetaTextAndNotify("dc:title", the_value);
+}
+
+OUString SAL_CALL
+SfxDocumentMetaData::getSubject()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return getMetaText("dc:subject");
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setSubject(const OUString & the_value)
+{
+ setMetaTextAndNotify("dc:subject", the_value);
+}
+
+OUString SAL_CALL
+SfxDocumentMetaData::getDescription()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return getMetaText("dc:description");
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setDescription(const OUString & the_value)
+{
+ setMetaTextAndNotify("dc:description", the_value);
+}
+
+css::uno::Sequence< OUString >
+SAL_CALL SfxDocumentMetaData::getKeywords()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return getMetaList("meta:keyword");
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setKeywords(
+ const css::uno::Sequence< OUString > & the_value)
+{
+ ::osl::ClearableMutexGuard g(m_aMutex);
+ if (setMetaList("meta:keyword", the_value, nullptr)) {
+ g.clear();
+ setModified(true);
+ }
+}
+
+// css::document::XDocumentProperties2
+css::uno::Sequence<OUString> SAL_CALL SfxDocumentMetaData::getContributor()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return getMetaList("dc:contributor");
+}
+
+void SAL_CALL SfxDocumentMetaData::setContributor(const css::uno::Sequence<OUString>& the_value)
+{
+ ::osl::ClearableMutexGuard g(m_aMutex);
+ if (setMetaList("dc:contributor", the_value, nullptr))
+ {
+ g.clear();
+ setModified(true);
+ }
+}
+
+OUString SAL_CALL SfxDocumentMetaData::getCoverage()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return getMetaText("dc:coverage");
+}
+
+void SAL_CALL SfxDocumentMetaData::setCoverage(const OUString& the_value)
+{
+ setMetaTextAndNotify("dc:coverage", the_value);
+}
+
+OUString SAL_CALL SfxDocumentMetaData::getIdentifier()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return getMetaText("dc:identifier");
+}
+
+void SAL_CALL SfxDocumentMetaData::setIdentifier(const OUString& the_value)
+{
+ setMetaTextAndNotify("dc:identifier", the_value);
+}
+
+css::uno::Sequence<OUString> SAL_CALL SfxDocumentMetaData::getPublisher()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return getMetaList("dc:publisher");
+}
+
+void SAL_CALL SfxDocumentMetaData::setPublisher(const css::uno::Sequence<OUString>& the_value)
+{
+ ::osl::ClearableMutexGuard g(m_aMutex);
+ if (setMetaList("dc:publisher", the_value, nullptr))
+ {
+ g.clear();
+ setModified(true);
+ }
+}
+
+css::uno::Sequence<OUString> SAL_CALL SfxDocumentMetaData::getRelation()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return getMetaList("dc:relation");
+}
+
+void SAL_CALL SfxDocumentMetaData::setRelation(const css::uno::Sequence<OUString>& the_value)
+{
+ ::osl::ClearableMutexGuard g(m_aMutex);
+ if (setMetaList("dc:relation", the_value, nullptr))
+ {
+ g.clear();
+ setModified(true);
+ }
+}
+
+OUString SAL_CALL SfxDocumentMetaData::getRights()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return getMetaText("dc:rights");
+}
+
+void SAL_CALL SfxDocumentMetaData::setRights(const OUString& the_value)
+{
+ setMetaTextAndNotify("dc:rights", the_value);
+}
+
+OUString SAL_CALL SfxDocumentMetaData::getSource()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return getMetaText("dc:source");
+}
+
+void SAL_CALL SfxDocumentMetaData::setSource(const OUString& the_value)
+{
+ setMetaTextAndNotify("dc:source", the_value);
+}
+
+OUString SAL_CALL SfxDocumentMetaData::getType()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return getMetaText("dc:type");
+}
+
+void SAL_CALL SfxDocumentMetaData::setType(const OUString& the_value)
+{
+ setMetaTextAndNotify("dc:type", the_value);
+}
+
+css::lang::Locale SAL_CALL
+ SfxDocumentMetaData::getLanguage()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ css::lang::Locale loc( LanguageTag::convertToLocale( getMetaText("dc:language"), false));
+ return loc;
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setLanguage(const css::lang::Locale & the_value)
+{
+ OUString text( LanguageTag::convertToBcp47( the_value, false));
+ setMetaTextAndNotify("dc:language", text);
+}
+
+OUString SAL_CALL
+SfxDocumentMetaData::getModifiedBy()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return getMetaText("dc:creator");
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setModifiedBy(const OUString & the_value)
+{
+ setMetaTextAndNotify("dc:creator", the_value);
+}
+
+css::util::DateTime SAL_CALL
+SfxDocumentMetaData::getModificationDate()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return textToDateTimeDefault(getMetaText("dc:date"));
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setModificationDate(const css::util::DateTime & the_value)
+{
+ setMetaTextAndNotify("dc:date", dateTimeToText(the_value));
+}
+
+OUString SAL_CALL
+SfxDocumentMetaData::getPrintedBy()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return getMetaText("meta:printed-by");
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setPrintedBy(const OUString & the_value)
+{
+ setMetaTextAndNotify("meta:printed-by", the_value);
+}
+
+css::util::DateTime SAL_CALL
+SfxDocumentMetaData::getPrintDate()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return textToDateTimeDefault(getMetaText("meta:print-date"));
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setPrintDate(const css::util::DateTime & the_value)
+{
+ setMetaTextAndNotify("meta:print-date", dateTimeToText(the_value));
+}
+
+OUString SAL_CALL
+SfxDocumentMetaData::getTemplateName()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ checkInit();
+ return m_TemplateName;
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setTemplateName(const OUString & the_value)
+{
+ ::osl::ClearableMutexGuard g(m_aMutex);
+ checkInit();
+ if (m_TemplateName != the_value) {
+ m_TemplateName = the_value;
+ g.clear();
+ setModified(true);
+ }
+}
+
+OUString SAL_CALL
+SfxDocumentMetaData::getTemplateURL()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ checkInit();
+ return m_TemplateURL;
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setTemplateURL(const OUString & the_value)
+{
+ ::osl::ClearableMutexGuard g(m_aMutex);
+ checkInit();
+ if (m_TemplateURL != the_value) {
+ m_TemplateURL = the_value;
+ g.clear();
+ setModified(true);
+ }
+}
+
+css::util::DateTime SAL_CALL
+SfxDocumentMetaData::getTemplateDate()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ checkInit();
+ return m_TemplateDate;
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setTemplateDate(const css::util::DateTime & the_value)
+{
+ ::osl::ClearableMutexGuard g(m_aMutex);
+ checkInit();
+ if (m_TemplateDate != the_value) {
+ m_TemplateDate = the_value;
+ g.clear();
+ setModified(true);
+ }
+}
+
+OUString SAL_CALL
+SfxDocumentMetaData::getAutoloadURL()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ checkInit();
+ return m_AutoloadURL;
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setAutoloadURL(const OUString & the_value)
+{
+ ::osl::ClearableMutexGuard g(m_aMutex);
+ checkInit();
+ if (m_AutoloadURL != the_value) {
+ m_AutoloadURL = the_value;
+ g.clear();
+ setModified(true);
+ }
+}
+
+::sal_Int32 SAL_CALL
+SfxDocumentMetaData::getAutoloadSecs()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ checkInit();
+ return m_AutoloadSecs;
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setAutoloadSecs(::sal_Int32 the_value)
+{
+ if (the_value < 0)
+ throw css::lang::IllegalArgumentException(
+ "SfxDocumentMetaData::setAutoloadSecs: argument is negative",
+ *this, 0);
+ ::osl::ClearableMutexGuard g(m_aMutex);
+ checkInit();
+ if (m_AutoloadSecs != the_value) {
+ m_AutoloadSecs = the_value;
+ g.clear();
+ setModified(true);
+ }
+}
+
+OUString SAL_CALL
+SfxDocumentMetaData::getDefaultTarget()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ checkInit();
+ return m_DefaultTarget;
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setDefaultTarget(const OUString & the_value)
+{
+ ::osl::ClearableMutexGuard g(m_aMutex);
+ checkInit();
+ if (m_DefaultTarget != the_value) {
+ m_DefaultTarget = the_value;
+ g.clear();
+ setModified(true);
+ }
+}
+
+css::uno::Sequence< css::beans::NamedValue > SAL_CALL
+SfxDocumentMetaData::getDocumentStatistics()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ checkInit();
+ ::std::vector<css::beans::NamedValue> stats;
+ for (size_t i = 0; s_stdStats[i] != nullptr; ++i) {
+ OUString text = getMetaAttr("meta:document-statistic", s_stdStatAttrs[i]);
+ if (text.isEmpty()) continue;
+ css::beans::NamedValue stat;
+ stat.Name = OUString::createFromAscii(s_stdStats[i]);
+ sal_Int32 val;
+ css::uno::Any any;
+ if (!::sax::Converter::convertNumber(val, text, 0) || (val < 0)) {
+ val = 0;
+ SAL_WARN("sfx.doc", "Invalid number: " << text);
+ }
+ any <<= val;
+ stat.Value = any;
+ stats.push_back(stat);
+ }
+
+ return ::comphelper::containerToSequence(stats);
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setDocumentStatistics(
+ const css::uno::Sequence< css::beans::NamedValue > & the_value)
+{
+ {
+ osl::MutexGuard g(m_aMutex);
+ checkInit();
+ std::vector<std::pair<OUString, OUString> > attributes;
+ for (const auto& rValue : the_value) {
+ const OUString name = rValue.Name;
+ // inefficiently search for matching attribute
+ for (size_t j = 0; s_stdStats[j] != nullptr; ++j) {
+ if (name.equalsAscii(s_stdStats[j])) {
+ const css::uno::Any any = rValue.Value;
+ sal_Int32 val = 0;
+ if (any >>= val) {
+ attributes.emplace_back(s_stdStatAttrs[j],
+ OUString::number(val));
+ }
+ else {
+ SAL_WARN("sfx.doc", "Invalid statistic: " << name);
+ }
+ break;
+ }
+ }
+ }
+ updateElement("meta:document-statistic", &attributes);
+ }
+ setModified(true);
+}
+
+::sal_Int16 SAL_CALL
+SfxDocumentMetaData::getEditingCycles()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ OUString text = getMetaText("meta:editing-cycles");
+ sal_Int32 ret;
+ if (::sax::Converter::convertNumber(ret, text,
+ 0, std::numeric_limits<sal_Int16>::max())) {
+ return static_cast<sal_Int16>(ret);
+ } else {
+ return 0;
+ }
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setEditingCycles(::sal_Int16 the_value)
+{
+ if (the_value < 0)
+ throw css::lang::IllegalArgumentException(
+ "SfxDocumentMetaData::setEditingCycles: argument is negative",
+ *this, 0);
+ setMetaTextAndNotify("meta:editing-cycles", OUString::number(the_value));
+}
+
+::sal_Int32 SAL_CALL
+SfxDocumentMetaData::getEditingDuration()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ return textToDuration(getMetaText("meta:editing-duration"));
+}
+
+void SAL_CALL
+SfxDocumentMetaData::setEditingDuration(::sal_Int32 the_value)
+{
+ if (the_value < 0)
+ throw css::lang::IllegalArgumentException(
+ "SfxDocumentMetaData::setEditingDuration: argument is negative",
+ *this, 0);
+ setMetaTextAndNotify("meta:editing-duration", durationToText(the_value));
+}
+
+void SAL_CALL
+SfxDocumentMetaData::resetUserData(const OUString & the_value)
+{
+ ::osl::ClearableMutexGuard g(m_aMutex);
+
+ bool bModified( false );
+ bModified |= setMetaText("meta:initial-creator", the_value);
+ ::DateTime now( ::DateTime::SYSTEM );
+ css::util::DateTime uDT(now.GetUNODateTime());
+ bModified |= setMetaText("meta:creation-date", dateTimeToText(uDT));
+ bModified |= setMetaText("dc:creator", OUString());
+ bModified |= setMetaText("meta:printed-by", OUString());
+ bModified |= setMetaText("dc:date", dateTimeToText(css::util::DateTime()));
+ bModified |= setMetaText("meta:print-date",
+ dateTimeToText(css::util::DateTime()));
+ bModified |= setMetaText("meta:editing-duration", durationToText(0));
+ bModified |= setMetaText("meta:editing-cycles",
+ "1");
+
+ if (bModified) {
+ g.clear();
+ setModified(true);
+ }
+}
+
+
+css::uno::Reference< css::beans::XPropertyContainer > SAL_CALL
+SfxDocumentMetaData::getUserDefinedProperties()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ checkInit();
+ createUserDefined();
+ return m_xUserDefined;
+}
+
+
+void SAL_CALL
+SfxDocumentMetaData::loadFromStorage(
+ const css::uno::Reference< css::embed::XStorage > & xStorage,
+ const css::uno::Sequence< css::beans::PropertyValue > & Medium)
+{
+ if (!xStorage.is())
+ throw css::lang::IllegalArgumentException("SfxDocumentMetaData::loadFromStorage: argument is null", *this, 0);
+ ::osl::MutexGuard g(m_aMutex);
+
+ // open meta data file
+ css::uno::Reference<css::io::XStream> xStream(
+ xStorage->openStreamElement(
+ s_meta,
+ css::embed::ElementModes::READ) );
+ if (!xStream.is()) throw css::uno::RuntimeException();
+ css::uno::Reference<css::io::XInputStream> xInStream =
+ xStream->getInputStream();
+ if (!xInStream.is()) throw css::uno::RuntimeException();
+
+ // create DOM parser service
+ css::uno::Reference<css::lang::XMultiComponentFactory> xMsf (
+ m_xContext->getServiceManager());
+ css::xml::sax::InputSource input;
+ input.aInputStream = xInStream;
+
+ sal_uInt64 version = SotStorage::GetVersion( xStorage );
+ // Oasis is also the default (0)
+ bool bOasis = ( version > SOFFICE_FILEFORMAT_60 || version == 0 );
+ const char *pServiceName = bOasis
+ ? "com.sun.star.document.XMLOasisMetaImporter"
+ : "com.sun.star.document.XMLMetaImporter";
+
+ // set base URL
+ css::uno::Reference<css::beans::XPropertySet> xPropArg =
+ getURLProperties(Medium);
+ try {
+ xPropArg->getPropertyValue("BaseURI")
+ >>= input.sSystemId;
+ input.sSystemId += OUString::Concat("/") + s_meta;
+ } catch (const css::uno::Exception &) {
+ input.sSystemId = s_meta;
+ }
+ css::uno::Sequence< css::uno::Any > args{ css::uno::Any(xPropArg) };
+
+ // the underlying SvXMLImport implements XFastParser, XImporter, XFastDocumentHandler
+ css::uno::Reference<XInterface> xFilter =
+ xMsf->createInstanceWithArgumentsAndContext(
+ OUString::createFromAscii(pServiceName), args, m_xContext);
+ assert(xFilter);
+ css::uno::Reference<css::xml::sax::XFastParser> xFastParser(xFilter, css::uno::UNO_QUERY);
+ css::uno::Reference<css::document::XImporter> xImp(xFilter, css::uno::UNO_QUERY_THROW);
+ xImp->setTargetDocument(css::uno::Reference<css::lang::XComponent>(this));
+ try {
+ if (xFastParser)
+ xFastParser->parseStream(input);
+ else
+ {
+ css::uno::Reference<css::xml::sax::XDocumentHandler> xDocHandler(xFilter, css::uno::UNO_QUERY_THROW);
+ css::uno::Reference<css::xml::sax::XParser> xParser = css::xml::sax::Parser::create(m_xContext);
+ xParser->setDocumentHandler(xDocHandler);
+ xParser->parseStream(input);
+ }
+ } catch (const css::xml::sax::SAXException &) {
+ throw css::io::WrongFormatException(
+ "SfxDocumentMetaData::loadFromStorage:"
+ " XML parsing exception", *this);
+ }
+ // NB: the implementation of XMLOasisMetaImporter calls initialize
+ checkInit();
+}
+
+void SAL_CALL
+SfxDocumentMetaData::storeToStorage(
+ const css::uno::Reference< css::embed::XStorage > & xStorage,
+ const css::uno::Sequence< css::beans::PropertyValue > & Medium)
+{
+ if (!xStorage.is())
+ throw css::lang::IllegalArgumentException(
+ "SfxDocumentMetaData::storeToStorage: argument is null", *this, 0);
+ ::osl::MutexGuard g(m_aMutex);
+ checkInit();
+
+ // update user-defined meta data in DOM tree
+// updateUserDefinedAndAttributes(); // this will be done in serialize!
+
+ // write into storage
+ css::uno::Reference<css::io::XStream> xStream =
+ xStorage->openStreamElement(s_meta,
+ css::embed::ElementModes::WRITE
+ | css::embed::ElementModes::TRUNCATE);
+ if (!xStream.is()) throw css::uno::RuntimeException();
+ css::uno::Reference< css::beans::XPropertySet > xStreamProps(xStream,
+ css::uno::UNO_QUERY_THROW);
+ xStreamProps->setPropertyValue(
+ "MediaType",
+ css::uno::Any(OUString("text/xml")));
+ xStreamProps->setPropertyValue(
+ "Compressed",
+ css::uno::Any(false));
+ xStreamProps->setPropertyValue(
+ "UseCommonStoragePasswordEncryption",
+ css::uno::Any(false));
+ css::uno::Reference<css::io::XOutputStream> xOutStream =
+ xStream->getOutputStream();
+ if (!xOutStream.is()) throw css::uno::RuntimeException();
+ css::uno::Reference<css::lang::XMultiComponentFactory> xMsf (
+ m_xContext->getServiceManager());
+ css::uno::Reference<css::xml::sax::XWriter> xSaxWriter(
+ css::xml::sax::Writer::create(m_xContext));
+ xSaxWriter->setOutputStream(xOutStream);
+
+ const sal_uInt64 version = SotStorage::GetVersion( xStorage );
+ // Oasis is also the default (0)
+ const bool bOasis = ( version > SOFFICE_FILEFORMAT_60 || version == 0 );
+ const char *pServiceName = bOasis
+ ? "com.sun.star.document.XMLOasisMetaExporter"
+ : "com.sun.star.document.XMLMetaExporter";
+
+ // set base URL
+ css::uno::Reference<css::beans::XPropertySet> xPropArg =
+ getURLProperties(Medium);
+ css::uno::Sequence< css::uno::Any > args{ css::uno::Any(xSaxWriter), css::uno::Any(xPropArg) };
+
+ css::uno::Reference<css::document::XExporter> xExp(
+ xMsf->createInstanceWithArgumentsAndContext(
+ OUString::createFromAscii(pServiceName), args, m_xContext),
+ css::uno::UNO_QUERY_THROW);
+ xExp->setSourceDocument(css::uno::Reference<css::lang::XComponent>(this));
+ css::uno::Reference<css::document::XFilter> xFilter(xExp,
+ css::uno::UNO_QUERY_THROW);
+ if (!xFilter->filter(css::uno::Sequence< css::beans::PropertyValue >())) {
+ throw css::io::IOException(
+ "SfxDocumentMetaData::storeToStorage: cannot filter", *this);
+ }
+ css::uno::Reference<css::embed::XTransactedObject> xTransaction(
+ xStorage, css::uno::UNO_QUERY);
+ if (xTransaction.is()) {
+ xTransaction->commit();
+ }
+}
+
+void SAL_CALL
+SfxDocumentMetaData::loadFromMedium(const OUString & URL,
+ const css::uno::Sequence< css::beans::PropertyValue > & Medium)
+{
+ css::uno::Reference<css::io::XInputStream> xIn;
+ utl::MediaDescriptor md(Medium);
+ // if we have a URL parameter, it replaces the one in the media descriptor
+ if (!URL.isEmpty()) {
+ md[ utl::MediaDescriptor::PROP_URL ] <<= URL;
+ md[ utl::MediaDescriptor::PROP_READONLY ] <<= true;
+ }
+ if (md.addInputStream()) {
+ md[ utl::MediaDescriptor::PROP_INPUTSTREAM ] >>= xIn;
+ }
+ css::uno::Reference<css::embed::XStorage> xStorage;
+ try {
+ if (xIn.is()) {
+ xStorage = ::comphelper::OStorageHelper::GetStorageFromInputStream(
+ xIn, m_xContext);
+ } else { // fallback to url parameter
+ xStorage = ::comphelper::OStorageHelper::GetStorageFromURL(
+ URL, css::embed::ElementModes::READ, m_xContext);
+ }
+ } catch (const css::uno::RuntimeException &) {
+ throw;
+ } catch (const css::io::IOException &) {
+ throw;
+ } catch (const css::uno::Exception &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw css::lang::WrappedTargetException(
+ "SfxDocumentMetaData::loadFromMedium: exception",
+ css::uno::Reference<css::uno::XInterface>(*this),
+ anyEx);
+ }
+ if (!xStorage.is()) {
+ throw css::uno::RuntimeException(
+ "SfxDocumentMetaData::loadFromMedium: cannot get Storage",
+ *this);
+ }
+ loadFromStorage(xStorage, md.getAsConstPropertyValueList());
+}
+
+void SAL_CALL
+SfxDocumentMetaData::storeToMedium(const OUString & URL,
+ const css::uno::Sequence< css::beans::PropertyValue > & Medium)
+{
+ utl::MediaDescriptor md(Medium);
+ if (!URL.isEmpty()) {
+ md[ utl::MediaDescriptor::PROP_URL ] <<= URL;
+ }
+ SfxMedium aMedium(md.getAsConstPropertyValueList());
+ css::uno::Reference<css::embed::XStorage> xStorage
+ = aMedium.GetOutputStorage();
+
+
+ if (!xStorage.is()) {
+ throw css::uno::RuntimeException(
+ "SfxDocumentMetaData::storeToMedium: cannot get Storage",
+ *this);
+ }
+ // set MIME type of the storage
+ utl::MediaDescriptor::const_iterator iter
+ = md.find(utl::MediaDescriptor::PROP_MEDIATYPE);
+ if (iter != md.end()) {
+ css::uno::Reference< css::beans::XPropertySet > xProps(xStorage,
+ css::uno::UNO_QUERY_THROW);
+ xProps->setPropertyValue(
+ utl::MediaDescriptor::PROP_MEDIATYPE,
+ iter->second);
+ }
+ storeToStorage(xStorage, md.getAsConstPropertyValueList());
+
+
+ const bool bOk = aMedium.Commit();
+ aMedium.Close();
+ if ( !bOk ) {
+ ErrCodeMsg nError = aMedium.GetErrorIgnoreWarning();
+ if ( nError == ERRCODE_NONE ) {
+ nError = ERRCODE_IO_GENERAL;
+ }
+
+ throw css::task::ErrorCodeIOException(
+ "SfxDocumentMetaData::storeToMedium <" + URL + "> Commit failed: " + nError.toString(),
+ css::uno::Reference< css::uno::XInterface >(), sal_uInt32(nError.GetCode()));
+
+ }
+}
+
+// css::lang::XInitialization:
+void SAL_CALL SfxDocumentMetaData::initialize( const css::uno::Sequence< css::uno::Any > & aArguments)
+{
+ // possible arguments:
+ // - no argument: default initialization (empty DOM)
+ // - 1 argument, XDocument: initialize with given DOM and empty base URL
+ // NB: links in document must be absolute
+
+ ::osl::MutexGuard g(m_aMutex);
+ css::uno::Reference<css::xml::dom::XDocument> xDoc;
+
+ for (sal_Int32 i = 0; i < aArguments.getLength(); ++i) {
+ const css::uno::Any any = aArguments[i];
+ if (!(any >>= xDoc)) {
+ throw css::lang::IllegalArgumentException(
+ "SfxDocumentMetaData::initialize: argument must be XDocument",
+ *this, static_cast<sal_Int16>(i));
+ }
+ if (!xDoc.is()) {
+ throw css::lang::IllegalArgumentException(
+ "SfxDocumentMetaData::initialize: argument is null",
+ *this, static_cast<sal_Int16>(i));
+ }
+ }
+
+ if (!xDoc.is()) {
+ // For a new document, we create a new DOM tree here.
+ xDoc = createDOM();
+ }
+
+ init(xDoc);
+}
+
+// css::util::XCloneable:
+css::uno::Reference<css::util::XCloneable> SAL_CALL
+SfxDocumentMetaData::createClone()
+{
+ ::osl::MutexGuard g(m_aMutex);
+ checkInit();
+
+ rtl::Reference<SfxDocumentMetaData> pNew = createMe(m_xContext);
+
+ // NB: do not copy the modification listeners, only DOM
+ css::uno::Reference<css::xml::dom::XDocument> xDoc = createDOM();
+ try {
+ updateUserDefinedAndAttributes();
+ // deep copy of root node
+ css::uno::Reference<css::xml::dom::XNode> xRoot(
+ m_xDoc->getDocumentElement(), css::uno::UNO_QUERY_THROW);
+ css::uno::Reference<css::xml::dom::XNode> xRootNew(
+ xDoc->importNode(xRoot, true));
+ xDoc->appendChild(xRootNew);
+ pNew->init(xDoc);
+ } catch (const css::uno::RuntimeException &) {
+ throw;
+ } catch (const css::uno::Exception &) {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw css::lang::WrappedTargetRuntimeException(
+ "SfxDocumentMetaData::createClone: exception",
+ css::uno::Reference<css::uno::XInterface>(*this), anyEx);
+ }
+ return css::uno::Reference<css::util::XCloneable> (pNew);
+}
+
+// css::util::XModifiable:
+sal_Bool SAL_CALL SfxDocumentMetaData::isModified( )
+{
+ ::osl::MutexGuard g(m_aMutex);
+ checkInit();
+ css::uno::Reference<css::util::XModifiable> xMB(m_xUserDefined,
+ css::uno::UNO_QUERY);
+ return m_isModified || (xMB.is() && xMB->isModified());
+}
+
+void SAL_CALL SfxDocumentMetaData::setModified( sal_Bool bModified )
+{
+ css::uno::Reference<css::util::XModifiable> xMB;
+ { // do not lock mutex while notifying (#i93514#) to prevent deadlock
+ ::osl::MutexGuard g(m_aMutex);
+ checkInit();
+ m_isModified = bModified;
+ if ( !bModified && m_xUserDefined.is() )
+ {
+ xMB.set(m_xUserDefined, css::uno::UNO_QUERY);
+ assert(xMB.is() &&
+ "SfxDocumentMetaData::setModified: PropertyBag not Modifiable?");
+ }
+ }
+ if (bModified) {
+ try {
+ css::uno::Reference<css::uno::XInterface> xThis(*this);
+ css::lang::EventObject event(xThis);
+ m_NotifyListeners.notifyEach(&css::util::XModifyListener::modified,
+ event);
+ } catch (const css::uno::RuntimeException &) {
+ throw;
+ } catch (const css::uno::Exception &) {
+ // ignore
+ TOOLS_WARN_EXCEPTION("sfx.doc", "setModified");
+ }
+ } else {
+ if (xMB.is()) {
+ xMB->setModified(false);
+ }
+ }
+}
+
+// css::util::XModifyBroadcaster:
+void SAL_CALL SfxDocumentMetaData::addModifyListener(
+ const css::uno::Reference< css::util::XModifyListener > & xListener)
+{
+ ::osl::MutexGuard g(m_aMutex);
+ checkInit();
+ m_NotifyListeners.addInterface(xListener);
+ css::uno::Reference<css::util::XModifyBroadcaster> xMB(m_xUserDefined,
+ css::uno::UNO_QUERY);
+ if (xMB.is()) {
+ xMB->addModifyListener(xListener);
+ }
+}
+
+void SAL_CALL SfxDocumentMetaData::removeModifyListener(
+ const css::uno::Reference< css::util::XModifyListener > & xListener)
+{
+ ::osl::MutexGuard g(m_aMutex);
+ checkInit();
+ m_NotifyListeners.removeInterface(xListener);
+ css::uno::Reference<css::util::XModifyBroadcaster> xMB(m_xUserDefined,
+ css::uno::UNO_QUERY);
+ if (xMB.is()) {
+ xMB->removeModifyListener(xListener);
+ }
+}
+
+// css::xml::sax::XSAXSerializable
+void SAL_CALL SfxDocumentMetaData::serialize(
+ const css::uno::Reference<css::xml::sax::XDocumentHandler>& i_xHandler,
+ const css::uno::Sequence< css::beans::StringPair >& i_rNamespaces)
+{
+ ::osl::MutexGuard g(m_aMutex);
+ checkInit();
+ updateUserDefinedAndAttributes();
+ css::uno::Reference<css::xml::sax::XSAXSerializable> xSAXable(m_xDoc,
+ css::uno::UNO_QUERY_THROW);
+ xSAXable->serialize(i_xHandler, i_rNamespaces);
+}
+
+void SfxDocumentMetaData::createUserDefined()
+{
+ // user-defined meta data: create PropertyBag which only accepts property
+ // values of allowed types
+ if ( m_xUserDefined.is() )
+ return;
+
+ css::uno::Sequence<css::uno::Type> types{
+ ::cppu::UnoType<bool>::get(),
+ ::cppu::UnoType< OUString>::get(),
+ ::cppu::UnoType<css::util::DateTime>::get(),
+ ::cppu::UnoType<css::util::Date>::get(),
+ ::cppu::UnoType<css::util::DateTimeWithTimezone>::get(),
+ ::cppu::UnoType<css::util::DateWithTimezone>::get(),
+ ::cppu::UnoType<css::util::Duration>::get(),
+ ::cppu::UnoType<float>::get(),
+ ::cppu::UnoType<double>::get(),
+ ::cppu::UnoType<sal_Int16>::get(),
+ ::cppu::UnoType<sal_Int32>::get(),
+ ::cppu::UnoType<sal_Int64>::get(),
+ // Time is supported for backward compatibility with OOo 3.x, x<=2
+ ::cppu::UnoType<css::util::Time>::get()
+ };
+ // #i94175#: ODF allows empty user-defined property names!
+ m_xUserDefined.set(
+ css::beans::PropertyBag::createWithTypes( m_xContext, types, true/*AllowEmptyPropertyName*/, false/*AutomaticAddition*/ ),
+ css::uno::UNO_QUERY_THROW);
+
+ const css::uno::Reference<css::util::XModifyBroadcaster> xMB(
+ m_xUserDefined, css::uno::UNO_QUERY);
+ if (xMB.is())
+ {
+ const std::vector<css::uno::Reference<css::util::XModifyListener> >
+ listeners(m_NotifyListeners.getElements());
+ for (const auto& l : listeners) {
+ xMB->addModifyListener(l);
+ }
+ }
+}
+
+} // closing anonymous implementation namespace
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+CompatWriterDocPropsImpl_get_implementation(
+ css::uno::XComponentContext *context,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new CompatWriterDocPropsImpl(context));
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+SfxDocumentMetaData_get_implementation(
+ css::uno::XComponentContext *context,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new SfxDocumentMetaData(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/SfxRedactionHelper.cxx b/sfx2/source/doc/SfxRedactionHelper.cxx
new file mode 100644
index 0000000000..ced6158990
--- /dev/null
+++ b/sfx2/source/doc/SfxRedactionHelper.cxx
@@ -0,0 +1,560 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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/.
+ */
+
+#include <SfxRedactionHelper.hxx>
+#include <autoredactdialog.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/drawing/LineStyle.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/frame/XLayoutManager.hpp>
+
+// For page margin related methods
+#include <com/sun/star/style/XStyle.hpp>
+#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
+#include <com/sun/star/text/XPageCursor.hpp>
+#include <com/sun/star/text/XTextViewCursorSupplier.hpp>
+#include <com/sun/star/sheet/XSpreadsheetView.hpp>
+
+// Search util
+#include <i18nutil/searchopt.hxx>
+#include <com/sun/star/util/SearchAlgorithms.hpp>
+#include <com/sun/star/util/SearchAlgorithms2.hpp>
+#include <com/sun/star/util/SearchFlags.hpp>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <unotools/textsearch.hxx>
+
+#include <sfx2/request.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <sfx2/viewfrm.hxx>
+
+#include <svl/eitem.hxx>
+#include <svl/stritem.hxx>
+
+#include <svtools/DocumentToGraphicRenderer.hxx>
+
+#include <tools/gen.hxx>
+#include <tools/stream.hxx>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/graph.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/wmf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/vcllayout.hxx>
+#include <o3tl/string_view.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+bool SfxRedactionHelper::isRedactMode(const SfxRequest& rReq)
+{
+ const SfxItemSet* pArgs = rReq.GetArgs();
+ if (pArgs)
+ {
+ const SfxBoolItem* pIsRedactMode = rReq.GetArg<SfxBoolItem>(SID_IS_REDACT_MODE);
+ if (pIsRedactMode && pIsRedactMode->GetValue())
+ return true;
+ }
+
+ return false;
+}
+
+OUString SfxRedactionHelper::getStringParam(const SfxRequest& rReq, sal_uInt16 nParamId)
+{
+ OUString sStringParam;
+
+ const SfxItemSet* pArgs = rReq.GetArgs();
+ if (!pArgs)
+ return sStringParam;
+
+ const SfxStringItem* pStringArg = rReq.GetArg<SfxStringItem>(nParamId);
+ if (!pStringArg)
+ return sStringParam;
+
+ sStringParam = pStringArg->GetValue();
+ return sStringParam;
+}
+
+namespace
+{
+/*
+ * Roundtrip the gdimetafile to and from WMF
+ * to get rid of the position and size irregularities
+ * We better check the conversion method to see what it
+ * actually does to correct these issues, and do it ourselves.
+ * */
+void fixMetaFile(GDIMetaFile& tmpMtf)
+{
+ SvMemoryStream aDestStrm(65535, 65535);
+ ConvertGDIMetaFileToWMF(tmpMtf, aDestStrm, nullptr, false);
+ aDestStrm.Seek(0);
+
+ tmpMtf.Clear();
+
+ ReadWindowMetafile(aDestStrm, tmpMtf);
+}
+
+/*
+ * Sets page margins for a Draw page. Negative values are considered erroneous
+ * */
+void setPageMargins(const uno::Reference<beans::XPropertySet>& xPageProperySet,
+ const PageMargins& aPageMargins)
+{
+ if (aPageMargins.nTop < 0 || aPageMargins.nBottom < 0 || aPageMargins.nLeft < 0
+ || aPageMargins.nRight < 0)
+ return;
+
+ xPageProperySet->setPropertyValue("BorderTop", css::uno::Any(aPageMargins.nTop));
+ xPageProperySet->setPropertyValue("BorderBottom", css::uno::Any(aPageMargins.nBottom));
+ xPageProperySet->setPropertyValue("BorderLeft", css::uno::Any(aPageMargins.nLeft));
+ xPageProperySet->setPropertyValue("BorderRight", css::uno::Any(aPageMargins.nRight));
+}
+
+// #i10613# Extracted from ImplCheckRect::ImplCreate
+tools::Rectangle ImplCalcActionBounds(const MetaAction& rAct, const OutputDevice& rOut,
+ sal_Int32 nStrStartPos, sal_Int32 nStrEndPos)
+{
+ tools::Rectangle aActionBounds;
+
+ switch (rAct.GetType())
+ {
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction& rTextAct = static_cast<const MetaTextArrayAction&>(rAct);
+ const OUString aString(rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()));
+
+ if (!aString.isEmpty())
+ {
+ // #105987# ImplLayout takes everything in logical coordinates
+ std::unique_ptr<SalLayout> pSalLayout1 = rOut.ImplLayout(
+ aString, 0, nStrStartPos, rTextAct.GetPoint(), 0, rTextAct.GetDXArray());
+ std::unique_ptr<SalLayout> pSalLayout2 = rOut.ImplLayout(
+ aString, 0, nStrEndPos, rTextAct.GetPoint(), 0, rTextAct.GetDXArray());
+ if (pSalLayout2)
+ {
+ tools::Rectangle aBoundRect2(rOut.ImplGetTextBoundRect(*pSalLayout2));
+ aActionBounds = rOut.PixelToLogic(aBoundRect2);
+ }
+ if (pSalLayout1 && nStrStartPos > 0)
+ {
+ tools::Rectangle aBoundRect1(rOut.ImplGetTextBoundRect(*pSalLayout1));
+ aActionBounds.SetLeft(rOut.PixelToLogic(aBoundRect1).Right());
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (!aActionBounds.IsEmpty())
+ {
+ // fdo#40421 limit current action's output to clipped area
+ if (rOut.IsClipRegion())
+ return rOut.GetClipRegion().GetBoundRect().Intersection(aActionBounds);
+ else
+ return aActionBounds;
+ }
+ else
+ return aActionBounds;
+}
+
+} // End of anon namespace
+
+void SfxRedactionHelper::getPageMetaFilesFromDoc(std::vector<GDIMetaFile>& aMetaFiles,
+ std::vector<::Size>& aPageSizes, sal_Int32 nPages,
+ DocumentToGraphicRenderer& aRenderer)
+{
+ for (sal_Int32 nPage = 1; nPage <= nPages; ++nPage)
+ {
+ ::Size aDocumentSizePixel = aRenderer.getDocumentSizeInPixels(nPage);
+ ::Point aLogicPos;
+ ::Point aCalcPageLogicPos;
+ ::Size aCalcPageContentSize;
+ ::Size aLogic = aRenderer.getDocumentSizeIn100mm(nPage, &aLogicPos, &aCalcPageLogicPos,
+ &aCalcPageContentSize);
+
+ aPageSizes.push_back(aLogic);
+
+ Graphic aGraphic = aRenderer.renderToGraphic(nPage, aDocumentSizePixel, aDocumentSizePixel,
+ COL_TRANSPARENT, true);
+ auto& rGDIMetaFile = const_cast<GDIMetaFile&>(aGraphic.GetGDIMetaFile());
+
+ // Set preferred map unit and size on the metafile, so the Shape size
+ // will be correct in MM.
+ MapMode aMapMode;
+ aMapMode.SetMapUnit(MapUnit::Map100thMM);
+
+ rGDIMetaFile.SetPrefMapMode(aMapMode);
+ rGDIMetaFile.SetPrefSize(aLogic);
+
+ fixMetaFile(rGDIMetaFile);
+
+ aMetaFiles.push_back(rGDIMetaFile);
+ }
+}
+
+void SfxRedactionHelper::addPagesToDraw(
+ const uno::Reference<XComponent>& xComponent, sal_Int32 nPages,
+ const std::vector<GDIMetaFile>& aMetaFiles, const std::vector<::Size>& aPageSizes,
+ const PageMargins& aPageMargins,
+ const std::vector<std::pair<RedactionTarget, OUString>>& r_aTableTargets, bool bIsAutoRedact)
+{
+ // Access the draw pages
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(xComponent, uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPages> xDrawPages = xDrawPagesSupplier->getDrawPages();
+
+ uno::Reference<css::lang::XMultiServiceFactory> xFactory(xComponent, uno::UNO_QUERY);
+
+ for (sal_Int32 nPage = 0; nPage < nPages; ++nPage)
+ {
+ GDIMetaFile rGDIMetaFile = aMetaFiles[nPage];
+ Graphic aGraphic(rGDIMetaFile);
+
+ sal_Int32 nPageHeight(aPageSizes[nPage].Height());
+ sal_Int32 nPageWidth(aPageSizes[nPage].Width());
+
+ uno::Reference<graphic::XGraphic> xGraph = aGraphic.GetXGraphic();
+ uno::Reference<drawing::XDrawPage> xPage = xDrawPages->insertNewByIndex(nPage);
+
+ // Set page size & margins
+ uno::Reference<beans::XPropertySet> xPageProperySet(xPage, uno::UNO_QUERY);
+ xPageProperySet->setPropertyValue("Height", css::uno::Any(nPageHeight));
+ xPageProperySet->setPropertyValue("Width", css::uno::Any(nPageWidth));
+
+ setPageMargins(xPageProperySet, aPageMargins);
+
+ // Create and insert the shape
+ uno::Reference<drawing::XShape> xShape(
+ xFactory->createInstance("com.sun.star.drawing.GraphicObjectShape"), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xShapeProperySet(xShape, uno::UNO_QUERY);
+ xShapeProperySet->setPropertyValue("Graphic", uno::Any(xGraph));
+ xShapeProperySet->setPropertyValue("MoveProtect", uno::Any(true));
+ xShapeProperySet->setPropertyValue("SizeProtect", uno::Any(true));
+
+ // Set size
+ xShape->setSize(
+ awt::Size(rGDIMetaFile.GetPrefSize().Width(), rGDIMetaFile.GetPrefSize().Height()));
+
+ xPage->add(xShape);
+
+ if (bIsAutoRedact && !r_aTableTargets.empty())
+ {
+ for (const auto& targetPair : r_aTableTargets)
+ {
+ autoRedactPage(targetPair.first, rGDIMetaFile, xPage, xComponent);
+ }
+ }
+ }
+
+ // Remove the extra page at the beginning
+ uno::Reference<drawing::XDrawPage> xPage(xDrawPages->getByIndex(0), uno::UNO_QUERY_THROW);
+ xDrawPages->remove(xPage);
+}
+
+void SfxRedactionHelper::showRedactionToolbar(const SfxViewFrame* pViewFrame)
+{
+ if (!pViewFrame)
+ return;
+
+ Reference<frame::XFrame> xFrame = pViewFrame->GetFrame().GetFrameInterface();
+ Reference<css::beans::XPropertySet> xPropSet(xFrame, UNO_QUERY);
+ Reference<css::frame::XLayoutManager> xLayoutManager;
+
+ if (!xPropSet.is())
+ return;
+
+ try
+ {
+ Any aValue = xPropSet->getPropertyValue("LayoutManager");
+ aValue >>= xLayoutManager;
+ xLayoutManager->createElement("private:resource/toolbar/redactionbar");
+ xLayoutManager->showElement("private:resource/toolbar/redactionbar");
+ }
+ catch (const css::uno::RuntimeException&)
+ {
+ throw;
+ }
+ catch (css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "Exception while trying to show the Redaction Toolbar!");
+ }
+}
+
+PageMargins
+SfxRedactionHelper::getPageMarginsForWriter(const css::uno::Reference<css::frame::XModel>& xModel)
+{
+ PageMargins aPageMargins = { -1, -1, -1, -1 };
+
+ Reference<text::XTextViewCursorSupplier> xTextViewCursorSupplier(xModel->getCurrentController(),
+ UNO_QUERY);
+ if (!xTextViewCursorSupplier.is())
+ {
+ SAL_WARN("sfx.doc", "Ref to xTextViewCursorSupplier is null in setPageMargins().");
+ return aPageMargins;
+ }
+
+ Reference<text::XPageCursor> xCursor(xTextViewCursorSupplier->getViewCursor(), UNO_QUERY);
+
+ uno::Reference<beans::XPropertySet> xPageProperySet(xCursor, UNO_QUERY);
+ OUString sPageStyleName;
+ Any aValue = xPageProperySet->getPropertyValue("PageStyleName");
+ aValue >>= sPageStyleName;
+
+ Reference<css::style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, UNO_QUERY);
+ if (!xStyleFamiliesSupplier.is())
+ {
+ SAL_WARN("sfx.doc", "Ref to xStyleFamiliesSupplier is null in setPageMargins().");
+ return aPageMargins;
+ }
+ uno::Reference<container::XNameAccess> xStyleFamilies
+ = xStyleFamiliesSupplier->getStyleFamilies();
+
+ if (!xStyleFamilies.is())
+ return aPageMargins;
+
+ uno::Reference<container::XNameAccess> xPageStyles(xStyleFamilies->getByName("PageStyles"),
+ UNO_QUERY);
+
+ if (!xPageStyles.is())
+ return aPageMargins;
+
+ uno::Reference<css::style::XStyle> xPageStyle(xPageStyles->getByName(sPageStyleName),
+ UNO_QUERY);
+
+ if (!xPageStyle.is())
+ return aPageMargins;
+
+ uno::Reference<beans::XPropertySet> xPageProperties(xPageStyle, uno::UNO_QUERY);
+
+ if (!xPageProperties.is())
+ return aPageMargins;
+
+ xPageProperties->getPropertyValue("LeftMargin") >>= aPageMargins.nLeft;
+ xPageProperties->getPropertyValue("RightMargin") >>= aPageMargins.nRight;
+ xPageProperties->getPropertyValue("TopMargin") >>= aPageMargins.nTop;
+ xPageProperties->getPropertyValue("BottomMargin") >>= aPageMargins.nBottom;
+
+ return aPageMargins;
+}
+
+PageMargins
+SfxRedactionHelper::getPageMarginsForCalc(const css::uno::Reference<css::frame::XModel>& xModel)
+{
+ PageMargins aPageMargins = { -1, -1, -1, -1 };
+ OUString sPageStyleName("Default");
+
+ css::uno::Reference<css::sheet::XSpreadsheetView> xSpreadsheetView(
+ xModel->getCurrentController(), UNO_QUERY);
+
+ if (!xSpreadsheetView.is())
+ {
+ SAL_WARN("sfx.doc", "Ref to xSpreadsheetView is null in getPageMarginsForCalc().");
+ return aPageMargins;
+ }
+
+ uno::Reference<beans::XPropertySet> xSheetProperties(xSpreadsheetView->getActiveSheet(),
+ UNO_QUERY);
+
+ xSheetProperties->getPropertyValue("PageStyle") >>= sPageStyleName;
+
+ Reference<css::style::XStyleFamiliesSupplier> xStyleFamiliesSupplier(xModel, UNO_QUERY);
+ if (!xStyleFamiliesSupplier.is())
+ {
+ SAL_WARN("sfx.doc", "Ref to xStyleFamiliesSupplier is null in getPageMarginsForCalc().");
+ return aPageMargins;
+ }
+ uno::Reference<container::XNameAccess> xStyleFamilies
+ = xStyleFamiliesSupplier->getStyleFamilies();
+
+ if (!xStyleFamilies.is())
+ return aPageMargins;
+
+ uno::Reference<container::XNameAccess> xPageStyles(xStyleFamilies->getByName("PageStyles"),
+ UNO_QUERY);
+
+ if (!xPageStyles.is())
+ return aPageMargins;
+
+ uno::Reference<css::style::XStyle> xPageStyle(xPageStyles->getByName(sPageStyleName),
+ UNO_QUERY);
+
+ if (!xPageStyle.is())
+ return aPageMargins;
+
+ uno::Reference<beans::XPropertySet> xPageProperties(xPageStyle, uno::UNO_QUERY);
+
+ if (!xPageProperties.is())
+ return aPageMargins;
+
+ xPageProperties->getPropertyValue("LeftMargin") >>= aPageMargins.nLeft;
+ xPageProperties->getPropertyValue("RightMargin") >>= aPageMargins.nRight;
+ xPageProperties->getPropertyValue("TopMargin") >>= aPageMargins.nTop;
+ xPageProperties->getPropertyValue("BottomMargin") >>= aPageMargins.nBottom;
+
+ return aPageMargins;
+}
+
+void SfxRedactionHelper::searchInMetaFile(const RedactionTarget& rRedactionTarget,
+ const GDIMetaFile& rMtf,
+ std::vector<::tools::Rectangle>& aRedactionRectangles,
+ const uno::Reference<XComponent>& xComponent)
+{
+ // Initialize search
+ i18nutil::SearchOptions2 aSearchOptions;
+ fillSearchOptions(aSearchOptions, rRedactionTarget);
+
+ utl::TextSearch textSearch(aSearchOptions);
+ static tools::Long aLastFontHeight = 0;
+
+ MetaAction* pCurrAct;
+
+ for (pCurrAct = const_cast<GDIMetaFile&>(rMtf).FirstAction(); pCurrAct;
+ pCurrAct = const_cast<GDIMetaFile&>(rMtf).NextAction())
+ {
+ // Watch for TEXTARRAY actions.
+ // They contain the text of paragraphs.
+ if (pCurrAct->GetType() == MetaActionType::TEXTARRAY)
+ {
+ MetaTextArrayAction* pMetaTextArrayAction = static_cast<MetaTextArrayAction*>(pCurrAct);
+
+ // Search operation takes place here
+ OUString sText = pMetaTextArrayAction->GetText();
+ sal_Int32 nStart = 0;
+ sal_Int32 nEnd = sText.getLength();
+
+ bool bFound = textSearch.SearchForward(sText, &nStart, &nEnd);
+
+ // If found the string, add the corresponding rectangle to the collection
+ while (bFound)
+ {
+ OutputDevice* pOutputDevice
+ = SfxObjectShell::GetShellFromComponent(xComponent)->GetDocumentRefDev();
+ tools::Rectangle aNewRect(
+ ImplCalcActionBounds(*pMetaTextArrayAction, *pOutputDevice, nStart, nEnd));
+
+ if (!aNewRect.IsEmpty())
+ {
+ // Calculate the difference between current wrong value and value should it be.
+ // Add the difference to current value.
+ // Then increase 10% of the new value to make it look better.
+ aNewRect.SetTop(aNewRect.Bottom() - aLastFontHeight - aLastFontHeight / 10);
+ aRedactionRectangles.push_back(aNewRect);
+ }
+
+ // Search for the next occurrence
+ nStart = nEnd;
+ nEnd = sText.getLength();
+ bFound = textSearch.SearchForward(sText, &nStart, &nEnd);
+ }
+ }
+ else if (pCurrAct->GetType() == MetaActionType::FONT)
+ {
+ const MetaFontAction* pFontAct = static_cast<const MetaFontAction*>(pCurrAct);
+ aLastFontHeight = pFontAct->GetFont().GetFontSize().getHeight();
+ }
+ }
+}
+
+void SfxRedactionHelper::addRedactionRectToPage(
+ const uno::Reference<XComponent>& xComponent, const uno::Reference<drawing::XDrawPage>& xPage,
+ const std::vector<::tools::Rectangle>& aNewRectangles)
+{
+ if (!xComponent.is() || !xPage.is())
+ return;
+
+ if (aNewRectangles.empty())
+ return;
+
+ uno::Reference<css::lang::XMultiServiceFactory> xFactory(xComponent, uno::UNO_QUERY);
+
+ for (auto const& aNewRectangle : aNewRectangles)
+ {
+ uno::Reference<drawing::XShape> xRectShape(
+ xFactory->createInstance("com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xRectShapeProperySet(xRectShape, uno::UNO_QUERY);
+
+ xRectShapeProperySet->setPropertyValue("Name",
+ uno::Any(OUString("RectangleRedactionShape")));
+ xRectShapeProperySet->setPropertyValue("FillTransparence",
+ css::uno::Any(static_cast<sal_Int16>(50)));
+ xRectShapeProperySet->setPropertyValue("FillColor", css::uno::Any(COL_GRAY7));
+ xRectShapeProperySet->setPropertyValue(
+ "LineStyle", css::uno::Any(css::drawing::LineStyle::LineStyle_NONE));
+
+ xRectShape->setSize(awt::Size(aNewRectangle.GetWidth(), aNewRectangle.GetHeight()));
+ xRectShape->setPosition(awt::Point(aNewRectangle.Left(), aNewRectangle.Top()));
+
+ xPage->add(xRectShape);
+ }
+}
+
+void SfxRedactionHelper::autoRedactPage(const RedactionTarget& rRedactionTarget,
+ const GDIMetaFile& rGDIMetaFile,
+ const uno::Reference<drawing::XDrawPage>& xPage,
+ const uno::Reference<XComponent>& xComponent)
+{
+ if (rRedactionTarget.sContent.isEmpty())
+ return;
+
+ // Search for the redaction strings, and get the rectangle coordinates
+ std::vector<::tools::Rectangle> aRedactionRectangles;
+ searchInMetaFile(rRedactionTarget, rGDIMetaFile, aRedactionRectangles, xComponent);
+
+ // Add the redaction rectangles to the page
+ addRedactionRectToPage(xComponent, xPage, aRedactionRectangles);
+}
+
+namespace
+{
+const LanguageTag& GetAppLanguageTag() { return Application::GetSettings().GetLanguageTag(); }
+}
+
+void SfxRedactionHelper::fillSearchOptions(i18nutil::SearchOptions2& rSearchOpt,
+ const RedactionTarget& rTarget)
+{
+ if (rTarget.sType == RedactionTargetType::REDACTION_TARGET_REGEX
+ || rTarget.sType == RedactionTargetType::REDACTION_TARGET_PREDEFINED)
+ {
+ rSearchOpt.AlgorithmType2 = util::SearchAlgorithms2::REGEXP;
+ }
+ else
+ {
+ rSearchOpt.AlgorithmType2 = util::SearchAlgorithms2::ABSOLUTE;
+ }
+
+ rSearchOpt.Locale = GetAppLanguageTag().getLocale();
+ if (rTarget.sType == RedactionTargetType::REDACTION_TARGET_PREDEFINED)
+ {
+ auto nPredefIndex = o3tl::toUInt32(o3tl::getToken(rTarget.sContent, 0, ';'));
+ rSearchOpt.searchString = m_aPredefinedTargets[nPredefIndex];
+ }
+ else
+ rSearchOpt.searchString = rTarget.sContent;
+
+ rSearchOpt.replaceString.clear();
+
+ if (!rTarget.bCaseSensitive && rTarget.sType != RedactionTargetType::REDACTION_TARGET_REGEX
+ && rTarget.sType != RedactionTargetType::REDACTION_TARGET_PREDEFINED)
+ rSearchOpt.transliterateFlags |= TransliterationFlags::IGNORE_CASE;
+ if (rTarget.bWholeWords)
+ rSearchOpt.searchFlag |= util::SearchFlags::NORM_WORD_ONLY;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/sfx2/source/doc/autoredactdialog.cxx b/sfx2/source/doc/autoredactdialog.cxx
new file mode 100644
index 0000000000..665e5c0ec7
--- /dev/null
+++ b/sfx2/source/doc/autoredactdialog.cxx
@@ -0,0 +1,766 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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/.
+ */
+
+#include <autoredactdialog.hxx>
+
+#include <sfx2/filedlghelper.hxx>
+#include <sfx2/sfxresid.hxx>
+#include <sfx2/strings.hrc>
+
+#include <osl/file.hxx>
+#include <sal/log.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <unotools/viewoptions.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+
+#include <boost/property_tree/json_parser.hpp>
+
+constexpr OUStringLiteral FILEDIALOG_FILTER_JSON = u"*.json";
+
+int TargetsTable::GetRowByTargetName(std::u16string_view sName)
+{
+ for (int i = 0, nCount = m_xControl->n_children(); i < nCount; ++i)
+ {
+ RedactionTarget* pTarget = weld::fromId<RedactionTarget*>(m_xControl->get_id(i));
+ if (pTarget->sName == sName)
+ {
+ return i;
+ }
+ }
+ return -1;
+}
+
+TargetsTable::TargetsTable(std::unique_ptr<weld::TreeView> xControl)
+ : m_xControl(std::move(xControl))
+{
+ m_xControl->set_size_request(555, 250);
+ std::vector<int> aWidths{ 100, 50, 200, 105, 105 };
+ m_xControl->set_column_fixed_widths(aWidths);
+ m_xControl->set_selection_mode(SelectionMode::Multiple);
+}
+
+namespace
+{
+OUString getTypeName(RedactionTargetType nType)
+{
+ OUString sTypeName(SfxResId(STR_REDACTION_TARGET_TYPE_UNKNOWN));
+
+ switch (nType)
+ {
+ case RedactionTargetType::REDACTION_TARGET_TEXT:
+ sTypeName = SfxResId(STR_REDACTION_TARGET_TYPE_TEXT);
+ break;
+ case RedactionTargetType::REDACTION_TARGET_REGEX:
+ sTypeName = SfxResId(STR_REDACTION_TARGET_TYPE_REGEX);
+ break;
+ case RedactionTargetType::REDACTION_TARGET_PREDEFINED:
+ sTypeName = SfxResId(STR_REDACTION_TARGET_TYPE_PREDEF);
+ break;
+ case RedactionTargetType::REDACTION_TARGET_UNKNOWN:
+ sTypeName = SfxResId(STR_REDACTION_TARGET_TYPE_UNKNOWN);
+ break;
+ }
+
+ return sTypeName;
+}
+
+/// Returns TypeID to be used in the add/edit target dialog
+OUString getTypeID(RedactionTargetType nType)
+{
+ OUString sTypeID("unknown");
+
+ switch (nType)
+ {
+ case RedactionTargetType::REDACTION_TARGET_TEXT:
+ sTypeID = "text";
+ break;
+ case RedactionTargetType::REDACTION_TARGET_REGEX:
+ sTypeID = "regex";
+ break;
+ case RedactionTargetType::REDACTION_TARGET_PREDEFINED:
+ sTypeID = "predefined";
+ break;
+ case RedactionTargetType::REDACTION_TARGET_UNKNOWN:
+ sTypeID = "unknown";
+ break;
+ }
+
+ return sTypeID;
+}
+}
+
+void TargetsTable::InsertTarget(RedactionTarget* pTarget)
+{
+ if (!pTarget)
+ {
+ SAL_WARN("sfx.doc", "pTarget is null in TargetsTable::InsertTarget()");
+ return;
+ }
+
+ // Check if the name is empty or invalid (clashing with another entry's name)
+ if (pTarget->sName.isEmpty() || GetRowByTargetName(pTarget->sName) != -1)
+ {
+ pTarget->sName = GetNameProposal();
+ }
+
+ OUString sContent = pTarget->sContent;
+
+ if (pTarget->sType == RedactionTargetType::REDACTION_TARGET_PREDEFINED)
+ {
+ //selection_num;selection_name
+ sContent = sContent.getToken(1, ';');
+ }
+
+ // Add to the end
+ int nRow = m_xControl->n_children();
+ m_xControl->append(weld::toId(pTarget), pTarget->sName);
+ m_xControl->set_text(nRow, getTypeName(pTarget->sType), 1);
+ m_xControl->set_text(nRow, sContent, 2);
+ m_xControl->set_text(
+ nRow, pTarget->bCaseSensitive ? SfxResId(STR_REDACTION_YES) : SfxResId(STR_REDACTION_NO),
+ 3);
+ m_xControl->set_text(
+ nRow, pTarget->bWholeWords ? SfxResId(STR_REDACTION_YES) : SfxResId(STR_REDACTION_NO), 4);
+}
+
+RedactionTarget* TargetsTable::GetTargetByName(std::u16string_view sName)
+{
+ int nEntry = GetRowByTargetName(sName);
+ if (nEntry == -1)
+ return nullptr;
+
+ return weld::fromId<RedactionTarget*>(m_xControl->get_id(nEntry));
+}
+
+OUString TargetsTable::GetNameProposal() const
+{
+ OUString sDefaultTargetName(SfxResId(STR_REDACTION_TARGET));
+ sal_Int32 nHighestTargetId = 0;
+ for (int i = 0, nCount = m_xControl->n_children(); i < nCount; ++i)
+ {
+ RedactionTarget* pTarget = weld::fromId<RedactionTarget*>(m_xControl->get_id(i));
+ const OUString& sName = pTarget->sName;
+ sal_Int32 nIndex = 0;
+ if (o3tl::getToken(sName, 0, ' ', nIndex) == sDefaultTargetName)
+ {
+ sal_Int32 nCurrTargetId = o3tl::toInt32(o3tl::getToken(sName, 0, ' ', nIndex));
+ nHighestTargetId = std::max<sal_Int32>(nHighestTargetId, nCurrTargetId);
+ }
+ }
+ return sDefaultTargetName + " " + OUString::number(nHighestTargetId + 1);
+}
+
+void TargetsTable::setRowData(int nRowIndex, const RedactionTarget* pTarget)
+{
+ OUString sContent = pTarget->sContent;
+
+ if (pTarget->sType == RedactionTargetType::REDACTION_TARGET_PREDEFINED)
+ {
+ //selection_num;selection_name
+ sContent = sContent.getToken(1, ';');
+ }
+
+ m_xControl->set_text(nRowIndex, pTarget->sName, 0);
+ m_xControl->set_text(nRowIndex, getTypeName(pTarget->sType), 1);
+ m_xControl->set_text(nRowIndex, sContent, 2);
+ m_xControl->set_text(
+ nRowIndex,
+ pTarget->bCaseSensitive ? SfxResId(STR_REDACTION_YES) : SfxResId(STR_REDACTION_NO), 3);
+ m_xControl->set_text(
+ nRowIndex, pTarget->bWholeWords ? SfxResId(STR_REDACTION_YES) : SfxResId(STR_REDACTION_NO),
+ 4);
+}
+
+IMPL_LINK_NOARG(SfxAutoRedactDialog, Load, weld::Button&, void)
+{
+ //Load a targets list from a previously saved file (a json file?)
+ // ask for filename, where we should load the new config data from
+ StartFileDialog(StartFileDialogType::Open, SfxResId(STR_REDACTION_LOAD_TARGETS));
+}
+
+IMPL_LINK_NOARG(SfxAutoRedactDialog, Save, weld::Button&, void)
+{
+ //Allow saving the targets into a file
+ StartFileDialog(StartFileDialogType::SaveAs, SfxResId(STR_REDACTION_SAVE_TARGETS));
+}
+
+IMPL_LINK_NOARG(SfxAutoRedactDialog, AddHdl, weld::Button&, void)
+{
+ // Open the Add Target dialog, create a new target and insert into the targets vector and the listbox
+ SfxAddTargetDialog aAddTargetDialog(getDialog(), m_aTargetsBox.GetNameProposal());
+
+ bool bIncomplete;
+ do
+ {
+ bIncomplete = false;
+
+ if (aAddTargetDialog.run() != RET_OK)
+ return;
+
+ if (aAddTargetDialog.getName().isEmpty()
+ || aAddTargetDialog.getType() == RedactionTargetType::REDACTION_TARGET_UNKNOWN
+ || aAddTargetDialog.getContent().isEmpty())
+ {
+ bIncomplete = true;
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(
+ getDialog(), VclMessageType::Warning, VclButtonsType::Ok,
+ SfxResId(STR_REDACTION_FIELDS_REQUIRED)));
+ xBox->run();
+ }
+ else if (m_aTargetsBox.GetTargetByName(aAddTargetDialog.getName()))
+ {
+ bIncomplete = true;
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(
+ getDialog(), VclMessageType::Warning, VclButtonsType::Ok,
+ SfxResId(STR_REDACTION_TARGET_NAME_CLASH)));
+ xBox->run();
+ }
+
+ } while (bIncomplete);
+
+ //Alright, we now have everything we need to construct a new target
+ RedactionTarget* redactiontarget = new RedactionTarget(
+ { aAddTargetDialog.getName(), aAddTargetDialog.getType(), aAddTargetDialog.getContent(),
+ aAddTargetDialog.isCaseSensitive(), aAddTargetDialog.isWholeWords(), 0 });
+
+ // Only the visual/display part
+ m_aTargetsBox.InsertTarget(redactiontarget);
+
+ // Actually add to the targets vector
+ if (m_aTargetsBox.GetTargetByName(redactiontarget->sName))
+ m_aTableTargets.emplace_back(redactiontarget, redactiontarget->sName);
+ else
+ {
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(
+ getDialog(), VclMessageType::Warning, VclButtonsType::Ok,
+ SfxResId(STR_REDACTION_TARGET_ADD_ERROR)));
+ xBox->run();
+ delete redactiontarget;
+ }
+}
+
+IMPL_LINK_NOARG(SfxAutoRedactDialog, EditHdl, weld::Button&, void)
+{
+ sal_Int32 nSelectedRow = m_aTargetsBox.get_selected_index();
+
+ // No selection, nothing to edit
+ if (nSelectedRow < 0)
+ return;
+
+ // Only one entry should be selected for editing
+ if (m_aTargetsBox.get_selected_rows().size() > 1)
+ {
+ //Warn the user about multiple selections
+ std::unique_ptr<weld::MessageDialog> xBox(
+ Application::CreateMessageDialog(getDialog(), VclMessageType::Error, VclButtonsType::Ok,
+ SfxResId(STR_REDACTION_MULTI_EDIT)));
+ xBox->run();
+ return;
+ }
+
+ // Get the redaction target to be edited
+ RedactionTarget* pTarget = weld::fromId<RedactionTarget*>(m_aTargetsBox.get_id(nSelectedRow));
+
+ // Construct and run the edit target dialog
+ SfxAddTargetDialog aEditTargetDialog(getDialog(), pTarget->sName, pTarget->sType,
+ pTarget->sContent, pTarget->bCaseSensitive,
+ pTarget->bWholeWords);
+
+ bool bIncomplete;
+ do
+ {
+ bIncomplete = false;
+
+ if (aEditTargetDialog.run() != RET_OK)
+ return;
+
+ if (aEditTargetDialog.getName().isEmpty()
+ || aEditTargetDialog.getType() == RedactionTargetType::REDACTION_TARGET_UNKNOWN
+ || aEditTargetDialog.getContent().isEmpty())
+ {
+ bIncomplete = true;
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(
+ getDialog(), VclMessageType::Warning, VclButtonsType::Ok,
+ SfxResId(STR_REDACTION_FIELDS_REQUIRED)));
+ xBox->run();
+ }
+ else if (aEditTargetDialog.getName() != pTarget->sName
+ && m_aTargetsBox.GetTargetByName(aEditTargetDialog.getName()))
+ {
+ bIncomplete = true;
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(
+ getDialog(), VclMessageType::Warning, VclButtonsType::Ok,
+ SfxResId(STR_REDACTION_TARGET_NAME_CLASH)));
+ xBox->run();
+ }
+
+ } while (bIncomplete);
+
+ // Update the redaction target
+ pTarget->sName = aEditTargetDialog.getName();
+ pTarget->sType = aEditTargetDialog.getType();
+ pTarget->sContent = aEditTargetDialog.getContent();
+ pTarget->bCaseSensitive = aEditTargetDialog.isCaseSensitive();
+ pTarget->bWholeWords = aEditTargetDialog.isWholeWords();
+
+ // And sync the targets box row with the actual target data
+ m_aTargetsBox.setRowData(nSelectedRow, pTarget);
+}
+IMPL_LINK_NOARG(SfxAutoRedactDialog, DoubleClickEditHdl, weld::TreeView&, bool)
+{
+ if (m_xEditBtn->get_sensitive())
+ m_xEditBtn->clicked();
+ return true;
+}
+IMPL_LINK_NOARG(SfxAutoRedactDialog, DeleteHdl, weld::Button&, void)
+{
+ std::vector<int> aSelectedRows = m_aTargetsBox.get_selected_rows();
+
+ //No selection, so nothing to delete
+ if (aSelectedRows.empty())
+ return;
+
+ if (aSelectedRows.size() > 1)
+ {
+ OUString sMsg(SfxResId(STR_REDACTION_MULTI_DELETE)
+ .replaceFirst("$(TARGETSCOUNT)", OUString::number(aSelectedRows.size())));
+ //Warn the user about multiple deletions
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(
+ getDialog(), VclMessageType::Question, VclButtonsType::OkCancel, sMsg));
+ if (xBox->run() == RET_CANCEL)
+ return;
+ }
+
+ // After each delete, the indexes of the following items decrease by one.
+ int delta = 0;
+ for (const auto& i : aSelectedRows)
+ {
+ m_aTableTargets.erase(m_aTableTargets.begin() + (i - delta));
+ m_aTargetsBox.remove(i - delta++);
+ }
+}
+
+namespace
+{
+boost::property_tree::ptree redactionTargetToJSON(const RedactionTarget* pTarget)
+{
+ boost::property_tree::ptree aNode;
+ aNode.put("sName", pTarget->sName.toUtf8().getStr());
+ aNode.put("eType", pTarget->sType);
+ aNode.put("sContent", pTarget->sContent.toUtf8().getStr());
+ aNode.put("bWholeWords", pTarget->bWholeWords);
+ aNode.put("bCaseSensitive", pTarget->bCaseSensitive);
+ aNode.put("nID", pTarget->nID);
+
+ return aNode;
+}
+
+std::unique_ptr<RedactionTarget>
+JSONtoRedactionTarget(const boost::property_tree::ptree::value_type& rValue)
+{
+ OUString sName = OUString::fromUtf8(rValue.second.get<std::string>("sName"));
+ RedactionTargetType eType
+ = static_cast<RedactionTargetType>(atoi(rValue.second.get<std::string>("eType").c_str()));
+ OUString sContent = OUString::fromUtf8(rValue.second.get<std::string>("sContent"));
+ bool bCaseSensitive
+ = OUString::fromUtf8(rValue.second.get<std::string>("bCaseSensitive")).toBoolean();
+ bool bWholeWords
+ = OUString::fromUtf8(rValue.second.get<std::string>("bWholeWords")).toBoolean();
+ sal_uInt32 nID = atoi(rValue.second.get<std::string>("nID").c_str());
+
+ return std::unique_ptr<RedactionTarget>(
+ new RedactionTarget{ sName, eType, sContent, bCaseSensitive, bWholeWords, nID });
+}
+}
+
+IMPL_LINK_NOARG(SfxAutoRedactDialog, LoadHdl, sfx2::FileDialogHelper*, void)
+{
+ assert(m_pFileDlg);
+
+ OUString sTargetsFile;
+ if (ERRCODE_NONE == m_pFileDlg->GetError())
+ sTargetsFile = m_pFileDlg->GetPath();
+
+ if (sTargetsFile.isEmpty())
+ return;
+
+ OUString sSysPath;
+ osl::File::getSystemPathFromFileURL(sTargetsFile, sSysPath);
+ sTargetsFile = sSysPath;
+
+ weld::WaitObject aWaitObject(getDialog());
+
+ try
+ {
+ // Create path string, and read JSON from file
+ std::string sPathStr(OUStringToOString(sTargetsFile, RTL_TEXTENCODING_UTF8));
+
+ boost::property_tree::ptree aTargetsJSON;
+
+ boost::property_tree::read_json(sPathStr, aTargetsJSON);
+
+ // Clear the dialog
+ clearTargets();
+
+ // Recreate & add the targets to the dialog
+ for (const boost::property_tree::ptree::value_type& rValue :
+ aTargetsJSON.get_child("RedactionTargets"))
+ {
+ addTarget(JSONtoRedactionTarget(rValue));
+ }
+ }
+ catch (css::uno::Exception& e)
+ {
+ SAL_WARN("sfx.doc",
+ "Exception caught while trying to load the targets JSON from file: " << e.Message);
+ return;
+ //TODO: Warn the user with a message box
+ }
+}
+
+IMPL_LINK_NOARG(SfxAutoRedactDialog, SaveHdl, sfx2::FileDialogHelper*, void)
+{
+ assert(m_pFileDlg);
+
+ OUString sTargetsFile;
+ if (ERRCODE_NONE == m_pFileDlg->GetError())
+ sTargetsFile = m_pFileDlg->GetPath();
+
+ if (sTargetsFile.isEmpty())
+ return;
+
+ OUString sSysPath;
+ osl::File::getSystemPathFromFileURL(sTargetsFile, sSysPath);
+ sTargetsFile = sSysPath;
+
+ weld::WaitObject aWaitObject(getDialog());
+
+ try
+ {
+ // Put the targets into a JSON array
+ boost::property_tree::ptree aTargetsArray;
+ for (const auto& targetPair : m_aTableTargets)
+ {
+ aTargetsArray.push_back(
+ std::make_pair("", redactionTargetToJSON(targetPair.first.get())));
+ }
+
+ // Build the JSON tree
+ boost::property_tree::ptree aTargetsTree;
+ aTargetsTree.add_child("RedactionTargets", aTargetsArray);
+
+ // Create path string, and write JSON to file
+ std::string sPathStr(OUStringToOString(sTargetsFile, RTL_TEXTENCODING_UTF8));
+
+ boost::property_tree::write_json(sPathStr, aTargetsTree);
+ }
+ catch (css::uno::Exception& e)
+ {
+ SAL_WARN("sfx.doc",
+ "Exception caught while trying to save the targets JSON to file: " << e.Message);
+ return;
+ //TODO: Warn the user with a message box
+ }
+}
+
+void SfxAutoRedactDialog::StartFileDialog(StartFileDialogType nType, const OUString& rTitle)
+{
+ OUString aFilterAllStr(SfxResId(STR_SFX_FILTERNAME_ALL));
+ OUString aFilterJsonStr(SfxResId(STR_REDACTION_JSON_FILE_FILTER));
+
+ bool bSave = nType == StartFileDialogType::SaveAs;
+ short nDialogType = bSave ? css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION
+ : css::ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE;
+ m_pFileDlg.reset(new sfx2::FileDialogHelper(nDialogType, FileDialogFlags::NONE, getDialog()));
+
+ m_pFileDlg->SetTitle(rTitle);
+ m_pFileDlg->AddFilter(aFilterAllStr, FILEDIALOG_FILTER_ALL);
+ m_pFileDlg->AddFilter(aFilterJsonStr, FILEDIALOG_FILTER_JSON);
+ m_pFileDlg->SetCurrentFilter(aFilterJsonStr);
+
+ Link<sfx2::FileDialogHelper*, void> aDlgClosedLink
+ = bSave ? LINK(this, SfxAutoRedactDialog, SaveHdl)
+ : LINK(this, SfxAutoRedactDialog, LoadHdl);
+ m_pFileDlg->SetContext(sfx2::FileDialogHelper::AutoRedact);
+ m_pFileDlg->StartExecuteModal(aDlgClosedLink);
+}
+
+void SfxAutoRedactDialog::addTarget(std::unique_ptr<RedactionTarget> pTarget)
+{
+ // Only the visual/display part
+ m_aTargetsBox.InsertTarget(pTarget.get());
+
+ // Actually add to the targets vector
+ auto name = pTarget->sName;
+ if (m_aTargetsBox.GetTargetByName(name))
+ m_aTableTargets.emplace_back(std::move(pTarget), name);
+ else
+ {
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(
+ getDialog(), VclMessageType::Warning, VclButtonsType::Ok,
+ SfxResId(STR_REDACTION_TARGET_ADD_ERROR)));
+ xBox->run();
+ }
+}
+
+void SfxAutoRedactDialog::clearTargets()
+{
+ // Clear the targets box
+ m_aTargetsBox.clear();
+
+ // Clear the targets vector
+ m_aTableTargets.clear();
+}
+
+SfxAutoRedactDialog::SfxAutoRedactDialog(weld::Window* pParent)
+ : SfxDialogController(pParent, "sfx/ui/autoredactdialog.ui", "AutoRedactDialog")
+ , m_bIsValidState(true)
+ , m_bTargetsCopied(false)
+ , m_aTargetsBox(m_xBuilder->weld_tree_view("targets"))
+ , m_xLoadBtn(m_xBuilder->weld_button("btnLoadTargets"))
+ , m_xSaveBtn(m_xBuilder->weld_button("btnSaveTargets"))
+ , m_xAddBtn(m_xBuilder->weld_button("add"))
+ , m_xEditBtn(m_xBuilder->weld_button("edit"))
+ , m_xDeleteBtn(m_xBuilder->weld_button("delete"))
+{
+ // Can be used to remember the last set of redaction targets?
+ OUString sExtraData;
+ SvtViewOptions aDlgOpt(EViewType::Dialog, m_xDialog->get_help_id());
+
+ if (aDlgOpt.Exists())
+ {
+ css::uno::Any aUserItem = aDlgOpt.GetUserItem("UserItem");
+ aUserItem >>= sExtraData;
+ }
+
+ // update the targets configuration if necessary
+ if (!sExtraData.isEmpty())
+ {
+ weld::WaitObject aWaitCursor(m_xDialog.get());
+
+ try
+ {
+ // Create path string, and read JSON from file
+ boost::property_tree::ptree aTargetsJSON;
+ std::stringstream aStream(std::string(sExtraData.toUtf8()));
+
+ boost::property_tree::read_json(aStream, aTargetsJSON);
+
+ // Recreate & add the targets to the dialog
+ for (const boost::property_tree::ptree::value_type& rValue :
+ aTargetsJSON.get_child("RedactionTargets"))
+ {
+ addTarget(JSONtoRedactionTarget(rValue));
+ }
+ }
+ catch (css::uno::Exception& e)
+ {
+ SAL_WARN("sfx.doc",
+ "Exception caught while trying to load the last dialog state: " << e.Message);
+ return;
+ //TODO: Warn the user with a message box
+ }
+ }
+
+ // Handler connections
+ m_xLoadBtn->connect_clicked(LINK(this, SfxAutoRedactDialog, Load));
+ m_xSaveBtn->connect_clicked(LINK(this, SfxAutoRedactDialog, Save));
+ m_xAddBtn->connect_clicked(LINK(this, SfxAutoRedactDialog, AddHdl));
+ m_xEditBtn->connect_clicked(LINK(this, SfxAutoRedactDialog, EditHdl));
+ m_xDeleteBtn->connect_clicked(LINK(this, SfxAutoRedactDialog, DeleteHdl));
+ m_aTargetsBox.connect_row_activated(LINK(this, SfxAutoRedactDialog, DoubleClickEditHdl));
+}
+
+SfxAutoRedactDialog::~SfxAutoRedactDialog()
+{
+ if (m_aTableTargets.empty())
+ {
+ // Clear the dialog data
+ SvtViewOptions aDlgOpt(EViewType::Dialog, m_xDialog->get_help_id());
+ aDlgOpt.Delete();
+ return;
+ }
+
+ try
+ {
+ // Put the targets into a JSON array
+ boost::property_tree::ptree aTargetsArray;
+ for (const auto& targetPair : m_aTableTargets)
+ {
+ aTargetsArray.push_back(
+ std::make_pair("", redactionTargetToJSON(targetPair.first.get())));
+ }
+
+ // Build the JSON tree
+ boost::property_tree::ptree aTargetsTree;
+ aTargetsTree.add_child("RedactionTargets", aTargetsArray);
+ std::stringstream aStream;
+
+ boost::property_tree::write_json(aStream, aTargetsTree, false);
+
+ OUString sUserDataStr(OUString::fromUtf8(aStream.str()));
+
+ // Store the dialog data
+ SvtViewOptions aDlgOpt(EViewType::Dialog, m_xDialog->get_help_id());
+ aDlgOpt.SetUserItem("UserItem", css::uno::Any(sUserDataStr));
+
+ if (!m_bTargetsCopied)
+ clearTargets();
+ }
+ catch (css::uno::Exception& e)
+ {
+ SAL_WARN("sfx.doc",
+ "Exception caught while trying to store the dialog state: " << e.Message);
+ return;
+ //TODO: Warn the user with a message box
+ }
+}
+
+bool SfxAutoRedactDialog::hasTargets() const
+{
+ //TODO: Add also some validity checks?
+ if (m_aTableTargets.empty())
+ return false;
+
+ return true;
+}
+
+bool SfxAutoRedactDialog::getTargets(std::vector<std::pair<RedactionTarget, OUString>>& r_aTargets)
+{
+ if (m_aTableTargets.empty())
+ return true;
+
+ for (auto const& rPair : m_aTableTargets)
+ r_aTargets.push_back({ *rPair.first, rPair.second });
+ m_bTargetsCopied = true;
+ return true;
+}
+
+IMPL_LINK_NOARG(SfxAddTargetDialog, SelectTypeHdl, weld::ComboBox&, void)
+{
+ if (m_xType->get_active_id() == "predefined")
+ {
+ // Hide the usual content widgets
+ // We will just set the id as content
+ // And handle with proper regex in the SfxRedactionHelper
+ m_xLabelContent->set_sensitive(false);
+ m_xLabelContent->set_visible(false);
+ m_xContent->set_sensitive(false);
+ m_xContent->set_visible(false);
+ m_xWholeWords->set_sensitive(false);
+ m_xWholeWords->set_visible(false);
+ m_xCaseSensitive->set_sensitive(false);
+ m_xCaseSensitive->set_visible(false);
+
+ // And show the predefined targets
+ m_xLabelPredefContent->set_sensitive(true);
+ m_xLabelPredefContent->set_visible(true);
+ m_xPredefContent->set_sensitive(true);
+ m_xPredefContent->set_visible(true);
+ }
+ else
+ {
+ m_xLabelPredefContent->set_sensitive(false);
+ m_xLabelPredefContent->set_visible(false);
+ m_xPredefContent->set_sensitive(false);
+ m_xPredefContent->set_visible(false);
+
+ m_xLabelContent->set_sensitive(true);
+ m_xLabelContent->set_visible(true);
+ m_xContent->set_sensitive(true);
+ m_xContent->set_visible(true);
+ m_xWholeWords->set_sensitive(true);
+ m_xWholeWords->set_visible(true);
+ m_xCaseSensitive->set_sensitive(true);
+ m_xCaseSensitive->set_visible(true);
+ }
+}
+
+SfxAddTargetDialog::SfxAddTargetDialog(weld::Window* pParent, const OUString& rName)
+ : GenericDialogController(pParent, "sfx/ui/addtargetdialog.ui", "AddTargetDialog")
+ , m_xName(m_xBuilder->weld_entry("name"))
+ , m_xType(m_xBuilder->weld_combo_box("type"))
+ , m_xLabelContent(m_xBuilder->weld_label("label_content"))
+ , m_xContent(m_xBuilder->weld_entry("content"))
+ , m_xLabelPredefContent(m_xBuilder->weld_label("label_content_predef"))
+ , m_xPredefContent(m_xBuilder->weld_combo_box("content_predef"))
+ , m_xCaseSensitive(m_xBuilder->weld_check_button("checkboxCaseSensitive"))
+ , m_xWholeWords(m_xBuilder->weld_check_button("checkboxWholeWords"))
+{
+ m_xName->set_text(rName);
+ m_xName->select_region(0, rName.getLength());
+
+ m_xType->connect_changed(LINK(this, SfxAddTargetDialog, SelectTypeHdl));
+}
+
+SfxAddTargetDialog::SfxAddTargetDialog(weld::Window* pParent, const OUString& sName,
+ const RedactionTargetType& eTargetType,
+ const OUString& sContent, bool bCaseSensitive,
+ bool bWholeWords)
+ : GenericDialogController(pParent, "sfx/ui/addtargetdialog.ui", "AddTargetDialog")
+ , m_xName(m_xBuilder->weld_entry("name"))
+ , m_xType(m_xBuilder->weld_combo_box("type"))
+ , m_xLabelContent(m_xBuilder->weld_label("label_content"))
+ , m_xContent(m_xBuilder->weld_entry("content"))
+ , m_xLabelPredefContent(m_xBuilder->weld_label("label_content_predef"))
+ , m_xPredefContent(m_xBuilder->weld_combo_box("content_predef"))
+ , m_xCaseSensitive(m_xBuilder->weld_check_button("checkboxCaseSensitive"))
+ , m_xWholeWords(m_xBuilder->weld_check_button("checkboxWholeWords"))
+{
+ m_xName->set_text(sName);
+ m_xName->select_region(0, sName.getLength());
+
+ m_xType->set_active_id(getTypeID(eTargetType));
+ m_xType->connect_changed(LINK(this, SfxAddTargetDialog, SelectTypeHdl));
+
+ if (eTargetType == RedactionTargetType::REDACTION_TARGET_PREDEFINED)
+ {
+ SelectTypeHdl(*m_xPredefContent);
+ m_xPredefContent->set_active(o3tl::toInt32(o3tl::getToken(sContent, 0, ';')));
+ }
+ else
+ {
+ m_xContent->set_text(sContent);
+ }
+
+ m_xCaseSensitive->set_active(bCaseSensitive);
+ m_xWholeWords->set_active(bWholeWords);
+
+ set_title(SfxResId(STR_REDACTION_EDIT_TARGET));
+}
+
+RedactionTargetType SfxAddTargetDialog::getType() const
+{
+ OUString sTypeID = m_xType->get_active_id();
+
+ if (sTypeID == "text")
+ return RedactionTargetType::REDACTION_TARGET_TEXT;
+ else if (sTypeID == "regex")
+ return RedactionTargetType::REDACTION_TARGET_REGEX;
+ else if (sTypeID == "predefined")
+ return RedactionTargetType::REDACTION_TARGET_PREDEFINED;
+ else
+ return RedactionTargetType::REDACTION_TARGET_UNKNOWN;
+}
+
+OUString SfxAddTargetDialog::getContent() const
+{
+ if (m_xType->get_active_id() == "predefined")
+ {
+ return OUString(OUString::number(m_xPredefContent->get_active()) + ";"
+ + m_xPredefContent->get_active_text());
+ }
+
+ return m_xContent->get_text();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/sfx2/source/doc/docfac.cxx b/sfx2/source/doc/docfac.cxx
new file mode 100644
index 0000000000..ee667ed5d0
--- /dev/null
+++ b/sfx2/source/doc/docfac.cxx
@@ -0,0 +1,355 @@
+/* -*- 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 <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/ucb/SimpleFileAccess.hpp>
+#include <com/sun/star/document/XTypeDetection.hpp>
+#include <com/sun/star/frame/ModuleManager.hpp>
+#include <com/sun/star/frame/XLoadable.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <unotools/moduleoptions.hxx>
+#include <comphelper/sequenceashashmap.hxx>
+#include <comphelper/configurationhelper.hxx>
+
+#include <sfx2/docfilt.hxx>
+#include <sfx2/docfac.hxx>
+#include <sfx2/viewfac.hxx>
+#include <sfx2/fcontnr.hxx>
+#include <sfx2/module.hxx>
+#include "syspath.hxx"
+#include <osl/file.hxx>
+#include <osl/security.hxx>
+
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+#include <tools/globname.hxx>
+
+#include <memory>
+#include <utility>
+
+using namespace ::com::sun::star;
+
+
+struct SfxObjectFactory_Impl
+{
+ std::vector<SfxViewFactory*> aViewFactoryArr;// List of <SfxViewFactory>s
+ OUString aServiceName;
+ SfxFilterContainer* pFilterContainer;
+ SfxModule* pModule;
+ SvGlobalName aClassName;
+
+ SfxObjectFactory_Impl() :
+ pFilterContainer ( nullptr ),
+ pModule ( nullptr )
+ {}
+};
+
+SfxFilterContainer* SfxObjectFactory::GetFilterContainer() const
+{
+ return pImpl->pFilterContainer;
+}
+
+SfxObjectFactory::SfxObjectFactory
+(
+ const SvGlobalName& rName,
+ OUString sName
+) : m_sFactoryName(std::move( sName )),
+ pImpl( new SfxObjectFactory_Impl )
+{
+ pImpl->pFilterContainer = new SfxFilterContainer( m_sFactoryName );
+ pImpl->aClassName = rName;
+}
+
+SfxObjectFactory::~SfxObjectFactory()
+{
+ delete pImpl->pFilterContainer;
+}
+
+
+void SfxObjectFactory::RegisterViewFactory
+(
+ SfxViewFactory &rFactory
+)
+{
+#if OSL_DEBUG_LEVEL > 0
+ {
+ const OUString sViewName( rFactory.GetAPIViewName() );
+ for (auto const& viewFactory : pImpl->aViewFactoryArr)
+ {
+ if ( viewFactory->GetAPIViewName() != sViewName )
+ continue;
+ SAL_WARN( "sfx", "SfxObjectFactory::RegisterViewFactory: duplicate view name: " << sViewName );
+ break;
+ }
+ }
+#endif
+ auto it = std::find_if(pImpl->aViewFactoryArr.begin(), pImpl->aViewFactoryArr.end(),
+ [&rFactory](SfxViewFactory* pFactory) { return pFactory->GetOrdinal() > rFactory.GetOrdinal(); });
+ pImpl->aViewFactoryArr.insert(it, &rFactory);
+}
+
+
+sal_uInt16 SfxObjectFactory::GetViewFactoryCount() const
+{
+ return pImpl->aViewFactoryArr.size();
+}
+
+
+SfxViewFactory& SfxObjectFactory::GetViewFactory(sal_uInt16 i) const
+{
+ return *pImpl->aViewFactoryArr[i];
+}
+
+
+SfxModule* SfxObjectFactory::GetModule() const
+{
+ return pImpl->pModule;
+}
+
+void SfxObjectFactory::SetModule_Impl( SfxModule *pMod )
+{
+ pImpl->pModule = pMod;
+}
+
+void SfxObjectFactory::SetSystemTemplate( const OUString& rServiceName, const OUString& rTemplateName )
+{
+ static const int nMaxPathSize = 16000;
+
+ const OUString sConfPath = "Office/Factories/" + rServiceName;
+ static constexpr OUString PROP_DEF_TEMPL_CHANGED
+ = u"ooSetupFactorySystemDefaultTemplateChanged"_ustr;
+
+ static const char DEF_TPL_STR[] = "/soffice.";
+
+ OUString sUserTemplateURL;
+ OUString sPath;
+ sal_Unicode aPathBuffer[nMaxPathSize];
+ if ( SystemPath::GetUserTemplateLocation( aPathBuffer, nMaxPathSize ))
+ sPath = OUString( aPathBuffer );
+ osl::FileBase::getFileURLFromSystemPath( sPath, sUserTemplateURL );
+
+ if ( sUserTemplateURL.isEmpty())
+ return;
+
+ try
+ {
+ uno::Reference< lang::XMultiServiceFactory > xFactory = ::comphelper::getProcessServiceFactory();
+ uno::Reference< uno::XInterface > xConfig = ::comphelper::ConfigurationHelper::openConfig(
+ ::comphelper::getProcessComponentContext(), "/org.openoffice.Setup", ::comphelper::EConfigurationModes::Standard );
+
+ OUString aActualFilter;
+ ::comphelper::ConfigurationHelper::readRelativeKey( xConfig, sConfPath, "ooSetupFactoryActualFilter" ) >>= aActualFilter;
+ bool bChanged(false);
+ ::comphelper::ConfigurationHelper::readRelativeKey( xConfig, sConfPath, PROP_DEF_TEMPL_CHANGED ) >>= bChanged;
+
+ uno::Reference< container::XNameAccess > xFilterFactory(
+ xFactory->createInstance( "com.sun.star.document.FilterFactory" ), uno::UNO_QUERY_THROW );
+ uno::Reference< container::XNameAccess > xTypeDetection(
+ xFactory->createInstance( "com.sun.star.document.TypeDetection" ), uno::UNO_QUERY_THROW );
+
+ OUString aActualFilterTypeName;
+ uno::Sequence< beans::PropertyValue > aActuralFilterData;
+ xFilterFactory->getByName( aActualFilter ) >>= aActuralFilterData;
+ for ( const auto& rProp : std::as_const(aActuralFilterData) )
+ if ( rProp.Name == "Type" )
+ rProp.Value >>= aActualFilterTypeName;
+ ::comphelper::SequenceAsHashMap aProps1( xTypeDetection->getByName( aActualFilterTypeName ) );
+ uno::Sequence< OUString > aAllExt =
+ aProps1.getUnpackedValueOrDefault("Extensions", uno::Sequence< OUString >() );
+ //To-do: check if aAllExt is empty first
+ const OUString aExt = DEF_TPL_STR + aAllExt[0];
+
+ sUserTemplateURL += aExt;
+
+ uno::Reference<ucb::XSimpleFileAccess3> xSimpleFileAccess(
+ ucb::SimpleFileAccess::create( ::comphelper::getComponentContext(xFactory) ) );
+
+ OUString aBackupURL;
+ ::osl::Security().getConfigDir(aBackupURL);
+ aBackupURL += "/temp";
+
+ if ( !xSimpleFileAccess->exists( aBackupURL ) )
+ xSimpleFileAccess->createFolder( aBackupURL );
+
+ aBackupURL += aExt;
+
+ if ( !rTemplateName.isEmpty() )
+ {
+ if ( xSimpleFileAccess->exists( sUserTemplateURL ) && !bChanged )
+ xSimpleFileAccess->copy( sUserTemplateURL, aBackupURL );
+
+ uno::Reference< document::XTypeDetection > xTypeDetector( xTypeDetection, uno::UNO_QUERY );
+ ::comphelper::SequenceAsHashMap aProps2( xTypeDetection->getByName( xTypeDetector->queryTypeByURL( rTemplateName ) ) );
+ OUString aFilterName =
+ aProps2.getUnpackedValueOrDefault("PreferredFilter", OUString() );
+
+ uno::Sequence< beans::PropertyValue > aArgs{
+ comphelper::makePropertyValue("FilterName", aFilterName),
+ comphelper::makePropertyValue("AsTemplate", true),
+ comphelper::makePropertyValue("URL", rTemplateName)
+ };
+
+ uno::Reference< frame::XLoadable > xLoadable( xFactory->createInstance( rServiceName ), uno::UNO_QUERY );
+ xLoadable->load( aArgs );
+
+ aArgs.realloc( 2 );
+ auto pArgs = aArgs.getArray();
+ pArgs[1].Name = "Overwrite";
+ pArgs[1].Value <<= true;
+
+ uno::Reference< frame::XStorable > xStorable( xLoadable, uno::UNO_QUERY );
+ xStorable->storeToURL( sUserTemplateURL, aArgs );
+ ::comphelper::ConfigurationHelper::writeRelativeKey( xConfig, sConfPath, PROP_DEF_TEMPL_CHANGED, uno::Any( true ));
+ ::comphelper::ConfigurationHelper::flush( xConfig );
+ }
+ else
+ {
+ DBG_ASSERT( bChanged, "invalid ooSetupFactorySystemDefaultTemplateChanged value!" );
+
+ xSimpleFileAccess->copy( aBackupURL, sUserTemplateURL );
+ xSimpleFileAccess->kill( aBackupURL );
+ ::comphelper::ConfigurationHelper::writeRelativeKey( xConfig, sConfPath, PROP_DEF_TEMPL_CHANGED, uno::Any( false ));
+ ::comphelper::ConfigurationHelper::flush( xConfig );
+ }
+ }
+ catch(const uno::Exception&)
+ {
+ }
+}
+
+void SfxObjectFactory::SetStandardTemplate( const OUString& rServiceName, const OUString& rTemplate )
+{
+ SvtModuleOptions::EFactory eFac = SvtModuleOptions::ClassifyFactoryByServiceName(rServiceName);
+ if (eFac == SvtModuleOptions::EFactory::UNKNOWN_FACTORY)
+ eFac = SvtModuleOptions::ClassifyFactoryByShortName(rServiceName);
+ if (eFac != SvtModuleOptions::EFactory::UNKNOWN_FACTORY)
+ {
+ SetSystemTemplate( rServiceName, rTemplate );
+ SvtModuleOptions().SetFactoryStandardTemplate(eFac, rTemplate);
+ }
+}
+
+OUString SfxObjectFactory::GetStandardTemplate( std::u16string_view rServiceName )
+{
+ SvtModuleOptions::EFactory eFac = SvtModuleOptions::ClassifyFactoryByServiceName(rServiceName);
+ if (eFac == SvtModuleOptions::EFactory::UNKNOWN_FACTORY)
+ eFac = SvtModuleOptions::ClassifyFactoryByShortName(rServiceName);
+
+ if (eFac != SvtModuleOptions::EFactory::UNKNOWN_FACTORY)
+ return SvtModuleOptions().GetFactoryStandardTemplate(eFac);
+
+ return OUString();
+}
+
+std::shared_ptr<const SfxFilter> SfxObjectFactory::GetTemplateFilter() const
+{
+ sal_uInt16 nVersion=0;
+ SfxFilterMatcher aMatcher ( m_sFactoryName );
+ SfxFilterMatcherIter aIter( aMatcher );
+ std::shared_ptr<const SfxFilter> pFilter;
+ std::shared_ptr<const SfxFilter> pTemp = aIter.First();
+ while ( pTemp )
+ {
+ if( pTemp->IsOwnFormat() && pTemp->IsOwnTemplateFormat() && ( pTemp->GetVersion() > nVersion ) )
+ {
+ pFilter = pTemp;
+ nVersion = static_cast<sal_uInt16>(pTemp->GetVersion());
+ }
+
+ pTemp = aIter.Next();
+ }
+
+ return pFilter;
+}
+
+void SfxObjectFactory::SetDocumentServiceName( const OUString& rServiceName )
+{
+ pImpl->aServiceName = rServiceName;
+}
+
+const OUString& SfxObjectFactory::GetDocumentServiceName() const
+{
+ return pImpl->aServiceName;
+}
+
+const SvGlobalName& SfxObjectFactory::GetClassId() const
+{
+ return pImpl->aClassName;
+}
+
+OUString SfxObjectFactory::GetFactoryURL() const
+{
+ return "private:factory/" + m_sFactoryName;
+}
+
+OUString SfxObjectFactory::GetModuleName() const
+{
+ try
+ {
+ css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+
+ css::uno::Reference< css::frame::XModuleManager2 > xModuleManager(
+ css::frame::ModuleManager::create(xContext));
+
+ ::comphelper::SequenceAsHashMap aPropSet( xModuleManager->getByName(GetDocumentServiceName()) );
+ return aPropSet.getUnpackedValueOrDefault("ooSetupFactoryUIName", OUString());
+ }
+ catch(const css::uno::RuntimeException&)
+ {
+ throw;
+ }
+ catch(const css::uno::Exception&)
+ {
+ }
+
+ return OUString();
+}
+
+
+sal_uInt16 SfxObjectFactory::GetViewNo_Impl( const SfxInterfaceId i_nViewId, const sal_uInt16 i_nFallback ) const
+{
+ for ( sal_uInt16 curViewNo = 0; curViewNo < GetViewFactoryCount(); ++curViewNo )
+ {
+ const SfxInterfaceId curViewId = GetViewFactory( curViewNo ).GetOrdinal();
+ if ( i_nViewId == curViewId )
+ return curViewNo;
+ }
+ return i_nFallback;
+}
+
+SfxViewFactory* SfxObjectFactory::GetViewFactoryByViewName( std::u16string_view i_rViewName ) const
+{
+ for ( sal_uInt16 nViewNo = 0;
+ nViewNo < GetViewFactoryCount();
+ ++nViewNo
+ )
+ {
+ SfxViewFactory& rViewFac( GetViewFactory( nViewNo ) );
+ if ( ( rViewFac.GetAPIViewName() == i_rViewName )
+ || ( rViewFac.GetLegacyViewName() == i_rViewName )
+ )
+ return &rViewFac;
+ }
+ return nullptr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/docfile.cxx b/sfx2/source/doc/docfile.cxx
new file mode 100644
index 0000000000..422fa98ac3
--- /dev/null
+++ b/sfx2/source/doc/docfile.cxx
@@ -0,0 +1,4976 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <config_features.h>
+
+#ifdef UNX
+#include <sys/stat.h>
+#endif
+
+#include <sfx2/docfile.hxx>
+#include <sfx2/signaturestate.hxx>
+
+#include <com/sun/star/task/InteractionHandler.hpp>
+#include <com/sun/star/task/XStatusIndicator.hpp>
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/ucb/XContent.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/XChild.hpp>
+#include <com/sun/star/document/XDocumentRevisionListPersistence.hpp>
+#include <com/sun/star/document/LockedDocumentRequest.hpp>
+#include <com/sun/star/document/LockedOnSavingRequest.hpp>
+#include <com/sun/star/document/OwnLockOnDocumentRequest.hpp>
+#include <com/sun/star/document/LockFileIgnoreRequest.hpp>
+#include <com/sun/star/document/LockFileCorruptRequest.hpp>
+#include <com/sun/star/document/ChangedByOthersRequest.hpp>
+#include <com/sun/star/document/ReloadEditableRequest.hpp>
+#include <com/sun/star/embed/XTransactedObject.hpp>
+#include <com/sun/star/embed/ElementModes.hpp>
+#include <com/sun/star/embed/UseBackupException.hpp>
+#include <com/sun/star/embed/XOptimizedStorage.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/frame/XTerminateListener.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/ucb/ContentCreationException.hpp>
+#include <com/sun/star/ucb/InteractiveIOException.hpp>
+#include <com/sun/star/ucb/CommandFailedException.hpp>
+#include <com/sun/star/ucb/CommandAbortedException.hpp>
+#include <com/sun/star/ucb/InteractiveLockingLockedException.hpp>
+#include <com/sun/star/ucb/InteractiveNetworkReadException.hpp>
+#include <com/sun/star/ucb/InteractiveNetworkWriteException.hpp>
+#include <com/sun/star/ucb/Lock.hpp>
+#include <com/sun/star/ucb/NameClashException.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/ucb/XProgressHandler.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/io/XTruncate.hpp>
+#include <com/sun/star/io/XSeekable.hpp>
+#include <com/sun/star/io/TempFile.hpp>
+#include <com/sun/star/lang/XSingleServiceFactory.hpp>
+#include <com/sun/star/ucb/InsertCommandArgument.hpp>
+#include <com/sun/star/ucb/NameClash.hpp>
+#include <com/sun/star/util/XModifiable.hpp>
+#include <com/sun/star/beans/NamedValue.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/security/DocumentDigitalSignatures.hpp>
+#include <com/sun/star/security/XCertificate.hpp>
+#include <tools/urlobj.hxx>
+#include <tools/fileutil.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/tempfile.hxx>
+#include <comphelper/fileurl.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/interaction.hxx>
+#include <comphelper/sequence.hxx>
+#include <comphelper/simplefileaccessinteraction.hxx>
+#include <comphelper/string.hxx>
+#include <framework/interaction.hxx>
+#include <utility>
+#include <svl/stritem.hxx>
+#include <svl/eitem.hxx>
+#include <svtools/sfxecode.hxx>
+#include <svl/itemset.hxx>
+#include <svl/intitem.hxx>
+#include <svtools/svparser.hxx>
+#include <sal/log.hxx>
+
+#include <unotools/streamwrap.hxx>
+
+#include <osl/file.hxx>
+
+#include <comphelper/storagehelper.hxx>
+#include <unotools/mediadescriptor.hxx>
+#include <comphelper/docpasswordhelper.hxx>
+#include <tools/datetime.hxx>
+#include <unotools/pathoptions.hxx>
+#include <svtools/asynclink.hxx>
+#include <ucbhelper/commandenvironment.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <unotools/ucbhelper.hxx>
+#include <unotools/progresshandlerwrap.hxx>
+#include <ucbhelper/content.hxx>
+#include <ucbhelper/interactionrequest.hxx>
+#include <sot/storage.hxx>
+#include <svl/documentlockfile.hxx>
+#include <svl/msodocumentlockfile.hxx>
+#include <com/sun/star/document/DocumentRevisionListPersistence.hpp>
+
+#include <sfx2/app.hxx>
+#include <sfx2/frame.hxx>
+#include <sfx2/dispatch.hxx>
+#include <sfx2/fcontnr.hxx>
+#include <sfx2/docfilt.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <sfx2/sfxuno.hxx>
+#include <openflag.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/svapp.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <unotools/fltrcfg.hxx>
+#include <sfx2/digitalsignatures.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <comphelper/threadpool.hxx>
+#include <o3tl/string_view.hxx>
+#include <condition_variable>
+
+#include <com/sun/star/io/WrongFormatException.hpp>
+
+#include <memory>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::graphic;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::io;
+using namespace ::com::sun::star::security;
+
+namespace
+{
+
+struct ReadOnlyMediumEntry
+{
+ ReadOnlyMediumEntry(std::shared_ptr<std::recursive_mutex> pMutex,
+ std::shared_ptr<bool> pIsDestructed)
+ : _pMutex(std::move(pMutex))
+ , _pIsDestructed(std::move(pIsDestructed))
+ {
+ }
+ std::shared_ptr<std::recursive_mutex> _pMutex;
+ std::shared_ptr<bool> _pIsDestructed;
+};
+
+}
+
+static std::mutex g_chkReadOnlyGlobalMutex;
+static bool g_bChkReadOnlyTaskRunning = false;
+static std::unordered_map<SfxMedium*, std::shared_ptr<ReadOnlyMediumEntry>> g_newReadOnlyDocs;
+static std::unordered_map<SfxMedium*, std::shared_ptr<ReadOnlyMediumEntry>> g_existingReadOnlyDocs;
+
+namespace {
+
+#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+
+bool IsSystemFileLockingUsed()
+{
+#if HAVE_FEATURE_MACOSX_SANDBOX
+ return true;
+#else
+ return officecfg::Office::Common::Misc::UseDocumentSystemFileLocking::get();
+#endif
+}
+
+
+bool IsOOoLockFileUsed()
+{
+#if HAVE_FEATURE_MACOSX_SANDBOX
+ return false;
+#else
+ return officecfg::Office::Common::Misc::UseDocumentOOoLockFile::get();
+#endif
+}
+
+bool IsLockingUsed()
+{
+ return officecfg::Office::Common::Misc::UseLocking::get();
+}
+
+#endif
+
+#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+bool IsWebDAVLockingUsed()
+{
+ return officecfg::Office::Common::Misc::UseWebDAVFileLocking::get();
+}
+#endif
+
+/// Gets default attributes of a file:// URL.
+sal_uInt64 GetDefaultFileAttributes(const OUString& rURL)
+{
+ sal_uInt64 nRet = 0;
+
+ if (!comphelper::isFileUrl(rURL))
+ return nRet;
+
+ // Make sure the file exists (and create it if not).
+ osl::File aFile(rURL);
+ osl::File::RC nRes = aFile.open(osl_File_OpenFlag_Create);
+ if (nRes != osl::File::E_None && nRes != osl::File::E_EXIST)
+ return nRet;
+
+ aFile.close();
+
+ osl::DirectoryItem aItem;
+ if (osl::DirectoryItem::get(rURL, aItem) != osl::DirectoryItem::E_None)
+ return nRet;
+
+ osl::FileStatus aStatus(osl_FileStatus_Mask_Attributes);
+ if (aItem.getFileStatus(aStatus) != osl::DirectoryItem::E_None)
+ return nRet;
+
+ nRet = aStatus.getAttributes();
+ return nRet;
+}
+
+/// Determines if rURL is safe to move or not.
+bool IsFileMovable(const INetURLObject& rURL)
+{
+#ifdef MACOSX
+ (void)rURL;
+ // Hide extension macOS-specific file property would be lost.
+ return false;
+#else
+
+ if (rURL.GetProtocol() != INetProtocol::File)
+ // Not a file:// URL.
+ return false;
+
+#ifdef UNX
+ OUString sPath = rURL.getFSysPath(FSysStyle::Unix);
+ if (sPath.isEmpty())
+ return false;
+
+ struct stat buf;
+ if (lstat(sPath.toUtf8().getStr(), &buf) != 0)
+ return false;
+
+ // Hardlink or symlink: osl::File::move() doesn't play with these nicely.
+ if (buf.st_nlink > 1 || S_ISLNK(buf.st_mode))
+ return false;
+#elif defined _WIN32
+ if (tools::IsMappedWebDAVPath(rURL.GetMainURL(INetURLObject::DecodeMechanism::NONE)))
+ return false;
+#endif
+
+ return true;
+#endif
+}
+
+class CheckReadOnlyTaskTerminateListener
+ : public ::cppu::WeakImplHelper<css::frame::XTerminateListener>
+{
+public:
+ // XEventListener
+ void SAL_CALL disposing(const css::lang::EventObject& Source) override;
+
+ // XTerminateListener
+ void SAL_CALL queryTermination(const css::lang::EventObject& aEvent) override;
+ void SAL_CALL notifyTermination(const css::lang::EventObject& aEvent) override;
+
+ bool bIsTerminated = false;
+ std::condition_variable mCond;
+ std::mutex mMutex;
+};
+
+class CheckReadOnlyTask : public comphelper::ThreadTask
+{
+public:
+ CheckReadOnlyTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag);
+ ~CheckReadOnlyTask();
+
+ virtual void doWork() override;
+
+private:
+ rtl::Reference<CheckReadOnlyTaskTerminateListener> m_xListener;
+};
+
+} // anonymous namespace
+
+CheckReadOnlyTask::CheckReadOnlyTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag)
+ : ThreadTask(pTag)
+ , m_xListener(new CheckReadOnlyTaskTerminateListener)
+{
+ Reference<css::frame::XDesktop> xDesktop
+ = css::frame::Desktop::create(comphelper::getProcessComponentContext());
+ if (xDesktop.is() && m_xListener != nullptr)
+ {
+ xDesktop->addTerminateListener(m_xListener);
+ }
+}
+
+CheckReadOnlyTask::~CheckReadOnlyTask()
+{
+ Reference<css::frame::XDesktop> xDesktop
+ = css::frame::Desktop::create(comphelper::getProcessComponentContext());
+ if (xDesktop.is() && m_xListener != nullptr)
+ {
+ std::unique_lock<std::mutex> lock(m_xListener->mMutex);
+ if (!m_xListener->bIsTerminated)
+ {
+ lock.unlock();
+ xDesktop->removeTerminateListener(m_xListener);
+ }
+ }
+}
+
+namespace
+{
+void SAL_CALL
+CheckReadOnlyTaskTerminateListener::disposing(const css::lang::EventObject& /*Source*/)
+{
+}
+
+void SAL_CALL
+CheckReadOnlyTaskTerminateListener::queryTermination(const css::lang::EventObject& /*aEvent*/)
+{
+}
+
+void SAL_CALL
+CheckReadOnlyTaskTerminateListener::notifyTermination(const css::lang::EventObject& /*aEvent*/)
+{
+ std::unique_lock<std::mutex> lock(mMutex);
+ bIsTerminated = true;
+ lock.unlock();
+ mCond.notify_one();
+}
+}
+
+class SfxMedium_Impl
+{
+public:
+ StreamMode m_nStorOpenMode;
+ ErrCodeMsg m_eError;
+ ErrCodeMsg m_eWarningError;
+
+ ::ucbhelper::Content aContent;
+ bool bUpdatePickList:1;
+ bool bIsTemp:1;
+ bool bDownloadDone:1;
+ bool bIsStorage:1;
+ bool bUseInteractionHandler:1;
+ bool bAllowDefaultIntHdl:1;
+ bool bDisposeStorage:1;
+ bool bStorageBasedOnInStream:1;
+ bool m_bSalvageMode:1;
+ bool m_bVersionsAlreadyLoaded:1;
+ bool m_bLocked:1;
+ bool m_bMSOLockFileCreated : 1;
+ bool m_bDisableUnlockWebDAV:1;
+ bool m_bGotDateTime:1;
+ bool m_bRemoveBackup:1;
+ bool m_bOriginallyReadOnly:1;
+ bool m_bOriginallyLoadedReadOnly:1;
+ bool m_bTriedStorage:1;
+ bool m_bRemote:1;
+ bool m_bInputStreamIsReadOnly:1;
+ bool m_bInCheckIn:1;
+ bool m_bDisableFileSync = false;
+ bool m_bNotifyWhenEditable = false;
+ /// if true, xStorage is an inner package and not directly from xStream
+ bool m_bODFWholesomeEncryption = false;
+
+ OUString m_aName;
+ OUString m_aLogicName;
+ OUString m_aLongName;
+
+ mutable std::shared_ptr<SfxItemSet> m_pSet;
+ mutable std::unique_ptr<INetURLObject> m_pURLObj;
+
+ std::shared_ptr<const SfxFilter> m_pFilter;
+ std::shared_ptr<const SfxFilter> m_pCustomFilter;
+
+ std::shared_ptr<std::recursive_mutex> m_pCheckEditableWorkerMutex;
+ std::shared_ptr<bool> m_pIsDestructed;
+ ImplSVEvent* m_pReloadEvent;
+
+ std::unique_ptr<SvStream> m_pInStream;
+ std::unique_ptr<SvStream> m_pOutStream;
+
+ OUString aOrigURL;
+ DateTime aExpireTime;
+ SfxFrameWeakRef wLoadTargetFrame;
+ SvKeyValueIteratorRef xAttributes;
+
+ svtools::AsynchronLink aDoneLink;
+
+ uno::Sequence < util::RevisionTag > aVersions;
+
+ std::unique_ptr<::utl::TempFileNamed> pTempFile;
+
+ uno::Reference<embed::XStorage> xStorage;
+ uno::Reference<embed::XStorage> m_xZipStorage;
+ uno::Reference<io::XInputStream> m_xInputStreamToLoadFrom;
+ uno::Reference<io::XInputStream> xInputStream;
+ uno::Reference<io::XStream> xStream;
+ uno::Reference<io::XStream> m_xLockingStream;
+ uno::Reference<task::XInteractionHandler> xInteraction;
+ uno::Reference<io::XStream> m_xODFDecryptedInnerPackageStream;
+ uno::Reference<embed::XStorage> m_xODFEncryptedOuterStorage;
+ uno::Reference<embed::XStorage> m_xODFDecryptedInnerZipStorage;
+
+ ErrCodeMsg nLastStorageError;
+
+ OUString m_aBackupURL;
+
+ // the following member is changed and makes sense only during saving
+ // TODO/LATER: in future the signature state should be controlled by the medium not by the document
+ // in this case the member will hold this information
+ SignatureState m_nSignatureState;
+
+ bool m_bHasEmbeddedObjects = false;
+
+ util::DateTime m_aDateTime;
+
+ uno::Sequence<beans::PropertyValue> m_aArgs;
+
+ explicit SfxMedium_Impl();
+ ~SfxMedium_Impl();
+ SfxMedium_Impl(const SfxMedium_Impl&) = delete;
+ SfxMedium_Impl& operator=(const SfxMedium_Impl&) = delete;
+
+ OUString getFilterMimeType() const
+ { return !m_pFilter ? OUString() : m_pFilter->GetMimeType(); }
+};
+
+SfxMedium_Impl::SfxMedium_Impl() :
+ m_nStorOpenMode(SFX_STREAM_READWRITE),
+ m_eError(ERRCODE_NONE),
+ m_eWarningError(ERRCODE_NONE),
+ bUpdatePickList(true),
+ bIsTemp( false ),
+ bDownloadDone( true ),
+ bIsStorage( false ),
+ bUseInteractionHandler( true ),
+ bAllowDefaultIntHdl( false ),
+ bDisposeStorage( false ),
+ bStorageBasedOnInStream( false ),
+ m_bSalvageMode( false ),
+ m_bVersionsAlreadyLoaded( false ),
+ m_bLocked( false ),
+ m_bMSOLockFileCreated( false ),
+ m_bDisableUnlockWebDAV( false ),
+ m_bGotDateTime( false ),
+ m_bRemoveBackup( false ),
+ m_bOriginallyReadOnly(false),
+ m_bOriginallyLoadedReadOnly(false),
+ m_bTriedStorage(false),
+ m_bRemote(false),
+ m_bInputStreamIsReadOnly(false),
+ m_bInCheckIn(false),
+ m_pReloadEvent(nullptr),
+ aExpireTime( DateTime( DateTime::SYSTEM ) + static_cast<sal_Int32>(10) ),
+ nLastStorageError( ERRCODE_NONE ),
+ m_nSignatureState( SignatureState::NOSIGNATURES )
+{
+}
+
+
+SfxMedium_Impl::~SfxMedium_Impl()
+{
+ aDoneLink.ClearPendingCall();
+
+ pTempFile.reset();
+ m_pSet.reset();
+ std::unique_lock<std::recursive_mutex> chkEditLock;
+ if (m_pCheckEditableWorkerMutex != nullptr)
+ chkEditLock = std::unique_lock<std::recursive_mutex>(*m_pCheckEditableWorkerMutex);
+ m_pURLObj.reset();
+}
+
+void SfxMedium::ResetError()
+{
+ pImpl->m_eError = ERRCODE_NONE;
+ if( pImpl->m_pInStream )
+ pImpl->m_pInStream->ResetError();
+ if( pImpl->m_pOutStream )
+ pImpl->m_pOutStream->ResetError();
+}
+
+ErrCodeMsg const & SfxMedium::GetWarningError() const
+{
+ return pImpl->m_eWarningError;
+}
+
+ErrCodeMsg const & SfxMedium::GetLastStorageCreationState() const
+{
+ return pImpl->nLastStorageError;
+}
+
+void SfxMedium::SetError(ErrCodeMsg nError)
+{
+ pImpl->m_eError = nError;
+}
+
+void SfxMedium::SetWarningError(const ErrCodeMsg& nWarningError)
+{
+ pImpl->m_eWarningError = nWarningError;
+}
+
+ErrCodeMsg SfxMedium::GetErrorCode() const
+{
+ ErrCodeMsg lError = pImpl->m_eError;
+ if(!lError && pImpl->m_pInStream)
+ lError = pImpl->m_pInStream->GetErrorCode();
+ if(!lError && pImpl->m_pOutStream)
+ lError = pImpl->m_pOutStream->GetErrorCode();
+ return lError;
+}
+
+void SfxMedium::CheckFileDate( const util::DateTime& aInitDate )
+{
+ GetInitFileDate( true );
+ if ( pImpl->m_aDateTime.Seconds == aInitDate.Seconds
+ && pImpl->m_aDateTime.Minutes == aInitDate.Minutes
+ && pImpl->m_aDateTime.Hours == aInitDate.Hours
+ && pImpl->m_aDateTime.Day == aInitDate.Day
+ && pImpl->m_aDateTime.Month == aInitDate.Month
+ && pImpl->m_aDateTime.Year == aInitDate.Year )
+ return;
+
+ uno::Reference< task::XInteractionHandler > xHandler = GetInteractionHandler();
+
+ if ( !xHandler.is() )
+ return;
+
+ try
+ {
+ ::rtl::Reference< ::ucbhelper::InteractionRequest > xInteractionRequestImpl = new ::ucbhelper::InteractionRequest( uno::Any(
+ document::ChangedByOthersRequest() ) );
+ uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations{
+ new ::ucbhelper::InteractionAbort( xInteractionRequestImpl.get() ),
+ new ::ucbhelper::InteractionApprove( xInteractionRequestImpl.get() )
+ };
+ xInteractionRequestImpl->setContinuations( aContinuations );
+
+ xHandler->handle( xInteractionRequestImpl );
+
+ ::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = xInteractionRequestImpl->getSelection();
+ if ( uno::Reference< task::XInteractionAbort >( xSelected.get(), uno::UNO_QUERY ).is() )
+ {
+ SetError(ERRCODE_ABORT);
+ }
+ }
+ catch ( const uno::Exception& )
+ {}
+}
+
+bool SfxMedium::DocNeedsFileDateCheck() const
+{
+ return ( !IsReadOnly() && ( GetURLObject().GetProtocol() == INetProtocol::File ||
+ GetURLObject().isAnyKnownWebDAVScheme() ) );
+}
+
+util::DateTime const & SfxMedium::GetInitFileDate( bool bIgnoreOldValue )
+{
+ if ( ( bIgnoreOldValue || !pImpl->m_bGotDateTime ) && !pImpl->m_aLogicName.isEmpty() )
+ {
+ try
+ {
+ // add a default css::ucb::XCommandEnvironment
+ // in order to have the WebDAV UCP provider manage http/https authentication correctly
+ ::ucbhelper::Content aContent( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ),
+ utl::UCBContentHelper::getDefaultCommandEnvironment(),
+ comphelper::getProcessComponentContext() );
+
+ aContent.getPropertyValue("DateModified") >>= pImpl->m_aDateTime;
+ pImpl->m_bGotDateTime = true;
+ }
+ catch ( const css::uno::Exception& )
+ {
+ }
+ }
+
+ return pImpl->m_aDateTime;
+}
+
+
+Reference < XContent > SfxMedium::GetContent() const
+{
+ if ( !pImpl->aContent.get().is() )
+ {
+ Reference < css::ucb::XContent > xContent;
+
+ // tdf#95144 add a default css::ucb::XCommandEnvironment
+ // in order to have the WebDAV UCP provider manage https protocol certificates correctly
+ css:: uno::Reference< task::XInteractionHandler > xIH(
+ css::task::InteractionHandler::createWithParent( comphelper::getProcessComponentContext(), nullptr ) );
+
+ css::uno::Reference< css::ucb::XProgressHandler > xProgress;
+ rtl::Reference<::ucbhelper::CommandEnvironment> pCommandEnv = new ::ucbhelper::CommandEnvironment( new comphelper::SimpleFileAccessInteraction( xIH ), xProgress );
+
+ const SfxUnoAnyItem* pItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_CONTENT, false);
+ if ( pItem )
+ pItem->GetValue() >>= xContent;
+
+ if ( xContent.is() )
+ {
+ try
+ {
+ pImpl->aContent = ::ucbhelper::Content( xContent, pCommandEnv, comphelper::getProcessComponentContext() );
+ }
+ catch ( const Exception& )
+ {
+ }
+ }
+ else
+ {
+ // TODO: SAL_WARN( "sfx.doc", "SfxMedium::GetContent()\nCreate Content? This code exists as fallback only. Please clarify, why it's used.");
+ OUString aURL;
+ if ( !pImpl->m_aName.isEmpty() )
+ osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aURL );
+ else if ( !pImpl->m_aLogicName.isEmpty() )
+ aURL = GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ if (!aURL.isEmpty() )
+ (void)::ucbhelper::Content::create( aURL, pCommandEnv, comphelper::getProcessComponentContext(), pImpl->aContent );
+ }
+ }
+
+ return pImpl->aContent.get();
+}
+
+OUString SfxMedium::GetBaseURL( bool bForSaving )
+{
+ if (bForSaving)
+ {
+ bool bIsRemote = IsRemote();
+ if ((bIsRemote && !officecfg::Office::Common::Save::URL::Internet::get())
+ || (!bIsRemote && !officecfg::Office::Common::Save::URL::FileSystem::get()))
+ return OUString();
+ }
+
+ if (const SfxStringItem* pBaseURLItem = GetItemSet().GetItem<SfxStringItem>(SID_DOC_BASEURL))
+ return pBaseURLItem->GetValue();
+
+ OUString aBaseURL;
+ if (!utl::ConfigManager::IsFuzzing() && GetContent().is())
+ {
+ try
+ {
+ Any aAny = pImpl->aContent.getPropertyValue("BaseURI");
+ aAny >>= aBaseURL;
+ }
+ catch ( const css::uno::Exception& )
+ {
+ }
+
+ if ( aBaseURL.isEmpty() )
+ aBaseURL = GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ }
+ return aBaseURL;
+}
+
+bool SfxMedium::IsSkipImages() const
+{
+ const SfxStringItem* pSkipImagesItem = GetItemSet().GetItem<SfxStringItem>(SID_FILE_FILTEROPTIONS);
+ return pSkipImagesItem && pSkipImagesItem->GetValue() == "SkipImages";
+}
+
+SvStream* SfxMedium::GetInStream()
+{
+ if ( pImpl->m_pInStream )
+ return pImpl->m_pInStream.get();
+
+ if ( pImpl->pTempFile )
+ {
+ pImpl->m_pInStream.reset( new SvFileStream(pImpl->m_aName, pImpl->m_nStorOpenMode) );
+
+ pImpl->m_eError = pImpl->m_pInStream->GetError();
+
+ if (!pImpl->m_eError && (pImpl->m_nStorOpenMode & StreamMode::WRITE)
+ && ! pImpl->m_pInStream->IsWritable() )
+ {
+ pImpl->m_eError = ERRCODE_IO_ACCESSDENIED;
+ pImpl->m_pInStream.reset();
+ }
+ else
+ return pImpl->m_pInStream.get();
+ }
+
+ GetMedium_Impl();
+
+ if ( GetErrorIgnoreWarning() )
+ return nullptr;
+
+ return pImpl->m_pInStream.get();
+}
+
+
+void SfxMedium::CloseInStream()
+{
+ CloseInStream_Impl();
+}
+
+void SfxMedium::CloseInStream_Impl(bool bInDestruction)
+{
+ // if there is a storage based on the InStream, we have to
+ // close the storage, too, because otherwise the storage
+ // would use an invalid ( deleted ) stream.
+ if ( pImpl->m_pInStream && pImpl->xStorage.is() )
+ {
+ if ( pImpl->bStorageBasedOnInStream )
+ CloseStorage();
+ }
+
+ if ( pImpl->m_pInStream && !GetContent().is() && !bInDestruction )
+ {
+ CreateTempFile();
+ return;
+ }
+
+ pImpl->m_pInStream.reset();
+ if ( pImpl->m_pSet )
+ pImpl->m_pSet->ClearItem( SID_INPUTSTREAM );
+
+ CloseZipStorage_Impl();
+ pImpl->xInputStream.clear();
+
+ if ( !pImpl->m_pOutStream )
+ {
+ // output part of the stream is not used so the whole stream can be closed
+ // TODO/LATER: is it correct?
+ pImpl->xStream.clear();
+ if ( pImpl->m_pSet )
+ pImpl->m_pSet->ClearItem( SID_STREAM );
+ }
+}
+
+
+SvStream* SfxMedium::GetOutStream()
+{
+ if ( !pImpl->m_pOutStream )
+ {
+ // Create a temp. file if there is none because we always
+ // need one.
+ CreateTempFile( false );
+
+ if ( pImpl->pTempFile )
+ {
+ // On windows we try to re-use XOutStream from xStream if that exists;
+ // because opening new SvFileStream in this situation may fail with ERROR_SHARING_VIOLATION
+ // TODO: this is a horrible hack that should probably be removed,
+ // somebody needs to investigate this more thoroughly...
+ if (getenv("SFX_MEDIUM_REUSE_STREAM") && pImpl->xStream.is())
+ {
+ assert(pImpl->xStream->getOutputStream().is()); // need that...
+ pImpl->m_pOutStream = utl::UcbStreamHelper::CreateStream(
+ pImpl->xStream, false);
+ }
+ else
+ {
+ // On Unix don't try to re-use XOutStream from xStream if that exists;
+ // it causes fdo#59022 (fails opening files via SMB on Linux)
+ pImpl->m_pOutStream.reset( new SvFileStream(
+ pImpl->m_aName, StreamMode::STD_READWRITE) );
+ }
+ CloseStorage();
+ }
+ }
+
+ return pImpl->m_pOutStream.get();
+}
+
+
+void SfxMedium::CloseOutStream()
+{
+ CloseOutStream_Impl();
+}
+
+void SfxMedium::CloseOutStream_Impl()
+{
+ if ( pImpl->m_pOutStream )
+ {
+ // if there is a storage based on the OutStream, we have to
+ // close the storage, too, because otherwise the storage
+ // would use an invalid ( deleted ) stream.
+ //TODO/MBA: how to deal with this?!
+ //maybe we need a new flag when the storage was created from the outstream
+ if ( pImpl->xStorage.is() )
+ {
+ CloseStorage();
+ }
+
+ pImpl->m_pOutStream.reset();
+ }
+
+ if ( !pImpl->m_pInStream )
+ {
+ // input part of the stream is not used so the whole stream can be closed
+ // TODO/LATER: is it correct?
+ pImpl->xStream.clear();
+ if ( pImpl->m_pSet )
+ pImpl->m_pSet->ClearItem( SID_STREAM );
+ }
+}
+
+
+const OUString& SfxMedium::GetPhysicalName() const
+{
+ if ( pImpl->m_aName.isEmpty() && !pImpl->m_aLogicName.isEmpty() )
+ const_cast<SfxMedium*>(this)->CreateFileStream();
+
+ // return the name then
+ return pImpl->m_aName;
+}
+
+
+void SfxMedium::CreateFileStream()
+{
+ // force synchron
+ if( pImpl->m_pInStream )
+ {
+ SvLockBytes* pBytes = pImpl->m_pInStream->GetLockBytes();
+ if( pBytes )
+ pBytes->SetSynchronMode();
+ }
+
+ GetInStream();
+ if( pImpl->m_pInStream )
+ {
+ CreateTempFile( false );
+ pImpl->bIsTemp = true;
+ CloseInStream_Impl();
+ }
+}
+
+
+bool SfxMedium::Commit()
+{
+ if( pImpl->xStorage.is() )
+ StorageCommit_Impl();
+ else if( pImpl->m_pOutStream )
+ pImpl->m_pOutStream->FlushBuffer();
+ else if( pImpl->m_pInStream )
+ pImpl->m_pInStream->FlushBuffer();
+
+ if ( GetErrorIgnoreWarning() == ERRCODE_NONE )
+ {
+ // does something only in case there is a temporary file ( means aName points to different location than aLogicName )
+ Transfer_Impl();
+ }
+
+ bool bResult = ( GetErrorIgnoreWarning() == ERRCODE_NONE );
+
+ if ( bResult && DocNeedsFileDateCheck() )
+ GetInitFileDate( true );
+
+ // remove truncation mode from the flags
+ pImpl->m_nStorOpenMode &= ~StreamMode::TRUNC;
+ return bResult;
+}
+
+
+bool SfxMedium::IsStorage()
+{
+ if ( pImpl->xStorage.is() )
+ return true;
+
+ if ( pImpl->m_bTriedStorage )
+ return pImpl->bIsStorage;
+
+ if ( pImpl->pTempFile )
+ {
+ OUString aURL;
+ if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aURL )
+ != osl::FileBase::E_None )
+ {
+ SAL_WARN( "sfx.doc", "Physical name '" << pImpl->m_aName << "' not convertible to file URL");
+ }
+ pImpl->bIsStorage = SotStorage::IsStorageFile( aURL ) && !SotStorage::IsOLEStorage( aURL);
+ if ( !pImpl->bIsStorage )
+ pImpl->m_bTriedStorage = true;
+ }
+ else if ( GetInStream() )
+ {
+ pImpl->bIsStorage = SotStorage::IsStorageFile( pImpl->m_pInStream.get() ) && !SotStorage::IsOLEStorage( pImpl->m_pInStream.get() );
+ if ( !pImpl->m_pInStream->GetError() && !pImpl->bIsStorage )
+ pImpl->m_bTriedStorage = true;
+ }
+
+ return pImpl->bIsStorage;
+}
+
+
+bool SfxMedium::IsPreview_Impl() const
+{
+ bool bPreview = false;
+ const SfxBoolItem* pPreview = GetItemSet().GetItem(SID_PREVIEW, false);
+ if ( pPreview )
+ bPreview = pPreview->GetValue();
+ else
+ {
+ const SfxStringItem* pFlags = GetItemSet().GetItem(SID_OPTIONS, false);
+ if ( pFlags )
+ {
+ OUString aFileFlags = pFlags->GetValue();
+ aFileFlags = aFileFlags.toAsciiUpperCase();
+ if ( -1 != aFileFlags.indexOf( 'B' ) )
+ bPreview = true;
+ }
+ }
+
+ return bPreview;
+}
+
+
+void SfxMedium::StorageBackup_Impl()
+{
+ ::ucbhelper::Content aOriginalContent;
+ Reference< css::ucb::XCommandEnvironment > xDummyEnv;
+
+ bool bBasedOnOriginalFile =
+ !pImpl->pTempFile
+ && ( pImpl->m_aLogicName.isEmpty() || !pImpl->m_bSalvageMode )
+ && !GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ).isEmpty()
+ && GetURLObject().GetProtocol() == INetProtocol::File
+ && ::utl::UCBContentHelper::IsDocument( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+
+ if ( bBasedOnOriginalFile && pImpl->m_aBackupURL.isEmpty()
+ && ::ucbhelper::Content::create( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext(), aOriginalContent ) )
+ {
+ DoInternalBackup_Impl( aOriginalContent );
+ if( pImpl->m_aBackupURL.isEmpty() )
+ SetError(ERRCODE_SFX_CANTCREATEBACKUP);
+ }
+}
+
+
+OUString const & SfxMedium::GetBackup_Impl()
+{
+ if ( pImpl->m_aBackupURL.isEmpty() )
+ StorageBackup_Impl();
+
+ return pImpl->m_aBackupURL;
+}
+
+
+uno::Reference < embed::XStorage > SfxMedium::GetOutputStorage()
+{
+ if ( GetErrorIgnoreWarning() )
+ return uno::Reference< embed::XStorage >();
+
+ // if the medium was constructed with a Storage: use this one, not a temp. storage
+ // if a temporary storage already exists: use it
+ if (pImpl->xStorage.is()
+ && (pImpl->m_bODFWholesomeEncryption || pImpl->m_aLogicName.isEmpty() || pImpl->pTempFile))
+ {
+ return pImpl->xStorage;
+ }
+
+ // if necessary close stream that was used for reading
+ if ( pImpl->m_pInStream && !pImpl->m_pInStream->IsWritable() )
+ CloseInStream();
+
+ DBG_ASSERT( !pImpl->m_pOutStream, "OutStream in a readonly Medium?!" );
+
+ // TODO/LATER: The current solution is to store the document temporary and then copy it to the target location;
+ // in future it should be stored directly and then copied to the temporary location, since in this case no
+ // file attributes have to be preserved and system copying mechanics could be used instead of streaming.
+ CreateTempFileNoCopy();
+
+ return GetStorage();
+}
+
+
+bool SfxMedium::SetEncryptionDataToStorage_Impl()
+{
+ // in case media-descriptor contains password it should be used on opening
+ if ( !pImpl->xStorage.is() || !pImpl->m_pSet )
+ return false;
+
+ uno::Sequence< beans::NamedValue > aEncryptionData;
+ if ( !GetEncryptionData_Impl( pImpl->m_pSet.get(), aEncryptionData ) )
+ return false;
+
+ // replace the password with encryption data
+ pImpl->m_pSet->ClearItem( SID_PASSWORD );
+ pImpl->m_pSet->Put( SfxUnoAnyItem( SID_ENCRYPTIONDATA, uno::Any( aEncryptionData ) ) );
+
+ try
+ {
+ ::comphelper::OStorageHelper::SetCommonStorageEncryptionData( pImpl->xStorage, aEncryptionData );
+ }
+ catch( const uno::Exception& )
+ {
+ SAL_WARN( "sfx.doc", "It must be possible to set a common password for the storage" );
+ SetError(ERRCODE_IO_GENERAL);
+ return false;
+ }
+ return true;
+}
+
+#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+
+// FIXME: Hmm actually lock files should be used for sftp: documents
+// even if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT. Only the use of lock
+// files for *local* documents is unnecessary in that case. But
+// actually, the checks for sftp: here are just wishful thinking; I
+// don't this there is any support for actually editing documents
+// behind sftp: URLs anyway.
+
+// Sure, there could perhaps be a 3rd-party extension that brings UCB
+// the potential to handle files behind sftp:. But there could also be
+// an extension that handles some arbitrary foobar: scheme *and* it
+// could be that lock files would be the correct thing to use for
+// foobar: documents, too. But the hardcoded test below won't know
+// that. Clearly the knowledge whether lock files should be used or
+// not for some URL scheme belongs in UCB, not here.
+
+namespace
+{
+
+OUString tryMSOwnerFiles(std::u16string_view sDocURL)
+{
+ svt::MSODocumentLockFile aMSOLockFile(sDocURL);
+ LockFileEntry aData;
+ try
+ {
+ aData = aMSOLockFile.GetLockData();
+ }
+ catch( const uno::Exception& )
+ {
+ return OUString();
+ }
+
+ OUString sUserData = aData[LockFileComponent::OOOUSERNAME];
+
+ if (!sUserData.isEmpty())
+ sUserData += " (MS Office)"; // Mention the used office suite
+
+ return sUserData;
+}
+
+OUString tryForeignLockfiles(std::u16string_view sDocURL)
+{
+ OUString sUserData = tryMSOwnerFiles(sDocURL);
+ // here we can test for empty result, and add other known applications' lockfile testing
+ return sUserData.trim();
+}
+}
+
+SfxMedium::ShowLockResult SfxMedium::ShowLockedDocumentDialog(const LockFileEntry& aData,
+ bool bIsLoading, bool bOwnLock,
+ bool bHandleSysLocked)
+{
+ ShowLockResult nResult = ShowLockResult::NoLock;
+
+ // tdf#92817: Simple check for empty lock file that needs to be deleted, when system locking is enabled
+ if( aData[LockFileComponent::OOOUSERNAME].isEmpty() && aData[LockFileComponent::SYSUSERNAME].isEmpty() && !bHandleSysLocked )
+ bOwnLock=true;
+
+ // show the interaction regarding the document opening
+ uno::Reference< task::XInteractionHandler > xHandler = GetInteractionHandler();
+
+ if ( xHandler.is() && ( bIsLoading || !bHandleSysLocked || bOwnLock ) )
+ {
+ OUString aDocumentURL
+ = GetURLObject().GetLastName(INetURLObject::DecodeMechanism::WithCharset);
+ OUString aInfo;
+ ::rtl::Reference< ::ucbhelper::InteractionRequest > xInteractionRequestImpl;
+
+ sal_Int32 nContinuations = 3;
+
+ if ( bOwnLock )
+ {
+ aInfo = aData[LockFileComponent::EDITTIME];
+
+ xInteractionRequestImpl = new ::ucbhelper::InteractionRequest( uno::Any(
+ document::OwnLockOnDocumentRequest( OUString(), uno::Reference< uno::XInterface >(), aDocumentURL, aInfo, !bIsLoading ) ) );
+ }
+ else
+ {
+ // Use a fourth continuation in case there's no filesystem lock:
+ // "Ignore lock file and open/replace the document"
+ if (!bHandleSysLocked)
+ nContinuations = 4;
+
+ if ( !aData[LockFileComponent::OOOUSERNAME].isEmpty() )
+ aInfo = aData[LockFileComponent::OOOUSERNAME];
+ else
+ aInfo = aData[LockFileComponent::SYSUSERNAME];
+
+ if (aInfo.isEmpty() && !GetURLObject().isAnyKnownWebDAVScheme())
+ // Try to get name of user who has locked the file using other applications
+ aInfo = tryForeignLockfiles(
+ GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::NONE));
+
+ if ( !aInfo.isEmpty() && !aData[LockFileComponent::EDITTIME].isEmpty() )
+ aInfo += " ( " + aData[LockFileComponent::EDITTIME] + " )";
+
+ if (!bIsLoading) // so, !bHandleSysLocked
+ {
+ xInteractionRequestImpl = new ::ucbhelper::InteractionRequest(uno::Any(
+ document::LockedOnSavingRequest(OUString(), uno::Reference< uno::XInterface >(), aDocumentURL, aInfo)));
+ // Currently, only the last "Retry" continuation (meaning ignore the lock and try overwriting) can be returned.
+ }
+ else /*logically therefore bIsLoading is set */
+ {
+ xInteractionRequestImpl = new ::ucbhelper::InteractionRequest( uno::Any(
+ document::LockedDocumentRequest( OUString(), uno::Reference< uno::XInterface >(), aDocumentURL, aInfo ) ) );
+ }
+ }
+
+ uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations(nContinuations);
+ auto pContinuations = aContinuations.getArray();
+ pContinuations[0] = new ::ucbhelper::InteractionAbort( xInteractionRequestImpl.get() );
+ pContinuations[1] = new ::ucbhelper::InteractionApprove( xInteractionRequestImpl.get() );
+ pContinuations[2] = new ::ucbhelper::InteractionDisapprove( xInteractionRequestImpl.get() );
+ if (nContinuations > 3)
+ {
+ // We use InteractionRetry to reflect that user wants to
+ // ignore the (stale?) alien lock file and open/overwrite the document
+ pContinuations[3] = new ::ucbhelper::InteractionRetry(xInteractionRequestImpl.get());
+ }
+ xInteractionRequestImpl->setContinuations( aContinuations );
+
+ xHandler->handle( xInteractionRequestImpl );
+
+ bool bOpenReadOnly = false;
+ ::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = xInteractionRequestImpl->getSelection();
+ if ( uno::Reference< task::XInteractionAbort >( xSelected.get(), uno::UNO_QUERY ).is() )
+ {
+ SetError(ERRCODE_ABORT);
+ }
+ else if ( uno::Reference< task::XInteractionDisapprove >( xSelected.get(), uno::UNO_QUERY ).is() )
+ {
+ // own lock on loading, user has selected to ignore the lock
+ // own lock on saving, user has selected to ignore the lock
+ // alien lock on loading, user has selected to edit a copy of document
+ // TODO/LATER: alien lock on saving, user has selected to do SaveAs to different location
+ if ( !bOwnLock ) // bIsLoading implied from outermost condition
+ {
+ // means that a copy of the document should be opened
+ GetItemSet().Put( SfxBoolItem( SID_TEMPLATE, true ) );
+ }
+ else
+ nResult = ShowLockResult::Succeeded;
+ }
+ else if (uno::Reference< task::XInteractionRetry >(xSelected.get(), uno::UNO_QUERY).is())
+ {
+ // User decided to ignore the alien (stale?) lock file without filesystem lock
+ nResult = ShowLockResult::Succeeded;
+ }
+ else if (uno::Reference< task::XInteractionApprove >( xSelected.get(), uno::UNO_QUERY ).is())
+ {
+ bOpenReadOnly = true;
+ }
+ else // user selected "Notify"
+ {
+ pImpl->m_bNotifyWhenEditable = true;
+ AddToCheckEditableWorkerList();
+ bOpenReadOnly = true;
+ }
+
+ if (bOpenReadOnly)
+ {
+ // own lock on loading, user has selected to open readonly
+ // own lock on saving, user has selected to open readonly
+ // alien lock on loading, user has selected to retry saving
+ // TODO/LATER: alien lock on saving, user has selected to retry saving
+
+ if (bIsLoading)
+ GetItemSet().Put(SfxBoolItem(SID_DOC_READONLY, true));
+ else
+ nResult = ShowLockResult::Try;
+ }
+ }
+ else
+ {
+ if ( bIsLoading )
+ {
+ // if no interaction handler is provided the default answer is open readonly
+ // that usually happens in case the document is loaded per API
+ // so the document must be opened readonly for backward compatibility
+ GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
+ }
+ else
+ SetError(ERRCODE_IO_ACCESSDENIED);
+
+ }
+
+ return nResult;
+}
+
+bool SfxMedium::ShowLockFileProblemDialog(MessageDlg nWhichDlg)
+{
+ // system file locking is not active, ask user whether he wants to open the document without any locking
+ uno::Reference< task::XInteractionHandler > xHandler = GetInteractionHandler();
+
+ if (xHandler.is())
+ {
+ ::rtl::Reference< ::ucbhelper::InteractionRequest > xIgnoreRequestImpl;
+
+ switch (nWhichDlg)
+ {
+ case MessageDlg::LockFileIgnore:
+ xIgnoreRequestImpl = new ::ucbhelper::InteractionRequest(uno::Any( document::LockFileIgnoreRequest() ));
+ break;
+ case MessageDlg::LockFileCorrupt:
+ xIgnoreRequestImpl = new ::ucbhelper::InteractionRequest(uno::Any( document::LockFileCorruptRequest() ));
+ break;
+ }
+
+ uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations{
+ new ::ucbhelper::InteractionAbort(xIgnoreRequestImpl.get()),
+ new ::ucbhelper::InteractionApprove(xIgnoreRequestImpl.get())
+ };
+ xIgnoreRequestImpl->setContinuations(aContinuations);
+
+ xHandler->handle(xIgnoreRequestImpl);
+
+ ::rtl::Reference< ::ucbhelper::InteractionContinuation > xSelected = xIgnoreRequestImpl->getSelection();
+ bool bReadOnly = true;
+
+ if (uno::Reference<task::XInteractionAbort>(xSelected.get(), uno::UNO_QUERY).is())
+ {
+ SetError(ERRCODE_ABORT);
+ bReadOnly = false;
+ }
+ else if (!uno::Reference<task::XInteractionApprove>(xSelected.get(), uno::UNO_QUERY).is())
+ {
+ // user selected "Notify"
+ pImpl->m_bNotifyWhenEditable = true;
+ AddToCheckEditableWorkerList();
+ }
+
+ if (bReadOnly)
+ GetItemSet().Put(SfxBoolItem(SID_DOC_READONLY, true));
+
+ return bReadOnly;
+ }
+
+ return false;
+}
+
+namespace
+{
+ bool isSuitableProtocolForLocking(const OUString & rLogicName)
+ {
+ INetURLObject aUrl( rLogicName );
+ INetProtocol eProt = aUrl.GetProtocol();
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+ if (eProt == INetProtocol::File) {
+ return true;
+ }
+#endif
+ return eProt == INetProtocol::Smb || eProt == INetProtocol::Sftp;
+ }
+}
+
+namespace
+{
+
+// for LOCK request, suppress dialog on 403, typically indicates read-only
+// document and there's a 2nd dialog prompting to open a copy anyway
+class LockInteractionHandler : public ::cppu::WeakImplHelper<task::XInteractionHandler>
+{
+private:
+ uno::Reference<task::XInteractionHandler> m_xHandler;
+
+public:
+ explicit LockInteractionHandler(uno::Reference<task::XInteractionHandler> const& xHandler)
+ : m_xHandler(xHandler)
+ {
+ }
+
+ virtual void SAL_CALL handle(uno::Reference<task::XInteractionRequest> const& xRequest) override
+ {
+ ucb::InteractiveNetworkWriteException readException;
+ ucb::InteractiveNetworkReadException writeException;
+ if ((xRequest->getRequest() >>= readException)
+ || (xRequest->getRequest() >>= writeException))
+ {
+ return; // 403 gets reported as one of these; ignore to avoid dialog
+ }
+ m_xHandler->handle(xRequest);
+ }
+};
+
+} // namespace
+
+#endif // HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+
+// sets SID_DOC_READONLY if the document cannot be opened for editing
+// if user cancel the loading the ERROR_ABORT is set
+SfxMedium::LockFileResult SfxMedium::LockOrigFileOnDemand(bool bLoading, bool bNoUI,
+ bool bTryIgnoreLockFile,
+ LockFileEntry* pLockData)
+{
+#if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+ (void) bLoading;
+ (void) bNoUI;
+ (void) bTryIgnoreLockFile;
+ (void) pLockData;
+ return LockFileResult::Succeeded;
+#else
+ LockFileResult eResult = LockFileResult::Failed;
+
+ // check if path scheme is http:// or https://
+ // may be this is better if used always, in Android and iOS as well?
+ // if this code should be always there, remember to move the relevant code in UnlockFile method as well !
+
+ if ( GetURLObject().isAnyKnownWebDAVScheme() )
+ {
+ // do nothing if WebDAV locking is disabled
+ if (!IsWebDAVLockingUsed())
+ return LockFileResult::Succeeded;
+
+ {
+ bool bResult = pImpl->m_bLocked;
+ bool bIsTemplate = false;
+ // so, this is webdav stuff...
+ if ( !bResult )
+ {
+ // no read-write access is necessary on loading if the document is explicitly opened as copy
+ const SfxBoolItem* pTemplateItem = GetItemSet().GetItem(SID_TEMPLATE, false);
+ bIsTemplate = ( bLoading && pTemplateItem && pTemplateItem->GetValue() );
+ }
+
+ if ( !bIsTemplate && !bResult && !IsReadOnly() )
+ {
+ ShowLockResult bUIStatus = ShowLockResult::NoLock;
+ do
+ {
+ if( !bResult )
+ {
+ uno::Reference< task::XInteractionHandler > xCHandler = GetInteractionHandler( true );
+ // Dialog with error is superfluous:
+ // on loading, will result in read-only with infobar.
+ // bNoUI case for Reload failing, will open dialog later.
+ if (bLoading || bNoUI)
+ {
+ xCHandler = new LockInteractionHandler(xCHandler);
+ }
+ Reference< css::ucb::XCommandEnvironment > xComEnv = new ::ucbhelper::CommandEnvironment(
+ xCHandler, Reference< css::ucb::XProgressHandler >() );
+
+ ucbhelper::Content aContentToLock(
+ GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ),
+ xComEnv, comphelper::getProcessComponentContext() );
+
+ try
+ {
+ aContentToLock.lock();
+ bResult = true;
+ }
+ catch ( ucb::InteractiveLockingLockedException& )
+ {
+ // received when the resource is already locked
+ if (!bNoUI || pLockData)
+ {
+ // get the lock owner, using a special ucb.webdav property
+ // the owner property retrieved here is what the other principal send the server
+ // when activating the lock.
+ // See http://tools.ietf.org/html/rfc4918#section-14.17 for details
+ LockFileEntry aLockData;
+ aLockData[LockFileComponent::OOOUSERNAME] = "Unknown user";
+ // This solution works right when the LO user name and the WebDAV user
+ // name are the same.
+ // A better thing to do would be to obtain the 'real' WebDAV user name,
+ // but that's not possible from a WebDAV UCP provider client.
+ LockFileEntry aOwnData = svt::LockFileCommon::GenerateOwnEntry();
+ // use the current LO user name as the system name
+ aLockData[LockFileComponent::SYSUSERNAME]
+ = aOwnData[LockFileComponent::SYSUSERNAME];
+
+ uno::Sequence<css::ucb::Lock> aLocks;
+ // getting the property, send a PROPFIND to the server over the net
+ if ((aContentToLock.getPropertyValue("DAV:lockdiscovery") >>= aLocks) && aLocks.hasElements())
+ {
+ // got at least a lock, show the owner of the first lock returned
+ css::ucb::Lock aLock = aLocks[0];
+ OUString aOwner;
+ if (aLock.Owner >>= aOwner)
+ {
+ // we need to display the WebDAV user name owning the lock, not the local one
+ aLockData[LockFileComponent::OOOUSERNAME] = aOwner;
+ }
+ }
+
+ if (!bNoUI)
+ {
+ bUIStatus = ShowLockedDocumentDialog(aLockData, bLoading, false,
+ true);
+ }
+
+ if (pLockData)
+ {
+ std::copy(aLockData.begin(), aLockData.end(), pLockData->begin());
+ }
+ }
+ }
+ catch( ucb::InteractiveNetworkWriteException& )
+ {
+ // This catch it's not really needed, here just for the sake of documentation on the behaviour.
+ // This is the most likely reason:
+ // - the remote site is a WebDAV with special configuration: read/only for read operations
+ // and read/write for write operations, the user is not allowed to lock/write and
+ // she cancelled the credentials request.
+ // this is not actually an error, but the exception is sent directly from ucb, avoiding the automatic
+ // management that takes part in cancelCommandExecution()
+ // Unfortunately there is no InteractiveNetwork*Exception available to signal this more correctly
+ // since it mostly happens on read/only part of webdav, this can be the most correct
+ // exception available
+ }
+ catch( uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "Locking exception: WebDAV while trying to lock the file" );
+ }
+ }
+ } while( !bResult && bUIStatus == ShowLockResult::Try );
+ }
+
+ pImpl->m_bLocked = bResult;
+
+ if ( !bResult && GetErrorIgnoreWarning() == ERRCODE_NONE )
+ {
+ // the error should be set in case it is storing process
+ // or the document has been opened for editing explicitly
+ const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
+
+ if ( !bLoading || (pReadOnlyItem && !pReadOnlyItem->GetValue()) )
+ SetError(ERRCODE_IO_ACCESSDENIED);
+ else
+ GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
+ }
+
+ // when the file is locked, get the current file date
+ if ( bResult && DocNeedsFileDateCheck() )
+ GetInitFileDate( true );
+
+ if ( bResult )
+ eResult = LockFileResult::Succeeded;
+ }
+ return eResult;
+ }
+
+ if (!IsLockingUsed())
+ return LockFileResult::Succeeded;
+ if (GetURLObject().HasError())
+ return eResult;
+
+ try
+ {
+ if ( pImpl->m_bLocked && bLoading
+ && GetURLObject().GetProtocol() == INetProtocol::File )
+ {
+ // if the document is already locked the system locking might be temporarily off after storing
+ // check whether the system file locking should be taken again
+ GetLockingStream_Impl();
+ }
+
+ bool bResult = pImpl->m_bLocked;
+
+ if ( !bResult )
+ {
+ // no read-write access is necessary on loading if the document is explicitly opened as copy
+ const SfxBoolItem* pTemplateItem = GetItemSet().GetItem(SID_TEMPLATE, false);
+ bResult = ( bLoading && pTemplateItem && pTemplateItem->GetValue() );
+ }
+
+ if ( !bResult && !IsReadOnly() )
+ {
+ bool bContentReadonly = false;
+ if ( bLoading && GetURLObject().GetProtocol() == INetProtocol::File )
+ {
+ // let the original document be opened to check the possibility to open it for editing
+ // and to let the writable stream stay open to hold the lock on the document
+ GetLockingStream_Impl();
+ }
+
+ // "IsReadOnly" property does not allow to detect whether the file is readonly always
+ // so we try always to open the file for editing
+ // the file is readonly only in case the read-write stream can not be opened
+ if ( bLoading && !pImpl->m_xLockingStream.is() )
+ {
+ try
+ {
+ // MediaDescriptor does this check also, the duplication should be avoided in future
+ Reference< css::ucb::XCommandEnvironment > xDummyEnv;
+ ::ucbhelper::Content aContent( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext() );
+ aContent.getPropertyValue("IsReadOnly") >>= bContentReadonly;
+ }
+ catch( const uno::Exception& ) {}
+ }
+
+ // do further checks only if the file not readonly in fs
+ if ( !bContentReadonly )
+ {
+ // the special file locking should be used only for suitable URLs
+ if ( isSuitableProtocolForLocking( pImpl->m_aLogicName ) )
+ {
+
+ // in case of storing the document should request the output before locking
+ if ( bLoading )
+ {
+ // let the stream be opened to check the system file locking
+ GetMedium_Impl();
+ if (GetErrorIgnoreWarning() != ERRCODE_NONE) {
+ return eResult;
+ }
+ }
+
+ ShowLockResult bUIStatus = ShowLockResult::NoLock;
+
+ // check whether system file locking has been used, the default value is false
+ bool bUseSystemLock = comphelper::isFileUrl( pImpl->m_aLogicName ) && IsSystemFileLockingUsed();
+
+ // TODO/LATER: This implementation does not allow to detect the system lock on saving here, actually this is no big problem
+ // if system lock is used the writeable stream should be available
+ bool bHandleSysLocked = ( bLoading && bUseSystemLock && !pImpl->xStream.is() && !pImpl->m_pOutStream );
+
+ // The file is attempted to get locked for the duration of lockfile creation on save
+ std::unique_ptr<osl::File> pFileLock;
+ if (!bLoading && bUseSystemLock && pImpl->pTempFile)
+ {
+ INetURLObject aDest(GetURLObject());
+ OUString aDestURL(aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+
+ if (comphelper::isFileUrl(aDestURL) || !aDest.removeSegment())
+ {
+ pFileLock = std::make_unique<osl::File>(aDestURL);
+ auto rc = pFileLock->open(osl_File_OpenFlag_Write);
+ if (rc == osl::FileBase::E_ACCES)
+ bHandleSysLocked = true;
+ }
+ }
+
+ do
+ {
+ try
+ {
+ ::svt::DocumentLockFile aLockFile( pImpl->m_aLogicName );
+
+ std::unique_ptr<svt::MSODocumentLockFile> pMSOLockFile;
+ const SvtFilterOptions& rOpt = SvtFilterOptions::Get();
+ if (rOpt.IsMSOLockFileCreationIsEnabled() && svt::MSODocumentLockFile::IsMSOSupportedFileFormat(pImpl->m_aLogicName))
+ {
+ pMSOLockFile.reset(new svt::MSODocumentLockFile(pImpl->m_aLogicName));
+ pImpl->m_bMSOLockFileCreated = true;
+ }
+
+ bool bIoErr = false;
+
+ if (!bHandleSysLocked)
+ {
+ try
+ {
+ bResult = aLockFile.CreateOwnLockFile();
+ if(pMSOLockFile)
+ bResult &= pMSOLockFile->CreateOwnLockFile();
+ }
+ catch (const uno::Exception&)
+ {
+ if (tools::IsMappedWebDAVPath(GetURLObject().GetMainURL(
+ INetURLObject::DecodeMechanism::NONE)))
+ {
+ // This is a path that redirects to a WebDAV resource;
+ // so failure creating lockfile is not an error here.
+ bResult = true;
+ }
+ else if (bLoading && !bNoUI)
+ {
+ bIoErr = true;
+ ShowLockFileProblemDialog(MessageDlg::LockFileIgnore);
+ bResult = true; // always delete the defect lock-file
+ }
+ }
+
+ // in case OOo locking is turned off the lock file is still written if possible
+ // but it is ignored while deciding whether the document should be opened for editing or not
+ if (!bResult && !IsOOoLockFileUsed() && !bIoErr)
+ {
+ bResult = true;
+ // take the ownership over the lock file
+ aLockFile.OverwriteOwnLockFile();
+
+ if(pMSOLockFile)
+ pMSOLockFile->OverwriteOwnLockFile();
+ }
+ }
+
+ if ( !bResult )
+ {
+ LockFileEntry aData;
+ try
+ {
+ aData = aLockFile.GetLockData();
+ }
+ catch (const io::WrongFormatException&)
+ {
+ // we get empty or corrupt data
+ // info to the user
+ if (!bIoErr && bLoading && !bNoUI )
+ bResult = ShowLockFileProblemDialog(MessageDlg::LockFileCorrupt);
+
+ // not show the Lock Document Dialog
+ bIoErr = true;
+ }
+ catch( const uno::Exception& )
+ {
+ // show the Lock Document Dialog, when locked from other app
+ bIoErr = !bHandleSysLocked;
+ }
+
+ bool bOwnLock = false;
+
+ if (!bHandleSysLocked)
+ {
+ LockFileEntry aOwnData = svt::LockFileCommon::GenerateOwnEntry();
+ bOwnLock = aOwnData[LockFileComponent::SYSUSERNAME] == aData[LockFileComponent::SYSUSERNAME];
+
+ if (bOwnLock
+ && aOwnData[LockFileComponent::LOCALHOST] == aData[LockFileComponent::LOCALHOST]
+ && aOwnData[LockFileComponent::USERURL] == aData[LockFileComponent::USERURL])
+ {
+ // this is own lock from the same installation, it could remain because of crash
+ bResult = true;
+ }
+ }
+
+ if ( !bResult && !bIoErr)
+ {
+ if (!bNoUI)
+ bUIStatus = ShowLockedDocumentDialog(
+ aData, bLoading, bOwnLock, bHandleSysLocked);
+ else if (bLoading && bTryIgnoreLockFile && !bHandleSysLocked)
+ bUIStatus = ShowLockResult::Succeeded;
+
+ if ( bUIStatus == ShowLockResult::Succeeded )
+ {
+ // take the ownership over the lock file
+ bResult = aLockFile.OverwriteOwnLockFile();
+
+ if(pMSOLockFile)
+ pMSOLockFile->OverwriteOwnLockFile();
+ }
+ else if (bLoading && !bHandleSysLocked)
+ eResult = LockFileResult::FailedLockFile;
+
+ if (!bResult && pLockData)
+ {
+ std::copy(aData.begin(), aData.end(), pLockData->begin());
+ }
+ }
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ }
+ } while( !bResult && bUIStatus == ShowLockResult::Try );
+
+ pImpl->m_bLocked = bResult;
+ }
+ else
+ {
+ // this is no file URL, check whether the file is readonly
+ bResult = !bContentReadonly;
+ }
+ }
+ else // read-only
+ {
+ AddToCheckEditableWorkerList();
+ }
+ }
+
+ if ( !bResult && GetErrorIgnoreWarning() == ERRCODE_NONE )
+ {
+ // the error should be set in case it is storing process
+ // or the document has been opened for editing explicitly
+ const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
+
+ if ( !bLoading || (pReadOnlyItem && !pReadOnlyItem->GetValue()) )
+ SetError(ERRCODE_IO_ACCESSDENIED);
+ else
+ GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
+ }
+
+ // when the file is locked, get the current file date
+ if ( bResult && DocNeedsFileDateCheck() )
+ GetInitFileDate( true );
+
+ if ( bResult )
+ eResult = LockFileResult::Succeeded;
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "Locking exception: high probability, that the content has not been created" );
+ }
+
+ return eResult;
+#endif
+}
+
+// this either returns non-null or throws exception
+uno::Reference<embed::XStorage>
+SfxMedium::TryEncryptedInnerPackage(uno::Reference<embed::XStorage> const xStorage)
+{
+ uno::Reference<embed::XStorage> xRet;
+ if (xStorage->hasByName("encrypted-package"))
+ {
+ uno::Reference<io::XStream> const
+ xDecryptedInnerPackage = xStorage->openStreamElement(
+ "encrypted-package",
+ embed::ElementModes::READ | embed::ElementModes::NOCREATE);
+ // either this throws due to wrong password or IO error, or returns stream
+ assert(xDecryptedInnerPackage.is());
+ // need a seekable stream => copy
+ Reference<uno::XComponentContext> const xContext(::comphelper::getProcessComponentContext());
+ uno::Reference<io::XStream> const xDecryptedInnerPackageStream(
+ xContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.comp.MemoryStream", xContext),
+ UNO_QUERY_THROW);
+ comphelper::OStorageHelper::CopyInputToOutput(xDecryptedInnerPackage->getInputStream(), xDecryptedInnerPackageStream->getOutputStream());
+ xDecryptedInnerPackageStream->getOutputStream()->closeOutput();
+#if 0
+ // debug: dump to temp file
+ uno::Reference<io::XTempFile> const xTempFile(io::TempFile::create(xContext), uno::UNO_SET_THROW);
+ xTempFile->setRemoveFile(false);
+ comphelper::OStorageHelper::CopyInputToOutput(xDecryptedInnerPackageStream->getInputStream(), xTempFile->getOutputStream());
+ xTempFile->getOutputStream()->closeOutput();
+ SAL_DE BUG("AAA tempfile " << xTempFile->getResourceName());
+ uno::Reference<io::XSeekable>(xDecryptedInnerPackageStream, uno::UNO_QUERY_THROW)->seek(0);
+#endif
+ // create inner storage; opening the stream should have already verified
+ // the password so any failure here is probably due to a bug
+ xRet = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
+ PACKAGE_STORAGE_FORMAT_STRING, xDecryptedInnerPackageStream,
+ embed::ElementModes::READWRITE, xContext, false);
+ assert(xRet.is());
+ // consistency check: outer and inner package must have same mimetype
+ OUString const outerMediaType(uno::Reference<beans::XPropertySet>(pImpl->xStorage,
+ uno::UNO_QUERY_THROW)->getPropertyValue("MediaType").get<OUString>());
+ OUString const innerMediaType(uno::Reference<beans::XPropertySet>(xRet,
+ uno::UNO_QUERY_THROW)->getPropertyValue("MediaType").get<OUString>());
+ if (outerMediaType.isEmpty() || outerMediaType != innerMediaType)
+ {
+ throw io::WrongFormatException("MediaType inconsistent in encrypted ODF package");
+ }
+ // success:
+ pImpl->m_bODFWholesomeEncryption = true;
+ pImpl->m_xODFDecryptedInnerPackageStream = xDecryptedInnerPackageStream;
+ pImpl->m_xODFEncryptedOuterStorage = xStorage;
+ pImpl->xStorage = xRet;
+ }
+ return xRet;
+}
+
+uno::Reference < embed::XStorage > SfxMedium::GetStorage( bool bCreateTempFile )
+{
+ if ( pImpl->xStorage.is() || pImpl->m_bTriedStorage )
+ return pImpl->xStorage;
+
+ uno::Sequence< uno::Any > aArgs( 2 );
+ auto pArgs = aArgs.getArray();
+
+ // the medium should be retrieved before temporary file creation
+ // to let the MediaDescriptor be filled with the streams
+ GetMedium_Impl();
+
+ if ( bCreateTempFile )
+ CreateTempFile( false );
+
+ GetMedium_Impl();
+
+ if ( GetErrorIgnoreWarning() )
+ return pImpl->xStorage;
+
+ const SfxBoolItem* pRepairItem = GetItemSet().GetItem(SID_REPAIRPACKAGE, false);
+ if ( pRepairItem && pRepairItem->GetValue() )
+ {
+ // the storage should be created for repairing mode
+ CreateTempFile( false );
+ GetMedium_Impl();
+
+ Reference< css::ucb::XProgressHandler > xProgressHandler;
+ Reference< css::task::XStatusIndicator > xStatusIndicator;
+
+ const SfxUnoAnyItem* pxProgressItem = GetItemSet().GetItem(SID_PROGRESS_STATUSBAR_CONTROL, false);
+ if( pxProgressItem && ( pxProgressItem->GetValue() >>= xStatusIndicator ) )
+ xProgressHandler.set( new utl::ProgressHandlerWrap( xStatusIndicator ) );
+
+ uno::Sequence< beans::PropertyValue > aAddProps{
+ comphelper::makePropertyValue("RepairPackage", true),
+ comphelper::makePropertyValue("StatusIndicator", xProgressHandler)
+ };
+
+ // the first arguments will be filled later
+ aArgs.realloc( 3 );
+ pArgs = aArgs.getArray();
+ pArgs[2] <<= aAddProps;
+ }
+
+ if ( pImpl->xStream.is() )
+ {
+ // since the storage is based on temporary stream we open it always read-write
+ pArgs[0] <<= pImpl->xStream;
+ pArgs[1] <<= embed::ElementModes::READWRITE;
+ pImpl->bStorageBasedOnInStream = true;
+ if (pImpl->m_bDisableFileSync)
+ {
+ // Forward NoFileSync to the storage factory.
+ aArgs.realloc(3); // ??? this may re-write the data added above for pRepairItem
+ pArgs = aArgs.getArray();
+ uno::Sequence<beans::PropertyValue> aProperties(
+ comphelper::InitPropertySequence({ { "NoFileSync", uno::Any(true) } }));
+ pArgs[2] <<= aProperties;
+ }
+ }
+ else if ( pImpl->xInputStream.is() )
+ {
+ // since the storage is based on temporary stream we open it always read-write
+ pArgs[0] <<= pImpl->xInputStream;
+ pArgs[1] <<= embed::ElementModes::READ;
+ pImpl->bStorageBasedOnInStream = true;
+ }
+ else
+ {
+ CloseStreams_Impl();
+ pArgs[0] <<= pImpl->m_aName;
+ pArgs[1] <<= embed::ElementModes::READ;
+ pImpl->bStorageBasedOnInStream = false;
+ }
+
+ try
+ {
+ pImpl->xStorage.set( ::comphelper::OStorageHelper::GetStorageFactory()->createInstanceWithArguments( aArgs ),
+ uno::UNO_QUERY );
+ }
+ catch( const uno::Exception& )
+ {
+ // impossibility to create the storage is no error
+ }
+
+ pImpl->nLastStorageError = GetErrorIgnoreWarning();
+ if( pImpl->nLastStorageError != ERRCODE_NONE )
+ {
+ pImpl->xStorage = nullptr;
+ if ( pImpl->m_pInStream )
+ pImpl->m_pInStream->Seek(0);
+ return uno::Reference< embed::XStorage >();
+ }
+
+ pImpl->m_bTriedStorage = true;
+
+ if (pImpl->xStorage.is())
+ {
+ pImpl->m_bODFWholesomeEncryption = false;
+ if (SetEncryptionDataToStorage_Impl())
+ {
+ try
+ {
+ TryEncryptedInnerPackage(pImpl->xStorage);
+ }
+ catch (Exception const&)
+ {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "exception from TryEncryptedInnerPackage: ");
+ SetError(ERRCODE_IO_GENERAL);
+ }
+ }
+ }
+
+ if (GetErrorCode()) // decryption failed?
+ {
+ pImpl->xStorage.clear();
+ }
+
+ // TODO/LATER: Get versionlist on demand
+ if ( pImpl->xStorage.is() )
+ {
+ GetVersionList();
+ }
+
+ const SfxInt16Item* pVersion = SfxItemSet::GetItem<SfxInt16Item>(pImpl->m_pSet.get(), SID_VERSION, false);
+
+ bool bResetStorage = false;
+ if ( pVersion && pVersion->GetValue() )
+ {
+ // Read all available versions
+ if ( pImpl->aVersions.hasElements() )
+ {
+ // Search for the version fits the comment
+ // The versions are numbered starting with 1, versions with
+ // negative versions numbers are counted backwards from the
+ // current version
+ short nVersion = pVersion->GetValue();
+ if ( nVersion<0 )
+ nVersion = static_cast<short>(pImpl->aVersions.getLength()) + nVersion;
+ else // nVersion > 0; pVersion->GetValue() != 0 was the condition to this block
+ nVersion--;
+
+ const util::RevisionTag& rTag = pImpl->aVersions[nVersion];
+ {
+ // Open SubStorage for all versions
+ uno::Reference < embed::XStorage > xSub = pImpl->xStorage->openStorageElement( "Versions",
+ embed::ElementModes::READ );
+
+ DBG_ASSERT( xSub.is(), "Version list, but no Versions!" );
+
+ // There the version is stored as packed Stream
+ uno::Reference < io::XStream > xStr = xSub->openStreamElement( rTag.Identifier, embed::ElementModes::READ );
+ std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream( xStr ));
+ if ( pStream && pStream->GetError() == ERRCODE_NONE )
+ {
+ // Unpack Stream in TempDir
+ const OUString aTmpName = ::utl::CreateTempURL();
+ SvFileStream aTmpStream( aTmpName, SFX_STREAM_READWRITE );
+
+ pStream->ReadStream( aTmpStream );
+ pStream.reset();
+ aTmpStream.Close();
+
+ // Open data as Storage
+ pImpl->m_nStorOpenMode = SFX_STREAM_READONLY;
+ pImpl->xStorage = comphelper::OStorageHelper::GetStorageFromURL( aTmpName, embed::ElementModes::READ );
+ pImpl->bStorageBasedOnInStream = false;
+ OUString aTemp;
+ osl::FileBase::getSystemPathFromFileURL( aTmpName, aTemp );
+ SetPhysicalName_Impl( aTemp );
+
+ pImpl->bIsTemp = true;
+ GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
+ // TODO/MBA
+ pImpl->aVersions.realloc(0);
+ }
+ else
+ bResetStorage = true;
+ }
+ }
+ else
+ bResetStorage = true;
+ }
+
+ if ( bResetStorage )
+ {
+ pImpl->xStorage.clear();
+ pImpl->m_xODFDecryptedInnerPackageStream.clear();
+ pImpl->m_xODFEncryptedOuterStorage.clear();
+ if ( pImpl->m_pInStream )
+ pImpl->m_pInStream->Seek( 0 );
+ }
+
+ pImpl->bIsStorage = pImpl->xStorage.is();
+ return pImpl->xStorage;
+}
+
+uno::Reference<embed::XStorage> SfxMedium::GetScriptingStorageToSign_Impl()
+{
+ // this was set when it was initially loaded
+ if (pImpl->m_bODFWholesomeEncryption)
+ {
+ // (partial) scripting signature can only be in inner storage!
+ // Note: a "PackageFormat" storage like pImpl->xStorage doesn't work
+ // (even if it's not encrypted) because it hides the "META-INF" dir.
+ // This "ZipFormat" storage is used only read-only; a writable one is
+ // created manually in SignContents_Impl().
+ if (!pImpl->m_xODFDecryptedInnerZipStorage.is())
+ {
+ GetStorage(false);
+ // don't care about xStorage here because Zip is readonly
+ SAL_WARN_IF(!pImpl->m_xODFDecryptedInnerPackageStream.is(), "sfx.doc", "no inner package stream?");
+ if (pImpl->m_xODFDecryptedInnerPackageStream.is())
+ {
+ pImpl->m_xODFDecryptedInnerZipStorage =
+ ::comphelper::OStorageHelper::GetStorageOfFormatFromInputStream(
+ ZIP_STORAGE_FORMAT_STRING,
+ pImpl->m_xODFDecryptedInnerPackageStream->getInputStream());
+ }
+ }
+ return pImpl->m_xODFDecryptedInnerZipStorage;
+ }
+ else
+ {
+ return GetZipStorageToSign_Impl(true);
+ }
+}
+
+// note: currently nobody who calls this with "false" writes into an ODF
+// storage that is returned here, that is only for OOXML
+uno::Reference< embed::XStorage > const & SfxMedium::GetZipStorageToSign_Impl( bool bReadOnly )
+{
+ if ( !GetErrorIgnoreWarning() && !pImpl->m_xZipStorage.is() )
+ {
+ GetMedium_Impl();
+
+ try
+ {
+ // we can not sign document if there is no stream
+ // should it be possible at all?
+ if ( !bReadOnly && pImpl->xStream.is() )
+ {
+ pImpl->m_xZipStorage = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream( ZIP_STORAGE_FORMAT_STRING, pImpl->xStream );
+ }
+ else if ( pImpl->xInputStream.is() )
+ {
+ pImpl->m_xZipStorage = ::comphelper::OStorageHelper::GetStorageOfFormatFromInputStream( ZIP_STORAGE_FORMAT_STRING, pImpl->xInputStream );
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ SAL_WARN( "sfx.doc", "No possibility to get readonly version of storage from medium!" );
+ }
+
+ if ( GetErrorIgnoreWarning() ) // do not remove warnings
+ ResetError();
+ }
+
+ return pImpl->m_xZipStorage;
+}
+
+
+void SfxMedium::CloseZipStorage_Impl()
+{
+ if ( pImpl->m_xZipStorage.is() )
+ {
+ try {
+ pImpl->m_xZipStorage->dispose();
+ } catch( const uno::Exception& )
+ {}
+
+ pImpl->m_xZipStorage.clear();
+ }
+ pImpl->m_xODFDecryptedInnerZipStorage.clear();
+}
+
+void SfxMedium::CloseStorage()
+{
+ if ( pImpl->xStorage.is() )
+ {
+ uno::Reference < lang::XComponent > xComp = pImpl->xStorage;
+ // in the salvage mode the medium does not own the storage
+ if ( pImpl->bDisposeStorage && !pImpl->m_bSalvageMode )
+ {
+ try {
+ xComp->dispose();
+ } catch( const uno::Exception& )
+ {
+ SAL_WARN( "sfx.doc", "Medium's storage is already disposed!" );
+ }
+ }
+
+ pImpl->xStorage.clear();
+ pImpl->m_xODFDecryptedInnerPackageStream.clear();
+// pImpl->m_xODFDecryptedInnerZipStorage.clear();
+ pImpl->m_xODFEncryptedOuterStorage.clear();
+ pImpl->bStorageBasedOnInStream = false;
+ }
+
+ pImpl->m_bTriedStorage = false;
+ pImpl->bIsStorage = false;
+}
+
+void SfxMedium::CanDisposeStorage_Impl( bool bDisposeStorage )
+{
+ pImpl->bDisposeStorage = bDisposeStorage;
+}
+
+bool SfxMedium::WillDisposeStorageOnClose_Impl()
+{
+ return pImpl->bDisposeStorage;
+}
+
+StreamMode SfxMedium::GetOpenMode() const
+{
+ return pImpl->m_nStorOpenMode;
+}
+
+void SfxMedium::SetOpenMode( StreamMode nStorOpen,
+ bool bDontClose )
+{
+ if ( pImpl->m_nStorOpenMode != nStorOpen )
+ {
+ pImpl->m_nStorOpenMode = nStorOpen;
+
+ if( !bDontClose )
+ {
+ if ( pImpl->xStorage.is() )
+ CloseStorage();
+
+ CloseStreams_Impl();
+ }
+ }
+}
+
+
+bool SfxMedium::UseBackupToRestore_Impl( ::ucbhelper::Content& aOriginalContent,
+ const Reference< css::ucb::XCommandEnvironment >& xComEnv )
+{
+ try
+ {
+ ::ucbhelper::Content aTransactCont( pImpl->m_aBackupURL, xComEnv, comphelper::getProcessComponentContext() );
+
+ Reference< XInputStream > aOrigInput = aTransactCont.openStream();
+ aOriginalContent.writeStream( aOrigInput, true );
+ return true;
+ }
+ catch( const Exception& )
+ {
+ // in case of failure here the backup file should not be removed
+ // TODO/LATER: a message should be used to let user know about the backup
+ pImpl->m_bRemoveBackup = false;
+ // TODO/LATER: needs a specific error code
+ pImpl->m_eError = ERRCODE_IO_GENERAL;
+ }
+
+ return false;
+}
+
+
+bool SfxMedium::StorageCommit_Impl()
+{
+ bool bResult = false;
+ Reference< css::ucb::XCommandEnvironment > xDummyEnv;
+ ::ucbhelper::Content aOriginalContent;
+
+ if ( pImpl->xStorage.is() )
+ {
+ if ( !GetErrorIgnoreWarning() )
+ {
+ uno::Reference < embed::XTransactedObject > xTrans( pImpl->xStorage, uno::UNO_QUERY );
+ if ( xTrans.is() )
+ {
+ try
+ {
+ xTrans->commit();
+ CloseZipStorage_Impl();
+ bResult = true;
+ }
+ catch ( const embed::UseBackupException& aBackupExc )
+ {
+ // since the temporary file is created always now, the scenario is close to be impossible
+ if ( !pImpl->pTempFile )
+ {
+ OSL_ENSURE( !pImpl->m_aBackupURL.isEmpty(), "No backup on storage commit!" );
+ if ( !pImpl->m_aBackupURL.isEmpty()
+ && ::ucbhelper::Content::create( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ),
+ xDummyEnv, comphelper::getProcessComponentContext(),
+ aOriginalContent ) )
+ {
+ // use backup to restore the file
+ // the storage has already disconnected from original location
+ CloseAndReleaseStreams_Impl();
+ if ( !UseBackupToRestore_Impl( aOriginalContent, xDummyEnv ) )
+ {
+ // connect the medium to the temporary file of the storage
+ pImpl->aContent = ::ucbhelper::Content();
+ pImpl->m_aName = aBackupExc.TemporaryFileURL;
+ OSL_ENSURE( !pImpl->m_aName.isEmpty(), "The exception _must_ contain the temporary URL!" );
+ }
+ }
+ }
+
+ if (!GetErrorIgnoreWarning())
+ SetError(ERRCODE_IO_GENERAL);
+ }
+ catch ( const uno::Exception& )
+ {
+ //TODO/LATER: improve error handling
+ SetError(ERRCODE_IO_GENERAL);
+ }
+ }
+ }
+ }
+
+ return bResult;
+}
+
+
+void SfxMedium::TransactedTransferForFS_Impl( const INetURLObject& aSource,
+ const INetURLObject& aDest,
+ const Reference< css::ucb::XCommandEnvironment >& xComEnv )
+{
+ Reference< css::ucb::XCommandEnvironment > xDummyEnv;
+ ::ucbhelper::Content aOriginalContent;
+
+ try
+ {
+ aOriginalContent = ::ucbhelper::Content( aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
+ }
+ catch ( const css::ucb::CommandAbortedException& )
+ {
+ pImpl->m_eError = ERRCODE_ABORT;
+ }
+ catch ( const css::ucb::CommandFailedException& )
+ {
+ pImpl->m_eError = ERRCODE_ABORT;
+ }
+ catch (const css::ucb::ContentCreationException& ex)
+ {
+ pImpl->m_eError = ERRCODE_IO_GENERAL;
+ if (
+ (ex.eError == css::ucb::ContentCreationError_NO_CONTENT_PROVIDER ) ||
+ (ex.eError == css::ucb::ContentCreationError_CONTENT_CREATION_FAILED)
+ )
+ {
+ pImpl->m_eError = ERRCODE_IO_NOTEXISTSPATH;
+ }
+ }
+ catch (const css::uno::Exception&)
+ {
+ pImpl->m_eError = ERRCODE_IO_GENERAL;
+ }
+
+ if( pImpl->m_eError && !pImpl->m_eError.IsWarning() )
+ return;
+
+ if ( pImpl->xStorage.is() )
+ CloseStorage();
+
+ CloseStreams_Impl();
+
+ ::ucbhelper::Content aTempCont;
+ if( ::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext(), aTempCont ) )
+ {
+ bool bTransactStarted = false;
+ const SfxBoolItem* pOverWrite = GetItemSet().GetItem<SfxBoolItem>(SID_OVERWRITE, false);
+ bool bOverWrite = !pOverWrite || pOverWrite->GetValue();
+ bool bResult = false;
+
+ try
+ {
+ // tdf#60237 - if the OverWrite property of the MediaDescriptor is set to false,
+ // try to write the file before trying to rename or copy it
+ if (!(bOverWrite
+ && ::utl::UCBContentHelper::IsDocument(
+ aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE))))
+ {
+ Reference< XInputStream > aTempInput = aTempCont.openStream();
+ aOriginalContent.writeStream( aTempInput, bOverWrite );
+ bResult = true;
+ } else {
+ OUString aSourceMainURL = aSource.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+ OUString aDestMainURL = aDest.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+
+ sal_uInt64 nAttributes = GetDefaultFileAttributes(aDestMainURL);
+ if (IsFileMovable(aDest)
+ && osl::File::replace(aSourceMainURL, aDestMainURL) == osl::FileBase::E_None)
+ {
+ if (nAttributes)
+ // Adjust attributes, source might be created with
+ // the osl_File_OpenFlag_Private flag.
+ osl::File::setAttributes(aDestMainURL, nAttributes);
+ bResult = true;
+ }
+ else
+ {
+ if( pImpl->m_aBackupURL.isEmpty() )
+ DoInternalBackup_Impl( aOriginalContent );
+
+ if( !pImpl->m_aBackupURL.isEmpty() )
+ {
+ Reference< XInputStream > aTempInput = aTempCont.openStream();
+ bTransactStarted = true;
+ aOriginalContent.setPropertyValue( "Size", uno::Any( sal_Int64(0) ) );
+ aOriginalContent.writeStream( aTempInput, bOverWrite );
+ bResult = true;
+ }
+ else
+ {
+ pImpl->m_eError = ERRCODE_SFX_CANTCREATEBACKUP;
+ }
+ }
+ }
+ }
+ catch ( const css::ucb::CommandAbortedException& )
+ {
+ pImpl->m_eError = ERRCODE_ABORT;
+ }
+ catch ( const css::ucb::CommandFailedException& )
+ {
+ pImpl->m_eError = ERRCODE_ABORT;
+ }
+ catch ( const css::ucb::InteractiveIOException& r )
+ {
+ if ( r.Code == IOErrorCode_ACCESS_DENIED )
+ pImpl->m_eError = ERRCODE_IO_ACCESSDENIED;
+ else if ( r.Code == IOErrorCode_NOT_EXISTING )
+ pImpl->m_eError = ERRCODE_IO_NOTEXISTS;
+ else if ( r.Code == IOErrorCode_CANT_READ )
+ pImpl->m_eError = ERRCODE_IO_CANTREAD;
+ else
+ pImpl->m_eError = ERRCODE_IO_GENERAL;
+ }
+ // tdf#60237 - if the file is already present, raise the appropriate error
+ catch (const css::ucb::NameClashException& )
+ {
+ pImpl->m_eError = ERRCODE_IO_ALREADYEXISTS;
+ }
+ catch ( const css::uno::Exception& )
+ {
+ pImpl->m_eError = ERRCODE_IO_GENERAL;
+ }
+
+ if ( bResult )
+ {
+ if ( pImpl->pTempFile )
+ {
+ pImpl->pTempFile->EnableKillingFile();
+ pImpl->pTempFile.reset();
+ }
+ }
+ else if ( bTransactStarted && pImpl->m_eError != ERRCODE_ABORT )
+ {
+ UseBackupToRestore_Impl( aOriginalContent, xDummyEnv );
+ }
+ }
+ else
+ pImpl->m_eError = ERRCODE_IO_CANTREAD;
+}
+
+
+bool SfxMedium::TryDirectTransfer( const OUString& aURL, SfxItemSet const & aTargetSet )
+{
+ if ( GetErrorIgnoreWarning() )
+ return false;
+
+ // if the document had no password it should be stored without password
+ // if the document had password it should be stored with the same password
+ // otherwise the stream copying can not be done
+ const SfxStringItem* pNewPassItem = aTargetSet.GetItem(SID_PASSWORD, false);
+ const SfxStringItem* pOldPassItem = GetItemSet().GetItem(SID_PASSWORD, false);
+ if ( ( !pNewPassItem && !pOldPassItem )
+ || ( pNewPassItem && pOldPassItem && pNewPassItem->GetValue() == pOldPassItem->GetValue() ) )
+ {
+ // the filter must be the same
+ const SfxStringItem* pNewFilterItem = aTargetSet.GetItem(SID_FILTER_NAME, false);
+ const SfxStringItem* pOldFilterItem = GetItemSet().GetItem(SID_FILTER_NAME, false);
+ if ( pNewFilterItem && pOldFilterItem && pNewFilterItem->GetValue() == pOldFilterItem->GetValue() )
+ {
+ // get the input stream and copy it
+ // in case of success return true
+ uno::Reference< io::XInputStream > xInStream = GetInputStream();
+
+ ResetError();
+ if ( xInStream.is() )
+ {
+ try
+ {
+ uno::Reference< io::XSeekable > xSeek( xInStream, uno::UNO_QUERY );
+ sal_Int64 nPos = 0;
+ if ( xSeek.is() )
+ {
+ nPos = xSeek->getPosition();
+ xSeek->seek( 0 );
+ }
+
+ uno::Reference < css::ucb::XCommandEnvironment > xEnv;
+ ::ucbhelper::Content aTargetContent( aURL, xEnv, comphelper::getProcessComponentContext() );
+
+ InsertCommandArgument aInsertArg;
+ aInsertArg.Data = xInStream;
+ const SfxBoolItem* pOverWrite = aTargetSet.GetItem<SfxBoolItem>(SID_OVERWRITE, false);
+ if ( pOverWrite && !pOverWrite->GetValue() ) // argument says: never overwrite
+ aInsertArg.ReplaceExisting = false;
+ else
+ aInsertArg.ReplaceExisting = true; // default is overwrite existing files
+
+ Any aCmdArg;
+ aCmdArg <<= aInsertArg;
+ aTargetContent.executeCommand( "insert",
+ aCmdArg );
+
+ if ( xSeek.is() )
+ xSeek->seek( nPos );
+
+ return true;
+ }
+ catch( const uno::Exception& )
+ {}
+ }
+ }
+ }
+
+ return false;
+}
+
+
+void SfxMedium::Transfer_Impl()
+{
+ // The transfer is required only in two cases: either if there is a temporary file or if there is a salvage item
+ OUString aNameURL;
+ if ( pImpl->pTempFile )
+ aNameURL = pImpl->pTempFile->GetURL();
+ else if ( !pImpl->m_aLogicName.isEmpty() && pImpl->m_bSalvageMode )
+ {
+ // makes sense only in case logic name is set
+ if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aNameURL )
+ != osl::FileBase::E_None )
+ SAL_WARN( "sfx.doc", "The medium name is not convertible!" );
+ }
+
+ if ( aNameURL.isEmpty() || ( pImpl->m_eError && !pImpl->m_eError.IsWarning() ) )
+ return;
+
+ SAL_INFO( "sfx.doc", "SfxMedium::Transfer_Impl, copying to target" );
+
+ Reference < css::ucb::XCommandEnvironment > xEnv;
+ Reference< XOutputStream > rOutStream;
+
+ // in case an output stream is provided from outside and the URL is correct
+ // commit to the stream
+ if (pImpl->m_aLogicName.startsWith("private:stream"))
+ {
+ // TODO/LATER: support storing to SID_STREAM
+ const SfxUnoAnyItem* pOutStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_OUTPUTSTREAM, false);
+ if( pOutStreamItem && ( pOutStreamItem->GetValue() >>= rOutStream ) )
+ {
+ if ( pImpl->xStorage.is() )
+ CloseStorage();
+
+ CloseStreams_Impl();
+
+ INetURLObject aSource( aNameURL );
+ ::ucbhelper::Content aTempCont;
+ if( ::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aTempCont ) )
+ {
+ try
+ {
+ sal_Int32 nRead;
+ sal_Int32 nBufferSize = 32767;
+ Sequence < sal_Int8 > aSequence ( nBufferSize );
+ Reference< XInputStream > aTempInput = aTempCont.openStream();
+
+ do
+ {
+ nRead = aTempInput->readBytes ( aSequence, nBufferSize );
+ if ( nRead < nBufferSize )
+ {
+ Sequence < sal_Int8 > aTempBuf ( aSequence.getConstArray(), nRead );
+ rOutStream->writeBytes ( aTempBuf );
+ }
+ else
+ rOutStream->writeBytes ( aSequence );
+ }
+ while ( nRead == nBufferSize );
+
+ // remove temporary file
+ if ( pImpl->pTempFile )
+ {
+ pImpl->pTempFile->EnableKillingFile();
+ pImpl->pTempFile.reset();
+ }
+ }
+ catch( const Exception& )
+ {}
+ }
+ }
+ else
+ {
+ SAL_WARN( "sfx.doc", "Illegal Output stream parameter!" );
+ SetError(ERRCODE_IO_GENERAL);
+ }
+
+ // free the reference
+ if ( pImpl->m_pSet )
+ pImpl->m_pSet->ClearItem( SID_OUTPUTSTREAM );
+
+ return;
+ }
+
+ GetContent();
+ if ( !pImpl->aContent.get().is() )
+ {
+ pImpl->m_eError = ERRCODE_IO_NOTEXISTS;
+ return;
+ }
+
+ INetURLObject aDest( GetURLObject() );
+
+ // source is the temp file written so far
+ INetURLObject aSource( aNameURL );
+
+ // a special case, an interaction handler should be used for
+ // authentication in case it is available
+ Reference< css::ucb::XCommandEnvironment > xComEnv;
+ bool bForceInteractionHandler = GetURLObject().isAnyKnownWebDAVScheme();
+ Reference< css::task::XInteractionHandler > xInteractionHandler = GetInteractionHandler(bForceInteractionHandler);
+ if (xInteractionHandler.is())
+ xComEnv = new ::ucbhelper::CommandEnvironment( xInteractionHandler,
+ Reference< css::ucb::XProgressHandler >() );
+
+ OUString aDestURL( aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+
+ if ( comphelper::isFileUrl( aDestURL ) || !aDest.removeSegment() )
+ {
+ TransactedTransferForFS_Impl( aSource, aDest, xComEnv );
+
+ if (!pImpl->m_bDisableFileSync)
+ {
+ // Hideous - no clean way to do this, so we re-open the file just to fsync it
+ osl::File aFile( aDestURL );
+ if ( aFile.open( osl_File_OpenFlag_Write ) == osl::FileBase::E_None )
+ {
+ aFile.sync();
+ SAL_INFO( "sfx.doc", "fsync'd saved file '" << aDestURL << "'" );
+ aFile.close();
+ }
+ }
+ }
+ else
+ {
+ // create content for the parent folder and call transfer on that content with the source content
+ // and the destination file name as parameters
+ ::ucbhelper::Content aSourceContent;
+ ::ucbhelper::Content aTransferContent;
+
+ ::ucbhelper::Content aDestContent;
+ (void)::ucbhelper::Content::create( aDestURL, xComEnv, comphelper::getProcessComponentContext(), aDestContent );
+ // For checkin, we need the object URL, not the parent folder:
+ if ( !IsInCheckIn( ) )
+ {
+ // Get the parent URL from the XChild if possible: why would the URL necessarily have
+ // a hierarchical path? It's not always the case for CMIS.
+ Reference< css::container::XChild> xChild( aDestContent.get(), uno::UNO_QUERY );
+ OUString sParentUrl;
+ if ( xChild.is( ) )
+ {
+ Reference< css::ucb::XContent > xParent( xChild->getParent( ), uno::UNO_QUERY );
+ if ( xParent.is( ) )
+ {
+ sParentUrl = xParent->getIdentifier( )->getContentIdentifier();
+ }
+ }
+
+ if ( sParentUrl.isEmpty() )
+ aDestURL = aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ // adjust to above aDest.removeSegment()
+ else
+ aDestURL = sParentUrl;
+ }
+
+ // LongName wasn't defined anywhere, only used here... get the Title instead
+ // as it's less probably empty
+ OUString aFileName;
+ OUString sObjectId;
+ try
+ {
+ Any aAny = aDestContent.getPropertyValue("Title");
+ aAny >>= aFileName;
+ aAny = aDestContent.getPropertyValue("ObjectId");
+ aAny >>= sObjectId;
+ }
+ catch (uno::Exception const&)
+ {
+ SAL_INFO("sfx.doc", "exception while getting Title or ObjectId");
+ }
+ if ( aFileName.isEmpty() )
+ aFileName = GetURLObject().getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
+
+ try
+ {
+ aTransferContent = ::ucbhelper::Content( aDestURL, xComEnv, comphelper::getProcessComponentContext() );
+ }
+ catch (const css::ucb::ContentCreationException& ex)
+ {
+ pImpl->m_eError = ERRCODE_IO_GENERAL;
+ if (
+ (ex.eError == css::ucb::ContentCreationError_NO_CONTENT_PROVIDER ) ||
+ (ex.eError == css::ucb::ContentCreationError_CONTENT_CREATION_FAILED)
+ )
+ {
+ pImpl->m_eError = ERRCODE_IO_NOTEXISTSPATH;
+ }
+ }
+ catch (const css::uno::Exception&)
+ {
+ pImpl->m_eError = ERRCODE_IO_GENERAL;
+ }
+
+ if ( !pImpl->m_eError || pImpl->m_eError.IsWarning() )
+ {
+ // free resources, otherwise the transfer may fail
+ if ( pImpl->xStorage.is() )
+ CloseStorage();
+
+ CloseStreams_Impl();
+
+ (void)::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aSourceContent );
+
+ // check for external parameters that may customize the handling of NameClash situations
+ const SfxBoolItem* pOverWrite = GetItemSet().GetItem<SfxBoolItem>(SID_OVERWRITE, false);
+ sal_Int32 nNameClash;
+ if ( pOverWrite && !pOverWrite->GetValue() )
+ // argument says: never overwrite
+ nNameClash = NameClash::ERROR;
+ else
+ // default is overwrite existing files
+ nNameClash = NameClash::OVERWRITE;
+
+ try
+ {
+ OUString aMimeType = pImpl->getFilterMimeType();
+ ::ucbhelper::InsertOperation eOperation = ::ucbhelper::InsertOperation::Copy;
+ bool bMajor = false;
+ OUString sComment;
+ if ( IsInCheckIn( ) )
+ {
+ eOperation = ::ucbhelper::InsertOperation::Checkin;
+ const SfxBoolItem* pMajor = GetItemSet().GetItem<SfxBoolItem>(SID_DOCINFO_MAJOR, false);
+ bMajor = pMajor && pMajor->GetValue( );
+ const SfxStringItem* pComments = GetItemSet().GetItem(SID_DOCINFO_COMMENTS, false);
+ if ( pComments )
+ sComment = pComments->GetValue( );
+ }
+ OUString sResultURL;
+ aTransferContent.transferContent(
+ aSourceContent, eOperation,
+ aFileName, nNameClash, aMimeType, bMajor, sComment,
+ &sResultURL, sObjectId );
+
+ if ( !sResultURL.isEmpty( ) ) // Likely to happen only for checkin
+ SwitchDocumentToFile( sResultURL );
+ try
+ {
+ if ( GetURLObject().isAnyKnownWebDAVScheme() &&
+ eOperation == ::ucbhelper::InsertOperation::Copy )
+ {
+ // tdf#95272 try to re-issue a lock command when a new file is created.
+ // This may be needed because some WebDAV servers fail to implement the
+ // 'LOCK on unallocated reference', see issue comment:
+ // <https://bugs.documentfoundation.org/show_bug.cgi?id=95792#c8>
+ // and specification at:
+ // <http://tools.ietf.org/html/rfc4918#section-7.3>
+ // If the WebDAV resource is already locked by this LO instance, nothing will
+ // happen, e.g. the LOCK method will not be sent to the server.
+ ::ucbhelper::Content aLockContent( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
+ aLockContent.lock();
+ }
+ }
+ catch ( css::uno::Exception & )
+ {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "LOCK not working while re-issuing it" );
+ }
+ }
+ catch ( const css::ucb::CommandAbortedException& )
+ {
+ pImpl->m_eError = ERRCODE_ABORT;
+ }
+ catch ( const css::ucb::CommandFailedException& )
+ {
+ pImpl->m_eError = ERRCODE_ABORT;
+ }
+ catch ( const css::ucb::InteractiveIOException& r )
+ {
+ if ( r.Code == IOErrorCode_ACCESS_DENIED )
+ pImpl->m_eError = ERRCODE_IO_ACCESSDENIED;
+ else if ( r.Code == IOErrorCode_NOT_EXISTING )
+ pImpl->m_eError = ERRCODE_IO_NOTEXISTS;
+ else if ( r.Code == IOErrorCode_CANT_READ )
+ pImpl->m_eError = ERRCODE_IO_CANTREAD;
+ else
+ pImpl->m_eError = ERRCODE_IO_GENERAL;
+ }
+ catch ( const css::uno::Exception& )
+ {
+ pImpl->m_eError = ERRCODE_IO_GENERAL;
+ }
+
+ // do not switch from temporary file in case of nonfile protocol
+ }
+ }
+
+ if ( ( !pImpl->m_eError || pImpl->m_eError.IsWarning() ) && !pImpl->pTempFile )
+ {
+ // without a TempFile the physical and logical name should be the same after successful transfer
+ if (osl::FileBase::getSystemPathFromFileURL(
+ GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), pImpl->m_aName )
+ != osl::FileBase::E_None)
+ {
+ pImpl->m_aName.clear();
+ }
+ pImpl->m_bSalvageMode = false;
+ }
+}
+
+
+void SfxMedium::DoInternalBackup_Impl( const ::ucbhelper::Content& aOriginalContent,
+ std::u16string_view aPrefix,
+ std::u16string_view aExtension,
+ const OUString& aDestDir )
+{
+ if ( !pImpl->m_aBackupURL.isEmpty() )
+ return; // the backup was done already
+
+ ::utl::TempFileNamed aTransactTemp( aPrefix, true, aExtension, &aDestDir );
+
+ INetURLObject aBackObj( aTransactTemp.GetURL() );
+ OUString aBackupName = aBackObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
+
+ Reference < css::ucb::XCommandEnvironment > xDummyEnv;
+ ::ucbhelper::Content aBackupCont;
+ if( ::ucbhelper::Content::create( aDestDir, xDummyEnv, comphelper::getProcessComponentContext(), aBackupCont ) )
+ {
+ try
+ {
+ OUString sMimeType = pImpl->getFilterMimeType();
+ aBackupCont.transferContent( aOriginalContent,
+ ::ucbhelper::InsertOperation::Copy,
+ aBackupName,
+ NameClash::OVERWRITE,
+ sMimeType );
+ pImpl->m_aBackupURL = aBackObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ pImpl->m_bRemoveBackup = true;
+ }
+ catch( const Exception& )
+ {}
+ }
+
+ if ( pImpl->m_aBackupURL.isEmpty() )
+ aTransactTemp.EnableKillingFile();
+}
+
+
+void SfxMedium::DoInternalBackup_Impl( const ::ucbhelper::Content& aOriginalContent )
+{
+ if ( !pImpl->m_aBackupURL.isEmpty() )
+ return; // the backup was done already
+
+ OUString aFileName = GetURLObject().getName( INetURLObject::LAST_SEGMENT,
+ true,
+ INetURLObject::DecodeMechanism::NONE );
+
+ sal_Int32 nPrefixLen = aFileName.lastIndexOf( '.' );
+ OUString aPrefix = ( nPrefixLen == -1 ) ? aFileName : aFileName.copy( 0, nPrefixLen );
+ OUString aExtension = ( nPrefixLen == -1 ) ? OUString() : aFileName.copy( nPrefixLen );
+ OUString aBakDir = SvtPathOptions().GetBackupPath();
+
+ // create content for the parent folder ( = backup folder )
+ ::ucbhelper::Content aContent;
+ Reference < css::ucb::XCommandEnvironment > xEnv;
+ if( ::utl::UCBContentHelper::ensureFolder(comphelper::getProcessComponentContext(), xEnv, aBakDir, aContent) )
+ DoInternalBackup_Impl( aOriginalContent, aPrefix, aExtension, aBakDir );
+
+ if ( !pImpl->m_aBackupURL.isEmpty() )
+ return;
+
+ // the copying to the backup catalog failed ( for example because
+ // of using an encrypted partition as target catalog )
+ // since the user did not specify to make backup explicitly
+ // office should try to make backup in another place,
+ // target catalog does not look bad for this case ( and looks
+ // to be the only way for encrypted partitions )
+
+ INetURLObject aDest = GetURLObject();
+ if ( aDest.removeSegment() )
+ DoInternalBackup_Impl( aOriginalContent, aPrefix, aExtension, aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+}
+
+
+void SfxMedium::DoBackup_Impl(bool bForceUsingBackupPath)
+{
+ // source file name is the logical name of this medium
+ INetURLObject aSource( GetURLObject() );
+
+ // there is nothing to backup in case source file does not exist
+ if ( !::utl::UCBContentHelper::IsDocument( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
+ return;
+
+ bool bSuccess = false;
+ bool bOnErrorRetryUsingBackupPath = false;
+
+ // get path for backups
+ OUString aBakDir;
+ if (!bForceUsingBackupPath
+ && officecfg::Office::Common::Save::Document::BackupIntoDocumentFolder::get())
+ {
+ aBakDir = aSource.GetPartBeforeLastName();
+ bOnErrorRetryUsingBackupPath = true;
+ }
+ else
+ aBakDir = SvtPathOptions().GetBackupPath();
+ if( !aBakDir.isEmpty() )
+ {
+ // create content for the parent folder ( = backup folder )
+ ::ucbhelper::Content aContent;
+ Reference < css::ucb::XCommandEnvironment > xEnv;
+ if( ::utl::UCBContentHelper::ensureFolder(comphelper::getProcessComponentContext(), xEnv, aBakDir, aContent) )
+ {
+ // save as ".bak" file
+ INetURLObject aDest( aBakDir );
+ aDest.insertName( aSource.getName() );
+ const OUString sExt
+ = aSource.hasExtension() ? aSource.getExtension() + ".bak" : OUString("bak");
+ aDest.setExtension(sExt);
+ OUString aFileName = aDest.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
+
+ // create a content for the source file
+ ::ucbhelper::Content aSourceContent;
+ if ( ::ucbhelper::Content::create( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aSourceContent ) )
+ {
+ try
+ {
+ // do the transfer ( copy source file to backup dir )
+ OUString sMimeType = pImpl->getFilterMimeType();
+ aContent.transferContent( aSourceContent,
+ ::ucbhelper::InsertOperation::Copy,
+ aFileName,
+ NameClash::OVERWRITE,
+ sMimeType );
+ pImpl->m_aBackupURL = aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ pImpl->m_bRemoveBackup = false;
+ bSuccess = true;
+ }
+ catch ( const css::uno::Exception& )
+ {
+ }
+ }
+ }
+ }
+
+ if ( !bSuccess )
+ {
+ // in case a webdav server prevents file creation, or a partition is full, or whatever...
+ if (bOnErrorRetryUsingBackupPath)
+ return DoBackup_Impl(/*bForceUsingBackupPath=*/true);
+
+ pImpl->m_eError = ERRCODE_SFX_CANTCREATEBACKUP;
+ }
+}
+
+
+void SfxMedium::ClearBackup_Impl()
+{
+ if( pImpl->m_bRemoveBackup )
+ {
+ // currently a document is always stored in a new medium,
+ // thus if a backup can not be removed the backup URL should not be cleaned
+ if ( !pImpl->m_aBackupURL.isEmpty() )
+ {
+ if ( ::utl::UCBContentHelper::Kill( pImpl->m_aBackupURL ) )
+ {
+ pImpl->m_bRemoveBackup = false;
+ pImpl->m_aBackupURL.clear();
+ }
+ else
+ {
+
+ SAL_WARN( "sfx.doc", "Couldn't remove backup file!");
+ }
+ }
+ }
+ else
+ pImpl->m_aBackupURL.clear();
+}
+
+
+void SfxMedium::GetLockingStream_Impl()
+{
+ if ( GetURLObject().GetProtocol() != INetProtocol::File
+ || pImpl->m_xLockingStream.is() )
+ return;
+
+ const SfxUnoAnyItem* pWriteStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_STREAM, false);
+ if ( pWriteStreamItem )
+ pWriteStreamItem->GetValue() >>= pImpl->m_xLockingStream;
+
+ if ( pImpl->m_xLockingStream.is() )
+ return;
+
+ // open the original document
+ uno::Sequence< beans::PropertyValue > xProps;
+ TransformItems( SID_OPENDOC, GetItemSet(), xProps );
+ utl::MediaDescriptor aMedium( xProps );
+
+ aMedium.addInputStreamOwnLock();
+
+ uno::Reference< io::XInputStream > xInputStream;
+ aMedium[utl::MediaDescriptor::PROP_STREAM] >>= pImpl->m_xLockingStream;
+ aMedium[utl::MediaDescriptor::PROP_INPUTSTREAM] >>= xInputStream;
+
+ if ( !pImpl->pTempFile && pImpl->m_aName.isEmpty() )
+ {
+ // the medium is still based on the original file, it makes sense to initialize the streams
+ if ( pImpl->m_xLockingStream.is() )
+ pImpl->xStream = pImpl->m_xLockingStream;
+
+ if ( xInputStream.is() )
+ pImpl->xInputStream = xInputStream;
+
+ if ( !pImpl->xInputStream.is() && pImpl->xStream.is() )
+ pImpl->xInputStream = pImpl->xStream->getInputStream();
+ }
+}
+
+
+void SfxMedium::GetMedium_Impl()
+{
+ if ( pImpl->m_pInStream
+ && (!pImpl->bIsTemp || pImpl->xInputStream.is() || pImpl->m_xInputStreamToLoadFrom.is() || pImpl->xStream.is() || pImpl->m_xLockingStream.is() ) )
+ return;
+
+ pImpl->bDownloadDone = false;
+ Reference< css::task::XInteractionHandler > xInteractionHandler = GetInteractionHandler();
+
+ //TODO/MBA: need support for SID_STREAM
+ const SfxUnoAnyItem* pWriteStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_STREAM, false);
+ const SfxUnoAnyItem* pInStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_INPUTSTREAM, false);
+ if ( pWriteStreamItem )
+ {
+ pWriteStreamItem->GetValue() >>= pImpl->xStream;
+
+ if ( pInStreamItem )
+ pInStreamItem->GetValue() >>= pImpl->xInputStream;
+
+ if ( !pImpl->xInputStream.is() && pImpl->xStream.is() )
+ pImpl->xInputStream = pImpl->xStream->getInputStream();
+ }
+ else if ( pInStreamItem )
+ {
+ pInStreamItem->GetValue() >>= pImpl->xInputStream;
+ }
+ else
+ {
+ uno::Sequence < beans::PropertyValue > xProps;
+ OUString aFileName;
+ if (!pImpl->m_aName.isEmpty())
+ {
+ if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aFileName )
+ != osl::FileBase::E_None )
+ {
+ SAL_WARN( "sfx.doc", "Physical name not convertible!");
+ }
+ }
+ else
+ aFileName = GetName();
+
+ // in case the temporary file exists the streams should be initialized from it,
+ // but the original MediaDescriptor should not be changed
+ bool bFromTempFile = ( pImpl->pTempFile != nullptr );
+
+ if ( !bFromTempFile )
+ {
+ GetItemSet().Put( SfxStringItem( SID_FILE_NAME, aFileName ) );
+ if( !(pImpl->m_nStorOpenMode & StreamMode::WRITE) )
+ GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
+ if (xInteractionHandler.is())
+ GetItemSet().Put( SfxUnoAnyItem( SID_INTERACTIONHANDLER, Any(xInteractionHandler) ) );
+ }
+
+ if ( pImpl->m_xInputStreamToLoadFrom.is() )
+ {
+ pImpl->xInputStream = pImpl->m_xInputStreamToLoadFrom;
+ if (pImpl->m_bInputStreamIsReadOnly)
+ GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
+ }
+ else
+ {
+ TransformItems( SID_OPENDOC, GetItemSet(), xProps );
+ utl::MediaDescriptor aMedium( xProps );
+
+ if ( pImpl->m_xLockingStream.is() && !bFromTempFile )
+ {
+ // the medium is not based on the temporary file, so the original stream can be used
+ pImpl->xStream = pImpl->m_xLockingStream;
+ }
+ else
+ {
+ if ( bFromTempFile )
+ {
+ aMedium[utl::MediaDescriptor::PROP_URL] <<= aFileName;
+ aMedium.erase( utl::MediaDescriptor::PROP_READONLY );
+ aMedium.addInputStream();
+ }
+ else if ( GetURLObject().GetProtocol() == INetProtocol::File )
+ {
+ // use the special locking approach only for file URLs
+ aMedium.addInputStreamOwnLock();
+ }
+ else
+ {
+ // add a check for protocol, if it's http or https or provide webdav then add
+ // the interaction handler to be used by the authentication dialog
+ if ( GetURLObject().isAnyKnownWebDAVScheme() )
+ {
+ aMedium[utl::MediaDescriptor::PROP_AUTHENTICATIONHANDLER] <<= GetInteractionHandler( true );
+ }
+ aMedium.addInputStream();
+ }
+ // the ReadOnly property set in aMedium is ignored
+ // the check is done in LockOrigFileOnDemand() for file and non-file URLs
+
+ //TODO/MBA: what happens if property is not there?!
+ aMedium[utl::MediaDescriptor::PROP_STREAM] >>= pImpl->xStream;
+ aMedium[utl::MediaDescriptor::PROP_INPUTSTREAM] >>= pImpl->xInputStream;
+ }
+
+ GetContent();
+ if ( !pImpl->xInputStream.is() && pImpl->xStream.is() )
+ pImpl->xInputStream = pImpl->xStream->getInputStream();
+ }
+
+ if ( !bFromTempFile )
+ {
+ //TODO/MBA: need support for SID_STREAM
+ if ( pImpl->xStream.is() )
+ GetItemSet().Put( SfxUnoAnyItem( SID_STREAM, Any( pImpl->xStream ) ) );
+
+ GetItemSet().Put( SfxUnoAnyItem( SID_INPUTSTREAM, Any( pImpl->xInputStream ) ) );
+ }
+ }
+
+ //TODO/MBA: ErrorHandling - how to transport error from MediaDescriptor
+ if ( !GetErrorIgnoreWarning() && !pImpl->xStream.is() && !pImpl->xInputStream.is() )
+ SetError(ERRCODE_IO_ACCESSDENIED);
+
+ if ( !GetErrorIgnoreWarning() && !pImpl->m_pInStream )
+ {
+ if ( pImpl->xStream.is() )
+ pImpl->m_pInStream = utl::UcbStreamHelper::CreateStream( pImpl->xStream );
+ else if ( pImpl->xInputStream.is() )
+ pImpl->m_pInStream = utl::UcbStreamHelper::CreateStream( pImpl->xInputStream );
+ }
+
+ pImpl->bDownloadDone = true;
+ pImpl->aDoneLink.ClearPendingCall();
+ ErrCodeMsg nError = GetErrorIgnoreWarning();
+ pImpl->aDoneLink.Call( reinterpret_cast<void*>(sal_uInt32(nError.GetCode())) );
+}
+
+bool SfxMedium::IsRemote() const
+{
+ return pImpl->m_bRemote;
+}
+
+void SfxMedium::SetUpdatePickList(bool bVal)
+{
+ pImpl->bUpdatePickList = bVal;
+}
+
+bool SfxMedium::IsUpdatePickList() const
+{
+ return pImpl->bUpdatePickList;
+}
+
+void SfxMedium::SetLongName(const OUString &rName)
+{
+ pImpl->m_aLongName = rName;
+}
+
+const OUString& SfxMedium::GetLongName() const
+{
+ return pImpl->m_aLongName;
+}
+
+void SfxMedium::SetDoneLink( const Link<void*,void>& rLink )
+{
+ pImpl->aDoneLink = rLink;
+}
+
+void SfxMedium::Download( const Link<void*,void>& aLink )
+{
+ SetDoneLink( aLink );
+ GetInStream();
+ if ( pImpl->m_pInStream && !aLink.IsSet() )
+ {
+ while( !pImpl->bDownloadDone && !Application::IsQuit())
+ Application::Yield();
+ }
+}
+
+
+/**
+ Sets m_aLogicName to a valid URL and if available sets
+ the physical name m_aName to the file name.
+ */
+void SfxMedium::Init_Impl()
+{
+ Reference< XOutputStream > rOutStream;
+
+ // TODO/LATER: handle lifetime of storages
+ pImpl->bDisposeStorage = false;
+
+ const SfxStringItem* pSalvageItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_DOC_SALVAGE, false);
+ if ( pSalvageItem && pSalvageItem->GetValue().isEmpty() )
+ {
+ pSalvageItem = nullptr;
+ pImpl->m_pSet->ClearItem( SID_DOC_SALVAGE );
+ }
+
+ if (!pImpl->m_aLogicName.isEmpty())
+ {
+ INetURLObject aUrl( pImpl->m_aLogicName );
+ INetProtocol eProt = aUrl.GetProtocol();
+ if ( eProt == INetProtocol::NotValid )
+ {
+ SAL_WARN( "sfx.doc", "URL <" << pImpl->m_aLogicName << "> with unknown protocol" );
+ }
+ else
+ {
+ if ( aUrl.HasMark() )
+ {
+ std::unique_lock<std::recursive_mutex> chkEditLock;
+ if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
+ chkEditLock = std::unique_lock<std::recursive_mutex>(
+ *(pImpl->m_pCheckEditableWorkerMutex));
+ pImpl->m_aLogicName = aUrl.GetURLNoMark( INetURLObject::DecodeMechanism::NONE );
+ if (chkEditLock.owns_lock())
+ chkEditLock.unlock();
+ GetItemSet().Put( SfxStringItem( SID_JUMPMARK, aUrl.GetMark() ) );
+ }
+
+ // try to convert the URL into a physical name - but never change a physical name
+ // physical name may be set if the logical name is changed after construction
+ if ( pImpl->m_aName.isEmpty() )
+ osl::FileBase::getSystemPathFromFileURL( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), pImpl->m_aName );
+ else
+ {
+ DBG_ASSERT( pSalvageItem, "Suspicious change of logical name!" );
+ }
+ }
+ }
+
+ if ( pSalvageItem )
+ {
+ std::unique_lock<std::recursive_mutex> chkEditLock;
+ if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
+ chkEditLock
+ = std::unique_lock<std::recursive_mutex>(*(pImpl->m_pCheckEditableWorkerMutex));
+ pImpl->m_aLogicName = pSalvageItem->GetValue();
+ pImpl->m_pURLObj.reset();
+ if (chkEditLock.owns_lock())
+ chkEditLock.unlock();
+ pImpl->m_bSalvageMode = true;
+ }
+
+ // in case output stream is by mistake here
+ // clear the reference
+ const SfxUnoAnyItem* pOutStreamItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_OUTPUTSTREAM, false);
+ if( pOutStreamItem
+ && ( !( pOutStreamItem->GetValue() >>= rOutStream )
+ || !pImpl->m_aLogicName.startsWith("private:stream")) )
+ {
+ pImpl->m_pSet->ClearItem( SID_OUTPUTSTREAM );
+ SAL_WARN( "sfx.doc", "Unexpected Output stream parameter!" );
+ }
+
+ if (!pImpl->m_aLogicName.isEmpty())
+ {
+ // if the logic name is set it should be set in MediaDescriptor as well
+ const SfxStringItem* pFileNameItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_FILE_NAME, false);
+ if ( !pFileNameItem )
+ {
+ // let the ItemSet be created if necessary
+ GetItemSet().Put(
+ SfxStringItem(
+ SID_FILE_NAME, INetURLObject( pImpl->m_aLogicName ).GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) );
+ }
+ }
+
+ SetIsRemote_Impl();
+
+ osl::DirectoryItem item;
+ if (osl::DirectoryItem::get(GetName(), item) == osl::FileBase::E_None) {
+ osl::FileStatus stat(osl_FileStatus_Mask_Attributes);
+ if (item.getFileStatus(stat) == osl::FileBase::E_None
+ && stat.isValid(osl_FileStatus_Mask_Attributes))
+ {
+ if ((stat.getAttributes() & osl_File_Attribute_ReadOnly) != 0)
+ {
+ pImpl->m_bOriginallyReadOnly = true;
+ }
+ }
+ }
+}
+
+
+SfxMedium::SfxMedium() : pImpl(new SfxMedium_Impl)
+{
+ Init_Impl();
+}
+
+
+void SfxMedium::UseInteractionHandler( bool bUse )
+{
+ pImpl->bAllowDefaultIntHdl = bUse;
+}
+
+
+css::uno::Reference< css::task::XInteractionHandler >
+SfxMedium::GetInteractionHandler( bool bGetAlways )
+{
+ // if interaction isn't allowed explicitly ... return empty reference!
+ if ( !bGetAlways && !pImpl->bUseInteractionHandler )
+ return css::uno::Reference< css::task::XInteractionHandler >();
+
+ // search a possible existing handler inside cached item set
+ if ( pImpl->m_pSet )
+ {
+ css::uno::Reference< css::task::XInteractionHandler > xHandler;
+ const SfxUnoAnyItem* pHandler = SfxItemSet::GetItem<SfxUnoAnyItem>(pImpl->m_pSet.get(), SID_INTERACTIONHANDLER, false);
+ if ( pHandler && (pHandler->GetValue() >>= xHandler) && xHandler.is() )
+ return xHandler;
+ }
+
+ // if default interaction isn't allowed explicitly ... return empty reference!
+ if ( !bGetAlways && !pImpl->bAllowDefaultIntHdl )
+ return css::uno::Reference< css::task::XInteractionHandler >();
+
+ // otherwise return cached default handler ... if it exist.
+ if ( pImpl->xInteraction.is() )
+ return pImpl->xInteraction;
+
+ // create default handler and cache it!
+ Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+ pImpl->xInteraction.set(
+ task::InteractionHandler::createWithParent(xContext, nullptr), UNO_QUERY_THROW );
+ return pImpl->xInteraction;
+}
+
+void SfxMedium::SetFilter( const std::shared_ptr<const SfxFilter>& pFilter )
+{
+ pImpl->m_pFilter = pFilter;
+}
+
+const std::shared_ptr<const SfxFilter>& SfxMedium::GetFilter() const
+{
+ return pImpl->m_pFilter;
+}
+
+sal_uInt32 SfxMedium::CreatePasswordToModifyHash( std::u16string_view aPasswd, bool bWriter )
+{
+ sal_uInt32 nHash = 0;
+
+ if ( !aPasswd.empty() )
+ {
+ if ( bWriter )
+ {
+ nHash = ::comphelper::DocPasswordHelper::GetWordHashAsUINT32( aPasswd );
+ }
+ else
+ {
+ rtl_TextEncoding nEncoding = osl_getThreadTextEncoding();
+ nHash = ::comphelper::DocPasswordHelper::GetXLHashAsUINT16( aPasswd, nEncoding );
+ }
+ }
+
+ return nHash;
+}
+
+
+void SfxMedium::Close(bool bInDestruction)
+{
+ if ( pImpl->xStorage.is() )
+ {
+ CloseStorage();
+ }
+
+ CloseStreams_Impl(bInDestruction);
+
+ UnlockFile( false );
+}
+
+void SfxMedium::CloseAndRelease()
+{
+ if ( pImpl->xStorage.is() )
+ {
+ CloseStorage();
+ }
+
+ CloseAndReleaseStreams_Impl();
+
+ UnlockFile( true );
+}
+
+void SfxMedium::DisableUnlockWebDAV( bool bDisableUnlockWebDAV )
+{
+ pImpl->m_bDisableUnlockWebDAV = bDisableUnlockWebDAV;
+}
+
+void SfxMedium::DisableFileSync(bool bDisableFileSync)
+{
+ pImpl->m_bDisableFileSync = bDisableFileSync;
+}
+
+void SfxMedium::UnlockFile( bool bReleaseLockStream )
+{
+#if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+ (void) bReleaseLockStream;
+#else
+ // check if webdav
+ if ( GetURLObject().isAnyKnownWebDAVScheme() )
+ {
+ // do nothing if WebDAV locking if disabled
+ // (shouldn't happen because we already skipped locking,
+ // see LockOrigFileOnDemand, but just in case ...)
+ if (!IsWebDAVLockingUsed())
+ return;
+
+ if ( pImpl->m_bLocked )
+ {
+ // an interaction handler should be used for authentication, if needed
+ try {
+ uno::Reference< css::task::XInteractionHandler > xHandler = GetInteractionHandler( true );
+ uno::Reference< css::ucb::XCommandEnvironment > xComEnv = new ::ucbhelper::CommandEnvironment( xHandler,
+ Reference< css::ucb::XProgressHandler >() );
+ ucbhelper::Content aContentToUnlock( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext());
+ pImpl->m_bLocked = false;
+ //check if WebDAV unlock was explicitly disabled
+ if ( !pImpl->m_bDisableUnlockWebDAV )
+ aContentToUnlock.unlock();
+ }
+ catch ( uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "Locking exception: WebDAV while trying to lock the file" );
+ }
+ }
+ return;
+ }
+
+ if ( pImpl->m_xLockingStream.is() )
+ {
+ if ( bReleaseLockStream )
+ {
+ try
+ {
+ uno::Reference< io::XInputStream > xInStream = pImpl->m_xLockingStream->getInputStream();
+ uno::Reference< io::XOutputStream > xOutStream = pImpl->m_xLockingStream->getOutputStream();
+ if ( xInStream.is() )
+ xInStream->closeInput();
+ if ( xOutStream.is() )
+ xOutStream->closeOutput();
+ }
+ catch( const uno::Exception& )
+ {}
+ }
+
+ pImpl->m_xLockingStream.clear();
+ }
+
+ if ( !pImpl->m_bLocked )
+ return;
+
+ try
+ {
+ ::svt::DocumentLockFile aLockFile(pImpl->m_aLogicName);
+
+ try
+ {
+ pImpl->m_bLocked = false;
+ // TODO/LATER: A warning could be shown in case the file is not the own one
+ aLockFile.RemoveFile();
+ }
+ catch (const io::WrongFormatException&)
+ {
+ // erase the empty or corrupt file
+ aLockFile.RemoveFileDirectly();
+ }
+ }
+ catch( const uno::Exception& )
+ {}
+
+ if(!pImpl->m_bMSOLockFileCreated)
+ return;
+
+ try
+ {
+ ::svt::MSODocumentLockFile aMSOLockFile(pImpl->m_aLogicName);
+
+ try
+ {
+ pImpl->m_bLocked = false;
+ // TODO/LATER: A warning could be shown in case the file is not the own one
+ aMSOLockFile.RemoveFile();
+ }
+ catch (const io::WrongFormatException&)
+ {
+ // erase the empty or corrupt file
+ aMSOLockFile.RemoveFileDirectly();
+ }
+ }
+ catch( const uno::Exception& )
+ {}
+ pImpl->m_bMSOLockFileCreated = false;
+#endif
+}
+
+void SfxMedium::CloseAndReleaseStreams_Impl()
+{
+ CloseZipStorage_Impl();
+
+ uno::Reference< io::XInputStream > xInToClose = pImpl->xInputStream;
+ uno::Reference< io::XOutputStream > xOutToClose;
+ if ( pImpl->xStream.is() )
+ {
+ xOutToClose = pImpl->xStream->getOutputStream();
+
+ // if the locking stream is closed here the related member should be cleaned
+ if ( pImpl->xStream == pImpl->m_xLockingStream )
+ pImpl->m_xLockingStream.clear();
+ }
+
+ // The probably existing SvStream wrappers should be closed first
+ CloseStreams_Impl();
+
+ // in case of salvage mode the storage is based on the streams
+ if ( pImpl->m_bSalvageMode )
+ return;
+
+ try
+ {
+ if ( xInToClose.is() )
+ xInToClose->closeInput();
+ if ( xOutToClose.is() )
+ xOutToClose->closeOutput();
+ }
+ catch ( const uno::Exception& )
+ {
+ }
+}
+
+
+void SfxMedium::CloseStreams_Impl(bool bInDestruction)
+{
+ CloseInStream_Impl(bInDestruction);
+ CloseOutStream_Impl();
+
+ if ( pImpl->m_pSet )
+ pImpl->m_pSet->ClearItem( SID_CONTENT );
+
+ pImpl->aContent = ::ucbhelper::Content();
+}
+
+
+void SfxMedium::SetIsRemote_Impl()
+{
+ INetURLObject aObj( GetName() );
+ switch( aObj.GetProtocol() )
+ {
+ case INetProtocol::Ftp:
+ case INetProtocol::Http:
+ case INetProtocol::Https:
+ pImpl->m_bRemote = true;
+ break;
+ default:
+ pImpl->m_bRemote = GetName().startsWith("private:msgid");
+ break;
+ }
+
+ // As files that are written to the remote transmission must also be able
+ // to be read.
+ if (pImpl->m_bRemote)
+ pImpl->m_nStorOpenMode |= StreamMode::READ;
+}
+
+
+void SfxMedium::SetName( const OUString& aNameP, bool bSetOrigURL )
+{
+ if (pImpl->aOrigURL.isEmpty())
+ pImpl->aOrigURL = pImpl->m_aLogicName;
+ if( bSetOrigURL )
+ pImpl->aOrigURL = aNameP;
+ std::unique_lock<std::recursive_mutex> chkEditLock;
+ if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
+ chkEditLock = std::unique_lock<std::recursive_mutex>(*(pImpl->m_pCheckEditableWorkerMutex));
+ pImpl->m_aLogicName = aNameP;
+ pImpl->m_pURLObj.reset();
+ if (chkEditLock.owns_lock())
+ chkEditLock.unlock();
+ pImpl->aContent = ::ucbhelper::Content();
+ Init_Impl();
+}
+
+
+const OUString& SfxMedium::GetOrigURL() const
+{
+ return pImpl->aOrigURL.isEmpty() ? pImpl->m_aLogicName : pImpl->aOrigURL;
+}
+
+
+void SfxMedium::SetPhysicalName_Impl( const OUString& rNameP )
+{
+ if ( rNameP != pImpl->m_aName )
+ {
+ pImpl->pTempFile.reset();
+
+ if ( !pImpl->m_aName.isEmpty() || !rNameP.isEmpty() )
+ pImpl->aContent = ::ucbhelper::Content();
+
+ pImpl->m_aName = rNameP;
+ pImpl->m_bTriedStorage = false;
+ pImpl->bIsStorage = false;
+ }
+}
+
+void SfxMedium::ReOpen()
+{
+ bool bUseInteractionHandler = pImpl->bUseInteractionHandler;
+ pImpl->bUseInteractionHandler = false;
+ GetMedium_Impl();
+ pImpl->bUseInteractionHandler = bUseInteractionHandler;
+}
+
+void SfxMedium::CompleteReOpen()
+{
+ // do not use temporary file for reopen and in case of success throw the temporary file away
+ bool bUseInteractionHandler = pImpl->bUseInteractionHandler;
+ pImpl->bUseInteractionHandler = false;
+
+ std::unique_ptr<::utl::TempFileNamed> pTmpFile;
+ if ( pImpl->pTempFile )
+ {
+ pTmpFile = std::move(pImpl->pTempFile);
+ pImpl->m_aName.clear();
+ }
+
+ GetMedium_Impl();
+
+ if ( GetErrorIgnoreWarning() )
+ {
+ if ( pImpl->pTempFile )
+ {
+ pImpl->pTempFile->EnableKillingFile();
+ pImpl->pTempFile.reset();
+ }
+ pImpl->pTempFile = std::move( pTmpFile );
+ if ( pImpl->pTempFile )
+ pImpl->m_aName = pImpl->pTempFile->GetFileName();
+ }
+ else if (pTmpFile)
+ {
+ pTmpFile->EnableKillingFile();
+ pTmpFile.reset();
+ }
+
+ pImpl->bUseInteractionHandler = bUseInteractionHandler;
+}
+
+SfxMedium::SfxMedium(const OUString &rName, StreamMode nOpenMode, std::shared_ptr<const SfxFilter> pFilter, const std::shared_ptr<SfxItemSet>& pInSet) :
+ pImpl(new SfxMedium_Impl)
+{
+ pImpl->m_pSet = pInSet;
+ pImpl->m_pFilter = std::move(pFilter);
+ pImpl->m_aLogicName = rName;
+ pImpl->m_nStorOpenMode = nOpenMode;
+ Init_Impl();
+}
+
+SfxMedium::SfxMedium(const OUString &rName, const OUString &rReferer, StreamMode nOpenMode, std::shared_ptr<const SfxFilter> pFilter, const std::shared_ptr<SfxItemSet>& pInSet) :
+ pImpl(new SfxMedium_Impl)
+{
+ pImpl->m_pSet = pInSet;
+ SfxItemSet& s = GetItemSet();
+ if (s.GetItem(SID_REFERER) == nullptr) {
+ s.Put(SfxStringItem(SID_REFERER, rReferer));
+ }
+ pImpl->m_pFilter = std::move(pFilter);
+ pImpl->m_aLogicName = rName;
+ pImpl->m_nStorOpenMode = nOpenMode;
+ Init_Impl();
+}
+
+SfxMedium::SfxMedium( const uno::Sequence<beans::PropertyValue>& aArgs ) :
+ pImpl(new SfxMedium_Impl)
+{
+ SfxAllItemSet *pParams = new SfxAllItemSet( SfxGetpApp()->GetPool() );
+ pImpl->m_pSet.reset( pParams );
+ TransformParameters( SID_OPENDOC, aArgs, *pParams );
+ SetArgs(aArgs);
+
+ OUString aFilterProvider, aFilterName;
+ {
+ const SfxStringItem* pItem = nullptr;
+ if ((pItem = pImpl->m_pSet->GetItemIfSet(SID_FILTER_PROVIDER)))
+ aFilterProvider = pItem->GetValue();
+
+ if ((pItem = pImpl->m_pSet->GetItemIfSet(SID_FILTER_NAME)))
+ aFilterName = pItem->GetValue();
+ }
+
+ if (aFilterProvider.isEmpty())
+ {
+ // This is a conventional filter type.
+ pImpl->m_pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4FilterName( aFilterName );
+ }
+ else
+ {
+ // This filter is from an external provider such as orcus.
+ pImpl->m_pCustomFilter = std::make_shared<SfxFilter>(aFilterProvider, aFilterName);
+ pImpl->m_pFilter = pImpl->m_pCustomFilter;
+ }
+
+ const SfxStringItem* pSalvageItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_DOC_SALVAGE, false);
+ if( pSalvageItem )
+ {
+ // QUESTION: there is some treatment of Salvage in Init_Impl; align!
+ if ( !pSalvageItem->GetValue().isEmpty() )
+ {
+ // if a URL is provided in SalvageItem that means that the FileName refers to a temporary file
+ // that must be copied here
+
+ const SfxStringItem* pFileNameItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_FILE_NAME, false);
+ if (!pFileNameItem) throw uno::RuntimeException();
+ OUString aNewTempFileURL = SfxMedium::CreateTempCopyWithExt( pFileNameItem->GetValue() );
+ if ( !aNewTempFileURL.isEmpty() )
+ {
+ pImpl->m_pSet->Put( SfxStringItem( SID_FILE_NAME, aNewTempFileURL ) );
+ pImpl->m_pSet->ClearItem( SID_INPUTSTREAM );
+ pImpl->m_pSet->ClearItem( SID_STREAM );
+ pImpl->m_pSet->ClearItem( SID_CONTENT );
+ }
+ else
+ {
+ SAL_WARN( "sfx.doc", "Can not create a new temporary file for crash recovery!" );
+ }
+ }
+ }
+
+ const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
+ if ( pReadOnlyItem && pReadOnlyItem->GetValue() )
+ pImpl->m_bOriginallyLoadedReadOnly = true;
+
+ const SfxStringItem* pFileNameItem = SfxItemSet::GetItem<SfxStringItem>(pImpl->m_pSet.get(), SID_FILE_NAME, false);
+ if (!pFileNameItem) throw uno::RuntimeException();
+ pImpl->m_aLogicName = pFileNameItem->GetValue();
+ pImpl->m_nStorOpenMode = pImpl->m_bOriginallyLoadedReadOnly
+ ? SFX_STREAM_READONLY : SFX_STREAM_READWRITE;
+ Init_Impl();
+}
+
+void SfxMedium::SetArgs(const uno::Sequence<beans::PropertyValue>& rArgs)
+{
+ static constexpr OUStringLiteral sStream(u"Stream");
+ static constexpr OUStringLiteral sInputStream(u"InputStream");
+ comphelper::SequenceAsHashMap aArgsMap(rArgs);
+ aArgsMap.erase(sStream);
+ aArgsMap.erase(sInputStream);
+ pImpl->m_aArgs = aArgsMap.getAsConstPropertyValueList();
+}
+
+const uno::Sequence<beans::PropertyValue> & SfxMedium::GetArgs() const { return pImpl->m_aArgs; }
+
+SfxMedium::SfxMedium( const uno::Reference < embed::XStorage >& rStor, const OUString& rBaseURL, const std::shared_ptr<SfxItemSet>& p ) :
+ pImpl(new SfxMedium_Impl)
+{
+ OUString aType = SfxFilter::GetTypeFromStorage(rStor);
+ pImpl->m_pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4EA( aType );
+ DBG_ASSERT( pImpl->m_pFilter, "No Filter for storage found!" );
+
+ Init_Impl();
+ pImpl->xStorage = rStor;
+ pImpl->bDisposeStorage = false;
+
+ // always take BaseURL first, could be overwritten by ItemSet
+ GetItemSet().Put( SfxStringItem( SID_DOC_BASEURL, rBaseURL ) );
+ if ( p )
+ GetItemSet().Put( *p );
+}
+
+
+SfxMedium::SfxMedium( const uno::Reference < embed::XStorage >& rStor, const OUString& rBaseURL, const OUString &rTypeName, const std::shared_ptr<SfxItemSet>& p ) :
+ pImpl(new SfxMedium_Impl)
+{
+ pImpl->m_pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4EA( rTypeName );
+ DBG_ASSERT( pImpl->m_pFilter, "No Filter for storage found!" );
+
+ Init_Impl();
+ pImpl->xStorage = rStor;
+ pImpl->bDisposeStorage = false;
+
+ // always take BaseURL first, could be overwritten by ItemSet
+ GetItemSet().Put( SfxStringItem( SID_DOC_BASEURL, rBaseURL ) );
+ if ( p )
+ GetItemSet().Put( *p );
+}
+
+// NOTE: should only be called on main thread
+SfxMedium::~SfxMedium()
+{
+ CancelCheckEditableEntry();
+
+ // if there is a requirement to clean the backup this is the last possibility to do it
+ ClearBackup_Impl();
+
+ Close(/*bInDestruction*/true);
+
+ if( !pImpl->bIsTemp || pImpl->m_aName.isEmpty() )
+ return;
+
+ OUString aTemp;
+ if ( osl::FileBase::getFileURLFromSystemPath( pImpl->m_aName, aTemp )
+ != osl::FileBase::E_None )
+ {
+ SAL_WARN( "sfx.doc", "Physical name not convertible!");
+ }
+
+ if ( !::utl::UCBContentHelper::Kill( aTemp ) )
+ {
+ SAL_WARN( "sfx.doc", "Couldn't remove temporary file!");
+ }
+}
+
+const OUString& SfxMedium::GetName() const
+{
+ return pImpl->m_aLogicName;
+}
+
+const INetURLObject& SfxMedium::GetURLObject() const
+{
+ std::unique_lock<std::recursive_mutex> chkEditLock;
+ if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
+ chkEditLock = std::unique_lock<std::recursive_mutex>(*(pImpl->m_pCheckEditableWorkerMutex));
+
+ if (!pImpl->m_pURLObj)
+ {
+ pImpl->m_pURLObj.reset( new INetURLObject( pImpl->m_aLogicName ) );
+ pImpl->m_pURLObj->SetMark(u"");
+ }
+
+ return *pImpl->m_pURLObj;
+}
+
+void SfxMedium::SetExpired_Impl( const DateTime& rDateTime )
+{
+ pImpl->aExpireTime = rDateTime;
+}
+
+
+bool SfxMedium::IsExpired() const
+{
+ return pImpl->aExpireTime.IsValidAndGregorian() && pImpl->aExpireTime < DateTime( DateTime::SYSTEM );
+}
+
+
+SfxFrame* SfxMedium::GetLoadTargetFrame() const
+{
+ return pImpl->wLoadTargetFrame;
+}
+
+void SfxMedium::setStreamToLoadFrom(const css::uno::Reference<css::io::XInputStream>& xInputStream, bool bIsReadOnly )
+{
+ pImpl->m_xInputStreamToLoadFrom = xInputStream;
+ pImpl->m_bInputStreamIsReadOnly = bIsReadOnly;
+}
+
+void SfxMedium::SetLoadTargetFrame(SfxFrame* pFrame )
+{
+ pImpl->wLoadTargetFrame = pFrame;
+}
+
+void SfxMedium::SetStorage_Impl(const uno::Reference<embed::XStorage>& xStorage)
+{
+ pImpl->xStorage = xStorage;
+ pImpl->m_bODFWholesomeEncryption = false;
+}
+
+void SfxMedium::SetInnerStorage_Impl(const uno::Reference<embed::XStorage>& xStorage)
+{
+ pImpl->xStorage = xStorage;
+ pImpl->m_bODFWholesomeEncryption = true;
+}
+
+SfxItemSet& SfxMedium::GetItemSet() const
+{
+ if (!pImpl->m_pSet)
+ pImpl->m_pSet = std::make_shared<SfxAllItemSet>( SfxGetpApp()->GetPool() );
+ return *pImpl->m_pSet;
+}
+
+
+SvKeyValueIterator* SfxMedium::GetHeaderAttributes_Impl()
+{
+ if( !pImpl->xAttributes.is() )
+ {
+ pImpl->xAttributes = SvKeyValueIteratorRef( new SvKeyValueIterator );
+
+ if ( GetContent().is() )
+ {
+ try
+ {
+ Any aAny = pImpl->aContent.getPropertyValue("MediaType");
+ OUString aContentType;
+ aAny >>= aContentType;
+
+ pImpl->xAttributes->Append( SvKeyValue( "content-type", aContentType ) );
+ }
+ catch ( const css::uno::Exception& )
+ {
+ }
+ }
+ }
+
+ return pImpl->xAttributes.get();
+}
+
+css::uno::Reference< css::io::XInputStream > const & SfxMedium::GetInputStream()
+{
+ if ( !pImpl->xInputStream.is() )
+ GetMedium_Impl();
+ return pImpl->xInputStream;
+}
+
+const uno::Sequence < util::RevisionTag >& SfxMedium::GetVersionList( bool _bNoReload )
+{
+ // if the medium has no name, then this medium should represent a new document and can have no version info
+ if ( ( !_bNoReload || !pImpl->m_bVersionsAlreadyLoaded ) && !pImpl->aVersions.hasElements() &&
+ ( !pImpl->m_aName.isEmpty() || !pImpl->m_aLogicName.isEmpty() ) && GetStorage().is() )
+ {
+ uno::Reference < document::XDocumentRevisionListPersistence > xReader =
+ document::DocumentRevisionListPersistence::create( comphelper::getProcessComponentContext() );
+ try
+ {
+ pImpl->aVersions = xReader->load( GetStorage() );
+ }
+ catch ( const uno::Exception& )
+ {
+ }
+ }
+
+ if ( !pImpl->m_bVersionsAlreadyLoaded )
+ pImpl->m_bVersionsAlreadyLoaded = true;
+
+ return pImpl->aVersions;
+}
+
+uno::Sequence < util::RevisionTag > SfxMedium::GetVersionList( const uno::Reference < embed::XStorage >& xStorage )
+{
+ uno::Reference < document::XDocumentRevisionListPersistence > xReader =
+ document::DocumentRevisionListPersistence::create( comphelper::getProcessComponentContext() );
+ try
+ {
+ return xReader->load( xStorage );
+ }
+ catch ( const uno::Exception& )
+ {
+ }
+
+ return uno::Sequence < util::RevisionTag >();
+}
+
+void SfxMedium::AddVersion_Impl( util::RevisionTag& rRevision )
+{
+ if ( !GetStorage().is() )
+ return;
+
+ // To determine a unique name for the stream
+ std::vector<sal_uInt32> aLongs;
+ sal_Int32 nLength = pImpl->aVersions.getLength();
+ for ( const auto& rVersion : std::as_const(pImpl->aVersions) )
+ {
+ sal_uInt32 nVer = static_cast<sal_uInt32>( o3tl::toInt32(rVersion.Identifier.subView(7)));
+ size_t n;
+ for ( n=0; n<aLongs.size(); ++n )
+ if ( nVer<aLongs[n] )
+ break;
+
+ aLongs.insert( aLongs.begin()+n, nVer );
+ }
+
+ std::vector<sal_uInt32>::size_type nKey;
+ for ( nKey=0; nKey<aLongs.size(); ++nKey )
+ if ( aLongs[nKey] > nKey+1 )
+ break;
+
+ OUString aRevName = "Version" + OUString::number( nKey + 1 );
+ pImpl->aVersions.realloc( nLength+1 );
+ rRevision.Identifier = aRevName;
+ pImpl->aVersions.getArray()[nLength] = rRevision;
+}
+
+void SfxMedium::RemoveVersion_Impl( const OUString& rName )
+{
+ if ( !pImpl->aVersions.hasElements() )
+ return;
+
+ auto pVersion = std::find_if(std::cbegin(pImpl->aVersions), std::cend(pImpl->aVersions),
+ [&rName](const auto& rVersion) { return rVersion.Identifier == rName; });
+ if (pVersion != std::cend(pImpl->aVersions))
+ {
+ auto nIndex = static_cast<sal_Int32>(std::distance(std::cbegin(pImpl->aVersions), pVersion));
+ comphelper::removeElementAt(pImpl->aVersions, nIndex);
+ }
+}
+
+bool SfxMedium::TransferVersionList_Impl( SfxMedium const & rMedium )
+{
+ if ( rMedium.pImpl->aVersions.hasElements() )
+ {
+ pImpl->aVersions = rMedium.pImpl->aVersions;
+ return true;
+ }
+
+ return false;
+}
+
+void SfxMedium::SaveVersionList_Impl()
+{
+ if ( !GetStorage().is() )
+ return;
+
+ if ( !pImpl->aVersions.hasElements() )
+ return;
+
+ uno::Reference < document::XDocumentRevisionListPersistence > xWriter =
+ document::DocumentRevisionListPersistence::create( comphelper::getProcessComponentContext() );
+ try
+ {
+ xWriter->store( GetStorage(), pImpl->aVersions );
+ }
+ catch ( const uno::Exception& )
+ {
+ }
+}
+
+bool SfxMedium::IsReadOnly() const
+{
+ // a) ReadOnly filter can't produce read/write contents!
+ bool bReadOnly = pImpl->m_pFilter && (pImpl->m_pFilter->GetFilterFlags() & SfxFilterFlags::OPENREADONLY);
+
+ // b) if filter allow read/write contents .. check open mode of the storage
+ if (!bReadOnly)
+ bReadOnly = !( GetOpenMode() & StreamMode::WRITE );
+
+ // c) the API can force the readonly state!
+ if (!bReadOnly)
+ {
+ const SfxBoolItem* pItem = GetItemSet().GetItem(SID_DOC_READONLY, false);
+ if (pItem)
+ bReadOnly = pItem->GetValue();
+ }
+
+ return bReadOnly;
+}
+
+bool SfxMedium::IsOriginallyReadOnly() const
+{
+ return pImpl->m_bOriginallyReadOnly;
+}
+
+void SfxMedium::SetOriginallyReadOnly(bool val)
+{
+ pImpl->m_bOriginallyReadOnly = val;
+}
+
+bool SfxMedium::IsOriginallyLoadedReadOnly() const
+{
+ return pImpl->m_bOriginallyLoadedReadOnly;
+}
+
+bool SfxMedium::SetWritableForUserOnly( const OUString& aURL )
+{
+ // UCB does not allow to allow write access only for the user,
+ // use osl API
+ bool bResult = false;
+
+ ::osl::DirectoryItem aDirItem;
+ if ( ::osl::DirectoryItem::get( aURL, aDirItem ) == ::osl::FileBase::E_None )
+ {
+ ::osl::FileStatus aFileStatus( osl_FileStatus_Mask_Attributes );
+ if ( aDirItem.getFileStatus( aFileStatus ) == osl::FileBase::E_None
+ && aFileStatus.isValid( osl_FileStatus_Mask_Attributes ) )
+ {
+ sal_uInt64 nAttributes = aFileStatus.getAttributes();
+
+ nAttributes &= ~(osl_File_Attribute_OwnWrite |
+ osl_File_Attribute_GrpWrite |
+ osl_File_Attribute_OthWrite |
+ osl_File_Attribute_ReadOnly);
+ nAttributes |= (osl_File_Attribute_OwnWrite |
+ osl_File_Attribute_OwnRead);
+
+ bResult = ( osl::File::setAttributes( aURL, nAttributes ) == ::osl::FileBase::E_None );
+ }
+ }
+
+ return bResult;
+}
+
+namespace
+{
+/// Get the parent directory of a temporary file for output purposes.
+OUString GetLogicBase(const INetURLObject& rURL, std::unique_ptr<SfxMedium_Impl> const & pImpl)
+{
+ OUString aLogicBase;
+
+#if HAVE_FEATURE_MACOSX_SANDBOX
+ // In a sandboxed environment we don't want to attempt to create temporary files in the same
+ // directory where the user has selected an output file to be stored. The sandboxed process has
+ // permission only to create the specifically named output file in that directory.
+ (void) rURL;
+ (void) pImpl;
+#else
+
+ if (!pImpl->m_bHasEmbeddedObjects // Embedded objects would mean a special base, ignore that.
+ && rURL.GetProtocol() == INetProtocol::File && !pImpl->m_pInStream)
+ {
+ // Try to create the temp file in the same directory when storing.
+ INetURLObject aURL(rURL);
+ aURL.removeSegment();
+ aLogicBase = aURL.GetMainURL(INetURLObject::DecodeMechanism::WithCharset);
+ }
+
+#endif // !HAVE_FEATURE_MACOSX_SANDBOX
+
+ return aLogicBase;
+}
+}
+
+void SfxMedium::CreateTempFile( bool bReplace )
+{
+ if ( pImpl->pTempFile )
+ {
+ if ( !bReplace )
+ return;
+
+ pImpl->pTempFile.reset();
+ pImpl->m_aName.clear();
+ }
+
+ OUString aLogicBase = GetLogicBase(GetURLObject(), pImpl);
+ pImpl->pTempFile.reset(new ::utl::TempFileNamed(&aLogicBase));
+ pImpl->pTempFile->EnableKillingFile();
+ pImpl->m_aName = pImpl->pTempFile->GetFileName();
+ OUString aTmpURL = pImpl->pTempFile->GetURL();
+ if ( pImpl->m_aName.isEmpty() || aTmpURL.isEmpty() )
+ {
+ SetError(ERRCODE_IO_CANTWRITE);
+ return;
+ }
+
+ if ( !(pImpl->m_nStorOpenMode & StreamMode::TRUNC) )
+ {
+ bool bTransferSuccess = false;
+
+ if ( GetContent().is()
+ && GetURLObject().GetProtocol() == INetProtocol::File
+ && ::utl::UCBContentHelper::IsDocument( GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
+ {
+ // if there is already such a document, we should copy it
+ // if it is a file system use OS copy process
+ try
+ {
+ uno::Reference< css::ucb::XCommandEnvironment > xComEnv;
+ INetURLObject aTmpURLObj( aTmpURL );
+ OUString aFileName = aTmpURLObj.getName( INetURLObject::LAST_SEGMENT,
+ true,
+ INetURLObject::DecodeMechanism::WithCharset );
+ if ( !aFileName.isEmpty() && aTmpURLObj.removeSegment() )
+ {
+ ::ucbhelper::Content aTargetContent( aTmpURLObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
+ OUString sMimeType = pImpl->getFilterMimeType();
+ aTargetContent.transferContent( pImpl->aContent, ::ucbhelper::InsertOperation::Copy, aFileName, NameClash::OVERWRITE, sMimeType );
+ SetWritableForUserOnly( aTmpURL );
+ bTransferSuccess = true;
+ }
+ }
+ catch( const uno::Exception& )
+ {}
+
+ if ( bTransferSuccess )
+ {
+ CloseOutStream();
+ CloseInStream();
+ }
+ }
+
+ if ( !bTransferSuccess && pImpl->m_pInStream )
+ {
+ // the case when there is no URL-access available or this is a remote protocol
+ // but there is an input stream
+ GetOutStream();
+ if ( pImpl->m_pOutStream )
+ {
+ std::unique_ptr<char[]> pBuf(new char [8192]);
+ ErrCode nErr = ERRCODE_NONE;
+
+ pImpl->m_pInStream->Seek(0);
+ pImpl->m_pOutStream->Seek(0);
+
+ while( !pImpl->m_pInStream->eof() && nErr == ERRCODE_NONE )
+ {
+ sal_uInt32 nRead = pImpl->m_pInStream->ReadBytes(pBuf.get(), 8192);
+ nErr = pImpl->m_pInStream->GetError();
+ pImpl->m_pOutStream->WriteBytes( pBuf.get(), nRead );
+ }
+
+ bTransferSuccess = true;
+ CloseInStream();
+ }
+ CloseOutStream_Impl();
+ }
+ else
+ {
+ // Quite strange design, but currently it is expected that in this case no transfer happens
+ // TODO/LATER: get rid of this inconsistent part of the call design
+ bTransferSuccess = true;
+ CloseInStream();
+ }
+
+ if ( !bTransferSuccess )
+ {
+ SetError(ERRCODE_IO_CANTWRITE);
+ return;
+ }
+ }
+
+ CloseStorage();
+}
+
+
+void SfxMedium::CreateTempFileNoCopy()
+{
+ // this call always replaces the existing temporary file
+ pImpl->pTempFile.reset();
+
+ OUString aLogicBase = GetLogicBase(GetURLObject(), pImpl);
+ pImpl->pTempFile.reset(new ::utl::TempFileNamed(&aLogicBase));
+ pImpl->pTempFile->EnableKillingFile();
+ pImpl->m_aName = pImpl->pTempFile->GetFileName();
+ if ( pImpl->m_aName.isEmpty() )
+ {
+ SetError(ERRCODE_IO_CANTWRITE);
+ return;
+ }
+
+ CloseOutStream_Impl();
+ CloseStorage();
+}
+
+bool SfxMedium::SignDocumentContentUsingCertificate(
+ const css::uno::Reference<css::frame::XModel>& xModel, bool bHasValidDocumentSignature,
+ const Reference<XCertificate>& xCertificate)
+{
+ bool bChanges = false;
+
+ if (IsOpen() || GetErrorIgnoreWarning())
+ {
+ SAL_WARN("sfx.doc", "The medium must be closed by the signer!");
+ return bChanges;
+ }
+
+ // The component should know if there was a valid document signature, since
+ // it should show a warning in this case
+ OUString aODFVersion(comphelper::OStorageHelper::GetODFVersionFromStorage(GetStorage()));
+ uno::Reference< security::XDocumentDigitalSignatures > xSigner(
+ security::DocumentDigitalSignatures::createWithVersionAndValidSignature(
+ comphelper::getProcessComponentContext(), aODFVersion, bHasValidDocumentSignature ) );
+ auto xModelSigner = dynamic_cast<sfx2::DigitalSignatures*>(xSigner.get());
+ if (!xModelSigner)
+ {
+ return bChanges;
+ }
+
+ uno::Reference< embed::XStorage > xWriteableZipStor;
+
+ // we can reuse the temporary file if there is one already
+ CreateTempFile( false );
+ GetMedium_Impl();
+
+ try
+ {
+ if ( !pImpl->xStream.is() )
+ throw uno::RuntimeException();
+
+ bool bODF = GetFilter()->IsOwnFormat();
+ try
+ {
+ xWriteableZipStor = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream( ZIP_STORAGE_FORMAT_STRING, pImpl->xStream );
+ }
+ catch (const io::IOException&)
+ {
+ if (bODF)
+ {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "ODF stream is not a zip storage");
+ }
+ }
+
+ if ( !xWriteableZipStor.is() && bODF )
+ throw uno::RuntimeException();
+
+ uno::Reference< embed::XStorage > xMetaInf;
+ if (xWriteableZipStor.is() && xWriteableZipStor->hasByName("META-INF"))
+ {
+ xMetaInf = xWriteableZipStor->openStorageElement(
+ "META-INF",
+ embed::ElementModes::READWRITE );
+ if ( !xMetaInf.is() )
+ throw uno::RuntimeException();
+ }
+
+ {
+ if (xMetaInf.is())
+ {
+ // ODF.
+ uno::Reference< io::XStream > xStream;
+ if (GetFilter() && GetFilter()->IsOwnFormat())
+ xStream.set(xMetaInf->openStreamElement(xSigner->getDocumentContentSignatureDefaultStreamName(), embed::ElementModes::READWRITE), uno::UNO_SET_THROW);
+
+ bool bSuccess = xModelSigner->SignModelWithCertificate(
+ xModel, xCertificate, GetZipStorageToSign_Impl(), xStream);
+
+ if (bSuccess)
+ {
+ uno::Reference< embed::XTransactedObject > xTransact( xMetaInf, uno::UNO_QUERY_THROW );
+ xTransact->commit();
+ xTransact.set( xWriteableZipStor, uno::UNO_QUERY_THROW );
+ xTransact->commit();
+
+ // the temporary file has been written, commit it to the original file
+ Commit();
+ bChanges = true;
+ }
+ }
+ else if (xWriteableZipStor.is())
+ {
+ // OOXML.
+ uno::Reference<io::XStream> xStream;
+
+ // We need read-write to be able to add the signature relation.
+ bool bSuccess = xModelSigner->SignModelWithCertificate(
+ xModel, xCertificate, GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream);
+
+ if (bSuccess)
+ {
+ uno::Reference<embed::XTransactedObject> xTransact(xWriteableZipStor, uno::UNO_QUERY_THROW);
+ xTransact->commit();
+
+ // the temporary file has been written, commit it to the original file
+ Commit();
+ bChanges = true;
+ }
+ }
+ else
+ {
+ // Something not ZIP based: e.g. PDF.
+ std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(GetName(), StreamMode::READ | StreamMode::WRITE));
+ uno::Reference<io::XStream> xStream(new utl::OStreamWrapper(*pStream));
+ if (xModelSigner->SignModelWithCertificate(
+ xModel, xCertificate, uno::Reference<embed::XStorage>(), xStream))
+ bChanges = true;
+ }
+ }
+ }
+ catch ( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "Couldn't use signing functionality!");
+ }
+
+ CloseAndRelease();
+
+ ResetError();
+
+ return bChanges;
+}
+
+// note: this is the only function creating scripting signature
+bool SfxMedium::SignContents_Impl(weld::Window* pDialogParent,
+ bool bSignScriptingContent,
+ bool bHasValidDocumentSignature,
+ const OUString& aSignatureLineId,
+ const Reference<XCertificate>& xCert,
+ const Reference<XGraphic>& xValidGraphic,
+ const Reference<XGraphic>& xInvalidGraphic,
+ const OUString& aComment)
+{
+ bool bChanges = false;
+
+ if (IsOpen() || GetErrorIgnoreWarning())
+ {
+ SAL_WARN("sfx.doc", "The medium must be closed by the signer!");
+ return bChanges;
+ }
+
+ // The component should know if there was a valid document signature, since
+ // it should show a warning in this case
+ OUString aODFVersion(comphelper::OStorageHelper::GetODFVersionFromStorage(GetStorage()));
+ uno::Reference< security::XDocumentDigitalSignatures > xSigner(
+ security::DocumentDigitalSignatures::createWithVersionAndValidSignature(
+ comphelper::getProcessComponentContext(), aODFVersion, bHasValidDocumentSignature ) );
+ if (pDialogParent)
+ xSigner->setParentWindow(pDialogParent->GetXWindow());
+
+ uno::Reference< embed::XStorage > xWriteableZipStor;
+
+ // we can reuse the temporary file if there is one already
+ CreateTempFile( false );
+ GetMedium_Impl();
+
+ try
+ {
+ if ( !pImpl->xStream.is() )
+ throw uno::RuntimeException();
+
+ bool bODF = GetFilter()->IsOwnFormat();
+ try
+ {
+ if (pImpl->m_bODFWholesomeEncryption && bSignScriptingContent)
+ {
+ assert(pImpl->xStorage); // GetStorage was called above
+ assert(pImpl->m_xODFDecryptedInnerPackageStream);
+ xWriteableZipStor = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
+ ZIP_STORAGE_FORMAT_STRING, pImpl->m_xODFDecryptedInnerPackageStream);
+ }
+ else
+ {
+ xWriteableZipStor = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
+ ZIP_STORAGE_FORMAT_STRING, pImpl->xStream );
+ }
+ }
+ catch (const io::IOException&)
+ {
+ if (bODF)
+ {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "ODF stream is not a zip storage");
+ }
+ }
+
+ if ( !xWriteableZipStor.is() && bODF )
+ throw uno::RuntimeException();
+
+ uno::Reference< embed::XStorage > xMetaInf;
+ if (xWriteableZipStor.is() && xWriteableZipStor->hasByName("META-INF"))
+ {
+ xMetaInf = xWriteableZipStor->openStorageElement(
+ "META-INF",
+ embed::ElementModes::READWRITE );
+ if ( !xMetaInf.is() )
+ throw uno::RuntimeException();
+ }
+
+ if ( bSignScriptingContent )
+ {
+ // If the signature has already the document signature it will be removed
+ // after the scripting signature is inserted.
+ uno::Reference< io::XStream > xStream(
+ xMetaInf->openStreamElement( xSigner->getScriptingContentSignatureDefaultStreamName(),
+ embed::ElementModes::READWRITE ),
+ uno::UNO_SET_THROW );
+
+ // note: the storage passed here must be independent from the
+ // xWriteableZipStor because a writable storage can't have 2
+ // instances of sub-storage for the same directory open, but with
+ // independent storages it somehow works
+ if (xSigner->signScriptingContent(GetScriptingStorageToSign_Impl(), xStream))
+ {
+ // remove the document signature if any
+ OUString aDocSigName = xSigner->getDocumentContentSignatureDefaultStreamName();
+ if ( !aDocSigName.isEmpty() && xMetaInf->hasByName( aDocSigName ) )
+ xMetaInf->removeElement( aDocSigName );
+
+ uno::Reference< embed::XTransactedObject > xTransact( xMetaInf, uno::UNO_QUERY_THROW );
+ xTransact->commit();
+ xTransact.set( xWriteableZipStor, uno::UNO_QUERY_THROW );
+ xTransact->commit();
+
+ if (pImpl->m_bODFWholesomeEncryption)
+ { // manually copy the inner package to the outer one
+ uno::Reference<io::XSeekable>(pImpl->m_xODFDecryptedInnerPackageStream, uno::UNO_QUERY_THROW)->seek(0);
+ uno::Reference<io::XStream> const xEncryptedPackage =
+ pImpl->m_xODFEncryptedOuterStorage->openStreamElement(
+ "encrypted-package",
+ embed::ElementModes::WRITE|embed::ElementModes::TRUNCATE);
+ comphelper::OStorageHelper::CopyInputToOutput(pImpl->m_xODFDecryptedInnerPackageStream->getInputStream(), xEncryptedPackage->getOutputStream());
+ xTransact.set(pImpl->m_xODFEncryptedOuterStorage, uno::UNO_QUERY_THROW);
+ xTransact->commit(); // Commit() below won't do this
+ }
+
+ assert(!pImpl->xStorage.is() // ensure this doesn't overwrite
+ || !uno::Reference<util::XModifiable>(pImpl->xStorage, uno::UNO_QUERY_THROW)->isModified());
+ // the temporary file has been written, commit it to the original file
+ Commit();
+ bChanges = true;
+ }
+ }
+ else
+ {
+ if (xMetaInf.is())
+ {
+ // ODF.
+ uno::Reference< io::XStream > xStream;
+ if (GetFilter() && GetFilter()->IsOwnFormat())
+ xStream.set(xMetaInf->openStreamElement(xSigner->getDocumentContentSignatureDefaultStreamName(), embed::ElementModes::READWRITE), uno::UNO_SET_THROW);
+
+ bool bSuccess = false;
+ if (xCert.is())
+ bSuccess = xSigner->signSignatureLine(
+ GetZipStorageToSign_Impl(), xStream, aSignatureLineId, xCert,
+ xValidGraphic, xInvalidGraphic, aComment);
+ else
+ bSuccess = xSigner->signDocumentContent(GetZipStorageToSign_Impl(),
+ xStream);
+
+ if (bSuccess)
+ {
+ uno::Reference< embed::XTransactedObject > xTransact( xMetaInf, uno::UNO_QUERY_THROW );
+ xTransact->commit();
+ xTransact.set( xWriteableZipStor, uno::UNO_QUERY_THROW );
+ xTransact->commit();
+
+ // the temporary file has been written, commit it to the original file
+ Commit();
+ bChanges = true;
+ }
+ }
+ else if (xWriteableZipStor.is())
+ {
+ // OOXML.
+ uno::Reference<io::XStream> xStream;
+
+ bool bSuccess = false;
+ if (xCert.is())
+ {
+ bSuccess = xSigner->signSignatureLine(
+ GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream, aSignatureLineId,
+ xCert, xValidGraphic, xInvalidGraphic, aComment);
+ }
+ else
+ {
+ // We need read-write to be able to add the signature relation.
+ bSuccess =xSigner->signDocumentContent(
+ GetZipStorageToSign_Impl(/*bReadOnly=*/false), xStream);
+ }
+
+ if (bSuccess)
+ {
+ uno::Reference<embed::XTransactedObject> xTransact(xWriteableZipStor, uno::UNO_QUERY_THROW);
+ xTransact->commit();
+
+ // the temporary file has been written, commit it to the original file
+ Commit();
+ bChanges = true;
+ }
+ }
+ else
+ {
+ // Something not ZIP based: e.g. PDF.
+ std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(GetName(), StreamMode::READ | StreamMode::WRITE));
+ uno::Reference<io::XStream> xStream(new utl::OStreamWrapper(*pStream));
+ if (xSigner->signDocumentContent(uno::Reference<embed::XStorage>(), xStream))
+ bChanges = true;
+ }
+ }
+ }
+ catch ( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "Couldn't use signing functionality!");
+ }
+
+ CloseAndRelease();
+
+ ResetError();
+
+ return bChanges;
+}
+
+
+SignatureState SfxMedium::GetCachedSignatureState_Impl() const
+{
+ return pImpl->m_nSignatureState;
+}
+
+
+void SfxMedium::SetCachedSignatureState_Impl( SignatureState nState )
+{
+ pImpl->m_nSignatureState = nState;
+}
+
+void SfxMedium::SetHasEmbeddedObjects(bool bHasEmbeddedObjects)
+{
+ pImpl->m_bHasEmbeddedObjects = bHasEmbeddedObjects;
+}
+
+bool SfxMedium::HasStorage_Impl() const
+{
+ return pImpl->xStorage.is();
+}
+
+bool SfxMedium::IsOpen() const
+{
+ return pImpl->m_pInStream || pImpl->m_pOutStream || pImpl->xStorage.is();
+}
+
+OUString SfxMedium::CreateTempCopyWithExt( std::u16string_view aURL )
+{
+ OUString aResult;
+
+ if ( !aURL.empty() )
+ {
+ size_t nPrefixLen = aURL.rfind( '.' );
+ std::u16string_view aExt = ( nPrefixLen == std::u16string_view::npos ) ? std::u16string_view() : aURL.substr( nPrefixLen );
+
+ OUString aNewTempFileURL = ::utl::CreateTempURL( u"", true, aExt );
+ if ( !aNewTempFileURL.isEmpty() )
+ {
+ INetURLObject aSource( aURL );
+ INetURLObject aDest( aNewTempFileURL );
+ OUString aFileName = aDest.getName( INetURLObject::LAST_SEGMENT,
+ true,
+ INetURLObject::DecodeMechanism::WithCharset );
+ if ( !aFileName.isEmpty() && aDest.removeSegment() )
+ {
+ try
+ {
+ uno::Reference< css::ucb::XCommandEnvironment > xComEnv;
+ ::ucbhelper::Content aTargetContent( aDest.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
+ ::ucbhelper::Content aSourceContent( aSource.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xComEnv, comphelper::getProcessComponentContext() );
+ aTargetContent.transferContent( aSourceContent,
+ ::ucbhelper::InsertOperation::Copy,
+ aFileName,
+ NameClash::OVERWRITE );
+ aResult = aNewTempFileURL;
+ }
+ catch( const uno::Exception& )
+ {}
+ }
+ }
+ }
+
+ return aResult;
+}
+
+bool SfxMedium::CallApproveHandler(const uno::Reference< task::XInteractionHandler >& xHandler, const uno::Any& rRequest, bool bAllowAbort)
+{
+ bool bResult = false;
+
+ if ( xHandler.is() )
+ {
+ try
+ {
+ uno::Sequence< uno::Reference< task::XInteractionContinuation > > aContinuations( bAllowAbort ? 2 : 1 );
+ auto pContinuations = aContinuations.getArray();
+
+ ::rtl::Reference< ::comphelper::OInteractionApprove > pApprove( new ::comphelper::OInteractionApprove );
+ pContinuations[ 0 ] = pApprove.get();
+
+ if ( bAllowAbort )
+ {
+ ::rtl::Reference< ::comphelper::OInteractionAbort > pAbort( new ::comphelper::OInteractionAbort );
+ pContinuations[ 1 ] = pAbort.get();
+ }
+
+ xHandler->handle(::framework::InteractionRequest::CreateRequest(rRequest, aContinuations));
+ bResult = pApprove->wasSelected();
+ }
+ catch( const Exception& )
+ {
+ }
+ }
+
+ return bResult;
+}
+
+OUString SfxMedium::SwitchDocumentToTempFile()
+{
+ // the method returns empty string in case of failure
+ OUString aResult;
+ OUString aOrigURL = pImpl->m_aLogicName;
+
+ if ( !aOrigURL.isEmpty() )
+ {
+ sal_Int32 nPrefixLen = aOrigURL.lastIndexOf( '.' );
+ std::u16string_view aExt = (nPrefixLen == -1)
+ ? std::u16string_view()
+ : aOrigURL.subView(nPrefixLen);
+ OUString aNewURL = ::utl::CreateTempURL( u"", true, aExt );
+
+ // TODO/LATER: In future the aLogicName should be set to shared folder URL
+ // and a temporary file should be created. Transport_Impl should be impossible then.
+ if ( !aNewURL.isEmpty() )
+ {
+ uno::Reference< embed::XStorage > xStorage = GetStorage();
+ uno::Reference< embed::XOptimizedStorage > xOptStorage( xStorage, uno::UNO_QUERY );
+
+ if ( xOptStorage.is() )
+ {
+ // TODO/LATER: reuse the pImpl->pTempFile if it already exists
+ CanDisposeStorage_Impl( false );
+ Close();
+ SetPhysicalName_Impl( OUString() );
+ SetName( aNewURL );
+
+ // remove the readonly state
+ bool bWasReadonly = false;
+ pImpl->m_nStorOpenMode = SFX_STREAM_READWRITE;
+ const SfxBoolItem* pReadOnlyItem = SfxItemSet::GetItem<SfxBoolItem>(pImpl->m_pSet.get(), SID_DOC_READONLY, false);
+ if ( pReadOnlyItem && pReadOnlyItem->GetValue() )
+ bWasReadonly = true;
+ GetItemSet().ClearItem( SID_DOC_READONLY );
+
+ GetMedium_Impl();
+ LockOrigFileOnDemand( false, false );
+ CreateTempFile();
+ GetMedium_Impl();
+
+ if ( pImpl->xStream.is() )
+ {
+ try
+ {
+ xOptStorage->writeAndAttachToStream( pImpl->xStream );
+ pImpl->xStorage = xStorage;
+ aResult = aNewURL;
+ }
+ catch( const uno::Exception& )
+ {}
+ }
+
+ if (bWasReadonly)
+ {
+ // set the readonly state back
+ pImpl->m_nStorOpenMode = SFX_STREAM_READONLY;
+ GetItemSet().Put(SfxBoolItem(SID_DOC_READONLY, true));
+ }
+
+ if ( aResult.isEmpty() )
+ {
+ Close();
+ SetPhysicalName_Impl( OUString() );
+ SetName( aOrigURL );
+ GetMedium_Impl();
+ pImpl->xStorage = xStorage;
+ }
+ }
+ }
+ }
+
+ return aResult;
+}
+
+bool SfxMedium::SwitchDocumentToFile( const OUString& aURL )
+{
+ // the method is only for storage based documents
+ bool bResult = false;
+ OUString aOrigURL = pImpl->m_aLogicName;
+
+ if ( !aURL.isEmpty() && !aOrigURL.isEmpty() )
+ {
+ uno::Reference< embed::XStorage > xStorage = GetStorage();
+ uno::Reference< embed::XOptimizedStorage > xOptStorage( xStorage, uno::UNO_QUERY );
+
+ // TODO/LATER: reuse the pImpl->pTempFile if it already exists
+ CanDisposeStorage_Impl( false );
+ Close();
+ SetPhysicalName_Impl( OUString() );
+ SetName( aURL );
+
+ // open the temporary file based document
+ GetMedium_Impl();
+ LockOrigFileOnDemand( false, false );
+ CreateTempFile();
+ GetMedium_Impl();
+
+ if ( pImpl->xStream.is() )
+ {
+ try
+ {
+ uno::Reference< io::XTruncate > xTruncate( pImpl->xStream, uno::UNO_QUERY_THROW );
+ xTruncate->truncate();
+ if ( xOptStorage.is() )
+ xOptStorage->writeAndAttachToStream( pImpl->xStream );
+ pImpl->xStorage = xStorage;
+ bResult = true;
+ }
+ catch( const uno::Exception& )
+ {}
+ }
+
+ if ( !bResult )
+ {
+ Close();
+ SetPhysicalName_Impl( OUString() );
+ SetName( aOrigURL );
+ GetMedium_Impl();
+ pImpl->xStorage = xStorage;
+ }
+ }
+
+ return bResult;
+}
+
+void SfxMedium::SetInCheckIn( bool bInCheckIn )
+{
+ pImpl->m_bInCheckIn = bInCheckIn;
+}
+
+bool SfxMedium::IsInCheckIn( ) const
+{
+ return pImpl->m_bInCheckIn;
+}
+
+// should only be called on main thread
+const std::shared_ptr<std::recursive_mutex>& SfxMedium::GetCheckEditableMutex() const
+{
+ return pImpl->m_pCheckEditableWorkerMutex;
+}
+
+// should only be called while holding pImpl->m_pCheckEditableWorkerMutex
+void SfxMedium::SetWorkerReloadEvent(ImplSVEvent* pEvent)
+{
+ pImpl->m_pReloadEvent = pEvent;
+}
+
+// should only be called while holding pImpl->m_pCheckEditableWorkerMutex
+ImplSVEvent* SfxMedium::GetWorkerReloadEvent() const
+{
+ return pImpl->m_pReloadEvent;
+}
+
+// should only be called on main thread
+void SfxMedium::AddToCheckEditableWorkerList()
+{
+ if (!pImpl->m_bNotifyWhenEditable)
+ return;
+
+ CancelCheckEditableEntry();
+
+ if (pImpl->m_pCheckEditableWorkerMutex == nullptr)
+ {
+ pImpl->m_pCheckEditableWorkerMutex = std::make_shared<std::recursive_mutex>();
+ if (pImpl->m_pCheckEditableWorkerMutex == nullptr)
+ return;
+ }
+
+ pImpl->m_pIsDestructed = std::make_shared<bool>(false);
+ if (pImpl->m_pIsDestructed == nullptr)
+ return;
+
+ std::unique_lock<std::mutex> globalLock(g_chkReadOnlyGlobalMutex);
+ if (g_newReadOnlyDocs.find(this) == g_newReadOnlyDocs.end())
+ {
+ bool bAddNewEntry = false;
+ if (!g_bChkReadOnlyTaskRunning)
+ {
+ std::shared_ptr<comphelper::ThreadTaskTag> pTag
+ = comphelper::ThreadPool::createThreadTaskTag();
+ if (pTag != nullptr)
+ {
+ g_bChkReadOnlyTaskRunning = true;
+ bAddNewEntry = true;
+ comphelper::ThreadPool::getSharedOptimalPool().pushTask(
+ std::make_unique<CheckReadOnlyTask>(pTag));
+ }
+ }
+ else
+ bAddNewEntry = true;
+
+ if (bAddNewEntry)
+ {
+ std::shared_ptr<ReadOnlyMediumEntry> newEntry = std::make_shared<ReadOnlyMediumEntry>(
+ pImpl->m_pCheckEditableWorkerMutex, pImpl->m_pIsDestructed);
+
+ if (newEntry != nullptr)
+ {
+ g_newReadOnlyDocs[this] = newEntry;
+ }
+ }
+ }
+}
+
+// should only be called on main thread
+void SfxMedium::CancelCheckEditableEntry(bool bRemoveEvent)
+{
+ if (pImpl->m_pCheckEditableWorkerMutex != nullptr)
+ {
+ std::unique_lock<std::recursive_mutex> lock(*(pImpl->m_pCheckEditableWorkerMutex));
+
+ if (pImpl->m_pReloadEvent != nullptr)
+ {
+ if (bRemoveEvent)
+ Application::RemoveUserEvent(pImpl->m_pReloadEvent);
+ // make sure destructor doesn't use a freed reference
+ // and reset the event so we can check again
+ pImpl->m_pReloadEvent = nullptr;
+ }
+
+ if (pImpl->m_pIsDestructed != nullptr)
+ {
+ *(pImpl->m_pIsDestructed) = true;
+ pImpl->m_pIsDestructed = nullptr;
+ }
+ }
+}
+
+/** callback function, which is triggered by worker thread after successfully checking if the file
+ is editable. Sent from <Application::PostUserEvent(..)>
+ Note: This method has to be run in the main thread.
+*/
+IMPL_STATIC_LINK(SfxMedium, ShowReloadEditableDialog, void*, p, void)
+{
+ SfxMedium* pMed = static_cast<SfxMedium*>(p);
+ if (pMed == nullptr)
+ return;
+
+ pMed->CancelCheckEditableEntry(false);
+
+ uno::Reference<task::XInteractionHandler> xHandler = pMed->GetInteractionHandler();
+ if (xHandler.is())
+ {
+ OUString aDocumentURL
+ = pMed->GetURLObject().GetLastName(INetURLObject::DecodeMechanism::WithCharset);
+ ::rtl::Reference<::ucbhelper::InteractionRequest> xInteractionRequestImpl
+ = new ::ucbhelper::InteractionRequest(uno::Any(document::ReloadEditableRequest(
+ OUString(), uno::Reference<uno::XInterface>(), aDocumentURL)));
+ if (xInteractionRequestImpl != nullptr)
+ {
+ uno::Sequence<uno::Reference<task::XInteractionContinuation>> aContinuations{
+ new ::ucbhelper::InteractionAbort(xInteractionRequestImpl.get()),
+ new ::ucbhelper::InteractionApprove(xInteractionRequestImpl.get())
+ };
+ xInteractionRequestImpl->setContinuations(aContinuations);
+ xHandler->handle(xInteractionRequestImpl);
+ ::rtl::Reference<::ucbhelper::InteractionContinuation> xSelected
+ = xInteractionRequestImpl->getSelection();
+ if (uno::Reference<task::XInteractionApprove>(xSelected.get(), uno::UNO_QUERY).is())
+ {
+ for (SfxViewFrame* pFrame = SfxViewFrame::GetFirst(); pFrame;
+ pFrame = SfxViewFrame::GetNext(*pFrame))
+ {
+ if (pFrame->GetObjectShell()->GetMedium() == pMed)
+ {
+ // special case to ensure view isn't set to read-only in
+ // SfxViewFrame::ExecReload_Impl after reloading
+ pMed->SetOriginallyReadOnly(false);
+ pFrame->GetDispatcher()->Execute(SID_RELOAD);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+bool SfxMedium::CheckCanGetLockfile() const
+{
+#if !HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+ bool bCanReload = true;
+#else
+ bool bCanReload = false;
+ ::svt::DocumentLockFile aLockFile(GetName());
+ LockFileEntry aData;
+ osl::DirectoryItem rItem;
+ auto nError1 = osl::DirectoryItem::get(aLockFile.GetURL(), rItem);
+ if (nError1 == osl::FileBase::E_None)
+ {
+ try
+ {
+ aData = aLockFile.GetLockData();
+ }
+ catch (const io::WrongFormatException&)
+ {
+ // we get empty or corrupt data
+ return false;
+ }
+ catch (const uno::Exception&)
+ {
+ // locked from other app
+ return false;
+ }
+ LockFileEntry aOwnData = svt::LockFileCommon::GenerateOwnEntry();
+ bool bOwnLock
+ = aOwnData[LockFileComponent::SYSUSERNAME] == aData[LockFileComponent::SYSUSERNAME];
+ if (bOwnLock
+ && aOwnData[LockFileComponent::LOCALHOST] == aData[LockFileComponent::LOCALHOST]
+ && aOwnData[LockFileComponent::USERURL] == aData[LockFileComponent::USERURL])
+ {
+ // this is own lock from the same installation, it could remain because of crash
+ bCanReload = true;
+ }
+ }
+ else if (nError1 == osl::FileBase::E_NOENT) // file doesn't exist
+ {
+ try
+ {
+ aLockFile.CreateOwnLockFile();
+ try
+ {
+ // TODO/LATER: A warning could be shown in case the file is not the own one
+ aLockFile.RemoveFile();
+ }
+ catch (const io::WrongFormatException&)
+ {
+ try
+ {
+ // erase the empty or corrupt file
+ aLockFile.RemoveFileDirectly();
+ }
+ catch (const uno::Exception&)
+ {
+ }
+ }
+ bCanReload = true;
+ }
+ catch (const uno::Exception&)
+ {
+ }
+ }
+#endif
+ return bCanReload;
+}
+
+// worker thread method, should only be one thread globally
+void CheckReadOnlyTask::doWork()
+{
+ if (m_xListener == nullptr)
+ return;
+
+ while (true)
+ {
+ std::unique_lock<std::mutex> termLock(m_xListener->mMutex);
+ if (m_xListener->mCond.wait_for(termLock, std::chrono::seconds(60),
+ [this] { return m_xListener->bIsTerminated; }))
+ // signalled, spurious wakeups should not be possible
+ return;
+
+ // must have timed-out
+ termLock.unlock();
+ std::unique_lock<std::mutex> globalLock(g_chkReadOnlyGlobalMutex);
+ for (auto it = g_newReadOnlyDocs.begin(); it != g_newReadOnlyDocs.end(); )
+ {
+ auto [pMed, roEntry] = *it;
+ g_existingReadOnlyDocs[pMed] = roEntry;
+ it = g_newReadOnlyDocs.erase(it);
+ }
+ if (g_existingReadOnlyDocs.size() == 0)
+ {
+ g_bChkReadOnlyTaskRunning = false;
+ return;
+ }
+ globalLock.unlock();
+
+ auto checkForErase = [](SfxMedium* pMed, const std::shared_ptr<ReadOnlyMediumEntry>& roEntry) -> bool
+ {
+ if (pMed == nullptr || roEntry == nullptr || roEntry->_pMutex == nullptr
+ || roEntry->_pIsDestructed == nullptr)
+ return true;
+
+ std::unique_lock<std::recursive_mutex> medLock(*(roEntry->_pMutex));
+ if (*(roEntry->_pIsDestructed) || pMed->GetWorkerReloadEvent() != nullptr)
+ return true;
+
+ osl::File aFile(
+ pMed->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::WithCharset));
+ if (aFile.open(osl_File_OpenFlag_Write) != osl::FileBase::E_None)
+ return false;
+
+ if (!pMed->CheckCanGetLockfile())
+ return false;
+
+ if (aFile.close() != osl::FileBase::E_None)
+ return true;
+
+ // we can load, ask user
+ ImplSVEvent* pEvent = Application::PostUserEvent(
+ LINK(nullptr, SfxMedium, ShowReloadEditableDialog), pMed);
+ pMed->SetWorkerReloadEvent(pEvent);
+ return true;
+ };
+
+ for (auto it = g_existingReadOnlyDocs.begin(); it != g_existingReadOnlyDocs.end(); )
+ {
+ if (checkForErase(it->first, it->second))
+ it = g_existingReadOnlyDocs.erase(it);
+ else
+ ++it;
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/docfilt.cxx b/sfx2/source/doc/docfilt.cxx
new file mode 100644
index 0000000000..a5c29faee0
--- /dev/null
+++ b/sfx2/source/doc/docfilt.cxx
@@ -0,0 +1,200 @@
+/* -*- 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 .
+ */
+
+#ifdef __sun
+#include <ctime>
+#endif
+
+#include <sot/exchange.hxx>
+#include <sot/storage.hxx>
+#include <comphelper/fileformat.h>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/embed/XStorage.hpp>
+#include <sfx2/docfilt.hxx>
+#include <sfx2/fcontnr.hxx>
+#include <sfx2/objsh.hxx>
+#include <utility>
+
+using namespace ::com::sun::star;
+
+SfxFilter::SfxFilter( OUString aProvider, OUString aFilterName ) :
+ maFilterName(std::move(aFilterName)),
+ maProvider(std::move(aProvider)),
+ nFormatType(SfxFilterFlags::NONE),
+ nVersion(0),
+ lFormat(SotClipboardFormatId::NONE),
+ mbEnabled(true)
+{
+}
+
+SfxFilter::SfxFilter( OUString aName,
+ std::u16string_view rWildCard,
+ SfxFilterFlags nType,
+ SotClipboardFormatId lFmt,
+ OUString aTypNm,
+ OUString _aMimeType,
+ OUString aUsrDat,
+ OUString _aServiceName,
+ bool bEnabled ):
+ aWildCard(rWildCard, ';'),
+ aTypeName(std::move(aTypNm)),
+ aUserData(std::move(aUsrDat)),
+ aServiceName(std::move(_aServiceName)),
+ aMimeType(std::move(_aMimeType)),
+ maFilterName(std::move(aName)),
+ aUIName(maFilterName),
+ nFormatType(nType),
+ nVersion(SOFFICE_FILEFORMAT_50),
+ lFormat(lFmt),
+ mbEnabled(bEnabled)
+{
+ const OUString aExts = GetWildcard().getGlob();
+ sal_Int32 nLen{ aExts.getLength() };
+ if (nLen<=0)
+ return;
+
+ // truncate to first empty extension
+ if (aExts[0]==';')
+ {
+ aWildCard.setGlob(u"");
+ return;
+ }
+ const sal_Int32 nIdx{ aExts.indexOf(";;") };
+ if (nIdx>0)
+ nLen = nIdx;
+ else if (aExts[nLen-1]==';')
+ --nLen;
+ if (nLen<aExts.getLength())
+ aWildCard.setGlob(aExts.subView(0, nLen));
+}
+
+SfxFilter::~SfxFilter()
+{
+}
+
+OUString SfxFilter::GetDefaultExtension() const
+{
+ return GetWildcard().getGlob().getToken(0, ';');
+}
+
+
+OUString SfxFilter::GetSuffixes() const
+{
+ OUString aRet = GetWildcard().getGlob();
+ aRet = aRet.replaceAll( "*.", "" );
+ aRet = aRet.replaceAll( ";", "," );
+ return aRet;
+}
+
+std::shared_ptr<const SfxFilter> SfxFilter::GetDefaultFilter( std::u16string_view rName )
+{
+ return SfxFilterContainer::GetDefaultFilter_Impl( rName );
+}
+
+std::shared_ptr<const SfxFilter> SfxFilter::GetDefaultFilterFromFactory( const OUString& rFact )
+{
+ return GetDefaultFilter( SfxObjectShell::GetServiceNameFromFactory( rFact ) );
+}
+
+std::shared_ptr<const SfxFilter> SfxFilter::GetFilterByName( const OUString& rName )
+{
+ SfxFilterMatcher aMatch;
+ return aMatch.GetFilter4FilterName( rName, SfxFilterFlags::NONE, SfxFilterFlags::NONE );
+}
+
+OUString SfxFilter::GetTypeFromStorage( const SotStorage& rStg )
+{
+ const char* pType=nullptr;
+ if ( rStg.IsStream( "WordDocument" ) )
+ {
+ if ( rStg.IsStream( "0Table" ) || rStg.IsStream( "1Table" ) )
+ pType = "writer_MS_Word_97";
+ else
+ pType = "writer_MS_Word_95";
+ }
+ else if ( rStg.IsStream( "Book" ) )
+ {
+ pType = "calc_MS_Excel_95";
+ }
+ else if ( rStg.IsStream( "Workbook" ) )
+ {
+ pType = "calc_MS_Excel_97";
+ }
+ else if ( rStg.IsStream( "PowerPoint Document" ) )
+ {
+ pType = "impress_MS_PowerPoint_97";
+ }
+ else if ( rStg.IsStream( "Equation Native" ) )
+ {
+ pType = "math_MathType_3x";
+ }
+ else
+ {
+ SotClipboardFormatId nClipId = const_cast<SotStorage&>(rStg).GetFormat();
+ if ( nClipId != SotClipboardFormatId::NONE )
+ {
+ std::shared_ptr<const SfxFilter> pFilter = SfxFilterMatcher().GetFilter4ClipBoardId( nClipId );
+ if ( pFilter )
+ return pFilter->GetTypeName();
+ }
+ }
+
+ return pType ? OUString::createFromAscii(pType) : OUString();
+}
+
+OUString SfxFilter::GetTypeFromStorage(
+ const uno::Reference<embed::XStorage>& xStorage )
+{
+ SfxFilterMatcher aMatcher;
+
+ css::uno::Reference< css::beans::XPropertySet > xProps( xStorage, css::uno::UNO_QUERY );
+ if ( xProps.is() )
+ {
+ OUString aMediaType;
+ xProps->getPropertyValue("MediaType") >>= aMediaType;
+ if ( !aMediaType.isEmpty() )
+ {
+ css::datatransfer::DataFlavor aDataFlavor;
+ aDataFlavor.MimeType = aMediaType;
+ SotClipboardFormatId nClipId = SotExchange::GetFormat( aDataFlavor );
+ if ( nClipId != SotClipboardFormatId::NONE )
+ {
+ SfxFilterFlags const nMust = SfxFilterFlags::IMPORT;
+ // template filters shouldn't be detected if not explicitly asked for
+ SfxFilterFlags const nDont = SFX_FILTER_NOTINSTALLED | SfxFilterFlags::TEMPLATEPATH;
+
+ // get filter from storage MediaType
+ std::shared_ptr<const SfxFilter> pFilter = aMatcher.GetFilter4ClipBoardId( nClipId, nMust, nDont );
+ if ( !pFilter )
+ // template filter is asked for , but there isn't one; so at least the "normal" format should be detected
+ // or storage *is* a template, but bTemplate is not set
+ pFilter = aMatcher.GetFilter4ClipBoardId( nClipId );
+
+ if ( pFilter )
+ {
+ return pFilter->GetTypeName();
+ }
+ }
+ }
+ }
+
+ return OUString();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/docinf.cxx b/sfx2/source/doc/docinf.cxx
new file mode 100644
index 0000000000..6369131fe6
--- /dev/null
+++ b/sfx2/source/doc/docinf.cxx
@@ -0,0 +1,324 @@
+/* -*- 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 <sfx2/docinf.hxx>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/XPropertyContainer.hpp>
+#include <com/sun/star/document/XDocumentProperties.hpp>
+#include <com/sun/star/document/XCompatWriterDocProperties.hpp>
+#include <com/sun/star/uno/Exception.hpp>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+#include <comphelper/string.hxx>
+#include <sot/storage.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/dibtools.hxx>
+#include "oleprops.hxx"
+
+
+// stream names
+constexpr OUString STREAM_SUMMARYINFO = u"\005SummaryInformation"_ustr;
+constexpr OUString STREAM_DOCSUMMARYINFO = u"\005DocumentSummaryInformation"_ustr;
+
+// usings
+using namespace ::com::sun::star;
+
+
+namespace sfx2 {
+
+ErrCode LoadOlePropertySet(
+ const uno::Reference< document::XDocumentProperties>& i_xDocProps,
+ SotStorage* i_pStorage )
+{
+ // *** global properties from stream "005SummaryInformation" ***
+
+ // load the property set
+ SfxOlePropertySet aGlobSet;
+ ErrCode nGlobError = aGlobSet.LoadPropertySet(i_pStorage,
+ STREAM_SUMMARYINFO );
+
+ // global section
+ SfxOleSectionRef xGlobSect = aGlobSet.GetSection( SECTION_GLOBAL );
+ if( xGlobSect )
+ {
+ // set supported properties
+ OUString aStrValue;
+ util::DateTime aDateTime;
+
+ if( xGlobSect->GetStringValue( aStrValue, PROPID_TITLE ) )
+ i_xDocProps->setTitle( aStrValue );
+ if( xGlobSect->GetStringValue( aStrValue, PROPID_SUBJECT ) )
+ i_xDocProps->setSubject( aStrValue );
+ if( xGlobSect->GetStringValue( aStrValue, PROPID_KEYWORDS ) ) {
+ i_xDocProps->setKeywords(
+ ::comphelper::string::convertCommaSeparated(aStrValue) );
+ }
+ if( xGlobSect->GetStringValue( aStrValue, PROPID_TEMPLATE ) )
+ i_xDocProps->setTemplateName( aStrValue );
+ if( xGlobSect->GetStringValue( aStrValue, PROPID_COMMENTS ) )
+ i_xDocProps->setDescription( aStrValue );
+
+ util::DateTime aInvalid;
+ if( xGlobSect->GetStringValue( aStrValue, PROPID_AUTHOR) )
+ i_xDocProps->setAuthor( aStrValue );
+ else
+ i_xDocProps->setAuthor( OUString() );
+ if( xGlobSect->GetFileTimeValue( aDateTime, PROPID_CREATED ) )
+ i_xDocProps->setCreationDate( aDateTime );
+ else
+ i_xDocProps->setCreationDate( aInvalid );
+
+ if( xGlobSect->GetStringValue( aStrValue, PROPID_LASTAUTHOR) )
+ i_xDocProps->setModifiedBy( aStrValue );
+ else
+ i_xDocProps->setModifiedBy( OUString() );
+ if( xGlobSect->GetFileTimeValue( aDateTime, PROPID_LASTSAVED ) )
+ i_xDocProps->setModificationDate( aDateTime );
+ else
+ i_xDocProps->setModificationDate( aInvalid );
+
+ i_xDocProps->setPrintedBy( OUString() );
+ if( xGlobSect->GetFileTimeValue( aDateTime, PROPID_LASTPRINTED ) )
+ i_xDocProps->setPrintDate( aDateTime );
+ else
+ i_xDocProps->setPrintDate( aInvalid );
+
+ if( xGlobSect->GetStringValue( aStrValue, PROPID_REVNUMBER ) )
+ {
+ sal_Int16 nRevision = static_cast< sal_Int16 >( aStrValue.toInt32() );
+ if ( nRevision > 0 )
+ i_xDocProps->setEditingCycles( nRevision );
+ }
+
+ if( xGlobSect->GetFileTimeValue( aDateTime, PROPID_EDITTIME )
+ && !(aDateTime.NanoSeconds == 0 && aDateTime.Seconds == 0
+ && aDateTime.Minutes == 0 && aDateTime.Hours == 0
+ && aDateTime.Day == 0 && aDateTime.Month == 0
+ && aDateTime.Year == 0) )
+ {
+ // subtract offset 1601-01-01
+ aDateTime.Year -= 1601;
+ aDateTime.Month -= 1;
+ aDateTime.Day -= 1;
+ try
+ {
+ i_xDocProps->setEditingDuration(
+ aDateTime.Day * 60*60*24 +
+ aDateTime.Hours * 60*60 +
+ aDateTime.Minutes * 60 +
+ aDateTime.Seconds );
+ }
+ catch (const lang::IllegalArgumentException &)
+ {
+ // ignore
+ }
+ }
+ }
+
+ // *** custom properties from stream "005DocumentSummaryInformation" ***
+
+ // load the property set
+ SfxOlePropertySet aDocSet;
+ ErrCode nDocError = aDocSet.LoadPropertySet(i_pStorage,
+ STREAM_DOCSUMMARYINFO );
+
+ // custom properties
+ SfxOleSectionRef xCustomSect = aDocSet.GetSection( SECTION_CUSTOM );
+ if( xCustomSect )
+ {
+ uno::Reference < beans::XPropertyContainer > xUserDefined(
+ i_xDocProps->getUserDefinedProperties(), uno::UNO_SET_THROW);
+ ::std::vector< sal_Int32 > aPropIds;
+ xCustomSect->GetPropertyIds( aPropIds );
+ for( const auto& rPropId : aPropIds )
+ {
+ const OUString aPropName = xCustomSect->GetPropertyName( rPropId );
+ uno::Any aPropValue = xCustomSect->GetAnyValue( rPropId );
+ if( !aPropName.isEmpty() && aPropValue.hasValue() )
+ {
+ try
+ {
+ xUserDefined->addProperty( aPropName,
+ beans::PropertyAttribute::REMOVABLE, aPropValue );
+ }
+ catch (const uno::Exception&)
+ {
+ //ignore
+ }
+ }
+ }
+ }
+
+ uno::Reference< document::XCompatWriterDocProperties > xWriterProps( i_xDocProps, uno::UNO_QUERY );
+ if ( xWriterProps.is() )
+ {
+ SfxOleSectionRef xBuiltin = aDocSet.GetSection( SECTION_BUILTIN );
+ if ( xBuiltin )
+ {
+ try
+ {
+ OUString aStrValue;
+ if ( xBuiltin->GetStringValue( aStrValue, PROPID_MANAGER ) )
+ xWriterProps->setManager( aStrValue );
+ if ( xBuiltin->GetStringValue( aStrValue, PROPID_CATEGORY ) )
+ xWriterProps->setCategory( aStrValue );
+ if ( xBuiltin->GetStringValue( aStrValue, PROPID_COMPANY ) )
+ xWriterProps->setCompany( aStrValue );
+ }
+ catch (const uno::Exception&)
+ {
+ }
+ }
+ }
+
+ // return code
+ return (nGlobError != ERRCODE_NONE) ? nGlobError : nDocError;
+}
+
+bool SaveOlePropertySet(
+ const uno::Reference< document::XDocumentProperties>& i_xDocProps,
+ SotStorage* i_pStorage,
+ const uno::Sequence<sal_Int8> * i_pThumb,
+ const uno::Sequence<sal_Int8> * i_pGuid,
+ const uno::Sequence<sal_Int8> * i_pHyperlinks)
+{
+ // *** global properties into stream "005SummaryInformation" ***
+
+ SfxOlePropertySet aGlobSet;
+
+ // set supported properties
+ SfxOleSection& rGlobSect = aGlobSet.AddSection( SECTION_GLOBAL );
+ rGlobSect.SetStringValue( PROPID_TITLE, i_xDocProps->getTitle() );
+ rGlobSect.SetStringValue( PROPID_SUBJECT, i_xDocProps->getSubject() );
+ const OUString aStr = ::comphelper::string::convertCommaSeparated(
+ i_xDocProps->getKeywords() );
+ rGlobSect.SetStringValue( PROPID_KEYWORDS, aStr );
+ rGlobSect.SetStringValue( PROPID_TEMPLATE, i_xDocProps->getTemplateName() );
+ rGlobSect.SetStringValue( PROPID_COMMENTS, i_xDocProps->getDescription() );
+ rGlobSect.SetStringValue( PROPID_AUTHOR, i_xDocProps->getAuthor() );
+ rGlobSect.SetFileTimeValue(PROPID_CREATED, i_xDocProps->getCreationDate());
+ rGlobSect.SetStringValue( PROPID_LASTAUTHOR, i_xDocProps->getModifiedBy() );
+ rGlobSect.SetFileTimeValue(PROPID_LASTSAVED,
+ i_xDocProps->getModificationDate() );
+ // note: apparently PrintedBy is not supported in file format
+ rGlobSect.SetFileTimeValue(PROPID_LASTPRINTED, i_xDocProps->getPrintDate());
+
+ sal_Int32 dur = i_xDocProps->getEditingDuration();
+ util::DateTime aEditTime;
+ // add offset 1601-01-01
+ aEditTime.Year = 1601;
+ aEditTime.Month = 1;
+ aEditTime.Day = 1;
+ aEditTime.Hours = static_cast<sal_Int16>(dur / 3600);
+ aEditTime.Minutes = static_cast<sal_Int16>((dur % 3600) / 60);
+ aEditTime.Seconds = static_cast<sal_Int16>(dur % 60);
+ rGlobSect.SetFileTimeValue( PROPID_EDITTIME, aEditTime );
+
+ rGlobSect.SetStringValue( PROPID_REVNUMBER,
+ OUString::number( i_xDocProps->getEditingCycles() ) );
+ if ( i_pThumb && i_pThumb->hasElements() )
+ rGlobSect.SetThumbnailValue( PROPID_THUMBNAIL, *i_pThumb );
+
+ // save the property set
+ ErrCode nGlobError = aGlobSet.SavePropertySet(i_pStorage,
+ STREAM_SUMMARYINFO);
+
+ // *** custom properties into stream "005DocumentSummaryInformation" ***
+
+ SfxOlePropertySet aDocSet;
+
+ // set builtin properties
+ aDocSet.AddSection( SECTION_BUILTIN );
+
+ // set custom properties
+ SfxOleSection& rCustomSect = aDocSet.AddSection( SECTION_CUSTOM );
+
+ // write GUID
+ if (i_pGuid) {
+ const sal_Int32 nPropId = rCustomSect.GetFreePropertyId();
+ rCustomSect.SetBlobValue( nPropId, *i_pGuid );
+ rCustomSect.SetPropertyName( nPropId,
+ "_PID_GUID" );
+ }
+
+ // write hyperlinks
+ if (i_pHyperlinks) {
+ const sal_Int32 nPropId = rCustomSect.GetFreePropertyId();
+ rCustomSect.SetBlobValue( nPropId, *i_pHyperlinks );
+ rCustomSect.SetPropertyName( nPropId,
+ "_PID_HLINKS" );
+ }
+
+ uno::Reference<beans::XPropertySet> xUserDefinedProps(
+ i_xDocProps->getUserDefinedProperties(), uno::UNO_QUERY_THROW);
+ uno::Reference<beans::XPropertySetInfo> xPropInfo =
+ xUserDefinedProps->getPropertySetInfo();
+ DBG_ASSERT(xPropInfo.is(), "UserDefinedProperties Info is null");
+ const uno::Sequence<beans::Property> props = xPropInfo->getProperties();
+ for (const auto& rProp : props)
+ {
+ try
+ {
+ // skip transient properties
+ if (~rProp.Attributes & beans::PropertyAttribute::TRANSIENT)
+ {
+ const OUString name = rProp.Name;
+ const sal_Int32 nPropId = rCustomSect.GetFreePropertyId();
+ if (rCustomSect.SetAnyValue( nPropId,
+ xUserDefinedProps->getPropertyValue(name))) {
+ rCustomSect.SetPropertyName( nPropId, name );
+ }
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ // may happen with concurrent modification...
+ SAL_INFO("sfx", "SavePropertySet: exception");
+ }
+ }
+
+ // save the property set
+ ErrCode nDocError = aDocSet.SavePropertySet(i_pStorage,
+ STREAM_DOCSUMMARYINFO );
+
+ // return code
+ return (nGlobError == ERRCODE_NONE) && (nDocError == ERRCODE_NONE);
+}
+
+uno::Sequence<sal_Int8> convertMetaFile(GDIMetaFile const * i_pThumb)
+{
+ if (i_pThumb) {
+ BitmapEx aBitmap;
+ SvMemoryStream aStream;
+ if (i_pThumb->CreateThumbnail(aBitmap))
+ {
+ WriteDIB(aBitmap.GetBitmap(), aStream, false, false);
+ return uno::Sequence<sal_Int8>(static_cast< const sal_Int8* >( aStream.GetData() ), aStream.TellEnd());
+ }
+ }
+ return uno::Sequence<sal_Int8>();
+}
+
+} // namespace sfx2
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/docinsert.cxx b/sfx2/source/doc/docinsert.cxx
new file mode 100644
index 0000000000..52c55b103f
--- /dev/null
+++ b/sfx2/source/doc/docinsert.cxx
@@ -0,0 +1,293 @@
+/* -*- 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 <sfx2/app.hxx>
+#include <sfx2/docinsert.hxx>
+#include <sfx2/docfile.hxx>
+#include <sfx2/fcontnr.hxx>
+#include <sfx2/filedlghelper.hxx>
+#include <appopen.hxx>
+#include <openflag.hxx>
+#include <sfx2/passwd.hxx>
+
+#include <sfx2/sfxsids.hrc>
+#include <com/sun/star/ui/dialogs/ControlActions.hpp>
+#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+#include <com/sun/star/ui/dialogs/XFilePicker3.hpp>
+#include <com/sun/star/ui/dialogs/XFilePickerControlAccess.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <tools/urlobj.hxx>
+#include <svl/itemset.hxx>
+#include <svl/eitem.hxx>
+#include <svl/intitem.hxx>
+#include <svl/stritem.hxx>
+#include <memory>
+#include <utility>
+#include <comphelper/diagnose_ex.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::uno;
+
+namespace
+{
+
+FileDialogFlags lcl_map_mode_to_flags(const sfx2::DocumentInserter::Mode mode)
+{
+ FileDialogFlags f {FileDialogFlags::NONE};
+ switch (mode)
+ {
+ case sfx2::DocumentInserter::Mode::Insert:
+ f = FileDialogFlags::Insert;
+ break;
+ case sfx2::DocumentInserter::Mode::InsertMulti:
+ f = FileDialogFlags::Insert|FileDialogFlags::MultiSelection;
+ break;
+ case sfx2::DocumentInserter::Mode::Compare:
+ f = FileDialogFlags::InsertCompare;
+ break;
+ case sfx2::DocumentInserter::Mode::Merge:
+ f = FileDialogFlags::InsertMerge;
+ break;
+ }
+ return f;
+}
+
+}
+
+namespace sfx2 {
+
+DocumentInserter::DocumentInserter(weld::Window* pParent, OUString sFactory, const Mode mode)
+ : m_pParent ( pParent )
+ , m_sDocFactory (std::move( sFactory ))
+ , m_nDlgFlags ( lcl_map_mode_to_flags(mode) )
+ , m_nError ( ERRCODE_NONE )
+{
+}
+
+DocumentInserter::~DocumentInserter()
+{
+}
+
+void DocumentInserter::StartExecuteModal( const Link<sfx2::FileDialogHelper*,void>& _rDialogClosedLink )
+{
+ m_aDialogClosedLink = _rDialogClosedLink;
+ m_nError = ERRCODE_NONE;
+ if ( !m_pFileDlg )
+ {
+ m_pFileDlg.reset( new FileDialogHelper(
+ ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE,
+ m_nDlgFlags, m_sDocFactory, SfxFilterFlags::NONE, SfxFilterFlags::NONE, m_pParent ) );
+ }
+ m_pFileDlg->SetContext(FileDialogHelper::InsertDoc);
+ m_pFileDlg->StartExecuteModal( LINK( this, DocumentInserter, DialogClosedHdl ) );
+}
+
+std::unique_ptr<SfxMedium> DocumentInserter::CreateMedium(char const*const pFallbackHack)
+{
+ std::unique_ptr<SfxMedium> pMedium;
+ if (!m_nError && m_xItemSet && !m_pURLList.empty())
+ {
+ DBG_ASSERT( m_pURLList.size() == 1, "DocumentInserter::CreateMedium(): invalid URL list count" );
+ pMedium.reset(new SfxMedium(
+ m_pURLList[0], SFX_STREAM_READONLY,
+ SfxGetpApp()->GetFilterMatcher().GetFilter4FilterName( m_sFilter ), m_xItemSet ));
+ pMedium->UseInteractionHandler( true );
+ std::optional<SfxFilterMatcher> pMatcher;
+ if ( !m_sDocFactory.isEmpty() )
+ pMatcher.emplace(m_sDocFactory);
+ else
+ pMatcher.emplace();
+
+ std::shared_ptr<const SfxFilter> pFilter;
+ ErrCode nError = pMatcher->DetectFilter( *pMedium, pFilter );
+ // tdf#101813 hack: check again if it's a global document
+ if (ERRCODE_NONE != nError && pFallbackHack)
+ {
+ pMatcher.emplace(OUString::createFromAscii(pFallbackHack));
+ nError = pMatcher->DetectFilter( *pMedium, pFilter );
+ }
+ if ( nError == ERRCODE_NONE && pFilter )
+ pMedium->SetFilter( pFilter );
+ else
+ pMedium.reset();
+
+ if ( pMedium && CheckPasswd_Impl( nullptr, pMedium.get() ) == ERRCODE_ABORT )
+ pMedium.reset();
+ }
+
+ return pMedium;
+}
+
+SfxMediumList DocumentInserter::CreateMediumList()
+{
+ SfxMediumList aMediumList;
+ if (!m_nError && m_xItemSet && !m_pURLList.empty())
+ {
+ for (auto const& url : m_pURLList)
+ {
+ std::unique_ptr<SfxMedium> pMedium(new SfxMedium(
+ url, SFX_STREAM_READONLY,
+ SfxGetpApp()->GetFilterMatcher().GetFilter4FilterName( m_sFilter ), m_xItemSet ));
+
+ pMedium->UseInteractionHandler( true );
+
+ SfxFilterMatcher aMatcher( m_sDocFactory );
+ std::shared_ptr<const SfxFilter> pFilter;
+ ErrCode nError = aMatcher.DetectFilter( *pMedium, pFilter );
+ if ( nError == ERRCODE_NONE && pFilter )
+ pMedium->SetFilter( pFilter );
+ else
+ pMedium.reset();
+
+ if( pMedium && CheckPasswd_Impl( nullptr, pMedium.get() ) != ERRCODE_ABORT )
+ aMediumList.push_back( std::move(pMedium) );
+ }
+ }
+
+ return aMediumList;
+}
+
+static void impl_FillURLList( sfx2::FileDialogHelper const * _pFileDlg, std::vector<OUString>& _rpURLList )
+{
+ DBG_ASSERT( _pFileDlg, "DocumentInserter::fillURLList(): invalid file dialog" );
+
+ const Sequence < OUString > aPathSeq = _pFileDlg->GetSelectedFiles();
+
+ if ( aPathSeq.hasElements() )
+ {
+ _rpURLList.clear();
+
+ std::transform(aPathSeq.begin(), aPathSeq.end(), std::back_inserter(_rpURLList),
+ [](const OUString& rPath) -> OUString {
+ INetURLObject aPathObj( rPath );
+ return aPathObj.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+ });
+ }
+}
+
+IMPL_LINK_NOARG(DocumentInserter, DialogClosedHdl, sfx2::FileDialogHelper*, void)
+{
+ DBG_ASSERT( m_pFileDlg, "DocumentInserter::DialogClosedHdl(): no file dialog" );
+
+ m_nError = m_pFileDlg->GetError();
+ if ( ERRCODE_NONE == m_nError )
+ impl_FillURLList( m_pFileDlg.get(), m_pURLList );
+
+ Reference < XFilePicker3 > xFP = m_pFileDlg->GetFilePicker();
+ Reference < XFilePickerControlAccess > xCtrlAccess( xFP, UNO_QUERY );
+ if ( xCtrlAccess.is() )
+ {
+ // always create a new itemset
+ m_xItemSet = std::make_shared<SfxAllItemSet>( SfxGetpApp()->GetPool() );
+
+ short nDlgType = m_pFileDlg->GetDialogType();
+ bool bHasPassword = (
+ TemplateDescription::FILESAVE_AUTOEXTENSION_PASSWORD == nDlgType
+ || TemplateDescription::FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS == nDlgType );
+
+ // check, whether or not we have to display a password box
+ if ( bHasPassword && m_pFileDlg->IsPasswordEnabled() )
+ {
+ try
+ {
+ Any aValue = xCtrlAccess->getValue( ExtendedFilePickerElementIds::CHECKBOX_PASSWORD, 0 );
+ bool bPassWord = false;
+ if ( ( aValue >>= bPassWord ) && bPassWord )
+ {
+ // ask for the password
+ SfxPasswordDialog aPasswordDlg(m_pParent);
+ aPasswordDlg.ShowExtras( SfxShowExtras::CONFIRM );
+ short nRet = aPasswordDlg.run();
+ if ( RET_OK == nRet )
+ {
+ m_xItemSet->Put( SfxStringItem( SID_PASSWORD, aPasswordDlg.GetPassword() ) );
+ }
+ else
+ {
+ m_xItemSet.reset();
+ return;
+ }
+ }
+ }
+ catch( const IllegalArgumentException& ){}
+ }
+
+ if ( m_nDlgFlags & FileDialogFlags::Export )
+ {
+ try
+ {
+ Any aValue = xCtrlAccess->getValue( ExtendedFilePickerElementIds::CHECKBOX_SELECTION, 0 );
+ bool bSelection = false;
+ if ( aValue >>= bSelection )
+ m_xItemSet->Put( SfxBoolItem( SID_SELECTION, bSelection ) );
+ }
+ catch( const IllegalArgumentException& )
+ {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "FileDialogHelper_Impl::execute: caught an IllegalArgumentException!" );
+ }
+ }
+
+
+ // set the read-only flag. When inserting a file, this flag is always set
+ if ( m_nDlgFlags & FileDialogFlags::Insert )
+ m_xItemSet->Put( SfxBoolItem( SID_DOC_READONLY, true ) );
+ else
+ {
+ if ( TemplateDescription::FILEOPEN_READONLY_VERSION == nDlgType )
+ {
+ try
+ {
+ Any aValue = xCtrlAccess->getValue( ExtendedFilePickerElementIds::CHECKBOX_READONLY, 0 );
+ bool bReadOnly = false;
+ if ( ( aValue >>= bReadOnly ) && bReadOnly )
+ m_xItemSet->Put( SfxBoolItem( SID_DOC_READONLY, bReadOnly ) );
+ }
+ catch( const IllegalArgumentException& )
+ {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "FileDialogHelper_Impl::execute: caught an IllegalArgumentException!" );
+ }
+ }
+ }
+
+ if ( TemplateDescription::FILEOPEN_READONLY_VERSION == nDlgType )
+ {
+ try
+ {
+ Any aValue = xCtrlAccess->getValue( ExtendedFilePickerElementIds::LISTBOX_VERSION,
+ ControlActions::GET_SELECTED_ITEM_INDEX );
+ sal_Int32 nVersion = 0;
+ if ( ( aValue >>= nVersion ) && nVersion > 0 )
+ // open a special version; 0 == current version
+ m_xItemSet->Put( SfxInt16Item( SID_VERSION, static_cast<short>(nVersion) ) );
+ }
+ catch( const IllegalArgumentException& ){}
+ }
+ }
+
+ m_sFilter = m_pFileDlg->GetRealFilter();
+
+ m_aDialogClosedLink.Call( m_pFileDlg.get() );
+}
+
+} // namespace sfx2
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/docmacromode.cxx b/sfx2/source/doc/docmacromode.cxx
new file mode 100644
index 0000000000..4e9311593a
--- /dev/null
+++ b/sfx2/source/doc/docmacromode.cxx
@@ -0,0 +1,469 @@
+/* -*- 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 <config_features.h>
+
+#include <sfx2/docmacromode.hxx>
+#include <sfx2/signaturestate.hxx>
+#include <sfx2/docfile.hxx>
+
+#include <com/sun/star/document/MacroExecMode.hpp>
+#include <com/sun/star/task/ErrorCodeRequest.hpp>
+#include <com/sun/star/task/DocumentMacroConfirmationRequest.hpp>
+#include <com/sun/star/security/DocumentDigitalSignatures.hpp>
+#include <com/sun/star/script/XLibraryContainer.hpp>
+#include <com/sun/star/document/XEmbeddedScripts.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <framework/interaction.hxx>
+#include <osl/file.hxx>
+#include <unotools/securityoptions.hxx>
+#include <svtools/sfxecode.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/urlobj.hxx>
+
+#if defined(_WIN32)
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <systools/win32/comtools.hxx>
+#include <urlmon.h>
+#endif
+
+namespace sfx2
+{
+
+
+ using ::com::sun::star::uno::Reference;
+ using ::com::sun::star::task::XInteractionHandler;
+ using ::com::sun::star::uno::Any;
+ using ::com::sun::star::uno::Sequence;
+ using ::com::sun::star::task::DocumentMacroConfirmationRequest;
+ using ::com::sun::star::uno::Exception;
+ using ::com::sun::star::security::DocumentDigitalSignatures;
+ using ::com::sun::star::security::XDocumentDigitalSignatures;
+ using ::com::sun::star::embed::XStorage;
+ using ::com::sun::star::document::XEmbeddedScripts;
+ using ::com::sun::star::script::XLibraryContainer;
+ using ::com::sun::star::container::XNameAccess;
+ using ::com::sun::star::uno::UNO_QUERY_THROW;
+
+ namespace MacroExecMode = ::com::sun::star::document::MacroExecMode;
+
+
+ //= DocumentMacroMode_Data
+
+ struct DocumentMacroMode_Data
+ {
+ IMacroDocumentAccess& m_rDocumentAccess;
+ bool m_bHasUnsignedContentError;
+
+ explicit DocumentMacroMode_Data( IMacroDocumentAccess& rDocumentAccess )
+ :m_rDocumentAccess( rDocumentAccess )
+ ,m_bHasUnsignedContentError( false )
+ {
+ }
+ };
+
+ namespace
+ {
+ bool lcl_showMacroWarning( const Reference< XInteractionHandler >& rxHandler,
+ const OUString& rDocumentLocation )
+ {
+ DocumentMacroConfirmationRequest aRequest;
+ aRequest.DocumentURL = rDocumentLocation;
+ return SfxMedium::CallApproveHandler( rxHandler, Any( aRequest ), true );
+ }
+ }
+
+ //= DocumentMacroMode
+ DocumentMacroMode::DocumentMacroMode( IMacroDocumentAccess& rDocumentAccess )
+ :m_xData( std::make_shared<DocumentMacroMode_Data>( rDocumentAccess ) )
+ {
+ }
+
+ bool DocumentMacroMode::allowMacroExecution()
+ {
+ m_xData->m_rDocumentAccess.setCurrentMacroExecMode( MacroExecMode::ALWAYS_EXECUTE_NO_WARN );
+ return true;
+ }
+
+ bool DocumentMacroMode::disallowMacroExecution()
+ {
+ m_xData->m_rDocumentAccess.setCurrentMacroExecMode( MacroExecMode::NEVER_EXECUTE );
+ return false;
+ }
+
+ bool DocumentMacroMode::adjustMacroMode( const Reference< XInteractionHandler >& rxInteraction, bool bHasValidContentSignature )
+ {
+ if ( SvtSecurityOptions::IsMacroDisabled() )
+ {
+ // no macro should be executed at all
+ return disallowMacroExecution();
+ }
+
+ // get setting from configuration if required
+ enum AutoConfirmation
+ {
+ eNoAutoConfirm,
+ eAutoConfirmApprove,
+ eAutoConfirmReject
+ };
+ AutoConfirmation eAutoConfirm( eNoAutoConfirm );
+
+ sal_Int16 nMacroExecutionMode = m_xData->m_rDocumentAccess.getCurrentMacroExecMode();
+ if ( ( nMacroExecutionMode == MacroExecMode::USE_CONFIG )
+ || ( nMacroExecutionMode == MacroExecMode::USE_CONFIG_REJECT_CONFIRMATION )
+ || ( nMacroExecutionMode == MacroExecMode::USE_CONFIG_APPROVE_CONFIRMATION )
+ )
+ {
+ // check confirm first, as nMacroExecutionMode is always overwritten by the GetMacroSecurityLevel() switch
+ if (nMacroExecutionMode == MacroExecMode::USE_CONFIG_REJECT_CONFIRMATION)
+ eAutoConfirm = eAutoConfirmReject;
+ else if (nMacroExecutionMode == MacroExecMode::USE_CONFIG_APPROVE_CONFIRMATION)
+ eAutoConfirm = eAutoConfirmApprove;
+
+ switch ( SvtSecurityOptions::GetMacroSecurityLevel() )
+ {
+ case 3: // "Very high"
+ nMacroExecutionMode = MacroExecMode::FROM_LIST_NO_WARN;
+ break;
+ case 2: // "High"
+ nMacroExecutionMode = MacroExecMode::FROM_LIST_AND_SIGNED_WARN;
+ break;
+ case 1: // "Medium"
+ nMacroExecutionMode = MacroExecMode::ALWAYS_EXECUTE;
+ break;
+ case 0: // "Low"
+ nMacroExecutionMode = MacroExecMode::ALWAYS_EXECUTE_NO_WARN;
+ break;
+ default:
+ OSL_FAIL( "DocumentMacroMode::adjustMacroMode: unexpected macro security level!" );
+ nMacroExecutionMode = MacroExecMode::NEVER_EXECUTE;
+ }
+ }
+
+ if ( nMacroExecutionMode == MacroExecMode::NEVER_EXECUTE )
+ return disallowMacroExecution();
+
+ if ( nMacroExecutionMode == MacroExecMode::ALWAYS_EXECUTE_NO_WARN )
+ return allowMacroExecution();
+
+ SignatureState nSignatureState = SignatureState::UNKNOWN;
+ const OUString sURL(m_xData->m_rDocumentAccess.getDocumentLocation());
+ try
+ {
+ // get document location from medium name and check whether it is a trusted one
+ // the service is created without document version, since it is not of interest here
+ Reference< XDocumentDigitalSignatures > xSignatures(DocumentDigitalSignatures::createDefault(::comphelper::getProcessComponentContext()));
+ INetURLObject aURLReferer(sURL);
+
+ OUString aLocation = aURLReferer.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ if ( !aLocation.isEmpty() && xSignatures->isLocationTrusted( aLocation ) )
+ {
+ return allowMacroExecution();
+ }
+
+ // at this point it is clear that the document is not in the secure location
+ if ( nMacroExecutionMode == MacroExecMode::FROM_LIST_NO_WARN )
+ {
+ return disallowMacroExecution();
+ }
+
+ // check whether the document is signed with trusted certificate
+ if ( nMacroExecutionMode != MacroExecMode::FROM_LIST )
+ {
+ nSignatureState = m_xData->m_rDocumentAccess.getScriptingSignatureState();
+
+ if (!bHasValidContentSignature
+ && (nMacroExecutionMode == MacroExecMode::FROM_LIST_AND_SIGNED_NO_WARN
+ || nMacroExecutionMode == MacroExecMode::FROM_LIST_AND_SIGNED_WARN)
+ && m_xData->m_rDocumentAccess.macroCallsSeenWhileLoading())
+ {
+ // When macros are required to be signed, and the document has events which call
+ // macros, the document content needs to be signed, too. Do it here, and avoid
+ // possible UI asking to always trust certificates, after which the user's choice
+ // to allow macros would be ignored anyway.
+ m_xData->m_bHasUnsignedContentError
+ = nSignatureState == SignatureState::OK
+ || nSignatureState == SignatureState::NOTVALIDATED;
+ return disallowMacroExecution();
+ }
+
+ // At this point, the possible values of nMacroExecutionMode are: ALWAYS_EXECUTE,
+ // FROM_LIST_AND_SIGNED_WARN (the default), FROM_LIST_AND_SIGNED_NO_WARN.
+ // ALWAYS_EXECUTE corresponds to the Medium security level; it should ask for
+ // confirmation when macros are unsigned or untrusted. FROM_LIST_AND_SIGNED_NO_WARN
+ // should not ask any confirmations. FROM_LIST_AND_SIGNED_WARN should only allow
+ // trusted signed macros at this point; so it may only ask for confirmation to add
+ // certificates to trusted, and shouldn't show UI when trusted list is read-only.
+ const bool bAllowUI
+ = nMacroExecutionMode != MacroExecMode::FROM_LIST_AND_SIGNED_NO_WARN
+ && eAutoConfirm == eNoAutoConfirm
+ && (nMacroExecutionMode == MacroExecMode::ALWAYS_EXECUTE
+ || !SvtSecurityOptions::IsReadOnly(
+ SvtSecurityOptions::EOption::MacroTrustedAuthors));
+ const bool bHasTrustedMacroSignature = m_xData->m_rDocumentAccess.hasTrustedScriptingSignature(bAllowUI ? rxInteraction : nullptr);
+
+ if (bHasTrustedMacroSignature)
+ {
+ // there is trusted macro signature, allow macro execution
+ return allowMacroExecution();
+ }
+ else if ( nSignatureState == SignatureState::OK
+ || nSignatureState == SignatureState::NOTVALIDATED )
+ {
+ // there is valid signature, but it is not from the trusted author
+ if (eAutoConfirm == eAutoConfirmApprove
+ && nMacroExecutionMode == MacroExecMode::ALWAYS_EXECUTE)
+ {
+ // For ALWAYS_EXECUTE + eAutoConfirmApprove (USE_CONFIG_APPROVE_CONFIRMATION
+ // in Medium security mode), do not approve it right here; let Security Zone
+ // check below do its job first.
+ }
+ else
+ {
+ // All other cases of valid but untrusted signatures should result in denied
+ // macros here. This includes explicit reject from user in the UI in cases
+ // of FROM_LIST_AND_SIGNED_WARN and ALWAYS_EXECUTE
+ return disallowMacroExecution();
+ }
+ }
+ // Other values of nSignatureState would result in either rejected macros
+ // (FROM_LIST_AND_SIGNED_*), or a confirmation.
+ }
+ }
+ catch ( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("sfx.doc");
+ }
+
+ // at this point it is clear that the document is neither in secure location nor signed with trusted certificate
+ if ((nMacroExecutionMode == MacroExecMode::FROM_LIST_AND_SIGNED_NO_WARN)
+ || (nMacroExecutionMode == MacroExecMode::FROM_LIST_AND_SIGNED_WARN))
+ {
+ return disallowMacroExecution();
+ }
+
+#if defined(_WIN32)
+ // Windows specific: try to decide macros loading depending on Windows Security Zones
+ // (is the file local, or it was downloaded from internet, etc?)
+ OUString sFilePath;
+ osl::FileBase::getSystemPathFromFileURL(sURL, sFilePath);
+ sal::systools::COMReference<IZoneIdentifier> pZoneId;
+ pZoneId.CoCreateInstance(CLSID_PersistentZoneIdentifier);
+ sal::systools::COMReference<IPersistFile> pPersist(pZoneId, sal::systools::COM_QUERY);
+ DWORD dwZone;
+ if (!pPersist || !SUCCEEDED(pPersist->Load(o3tl::toW(sFilePath.getStr()), STGM_READ)) ||
+ !SUCCEEDED(pZoneId->GetId(&dwZone)))
+ {
+ // no Security Zone info found -> assume a local file, not
+ // from the internet
+ dwZone = URLZONE_LOCAL_MACHINE;
+ }
+
+ // determine action from zone and settings
+ sal_Int32 nAction;
+ switch (dwZone) {
+ case URLZONE_LOCAL_MACHINE:
+ nAction = officecfg::Office::Common::Security::Scripting::WindowsSecurityZone::ZoneLocal::get();
+ break;
+ case URLZONE_INTRANET:
+ nAction = officecfg::Office::Common::Security::Scripting::WindowsSecurityZone::ZoneIntranet::get();
+ break;
+ case URLZONE_TRUSTED:
+ nAction = officecfg::Office::Common::Security::Scripting::WindowsSecurityZone::ZoneTrusted::get();
+ break;
+ case URLZONE_INTERNET:
+ nAction = officecfg::Office::Common::Security::Scripting::WindowsSecurityZone::ZoneInternet::get();
+ break;
+ case URLZONE_UNTRUSTED:
+ nAction = officecfg::Office::Common::Security::Scripting::WindowsSecurityZone::ZoneUntrusted::get();
+ break;
+ default:
+ // unknown zone, let's ask the user
+ nAction = 0;
+ break;
+ }
+
+ // act on result
+ switch (nAction)
+ {
+ case 0: // Ask
+ break;
+ case 1: // Allow
+ if (nSignatureState != SignatureState::BROKEN
+ && nSignatureState != SignatureState::INVALID)
+ return allowMacroExecution();
+ break;
+ case 2: // Deny
+ return disallowMacroExecution();
+ }
+#endif
+ // confirmation is required
+ bool bSecure = false;
+
+ if ( eAutoConfirm == eNoAutoConfirm )
+ {
+ OUString sReferrer(sURL);
+ osl::FileBase::getSystemPathFromFileURL(sReferrer, sReferrer);
+
+ bSecure = lcl_showMacroWarning( rxInteraction, sReferrer );
+ }
+ else
+ bSecure = ( eAutoConfirm == eAutoConfirmApprove );
+
+ return ( bSecure ? allowMacroExecution() : disallowMacroExecution() );
+ }
+
+
+ bool DocumentMacroMode::isMacroExecutionDisallowed() const
+ {
+ return m_xData->m_rDocumentAccess.getCurrentMacroExecMode() == MacroExecMode::NEVER_EXECUTE;
+ }
+
+
+ bool DocumentMacroMode::containerHasBasicMacros( const Reference< XLibraryContainer >& xContainer )
+ {
+ bool bHasMacroLib = false;
+ try
+ {
+ if ( xContainer.is() )
+ {
+ // a library container exists; check if it's empty
+
+ // if there are libraries except the "Standard" library
+ // we assume that they are not empty (because they have been created by the user)
+ if ( !xContainer->hasElements() )
+ bHasMacroLib = false;
+ else
+ {
+ static constexpr OUStringLiteral aStdLibName( u"Standard" );
+ static constexpr OUStringLiteral aVBAProject( u"VBAProject" );
+ const Sequence< OUString > aElements = xContainer->getElementNames();
+ for( const OUString& aElement : aElements )
+ {
+ if( aElement == aStdLibName || aElement == aVBAProject )
+ {
+ Reference < XNameAccess > xLib;
+ Any aAny = xContainer->getByName( aElement );
+ aAny >>= xLib;
+ if ( xLib.is() && xLib->hasElements() )
+ return true;
+ }
+ else
+ return true;
+ }
+ }
+ }
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("sfx.doc");
+ }
+ return bHasMacroLib;
+ }
+
+
+ bool DocumentMacroMode::hasMacroLibrary() const
+ {
+ bool bHasMacroLib = false;
+#if HAVE_FEATURE_SCRIPTING
+ try
+ {
+ Reference< XEmbeddedScripts > xScripts( m_xData->m_rDocumentAccess.getEmbeddedDocumentScripts() );
+ Reference< XLibraryContainer > xContainer;
+ if ( xScripts.is() )
+ xContainer.set( xScripts->getBasicLibraries(), UNO_QUERY_THROW );
+ bHasMacroLib = containerHasBasicMacros( xContainer );
+
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("sfx.doc");
+ }
+#endif
+ return bHasMacroLib;
+ }
+
+ bool DocumentMacroMode::hasUnsignedContentError() const
+ {
+ return m_xData->m_bHasUnsignedContentError;
+ }
+
+
+ bool DocumentMacroMode::storageHasMacros( const Reference< XStorage >& rxStorage )
+ {
+ bool bHasMacros = false;
+ if ( rxStorage.is() )
+ {
+ try
+ {
+ static constexpr OUString s_sBasicStorageName( u"Basic"_ustr );
+ static constexpr OUString s_sScriptsStorageName( u"Scripts"_ustr );
+
+ bHasMacros =( ( rxStorage->hasByName( s_sBasicStorageName )
+ && rxStorage->isStorageElement( s_sBasicStorageName )
+ )
+ || ( rxStorage->hasByName( s_sScriptsStorageName )
+ && rxStorage->isStorageElement( s_sScriptsStorageName )
+ )
+ );
+ }
+ catch ( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("sfx.doc");
+ }
+ }
+ return bHasMacros;
+ }
+
+ bool DocumentMacroMode::hasMacros() const
+ {
+ return m_xData->m_rDocumentAccess.documentStorageHasMacros() || hasMacroLibrary() || m_xData->m_rDocumentAccess.macroCallsSeenWhileLoading();
+ }
+
+ bool DocumentMacroMode::checkMacrosOnLoading( const Reference< XInteractionHandler >& rxInteraction, bool bHasValidContentSignature, bool bHasMacros )
+ {
+ bool bAllow = false;
+ if ( SvtSecurityOptions::IsMacroDisabled() )
+ {
+ // no macro should be executed at all
+ bAllow = disallowMacroExecution();
+ }
+ else
+ {
+ if (bHasMacros)
+ {
+ bAllow = adjustMacroMode( rxInteraction, bHasValidContentSignature );
+ }
+ else if ( !isMacroExecutionDisallowed() )
+ {
+ // if macros will be added by the user later, the security check is obsolete
+ bAllow = allowMacroExecution();
+ }
+ }
+ return bAllow;
+ }
+
+
+} // namespace sfx2
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/docstoragemodifylistener.cxx b/sfx2/source/doc/docstoragemodifylistener.cxx
new file mode 100644
index 0000000000..5c68c2bb87
--- /dev/null
+++ b/sfx2/source/doc/docstoragemodifylistener.cxx
@@ -0,0 +1,73 @@
+/* -*- 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 <sfx2/docstoragemodifylistener.hxx>
+#include <comphelper/solarmutex.hxx>
+
+
+namespace sfx2
+{
+
+
+ using ::com::sun::star::lang::EventObject;
+
+
+ //=
+
+
+ DocumentStorageModifyListener::DocumentStorageModifyListener( IModifiableDocument& _rDocument, comphelper::SolarMutex& _rMutex )
+ :m_pDocument( &_rDocument )
+ ,m_rMutex( _rMutex )
+ {
+ }
+
+
+ DocumentStorageModifyListener::~DocumentStorageModifyListener()
+ {
+ }
+
+
+ void DocumentStorageModifyListener::dispose()
+ {
+ ::osl::Guard< comphelper::SolarMutex > aGuard( m_rMutex );
+ m_pDocument = nullptr;
+ }
+
+
+ void SAL_CALL DocumentStorageModifyListener::modified( const EventObject& /*aEvent*/ )
+ {
+ ::osl::Guard< comphelper::SolarMutex > aGuard( m_rMutex );
+ // storageIsModified must not contain any locking!
+ if ( m_pDocument )
+ m_pDocument->storageIsModified();
+ }
+
+
+ void SAL_CALL DocumentStorageModifyListener::disposing( const EventObject& /*Source*/ )
+ {
+ // not interested in. In particular, we do *not* dispose ourself when a storage we're
+ // listening at is disposed. The reason here is that this listener instance is *reused*
+ // in case the document is re-based to another storage.
+ }
+
+
+} // namespace sfx2
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/doctempl.cxx b/sfx2/source/doc/doctempl.cxx
new file mode 100644
index 0000000000..63d83f1298
--- /dev/null
+++ b/sfx2/source/doc/doctempl.cxx
@@ -0,0 +1,1751 @@
+/* -*- 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 <limits.h>
+#include <mutex>
+#include <string_view>
+
+#include <com/sun/star/uno/Any.h>
+#include <sal/log.hxx>
+
+#include <unotools/pathoptions.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/debug.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <ucbhelper/content.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/XPropertySetInfo.hpp>
+#include <com/sun/star/document/XTypeDetection.hpp>
+#include <com/sun/star/document/DocumentProperties.hpp>
+#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/DocumentTemplates.hpp>
+#include <com/sun/star/frame/XDocumentTemplates.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/io/XPersist.hpp>
+#include <com/sun/star/lang/XLocalizable.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/TransferInfo.hpp>
+#include <com/sun/star/ucb/XContent.hpp>
+#include <com/sun/star/ucb/XContentAccess.hpp>
+#include <com/sun/star/ucb/AnyCompareFactory.hpp>
+#include <com/sun/star/ucb/NumberedSortingInfo.hpp>
+
+#include "doctemplateslocal.hxx"
+#include <sfxurlrelocator.hxx>
+
+#include <sfx2/doctempl.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/sfxresid.hxx>
+#include <sfx2/strings.hrc>
+#include <strings.hxx>
+#include <svtools/templatefoldercache.hxx>
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::frame;
+using namespace ::com::sun::star::io;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::sdbc;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+using namespace ::com::sun::star::document;
+using namespace ::rtl;
+using namespace ::ucbhelper;
+
+constexpr OUString TITLE = u"Title"_ustr;
+constexpr OUString TARGET_URL = u"TargetURL"_ustr;
+
+constexpr OUStringLiteral COMMAND_TRANSFER = u"transfer";
+
+namespace {
+
+class RegionData_Impl;
+
+}
+
+namespace DocTempl {
+
+namespace {
+
+class DocTempl_EntryData_Impl
+{
+ RegionData_Impl* mpParent;
+
+ // the following member must be SfxObjectShellLock since it controls that SfxObjectShell lifetime by design
+ // and users of this class expect it to be so.
+ SfxObjectShellLock mxObjShell;
+
+ OUString maTitle;
+ OUString maOwnURL;
+ OUString maTargetURL;
+
+public:
+ DocTempl_EntryData_Impl( RegionData_Impl* pParent,
+ const OUString& rTitle );
+
+ const OUString& GetTitle() const { return maTitle; }
+ const OUString& GetTargetURL();
+ const OUString& GetHierarchyURL();
+
+ void SetTitle( const OUString& rTitle ) { maTitle = rTitle; }
+ void SetTargetURL( const OUString& rURL ) { maTargetURL = rURL; }
+ void SetHierarchyURL( const OUString& rURL) { maOwnURL = rURL; }
+
+ int Compare( std::u16string_view rTitle ) const;
+};
+
+}
+
+}
+
+using namespace ::DocTempl;
+
+namespace {
+
+class RegionData_Impl
+{
+ const SfxDocTemplate_Impl* mpParent;
+ std::vector<std::unique_ptr<DocTempl_EntryData_Impl>> maEntries;
+ OUString maTitle;
+ OUString maOwnURL;
+
+private:
+ size_t GetEntryPos( std::u16string_view rTitle,
+ bool& rFound ) const;
+
+public:
+ RegionData_Impl( const SfxDocTemplate_Impl* pParent,
+ OUString aTitle );
+
+ void SetHierarchyURL( const OUString& rURL) { maOwnURL = rURL; }
+
+ DocTempl_EntryData_Impl* GetEntry( size_t nIndex ) const;
+ DocTempl_EntryData_Impl* GetEntry( std::u16string_view rName ) const;
+
+ const OUString& GetTitle() const { return maTitle; }
+ const OUString& GetHierarchyURL();
+
+ size_t GetCount() const;
+
+ void SetTitle( const OUString& rTitle ) { maTitle = rTitle; }
+
+ void AddEntry( const OUString& rTitle,
+ const OUString& rTargetURL,
+ const size_t *pPos );
+ void DeleteEntry( size_t nIndex );
+
+ int Compare( RegionData_Impl const * pCompareWith ) const;
+};
+
+}
+
+class SfxDocTemplate_Impl : public SvRefBase
+{
+ uno::Reference< XPersist > mxInfo;
+ uno::Reference< XDocumentTemplates > mxTemplates;
+
+ std::mutex maMutex;
+ OUString maRootURL;
+ OUString maStandardGroup;
+ std::vector<std::unique_ptr<RegionData_Impl>> maRegions;
+ bool mbConstructed;
+
+ uno::Reference< XAnyCompareFactory > m_rCompareFactory;
+
+ // the following member is intended to prevent clearing of the global data when it is in use
+ // TODO/LATER: it still does not make the implementation complete thread-safe
+ sal_Int32 mnLockCounter;
+
+private:
+ void Clear();
+
+public:
+ SfxDocTemplate_Impl();
+ virtual ~SfxDocTemplate_Impl() override;
+
+ void IncrementLock();
+ void DecrementLock();
+
+ bool Construct( );
+ void CreateFromHierarchy( std::unique_lock<std::mutex>& rGuard, Content &rTemplRoot );
+ void ReInitFromComponent();
+ void AddRegion( std::unique_lock<std::mutex>& rGuard,
+ const OUString& rTitle,
+ Content& rContent );
+
+ void Rescan();
+
+ void DeleteRegion( size_t nIndex );
+
+ size_t GetRegionCount() const
+ { return maRegions.size(); }
+ RegionData_Impl* GetRegion( std::u16string_view rName ) const;
+ RegionData_Impl* GetRegion( size_t nIndex ) const;
+
+ bool GetTitleFromURL( const OUString& rURL, OUString& aTitle );
+ bool InsertRegion( std::unique_ptr<RegionData_Impl> pData, size_t nPos );
+ const OUString& GetRootURL() const { return maRootURL; }
+
+ const uno::Reference< XDocumentTemplates >& getDocTemplates() const { return mxTemplates; }
+};
+
+namespace {
+
+class DocTemplLocker_Impl
+{
+ SfxDocTemplate_Impl& m_aDocTempl;
+public:
+ explicit DocTemplLocker_Impl( SfxDocTemplate_Impl& aDocTempl )
+ : m_aDocTempl( aDocTempl )
+ {
+ m_aDocTempl.IncrementLock();
+ }
+
+ ~DocTemplLocker_Impl()
+ {
+ m_aDocTempl.DecrementLock();
+ }
+};
+
+}
+
+static SfxDocTemplate_Impl *gpTemplateData = nullptr;
+
+
+static bool getTextProperty_Impl( Content& rContent,
+ const OUString& rPropName,
+ OUString& rPropValue );
+
+
+OUString SfxDocumentTemplates::GetFullRegionName
+(
+ sal_uInt16 nIdx // Region Index
+) const
+
+/* [Description]
+
+ Returns the logical name of a region and its path
+
+ [Return value] Reference to the Region name
+
+*/
+
+{
+ // First: find the RegionData for the index
+
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ if ( pImp->Construct() )
+ {
+ RegionData_Impl *pData1 = pImp->GetRegion( nIdx );
+
+ if ( pData1 )
+ return pData1->GetTitle();
+
+ // --**-- here was some code which appended the path to the
+ // group if there was more than one with the same name.
+ // this should not happen anymore
+ }
+
+ return OUString();
+}
+
+
+OUString SfxDocumentTemplates::GetRegionName
+(
+ sal_uInt16 nIdx // Region Index
+) const
+
+/* [Description]
+
+ Returns the logical name of a region
+
+ [Return value]
+
+ const String& Reference to the Region name
+
+*/
+{
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ if ( pImp->Construct() )
+ {
+ RegionData_Impl *pData = pImp->GetRegion( nIdx );
+
+ if ( pData )
+ return pData->GetTitle();
+ }
+
+ return OUString();
+}
+
+
+sal_uInt16 SfxDocumentTemplates::GetRegionCount() const
+
+/* [Description]
+
+ Returns the number of Regions
+
+ [Return value]
+
+ sal_uInt16 Number of Regions
+*/
+{
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ if ( !pImp->Construct() )
+ return 0;
+
+ return pImp->GetRegionCount();
+}
+
+
+sal_uInt16 SfxDocumentTemplates::GetCount
+(
+ sal_uInt16 nRegion /* Region index whose number is
+ to be determined */
+
+) const
+
+/* [Description]
+
+ Number of entries in Region
+
+ [Return value] Number of entries
+*/
+
+{
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ if ( !pImp->Construct() )
+ return 0;
+
+ RegionData_Impl *pData = pImp->GetRegion( nRegion );
+
+ if ( !pData )
+ return 0;
+
+ return pData->GetCount();
+}
+
+
+OUString SfxDocumentTemplates::GetName
+(
+ sal_uInt16 nRegion, // Region Index, in which the entry lies
+ sal_uInt16 nIdx // Index of the entry
+) const
+
+/* [Description]
+
+ Returns the logical name of an entry in Region
+
+ [Return value]
+
+ const String& Entry Name
+*/
+
+{
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ if ( pImp->Construct() )
+ {
+ RegionData_Impl *pRegion = pImp->GetRegion( nRegion );
+
+ if ( pRegion )
+ {
+ DocTempl_EntryData_Impl *pEntry = pRegion->GetEntry( nIdx );
+ if ( pEntry )
+ return pEntry->GetTitle();
+ }
+ }
+
+ return OUString();
+}
+
+
+OUString SfxDocumentTemplates::GetPath
+(
+ sal_uInt16 nRegion, // Region Index, in which the entry lies
+ sal_uInt16 nIdx // Index of the entry
+) const
+
+/* [Description]
+
+ Returns the file name with full path to the file assigned to an entry
+
+ [Return value]
+
+ String File name with full path
+*/
+{
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ if ( !pImp->Construct() )
+ return OUString();
+
+ RegionData_Impl *pRegion = pImp->GetRegion( nRegion );
+
+ if ( pRegion )
+ {
+ DocTempl_EntryData_Impl *pEntry = pRegion->GetEntry( nIdx );
+ if ( pEntry )
+ return pEntry->GetTargetURL();
+ }
+
+ return OUString();
+}
+
+
+OUString SfxDocumentTemplates::GetTemplateTargetURLFromComponent( std::u16string_view aGroupName,
+ std::u16string_view aTitle )
+{
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ INetURLObject aTemplateObj( pImp->GetRootURL() );
+
+ aTemplateObj.insertName( aGroupName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+
+ aTemplateObj.insertName( aTitle, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+
+
+ Content aTemplate;
+ uno::Reference< XCommandEnvironment > aCmdEnv;
+ if ( Content::create( aTemplateObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) )
+ {
+ OUString aResult;
+ getTextProperty_Impl( aTemplate, TARGET_URL, aResult );
+ return SvtPathOptions().SubstituteVariable( aResult );
+ }
+
+ return OUString();
+}
+
+
+/** Convert a template name to its localised pair if it exists.
+ @param rString
+ Name to be translated.
+ @return
+ The localised pair of rString or rString if the former does not exist.
+*/
+OUString SfxDocumentTemplates::ConvertResourceString(const OUString& rString)
+{
+ static constexpr OUString aTemplateNames[] =
+ {
+ STR_TEMPLATE_NAME1_DEF,
+ STR_TEMPLATE_NAME2_DEF,
+ STR_TEMPLATE_NAME3_DEF,
+ STR_TEMPLATE_NAME4_DEF,
+ STR_TEMPLATE_NAME5_DEF,
+ STR_TEMPLATE_NAME6_DEF,
+ STR_TEMPLATE_NAME7_DEF,
+ STR_TEMPLATE_NAME8_DEF,
+ STR_TEMPLATE_NAME9_DEF,
+ STR_TEMPLATE_NAME10_DEF,
+ STR_TEMPLATE_NAME11_DEF,
+ STR_TEMPLATE_NAME12_DEF,
+ STR_TEMPLATE_NAME13_DEF,
+ STR_TEMPLATE_NAME14_DEF,
+ STR_TEMPLATE_NAME15_DEF,
+ STR_TEMPLATE_NAME16_DEF,
+ STR_TEMPLATE_NAME17_DEF,
+ STR_TEMPLATE_NAME18_DEF,
+ STR_TEMPLATE_NAME19_DEF,
+ STR_TEMPLATE_NAME20_DEF,
+ STR_TEMPLATE_NAME21_DEF,
+ STR_TEMPLATE_NAME22_DEF,
+ STR_TEMPLATE_NAME23_DEF,
+ STR_TEMPLATE_NAME24_DEF,
+ STR_TEMPLATE_NAME25_DEF,
+ STR_TEMPLATE_NAME26_DEF,
+ STR_TEMPLATE_NAME27_DEF,
+ STR_TEMPLATE_NAME28_DEF,
+ STR_TEMPLATE_NAME29_DEF,
+ STR_TEMPLATE_NAME30_DEF,
+ STR_TEMPLATE_NAME31_DEF,
+ STR_TEMPLATE_NAME32_DEF,
+ STR_TEMPLATE_NAME33_DEF,
+ STR_TEMPLATE_NAME34_DEF
+ };
+
+ TranslateId STR_TEMPLATE_NAME[] =
+ {
+ STR_TEMPLATE_NAME1,
+ STR_TEMPLATE_NAME2,
+ STR_TEMPLATE_NAME3,
+ STR_TEMPLATE_NAME4,
+ STR_TEMPLATE_NAME5,
+ STR_TEMPLATE_NAME6,
+ STR_TEMPLATE_NAME7,
+ STR_TEMPLATE_NAME8,
+ STR_TEMPLATE_NAME9,
+ STR_TEMPLATE_NAME10,
+ STR_TEMPLATE_NAME11,
+ STR_TEMPLATE_NAME12,
+ STR_TEMPLATE_NAME13,
+ STR_TEMPLATE_NAME14,
+ STR_TEMPLATE_NAME15,
+ STR_TEMPLATE_NAME16,
+ STR_TEMPLATE_NAME17,
+ STR_TEMPLATE_NAME18,
+ STR_TEMPLATE_NAME19,
+ STR_TEMPLATE_NAME20,
+ STR_TEMPLATE_NAME21,
+ STR_TEMPLATE_NAME22,
+ STR_TEMPLATE_NAME23,
+ STR_TEMPLATE_NAME24,
+ STR_TEMPLATE_NAME25,
+ STR_TEMPLATE_NAME26,
+ STR_TEMPLATE_NAME27,
+ STR_TEMPLATE_NAME28,
+ STR_TEMPLATE_NAME29,
+ STR_TEMPLATE_NAME30,
+ STR_TEMPLATE_NAME31,
+ STR_TEMPLATE_NAME32,
+ STR_TEMPLATE_NAME33,
+ STR_TEMPLATE_NAME34
+ };
+
+ static_assert(SAL_N_ELEMENTS(aTemplateNames) == SAL_N_ELEMENTS(STR_TEMPLATE_NAME));
+
+ for (size_t i = 0; i < SAL_N_ELEMENTS(STR_TEMPLATE_NAME); ++i)
+ {
+ if (rString == aTemplateNames[i])
+ return SfxResId(STR_TEMPLATE_NAME[i]);
+ }
+ return rString;
+}
+
+
+bool SfxDocumentTemplates::CopyOrMove
+(
+ sal_uInt16 nTargetRegion, // Target Region Index
+ sal_uInt16 nTargetIdx, // Target position Index
+ sal_uInt16 nSourceRegion, // Source Region Index
+ sal_uInt16 nSourceIdx, /* Index to be copied / to moved template */
+ bool bMove // Copy / Move
+)
+
+/* [Description]
+
+ Copy or move a document template
+
+ [Return value]
+
+ sal_Bool sal_True, Action could be performed
+ sal_False, Action could not be performed
+
+ [Cross-references]
+
+ <SfxDocumentTemplates::Move(sal_uInt16,sal_uInt16,sal_uInt16,sal_uInt16)>
+ <SfxDocumentTemplates::Copy(sal_uInt16,sal_uInt16,sal_uInt16,sal_uInt16)>
+*/
+
+{
+ /* to perform a copy or move, we need to send a transfer command to
+ the destination folder with the URL of the source as parameter.
+ ( If the destination content doesn't support the transfer command,
+ we could try a copy ( and delete ) instead. )
+ We need two transfers ( one for the real template and one for its
+ representation in the hierarchy )
+ ...
+ */
+
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ if ( !pImp->Construct() )
+ return false;
+
+ // Don't copy or move any folders
+ if( nSourceIdx == USHRT_MAX )
+ return false ;
+
+ if ( nSourceRegion == nTargetRegion )
+ {
+ SAL_WARN( "sfx.doc", "Don't know, what to do!" );
+ return false;
+ }
+
+ RegionData_Impl *pSourceRgn = pImp->GetRegion( nSourceRegion );
+ if ( !pSourceRgn )
+ return false;
+
+ DocTempl_EntryData_Impl *pSource = pSourceRgn->GetEntry( nSourceIdx );
+ if ( !pSource )
+ return false;
+
+ RegionData_Impl *pTargetRgn = pImp->GetRegion( nTargetRegion );
+ if ( !pTargetRgn )
+ return false;
+
+ const OUString aTitle = pSource->GetTitle();
+
+ uno::Reference< XDocumentTemplates > xTemplates = pImp->getDocTemplates();
+
+ if ( xTemplates->addTemplate( pTargetRgn->GetTitle(),
+ aTitle,
+ pSource->GetTargetURL() ) )
+ {
+ const OUString aNewTargetURL = GetTemplateTargetURLFromComponent( pTargetRgn->GetTitle(), aTitle );
+ if ( aNewTargetURL.isEmpty() )
+ return false;
+
+ if ( bMove )
+ {
+ // --**-- delete the original file
+ bool bDeleted = xTemplates->removeTemplate( pSourceRgn->GetTitle(),
+ pSource->GetTitle() );
+ if ( bDeleted )
+ pSourceRgn->DeleteEntry( nSourceIdx );
+ else
+ {
+ if ( xTemplates->removeTemplate( pTargetRgn->GetTitle(), aTitle ) )
+ return false; // will trigger retry with copy instead of move
+
+ // if it is not possible to remove just created template ( must be possible! )
+ // it is better to report success here, since at least the copy has succeeded
+ // TODO/LATER: solve it more gracefully in future
+ }
+ }
+
+ // todo: fix SfxDocumentTemplates to handle size_t instead of sal_uInt16
+ size_t temp_nTargetIdx = nTargetIdx;
+ pTargetRgn->AddEntry( aTitle, aNewTargetURL, &temp_nTargetIdx );
+
+ return true;
+ }
+
+ // --**-- if the current file is opened,
+ // it must be re-opened afterwards.
+
+ return false;
+}
+
+
+bool SfxDocumentTemplates::Move
+(
+ sal_uInt16 nTargetRegion, // Target Region Index
+ sal_uInt16 nTargetIdx, // Target position Index
+ sal_uInt16 nSourceRegion, // Source Region Index
+ sal_uInt16 nSourceIdx /* Index to be copied / to moved template */
+)
+
+/* [Description]
+
+ Moving a template
+
+ [Return value]
+
+ sal_Bool sal_True, Action could be performed
+ sal_False, Action could not be performed
+
+ [Cross-references]
+
+ <SfxDocumentTemplates::CopyOrMove(sal_uInt16,sal_uInt16,sal_uInt16,sal_uInt16,sal_Bool)>
+*/
+{
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ return CopyOrMove( nTargetRegion, nTargetIdx,
+ nSourceRegion, nSourceIdx, true );
+}
+
+
+bool SfxDocumentTemplates::Copy
+(
+ sal_uInt16 nTargetRegion, // Target Region Index
+ sal_uInt16 nTargetIdx, // Target position Index
+ sal_uInt16 nSourceRegion, // Source Region Index
+ sal_uInt16 nSourceIdx /* Index to be copied / to moved template */
+)
+
+/* [Description]
+
+ Copying a template
+
+ [Return value]
+
+ sal_Bool sal_True, Action could be performed
+ sal_False, Action could not be performed
+
+ [Cross-references]
+
+ <SfxDocumentTemplates::CopyOrMove(sal_uInt16,sal_uInt16,sal_uInt16,sal_uInt16,sal_Bool)>
+*/
+
+{
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ return CopyOrMove( nTargetRegion, nTargetIdx,
+ nSourceRegion, nSourceIdx, false );
+}
+
+
+bool SfxDocumentTemplates::CopyTo
+(
+ sal_uInt16 nRegion, // Region of the template to be exported
+ sal_uInt16 nIdx, // Index of the template to be exported
+ std::u16string_view rName /* File name under which the template is to
+ be created */
+) const
+
+/* [Description]
+
+ Exporting a template into the file system
+
+ [Return value]
+
+ sal_Bool sal_True, Action could be performed
+ sal_False, Action could not be performed
+
+ [Cross-references]
+
+ <SfxDocumentTemplates::CopyFrom(sal_uInt16,sal_uInt16,String&)>
+*/
+
+{
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ if ( ! pImp->Construct() )
+ return false;
+
+ RegionData_Impl *pSourceRgn = pImp->GetRegion( nRegion );
+ if ( !pSourceRgn )
+ return false;
+
+ DocTempl_EntryData_Impl *pSource = pSourceRgn->GetEntry( nIdx );
+ if ( !pSource )
+ return false;
+
+ INetURLObject aTargetURL( rName );
+
+ const OUString aTitle( aTargetURL.getName( INetURLObject::LAST_SEGMENT, true,
+ INetURLObject::DecodeMechanism::WithCharset ) );
+ aTargetURL.removeSegment();
+
+ const OUString aParentURL = aTargetURL.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ uno::Reference< XCommandEnvironment > aCmdEnv;
+ Content aTarget;
+
+ try
+ {
+ aTarget = Content( aParentURL, aCmdEnv, comphelper::getProcessComponentContext() );
+
+ TransferInfo aTransferInfo;
+ aTransferInfo.MoveData = false;
+ aTransferInfo.SourceURL = pSource->GetTargetURL();
+ aTransferInfo.NewTitle = aTitle;
+ aTransferInfo.NameClash = NameClash::RENAME;
+
+ Any aArg( aTransferInfo );
+ aTarget.executeCommand( COMMAND_TRANSFER, aArg );
+ }
+ catch ( ContentCreationException& )
+ { return false; }
+ catch ( Exception& )
+ { return false; }
+
+ return true;
+}
+
+
+bool SfxDocumentTemplates::CopyFrom
+(
+ sal_uInt16 nRegion, /* Region in which the template is to be
+ imported */
+ sal_uInt16 nIdx, // Index of the new template in this Region
+ OUString& rName /* File name of the template to be imported
+ as an out parameter of the (automatically
+ generated from the file name) logical name
+ of the template */
+)
+
+/* [Description]
+
+ Import a template from the file system
+
+ [Return value] Success (sal_True) or serfpTargetDirectory->GetContent());
+
+ sal_Bool sal_True, Action could be performed
+ sal_False, Action could not be performed
+
+ [Cross-references]
+
+ <SfxDocumentTemplates::CopyTo(sal_uInt16,sal_uInt16,const String&)>
+*/
+
+{
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ if ( ! pImp->Construct() )
+ return false;
+
+ RegionData_Impl *pTargetRgn = pImp->GetRegion( nRegion );
+
+ if ( !pTargetRgn )
+ return false;
+
+ uno::Reference< XDocumentTemplates > xTemplates = pImp->getDocTemplates();
+ if ( !xTemplates.is() )
+ return false;
+
+ OUString aTitle;
+ bool bTemplateAdded = false;
+
+ if( pImp->GetTitleFromURL( rName, aTitle ) )
+ {
+ bTemplateAdded = xTemplates->addTemplate( pTargetRgn->GetTitle(), aTitle, rName );
+ }
+ else
+ {
+ uno::Reference< XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
+
+ Sequence< PropertyValue > aArgs{ comphelper::makePropertyValue("Hidden", true) };
+
+ INetURLObject aTemplURL( rName );
+ uno::Reference< XDocumentPropertiesSupplier > xDocPropsSupplier;
+ uno::Reference< XStorable > xStorable;
+ try
+ {
+ xStorable.set(
+ xDesktop->loadComponentFromURL( aTemplURL.GetMainURL(INetURLObject::DecodeMechanism::NONE),
+ "_blank",
+ 0,
+ aArgs ),
+ UNO_QUERY );
+
+ xDocPropsSupplier.set( xStorable, UNO_QUERY );
+ }
+ catch( Exception& )
+ {
+ }
+
+ if( xStorable.is() )
+ {
+ // get Title from XDocumentPropertiesSupplier
+ if( xDocPropsSupplier.is() )
+ {
+ uno::Reference< XDocumentProperties > xDocProps
+ = xDocPropsSupplier->getDocumentProperties();
+ if (xDocProps.is() ) {
+ aTitle = xDocProps->getTitle();
+ }
+ }
+
+ if( aTitle.isEmpty() )
+ {
+ INetURLObject aURL( aTemplURL );
+ aURL.CutExtension();
+ aTitle = aURL.getName( INetURLObject::LAST_SEGMENT, true,
+ INetURLObject::DecodeMechanism::WithCharset );
+ }
+
+ // write a template using XStorable interface
+ bTemplateAdded = xTemplates->storeTemplate( pTargetRgn->GetTitle(), aTitle, xStorable );
+ }
+ }
+
+
+ if( bTemplateAdded )
+ {
+ INetURLObject aTemplObj( pTargetRgn->GetHierarchyURL() );
+ aTemplObj.insertName( aTitle, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ const OUString aTemplURL = aTemplObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ uno::Reference< XCommandEnvironment > aCmdEnv;
+ Content aTemplCont;
+
+ if( Content::create( aTemplURL, aCmdEnv, comphelper::getProcessComponentContext(), aTemplCont ) )
+ {
+ OUString aTemplName;
+ if( getTextProperty_Impl( aTemplCont, TARGET_URL, aTemplName ) )
+ {
+ if ( nIdx == USHRT_MAX )
+ nIdx = 0;
+ else
+ ++nIdx;
+
+ // todo: fix SfxDocumentTemplates to handle size_t instead of sal_uInt16
+ size_t temp_nIdx = nIdx;
+ pTargetRgn->AddEntry( aTitle, aTemplName, &temp_nIdx );
+ rName = aTitle;
+ return true;
+ }
+ else
+ {
+ SAL_WARN( "sfx.doc", "CopyFrom(): The content should contain target URL!" );
+ }
+ }
+ else
+ {
+ SAL_WARN( "sfx.doc", "CopyFrom(): The content just was created!" );
+ }
+ }
+
+ return false;
+}
+
+
+bool SfxDocumentTemplates::Delete
+(
+ sal_uInt16 nRegion, // Region Index
+ sal_uInt16 nIdx /* Index of the entry or USHRT_MAX,
+ if a directory is meant. */
+)
+
+/* [Description]
+
+ Deleting an entry or a directory
+
+ [Return value]
+
+ sal_Bool sal_True, Action could be performed
+ sal_False, Action could not be performed
+
+ [Cross-references]
+
+ <SfxDocumentTemplates::InsertDir(const String&,sal_uInt16)>
+ <SfxDocumentTemplates::KillDir(SfxTemplateDir&)>
+*/
+
+{
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ /* delete the template or folder in the hierarchy and in the
+ template folder by sending a delete command to the content.
+ Then remove the data from the lists
+ */
+ if ( ! pImp->Construct() )
+ return false;
+
+ RegionData_Impl *pRegion = pImp->GetRegion( nRegion );
+
+ if ( !pRegion )
+ return false;
+
+ bool bRet;
+ uno::Reference< XDocumentTemplates > xTemplates = pImp->getDocTemplates();
+
+ if ( nIdx == USHRT_MAX )
+ {
+ bRet = xTemplates->removeGroup( pRegion->GetTitle() );
+ if ( bRet )
+ pImp->DeleteRegion( nRegion );
+ }
+ else
+ {
+ DocTempl_EntryData_Impl *pEntry = pRegion->GetEntry( nIdx );
+
+ if ( !pEntry )
+ return false;
+
+ bRet = xTemplates->removeTemplate( pRegion->GetTitle(),
+ pEntry->GetTitle() );
+ if( bRet )
+ pRegion->DeleteEntry( nIdx );
+ }
+
+ return bRet;
+}
+
+
+bool SfxDocumentTemplates::InsertDir
+(
+ const OUString& rText, // the logical name of the new Region
+ sal_uInt16 nRegion // Region Index
+)
+
+/* [Description]
+
+ Insert an index
+
+ [Return value]
+
+ sal_Bool sal_True, Action could be performed
+ sal_False, Action could not be performed
+
+ [Cross-references]
+
+ <SfxDocumentTemplates::KillDir(SfxTemplateDir&)>
+*/
+{
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ if ( ! pImp->Construct() )
+ return false;
+
+ RegionData_Impl *pRegion = pImp->GetRegion( rText );
+
+ if ( pRegion )
+ return false;
+
+ uno::Reference< XDocumentTemplates > xTemplates = pImp->getDocTemplates();
+
+ if ( xTemplates->addGroup( rText ) )
+ {
+ return pImp->InsertRegion( std::make_unique<RegionData_Impl>( pImp.get(), rText ), nRegion );
+ }
+
+ return false;
+}
+
+bool SfxDocumentTemplates::InsertTemplate(sal_uInt16 nSourceRegion, sal_uInt16 nIdx, const OUString &rName, const OUString &rPath)
+{
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ if ( ! pImp->Construct() )
+ return false;
+
+ RegionData_Impl *pRegion = pImp->GetRegion( nSourceRegion );
+
+ if ( !pRegion )
+ return false;
+
+ size_t pos = nIdx;
+ pRegion->AddEntry( rName, rPath, &pos );
+
+ return true;
+}
+
+bool SfxDocumentTemplates::SetName( const OUString& rName, sal_uInt16 nRegion, sal_uInt16 nIdx )
+
+{
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ if ( ! pImp->Construct() )
+ return false;
+
+ RegionData_Impl *pRegion = pImp->GetRegion( nRegion );
+
+ if ( !pRegion )
+ return false;
+
+ uno::Reference< XDocumentTemplates > xTemplates = pImp->getDocTemplates();
+
+ if ( nIdx == USHRT_MAX )
+ {
+ if ( pRegion->GetTitle() == rName )
+ return true;
+
+ // we have to rename a region
+ if ( xTemplates->renameGroup( pRegion->GetTitle(), rName ) )
+ {
+ pRegion->SetTitle( rName );
+ pRegion->SetHierarchyURL( "" );
+ return true;
+ }
+ }
+ else
+ {
+ DocTempl_EntryData_Impl *pEntry = pRegion->GetEntry( nIdx );
+
+ if ( !pEntry )
+ return false;
+
+ if ( pEntry->GetTitle() == rName )
+ return true;
+
+ if ( xTemplates->renameTemplate( pRegion->GetTitle(),
+ pEntry->GetTitle(),
+ rName ) )
+ {
+ pEntry->SetTitle( rName );
+ pEntry->SetTargetURL( "" );
+ pEntry->SetHierarchyURL( "" );
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+bool SfxDocumentTemplates::GetFull
+(
+ std::u16string_view rRegion, // Region Name
+ std::u16string_view rName, // Template Name
+ OUString &rPath // Out: Path + File name
+)
+
+/* [Description]
+
+ Returns Path + File name of the template specified by rRegion and rName.
+
+ [Return value]
+
+ sal_Bool sal_True, Action could be performed
+ sal_False, Action could not be performed
+
+ [Cross-references]
+
+ <SfxDocumentTemplates::GetLogicNames(const String&,String&,String&)>
+*/
+
+{
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ // We don't search for empty names!
+ if ( rName.empty() )
+ return false;
+
+ if ( ! pImp->Construct() )
+ return false;
+
+ DocTempl_EntryData_Impl* pEntry = nullptr;
+ const sal_uInt16 nCount = GetRegionCount();
+
+ for ( sal_uInt16 i = 0; i < nCount; ++i )
+ {
+ RegionData_Impl *pRegion = pImp->GetRegion( i );
+
+ if( pRegion &&
+ ( rRegion.empty() || ( rRegion == pRegion->GetTitle() ) ) )
+ {
+ pEntry = pRegion->GetEntry( rName );
+
+ if ( pEntry )
+ {
+ rPath = pEntry->GetTargetURL();
+ break;
+ }
+ }
+ }
+
+ return ( pEntry != nullptr );
+}
+
+
+bool SfxDocumentTemplates::GetLogicNames
+(
+ std::u16string_view rPath, // Full Path to the template
+ OUString &rRegion, // Out: Region name
+ OUString &rName // Out: Template name
+) const
+
+/* [Description]
+
+ Returns and logical path name to the template specified by rPath
+
+ [Return value]
+
+ sal_Bool sal_True, Action could be performed
+ sal_False, Action could not be performed
+
+ [Cross-references]
+
+ <SfxDocumentTemplates::GetFull(const String&,const String&,DirEntry&)>
+*/
+
+{
+ DocTemplLocker_Impl aLocker( *pImp );
+
+ if ( ! pImp->Construct() )
+ return false;
+
+ INetURLObject aFullPath;
+
+ aFullPath.SetSmartProtocol( INetProtocol::File );
+ aFullPath.SetURL( rPath );
+ const OUString aPath( aFullPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+
+ const sal_uInt16 nCount = GetRegionCount();
+
+ for ( sal_uInt16 i=0; i<nCount; ++i )
+ {
+ RegionData_Impl *pData = pImp->GetRegion( i );
+ if ( pData )
+ {
+ const sal_uInt16 nChildCount = pData->GetCount();
+
+ for ( sal_uInt16 j=0; j<nChildCount; ++j )
+ {
+ DocTempl_EntryData_Impl *pEntry = pData->GetEntry( j );
+ if ( pEntry && pEntry->GetTargetURL() == aPath )
+ {
+ rRegion = pData->GetTitle();
+ rName = pEntry->GetTitle();
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+
+SfxDocumentTemplates::SfxDocumentTemplates()
+
+/* [Description]
+
+ Constructor
+*/
+{
+ if ( !gpTemplateData )
+ gpTemplateData = new SfxDocTemplate_Impl;
+
+ pImp = gpTemplateData;
+}
+
+
+SfxDocumentTemplates::~SfxDocumentTemplates()
+
+/* [Description]
+
+ Destructor
+ Release of administrative data
+*/
+
+{
+ pImp = nullptr;
+}
+
+void SfxDocumentTemplates::Update( )
+{
+ if ( ::svt::TemplateFolderCache( true ).needsUpdate() ) // update is really necessary
+ {
+ if ( pImp->Construct() )
+ pImp->Rescan();
+ }
+}
+
+void SfxDocumentTemplates::ReInitFromComponent()
+{
+ pImp->ReInitFromComponent();
+}
+
+DocTempl_EntryData_Impl::DocTempl_EntryData_Impl( RegionData_Impl* pParent,
+ const OUString& rTitle )
+{
+ mpParent = pParent;
+ maTitle = SfxDocumentTemplates::ConvertResourceString(rTitle);
+}
+
+
+int DocTempl_EntryData_Impl::Compare( std::u16string_view rTitle ) const
+{
+ return maTitle.compareTo( rTitle );
+}
+
+
+const OUString& DocTempl_EntryData_Impl::GetHierarchyURL()
+{
+ if ( maOwnURL.isEmpty() )
+ {
+ INetURLObject aTemplateObj( mpParent->GetHierarchyURL() );
+
+ aTemplateObj.insertName( GetTitle(), false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+
+ maOwnURL = aTemplateObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ DBG_ASSERT( !maOwnURL.isEmpty(), "GetHierarchyURL(): Could not create URL!" );
+ }
+
+ return maOwnURL;
+}
+
+
+const OUString& DocTempl_EntryData_Impl::GetTargetURL()
+{
+ if ( maTargetURL.isEmpty() )
+ {
+ uno::Reference< XCommandEnvironment > aCmdEnv;
+ Content aRegion;
+
+ if ( Content::create( GetHierarchyURL(), aCmdEnv, comphelper::getProcessComponentContext(), aRegion ) )
+ {
+ getTextProperty_Impl( aRegion, TARGET_URL, maTargetURL );
+ }
+ else
+ {
+ SAL_WARN( "sfx.doc", "GetTargetURL(): Could not create hierarchy content!" );
+ }
+ }
+
+ return maTargetURL;
+}
+
+
+RegionData_Impl::RegionData_Impl( const SfxDocTemplate_Impl* pParent,
+ OUString aTitle )
+ : mpParent(pParent), maTitle(std::move(aTitle))
+{
+}
+
+
+size_t RegionData_Impl::GetEntryPos( std::u16string_view rTitle, bool& rFound ) const
+{
+ const size_t nCount = maEntries.size();
+
+ for ( size_t i=0; i<nCount; ++i )
+ {
+ auto &pData = maEntries[ i ];
+
+ if ( pData->Compare( rTitle ) == 0 )
+ {
+ rFound = true;
+ return i;
+ }
+ }
+
+ rFound = false;
+ return nCount;
+}
+
+
+void RegionData_Impl::AddEntry( const OUString& rTitle,
+ const OUString& rTargetURL,
+ const size_t *pPos )
+{
+ INetURLObject aLinkObj( GetHierarchyURL() );
+ aLinkObj.insertName( rTitle, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ const OUString aLinkURL = aLinkObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ bool bFound = false;
+ size_t nPos = GetEntryPos( rTitle, bFound );
+
+ if ( bFound )
+ return;
+
+ if ( pPos )
+ nPos = *pPos;
+
+ auto pEntry = std::make_unique<DocTempl_EntryData_Impl>(
+ this, rTitle );
+ pEntry->SetTargetURL( rTargetURL );
+ pEntry->SetHierarchyURL( aLinkURL );
+ if ( nPos < maEntries.size() ) {
+ auto it = maEntries.begin();
+ std::advance( it, nPos );
+ maEntries.insert( it, std::move(pEntry) );
+ }
+ else
+ maEntries.push_back( std::move(pEntry) );
+}
+
+
+size_t RegionData_Impl::GetCount() const
+{
+ return maEntries.size();
+}
+
+
+const OUString& RegionData_Impl::GetHierarchyURL()
+{
+ if ( maOwnURL.isEmpty() )
+ {
+ INetURLObject aRegionObj( mpParent->GetRootURL() );
+
+ aRegionObj.insertName( GetTitle(), false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+
+ maOwnURL = aRegionObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ DBG_ASSERT( !maOwnURL.isEmpty(), "GetHierarchyURL(): Could not create URL!" );
+ }
+
+ return maOwnURL;
+}
+
+
+DocTempl_EntryData_Impl* RegionData_Impl::GetEntry( std::u16string_view rName ) const
+{
+ bool bFound = false;
+ tools::Long nPos = GetEntryPos( rName, bFound );
+
+ if ( bFound )
+ return maEntries[ nPos ].get();
+ return nullptr;
+}
+
+
+DocTempl_EntryData_Impl* RegionData_Impl::GetEntry( size_t nIndex ) const
+{
+ if ( nIndex < maEntries.size() )
+ return maEntries[ nIndex ].get();
+ return nullptr;
+}
+
+
+void RegionData_Impl::DeleteEntry( size_t nIndex )
+{
+ if ( nIndex < maEntries.size() )
+ {
+ auto it = maEntries.begin();
+ std::advance( it, nIndex );
+ maEntries.erase( it );
+ }
+}
+
+
+int RegionData_Impl::Compare( RegionData_Impl const * pCompare ) const
+{
+ return maTitle.compareTo( pCompare->maTitle );
+}
+
+
+SfxDocTemplate_Impl::SfxDocTemplate_Impl()
+: mbConstructed( false )
+, mnLockCounter( 0 )
+{
+}
+
+
+SfxDocTemplate_Impl::~SfxDocTemplate_Impl()
+{
+ gpTemplateData = nullptr;
+}
+
+
+void SfxDocTemplate_Impl::IncrementLock()
+{
+ std::unique_lock aGuard( maMutex );
+ mnLockCounter++;
+}
+
+
+void SfxDocTemplate_Impl::DecrementLock()
+{
+ std::unique_lock aGuard( maMutex );
+ if ( mnLockCounter )
+ mnLockCounter--;
+}
+
+
+RegionData_Impl* SfxDocTemplate_Impl::GetRegion( size_t nIndex ) const
+{
+ if ( nIndex < maRegions.size() )
+ return maRegions[ nIndex ].get();
+ return nullptr;
+}
+
+
+RegionData_Impl* SfxDocTemplate_Impl::GetRegion( std::u16string_view rName )
+ const
+{
+ for (auto& pData : maRegions)
+ {
+ if( pData->GetTitle() == rName )
+ return pData.get();
+ }
+ return nullptr;
+}
+
+
+void SfxDocTemplate_Impl::DeleteRegion( size_t nIndex )
+{
+ if ( nIndex < maRegions.size() )
+ {
+ auto it = maRegions.begin();
+ std::advance( it, nIndex );
+ maRegions.erase( it );
+ }
+}
+
+
+/* AddRegion adds a Region to the RegionList
+*/
+void SfxDocTemplate_Impl::AddRegion( std::unique_lock<std::mutex>& /*rGuard*/,
+ const OUString& rTitle,
+ Content& rContent )
+{
+ auto pRegion = std::make_unique<RegionData_Impl>( this, rTitle );
+ auto pRegionTmp = pRegion.get();
+
+ if ( ! InsertRegion( std::move(pRegion), size_t(-1) ) )
+ {
+ return;
+ }
+
+ // now get the content of the region
+ uno::Reference< XResultSet > xResultSet;
+
+ try
+ {
+ xResultSet = rContent.createSortedCursor( { TITLE, TARGET_URL }, { { 1, true } }, m_rCompareFactory, INCLUDE_DOCUMENTS_ONLY );
+ }
+ catch ( Exception& ) {}
+
+ if ( !xResultSet.is() )
+ return;
+
+ uno::Reference< XRow > xRow( xResultSet, UNO_QUERY );
+
+ try
+ {
+ while ( xResultSet->next() )
+ {
+ pRegionTmp->AddEntry( xRow->getString( 1 ), xRow->getString( 2 ), nullptr );
+ }
+ }
+ catch ( Exception& ) {}
+}
+
+
+void SfxDocTemplate_Impl::CreateFromHierarchy( std::unique_lock<std::mutex>& rGuard, Content &rTemplRoot )
+{
+ uno::Reference< XResultSet > xResultSet;
+ Sequence< OUString > aProps { TITLE };
+
+ try
+ {
+ xResultSet = rTemplRoot.createSortedCursor(
+ aProps,
+ { // Sequence
+ { // NumberedSortingInfo
+ /* ColumnIndex */ 1, /* Ascending */ true
+ }
+ },
+ m_rCompareFactory,
+ INCLUDE_FOLDERS_ONLY
+ );
+ }
+ catch ( Exception& ) {}
+
+ if ( !xResultSet.is() )
+ return;
+
+ uno::Reference< XCommandEnvironment > aCmdEnv;
+ uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY );
+ uno::Reference< XRow > xRow( xResultSet, UNO_QUERY );
+
+ try
+ {
+ while ( xResultSet->next() )
+ {
+ const OUString aId = xContentAccess->queryContentIdentifierString();
+ Content aContent( aId, aCmdEnv, comphelper::getProcessComponentContext() );
+
+ AddRegion( rGuard, xRow->getString( 1 ), aContent );
+ }
+ }
+ catch ( Exception& ) {}
+}
+
+
+bool SfxDocTemplate_Impl::Construct( )
+{
+ std::unique_lock aGuard( maMutex );
+
+ if ( mbConstructed )
+ return true;
+
+ uno::Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+
+ uno::Reference< XPersist > xInfo( document::DocumentProperties::create(xContext), UNO_QUERY );
+ mxInfo = xInfo;
+
+ mxTemplates = frame::DocumentTemplates::create(xContext);
+
+ uno::Reference< XLocalizable > xLocalizable( mxTemplates, UNO_QUERY );
+
+ m_rCompareFactory = AnyCompareFactory::createWithLocale(xContext, xLocalizable->getLocale());
+
+ uno::Reference < XContent > aRootContent = mxTemplates->getContent();
+ uno::Reference < XCommandEnvironment > aCmdEnv;
+
+ if ( ! aRootContent.is() )
+ return false;
+
+ mbConstructed = true;
+ maRootURL = aRootContent->getIdentifier()->getContentIdentifier();
+
+ maStandardGroup = DocTemplLocaleHelper::GetStandardGroupString();
+ Content aTemplRoot( aRootContent, aCmdEnv, xContext );
+ CreateFromHierarchy( aGuard, aTemplRoot );
+
+ return true;
+}
+
+
+void SfxDocTemplate_Impl::ReInitFromComponent()
+{
+ uno::Reference< XDocumentTemplates > xTemplates = getDocTemplates();
+ if ( xTemplates.is() )
+ {
+ uno::Reference < XContent > aRootContent = xTemplates->getContent();
+ uno::Reference < XCommandEnvironment > aCmdEnv;
+ Content aTemplRoot( aRootContent, aCmdEnv, comphelper::getProcessComponentContext() );
+ Clear();
+ std::unique_lock aGuard(maMutex);
+ CreateFromHierarchy( aGuard, aTemplRoot );
+ }
+}
+
+
+bool SfxDocTemplate_Impl::InsertRegion( std::unique_ptr<RegionData_Impl> pNew, size_t nPos )
+{
+ // return false (not inserted) if the entry already exists
+ for (auto const& pRegion : maRegions)
+ if ( pRegion->Compare( pNew.get() ) == 0 )
+ return false;
+
+ size_t newPos = nPos;
+ if ( pNew->GetTitle() == maStandardGroup )
+ newPos = 0;
+
+ if ( newPos < maRegions.size() )
+ {
+ auto it = maRegions.begin();
+ std::advance( it, newPos );
+ maRegions.emplace( it, std::move(pNew) );
+ }
+ else
+ maRegions.emplace_back( std::move(pNew) );
+
+ return true;
+}
+
+
+void SfxDocTemplate_Impl::Rescan()
+{
+ Clear();
+
+ try
+ {
+ uno::Reference< XDocumentTemplates > xTemplates = getDocTemplates();
+ DBG_ASSERT( xTemplates.is(), "SfxDocTemplate_Impl::Rescan:invalid template instance!" );
+ if ( xTemplates.is() )
+ {
+ xTemplates->update();
+
+ uno::Reference < XContent > aRootContent = xTemplates->getContent();
+ uno::Reference < XCommandEnvironment > aCmdEnv;
+
+ Content aTemplRoot( aRootContent, aCmdEnv, comphelper::getProcessComponentContext() );
+ std::unique_lock aGuard(maMutex);
+ CreateFromHierarchy( aGuard, aTemplRoot );
+ }
+ }
+ catch( const Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "SfxDocTemplate_Impl::Rescan: caught an exception while doing the update" );
+ }
+}
+
+
+bool SfxDocTemplate_Impl::GetTitleFromURL( const OUString& rURL,
+ OUString& aTitle )
+{
+ if ( mxInfo.is() )
+ {
+ try
+ {
+ mxInfo->read( rURL );
+ }
+ catch ( Exception& )
+ {
+ // the document is not a StarOffice document
+ return false;
+ }
+
+
+ try
+ {
+ uno::Reference< XPropertySet > aPropSet( mxInfo, UNO_QUERY );
+ if ( aPropSet.is() )
+ {
+ Any aValue = aPropSet->getPropertyValue( TITLE );
+ aValue >>= aTitle;
+ }
+ }
+ catch ( IOException& ) {}
+ catch ( UnknownPropertyException& ) {}
+ catch ( Exception& ) {}
+ }
+
+ if ( aTitle.isEmpty() )
+ {
+ INetURLObject aURL( rURL );
+ aURL.CutExtension();
+ aTitle = aURL.getName( INetURLObject::LAST_SEGMENT, true,
+ INetURLObject::DecodeMechanism::WithCharset );
+ }
+
+ return true;
+}
+
+
+void SfxDocTemplate_Impl::Clear()
+{
+ std::unique_lock aGuard( maMutex );
+ if ( mnLockCounter )
+ return;
+ maRegions.clear();
+}
+
+
+bool getTextProperty_Impl( Content& rContent,
+ const OUString& rPropName,
+ OUString& 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
+ Any aAnyValue = rContent.getPropertyValue( rPropName );
+ aAnyValue >>= rPropValue;
+
+ if ( SfxURLRelocator_Impl::propertyCanContainOfficeDir( rPropName ) )
+ {
+ SfxURLRelocator_Impl aRelocImpl( ::comphelper::getProcessComponentContext() );
+ aRelocImpl.makeAbsoluteURL( rPropValue );
+ }
+
+ bGotProperty = true;
+ }
+ catch ( RuntimeException& ) {}
+ catch ( Exception& ) {}
+
+ return bGotProperty;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/doctemplates.cxx b/sfx2/source/doc/doctemplates.cxx
new file mode 100644
index 0000000000..20aecdd028
--- /dev/null
+++ b/sfx2/source/doc/doctemplates.cxx
@@ -0,0 +1,2640 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <osl/mutex.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/urlobj.hxx>
+#include <rtl/uri.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <vcl/wrkwin.hxx>
+#include <unotools/pathoptions.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/sequenceashashmap.hxx>
+#include <comphelper/storagehelper.hxx>
+#include <comphelper/string.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <com/sun/star/beans/IllegalTypeException.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/beans/PropertyExistException.hpp>
+#include <com/sun/star/beans/XPropertySetInfo.hpp>
+#include <com/sun/star/beans/XPropertyContainer.hpp>
+#include <com/sun/star/beans/StringPair.hpp>
+#include <com/sun/star/ucb/SimpleFileAccess.hpp>
+#include <com/sun/star/util/theMacroExpander.hpp>
+#include <com/sun/star/util/theOfficeInstallationDirectories.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <com/sun/star/document/XTypeDetection.hpp>
+#include <com/sun/star/document/DocumentProperties.hpp>
+#include <com/sun/star/io/TempFile.hpp>
+#include <com/sun/star/sdbc/XResultSet.hpp>
+#include <com/sun/star/sdbc/XRow.hpp>
+#include <com/sun/star/ucb/ContentCreationException.hpp>
+#include <com/sun/star/ucb/NameClash.hpp>
+#include <com/sun/star/ucb/NameClashException.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/ucb/XContentAccess.hpp>
+#include <com/sun/star/frame/ModuleManager.hpp>
+#include <com/sun/star/uno/Exception.hpp>
+#include <com/sun/star/task/InteractionHandler.hpp>
+#include <com/sun/star/ucb/XProgressHandler.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/frame/XDocumentTemplates.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/lang/Locale.hpp>
+#include <com/sun/star/lang/XLocalizable.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/ucb/XContent.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/uno/RuntimeException.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/util/thePathSettings.hpp>
+
+#include <svtools/templatefoldercache.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/ucbhelper.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <ucbhelper/content.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <sfx2/sfxresid.hxx>
+#include <sfxurlrelocator.hxx>
+#include "doctemplateslocal.hxx"
+#include <sfx2/docfac.hxx>
+#include <sfx2/strings.hrc>
+#include <doctempl.hrc>
+
+#include <memory>
+#include <vector>
+
+constexpr OUStringLiteral SERVICENAME_TYPEDETECTION = u"com.sun.star.document.TypeDetection";
+
+constexpr OUStringLiteral TEMPLATE_ROOT_URL = u"vnd.sun.star.hier:/templates";
+constexpr OUString TITLE = u"Title"_ustr;
+constexpr OUString IS_FOLDER = u"IsFolder"_ustr;
+constexpr OUString IS_DOCUMENT = u"IsDocument"_ustr;
+constexpr OUString TARGET_URL = u"TargetURL"_ustr;
+constexpr OUStringLiteral TEMPLATE_VERSION = u"TemplateComponentVersion";
+constexpr OUStringLiteral TEMPLATE_VERSION_VALUE = u"2";
+constexpr OUStringLiteral TYPE_FOLDER = u"application/vnd.sun.star.hier-folder";
+constexpr OUStringLiteral TYPE_LINK = u"application/vnd.sun.star.hier-link";
+constexpr OUString TYPE_FSYS_FOLDER = u"application/vnd.sun.staroffice.fsys-folder"_ustr;
+constexpr OUStringLiteral TYPE_FSYS_FILE = u"application/vnd.sun.staroffice.fsys-file";
+
+constexpr OUString PROPERTY_DIRLIST = u"DirectoryList"_ustr;
+constexpr OUString PROPERTY_NEEDSUPDATE = u"NeedsUpdate"_ustr;
+constexpr OUString PROPERTY_TYPE = u"TypeDescription"_ustr;
+
+constexpr OUString TARGET_DIR_URL = u"TargetDirURL"_ustr;
+constexpr OUStringLiteral COMMAND_DELETE = u"delete";
+
+constexpr OUString STANDARD_FOLDER = u"standard"_ustr;
+
+#define C_DELIM ';'
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::document;
+using namespace ::com::sun::star::io;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::sdbc;
+using namespace ::com::sun::star::ucb;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::container;
+using namespace ::com::sun::star::util;
+
+using namespace ::ucbhelper;
+using namespace ::comphelper;
+
+using ::std::vector;
+
+namespace {
+
+class WaitWindow_Impl : public WorkWindow
+{
+ tools::Rectangle maRect;
+ OUString maText;
+ static constexpr DrawTextFlags gnTextStyle = DrawTextFlags::Center | DrawTextFlags::VCenter | DrawTextFlags::WordBreak | DrawTextFlags::MultiLine;
+
+public:
+ WaitWindow_Impl();
+ virtual ~WaitWindow_Impl() override;
+ virtual void dispose() override;
+ virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override;
+};
+
+#define X_OFFSET 15
+#define Y_OFFSET 15
+
+
+struct NamePair_Impl
+{
+ OUString maShortName;
+ OUString maLongName;
+};
+
+class DocTemplates_EntryData_Impl;
+class GroupData_Impl;
+
+typedef vector< std::unique_ptr<GroupData_Impl> > GroupList_Impl;
+
+
+class TplTaskEnvironment : public ::cppu::WeakImplHelper< ucb::XCommandEnvironment >
+{
+ uno::Reference< task::XInteractionHandler > m_xInteractionHandler;
+
+public:
+ explicit TplTaskEnvironment( uno::Reference< task::XInteractionHandler> xInteractionHandler )
+ : m_xInteractionHandler(std::move( xInteractionHandler ))
+ {}
+
+ virtual uno::Reference<task::XInteractionHandler> SAL_CALL getInteractionHandler() override
+ { return m_xInteractionHandler; }
+
+ virtual uno::Reference<ucb::XProgressHandler> SAL_CALL getProgressHandler() override
+ { return uno::Reference<ucb::XProgressHandler>(); }
+};
+
+class SfxDocTplService : public ::cppu::WeakImplHelper< css::lang::XLocalizable, css::frame::XDocumentTemplates, css::lang::XServiceInfo >
+{
+public:
+ explicit SfxDocTplService( const css::uno::Reference < uno::XComponentContext >& xContext );
+ virtual ~SfxDocTplService() override;
+
+ virtual OUString SAL_CALL getImplementationName() override
+ {
+ return "com.sun.star.comp.sfx2.DocumentTemplates";
+ }
+
+ virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+ virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
+ {
+ css::uno::Sequence< OUString > aSeq { "com.sun.star.frame.DocumentTemplates" };
+ return aSeq;
+ }
+
+
+ // --- XLocalizable ---
+ void SAL_CALL setLocale( const css::lang::Locale & eLocale ) override;
+ css::lang::Locale SAL_CALL getLocale() override;
+
+ // --- XDocumentTemplates ---
+ css::uno::Reference< css::ucb::XContent > SAL_CALL getContent() override;
+ sal_Bool SAL_CALL storeTemplate( const OUString& GroupName,
+ const OUString& TemplateName,
+ const css::uno::Reference< css::frame::XStorable >& Storable ) override;
+ sal_Bool SAL_CALL addTemplate( const OUString& GroupName,
+ const OUString& TemplateName,
+ const OUString& SourceURL ) override;
+ sal_Bool SAL_CALL removeTemplate( const OUString& GroupName,
+ const OUString& TemplateName ) override;
+ sal_Bool SAL_CALL renameTemplate( const OUString& GroupName,
+ const OUString& OldTemplateName,
+ const OUString& NewTemplateName ) override;
+ sal_Bool SAL_CALL addGroup( const OUString& GroupName ) override;
+ sal_Bool SAL_CALL removeGroup( const OUString& GroupName ) override;
+ sal_Bool SAL_CALL renameGroup( const OUString& OldGroupName,
+ const OUString& NewGroupName ) override;
+ void SAL_CALL update() override;
+
+private:
+ bool init() { if ( !mbIsInitialized ) init_Impl(); return mbIsInitialized; }
+
+ void doUpdate();
+
+ uno::Reference< XComponentContext > mxContext;
+ uno::Reference< XCommandEnvironment > maCmdEnv;
+ uno::Reference< XDocumentProperties> m_xDocProps;
+ uno::Reference< XTypeDetection > mxType;
+
+ ::osl::Mutex maMutex;
+ Sequence< OUString > maTemplateDirs;
+ Sequence< OUString > maInternalTemplateDirs;
+ OUString maRootURL;
+ std::vector< NamePair_Impl > maNames;
+ lang::Locale maLocale;
+ Content maRootContent;
+ bool mbIsInitialized : 1;
+ bool mbLocaleSet : 1;
+
+ SfxURLRelocator_Impl maRelocator;
+
+ void init_Impl();
+ void getDefaultLocale();
+ void getDirList();
+ void readFolderList();
+ bool needsUpdate();
+ OUString getLongName( const OUString& rShortName );
+ bool setTitleForURL( const OUString& rURL, const OUString& aTitle );
+ void getTitleFromURL( const OUString& rURL, OUString& aTitle, OUString& aType, bool& bDocHasTitle );
+
+ bool addEntry( Content& rParentFolder,
+ const OUString& rTitle,
+ const OUString& rTargetURL,
+ const OUString& rType );
+
+ bool createFolder( const OUString& rNewFolderURL,
+ bool bCreateParent,
+ bool bFsysFolder,
+ Content &rNewFolder );
+
+ static bool CreateNewUniqueFolderWithPrefix( std::u16string_view aPath,
+ const OUString& aPrefix,
+ OUString& aNewFolderName,
+ OUString& aNewFolderURL,
+ Content& aNewFolder );
+ static OUString CreateNewUniqueFileWithPrefix( std::u16string_view aPath,
+ const OUString& aPrefix,
+ std::u16string_view aExt );
+
+ std::vector< beans::StringPair > ReadUINamesForTemplateDir_Impl( std::u16string_view aUserPath );
+ bool UpdateUINamesForTemplateDir_Impl( std::u16string_view aUserPath,
+ const OUString& aGroupName,
+ const OUString& aNewFolderName );
+ bool ReplaceUINamesForTemplateDir_Impl( std::u16string_view aUserPath,
+ const OUString& aFsysGroupName,
+ std::u16string_view aOldGroupName,
+ const OUString& aNewGroupName );
+ void RemoveUINamesForTemplateDir_Impl( std::u16string_view aUserPath,
+ std::u16string_view aGroupName );
+ bool WriteUINamesForTemplateDir_Impl( std::u16string_view aUserPath,
+ const std::vector< beans::StringPair >& aUINames );
+
+ OUString CreateNewGroupFsys( const OUString& rGroupName, Content& aGroup );
+
+ static bool removeContent( Content& rContent );
+ bool removeContent( const OUString& rContentURL );
+
+ bool setProperty( Content& rContent,
+ const OUString& rPropName,
+ const Any& rPropValue );
+ bool getProperty( Content& rContent,
+ const OUString& rPropName,
+ Any& rPropValue );
+
+ void createFromContent( GroupList_Impl& rList,
+ Content &rContent,
+ bool bHierarchy,
+ bool bWriteableContent );
+ void addHierGroup( GroupList_Impl& rList,
+ const OUString& rTitle,
+ const OUString& rOwnURL );
+ void addFsysGroup( GroupList_Impl& rList,
+ const OUString& rTitle,
+ const OUString& rUITitle,
+ const OUString& rOwnURL,
+ bool bWriteableGroup );
+ void removeFromHierarchy( DocTemplates_EntryData_Impl const *pData );
+ void addToHierarchy( GroupData_Impl const *pGroup,
+ DocTemplates_EntryData_Impl const *pData );
+
+ void removeFromHierarchy( GroupData_Impl const *pGroup );
+ void addGroupToHierarchy( GroupData_Impl *pGroup );
+
+ void updateData( DocTemplates_EntryData_Impl const *pData );
+
+ //See: #i66157# and rhbz#1065807
+ //return which template dir the rURL is a subpath of
+ OUString findParentTemplateDir(const OUString& rURL) const;
+
+ //See: #i66157# and rhbz#1065807
+ //return true if rURL is a path (or subpath of) a dir which is not a user path
+ //which implies neither it or its contents can be removed
+ bool isInternalTemplateDir(const OUString& rURL) const;
+};
+
+
+class DocTemplates_EntryData_Impl
+{
+ OUString maTitle;
+ OUString maType;
+ OUString maTargetURL;
+ OUString maHierarchyURL;
+
+ bool mbInHierarchy : 1;
+ bool mbInUse : 1;
+ bool mbUpdateType : 1;
+ bool mbUpdateLink : 1;
+
+public:
+ explicit DocTemplates_EntryData_Impl( OUString aTitle );
+
+ void setInUse() { mbInUse = true; }
+ void setHierarchy( bool bInHierarchy ) { mbInHierarchy = bInHierarchy; }
+ void setUpdateLink( bool bUpdateLink ) { mbUpdateLink = bUpdateLink; }
+ void setUpdateType( bool bUpdateType ) { mbUpdateType = bUpdateType; }
+
+ bool getInUse() const { return mbInUse; }
+ bool getInHierarchy() const { return mbInHierarchy; }
+ bool getUpdateLink() const { return mbUpdateLink; }
+ bool getUpdateType() const { return mbUpdateType; }
+
+ const OUString& getHierarchyURL() const { return maHierarchyURL; }
+ const OUString& getTargetURL() const { return maTargetURL; }
+ const OUString& getTitle() const { return maTitle; }
+ const OUString& getType() const { return maType; }
+
+ void setHierarchyURL( const OUString& rURL ) { maHierarchyURL = rURL; }
+ void setTargetURL( const OUString& rURL ) { maTargetURL = rURL; }
+ void setType( const OUString& rType ) { maType = rType; }
+};
+
+
+class GroupData_Impl
+{
+ std::vector< std::unique_ptr<DocTemplates_EntryData_Impl> > maEntries;
+ OUString maTitle;
+ OUString maHierarchyURL;
+ OUString maTargetURL;
+ bool mbInUse : 1;
+ bool mbInHierarchy : 1;
+
+public:
+ explicit GroupData_Impl( OUString aTitle );
+
+ void setInUse() { mbInUse = true; }
+ void setHierarchy( bool bInHierarchy ) { mbInHierarchy = bInHierarchy; }
+ void setHierarchyURL( const OUString& rURL ) { maHierarchyURL = rURL; }
+ void setTargetURL( const OUString& rURL ) { maTargetURL = rURL; }
+
+ bool getInUse() const { return mbInUse; }
+ bool getInHierarchy() const { return mbInHierarchy; }
+ const OUString& getHierarchyURL() const { return maHierarchyURL; }
+ const OUString& getTargetURL() const { return maTargetURL; }
+ const OUString& getTitle() const { return maTitle; }
+
+ DocTemplates_EntryData_Impl* addEntry( const OUString& rTitle,
+ const OUString& rTargetURL,
+ const OUString& rType,
+ const OUString& rHierURL );
+ size_t count() { return maEntries.size(); }
+ DocTemplates_EntryData_Impl* getEntry( size_t nPos ) { return maEntries[ nPos ].get(); }
+};
+
+
+// private SfxDocTplService_Impl
+
+void SfxDocTplService::init_Impl()
+{
+ uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+ uno::Reference < task::XInteractionHandler > xInteractionHandler(
+ task::InteractionHandler::createWithParent(xContext, nullptr), uno::UNO_QUERY_THROW );
+ maCmdEnv = new TplTaskEnvironment( xInteractionHandler );
+
+ ::osl::ClearableMutexGuard aGuard( maMutex );
+ bool bIsInitialized = false;
+ bool bNeedsUpdate = false;
+
+ if ( !mbLocaleSet )
+ getDefaultLocale();
+
+ // convert locale to string
+ // set maRootContent to the root of the templates hierarchy. Create the
+ // entry if necessary
+
+ maRootURL = TEMPLATE_ROOT_URL + "/" + LanguageTag::convertToBcp47(maLocale);
+
+ const OUString aTemplVersPropName( TEMPLATE_VERSION );
+ const OUString aTemplVers( TEMPLATE_VERSION_VALUE );
+ if ( Content::create( maRootURL, maCmdEnv, comphelper::getProcessComponentContext(), maRootContent ) )
+ {
+ uno::Any aValue;
+ OUString aPropValue;
+ if ( getProperty( maRootContent, aTemplVersPropName, aValue )
+ && ( aValue >>= aPropValue )
+ && aPropValue == aTemplVers )
+ {
+ bIsInitialized = true;
+ }
+ else
+ removeContent( maRootContent );
+ }
+
+ if ( !bIsInitialized )
+ {
+ if ( createFolder( maRootURL, true, false, maRootContent )
+ && setProperty( maRootContent, aTemplVersPropName, uno::Any( aTemplVers ) ) )
+ bIsInitialized = true;
+
+ bNeedsUpdate = true;
+ }
+
+ if ( bIsInitialized )
+ {
+ try {
+ m_xDocProps.set(document::DocumentProperties::create(
+ ::comphelper::getProcessComponentContext()));
+ } catch (uno::RuntimeException const&) {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "SfxDocTplService_Impl::init_Impl: cannot create DocumentProperties service:");
+ }
+
+ mxType.set( mxContext->getServiceManager()->createInstanceWithContext(SERVICENAME_TYPEDETECTION, mxContext), UNO_QUERY );
+
+ getDirList();
+ readFolderList();
+
+ if ( bNeedsUpdate )
+ {
+ aGuard.clear();
+ SolarMutexClearableGuard aSolarGuard;
+
+ VclPtrInstance< WaitWindow_Impl > pWin;
+ aSolarGuard.clear();
+ {
+ osl::MutexGuard anotherGuard(maMutex);
+ doUpdate();
+ }
+ SolarMutexGuard aSecondSolarGuard;
+
+ pWin.disposeAndClear();
+ }
+ else if ( needsUpdate() )
+ // the UI should be shown only on the first update
+ doUpdate();
+ }
+ else
+ {
+ SAL_WARN( "sfx.doc", "init_Impl(): Could not create root" );
+ }
+
+ mbIsInitialized = bIsInitialized;
+}
+
+
+void SfxDocTplService::getDefaultLocale()
+{
+ if ( !mbLocaleSet )
+ {
+ ::osl::MutexGuard aGuard( maMutex );
+ if ( !mbLocaleSet )
+ {
+ maLocale = LanguageTag::convertToLocale( utl::ConfigManager::getUILocale(), false);
+ mbLocaleSet = true;
+ }
+ }
+}
+
+const char* TEMPLATE_SHORT_NAMES_ARY[] =
+{
+ "standard",
+ "styles",
+ "officorr",
+ "offimisc",
+ "personal",
+ "presnt",
+ "draw",
+ "l10n",
+};
+
+void SfxDocTplService::readFolderList()
+{
+ SolarMutexGuard aGuard;
+
+ static_assert( SAL_N_ELEMENTS(TEMPLATE_SHORT_NAMES_ARY) == SAL_N_ELEMENTS(TEMPLATE_LONG_NAMES_ARY), "mismatch array lengths" );
+ const size_t nCount = std::min(SAL_N_ELEMENTS(TEMPLATE_SHORT_NAMES_ARY), SAL_N_ELEMENTS(TEMPLATE_LONG_NAMES_ARY));
+ for (size_t i = 0; i < nCount; ++i)
+ {
+ NamePair_Impl aPair;
+ aPair.maShortName = OUString::createFromAscii(TEMPLATE_SHORT_NAMES_ARY[i]);
+ aPair.maLongName = SfxResId(TEMPLATE_LONG_NAMES_ARY[i]);
+
+ maNames.push_back( aPair );
+ }
+}
+
+
+OUString SfxDocTplService::getLongName( const OUString& rShortName )
+{
+ OUString aRet;
+
+ for (auto const & rPair : maNames)
+ {
+ if ( rPair.maShortName == rShortName )
+ {
+ aRet = rPair.maLongName;
+ break;
+ }
+ }
+
+ if ( aRet.isEmpty() )
+ aRet = rShortName;
+
+ return aRet;
+}
+
+
+void SfxDocTplService::getDirList()
+{
+ Any aValue;
+
+ // Get the template dir list
+ // TODO/LATER: let use service, register listener
+ INetURLObject aURL;
+ OUString aDirs = SvtPathOptions().GetTemplatePath();
+ sal_Int32 nCount = comphelper::string::getTokenCount(aDirs, C_DELIM);
+
+ maTemplateDirs = Sequence< OUString >( nCount );
+
+ uno::Reference< util::XMacroExpander > xExpander = util::theMacroExpander::get(mxContext);
+
+ sal_Int32 nIdx{ 0 };
+ for (auto& rTemplateDir : asNonConstRange(maTemplateDirs))
+ {
+ aURL.SetSmartProtocol( INetProtocol::File );
+ aURL.SetURL( o3tl::getToken(aDirs, 0, C_DELIM, nIdx ) );
+ rTemplateDir = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ if (xExpander && rTemplateDir.startsWithIgnoreAsciiCase("vnd.sun.star.expand:", &rTemplateDir))
+ {
+ rTemplateDir
+ = rtl::Uri::decode(rTemplateDir, rtl_UriDecodeStrict, RTL_TEXTENCODING_UTF8);
+ rTemplateDir = xExpander->expandMacros( rTemplateDir );
+ }
+ }
+
+ aValue <<= maTemplateDirs;
+
+ css::uno::Reference< css::util::XPathSettings > xPathSettings =
+ css::util::thePathSettings::get(mxContext);
+
+ // load internal paths
+ Any aAny = xPathSettings->getPropertyValue( "Template_internal" );
+ aAny >>= maInternalTemplateDirs;
+
+ for (auto& rInternalTemplateDir : asNonConstRange(maInternalTemplateDirs))
+ {
+ //expand vnd.sun.star.expand: and remove "..." from them
+ //to normalize into the expected url patterns
+ maRelocator.makeRelocatableURL(rInternalTemplateDir);
+ maRelocator.makeAbsoluteURL(rInternalTemplateDir);
+ }
+
+ // Store the template dir list
+ setProperty( maRootContent, PROPERTY_DIRLIST, aValue );
+}
+
+
+bool SfxDocTplService::needsUpdate()
+{
+ bool bNeedsUpdate = true;
+ Any aValue;
+
+ // Get the template dir list
+ bool bHasProperty = getProperty( maRootContent, PROPERTY_NEEDSUPDATE, aValue );
+
+ if ( bHasProperty )
+ aValue >>= bNeedsUpdate;
+
+ // the old template component also checks this state, but it is initialized from this component
+ // so if this component was already updated the old component does not need such an update
+ ::svt::TemplateFolderCache aTempCache;
+ if ( !bNeedsUpdate )
+ bNeedsUpdate = aTempCache.needsUpdate();
+
+ if ( bNeedsUpdate )
+ aTempCache.storeState();
+
+ return bNeedsUpdate;
+}
+
+
+bool SfxDocTplService::setTitleForURL( const OUString& rURL, const OUString& aTitle )
+{
+ if (m_xDocProps.is())
+ {
+ try
+ {
+ m_xDocProps->loadFromMedium(rURL, Sequence<PropertyValue>());
+ m_xDocProps->setTitle(aTitle);
+
+ uno::Reference< embed::XStorage > xStorage = ::comphelper::OStorageHelper::GetStorageFromURL(
+ rURL, embed::ElementModes::READWRITE);
+
+ uno::Sequence<beans::PropertyValue> medium( comphelper::InitPropertySequence({
+ { "DocumentBaseURL", Any(rURL) },
+ { "URL", Any(rURL) }
+ }));
+
+ m_xDocProps->storeToStorage(xStorage, medium);
+ return true;
+ }
+ catch ( Exception& )
+ {
+ }
+ }
+ return false;
+}
+
+
+void SfxDocTplService::getTitleFromURL( const OUString& rURL, OUString& aTitle, OUString& aType, bool& bDocHasTitle )
+{
+ bDocHasTitle = false;
+
+ if (m_xDocProps.is())
+ {
+ try
+ {
+ m_xDocProps->loadFromMedium(rURL, Sequence<PropertyValue>());
+ aTitle = m_xDocProps->getTitle();
+ }
+ catch ( Exception& )
+ {
+ }
+ }
+
+ if ( aType.isEmpty() && mxType.is() )
+ {
+ const OUString aDocType {mxType->queryTypeByURL( rURL )};
+ if ( !aDocType.isEmpty() )
+ try
+ {
+ uno::Reference< container::XNameAccess > xTypeDetection( mxType, uno::UNO_QUERY_THROW );
+ SequenceAsHashMap aTypeProps( xTypeDetection->getByName( aDocType ) );
+ aType = aTypeProps.getUnpackedValueOrDefault(
+ "MediaType",
+ OUString() );
+ }
+ catch( uno::Exception& )
+ {}
+ }
+
+ if ( aTitle.isEmpty() )
+ {
+ INetURLObject aURL( rURL );
+ aURL.CutExtension();
+ aTitle = aURL.getName( INetURLObject::LAST_SEGMENT, true,
+ INetURLObject::DecodeMechanism::WithCharset );
+ }
+ else
+ bDocHasTitle = true;
+}
+
+
+bool SfxDocTplService::addEntry( Content& rParentFolder,
+ const OUString& rTitle,
+ const OUString& rTargetURL,
+ const OUString& rType )
+{
+ bool bAddedEntry = false;
+
+ INetURLObject aLinkObj( rParentFolder.getURL() );
+ aLinkObj.insertName( rTitle, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ const OUString aLinkURL {aLinkObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
+
+ Content aLink;
+
+ if ( ! Content::create( aLinkURL, maCmdEnv, comphelper::getProcessComponentContext(), aLink ) )
+ {
+ Sequence< Any > aValues{ Any(rTitle), Any(false), Any(rTargetURL) };
+
+ try
+ {
+ rParentFolder.insertNewContent( TYPE_LINK, { TITLE, IS_FOLDER, TARGET_URL }, aValues, aLink );
+ setProperty( aLink, PROPERTY_TYPE, Any( rType ) );
+ bAddedEntry = true;
+ }
+ catch( Exception& )
+ {}
+ }
+ return bAddedEntry;
+}
+
+
+bool SfxDocTplService::createFolder( const OUString& rNewFolderURL,
+ bool bCreateParent,
+ bool bFsysFolder,
+ Content &rNewFolder )
+{
+ Content aParent;
+ bool bCreatedFolder = false;
+ INetURLObject aParentURL( rNewFolderURL );
+ const OUString aFolderName {aParentURL.getName( INetURLObject::LAST_SEGMENT, true,
+ INetURLObject::DecodeMechanism::WithCharset )};
+
+ // compute the parent folder url from the new folder url
+ // and remove the final slash, because Content::create doesn't
+ // like it
+ aParentURL.removeSegment();
+ if ( aParentURL.getSegmentCount() >= 1 )
+ aParentURL.removeFinalSlash();
+
+ // if the parent exists, we can continue with the creation of the
+ // new folder, we have to create the parent otherwise ( as long as
+ // bCreateParent is set to true )
+ if ( Content::create( aParentURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), maCmdEnv, comphelper::getProcessComponentContext(), aParent ) )
+ {
+ try
+ {
+ Sequence< Any > aValues{ Any(aFolderName), Any(true) };
+ OUString aType;
+
+ if ( bFsysFolder )
+ aType = TYPE_FSYS_FOLDER;
+ else
+ aType = TYPE_FOLDER;
+
+ aParent.insertNewContent( aType, { TITLE, IS_FOLDER }, aValues, rNewFolder );
+ bCreatedFolder = true;
+ }
+ catch( Exception const & )
+ {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "createFolder(): Could not create new folder" );
+ }
+ }
+ else if ( bCreateParent )
+ {
+ // if the parent doesn't exists and bCreateParent is set to true,
+ // we try to create the parent and if this was successful, we
+ // try to create the new folder again ( but this time, we set
+ // bCreateParent to false to avoid endless recursions )
+ if ( ( aParentURL.getSegmentCount() >= 1 ) &&
+ createFolder( aParentURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), bCreateParent, bFsysFolder, aParent ) )
+ {
+ bCreatedFolder = createFolder( rNewFolderURL, false, bFsysFolder, rNewFolder );
+ }
+ }
+
+ return bCreatedFolder;
+}
+
+
+bool SfxDocTplService::CreateNewUniqueFolderWithPrefix( std::u16string_view aPath,
+ const OUString& aPrefix,
+ OUString& aNewFolderName,
+ OUString& aNewFolderURL,
+ Content& aNewFolder )
+{
+ bool bCreated = false;
+ INetURLObject aDirPath( aPath );
+
+ Content aParent;
+ uno::Reference< XCommandEnvironment > aQuietEnv;
+ if ( Content::create( aDirPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aQuietEnv, comphelper::getProcessComponentContext(), aParent ) )
+ {
+ for ( sal_Int32 nInd = 0; nInd < 32000; nInd++ )
+ {
+ OUString aTryName = aPrefix;
+ if ( nInd )
+ aTryName += OUString::number( nInd );
+
+ try
+ {
+ Sequence< Any > aValues{ Any(aTryName), Any(true) };
+ bCreated = aParent.insertNewContent( TYPE_FSYS_FOLDER, { TITLE, IS_FOLDER }, aValues, aNewFolder );
+ }
+ catch( ucb::NameClashException& )
+ {
+ // if there is already an element, retry
+ }
+ catch( Exception& )
+ {
+ INetURLObject aObjPath( aDirPath );
+ aObjPath.insertName( aTryName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ // if there is already an element, retry
+ // if there was another error, do not try any more
+ if ( !::utl::UCBContentHelper::Exists( aObjPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
+ break;
+ }
+
+ if ( bCreated )
+ {
+ aNewFolderName = aTryName;
+ aNewFolderURL = aNewFolder.get()->getIdentifier()->getContentIdentifier();
+ break;
+ }
+ }
+ }
+
+ return bCreated;
+}
+
+
+OUString SfxDocTplService::CreateNewUniqueFileWithPrefix( std::u16string_view aPath,
+ const OUString& aPrefix,
+ std::u16string_view aExt )
+{
+ OUString aNewFileURL;
+ INetURLObject aDirPath( aPath );
+
+ Content aParent;
+
+ uno::Reference< XCommandEnvironment > aQuietEnv;
+ if ( Content::create( aDirPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aQuietEnv, comphelper::getProcessComponentContext(), aParent ) )
+ {
+ for ( sal_Int32 nInd = 0; nInd < 32000; nInd++ )
+ {
+ Content aNewFile;
+ bool bCreated = false;
+ OUString aTryName = aPrefix;
+ if ( nInd )
+ aTryName += OUString::number( nInd );
+ if ( aExt.empty() || aExt[0] != '.' )
+ aTryName += ".";
+ aTryName += aExt;
+
+ try
+ {
+ Sequence< Any > aValues{ Any(aTryName), Any(true) };
+ bCreated = aParent.insertNewContent( TYPE_FSYS_FILE, { TITLE, IS_DOCUMENT }, aValues, aNewFile );
+ }
+ catch( ucb::NameClashException& )
+ {
+ // if there is already an element, retry
+ }
+ catch( Exception& )
+ {
+ INetURLObject aObjPath( aPath );
+ aObjPath.insertName( aTryName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ // if there is already an element, retry
+ // if there was another error, do not try any more
+ if ( !::utl::UCBContentHelper::Exists( aObjPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
+ break;
+ }
+
+ if ( bCreated )
+ {
+ aNewFileURL = aNewFile.get()->getIdentifier()->getContentIdentifier();
+ break;
+ }
+ }
+ }
+
+ return aNewFileURL;
+}
+
+
+bool SfxDocTplService::removeContent( Content& rContent )
+{
+ bool bRemoved = false;
+ try
+ {
+ Any aArg( true );
+
+ rContent.executeCommand( COMMAND_DELETE, aArg );
+ bRemoved = true;
+ }
+ catch ( RuntimeException& ) {}
+ catch ( Exception& ) {}
+
+ return bRemoved;
+}
+
+
+bool SfxDocTplService::removeContent( const OUString& rContentURL )
+{
+ Content aContent;
+
+ if ( Content::create( rContentURL, maCmdEnv, comphelper::getProcessComponentContext(), aContent ) )
+ return removeContent( aContent );
+ return false;
+}
+
+
+bool SfxDocTplService::setProperty( Content& rContent,
+ const OUString& rPropName,
+ const Any& rPropValue )
+{
+ bool bPropertySet = false;
+
+ // Store the property
+ try
+ {
+ Any aPropValue( rPropValue );
+ uno::Reference< XPropertySetInfo > aPropInfo = rContent.getProperties();
+
+ // check, whether or not the property exists, create it, when not
+ if ( !aPropInfo.is() || !aPropInfo->hasPropertyByName( rPropName ) )
+ {
+ uno::Reference< XPropertyContainer > xProperties( rContent.get(), UNO_QUERY );
+ if ( xProperties.is() )
+ {
+ try
+ {
+ xProperties->addProperty( rPropName, PropertyAttribute::MAYBEVOID, rPropValue );
+ }
+ catch( PropertyExistException& ) {}
+ catch( IllegalTypeException& ) {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "" );
+ }
+ catch( IllegalArgumentException& ) {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "" );
+ }
+ }
+ }
+
+ // To ensure a reloctable office installation, the path to the
+ // office installation directory must never be stored directly.
+ if ( SfxURLRelocator_Impl::propertyCanContainOfficeDir( rPropName ) )
+ {
+ OUString aValue;
+ if ( rPropValue >>= aValue )
+ {
+ maRelocator.makeRelocatableURL( aValue );
+ aPropValue <<= aValue;
+ }
+ else
+ {
+ Sequence< OUString > aValues;
+ if ( rPropValue >>= aValues )
+ {
+ for ( auto& rValue : asNonConstRange(aValues) )
+ {
+ maRelocator.makeRelocatableURL( rValue );
+ }
+ aPropValue <<= aValues;
+ }
+ else
+ {
+ OSL_FAIL( "Unsupported property value type" );
+ }
+ }
+ }
+
+ // now set the property
+
+ rContent.setPropertyValue( rPropName, aPropValue );
+ bPropertySet = true;
+ }
+ catch ( RuntimeException& ) {}
+ catch ( Exception& ) {}
+
+ return bPropertySet;
+}
+
+
+bool SfxDocTplService::getProperty(Content& rContent, const OUString& rPropName, Any& rPropValue)
+{
+ bool bGotProperty = false;
+
+ // Get the property
+ try
+ {
+ uno::Reference< XPropertySetInfo > aPropInfo = rContent.getProperties();
+
+ // check, whether or not the property exists
+ if ( !aPropInfo.is() || !aPropInfo->hasPropertyByName( rPropName ) )
+ {
+ return false;
+ }
+
+ // now get the property
+
+ rPropValue = rContent.getPropertyValue( rPropName );
+
+ // To ensure a reloctable office installation, the path to the
+ // office installation directory must never be stored directly.
+ if ( SfxURLRelocator_Impl::propertyCanContainOfficeDir( rPropName ) )
+ {
+ OUString aValue;
+ if ( rPropValue >>= aValue )
+ {
+ maRelocator.makeAbsoluteURL( aValue );
+ rPropValue <<= aValue;
+ }
+ else
+ {
+ Sequence< OUString > aValues;
+ if ( rPropValue >>= aValues )
+ {
+ for ( auto& rValue : asNonConstRange(aValues) )
+ {
+ maRelocator.makeAbsoluteURL( rValue );
+ }
+ rPropValue <<= aValues;
+ }
+ else
+ {
+ OSL_FAIL( "Unsupported property value type" );
+ }
+ }
+ }
+
+ bGotProperty = true;
+ }
+ catch ( RuntimeException& ) {}
+ catch ( Exception& ) {}
+
+ return bGotProperty;
+}
+
+SfxDocTplService::SfxDocTplService( const uno::Reference< XComponentContext > & xContext )
+ : mxContext(xContext), mbIsInitialized(false), mbLocaleSet(false), maRelocator(xContext)
+{
+}
+
+
+SfxDocTplService::~SfxDocTplService()
+{
+ ::osl::MutexGuard aGuard( maMutex );
+ maNames.clear();
+}
+
+
+lang::Locale SfxDocTplService::getLocale()
+{
+ ::osl::MutexGuard aGuard( maMutex );
+
+ if ( !mbLocaleSet )
+ getDefaultLocale();
+
+ return maLocale;
+}
+
+
+void SfxDocTplService::setLocale( const lang::Locale &rLocale )
+{
+ ::osl::MutexGuard aGuard( maMutex );
+
+ if ( mbLocaleSet && (
+ ( maLocale.Language != rLocale.Language ) ||
+ ( maLocale.Country != rLocale.Country ) ||
+ ( maLocale.Variant != rLocale.Variant ) ) )
+ mbIsInitialized = false;
+
+ maLocale = rLocale;
+ mbLocaleSet = true;
+}
+
+
+void SfxDocTplService::update()
+{
+ if (!init())
+ return;
+
+ doUpdate();
+}
+
+
+void SfxDocTplService::doUpdate()
+{
+ ::osl::MutexGuard aGuard( maMutex );
+
+ const OUString aPropName( PROPERTY_NEEDSUPDATE );
+ Any aValue;
+
+ aValue <<= true;
+ setProperty( maRootContent, aPropName, aValue );
+
+ GroupList_Impl aGroupList;
+
+ // get the entries from the hierarchy
+ createFromContent( aGroupList, maRootContent, true, false );
+
+ // get the entries from the template directories
+ sal_Int32 nCountDir = maTemplateDirs.getLength();
+ const OUString* pDirs = maTemplateDirs.getConstArray();
+ Content aDirContent;
+
+ // the last directory in the list must be writable
+ bool bWriteableDirectory = true;
+
+ // the target folder might not exist, for this reason no interaction handler should be used
+ uno::Reference< XCommandEnvironment > aQuietEnv;
+
+ while ( nCountDir )
+ {
+ nCountDir--;
+ if ( Content::create( pDirs[ nCountDir ], aQuietEnv, comphelper::getProcessComponentContext(), aDirContent ) )
+ {
+ createFromContent( aGroupList, aDirContent, false, bWriteableDirectory );
+ }
+
+ bWriteableDirectory = false;
+ }
+
+ // now check the list
+ for(std::unique_ptr<GroupData_Impl>& pGroup : aGroupList)
+ {
+ if ( pGroup->getInUse() )
+ {
+ if ( pGroup->getInHierarchy() )
+ {
+ Content aGroup;
+ if ( Content::create( pGroup->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
+ setProperty( aGroup,
+ TARGET_DIR_URL,
+ Any( pGroup->getTargetURL() ) );
+
+ size_t nCount = pGroup->count();
+ for ( size_t i=0; i<nCount; i++ )
+ {
+ DocTemplates_EntryData_Impl *pData = pGroup->getEntry( i );
+ if ( ! pData->getInUse() )
+ {
+ if ( pData->getInHierarchy() )
+ removeFromHierarchy( pData ); // delete entry in hierarchy
+ else
+ addToHierarchy( pGroup.get(), pData ); // add entry to hierarchy
+ }
+ else if ( pData->getUpdateType() ||
+ pData->getUpdateLink() )
+ {
+ updateData( pData );
+ }
+ }
+ }
+ else
+ {
+ addGroupToHierarchy( pGroup.get() ); // add group to hierarchy
+ }
+ }
+ else
+ removeFromHierarchy( pGroup.get() ); // delete group from hierarchy
+ }
+ aGroupList.clear();
+
+ aValue <<= false;
+ setProperty( maRootContent, aPropName, aValue );
+}
+
+
+std::vector< beans::StringPair > SfxDocTplService::ReadUINamesForTemplateDir_Impl( std::u16string_view aUserPath )
+{
+ INetURLObject aLocObj( aUserPath );
+ aLocObj.insertName( u"groupuinames.xml", false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ Content aLocContent;
+
+ // TODO/LATER: Use hashmap in future
+ std::vector< beans::StringPair > aUINames;
+ if ( Content::create( aLocObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), uno::Reference < ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext(), aLocContent ) )
+ {
+ try
+ {
+ uno::Reference< io::XInputStream > xLocStream = aLocContent.openStream();
+ if ( xLocStream.is() )
+ aUINames = DocTemplLocaleHelper::ReadGroupLocalizationSequence( xLocStream, mxContext );
+ }
+ catch( uno::Exception& )
+ {}
+ }
+
+ return aUINames;
+}
+
+
+bool SfxDocTplService::UpdateUINamesForTemplateDir_Impl( std::u16string_view aUserPath,
+ const OUString& aGroupName,
+ const OUString& aNewFolderName )
+{
+ std::vector< beans::StringPair > aUINames = ReadUINamesForTemplateDir_Impl( aUserPath );
+ sal_Int32 nLen = aUINames.size();
+
+ // it is possible that the name is used already, but it should be checked before
+ for ( sal_Int32 nInd = 0; nInd < nLen; nInd++ )
+ if ( aUINames[nInd].First == aNewFolderName )
+ return false;
+
+ aUINames.resize( ++nLen );
+ aUINames[nLen-1].First = aNewFolderName;
+ aUINames[nLen-1].Second = aGroupName;
+
+ return WriteUINamesForTemplateDir_Impl( aUserPath, aUINames );
+}
+
+
+bool SfxDocTplService::ReplaceUINamesForTemplateDir_Impl( std::u16string_view aUserPath,
+ const OUString& aDefaultFsysGroupName,
+ std::u16string_view aOldGroupName,
+ const OUString& aNewGroupName )
+{
+ std::vector< beans::StringPair > aUINames = ReadUINamesForTemplateDir_Impl( aUserPath );
+ sal_Int32 nLen = aUINames.size();
+
+ bool bChanged = false;
+ for ( sal_Int32 nInd = 0; nInd < nLen; nInd++ )
+ if ( aUINames[nInd].Second == aOldGroupName )
+ {
+ aUINames[nInd].Second = aNewGroupName;
+ bChanged = true;
+ }
+
+ if ( !bChanged )
+ {
+ aUINames.resize( ++nLen );
+ aUINames[nLen-1].First = aDefaultFsysGroupName;
+ aUINames[nLen-1].Second = aNewGroupName;
+ }
+ return WriteUINamesForTemplateDir_Impl( aUserPath, aUINames );
+}
+
+
+void SfxDocTplService::RemoveUINamesForTemplateDir_Impl( std::u16string_view aUserPath,
+ std::u16string_view aGroupName )
+{
+ std::vector< beans::StringPair > aUINames = ReadUINamesForTemplateDir_Impl( aUserPath );
+ sal_Int32 nLen = aUINames.size();
+ std::vector< beans::StringPair > aNewUINames( nLen );
+ sal_Int32 nNewLen = 0;
+
+ bool bChanged = false;
+ for ( sal_Int32 nInd = 0; nInd < nLen; nInd++ )
+ if ( aUINames[nInd].Second == aGroupName )
+ bChanged = true;
+ else
+ {
+ nNewLen++;
+ aNewUINames[nNewLen-1].First = aUINames[nInd].First;
+ aNewUINames[nNewLen-1].Second = aUINames[nInd].Second;
+ }
+
+ aNewUINames.resize( nNewLen );
+
+ if (bChanged)
+ WriteUINamesForTemplateDir_Impl( aUserPath, aNewUINames );
+}
+
+
+bool SfxDocTplService::WriteUINamesForTemplateDir_Impl( std::u16string_view aUserPath,
+ const std::vector< beans::StringPair >& aUINames )
+{
+ bool bResult = false;
+ try {
+ uno::Reference< io::XTempFile > xTempFile(
+ io::TempFile::create(mxContext),
+ uno::UNO_SET_THROW );
+
+ uno::Reference< io::XOutputStream > xOutStream = xTempFile->getOutputStream();
+ if ( !xOutStream.is() )
+ throw uno::RuntimeException();
+
+ DocTemplLocaleHelper::WriteGroupLocalizationSequence( xOutStream, aUINames, mxContext);
+ try {
+ // the SAX writer might close the stream
+ xOutStream->closeOutput();
+ } catch( uno::Exception& )
+ {}
+
+ Content aTargetContent( OUString(aUserPath), maCmdEnv, comphelper::getProcessComponentContext() );
+ Content aSourceContent( xTempFile->getUri(), maCmdEnv, comphelper::getProcessComponentContext() );
+ aTargetContent.transferContent( aSourceContent,
+ InsertOperation::Copy,
+ "groupuinames.xml",
+ ucb::NameClash::OVERWRITE,
+ "text/xml" );
+
+ bResult = true;
+ }
+ catch ( uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "");
+ }
+
+ return bResult;
+}
+
+
+OUString SfxDocTplService::CreateNewGroupFsys( const OUString& rGroupName, Content& aGroup )
+{
+ OUString aResultURL;
+
+ if ( maTemplateDirs.hasElements() )
+ {
+ OUString aTargetPath = maTemplateDirs[ maTemplateDirs.getLength() - 1 ];
+
+ // create a new folder with the given name
+ Content aNewFolder;
+ OUString aNewFolderName;
+
+ // the Fsys name instead of GroupName should be used, the groupuinames must be added also
+ if ( !CreateNewUniqueFolderWithPrefix( aTargetPath,
+ rGroupName,
+ aNewFolderName,
+ aResultURL,
+ aNewFolder )
+ && !CreateNewUniqueFolderWithPrefix( aTargetPath,
+ "UserGroup",
+ aNewFolderName,
+ aResultURL,
+ aNewFolder ) )
+
+ return OUString();
+
+ if ( !UpdateUINamesForTemplateDir_Impl( aTargetPath, rGroupName, aNewFolderName ) )
+ {
+ // we could not create the groupuinames for the folder, so we delete the group in
+ // the folder and return
+ removeContent( aNewFolder );
+ return OUString();
+ }
+
+ // Now set the target url for this group and we are done
+ Any aValue( aResultURL );
+
+ if ( ! setProperty( aGroup, TARGET_DIR_URL, aValue ) )
+ {
+ removeContent( aNewFolder );
+ return OUString();
+ }
+ }
+
+ return aResultURL;
+}
+
+
+sal_Bool SfxDocTplService::addGroup( const OUString& rGroupName )
+{
+ if (!init())
+ return false;
+
+ ::osl::MutexGuard aGuard( maMutex );
+
+ // Check, whether or not there is a group with this name
+ Content aNewGroup;
+ OUString aNewGroupURL;
+ INetURLObject aNewGroupObj( maRootURL );
+
+ aNewGroupObj.insertName( rGroupName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+
+ aNewGroupURL = aNewGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ if ( Content::create( aNewGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aNewGroup ) ||
+ ! createFolder( aNewGroupURL, false, false, aNewGroup ) )
+ {
+ // if there already was a group with this name or the new group
+ // could not be created, we return here
+ return false;
+ }
+
+ // Get the user template path entry ( new group will always
+ // be added in the user template path )
+ sal_Int32 nIndex;
+ OUString aUserPath;
+
+ nIndex = maTemplateDirs.getLength();
+ if ( nIndex )
+ nIndex--;
+ else
+ return false; // We don't know where to add the group
+
+ aUserPath = maTemplateDirs[ nIndex ];
+
+ // create a new folder with the given name
+ Content aNewFolder;
+ OUString aNewFolderName;
+ OUString aNewFolderURL;
+
+ // the Fsys name instead of GroupName should be used, the groupuinames must be added also
+ if ( !CreateNewUniqueFolderWithPrefix( aUserPath,
+ rGroupName,
+ aNewFolderName,
+ aNewFolderURL,
+ aNewFolder )
+ && !CreateNewUniqueFolderWithPrefix( aUserPath,
+ "UserGroup",
+ aNewFolderName,
+ aNewFolderURL,
+ aNewFolder ) )
+ {
+ // we could not create the folder, so we delete the group in the
+ // hierarchy and return
+ removeContent( aNewGroup );
+ return false;
+ }
+
+ if ( !UpdateUINamesForTemplateDir_Impl( aUserPath, rGroupName, aNewFolderName ) )
+ {
+ // we could not create the groupuinames for the folder, so we delete the group in the
+ // hierarchy, the folder and return
+ removeContent( aNewGroup );
+ removeContent( aNewFolder );
+ return false;
+ }
+
+ // Now set the target url for this group and we are done
+ Any aValue( aNewFolderURL );
+
+ if ( ! setProperty( aNewGroup, TARGET_DIR_URL, aValue ) )
+ {
+ removeContent( aNewGroup );
+ removeContent( aNewFolder );
+ return false;
+ }
+
+ return true;
+}
+
+
+sal_Bool SfxDocTplService::removeGroup( const OUString& rGroupName )
+{
+ // remove all the elements that have the prefix aTargetURL
+ // if the group does not have other elements remove it
+
+ if (!init())
+ return false;
+
+ ::osl::MutexGuard aGuard( maMutex );
+
+ bool bResult = false;
+
+ // create the group url
+ INetURLObject aGroupObj( maRootURL );
+ aGroupObj.insertName( rGroupName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+
+ // Get the target url
+ Content aGroup;
+ const OUString aGroupURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ if ( Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
+ {
+ const OUString aPropName( TARGET_DIR_URL );
+ Any aValue;
+
+ OUString aGroupTargetURL;
+ if ( getProperty( aGroup, aPropName, aValue ) )
+ aValue >>= aGroupTargetURL;
+
+ if ( aGroupTargetURL.isEmpty() )
+ return false; // nothing is allowed to be removed
+
+ if ( !maTemplateDirs.hasElements() )
+ return false;
+
+ // check that the fs location is in writable folder and this is not a "My templates" folder
+ INetURLObject aGroupParentFolder( aGroupTargetURL );
+ if (!aGroupParentFolder.removeSegment())
+ return false;
+
+ OUString aGeneralTempPath = findParentTemplateDir(
+ aGroupParentFolder.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+
+ if (aGeneralTempPath.isEmpty())
+ return false;
+
+ // now get the content of the Group
+ uno::Reference< XResultSet > xResultSet;
+ Sequence< OUString > aProps { TARGET_URL };
+
+ try
+ {
+ xResultSet = aGroup.createCursor( aProps, INCLUDE_DOCUMENTS_ONLY );
+
+ if ( xResultSet.is() )
+ {
+ bool bHasNonRemovable = false;
+ bool bHasShared = false;
+
+ uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY_THROW );
+ uno::Reference< XRow > xRow( xResultSet, UNO_QUERY_THROW );
+
+ while ( xResultSet->next() )
+ {
+ OUString aTemplTargetURL( xRow->getString( 1 ) );
+ OUString aHierURL = xContentAccess->queryContentIdentifierString();
+
+ if ( ::utl::UCBContentHelper::IsSubPath( aGroupTargetURL, aTemplTargetURL ) )
+ {
+ // this is a user template, and it can be removed
+ if ( removeContent( aTemplTargetURL ) )
+ removeContent( aHierURL );
+ else
+ bHasNonRemovable = true;
+ }
+ else
+ bHasShared = true;
+ }
+
+ if ( !bHasNonRemovable && !bHasShared )
+ {
+ if ( removeContent( aGroupTargetURL )
+ || !::utl::UCBContentHelper::Exists( aGroupTargetURL ) )
+ {
+ removeContent( aGroupURL );
+ RemoveUINamesForTemplateDir_Impl( aGeneralTempPath, rGroupName );
+ bResult = true; // the operation is successful only if the whole group is removed
+ }
+ }
+ else if ( !bHasNonRemovable )
+ {
+ if ( removeContent( aGroupTargetURL )
+ || !::utl::UCBContentHelper::Exists( aGroupTargetURL ) )
+ {
+ RemoveUINamesForTemplateDir_Impl( aGeneralTempPath, rGroupName );
+ setProperty( aGroup, aPropName, uno::Any( OUString() ) );
+ }
+ }
+ }
+ }
+ catch ( Exception& ) {}
+ }
+
+ return bResult;
+}
+
+
+sal_Bool SfxDocTplService::renameGroup( const OUString& rOldName,
+ const OUString& rNewName )
+{
+ if ( rOldName == rNewName )
+ return true;
+
+ if (!init())
+ return false;
+
+ ::osl::MutexGuard aGuard( maMutex );
+
+ // create the group url
+ Content aGroup;
+ INetURLObject aGroupObj( maRootURL );
+ aGroupObj.insertName( rNewName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ OUString aGroupURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ // Check, if there is a group with the new name, return false
+ // if there is one.
+ if ( Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
+ return false;
+
+ aGroupObj.removeSegment();
+ aGroupObj.insertName( rOldName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ aGroupURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ // When there is no group with the old name, we can't rename it
+ if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
+ return false;
+
+ OUString aGroupTargetURL;
+ // there is no need to check whether target dir url is in target path, since if the target path is changed
+ // the target dir url should be already generated new
+ Any aValue;
+ if ( getProperty( aGroup, TARGET_DIR_URL, aValue ) )
+ aValue >>= aGroupTargetURL;
+
+ if ( aGroupTargetURL.isEmpty() )
+ return false;
+
+ if ( !maTemplateDirs.hasElements() )
+ return false;
+
+ // check that the fs location is in writable folder and this is not a "My templates" folder
+ INetURLObject aGroupParentFolder( aGroupTargetURL );
+ if (!aGroupParentFolder.removeSegment() ||
+ isInternalTemplateDir(aGroupParentFolder.GetMainURL(INetURLObject::DecodeMechanism::NONE)))
+ {
+ return false;
+ }
+
+ // check that the group can be renamed ( all the contents must be in target location )
+ bool bCanBeRenamed = false;
+ try
+ {
+ uno::Reference< XResultSet > xResultSet;
+ Sequence< OUString > aProps { TARGET_URL };
+ xResultSet = aGroup.createCursor( aProps, INCLUDE_DOCUMENTS_ONLY );
+
+ if ( xResultSet.is() )
+ {
+ uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY_THROW );
+ uno::Reference< XRow > xRow( xResultSet, UNO_QUERY_THROW );
+
+ while ( xResultSet->next() )
+ {
+ if ( !::utl::UCBContentHelper::IsSubPath( aGroupTargetURL, xRow->getString( 1 ) ) )
+ throw uno::Exception("not sub path", nullptr);
+ }
+
+ bCanBeRenamed = true;
+ }
+ }
+ catch ( Exception& ) {}
+
+ if ( bCanBeRenamed )
+ {
+ INetURLObject aGroupTargetObj( aGroupTargetURL );
+ const OUString aFsysName = aGroupTargetObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
+
+ if ( aGroupTargetObj.removeSegment()
+ && ReplaceUINamesForTemplateDir_Impl( aGroupTargetObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ),
+ aFsysName,
+ rOldName,
+ rNewName ) )
+ {
+ // rename the group in the hierarchy
+ Any aTitleValue;
+ aTitleValue <<= rNewName;
+
+ return setProperty( aGroup, TITLE, aTitleValue );
+ }
+ }
+
+ return false;
+}
+
+
+sal_Bool SfxDocTplService::storeTemplate( const OUString& rGroupName,
+ const OUString& rTemplateName,
+ const uno::Reference< frame::XStorable >& rStorable )
+{
+ if (!init())
+ return false;
+
+ ::osl::MutexGuard aGuard( maMutex );
+
+ // Check, whether or not there is a group with this name
+ // Return false, if there is no group with the given name
+ Content aGroup, aTemplateToRemove;
+ INetURLObject aGroupObj( maRootURL );
+ bool bRemoveOldTemplateContent = false;
+
+ aGroupObj.insertName( rGroupName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ const OUString aGroupURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
+
+ if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
+ return false;
+
+ OUString aGroupTargetURL;
+ Any aValue;
+ if ( getProperty( aGroup, TARGET_DIR_URL, aValue ) )
+ aValue >>= aGroupTargetURL;
+
+
+ // Check, if there's a template with the given name in this group
+ // the target template should be overwritten if it is imported by user
+ // in case the template is installed by office installation of by an add-in
+ // it can not be replaced
+ aGroupObj.insertName( rTemplateName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ const OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
+
+ OUString aTemplateToRemoveTargetURL;
+
+ if ( Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplateToRemove ) )
+ {
+ bRemoveOldTemplateContent = true;
+ if ( getProperty( aTemplateToRemove, TARGET_URL, aValue ) )
+ aValue >>= aTemplateToRemoveTargetURL;
+
+ if ( aGroupTargetURL.isEmpty() || !maTemplateDirs.hasElements()
+ || (!aTemplateToRemoveTargetURL.isEmpty() && isInternalTemplateDir(aTemplateToRemoveTargetURL)) )
+ return false; // it is not allowed to remove the template
+ }
+
+ try
+ {
+ uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+
+ // get document service name
+ uno::Reference< frame::XModuleManager2 > xModuleManager( frame::ModuleManager::create(xContext) );
+ const OUString sDocServiceName {xModuleManager->identify( uno::Reference< uno::XInterface >( rStorable, uno::UNO_QUERY ) )};
+ if ( sDocServiceName.isEmpty() )
+ throw uno::RuntimeException();
+
+ // get the actual filter name
+ uno::Reference< lang::XMultiServiceFactory > xConfigProvider =
+ configuration::theDefaultProvider::get( xContext );
+
+ uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"nodepath", uno::Any(OUString( "/org.openoffice.Setup/Office/Factories/" ))}
+ }));
+ uno::Reference< container::XNameAccess > xSOFConfig(
+ xConfigProvider->createInstanceWithArguments(
+ "com.sun.star.configuration.ConfigurationAccess",
+ aArgs ),
+ uno::UNO_QUERY_THROW );
+
+ uno::Reference< container::XNameAccess > xApplConfig;
+ xSOFConfig->getByName( sDocServiceName ) >>= xApplConfig;
+ if ( !xApplConfig.is() )
+ throw uno::RuntimeException();
+
+ OUString aFilterName;
+ xApplConfig->getByName("ooSetupFactoryActualTemplateFilter") >>= aFilterName;
+ if ( aFilterName.isEmpty() )
+ throw uno::RuntimeException();
+
+ // find the related type name
+ uno::Reference< container::XNameAccess > xFilterFactory(
+ mxContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.FilterFactory", mxContext),
+ uno::UNO_QUERY_THROW );
+
+ uno::Sequence< beans::PropertyValue > aFilterData;
+ xFilterFactory->getByName( aFilterName ) >>= aFilterData;
+ OUString aTypeName;
+ for ( const auto& rProp : std::as_const(aFilterData) )
+ if ( rProp.Name == "Type" )
+ rProp.Value >>= aTypeName;
+
+ if ( aTypeName.isEmpty() )
+ throw uno::RuntimeException();
+
+ // find the mediatype and extension
+ uno::Reference< container::XNameAccess > xTypeDetection =
+ mxType.is() ?
+ uno::Reference< container::XNameAccess >( mxType, uno::UNO_QUERY_THROW ) :
+ uno::Reference< container::XNameAccess >(
+ mxContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.TypeDetection", mxContext),
+ uno::UNO_QUERY_THROW );
+
+ SequenceAsHashMap aTypeProps( xTypeDetection->getByName( aTypeName ) );
+ uno::Sequence< OUString > aAllExt =
+ aTypeProps.getUnpackedValueOrDefault("Extensions", Sequence< OUString >() );
+ if ( !aAllExt.hasElements() )
+ throw uno::RuntimeException();
+
+ const OUString aMediaType {aTypeProps.getUnpackedValueOrDefault("MediaType", OUString() )};
+ const OUString aExt {aAllExt[0]};
+
+ if ( aMediaType.isEmpty() || aExt.isEmpty() )
+ throw uno::RuntimeException();
+
+ // construct destination url
+ if ( aGroupTargetURL.isEmpty() )
+ {
+ aGroupTargetURL = CreateNewGroupFsys( rGroupName, aGroup );
+
+ if ( aGroupTargetURL.isEmpty() )
+ throw uno::RuntimeException();
+ }
+
+ OUString aNewTemplateTargetURL = CreateNewUniqueFileWithPrefix( aGroupTargetURL, rTemplateName, aExt );
+ if ( aNewTemplateTargetURL.isEmpty() )
+ {
+ aNewTemplateTargetURL = CreateNewUniqueFileWithPrefix( aGroupTargetURL, "UserTemplate", aExt );
+
+ if ( aNewTemplateTargetURL.isEmpty() )
+ throw uno::RuntimeException();
+ }
+
+ // store template
+ uno::Sequence< PropertyValue > aStoreArgs{
+ comphelper::makePropertyValue("FilterName", aFilterName),
+ comphelper::makePropertyValue("DocumentTitle", rTemplateName)
+ };
+
+ if( !::utl::UCBContentHelper::EqualURLs( aNewTemplateTargetURL, rStorable->getLocation() ))
+ rStorable->storeToURL( aNewTemplateTargetURL, aStoreArgs );
+ else
+ rStorable->store();
+
+ // the storing was successful, now the old template with the same name can be removed if it existed
+ if ( !aTemplateToRemoveTargetURL.isEmpty() )
+ {
+ removeContent( aTemplateToRemoveTargetURL );
+
+ /*
+ * pb: #i79496#
+ * if the old template was the standard template
+ * it is necessary to change the standard template with the new file name
+ */
+ const OUString sStdTmplFile = SfxObjectFactory::GetStandardTemplate( sDocServiceName );
+ if ( INetURLObject( sStdTmplFile ) == INetURLObject( aTemplateToRemoveTargetURL ) )
+ {
+ SfxObjectFactory::SetStandardTemplate( sDocServiceName, aNewTemplateTargetURL );
+ }
+ }
+
+ if ( bRemoveOldTemplateContent )
+ removeContent( aTemplateToRemove );
+
+ // add the template to hierarchy
+ return addEntry( aGroup, rTemplateName, aNewTemplateTargetURL, aMediaType );
+ }
+ catch( Exception& )
+ {
+ // the template was not stored
+ return false;
+ }
+}
+
+
+sal_Bool SfxDocTplService::addTemplate( const OUString& rGroupName,
+ const OUString& rTemplateName,
+ const OUString& rSourceURL )
+{
+ if (!init())
+ return false;
+
+ ::osl::MutexGuard aGuard( maMutex );
+
+ // Check, whether or not there is a group with this name
+ // Return false, if there is no group with the given name
+ Content aGroup, aTemplate, aTargetGroup;
+ INetURLObject aGroupObj( maRootURL );
+
+ aGroupObj.insertName( rGroupName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ const OUString aGroupURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
+ return false;
+
+ // Check, if there's a template with the given name in this group
+ // Return false, if there already is a template
+ aGroupObj.insertName( rTemplateName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ const OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
+
+ if ( Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) )
+ return false;
+
+ // get the target url of the group
+ OUString aTargetURL;
+ Any aValue;
+
+ if ( getProperty( aGroup, TARGET_DIR_URL, aValue ) )
+ aValue >>= aTargetURL;
+
+ if ( aTargetURL.isEmpty() )
+ {
+ aTargetURL = CreateNewGroupFsys( rGroupName, aGroup );
+
+ if ( aTargetURL.isEmpty() )
+ return false;
+ }
+
+ // Get the content type
+ OUString aTitle, aType;
+
+ bool bDocHasTitle = false;
+ getTitleFromURL( rSourceURL, aTitle, aType, bDocHasTitle );
+
+ INetURLObject aSourceObj( rSourceURL );
+ if ( rTemplateName == aTitle )
+ {
+ // addTemplate will sometimes be called just to add an entry in the
+ // hierarchy; the target URL and the source URL will be the same in
+ // this scenario
+ // TODO/LATER: get rid of this old hack
+
+ INetURLObject aTargetObj( aTargetURL );
+
+ aTargetObj.insertName( rTemplateName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ aTargetObj.setExtension( aSourceObj.getExtension() );
+
+ const OUString aTargetURL2 = aTargetObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ if ( aTargetURL2 == rSourceURL )
+ return addEntry( aGroup, rTemplateName, aTargetURL2, aType );
+ }
+
+ // copy the template into the new group (targeturl)
+
+ INetURLObject aTmpURL( aSourceObj );
+ aTmpURL.CutExtension();
+ const OUString aPattern {aTmpURL.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset )};
+
+ const OUString aNewTemplateTargetURL {CreateNewUniqueFileWithPrefix( aTargetURL, aPattern, aSourceObj.getExtension() )};
+ INetURLObject aNewTemplateTargetObj( aNewTemplateTargetURL );
+ const OUString aNewTemplateTargetName {aNewTemplateTargetObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset )};
+ if ( aNewTemplateTargetURL.isEmpty() || aNewTemplateTargetName.isEmpty() )
+ return false;
+
+ // get access to source file
+ Content aSourceContent;
+ uno::Reference < ucb::XCommandEnvironment > xEnv;
+ INetURLObject aSourceURL( rSourceURL );
+ if( ! Content::create( aSourceURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aSourceContent ) )
+ return false;
+
+ if( ! Content::create( aTargetURL, xEnv, comphelper::getProcessComponentContext(), aTargetGroup ) )
+ return false;
+
+ // transfer source file
+ try
+ {
+ aTargetGroup.transferContent( aSourceContent,
+ InsertOperation::Copy,
+ aNewTemplateTargetName,
+ NameClash::OVERWRITE,
+ aType );
+
+ // allow to edit the added template
+ Content aResultContent;
+ if ( Content::create( aNewTemplateTargetURL, xEnv, comphelper::getProcessComponentContext(), aResultContent ) )
+ {
+ static constexpr OUString aPropertyName( u"IsReadOnly"_ustr );
+ uno::Any aProperty;
+ bool bReadOnly = false;
+ if ( getProperty( aResultContent, aPropertyName, aProperty ) && ( aProperty >>= bReadOnly ) && bReadOnly )
+ setProperty( aResultContent, aPropertyName, uno::Any( false ) );
+ }
+ }
+ catch ( ContentCreationException& )
+ { return false; }
+ catch ( Exception& )
+ { return false; }
+
+
+ // either the document has title and it is the same as requested, or we have to set it
+ bool bCorrectTitle = ( bDocHasTitle && aTitle == rTemplateName );
+ if ( !bCorrectTitle )
+ {
+ if ( !bDocHasTitle )
+ {
+ INetURLObject aNewTmpObj( aNewTemplateTargetObj );
+ aNewTmpObj.CutExtension();
+ bCorrectTitle = ( aNewTmpObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ) == rTemplateName );
+ }
+
+ if ( !bCorrectTitle )
+ bCorrectTitle = setTitleForURL( aNewTemplateTargetURL, rTemplateName );
+ }
+
+ if ( bCorrectTitle )
+ {
+ // create a new entry in the hierarchy
+ return addEntry( aGroup, rTemplateName, aNewTemplateTargetURL, aType );
+ }
+
+ // TODO/LATER: The user could be notified here that the renaming has failed
+ // create a new entry in the hierarchy
+ addEntry( aGroup, aTitle, aNewTemplateTargetURL, aType );
+ return false;
+}
+
+bool SfxDocTplService::isInternalTemplateDir(const OUString& rURL) const
+{
+ return std::any_of(maInternalTemplateDirs.begin(), maInternalTemplateDirs.end(),
+ [&rURL](const OUString& rDir) { return ::utl::UCBContentHelper::IsSubPath(rDir, rURL); });
+}
+
+OUString SfxDocTplService::findParentTemplateDir(const OUString& rURL) const
+{
+ const OUString* pDirs = std::find_if(maTemplateDirs.begin(), maTemplateDirs.end(),
+ [&rURL](const OUString& rDir) { return ::utl::UCBContentHelper::IsSubPath(rDir, rURL); });
+ if (pDirs != maTemplateDirs.end())
+ return *pDirs;
+ return OUString();
+}
+
+sal_Bool SfxDocTplService::removeTemplate( const OUString& rGroupName,
+ const OUString& rTemplateName )
+{
+ if (!init())
+ return false;
+ ::osl::MutexGuard aGuard( maMutex );
+
+ // Check, whether or not there is a group with this name
+ // Return false, if there is no group with the given name
+ Content aGroup, aTemplate;
+ INetURLObject aGroupObj( maRootURL );
+
+ aGroupObj.insertName( rGroupName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ const OUString aGroupURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
+
+ if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
+ return false;
+
+ // Check, if there's a template with the given name in this group
+ // Return false, if there is no template
+ aGroupObj.insertName( rTemplateName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ const OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
+
+ if ( !Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) )
+ return false;
+
+ // get the target URL from the template
+ OUString aTargetURL;
+ Any aValue;
+
+ if ( getProperty( aTemplate, TARGET_URL, aValue ) )
+ aValue >>= aTargetURL;
+
+ // delete the target template
+ if ( !aTargetURL.isEmpty() )
+ {
+ if (isInternalTemplateDir(aTargetURL))
+ return false;
+
+ removeContent( aTargetURL );
+ }
+
+ // delete the template entry
+ return removeContent( aTemplate );
+}
+
+
+sal_Bool SfxDocTplService::renameTemplate( const OUString& rGroupName,
+ const OUString& rOldName,
+ const OUString& rNewName )
+{
+ if ( rOldName == rNewName )
+ return true;
+ if (!init())
+ return false;
+
+ ::osl::MutexGuard aGuard( maMutex );
+
+ // Check, whether or not there is a group with this name
+ // Return false, if there is no group with the given name
+ Content aGroup, aTemplate;
+ INetURLObject aGroupObj( maRootURL );
+
+ aGroupObj.insertName( rGroupName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ const OUString aGroupURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
+
+ if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
+ return false;
+
+ // Check, if there's a template with the new name in this group
+ // Return false, if there is one
+ aGroupObj.insertName( rNewName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
+
+ if ( Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) )
+ return false;
+
+ // Check, if there's a template with the old name in this group
+ // Return false, if there is no template
+ aGroupObj.removeSegment();
+ aGroupObj.insertName( rOldName, false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+ aTemplateURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ if ( !Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) )
+ return false;
+
+ OUString aTemplateTargetURL;
+ Any aTargetValue;
+
+ if ( getProperty( aTemplate, TARGET_URL, aTargetValue ) )
+ aTargetValue >>= aTemplateTargetURL;
+
+ if ( !setTitleForURL( aTemplateTargetURL, rNewName ) )
+ return false;
+
+ // rename the template entry in the cache
+ Any aTitleValue;
+ aTitleValue <<= rNewName;
+
+ return setProperty( aTemplate, TITLE, aTitleValue );
+}
+
+
+
+//--- XDocumentTemplates ---
+
+uno::Reference< ucb::XContent > SAL_CALL SfxDocTplService::getContent()
+{
+ if ( init() )
+ return maRootContent.get();
+ return nullptr;
+}
+
+
+WaitWindow_Impl::WaitWindow_Impl() : WorkWindow(nullptr, WB_BORDER | WB_3DLOOK)
+{
+ tools::Rectangle aRect(0, 0, 300, 30000);
+ maText = SfxResId(RID_CNT_STR_WAITING);
+ maRect = GetTextRect(aRect, maText, gnTextStyle);
+ aRect = maRect;
+ aRect.AdjustRight(2 * X_OFFSET );
+ aRect.AdjustBottom(2 * Y_OFFSET );
+ maRect.SetPos(Point(X_OFFSET, Y_OFFSET));
+ SetOutputSizePixel(aRect.GetSize());
+
+ Show();
+ PaintImmediately();
+ GetOutDev()->Flush();
+}
+
+
+WaitWindow_Impl::~WaitWindow_Impl()
+{
+ disposeOnce();
+}
+
+void WaitWindow_Impl::dispose()
+{
+ Hide();
+ WorkWindow::dispose();
+}
+
+
+void WaitWindow_Impl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
+{
+ rRenderContext.DrawText(maRect, maText, gnTextStyle);
+}
+
+void SfxDocTplService::addHierGroup( GroupList_Impl& rList,
+ const OUString& rTitle,
+ const OUString& rOwnURL )
+{
+ // now get the content of the Group
+ Content aContent;
+ uno::Reference<XResultSet> xResultSet;
+
+ try
+ {
+ aContent = Content(rOwnURL, maCmdEnv, comphelper::getProcessComponentContext());
+ xResultSet = aContent.createCursor( { TITLE, TARGET_URL, PROPERTY_TYPE }, INCLUDE_DOCUMENTS_ONLY );
+ }
+ catch (ContentCreationException&)
+ {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "" );
+ }
+ catch (Exception&) {}
+
+ if ( !xResultSet.is() )
+ return;
+
+ GroupData_Impl *pGroup = new GroupData_Impl( rTitle );
+ pGroup->setHierarchy( true );
+ pGroup->setHierarchyURL( rOwnURL );
+ rList.push_back( std::unique_ptr<GroupData_Impl>(pGroup) );
+
+ uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY );
+ uno::Reference< XRow > xRow( xResultSet, UNO_QUERY );
+
+ try
+ {
+ while ( xResultSet->next() )
+ {
+ bool bUpdateType = false;
+ DocTemplates_EntryData_Impl *pData;
+
+ const OUString aTitle( xRow->getString( 1 ) );
+ const OUString aTargetDir( xRow->getString( 2 ) );
+ OUString aType( xRow->getString( 3 ) );
+ const OUString aHierURL {xContentAccess->queryContentIdentifierString()};
+
+ if ( aType.isEmpty() )
+ {
+ OUString aTmpTitle;
+
+ bool bDocHasTitle = false;
+ getTitleFromURL( aTargetDir, aTmpTitle, aType, bDocHasTitle );
+
+ if ( !aType.isEmpty() )
+ bUpdateType = true;
+ }
+
+ pData = pGroup->addEntry( aTitle, aTargetDir, aType, aHierURL );
+ pData->setUpdateType( bUpdateType );
+ }
+ }
+ catch ( Exception& ) {}
+}
+
+
+void SfxDocTplService::addFsysGroup( GroupList_Impl& rList,
+ const OUString& rTitle,
+ const OUString& rUITitle,
+ const OUString& rOwnURL,
+ bool bWriteableGroup )
+{
+ OUString aTitle;
+
+ if ( rUITitle.isEmpty() )
+ {
+ // reserved FS names that should not be used
+ if ( rTitle == "wizard" )
+ return;
+ else if ( rTitle == "internal" )
+ return;
+
+ aTitle = getLongName( rTitle );
+ }
+ else
+ aTitle = rUITitle;
+
+ if ( aTitle.isEmpty() )
+ return;
+
+ GroupData_Impl* pGroup = nullptr;
+ for (const std::unique_ptr<GroupData_Impl>& i : rList)
+ {
+ if ( i->getTitle() == aTitle )
+ {
+ pGroup = i.get();
+ break;
+ }
+ }
+
+ if ( !pGroup )
+ {
+ pGroup = new GroupData_Impl( aTitle );
+ rList.push_back( std::unique_ptr<GroupData_Impl>(pGroup) );
+ }
+
+ if ( bWriteableGroup )
+ pGroup->setTargetURL( rOwnURL );
+
+ pGroup->setInUse();
+
+ // now get the content of the Group
+ Content aContent;
+ uno::Reference< XResultSet > xResultSet;
+ Sequence< OUString > aProps { TITLE };
+
+ try
+ {
+ // this method is only used during checking of the available template-folders
+ // that should happen quietly
+ uno::Reference< XCommandEnvironment > aQuietEnv;
+ aContent = Content( rOwnURL, aQuietEnv, comphelper::getProcessComponentContext() );
+ xResultSet = aContent.createCursor( aProps, INCLUDE_DOCUMENTS_ONLY );
+ }
+ catch ( Exception& ) {}
+
+ if ( !xResultSet.is() )
+ return;
+
+ uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY );
+ uno::Reference< XRow > xRow( xResultSet, UNO_QUERY );
+
+ try
+ {
+ while ( xResultSet->next() )
+ {
+ OUString aChildTitle( xRow->getString( 1 ) );
+ const OUString aTargetURL {xContentAccess->queryContentIdentifierString()};
+ OUString aType;
+
+ if ( aChildTitle == "sfx.tlx" || aChildTitle == "groupuinames.xml" )
+ continue;
+
+ bool bDocHasTitle = false;
+ getTitleFromURL( aTargetURL, aChildTitle, aType, bDocHasTitle );
+
+ pGroup->addEntry( aChildTitle, aTargetURL, aType, OUString() );
+ }
+ }
+ catch ( Exception& ) {}
+}
+
+
+void SfxDocTplService::createFromContent( GroupList_Impl& rList,
+ Content &rContent,
+ bool bHierarchy,
+ bool bWriteableContent )
+{
+ const OUString aTargetURL {rContent.get()->getIdentifier()->getContentIdentifier()};
+
+ // when scanning the file system, we have to add the 'standard' group, too
+ if ( ! bHierarchy )
+ {
+ const OUString aUIStdTitle {getLongName( STANDARD_FOLDER )};
+ addFsysGroup( rList, OUString(), aUIStdTitle, aTargetURL, bWriteableContent );
+ }
+
+ // search for predefined UI names
+ INetURLObject aLayerObj( aTargetURL );
+
+ // TODO/LATER: Use hashmap in future
+ std::vector< beans::StringPair > aUINames;
+ if ( !bHierarchy )
+ aUINames = ReadUINamesForTemplateDir_Impl( aLayerObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+
+ uno::Reference< XResultSet > xResultSet;
+ Sequence< OUString > aProps { TITLE };
+
+ try
+ {
+ xResultSet = rContent.createCursor( aProps, INCLUDE_FOLDERS_ONLY );
+ }
+ catch ( Exception& ) {}
+
+ if ( !xResultSet.is() )
+ return;
+
+ uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY );
+ uno::Reference< XRow > xRow( xResultSet, UNO_QUERY );
+
+ try
+ {
+ while ( xResultSet->next() )
+ {
+ // TODO/LATER: clarify the encoding of the Title
+ const OUString aTitle( xRow->getString( 1 ) );
+ const OUString aTargetSubfolderURL( xContentAccess->queryContentIdentifierString() );
+
+ if ( bHierarchy )
+ addHierGroup( rList, aTitle, aTargetSubfolderURL );
+ else
+ {
+ OUString aUITitle;
+ for (const beans::StringPair & rUIName : aUINames)
+ if ( rUIName.First == aTitle )
+ {
+ aUITitle = rUIName.Second;
+ break;
+ }
+
+ addFsysGroup( rList, aTitle, aUITitle, aTargetSubfolderURL, bWriteableContent );
+ }
+ }
+ }
+ catch ( Exception& ) {}
+}
+
+
+void SfxDocTplService::removeFromHierarchy( DocTemplates_EntryData_Impl const *pData )
+{
+ Content aTemplate;
+
+ if ( Content::create( pData->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) )
+ {
+ removeContent( aTemplate );
+ }
+}
+
+
+void SfxDocTplService::addToHierarchy( GroupData_Impl const *pGroup,
+ DocTemplates_EntryData_Impl const *pData )
+{
+ Content aGroup, aTemplate;
+
+ if ( ! Content::create( pGroup->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
+ return;
+
+ // Check, if there's a template with the given name in this group
+ // Return if there is already a template
+ INetURLObject aGroupObj( pGroup->getHierarchyURL() );
+
+ aGroupObj.insertName( pData->getTitle(), false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+
+ const OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
+
+ if ( Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) )
+ return;
+
+ addEntry( aGroup, pData->getTitle(),
+ pData->getTargetURL(),
+ pData->getType() );
+}
+
+
+void SfxDocTplService::updateData( DocTemplates_EntryData_Impl const *pData )
+{
+ Content aTemplate;
+
+ if ( ! Content::create( pData->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) )
+ return;
+
+ if ( pData->getUpdateType() )
+ {
+ setProperty( aTemplate, PROPERTY_TYPE, Any( pData->getType() ) );
+ }
+
+ if ( pData->getUpdateLink() )
+ {
+ setProperty( aTemplate, TARGET_URL, Any( pData->getTargetURL() ) );
+ }
+}
+
+
+void SfxDocTplService::addGroupToHierarchy( GroupData_Impl *pGroup )
+{
+ Content aGroup;
+
+ INetURLObject aNewGroupObj( maRootURL );
+ aNewGroupObj.insertName( pGroup->getTitle(), false,
+ INetURLObject::LAST_SEGMENT,
+ INetURLObject::EncodeMechanism::All );
+
+ const OUString aNewGroupURL {aNewGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )};
+
+ if ( createFolder( aNewGroupURL, false, false, aGroup ) )
+ {
+ setProperty( aGroup, TARGET_DIR_URL, Any( pGroup->getTargetURL() ) );
+ pGroup->setHierarchyURL( aNewGroupURL );
+
+ size_t nCount = pGroup->count();
+ for ( size_t i = 0; i < nCount; i++ )
+ {
+ DocTemplates_EntryData_Impl *pData = pGroup->getEntry( i );
+ addToHierarchy( pGroup, pData ); // add entry to hierarchy
+ }
+ }
+}
+
+
+void SfxDocTplService::removeFromHierarchy( GroupData_Impl const *pGroup )
+{
+ Content aGroup;
+
+ if ( Content::create( pGroup->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) )
+ {
+ removeContent( aGroup );
+ }
+}
+
+
+GroupData_Impl::GroupData_Impl( OUString aTitle )
+ : maTitle(std::move(aTitle)), mbInUse(false), mbInHierarchy(false)
+{
+}
+
+
+DocTemplates_EntryData_Impl* GroupData_Impl::addEntry( const OUString& rTitle,
+ const OUString& rTargetURL,
+ const OUString& rType,
+ const OUString& rHierURL )
+{
+ DocTemplates_EntryData_Impl* pData = nullptr;
+ bool EntryFound = false;
+
+ for (auto const & p : maEntries)
+ {
+ pData = p.get();
+ if ( pData->getTitle() == rTitle )
+ {
+ EntryFound = true;
+ break;
+ }
+ }
+
+ if ( !EntryFound )
+ {
+ pData = new DocTemplates_EntryData_Impl( rTitle );
+ pData->setTargetURL( rTargetURL );
+ pData->setType( rType );
+ if ( !rHierURL.isEmpty() )
+ {
+ pData->setHierarchyURL( rHierURL );
+ pData->setHierarchy( true );
+ }
+ maEntries.emplace_back( pData );
+ }
+ else
+ {
+ if ( !rHierURL.isEmpty() )
+ {
+ pData->setHierarchyURL( rHierURL );
+ pData->setHierarchy( true );
+ }
+
+ if ( pData->getInHierarchy() )
+ pData->setInUse();
+
+ if ( rTargetURL != pData->getTargetURL() )
+ {
+ pData->setTargetURL( rTargetURL );
+ pData->setUpdateLink( true );
+ }
+ }
+
+ return pData;
+}
+
+
+DocTemplates_EntryData_Impl::DocTemplates_EntryData_Impl( OUString aTitle )
+ : maTitle(std::move(aTitle)), mbInHierarchy(false), mbInUse(false), mbUpdateType(false), mbUpdateLink(false)
+{
+}
+
+}
+
+// static
+bool SfxURLRelocator_Impl::propertyCanContainOfficeDir(
+ std::u16string_view rPropName )
+{
+ // Note: TargetURL is handled by UCB itself (because it is a property
+ // with a predefined semantic). Additional Core properties introduced
+ // be a client app must be handled by the client app itself, because
+ // the UCB does not know the semantics of those properties.
+ return ( rPropName == TARGET_DIR_URL || rPropName == PROPERTY_DIRLIST );
+}
+
+
+SfxURLRelocator_Impl::SfxURLRelocator_Impl( uno::Reference< XComponentContext > xContext )
+: mxContext(std::move( xContext ))
+{
+}
+
+
+SfxURLRelocator_Impl::~SfxURLRelocator_Impl()
+{
+}
+
+
+void SfxURLRelocator_Impl::initOfficeInstDirs()
+{
+ if ( !mxOfficeInstDirs.is() )
+ {
+ std::scoped_lock aGuard( maMutex );
+ if ( !mxOfficeInstDirs.is() )
+ {
+ OSL_ENSURE( mxContext.is(), "No service manager!" );
+
+ mxOfficeInstDirs = theOfficeInstallationDirectories::get(mxContext);
+ }
+ }
+}
+
+
+void SfxURLRelocator_Impl::implExpandURL( OUString& io_url )
+{
+ const INetURLObject aParser( io_url );
+ if ( aParser.GetProtocol() != INetProtocol::VndSunStarExpand )
+ return;
+
+ io_url = aParser.GetURLPath( INetURLObject::DecodeMechanism::WithCharset );
+ try
+ {
+ if ( !mxMacroExpander.is() )
+ {
+ mxMacroExpander.set( theMacroExpander::get(mxContext), UNO_SET_THROW );
+ }
+ io_url = mxMacroExpander->expandMacros( io_url );
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("sfx.doc");
+ }
+}
+
+
+void SfxURLRelocator_Impl::makeRelocatableURL( OUString & rURL )
+{
+ if ( !rURL.isEmpty() )
+ {
+ initOfficeInstDirs();
+ implExpandURL( rURL );
+ rURL = mxOfficeInstDirs->makeRelocatableURL( rURL );
+ }
+}
+
+
+void SfxURLRelocator_Impl::makeAbsoluteURL( OUString & rURL )
+{
+ if ( !rURL.isEmpty() )
+ {
+ initOfficeInstDirs();
+ implExpandURL( rURL );
+ rURL = mxOfficeInstDirs->makeAbsoluteURL( rURL );
+ }
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_sfx2_DocumentTemplates_get_implementation(
+ css::uno::XComponentContext *context,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new SfxDocTplService(context));
+}
+
+OUString DocTemplLocaleHelper::GetStandardGroupString()
+{
+ return SfxResId(TEMPLATE_LONG_NAMES_ARY[0]);
+}
+
+std::vector<OUString> DocTemplLocaleHelper::GetBuiltInGroupNames()
+{
+ std::vector<OUString> aGroups;
+ for(auto const & aGroupName : TEMPLATE_LONG_NAMES_ARY)
+ aGroups.push_back(SfxResId(aGroupName));
+ return aGroups;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/doctemplateslocal.cxx b/sfx2/source/doc/doctemplateslocal.cxx
new file mode 100644
index 0000000000..71d4c69aa5
--- /dev/null
+++ b/sfx2/source/doc/doctemplateslocal.cxx
@@ -0,0 +1,211 @@
+/* -*- 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 <com/sun/star/beans/StringPair.hpp>
+#include <com/sun/star/xml/sax/Parser.hpp>
+#include <com/sun/star/xml/sax/SAXException.hpp>
+#include <com/sun/star/xml/sax/Writer.hpp>
+#include <com/sun/star/xml/sax/XDocumentHandler.hpp>
+
+#include <comphelper/attributelist.hxx>
+#include <rtl/ref.hxx>
+
+#include "doctemplateslocal.hxx"
+
+using namespace ::com::sun::star;
+
+namespace
+{
+
+// Relations info related strings
+constexpr OUString g_sGroupListElement(u"groupuinames:template-group-list"_ustr);
+constexpr OUString g_sGroupElement(u"groupuinames:template-group"_ustr);
+constexpr OUString g_sNameAttr(u"groupuinames:name"_ustr);
+constexpr OUString g_sUINameAttr(u"groupuinames:default-ui-name"_ustr);
+
+}
+
+std::vector< beans::StringPair > DocTemplLocaleHelper::ReadGroupLocalizationSequence( const uno::Reference< io::XInputStream >& xInStream, const uno::Reference< uno::XComponentContext >& xContext )
+{
+ return ReadLocalizationSequence_Impl( xInStream, "groupuinames.xml", xContext );
+}
+
+
+void DocTemplLocaleHelper::WriteGroupLocalizationSequence( const uno::Reference< io::XOutputStream >& xOutStream, const std::vector< beans::StringPair >& aSequence, const uno::Reference< uno::XComponentContext >& xContext )
+{
+ if ( !xOutStream.is() )
+ throw uno::RuntimeException();
+
+ uno::Reference< xml::sax::XWriter > xWriterHandler(
+ xml::sax::Writer::create(xContext) );
+
+ xWriterHandler->setOutputStream( xOutStream );
+
+ static constexpr OUString aWhiteSpace( u" "_ustr );
+
+ // write the namespace
+ rtl::Reference<::comphelper::AttributeList> pRootAttrList = new ::comphelper::AttributeList;
+ pRootAttrList->AddAttribute(
+ "xmlns:groupuinames",
+ "http://openoffice.org/2006/groupuinames" );
+
+ xWriterHandler->startDocument();
+ xWriterHandler->startElement( g_sGroupListElement, pRootAttrList );
+
+ for (const auto & i : aSequence)
+ {
+ rtl::Reference<::comphelper::AttributeList> pAttrList = new ::comphelper::AttributeList;
+ pAttrList->AddAttribute( g_sNameAttr, i.First );
+ pAttrList->AddAttribute( g_sUINameAttr, i.Second );
+
+ xWriterHandler->startElement( g_sGroupElement, pAttrList );
+ xWriterHandler->ignorableWhitespace( aWhiteSpace );
+ xWriterHandler->endElement( g_sGroupElement );
+ }
+
+ xWriterHandler->ignorableWhitespace( aWhiteSpace );
+ xWriterHandler->endElement( g_sGroupListElement );
+ xWriterHandler->endDocument();
+}
+
+
+std::vector< beans::StringPair > DocTemplLocaleHelper::ReadLocalizationSequence_Impl( const uno::Reference< io::XInputStream >& xInStream, const OUString& aStringID, const uno::Reference< uno::XComponentContext >& xContext )
+{
+ if ( !xContext.is() || !xInStream.is() )
+ throw uno::RuntimeException();
+
+ uno::Reference< xml::sax::XParser > xParser = xml::sax::Parser::create( xContext );
+
+ rtl::Reference<DocTemplLocaleHelper> pHelper = new DocTemplLocaleHelper();
+ xml::sax::InputSource aParserInput;
+ aParserInput.aInputStream = xInStream;
+ aParserInput.sSystemId = aStringID;
+ xParser->setDocumentHandler( pHelper );
+ xParser->parseStream( aParserInput );
+ xParser->setDocumentHandler( uno::Reference < xml::sax::XDocumentHandler > () );
+
+ return pHelper->GetParsingResult();
+}
+
+
+DocTemplLocaleHelper::DocTemplLocaleHelper()
+{
+}
+
+
+DocTemplLocaleHelper::~DocTemplLocaleHelper()
+{
+}
+
+
+std::vector< beans::StringPair > const & DocTemplLocaleHelper::GetParsingResult() const
+{
+ if ( !m_aElementsSeq.empty() )
+ throw uno::RuntimeException("The parsing has still not finished!");
+
+ return m_aResultSeq;
+}
+
+
+void SAL_CALL DocTemplLocaleHelper::startDocument()
+{
+}
+
+
+void SAL_CALL DocTemplLocaleHelper::endDocument()
+{
+}
+
+
+void SAL_CALL DocTemplLocaleHelper::startElement( const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs )
+{
+ if ( aName == g_sGroupListElement )
+ {
+ if ( !m_aElementsSeq.empty() )
+ throw xml::sax::SAXException(); // TODO: this element must be the first level element
+
+ m_aElementsSeq.push_back( aName );
+
+ return; // nothing to do
+ }
+ else if ( aName == g_sGroupElement )
+ {
+ if ( m_aElementsSeq.size() != 1 )
+ throw xml::sax::SAXException(); // TODO: this element must be the second level element
+
+ m_aElementsSeq.push_back( aName );
+
+ const auto nNewEntryNum = m_aResultSeq.size();
+ m_aResultSeq.resize( nNewEntryNum+1 );
+
+ const OUString aNameValue = xAttribs->getValueByName( g_sNameAttr );
+ if ( aNameValue.isEmpty() )
+ throw xml::sax::SAXException(); // TODO: the ID value must present
+
+ const OUString aUINameValue = xAttribs->getValueByName( g_sUINameAttr );
+ if ( aUINameValue.isEmpty() )
+ throw xml::sax::SAXException(); // TODO: the ID value must present
+
+ m_aResultSeq[nNewEntryNum].First = aNameValue;
+ m_aResultSeq[nNewEntryNum].Second = aUINameValue;
+ }
+ else
+ {
+ // accept future extensions
+ if ( m_aElementsSeq.empty() )
+ throw xml::sax::SAXException(); // TODO: the extension element must not be the first level element
+
+ m_aElementsSeq.push_back( aName );
+ }
+}
+
+
+void SAL_CALL DocTemplLocaleHelper::endElement( const OUString& aName )
+{
+ if ( m_aElementsSeq.empty() )
+ throw xml::sax::SAXException(); // TODO: no other end elements expected!
+
+ if ( m_aElementsSeq.back() != aName )
+ throw xml::sax::SAXException(); // TODO: unexpected element ended
+
+ m_aElementsSeq.pop_back();
+}
+
+
+void SAL_CALL DocTemplLocaleHelper::characters( const OUString& /*aChars*/ )
+{
+}
+
+
+void SAL_CALL DocTemplLocaleHelper::ignorableWhitespace( const OUString& /*aWhitespaces*/ )
+{
+}
+
+
+void SAL_CALL DocTemplLocaleHelper::processingInstruction( const OUString& /*aTarget*/, const OUString& /*aData*/ )
+{
+}
+
+
+void SAL_CALL DocTemplLocaleHelper::setDocumentLocator( const uno::Reference< xml::sax::XLocator >& /*xLocator*/ )
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/doctemplateslocal.hxx b/sfx2/source/doc/doctemplateslocal.hxx
new file mode 100644
index 0000000000..466c847db1
--- /dev/null
+++ b/sfx2/source/doc/doctemplateslocal.hxx
@@ -0,0 +1,78 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SFX2_SOURCE_DOC_DOCTEMPLATESLOCAL_HXX
+#define INCLUDED_SFX2_SOURCE_DOC_DOCTEMPLATESLOCAL_HXX
+
+#include <com/sun/star/xml/sax/XDocumentHandler.hpp>
+#include <com/sun/star/beans/StringPair.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <vector>
+
+
+class DocTemplLocaleHelper : public cppu::WeakImplHelper < css::xml::sax::XDocumentHandler >
+{
+ std::vector< css::beans::StringPair > m_aResultSeq;
+ std::vector< OUString > m_aElementsSeq; // stack of elements being parsed
+
+ DocTemplLocaleHelper();
+ std::vector< css::beans::StringPair > const & GetParsingResult() const;
+
+ /// @throws css::uno::Exception
+ static std::vector< css::beans::StringPair > ReadLocalizationSequence_Impl( const css::uno::Reference< css::io::XInputStream >& xInStream, const OUString& aStringID, const css::uno::Reference< css::uno::XComponentContext >& xContext );
+
+public:
+ virtual ~DocTemplLocaleHelper() override;
+
+ // returns sequence of pairs ( GroupName, GroupUIName )
+ /// @throws css::uno::Exception
+ static
+ std::vector< css::beans::StringPair >
+ ReadGroupLocalizationSequence(
+ const css::uno::Reference< css::io::XInputStream >& xInStream,
+ const css::uno::Reference< css::uno::XComponentContext >& xContext );
+
+ // writes sequence of elements ( GroupName, GroupUIName )
+ /// @throws css::uno::Exception
+ static
+ void WriteGroupLocalizationSequence(
+ const css::uno::Reference< css::io::XOutputStream >& xOutStream,
+ const std::vector< css::beans::StringPair >& aSequence,
+ const css::uno::Reference< css::uno::XComponentContext >& xContext );
+
+ static OUString GetStandardGroupString();
+ static std::vector<OUString> GetBuiltInGroupNames();
+
+ // XDocumentHandler
+ virtual void SAL_CALL startDocument() override;
+ virtual void SAL_CALL endDocument() override;
+ virtual void SAL_CALL startElement( const OUString& aName, const css::uno::Reference< css::xml::sax::XAttributeList >& xAttribs ) override;
+ virtual void SAL_CALL endElement( const OUString& aName ) override;
+ virtual void SAL_CALL characters( const OUString& aChars ) override;
+ virtual void SAL_CALL ignorableWhitespace( const OUString& aWhitespaces ) override;
+ virtual void SAL_CALL processingInstruction( const OUString& aTarget, const OUString& aData ) override;
+ virtual void SAL_CALL setDocumentLocator( const css::uno::Reference< css::xml::sax::XLocator >& xLocator ) override;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/docundomanager.cxx b/sfx2/source/doc/docundomanager.cxx
new file mode 100644
index 0000000000..94f416c2d7
--- /dev/null
+++ b/sfx2/source/doc/docundomanager.cxx
@@ -0,0 +1,422 @@
+/* -*- 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 <docundomanager.hxx>
+#include <sfx2/sfxbasemodel.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/viewsh.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <com/sun/star/lang/NoSupportException.hpp>
+#include <com/sun/star/lang/NotInitializedException.hpp>
+#include <svl/undo.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <framework/undomanagerhelper.hxx>
+#include <framework/imutex.hxx>
+
+
+namespace sfx2
+{
+
+
+ using ::com::sun::star::uno::Reference;
+ using ::com::sun::star::uno::XInterface;
+ using ::com::sun::star::uno::Sequence;
+ using ::com::sun::star::document::XUndoAction;
+ using ::com::sun::star::lang::NotInitializedException;
+ using ::com::sun::star::document::XUndoManagerListener;
+ using ::com::sun::star::document::XUndoManager;
+ using ::com::sun::star::lang::NoSupportException;
+ using ::com::sun::star::frame::XModel;
+
+ //= DocumentUndoManager_Impl
+
+ struct DocumentUndoManager_Impl : public ::framework::IUndoManagerImplementation
+ {
+ DocumentUndoManager& rAntiImpl;
+ SfxUndoManager* pUndoManager;
+ ::framework::UndoManagerHelper aUndoHelper;
+
+ explicit DocumentUndoManager_Impl( DocumentUndoManager& i_antiImpl )
+ :rAntiImpl( i_antiImpl )
+ ,pUndoManager( impl_retrieveUndoManager( i_antiImpl.getBaseModel() ) )
+ // do this *before* the construction of aUndoHelper (which actually means: put pUndoManager before
+ // aUndoHelper in the member list)!
+ ,aUndoHelper( *this )
+ {
+ }
+
+ virtual ~DocumentUndoManager_Impl()
+ {
+ };
+
+ // IUndoManagerImplementation
+ virtual SfxUndoManager& getImplUndoManager() override;
+ virtual Reference< XUndoManager > getThis() override;
+
+ void disposing()
+ {
+ aUndoHelper.disposing();
+ ENSURE_OR_RETURN_VOID( pUndoManager, "DocumentUndoManager_Impl::disposing: already disposed!" );
+ pUndoManager = nullptr;
+ }
+
+ void invalidateXDo_nolck();
+
+ private:
+ static SfxUndoManager* impl_retrieveUndoManager( SfxBaseModel& i_baseModel )
+ {
+ SfxUndoManager* pUndoManager( nullptr );
+ SfxObjectShell* pObjectShell = i_baseModel.GetObjectShell();
+ if ( pObjectShell != nullptr )
+ pUndoManager = pObjectShell->GetUndoManager();
+ if ( !pUndoManager )
+ throw NotInitializedException( OUString(), i_baseModel );
+ return pUndoManager;
+ }
+ };
+
+
+ SfxUndoManager& DocumentUndoManager_Impl::getImplUndoManager()
+ {
+ ENSURE_OR_THROW( pUndoManager != nullptr, "DocumentUndoManager_Impl::getImplUndoManager: no access to the doc's UndoManager implementation!" );
+
+#if OSL_DEBUG_LEVEL > 0
+ // in a non-product build, assert if the current UndoManager at the shell is not the same we obtained
+ // (and cached) at construction time
+ SfxObjectShell* pObjectShell = rAntiImpl.getBaseModel().GetObjectShell();
+ OSL_ENSURE( ( pObjectShell != nullptr ) && ( pUndoManager == pObjectShell->GetUndoManager() ),
+ "DocumentUndoManager_Impl::getImplUndoManager: the UndoManager changed meanwhile - what about our listener?" );
+#endif
+
+ return *pUndoManager;
+ }
+
+
+ Reference< XUndoManager > DocumentUndoManager_Impl::getThis()
+ {
+ return static_cast< XUndoManager* >( &rAntiImpl );
+ }
+
+
+ void DocumentUndoManager_Impl::invalidateXDo_nolck()
+ {
+ SfxModelGuard aGuard( rAntiImpl );
+
+ const SfxObjectShell* pDocShell = rAntiImpl.getBaseModel().GetObjectShell();
+ ENSURE_OR_THROW( pDocShell != nullptr, "lcl_invalidateUndo: no access to the doc shell!" );
+ SfxViewFrame* pViewFrame = SfxViewFrame::GetFirst( pDocShell );
+ while ( pViewFrame )
+ {
+ pViewFrame->GetBindings().Invalidate( SID_UNDO );
+ pViewFrame->GetBindings().Invalidate( SID_REDO );
+ pViewFrame = SfxViewFrame::GetNext( *pViewFrame, pDocShell );
+ }
+ }
+
+
+ //= SolarMutexFacade
+
+ namespace {
+
+ /** a facade for the SolarMutex, implementing ::framework::IMutex
+ */
+ class SolarMutexFacade : public ::framework::IMutex
+ {
+ public:
+ SolarMutexFacade()
+ {
+ }
+
+ virtual ~SolarMutexFacade() {}
+
+ virtual void acquire() override
+ {
+ Application::GetSolarMutex().acquire();
+ }
+
+ virtual void release() override
+ {
+ Application::GetSolarMutex().release();
+ }
+ };
+
+
+ //= UndoManagerGuard
+
+ class UndoManagerGuard :public ::framework::IMutexGuard
+ {
+ public:
+ explicit UndoManagerGuard( DocumentUndoManager& i_undoManager )
+ :m_guard( i_undoManager )
+ {
+ }
+
+ virtual ~UndoManagerGuard()
+ {
+ }
+
+ UndoManagerGuard(const UndoManagerGuard&) = delete;
+ UndoManagerGuard& operator=(const UndoManagerGuard&) = delete;
+
+ virtual void clear() override
+ {
+ m_guard.clear();
+ }
+
+ virtual ::framework::IMutex& getGuardedMutex() override
+ {
+ // note that this means that we *know* that SfxModelGuard also locks the SolarMutex (nothing more, nothing less).
+ // If this ever changes, we need to adjust this code here, too.
+ return m_solarMutexFacade;
+ }
+
+ private:
+ SfxModelGuard m_guard;
+ SolarMutexFacade m_solarMutexFacade;
+ };
+
+ }
+
+ //= DocumentUndoManager
+
+
+ DocumentUndoManager::DocumentUndoManager( SfxBaseModel& i_document )
+ :SfxModelSubComponent( i_document )
+ ,m_pImpl( new DocumentUndoManager_Impl( *this ) )
+ {
+ }
+
+ DocumentUndoManager::~DocumentUndoManager()
+ {
+ }
+
+ void DocumentUndoManager::disposing()
+ {
+ m_pImpl->disposing();
+ }
+
+
+ bool DocumentUndoManager::isInContext() const
+ {
+ // No mutex locking within this method, no disposal check - this is the responsibility of the owner.
+ return m_pImpl->getImplUndoManager().IsInListAction();
+ }
+
+
+ void SAL_CALL DocumentUndoManager::acquire() noexcept
+ {
+ OWeakObject::acquire();
+ SfxModelSubComponent::acquireModel();
+ }
+
+
+ void SAL_CALL DocumentUndoManager::release() noexcept
+ {
+ SfxModelSubComponent::releaseModel();
+ OWeakObject::release();
+ }
+
+
+ void SAL_CALL DocumentUndoManager::enterUndoContext( const OUString& i_title )
+ {
+ // SYNCHRONIZED --->
+ UndoManagerGuard aGuard( *this );
+ m_pImpl->aUndoHelper.enterUndoContext( i_title, aGuard );
+ // <--- SYNCHRONIZED
+ m_pImpl->invalidateXDo_nolck();
+ }
+
+
+ void SAL_CALL DocumentUndoManager::enterHiddenUndoContext( )
+ {
+ // SYNCHRONIZED --->
+ UndoManagerGuard aGuard( *this );
+ m_pImpl->aUndoHelper.enterHiddenUndoContext( aGuard );
+ // <--- SYNCHRONIZED
+ m_pImpl->invalidateXDo_nolck();
+ }
+
+
+ void SAL_CALL DocumentUndoManager::leaveUndoContext( )
+ {
+ // SYNCHRONIZED --->
+ UndoManagerGuard aGuard( *this );
+ m_pImpl->aUndoHelper.leaveUndoContext( aGuard );
+ // <--- SYNCHRONIZED
+ m_pImpl->invalidateXDo_nolck();
+ }
+
+
+ void SAL_CALL DocumentUndoManager::addUndoAction( const Reference< XUndoAction >& i_action )
+ {
+ // SYNCHRONIZED --->
+ UndoManagerGuard aGuard( *this );
+ m_pImpl->aUndoHelper.addUndoAction( i_action, aGuard );
+ // <--- SYNCHRONIZED
+ m_pImpl->invalidateXDo_nolck();
+ }
+
+
+ void SAL_CALL DocumentUndoManager::undo( )
+ {
+ // SYNCHRONIZED --->
+ UndoManagerGuard aGuard( *this );
+ m_pImpl->aUndoHelper.undo( aGuard );
+ // <--- SYNCHRONIZED
+ m_pImpl->invalidateXDo_nolck();
+ }
+
+
+ void SAL_CALL DocumentUndoManager::redo( )
+ {
+ // SYNCHRONIZED --->
+ UndoManagerGuard aGuard( *this );
+ m_pImpl->aUndoHelper.redo( aGuard );
+ // <--- SYNCHRONIZED
+ m_pImpl->invalidateXDo_nolck();
+ }
+
+
+ sal_Bool SAL_CALL DocumentUndoManager::isUndoPossible( )
+ {
+ UndoManagerGuard aGuard( *this );
+ return m_pImpl->aUndoHelper.isUndoPossible();
+ }
+
+
+ sal_Bool SAL_CALL DocumentUndoManager::isRedoPossible( )
+ {
+ UndoManagerGuard aGuard( *this );
+ return m_pImpl->aUndoHelper.isRedoPossible();
+ }
+
+
+ OUString SAL_CALL DocumentUndoManager::getCurrentUndoActionTitle( )
+ {
+ UndoManagerGuard aGuard( *this );
+ return m_pImpl->aUndoHelper.getCurrentUndoActionTitle();
+ }
+
+
+ OUString SAL_CALL DocumentUndoManager::getCurrentRedoActionTitle( )
+ {
+ UndoManagerGuard aGuard( *this );
+ return m_pImpl->aUndoHelper.getCurrentRedoActionTitle();
+ }
+
+
+ Sequence< OUString > SAL_CALL DocumentUndoManager::getAllUndoActionTitles( )
+ {
+ UndoManagerGuard aGuard( *this );
+ return m_pImpl->aUndoHelper.getAllUndoActionTitles();
+ }
+
+
+ Sequence< OUString > SAL_CALL DocumentUndoManager::getAllRedoActionTitles( )
+ {
+ UndoManagerGuard aGuard( *this );
+ return m_pImpl->aUndoHelper.getAllRedoActionTitles();
+ }
+
+
+ void SAL_CALL DocumentUndoManager::clear( )
+ {
+ // SYNCHRONIZED --->
+ UndoManagerGuard aGuard( *this );
+ m_pImpl->aUndoHelper.clear( aGuard );
+ // <--- SYNCHRONIZED
+ m_pImpl->invalidateXDo_nolck();
+ }
+
+
+ void SAL_CALL DocumentUndoManager::clearRedo( )
+ {
+ // SYNCHRONIZED --->
+ UndoManagerGuard aGuard( *this );
+ m_pImpl->aUndoHelper.clearRedo( aGuard );
+ // <--- SYNCHRONIZED
+ m_pImpl->invalidateXDo_nolck();
+ }
+
+
+ void SAL_CALL DocumentUndoManager::reset()
+ {
+ // SYNCHRONIZED --->
+ UndoManagerGuard aGuard( *this );
+ m_pImpl->aUndoHelper.reset( aGuard );
+ // <--- SYNCHRONIZED
+ m_pImpl->invalidateXDo_nolck();
+ }
+
+
+ void SAL_CALL DocumentUndoManager::lock( )
+ {
+ UndoManagerGuard aGuard( *this );
+ m_pImpl->aUndoHelper.lock();
+ }
+
+
+ void SAL_CALL DocumentUndoManager::unlock( )
+ {
+ UndoManagerGuard aGuard( *this );
+ m_pImpl->aUndoHelper.unlock();
+ }
+
+
+ sal_Bool SAL_CALL DocumentUndoManager::isLocked( )
+ {
+ UndoManagerGuard aGuard( *this );
+ return m_pImpl->aUndoHelper.isLocked();
+ }
+
+
+ void SAL_CALL DocumentUndoManager::addUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
+ {
+ UndoManagerGuard aGuard( *this );
+ return m_pImpl->aUndoHelper.addUndoManagerListener( i_listener );
+ }
+
+
+ void SAL_CALL DocumentUndoManager::removeUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
+ {
+ UndoManagerGuard aGuard( *this );
+ return m_pImpl->aUndoHelper.removeUndoManagerListener( i_listener );
+ }
+
+
+ Reference< XInterface > SAL_CALL DocumentUndoManager::getParent( )
+ {
+ UndoManagerGuard aGuard( *this );
+ return static_cast< XModel* >( &getBaseModel() );
+ }
+
+
+ void SAL_CALL DocumentUndoManager::setParent( const Reference< XInterface >& )
+ {
+ throw NoSupportException( OUString(), m_pImpl->getThis() );
+ }
+
+
+} // namespace sfx2
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/exoticfileloadexception.cxx b/sfx2/source/doc/exoticfileloadexception.cxx
new file mode 100644
index 0000000000..2b2500d71a
--- /dev/null
+++ b/sfx2/source/doc/exoticfileloadexception.cxx
@@ -0,0 +1,32 @@
+/* -*- 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/.
+ */
+
+#include "exoticfileloadexception.hxx"
+
+#include <comphelper/interaction.hxx>
+#include <com/sun/star/document/ExoticFileLoadException.hpp>
+
+using namespace com::sun::star;
+
+ExoticFileLoadException::ExoticFileLoadException(const OUString& rURL,
+ const OUString& rFilterUIName)
+ : m_xAbort(new comphelper::OInteractionAbort)
+ , m_xApprove(new comphelper::OInteractionApprove)
+ , m_lContinuations{ m_xApprove, m_xAbort }
+{
+ document::ExoticFileLoadException aReq;
+ aReq.URL = rURL;
+ aReq.FilterUIName = rFilterUIName;
+
+ m_aRequest <<= aReq;
+}
+
+bool ExoticFileLoadException::isApprove() const { return m_xApprove->wasSelected(); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/exoticfileloadexception.hxx b/sfx2/source/doc/exoticfileloadexception.hxx
new file mode 100644
index 0000000000..9a4211d5d6
--- /dev/null
+++ b/sfx2/source/doc/exoticfileloadexception.hxx
@@ -0,0 +1,45 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_SFX2_EXOTICFILELOADEXCEPTION_HXX
+#define INCLUDED_SFX2_EXOTICFILELOADEXCEPTION_HXX
+
+#include <com/sun/star/task/XInteractionContinuation.hpp>
+#include <com/sun/star/task/XInteractionRequest.hpp>
+#include <comphelper/interaction.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <rtl/ref.hxx>
+
+class ExoticFileLoadException : public cppu::WeakImplHelper<css::task::XInteractionRequest>
+{
+ // C++ interface
+public:
+ ExoticFileLoadException(const OUString& rURL, const OUString& rFilterUIName);
+ bool isApprove() const;
+
+ // UNO interface
+public:
+ virtual css::uno::Sequence<css::uno::Reference<css::task::XInteractionContinuation>>
+ SAL_CALL getContinuations() override
+ {
+ return m_lContinuations;
+ }
+ css::uno::Any SAL_CALL getRequest() override { return m_aRequest; }
+
+ // member
+private:
+ css::uno::Any m_aRequest;
+ rtl::Reference<comphelper::OInteractionAbort> m_xAbort;
+ rtl::Reference<comphelper::OInteractionApprove> m_xApprove;
+ css::uno::Sequence<css::uno::Reference<css::task::XInteractionContinuation>> m_lContinuations;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/frmdescr.cxx b/sfx2/source/doc/frmdescr.cxx
new file mode 100644
index 0000000000..43183986d2
--- /dev/null
+++ b/sfx2/source/doc/frmdescr.cxx
@@ -0,0 +1,57 @@
+/* -*- 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 <svl/itemset.hxx>
+
+#include <sfx2/frmdescr.hxx>
+#include <sfx2/app.hxx>
+#include <memory>
+
+SfxFrameDescriptor::SfxFrameDescriptor() :
+ aMargin( -1, -1 ),
+ eScroll( ScrollingMode::Auto ),
+ bHasBorder( true ),
+ bHasBorderSet( false )
+{
+}
+
+SfxFrameDescriptor::~SfxFrameDescriptor()
+{
+}
+
+SfxItemSet* SfxFrameDescriptor::GetArgs()
+{
+ if( !m_pArgs )
+ m_pArgs.reset( new SfxAllItemSet( SfxGetpApp()->GetPool() ) );
+ return m_pArgs.get();
+}
+
+void SfxFrameDescriptor::SetURL( std::u16string_view rURL )
+{
+ aURL = INetURLObject(rURL);
+ SetActualURL();
+}
+
+void SfxFrameDescriptor::SetActualURL()
+{
+ if ( m_pArgs )
+ m_pArgs->ClearItem();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/graphhelp.cxx b/sfx2/source/doc/graphhelp.cxx
new file mode 100644
index 0000000000..7cfdf76fe6
--- /dev/null
+++ b/sfx2/source/doc/graphhelp.cxx
@@ -0,0 +1,259 @@
+/* -*- 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 .
+ */
+
+
+#ifdef _WIN32
+#include <prewin.h>
+#include <postwin.h>
+#endif
+
+#include <com/sun/star/uno/Exception.hpp>
+#include <com/sun/star/graphic/GraphicProvider.hpp>
+#include <com/sun/star/graphic/XGraphicProvider.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/graphicfilter.hxx>
+
+#include <tools/stream.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <o3tl/string_view.hxx>
+
+#include "graphhelp.hxx"
+#include <bitmaps.hlst>
+
+#include <memory>
+
+#if defined _WIN32
+#include <unotools/tempfile.hxx>
+#include <vcl/outdev.hxx>
+#endif
+
+using namespace css;
+
+std::unique_ptr<SvMemoryStream> GraphicHelper::getFormatStrFromGDI_Impl( const GDIMetaFile* pGDIMeta, ConvertDataFormat nFormat )
+{
+ std::unique_ptr<SvMemoryStream> pResult;
+ if ( pGDIMeta )
+ {
+ std::unique_ptr<SvMemoryStream> pStream(new SvMemoryStream( 65535, 65535 ));
+ Graphic aGraph( *pGDIMeta );
+ if ( GraphicConverter::Export( *pStream, aGraph, nFormat ) == ERRCODE_NONE )
+ pResult = std::move(pStream);
+ }
+
+ return pResult;
+}
+
+
+// static
+void* GraphicHelper::getEnhMetaFileFromGDI_Impl( const GDIMetaFile* pGDIMeta )
+{
+ void* pResult = nullptr;
+
+#ifdef _WIN32
+ if ( pGDIMeta )
+ {
+ ::utl::TempFileNamed aTempFile( u"", true, u".emf" );
+
+ OUString aMetaFile = aTempFile.GetFileName();
+ OUString aMetaURL = aTempFile.GetURL();
+
+ std::unique_ptr<SvStream> pStream = ::utl::UcbStreamHelper::CreateStream( aMetaURL, StreamMode::STD_READWRITE );
+ if ( pStream )
+ {
+ Graphic aGraph( *pGDIMeta );
+ ErrCode nFailed = GraphicConverter::Export( *pStream, aGraph, ConvertDataFormat::EMF );
+ pStream->Flush();
+ pStream.reset();
+
+ if ( !nFailed )
+ pResult = GetEnhMetaFileW( o3tl::toW(aMetaFile.getStr()) );
+ }
+ }
+#else
+ (void)pGDIMeta; // unused
+#endif
+
+ return pResult;
+}
+
+
+// static
+void* GraphicHelper::getWinMetaFileFromGDI_Impl( const GDIMetaFile* pGDIMeta, const Size& aMetaSize )
+{
+ void* pResult = nullptr;
+
+#ifdef _WIN32
+ if ( pGDIMeta )
+ {
+ SvMemoryStream pStream( 65535, 65535 );
+ Graphic aGraph( *pGDIMeta );
+ ErrCode nFailed = GraphicConverter::Export( pStream, aGraph, ConvertDataFormat::WMF );
+ pStream.Flush();
+ if ( !nFailed )
+ {
+ sal_uInt64 nLength = pStream.TellEnd();
+ if ( nLength > 22 )
+ {
+ HMETAFILE hMeta = SetMetaFileBitsEx( nLength - 22,
+ static_cast< const unsigned char*>( pStream.GetData() ) + 22 );
+
+ if ( hMeta )
+ {
+ HGLOBAL hMemory = GlobalAlloc( GMEM_DDESHARE | GMEM_MOVEABLE, sizeof( METAFILEPICT ) );
+
+ if ( hMemory )
+ {
+ METAFILEPICT* pMF = static_cast<METAFILEPICT*>(GlobalLock( hMemory ));
+
+ pMF->hMF = hMeta;
+ pMF->mm = MM_ANISOTROPIC;
+
+ MapMode aWinMode( MapUnit::Map100thMM );
+
+ if ( aWinMode == pGDIMeta->GetPrefMapMode() )
+ {
+ pMF->xExt = aMetaSize.Width();
+ pMF->yExt = aMetaSize.Height();
+ }
+ else
+ {
+ Size aWinSize = OutputDevice::LogicToLogic( Size( aMetaSize.Width(), aMetaSize.Height() ),
+ pGDIMeta->GetPrefMapMode(),
+ aWinMode );
+ pMF->xExt = aWinSize.Width();
+ pMF->yExt = aWinSize.Height();
+ }
+
+ GlobalUnlock( hMemory );
+ pResult = static_cast<void*>(hMemory);
+ }
+ else
+ DeleteMetaFile( hMeta );
+ }
+ }
+ }
+ }
+#else
+ (void)pGDIMeta; // unused
+ (void)aMetaSize; // unused
+#endif
+
+
+ return pResult;
+}
+
+
+// static
+bool GraphicHelper::getThumbnailFormatFromBitmap_Impl(const BitmapEx& rBitmap, const uno::Reference<io::XStream>& xStream)
+{
+ if (rBitmap.IsEmpty() || !xStream.is())
+ return false;
+
+ std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xStream));
+
+ if (pStream->GetError())
+ return false;
+
+ BitmapEx bitmap(rBitmap);
+ bitmap.Convert(BmpConversion::N8BitColors);
+
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+
+ if (rFilter.compressAsPNG(bitmap, *pStream) != ERRCODE_NONE)
+ return false;
+
+ pStream->FlushBuffer();
+
+ return !pStream->GetError();
+}
+
+// static
+bool GraphicHelper::getThumbnailReplacement_Impl(std::u16string_view rResID, const uno::Reference< io::XStream >& xStream )
+{
+ bool bResult = false;
+ if (!rResID.empty() && xStream.is())
+ {
+ uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+ try
+ {
+ uno::Reference< graphic::XGraphicProvider > xGraphProvider(graphic::GraphicProvider::create(xContext));
+ const OUString aURL{OUString::Concat("private:graphicrepository/") + rResID};
+
+ uno::Sequence< beans::PropertyValue > aMediaProps{ comphelper::makePropertyValue("URL",
+ aURL) };
+
+ uno::Reference< graphic::XGraphic > xGraphic = xGraphProvider->queryGraphic( aMediaProps );
+ if ( xGraphic.is() )
+ {
+ uno::Sequence< beans::PropertyValue > aStoreProps{
+ comphelper::makePropertyValue("OutputStream", xStream),
+ comphelper::makePropertyValue("MimeType", OUString("image/png"))
+ };
+
+ xGraphProvider->storeGraphic( xGraphic, aStoreProps );
+ bResult = true;
+ }
+ }
+ catch(const uno::Exception&)
+ {
+ }
+ }
+
+ return bResult;
+}
+
+// static
+OUString GraphicHelper::getThumbnailReplacementIDByFactoryName_Impl( std::u16string_view aFactoryShortName )
+{
+ OUString sResult;
+
+ if ( aFactoryShortName == u"scalc" )
+ {
+ sResult = BMP_128X128_CALC_DOC;
+ }
+ else if ( aFactoryShortName == u"sdraw" )
+ {
+ sResult = BMP_128X128_DRAW_DOC;
+ }
+ else if ( aFactoryShortName == u"simpress" )
+ {
+ sResult = BMP_128X128_IMPRESS_DOC;
+ }
+ else if ( aFactoryShortName == u"smath" )
+ {
+ sResult = BMP_128X128_MATH_DOC;
+ }
+ else if ( aFactoryShortName == u"swriter" || o3tl::starts_with(aFactoryShortName, u"swriter/") )
+ {
+ sResult = BMP_128X128_WRITER_DOC;
+ }
+
+ return sResult;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/graphhelp.hxx b/sfx2/source/doc/graphhelp.hxx
new file mode 100644
index 0000000000..1945661fc1
--- /dev/null
+++ b/sfx2/source/doc/graphhelp.hxx
@@ -0,0 +1,71 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SFX2_SOURCE_DOC_GRAPHHELP_HXX
+#define INCLUDED_SFX2_SOURCE_DOC_GRAPHHELP_HXX
+
+#include <sal/config.h>
+
+#include <string_view>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/io/XStream.hpp>
+#include <rtl/ustring.hxx>
+#include <tools/gen.hxx>
+
+class SvMemoryStream;
+class GDIMetaFile;
+class BitmapEx;
+enum class ConvertDataFormat;
+
+class GraphicHelper
+{
+public:
+
+ static std::unique_ptr<SvMemoryStream> getFormatStrFromGDI_Impl( const GDIMetaFile* pGDIMeta, ConvertDataFormat nFormat );
+
+ static void* getEnhMetaFileFromGDI_Impl( const GDIMetaFile* pGDIMeta );
+
+ static void* getWinMetaFileFromGDI_Impl( const GDIMetaFile* pGDIMeta, const Size& aMetaSize );
+
+ static bool supportsMetaFileHandle_Impl()
+ {
+#ifdef _WIN32
+ return true;
+#else
+ return false;
+#endif
+ }
+
+ static bool getThumbnailFormatFromBitmap_Impl(
+ const BitmapEx& rBitmap,
+ const css::uno::Reference< css::io::XStream >& xStream );
+
+ static OUString getThumbnailReplacementIDByFactoryName_Impl(
+ std::u16string_view aFactoryShortName);
+
+ static bool getThumbnailReplacement_Impl(
+ std::u16string_view rResID,
+ const css::uno::Reference< css::io::XStream >& xStream );
+
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/guisaveas.cxx b/sfx2/source/doc/guisaveas.cxx
new file mode 100644
index 0000000000..9e06dca31d
--- /dev/null
+++ b/sfx2/source/doc/guisaveas.cxx
@@ -0,0 +1,2015 @@
+/* -*- 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 <com/sun/star/ui/dialogs/XExecutableDialog.hpp>
+#include <com/sun/star/ui/dialogs/XAsynchronousExecutableDialog.hpp>
+#include <com/sun/star/ui/dialogs/XFilePicker3.hpp>
+#include <com/sun/star/ui/dialogs/XFilePickerControlAccess.hpp>
+#include <com/sun/star/ui/dialogs/CommonFilePickerElementIds.hpp>
+#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+#include <com/sun/star/view/XSelectionSupplier.hpp>
+#include <com/sun/star/beans/PropertyExistException.hpp>
+#include <com/sun/star/beans/XPropertyAccess.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/XPropertyContainer.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/document/XExporter.hpp>
+#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
+#include <com/sun/star/document/XDocumentProperties.hpp>
+#include <com/sun/star/task/ErrorCodeIOException.hpp>
+#include <com/sun/star/task/InteractionHandler.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <com/sun/star/util/XURLTransformer.hpp>
+#include <com/sun/star/frame/ModuleManager.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/frame/XStorable2.hpp>
+#include <com/sun/star/frame/XDispatchProvider.hpp>
+#include <com/sun/star/frame/XDispatch.hpp>
+#include <com/sun/star/frame/XTitle.hpp>
+#include <com/sun/star/util/XModifiable.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+
+#include <com/sun/star/util/XCloneable.hpp>
+
+#include <guisaveas.hxx>
+
+#include <sal/log.hxx>
+#include <svl/itemset.hxx>
+#include <svl/eitem.hxx>
+#include <tools/debug.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/json_writer.hxx>
+#include <tools/urlobj.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/sequenceashashmap.hxx>
+#include <comphelper/mimeconfighelper.hxx>
+#include <comphelper/lok.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <unotools/tempfile.hxx>
+#include <unotools/useroptions.hxx>
+
+#include <sfx2/objsh.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <sfx2/strings.hrc>
+#include <sfx2/sfxresid.hxx>
+#include <sfx2/filedlghelper.hxx>
+#include <sfx2/app.hxx>
+#include <sfx2/sfxuno.hxx>
+#include <sfx2/viewsh.hxx>
+#include <sfx2/bindings.hxx>
+#include <alienwarn.hxx>
+
+#include <memory>
+#include <string_view>
+
+#include <officecfg/Office/Common.hxx>
+
+#include <vcl/FilterConfigItem.hxx>
+#include <com/sun/star/system/SystemShellExecute.hpp>
+#include <com/sun/star/system/SystemShellExecuteFlags.hpp>
+
+#include <osl/file.hxx>
+
+#ifdef _WIN32
+#include <Shlobj.h>
+#ifdef GetTempPath
+#undef GetTempPath
+#endif
+#endif
+
+// flags that specify requested operation
+#define EXPORT_REQUESTED 1
+#define PDFEXPORT_REQUESTED 2
+#define PDFDIRECTEXPORT_REQUESTED 4
+#define WIDEEXPORT_REQUESTED 8
+#define SAVE_REQUESTED 16
+#define SAVEAS_REQUESTED 32
+#define SAVEACOPY_REQUESTED 64
+#define EPUBEXPORT_REQUESTED 128
+#define EPUBDIRECTEXPORT_REQUESTED 256
+#define SAVEASREMOTE_REQUESTED -1
+
+// possible statuses of save operation
+#define STATUS_NO_ACTION 0
+#define STATUS_SAVE 1
+#define STATUS_SAVEAS 2
+#define STATUS_SAVEAS_STANDARDNAME 3
+
+constexpr OUString aFilterNameString = u"FilterName"_ustr;
+constexpr OUString aFilterOptionsString = u"FilterOptions"_ustr;
+constexpr OUString aFilterDataString = u"FilterData"_ustr;
+
+using namespace ::com::sun::star;
+using namespace css::system;
+
+namespace {
+
+sal_uInt16 getSlotIDFromMode( sal_Int16 nStoreMode )
+{
+ // This is a temporary hardcoded solution must be removed when
+ // dialogs do not need parameters in SidSet representation any more
+
+ sal_uInt16 nResult = 0;
+ if ( nStoreMode == EXPORT_REQUESTED || nStoreMode == ( EXPORT_REQUESTED | SAVEACOPY_REQUESTED | WIDEEXPORT_REQUESTED ) )
+ nResult = SID_EXPORTDOC;
+ else if ( nStoreMode == ( EXPORT_REQUESTED | PDFEXPORT_REQUESTED ) )
+ nResult = SID_EXPORTDOCASPDF;
+ else if ( nStoreMode == ( EXPORT_REQUESTED | EPUBEXPORT_REQUESTED ) )
+ nResult = SID_EXPORTDOCASEPUB;
+ else if ( nStoreMode == ( EXPORT_REQUESTED | PDFEXPORT_REQUESTED | PDFDIRECTEXPORT_REQUESTED ) )
+ nResult = SID_DIRECTEXPORTDOCASPDF;
+ else if ( nStoreMode == ( EXPORT_REQUESTED | EPUBEXPORT_REQUESTED | EPUBDIRECTEXPORT_REQUESTED ) )
+ nResult = SID_DIRECTEXPORTDOCASEPUB;
+ else if ( nStoreMode == SAVEAS_REQUESTED || nStoreMode == ( EXPORT_REQUESTED | WIDEEXPORT_REQUESTED ) )
+ nResult = SID_SAVEASDOC;
+ else if ( nStoreMode == SAVEASREMOTE_REQUESTED )
+ nResult = SID_SAVEASREMOTE;
+ else {
+ SAL_WARN( "sfx.doc", "Unacceptable slot name is provided!" );
+ }
+
+ return nResult;
+}
+
+
+sal_Int16 getStoreModeFromSlotName( std::u16string_view aSlotName )
+{
+ sal_Int16 nResult = 0;
+ if ( aSlotName == u"ExportTo" )
+ nResult = EXPORT_REQUESTED;
+ else if ( aSlotName == u"ExportToPDF" )
+ nResult = EXPORT_REQUESTED | PDFEXPORT_REQUESTED;
+ else if ( aSlotName == u"ExportDirectToPDF" )
+ nResult = EXPORT_REQUESTED | PDFEXPORT_REQUESTED | PDFDIRECTEXPORT_REQUESTED;
+ else if ( aSlotName == u"ExportToEPUB" )
+ nResult = EXPORT_REQUESTED | EPUBEXPORT_REQUESTED;
+ else if ( aSlotName == u"ExportDirectToEPUB" )
+ nResult = EXPORT_REQUESTED | EPUBEXPORT_REQUESTED | EPUBDIRECTEXPORT_REQUESTED;
+ else if ( aSlotName == u"Save" )
+ nResult = SAVE_REQUESTED;
+ else if ( aSlotName == u"SaveAs" )
+ nResult = SAVEAS_REQUESTED;
+ else if ( aSlotName == u"SaveAsRemote" )
+ nResult = SAVEASREMOTE_REQUESTED;
+ else
+ throw task::ErrorCodeIOException(
+ (OUString::Concat("getStoreModeFromSlotName(\"") + aSlotName
+ + "): ERRCODE_IO_INVALIDPARAMETER"),
+ uno::Reference< uno::XInterface >(), sal_uInt32(ERRCODE_IO_INVALIDPARAMETER) );
+
+ return nResult;
+}
+
+
+SfxFilterFlags getMustFlags( sal_Int16 nStoreMode )
+{
+ return ( SfxFilterFlags::EXPORT
+ | ( ( ( nStoreMode & EXPORT_REQUESTED ) && !( nStoreMode & WIDEEXPORT_REQUESTED ) ) ? SfxFilterFlags::NONE : SfxFilterFlags::IMPORT ) );
+}
+
+
+SfxFilterFlags getDontFlags( sal_Int16 nStoreMode )
+{
+ return ( SfxFilterFlags::INTERNAL
+ | SfxFilterFlags::NOTINFILEDLG
+ | ( ( ( nStoreMode & EXPORT_REQUESTED ) && !( nStoreMode & WIDEEXPORT_REQUESTED ) ) ? SfxFilterFlags::IMPORT : SfxFilterFlags::NONE ) );
+}
+
+
+
+
+class DocumentSettingsGuard
+{
+ uno::Reference< beans::XPropertySet > m_xDocumentSettings;
+ bool m_bPreserveReadOnly;
+ bool m_bReadOnlySupported;
+
+ bool m_bRestoreSettings;
+public:
+ DocumentSettingsGuard( const uno::Reference< frame::XModel >& xModel, bool bReadOnly, bool bRestore )
+ : m_bPreserveReadOnly( false )
+ , m_bReadOnlySupported( false )
+ , m_bRestoreSettings( bRestore )
+ {
+ try
+ {
+ uno::Reference< lang::XMultiServiceFactory > xDocSettingsSupplier( xModel, uno::UNO_QUERY_THROW );
+ m_xDocumentSettings.set(
+ xDocSettingsSupplier->createInstance( "com.sun.star.document.Settings" ),
+ uno::UNO_QUERY_THROW );
+
+ try
+ {
+ OUString aLoadReadonlyString( "LoadReadonly" );
+ m_xDocumentSettings->getPropertyValue( aLoadReadonlyString ) >>= m_bPreserveReadOnly;
+ m_xDocumentSettings->setPropertyValue( aLoadReadonlyString, uno::Any( bReadOnly ) );
+ m_bReadOnlySupported = true;
+ }
+ catch( const uno::Exception& )
+ {}
+ }
+ catch( const uno::Exception& )
+ {}
+
+ if ( bReadOnly && !m_bReadOnlySupported )
+ throw uno::RuntimeException(); // the user could provide the data, so it must be stored
+ }
+
+ ~DocumentSettingsGuard()
+ {
+ if ( m_bRestoreSettings )
+ {
+ try
+ {
+ if ( m_bReadOnlySupported )
+ m_xDocumentSettings->setPropertyValue( "LoadReadonly", uno::Any( m_bPreserveReadOnly ) );
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "" );
+ }
+ }
+ }
+};
+} // anonymous namespace
+
+
+
+class ModelData_Impl
+{
+ SfxStoringHelper* m_pOwner;
+ uno::Reference< frame::XModel > m_xModel;
+ uno::Reference< frame::XStorable > m_xStorable;
+ uno::Reference< frame::XStorable2 > m_xStorable2;
+
+ OUString m_aModuleName;
+ std::unique_ptr<::comphelper::SequenceAsHashMap> m_pDocumentPropsHM;
+ std::unique_ptr<::comphelper::SequenceAsHashMap> m_pModulePropsHM;
+
+ uno::Reference<beans::XPropertyAccess> m_xFilterProperties;
+ uno::Reference<ui::dialogs::XAsynchronousExecutableDialog> m_xFilterDialog;
+
+ ::comphelper::SequenceAsHashMap m_aMediaDescrHM;
+
+ bool m_bRecommendReadOnly;
+
+ DECL_LINK(OptionsDialogClosedHdl, css::ui::dialogs::DialogClosedEvent*, void);
+
+public:
+ ModelData_Impl( SfxStoringHelper& aOwner,
+ uno::Reference< frame::XModel > xModel,
+ const uno::Sequence< beans::PropertyValue >& aMediaDescr );
+
+ ~ModelData_Impl();
+
+ void FreeDocumentProps();
+
+ uno::Reference< frame::XModel > const & GetModel() const;
+ uno::Reference< frame::XStorable > const & GetStorable();
+ uno::Reference< frame::XStorable2 > const & GetStorable2();
+
+ ::comphelper::SequenceAsHashMap& GetMediaDescr() { return m_aMediaDescrHM; }
+
+ bool IsRecommendReadOnly() const { return m_bRecommendReadOnly; }
+
+ const ::comphelper::SequenceAsHashMap& GetDocProps();
+
+ OUString const & GetModuleName();
+ const ::comphelper::SequenceAsHashMap& GetModuleProps();
+
+ void CheckInteractionHandler();
+
+
+ OUString GetDocServiceName();
+ uno::Sequence< beans::PropertyValue > GetDocServiceDefaultFilterCheckFlags( SfxFilterFlags nMust, SfxFilterFlags nDont );
+ uno::Sequence< beans::PropertyValue > GetDocServiceAnyFilter( SfxFilterFlags nMust, SfxFilterFlags nDont );
+ uno::Sequence< beans::PropertyValue > GetPreselectedFilter_Impl( sal_Int16 nStoreMode );
+ uno::Sequence< beans::PropertyValue > GetDocServiceDefaultFilter();
+
+ bool ExecuteFilterDialog_Impl( const OUString& aFilterName, bool bAsync );
+
+ sal_Int8 CheckSaveAcceptable( sal_Int8 nCurStatus );
+ sal_Int8 CheckStateForSave();
+
+ sal_Int8 CheckFilter( const OUString& );
+
+ bool CheckFilterOptionsDialogExistence();
+
+ bool OutputFileDialog( sal_Int16 nStoreMode,
+ const ::comphelper::SequenceAsHashMap& aPreselectedFilterPropsHM,
+ bool bSetStandardName,
+ OUString& aSuggestedName,
+ bool bPreselectPassword,
+ OUString& aSuggestedDir,
+ sal_Int16 nDialog,
+ const OUString& rStandardDir,
+ const css::uno::Sequence< OUString >& rDenyList
+ );
+
+ bool ShowDocumentInfoDialog();
+
+ static OUString GetRecommendedExtension( const OUString& aTypeName );
+ OUString GetRecommendedDir( const OUString& aSuggestedDir );
+ OUString GetRecommendedName( const OUString& aSuggestedName,
+ const OUString& aTypeName );
+};
+
+
+ModelData_Impl::ModelData_Impl( SfxStoringHelper& aOwner,
+ uno::Reference< frame::XModel > xModel,
+ const uno::Sequence< beans::PropertyValue >& aMediaDescr )
+: m_pOwner( &aOwner )
+, m_xModel(std::move( xModel ))
+, m_aMediaDescrHM( aMediaDescr )
+, m_bRecommendReadOnly( false )
+{
+ CheckInteractionHandler();
+}
+
+
+ModelData_Impl::~ModelData_Impl()
+{
+ FreeDocumentProps();
+ m_pDocumentPropsHM.reset();
+ m_pModulePropsHM.reset();
+ if (m_xFilterProperties)
+ m_xFilterProperties.clear();
+}
+
+
+void ModelData_Impl::FreeDocumentProps()
+{
+ m_pDocumentPropsHM.reset();
+}
+
+
+uno::Reference< frame::XModel > const & ModelData_Impl::GetModel() const
+{
+ if ( !m_xModel.is() )
+ throw uno::RuntimeException();
+
+ return m_xModel;
+}
+
+
+uno::Reference< frame::XStorable > const & ModelData_Impl::GetStorable()
+{
+ if ( !m_xStorable.is() )
+ {
+ m_xStorable.set( m_xModel, uno::UNO_QUERY_THROW );
+ }
+
+ return m_xStorable;
+}
+
+
+uno::Reference< frame::XStorable2 > const & ModelData_Impl::GetStorable2()
+{
+ if ( !m_xStorable2.is() )
+ {
+ m_xStorable2.set( m_xModel, uno::UNO_QUERY_THROW );
+ }
+
+ return m_xStorable2;
+}
+
+
+const ::comphelper::SequenceAsHashMap& ModelData_Impl::GetDocProps()
+{
+ if ( !m_pDocumentPropsHM )
+ m_pDocumentPropsHM.reset( new ::comphelper::SequenceAsHashMap( GetModel()->getArgs() ) );
+
+ return *m_pDocumentPropsHM;
+}
+
+
+OUString const & ModelData_Impl::GetModuleName()
+{
+ if ( m_aModuleName.isEmpty() )
+ {
+ m_aModuleName = m_pOwner->GetModuleManager()->identify(
+ uno::Reference< uno::XInterface >( m_xModel, uno::UNO_QUERY ) );
+ if ( m_aModuleName.isEmpty() )
+ throw uno::RuntimeException(); // TODO:
+ }
+ return m_aModuleName;
+}
+
+
+const ::comphelper::SequenceAsHashMap& ModelData_Impl::GetModuleProps()
+{
+ if ( !m_pModulePropsHM )
+ {
+ uno::Sequence< beans::PropertyValue > aModuleProps;
+ m_pOwner->GetModuleManager()->getByName( GetModuleName() ) >>= aModuleProps;
+ if ( !aModuleProps.hasElements() )
+ throw uno::RuntimeException(); // TODO;
+ m_pModulePropsHM.reset( new ::comphelper::SequenceAsHashMap( aModuleProps ) );
+ }
+
+ return *m_pModulePropsHM;
+}
+
+
+OUString ModelData_Impl::GetDocServiceName()
+{
+ return GetModuleProps().getUnpackedValueOrDefault("ooSetupFactoryDocumentService", OUString());
+}
+
+
+void ModelData_Impl::CheckInteractionHandler()
+{
+ static constexpr OUString sInteractionHandler {u"InteractionHandler"_ustr};
+ ::comphelper::SequenceAsHashMap::const_iterator aInteractIter =
+ m_aMediaDescrHM.find( sInteractionHandler );
+
+ if ( aInteractIter == m_aMediaDescrHM.end() )
+ {
+ try {
+ m_aMediaDescrHM[ sInteractionHandler ]
+ <<= task::InteractionHandler::createWithParent( comphelper::getProcessComponentContext(), nullptr);
+ }
+ catch( const uno::Exception& )
+ {
+ }
+ }
+ else
+ {
+ uno::Reference< task::XInteractionHandler > xInteract;
+ DBG_ASSERT( ( aInteractIter->second >>= xInteract ) && xInteract.is(), "Broken interaction handler is provided!\n" );
+ }
+}
+
+
+uno::Sequence< beans::PropertyValue > ModelData_Impl::GetDocServiceDefaultFilter()
+{
+ uno::Sequence< beans::PropertyValue > aProps;
+
+ const OUString aFilterName = GetModuleProps().getUnpackedValueOrDefault( "ooSetupFactoryDefaultFilter", OUString() );
+
+ m_pOwner->GetFilterConfiguration()->getByName( aFilterName ) >>= aProps;
+
+ return aProps;
+}
+
+
+uno::Sequence< beans::PropertyValue > ModelData_Impl::GetDocServiceDefaultFilterCheckFlags( SfxFilterFlags nMust,
+ SfxFilterFlags nDont )
+{
+ uno::Sequence< beans::PropertyValue > aFilterProps;
+ uno::Sequence< beans::PropertyValue > aProps = GetDocServiceDefaultFilter();
+ if ( aProps.hasElements() )
+ {
+ ::comphelper::SequenceAsHashMap aFiltHM( aProps );
+ SfxFilterFlags nFlags = static_cast<SfxFilterFlags>(aFiltHM.getUnpackedValueOrDefault("Flags",
+ sal_Int32(0) ));
+ if ( ( ( nFlags & nMust ) == nMust ) && !( nFlags & nDont ) )
+ aFilterProps = aProps;
+ }
+
+ return aFilterProps;
+}
+
+
+uno::Sequence< beans::PropertyValue > ModelData_Impl::GetDocServiceAnyFilter( SfxFilterFlags nMust, SfxFilterFlags nDont )
+{
+ uno::Sequence< beans::NamedValue > aSearchRequest { { "DocumentService", css::uno::Any(GetDocServiceName()) } };
+
+ return ::comphelper::MimeConfigurationHelper::SearchForFilter( m_pOwner->GetFilterQuery(), aSearchRequest, nMust, nDont );
+}
+
+
+uno::Sequence< beans::PropertyValue > ModelData_Impl::GetPreselectedFilter_Impl( sal_Int16 nStoreMode )
+{
+ if ( nStoreMode == SAVEASREMOTE_REQUESTED )
+ nStoreMode = SAVEAS_REQUESTED;
+
+ uno::Sequence< beans::PropertyValue > aFilterProps;
+
+ SfxFilterFlags nMust = getMustFlags( nStoreMode );
+ SfxFilterFlags nDont = getDontFlags( nStoreMode );
+
+ if ( ( nStoreMode != SAVEASREMOTE_REQUESTED ) && ( nStoreMode & PDFEXPORT_REQUESTED ) )
+ {
+ // Preselect PDF-Filter for EXPORT
+ uno::Sequence< beans::NamedValue > aSearchRequest
+ {
+ { "Type", css::uno::Any(OUString("pdf_Portable_Document_Format")) },
+ { "DocumentService", css::uno::Any(GetDocServiceName()) }
+ };
+
+ aFilterProps = ::comphelper::MimeConfigurationHelper::SearchForFilter( m_pOwner->GetFilterQuery(), aSearchRequest, nMust, nDont );
+ }
+ else if ( ( nStoreMode != SAVEASREMOTE_REQUESTED ) && ( nStoreMode & EPUBEXPORT_REQUESTED ) )
+ {
+ // Preselect EPUB filter for export.
+ uno::Sequence<beans::NamedValue> aSearchRequest
+ {
+ { "Type", css::uno::Any(OUString("writer_EPUB_Document")) },
+ { "DocumentService", css::uno::Any(GetDocServiceName()) }
+ };
+
+ aFilterProps = ::comphelper::MimeConfigurationHelper::SearchForFilter( m_pOwner->GetFilterQuery(), aSearchRequest, nMust, nDont );
+ }
+ else
+ {
+ aFilterProps = GetDocServiceDefaultFilterCheckFlags( nMust, nDont );
+
+ if ( !aFilterProps.hasElements() )
+ {
+ // the default filter was not found, use just the first acceptable one
+ aFilterProps = GetDocServiceAnyFilter( nMust, nDont );
+ }
+ }
+
+ return aFilterProps;
+}
+
+
+bool ModelData_Impl::ExecuteFilterDialog_Impl( const OUString& aFilterName, bool bIsAsync )
+{
+ bool bDialogUsed = false;
+
+ try {
+ uno::Sequence < beans::PropertyValue > aProps;
+ uno::Any aAny = m_pOwner->GetFilterConfiguration()->getByName( aFilterName );
+ if ( aAny >>= aProps )
+ {
+ auto pProp = std::find_if(std::cbegin(aProps), std::cend(aProps),
+ [](const beans::PropertyValue& rProp) { return rProp.Name == "UIComponent"; });
+ if (pProp != std::cend(aProps))
+ {
+ OUString aServiceName;
+ pProp->Value >>= aServiceName;
+ if( !aServiceName.isEmpty() )
+ {
+ uno::Sequence<uno::Any> aDialogArgs(comphelper::InitAnyPropertySequence(
+ {
+ {"ParentWindow", uno::Any(SfxStoringHelper::GetModelXWindow(m_xModel))},
+ }));
+
+ uno::Reference< beans::XPropertyAccess > xFilterProperties;
+ uno::Reference< ui::dialogs::XExecutableDialog > xFilterDialog;
+ uno::Reference< ui::dialogs::XAsynchronousExecutableDialog > xAsyncFilterDialog;
+ uno::Reference< document::XExporter > xExporter;
+
+ if ( bIsAsync )
+ {
+ xAsyncFilterDialog = uno::Reference< ui::dialogs::XAsynchronousExecutableDialog >(
+ comphelper::getProcessServiceFactory()->createInstanceWithArguments( aServiceName, aDialogArgs ), uno::UNO_QUERY );
+ OSL_ENSURE(xAsyncFilterDialog.is(), "ModelData_Impl::ExecuteFilterDialog_Impl: Dialog is not async!");
+ xFilterProperties = uno::Reference< beans::XPropertyAccess >( xAsyncFilterDialog, uno::UNO_QUERY );
+ xExporter = uno::Reference< document::XExporter >( xAsyncFilterDialog, uno::UNO_QUERY );
+ }
+ else
+ {
+ xFilterDialog = uno::Reference< ui::dialogs::XExecutableDialog >(
+ comphelper::getProcessServiceFactory()->createInstanceWithArguments( aServiceName, aDialogArgs ), uno::UNO_QUERY );
+ xFilterProperties = uno::Reference< beans::XPropertyAccess >( xFilterDialog, uno::UNO_QUERY );
+ xExporter = uno::Reference< document::XExporter >( xFilterDialog, uno::UNO_QUERY );
+ }
+
+ if ( xFilterProperties.is() && ( xFilterDialog.is() || xAsyncFilterDialog.is() ) )
+ {
+ bDialogUsed = true;
+
+ if( xExporter.is() )
+ xExporter->setSourceDocument( GetModel() );
+
+ uno::Sequence< beans::PropertyValue > aPropsForDialog;
+ GetMediaDescr() >> aPropsForDialog;
+ xFilterProperties->setPropertyValues( aPropsForDialog );
+
+ if ( bIsAsync )
+ {
+ m_xFilterProperties = xFilterProperties;
+ m_xFilterDialog = xAsyncFilterDialog;
+
+ auto aDialogClosedListener = rtl::Reference(new svt::DialogClosedListener());
+ aDialogClosedListener->SetDialogClosedLink( LINK( this, ModelData_Impl, OptionsDialogClosedHdl ) );
+
+ m_xFilterDialog->startExecuteModal( aDialogClosedListener );
+ }
+ else
+ {
+ if( !xFilterDialog->execute() )
+ {
+ throw task::ErrorCodeIOException(
+ ("ModelData_Impl::ExecuteFilterDialog_Impl:"
+ " ERRCODE_IO_ABORT"),
+ uno::Reference< uno::XInterface >(),
+ sal_uInt32(ERRCODE_IO_ABORT));
+ }
+
+ const uno::Sequence< beans::PropertyValue > aPropsFromDialog =
+ xFilterProperties->getPropertyValues();
+ for ( const auto& rProp : aPropsFromDialog )
+ GetMediaDescr()[rProp.Name] = rProp.Value;
+ }
+ }
+ }
+ }
+ }
+ }
+ catch( const container::NoSuchElementException& e )
+ {
+ // the filter name is unknown
+ throw task::ErrorCodeIOException(
+ ("ModelData_Impl::ExecuteFilterDialog_Impl: NoSuchElementException"
+ " \"" + e.Message + "\": ERRCODE_IO_ABORT"),
+ uno::Reference< uno::XInterface >(), sal_uInt32(ERRCODE_IO_INVALIDPARAMETER));
+ }
+ catch( const task::ErrorCodeIOException& )
+ {
+ throw;
+ }
+ catch( const uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "ignoring");
+ }
+
+ return bDialogUsed;
+}
+
+void SfxStoringHelper::CallFinishGUIStoreModel()
+{
+ ::comphelper::SequenceAsHashMap::const_iterator aFileNameIter = m_xModelData->GetMediaDescr().find( OUString("URL") );
+ uno::Sequence< beans::PropertyValue > aFilterProps = m_xModelData->GetPreselectedFilter_Impl( m_nStoreMode );
+ const OUString aFilterFromMediaDescr = m_xModelData->GetMediaDescr().getUnpackedValueOrDefault( aFilterNameString, OUString() );
+ const OUString aOldFilterName = m_xModelData->GetDocProps().getUnpackedValueOrDefault( aFilterNameString, OUString() );
+ ::comphelper::SequenceAsHashMap aFilterPropsHM( aFilterProps );
+ OUString aFilterName = aFilterPropsHM.getUnpackedValueOrDefault( "Name", OUString() );
+
+ SfxStoringHelper::FinishGUIStoreModel(aFileNameIter, *m_xModelData, m_bRemote, m_nStoreMode, aFilterProps,
+ m_bSetStandardName, m_bPreselectPassword, m_bDialogUsed,
+ aFilterFromMediaDescr, aOldFilterName, m_aArgsSequence, aFilterName);
+
+ if (SfxViewShell::Current())
+ SfxViewShell::Current()->SetStoringHelper(nullptr);
+}
+
+IMPL_LINK( ModelData_Impl, OptionsDialogClosedHdl, css::ui::dialogs::DialogClosedEvent*, pEvt, void )
+{
+ if (pEvt->DialogResult == RET_OK && m_xFilterProperties)
+ {
+ if ( comphelper::LibreOfficeKit::isActive() && SfxViewShell::Current() )
+ SfxViewShell::Current()->libreOfficeKitViewCallback( LOK_CALLBACK_EXPORT_FILE, "PENDING"_ostr );
+
+ const uno::Sequence< beans::PropertyValue > aPropsFromDialog = m_xFilterProperties->getPropertyValues();
+ for ( const auto& rProp : aPropsFromDialog )
+ GetMediaDescr()[rProp.Name] = rProp.Value;
+
+ m_pOwner->CallFinishGUIStoreModel();
+ }
+ else if ( comphelper::LibreOfficeKit::isActive() && SfxViewShell::Current() )
+ {
+ SfxViewShell::Current()->libreOfficeKitViewCallback( LOK_CALLBACK_EXPORT_FILE, "ABORT"_ostr );
+ }
+}
+
+sal_Int8 ModelData_Impl::CheckSaveAcceptable( sal_Int8 nCurStatus )
+{
+ sal_Int8 nResult = nCurStatus;
+
+ if ( nResult != STATUS_NO_ACTION && GetStorable()->hasLocation() )
+ {
+ // the saving is acceptable
+ // in case the configuration entry is not set or set to false
+ // or in case of version creation
+ if ( officecfg::Office::Common::Save::Document::AlwaysSaveAs::get()
+ && GetMediaDescr().find( OUString("VersionComment") ) == GetMediaDescr().end() )
+ {
+ // notify the user that SaveAs is going to be done
+ std::unique_ptr<weld::MessageDialog> xMessageBox(Application::CreateMessageDialog(SfxStoringHelper::GetModelWindow(m_xModel),
+ VclMessageType::Question, VclButtonsType::OkCancel, SfxResId(STR_NEW_FILENAME_SAVE)));
+ if (xMessageBox->run() == RET_OK)
+ nResult = STATUS_SAVEAS;
+ else
+ nResult = STATUS_NO_ACTION;
+ }
+ }
+
+ return nResult;
+}
+
+
+sal_Int8 ModelData_Impl::CheckStateForSave()
+{
+ // if the document is readonly or a new one a SaveAs operation must be used
+ if ( !GetStorable()->hasLocation() || GetStorable()->isReadonly() )
+ return STATUS_SAVEAS;
+
+ // check acceptable entries for media descriptor
+ ::comphelper::SequenceAsHashMap aAcceptedArgs;
+
+ static constexpr OUString aVersionCommentString(u"VersionComment"_ustr);
+ static constexpr OUString aAuthorString(u"Author"_ustr);
+ static constexpr OUString aDontTerminateEdit(u"DontTerminateEdit"_ustr);
+ static constexpr OUString aInteractionHandlerString(u"InteractionHandler"_ustr);
+ static constexpr OUString aStatusIndicatorString(u"StatusIndicator"_ustr);
+ static constexpr OUString aFailOnWarningString(u"FailOnWarning"_ustr);
+ static constexpr OUString aNoFileSync(u"NoFileSync"_ustr);
+
+ if ( GetMediaDescr().find( aVersionCommentString ) != GetMediaDescr().end() )
+ aAcceptedArgs[ aVersionCommentString ] = GetMediaDescr()[ aVersionCommentString ];
+ if ( GetMediaDescr().find( aAuthorString ) != GetMediaDescr().end() )
+ aAcceptedArgs[ aAuthorString ] = GetMediaDescr()[ aAuthorString ];
+ if ( GetMediaDescr().find( aDontTerminateEdit ) != GetMediaDescr().end() )
+ aAcceptedArgs[ aDontTerminateEdit ] = GetMediaDescr()[ aDontTerminateEdit ];
+ if ( GetMediaDescr().find( aInteractionHandlerString ) != GetMediaDescr().end() )
+ aAcceptedArgs[ aInteractionHandlerString ] = GetMediaDescr()[ aInteractionHandlerString ];
+ if ( GetMediaDescr().find( aStatusIndicatorString ) != GetMediaDescr().end() )
+ aAcceptedArgs[ aStatusIndicatorString ] = GetMediaDescr()[ aStatusIndicatorString ];
+ if ( GetMediaDescr().find( aFailOnWarningString ) != GetMediaDescr().end() )
+ aAcceptedArgs[ aFailOnWarningString ] = GetMediaDescr()[ aFailOnWarningString ];
+ if (GetMediaDescr().find(aNoFileSync) != GetMediaDescr().end())
+ aAcceptedArgs[aNoFileSync] = GetMediaDescr()[aNoFileSync];
+
+ // remove unacceptable entry if there is any
+ DBG_ASSERT( GetMediaDescr().size() == aAcceptedArgs.size(),
+ "Unacceptable parameters are provided in Save request!\n" );
+ if ( GetMediaDescr().size() != aAcceptedArgs.size() )
+ GetMediaDescr() = aAcceptedArgs;
+
+ // check that the old filter is acceptable
+ return CheckFilter( GetDocProps().getUnpackedValueOrDefault(aFilterNameString, OUString()) );
+}
+
+sal_Int8 ModelData_Impl::CheckFilter( const OUString& aFilterName )
+{
+ ::comphelper::SequenceAsHashMap aFiltPropsHM;
+ SfxFilterFlags nFiltFlags = SfxFilterFlags::NONE;
+ if ( !aFilterName.isEmpty() )
+ {
+ // get properties of filter
+ uno::Sequence< beans::PropertyValue > aFilterProps;
+ m_pOwner->GetFilterConfiguration()->getByName( aFilterName ) >>= aFilterProps;
+
+ aFiltPropsHM = ::comphelper::SequenceAsHashMap( aFilterProps );
+ nFiltFlags = static_cast<SfxFilterFlags>(aFiltPropsHM.getUnpackedValueOrDefault("Flags", sal_Int32(0) ));
+ }
+
+ // only a temporary solution until default filter retrieving feature is implemented
+ // then GetDocServiceDefaultFilter() must be used
+ ::comphelper::SequenceAsHashMap aDefFiltPropsHM = GetDocServiceDefaultFilterCheckFlags( SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT, SfxFilterFlags::NONE );
+ SfxFilterFlags nDefFiltFlags = static_cast<SfxFilterFlags>(aDefFiltPropsHM.getUnpackedValueOrDefault("Flags", sal_Int32(0) ));
+
+ bool bAsk = false;
+
+ // if the old filter is not acceptable
+ // and there is no default filter or it is not acceptable for requested parameters then proceed with saveAs
+ if ( ( aFiltPropsHM.empty() || !( nFiltFlags & SfxFilterFlags::EXPORT ) )
+ && ( aDefFiltPropsHM.empty() || !( nDefFiltFlags & SfxFilterFlags::EXPORT ) || nDefFiltFlags & SfxFilterFlags::INTERNAL ) )
+ return STATUS_SAVEAS;
+
+ // so at this point there is either an acceptable old filter or default one
+ if ( aFiltPropsHM.empty() || !( nFiltFlags & SfxFilterFlags::EXPORT ) )
+ {
+ // so the default filter must be acceptable
+ return STATUS_SAVEAS_STANDARDNAME;
+ }
+ else if ( ( !( nFiltFlags & SfxFilterFlags::OWN ) || ( nFiltFlags & SfxFilterFlags::ALIEN ) )
+ && !aDefFiltPropsHM.empty()
+ && ( nDefFiltFlags & SfxFilterFlags::EXPORT ) && !( nDefFiltFlags & SfxFilterFlags::INTERNAL ))
+ {
+ bAsk = true;
+ }
+
+ // check if EncryptionData supports this output format
+ {
+ OUString aSupportedFilters;
+ const ::comphelper::SequenceAsHashMap& rDocumentProperties = GetDocProps();
+ const css::uno::Sequence<css::beans::NamedValue> aEncryptionData = rDocumentProperties.getUnpackedValueOrDefault("EncryptionData", css::uno::Sequence<css::beans::NamedValue>());
+ if (aEncryptionData != css::uno::Sequence<css::beans::NamedValue>())
+ {
+ for (const css::beans::NamedValue& aNamedValue : aEncryptionData)
+ {
+ if (aNamedValue.Name == "SupportedFilters")
+ {
+ aNamedValue.Value >>= aSupportedFilters;
+ }
+ }
+ }
+
+ // if 'SupportedFilters' is empty assume that all filters are supported.
+ if (!aSupportedFilters.isEmpty())
+ {
+ const OUString aSelectedFilter = aFiltPropsHM.getUnpackedValueOrDefault("UIName", OUString());
+
+ aSupportedFilters = ";" + aSupportedFilters + ";";
+ const OUString aSearchToken = ";" + aSelectedFilter + ";";
+ bAsk = (aSupportedFilters.indexOf(aSearchToken) < 0);
+ }
+ }
+
+ if (bAsk)
+ {
+ // the default filter is acceptable and the old filter is alien one
+ // so ask to make a saveAs operation
+ const OUString aUIName = aFiltPropsHM.getUnpackedValueOrDefault("UIName", OUString() );
+ const OUString aDefUIName = aDefFiltPropsHM.getUnpackedValueOrDefault("UIName", OUString() );
+ const OUString aPreusedFilterName = GetDocProps().getUnpackedValueOrDefault("PreusedFilterName", OUString() );
+ const OUString aDefType = aDefFiltPropsHM.getUnpackedValueOrDefault( "Type", OUString() );
+ const OUString aDefExtension = GetRecommendedExtension( aDefType );
+
+ if ( aPreusedFilterName != aFilterName && aUIName != aDefUIName )
+ {
+ if ( !SfxStoringHelper::WarnUnacceptableFormat( GetModel(), aUIName, aDefExtension,
+ static_cast<bool>( nDefFiltFlags & SfxFilterFlags::ALIEN ) ) )
+ return STATUS_SAVEAS_STANDARDNAME;
+ }
+ }
+
+ return STATUS_SAVE;
+}
+
+
+bool ModelData_Impl::CheckFilterOptionsDialogExistence()
+{
+ uno::Sequence< beans::NamedValue > aSearchRequest { { "DocumentService", css::uno::Any(GetDocServiceName()) } };
+
+ uno::Reference< container::XEnumeration > xFilterEnum =
+ m_pOwner->GetFilterQuery()->createSubSetEnumerationByProperties( aSearchRequest );
+
+ while ( xFilterEnum->hasMoreElements() )
+ {
+ uno::Sequence< beans::PropertyValue > aProps;
+ if ( xFilterEnum->nextElement() >>= aProps )
+ {
+ ::comphelper::SequenceAsHashMap aPropsHM( aProps );
+ if ( !aPropsHM.getUnpackedValueOrDefault("UIComponent", OUString()).isEmpty() )
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+bool ModelData_Impl::OutputFileDialog( sal_Int16 nStoreMode,
+ const ::comphelper::SequenceAsHashMap& aPreselectedFilterPropsHM,
+ bool bSetStandardName,
+ OUString& aSuggestedName,
+ bool bPreselectPassword,
+ OUString& aSuggestedDir,
+ sal_Int16 nDialog,
+ const OUString& rStandardDir,
+ const css::uno::Sequence< OUString >& rDenyList)
+{
+ if ( nStoreMode == SAVEASREMOTE_REQUESTED )
+ nStoreMode = SAVEAS_REQUESTED;
+
+ bool bUseFilterOptions = false;
+
+ ::comphelper::SequenceAsHashMap::const_iterator aOverwriteIter =
+ GetMediaDescr().find( OUString("Overwrite") );
+
+ // the file name must be specified if overwrite option is set
+ if ( aOverwriteIter != GetMediaDescr().end() )
+ throw task::ErrorCodeIOException(
+ "ModelData_Impl::OutputFileDialog: ERRCODE_IO_INVALIDPARAMETER",
+ uno::Reference< uno::XInterface >(),
+ sal_uInt32(ERRCODE_IO_INVALIDPARAMETER));
+
+ // no target file name is specified
+ // we need to show the file dialog
+
+ // check if we have a filter which allows for filter options, so we need a corresponding checkbox in the dialog
+ bool bAllowOptions = false;
+
+ // in case of Export, filter options dialog is used if available
+ if( !( nStoreMode & EXPORT_REQUESTED ) || ( nStoreMode & WIDEEXPORT_REQUESTED ) )
+ bAllowOptions = CheckFilterOptionsDialogExistence();
+
+ // get the filename by dialog ...
+ // create the file dialog
+ sal_Int16 aDialogMode = bAllowOptions
+ ? css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION_PASSWORD_FILTEROPTIONS
+ : css::ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION_PASSWORD;
+ FileDialogFlags aDialogFlags = FileDialogFlags::NONE;
+
+ if( ( nStoreMode & EXPORT_REQUESTED ) && !( nStoreMode & WIDEEXPORT_REQUESTED ) )
+ {
+ if ( (nStoreMode & PDFEXPORT_REQUESTED) || (nStoreMode & EPUBEXPORT_REQUESTED) )
+ aDialogMode = css::ui::dialogs::TemplateDescription::
+ FILESAVE_AUTOEXTENSION;
+ else
+ aDialogMode = css::ui::dialogs::TemplateDescription::
+ FILESAVE_AUTOEXTENSION_SELECTION;
+ aDialogFlags = FileDialogFlags::Export;
+ }
+
+ if( ( nStoreMode & EXPORT_REQUESTED ) && ( nStoreMode & SAVEACOPY_REQUESTED ) && ( nStoreMode & WIDEEXPORT_REQUESTED ) )
+ {
+ aDialogFlags = FileDialogFlags::SaveACopy;
+ }
+
+ std::unique_ptr<sfx2::FileDialogHelper> pFileDlg;
+
+ const OUString aDocServiceName {GetDocServiceName()};
+ DBG_ASSERT( !aDocServiceName.isEmpty(), "No document service for this module set!" );
+
+ SfxFilterFlags nMust = getMustFlags( nStoreMode );
+ SfxFilterFlags nDont = getDontFlags( nStoreMode );
+ weld::Window* pFrameWin = SfxStoringHelper::GetModelWindow(m_xModel);
+ if ( ( nStoreMode & EXPORT_REQUESTED ) && !( nStoreMode & WIDEEXPORT_REQUESTED ) )
+ {
+ if ( ( nStoreMode & PDFEXPORT_REQUESTED ) && !aPreselectedFilterPropsHM.empty() )
+ {
+ // this is a PDF export
+ // the filter options has been shown already
+ const OUString aFilterUIName = aPreselectedFilterPropsHM.getUnpackedValueOrDefault( "UIName", OUString() );
+ pFileDlg.reset(new sfx2::FileDialogHelper( aDialogMode, aDialogFlags, aFilterUIName, u"pdf", rStandardDir, rDenyList, pFrameWin ));
+ pFileDlg->SetCurrentFilter( aFilterUIName );
+ }
+ else if ((nStoreMode & EPUBEXPORT_REQUESTED) && !aPreselectedFilterPropsHM.empty())
+ {
+ // This is an EPUB export, the filter options has been shown already.
+ const OUString aFilterUIName = aPreselectedFilterPropsHM.getUnpackedValueOrDefault( "UIName", OUString() );
+ pFileDlg.reset(new sfx2::FileDialogHelper(aDialogMode, aDialogFlags, aFilterUIName, u"epub", rStandardDir, rDenyList, pFrameWin));
+ pFileDlg->SetCurrentFilter(aFilterUIName);
+ }
+ else
+ {
+ // This is the normal dialog
+ pFileDlg.reset(new sfx2::FileDialogHelper( aDialogMode, aDialogFlags, aDocServiceName, nDialog, nMust, nDont, rStandardDir, rDenyList, pFrameWin ));
+ }
+
+ sfx2::FileDialogHelper::Context eCtxt = sfx2::FileDialogHelper::UnknownContext;
+ if ( aDocServiceName == "com.sun.star.drawing.DrawingDocument" )
+ eCtxt = sfx2::FileDialogHelper::DrawExport;
+ else if ( aDocServiceName == "com.sun.star.presentation.PresentationDocument" )
+ eCtxt = sfx2::FileDialogHelper::ImpressExport;
+ else if ( aDocServiceName == "com.sun.star.text.TextDocument" )
+ eCtxt = sfx2::FileDialogHelper::WriterExport;
+ else if ( aDocServiceName == "com.sun.star.sheet.SpreadsheetDocument" )
+ eCtxt = sfx2::FileDialogHelper::CalcExport;
+
+ if ( eCtxt != sfx2::FileDialogHelper::UnknownContext )
+ pFileDlg->SetContext( eCtxt );
+
+ pFileDlg->CreateMatcher( aDocServiceName );
+
+ uno::Reference< ui::dialogs::XFilePicker3 > xFilePicker = pFileDlg->GetFilePicker();
+ uno::Reference< ui::dialogs::XFilePickerControlAccess > xControlAccess( xFilePicker, uno::UNO_QUERY );
+
+ if ( xControlAccess.is() )
+ {
+ xControlAccess->setLabel( ui::dialogs::CommonFilePickerElementIds::PUSHBUTTON_OK, SfxResId(STR_EXPORTBUTTON) );
+ xControlAccess->setLabel( ui::dialogs::CommonFilePickerElementIds::LISTBOX_FILTER_LABEL, SfxResId(STR_LABEL_FILEFORMAT) );
+ }
+ }
+ else
+ {
+ // This is the normal save as dialog
+ pFileDlg.reset(new sfx2::FileDialogHelper( aDialogMode, aDialogFlags, aDocServiceName, nDialog,
+ nMust, nDont, rStandardDir, rDenyList, pFrameWin ));
+ pFileDlg->CreateMatcher( aDocServiceName );
+
+ sfx2::FileDialogHelper::Context eCtxt = sfx2::FileDialogHelper::UnknownContext;
+ if ( aDocServiceName == "com.sun.star.drawing.DrawingDocument" )
+ eCtxt = sfx2::FileDialogHelper::DrawSaveAs;
+ else if ( aDocServiceName == "com.sun.star.presentation.PresentationDocument" )
+ eCtxt = sfx2::FileDialogHelper::ImpressSaveAs;
+ else if ( aDocServiceName == "com.sun.star.text.TextDocument" )
+ eCtxt = sfx2::FileDialogHelper::WriterSaveAs;
+ else if ( aDocServiceName == "com.sun.star.sheet.SpreadsheetDocument" )
+ eCtxt = sfx2::FileDialogHelper::CalcSaveAs;
+
+ if ( eCtxt != sfx2::FileDialogHelper::UnknownContext )
+ pFileDlg->SetContext( eCtxt );
+ }
+
+ OUString aAdjustToType;
+
+ const OUString sFilterNameString(aFilterNameString);
+
+ if ( ( nStoreMode & EXPORT_REQUESTED ) && !( nStoreMode & WIDEEXPORT_REQUESTED ) )
+ {
+ // it is export, set the preselected filter
+ pFileDlg->SetCurrentFilter( aPreselectedFilterPropsHM.getUnpackedValueOrDefault( "UIName", OUString() ) );
+ aAdjustToType = aPreselectedFilterPropsHM.getUnpackedValueOrDefault( "Type", OUString() );
+ }
+ // it is no export, bSetStandardName == true means that user agreed to store document in the default (default default ;-)) format
+ else if ( bSetStandardName || GetStorable()->hasLocation() )
+ {
+ uno::Sequence< beans::PropertyValue > aOldFilterProps;
+ const OUString aOldFilterName = GetDocProps().getUnpackedValueOrDefault( sFilterNameString, OUString() );
+
+ if ( !aOldFilterName.isEmpty() )
+ m_pOwner->GetFilterConfiguration()->getByName( aOldFilterName ) >>= aOldFilterProps;
+
+ ::comphelper::SequenceAsHashMap aOldFiltPropsHM( aOldFilterProps );
+ SfxFilterFlags nOldFiltFlags = static_cast<SfxFilterFlags>(aOldFiltPropsHM.getUnpackedValueOrDefault("Flags", sal_Int32(0) ));
+
+ if ( bSetStandardName || ( nOldFiltFlags & nMust ) != nMust || bool(nOldFiltFlags & nDont) )
+ {
+ // the suggested type will be changed, the extension should be adjusted
+ aAdjustToType = aPreselectedFilterPropsHM.getUnpackedValueOrDefault( "Type", OUString() );
+ pFileDlg->SetCurrentFilter( aPreselectedFilterPropsHM.getUnpackedValueOrDefault( "UIName", OUString() ) );
+ }
+ else
+ {
+ pFileDlg->SetCurrentFilter( aOldFiltPropsHM.getUnpackedValueOrDefault(
+ "UIName",
+ OUString() ) );
+ }
+ }
+
+ const OUString aRecommendedDir {GetRecommendedDir( aSuggestedDir )};
+ if ( !aRecommendedDir.isEmpty() )
+ pFileDlg->SetDisplayFolder( aRecommendedDir );
+ const OUString aRecommendedName {GetRecommendedName( aSuggestedName, aAdjustToType )};
+ if ( !aRecommendedName.isEmpty() )
+ pFileDlg->SetFileName( aRecommendedName );
+
+ uno::Reference < view::XSelectionSupplier > xSel( GetModel()->getCurrentController(), uno::UNO_QUERY );
+ if ( xSel.is() && xSel->getSelection().hasValue() )
+ GetMediaDescr()[OUString("SelectionOnly")] <<= true;
+
+ // This is a temporary hardcoded solution must be removed when
+ // dialogs do not need parameters in SidSet representation any more
+ sal_uInt16 nSlotID = getSlotIDFromMode( nStoreMode );
+ if ( !nSlotID )
+ throw lang::IllegalArgumentException(); // TODO:
+
+ // generate SidSet from MediaDescriptor and provide it into FileDialog
+ // than merge changed SidSet back
+ std::optional<SfxAllItemSet> pDialogParams( SfxGetpApp()->GetPool() );
+ TransformParameters( nSlotID,
+ GetMediaDescr().getAsConstPropertyValueList(),
+ *pDialogParams );
+
+ if ( bPreselectPassword && !pDialogParams->HasItem( SID_ENCRYPTIONDATA ) )
+ {
+ // the file dialog preselects the password checkbox if the provided mediadescriptor has encryption data entry
+ // after dialog execution the password interaction flag will be either removed or not
+ pDialogParams->Put( SfxBoolItem( SID_PASSWORDINTERACTION, true ) );
+ }
+
+ // aFilterName is a pure output parameter, pDialogParams is an in/out parameter
+ OUString aFilterName;
+ // in LOK case we don't show File Picker so it will fail, but execute to do other preparations
+ if ( pFileDlg->Execute( pDialogParams, aFilterName ) != ERRCODE_NONE
+ && !comphelper::LibreOfficeKit::isActive() )
+ {
+ throw task::ErrorCodeIOException(
+ "ModelData_Impl::OutputFileDialog: ERRCODE_IO_ABORT",
+ uno::Reference< uno::XInterface >(), sal_uInt32(ERRCODE_IO_ABORT));
+ }
+ else if (comphelper::LibreOfficeKit::isActive())
+ {
+ aFilterName = aPreselectedFilterPropsHM.getUnpackedValueOrDefault( "Name", OUString() );
+ }
+
+ // the following two arguments can not be converted in MediaDescriptor,
+ // so they should be removed from the ItemSet after retrieving
+ const SfxBoolItem* pRecommendReadOnly = SfxItemSet::GetItem<SfxBoolItem>(&*pDialogParams, SID_RECOMMENDREADONLY, false);
+ m_bRecommendReadOnly = ( pRecommendReadOnly && pRecommendReadOnly->GetValue() );
+ pDialogParams->ClearItem( SID_RECOMMENDREADONLY );
+
+ uno::Sequence< beans::PropertyValue > aPropsFromDialog;
+ TransformItems( nSlotID, *pDialogParams, aPropsFromDialog );
+ GetMediaDescr() << aPropsFromDialog;
+
+ // get the path from the dialog
+ INetURLObject aURL( pFileDlg->GetPath() );
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // keep name with extension
+ aSuggestedName = aRecommendedName;
+ OUString aExtension;
+ if (size_t nPos = aSuggestedName.lastIndexOf('.') + 1)
+ aExtension = aSuggestedName.copy(nPos, aSuggestedName.getLength() - nPos);
+ aURL.SetExtension(aExtension);
+ }
+ else
+ {
+ // the path should be provided outside since it might be used for further calls to the dialog
+ aSuggestedName = aURL.GetLastName(INetURLObject::DecodeMechanism::WithCharset);
+ }
+ aSuggestedDir = pFileDlg->GetDisplayDirectory();
+
+ // old filter options should be cleared in case different filter is used
+
+ const OUString aFilterFromMediaDescr = GetMediaDescr().getUnpackedValueOrDefault( sFilterNameString, OUString() );
+ const OUString aOldFilterName = GetDocProps().getUnpackedValueOrDefault( sFilterNameString, OUString() );
+
+ if ( aFilterName == aFilterFromMediaDescr )
+ {
+ // preserve current settings if any
+ // if there no current settings and the name is the same
+ // as old filter name use old filter settings
+
+ if ( aFilterFromMediaDescr == aOldFilterName )
+ {
+ ::comphelper::SequenceAsHashMap::const_iterator aIter =
+ GetDocProps().find( aFilterOptionsString );
+ if ( aIter != GetDocProps().end()
+ && GetMediaDescr().find( aFilterOptionsString ) == GetMediaDescr().end() )
+ GetMediaDescr()[aIter->first] = aIter->second;
+
+ aIter = GetDocProps().find( aFilterDataString );
+ if ( aIter != GetDocProps().end()
+ && GetMediaDescr().find( aFilterDataString ) == GetMediaDescr().end() )
+ GetMediaDescr()[aIter->first] = aIter->second;
+ }
+ }
+ else
+ {
+ GetMediaDescr().erase( aFilterDataString );
+ GetMediaDescr().erase( aFilterOptionsString );
+
+ if ( aFilterName == aOldFilterName )
+ {
+ // merge filter option of the document filter
+
+ ::comphelper::SequenceAsHashMap::const_iterator aIter =
+ GetDocProps().find( aFilterOptionsString );
+ if ( aIter != GetDocProps().end() )
+ GetMediaDescr()[aIter->first] = aIter->second;
+
+ aIter = GetDocProps().find( aFilterDataString );
+ if ( aIter != GetDocProps().end() )
+ GetMediaDescr()[aIter->first] = aIter->second;
+ }
+ }
+
+ uno::Reference< ui::dialogs::XFilePickerControlAccess > xExtFileDlg( pFileDlg->GetFilePicker(), uno::UNO_QUERY );
+ if ( xExtFileDlg.is() )
+ {
+ if ( SfxStoringHelper::CheckFilterOptionsAppearance( m_pOwner->GetFilterConfiguration(), aFilterName ) )
+ bUseFilterOptions = true;
+
+ if ( ( !( nStoreMode & EXPORT_REQUESTED ) || ( nStoreMode & WIDEEXPORT_REQUESTED ) ) && bUseFilterOptions )
+ {
+ try
+ {
+ // for exporters: always show dialog if format uses options
+ // for save: show dialog if format uses options and no options given or if forced by user
+ uno::Any aVal =
+ xExtFileDlg->getValue( ui::dialogs::ExtendedFilePickerElementIds::CHECKBOX_FILTEROPTIONS, 0 );
+
+ aVal >>= bUseFilterOptions;
+ if ( !bUseFilterOptions )
+ bUseFilterOptions =
+ ( GetMediaDescr().find( aFilterDataString ) == GetMediaDescr().end()
+ && GetMediaDescr().find( aFilterOptionsString ) == GetMediaDescr().end() );
+ }
+ catch( const lang::IllegalArgumentException& )
+ {}
+ }
+ }
+
+ // merge in results of the dialog execution
+ GetMediaDescr()[OUString("URL")] <<= aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ GetMediaDescr()[sFilterNameString] <<= aFilterName;
+
+ return bUseFilterOptions;
+}
+
+
+bool ModelData_Impl::ShowDocumentInfoDialog()
+{
+ bool bDialogUsed = false;
+
+ try {
+ uno::Reference< frame::XController > xController = GetModel()->getCurrentController();
+ if ( xController.is() )
+ {
+ uno::Reference< frame::XDispatchProvider > xFrameDispatch( xController->getFrame(), uno::UNO_QUERY );
+ if ( xFrameDispatch.is() )
+ {
+ util::URL aURL;
+ aURL.Complete = ".uno:SetDocumentProperties";
+
+ uno::Reference < util::XURLTransformer > xTransformer( util::URLTransformer::create( comphelper::getProcessComponentContext() ) );
+ if ( xTransformer->parseStrict( aURL ) )
+ {
+ uno::Reference< frame::XDispatch > xDispatch = xFrameDispatch->queryDispatch(
+ aURL,
+ "_self",
+ 0 );
+ if ( xDispatch.is() )
+ {
+ // tdf#119206 use (abuse?) a SynchronMode of true,
+ // which will become SfxRequest::IsSynchronCall of true
+ // in SfxObjectShell::ExecFile_Impl to request that we
+ // do not want the properties dialog to be run async
+ uno::Sequence< beans::PropertyValue > aProperties{
+ comphelper::makePropertyValue("SynchronMode", true)
+ };
+ xDispatch->dispatch(aURL, aProperties);
+ bDialogUsed = true;
+ }
+ }
+ }
+ }
+ }
+ catch ( const uno::Exception& )
+ {
+ }
+
+ return bDialogUsed;
+}
+
+
+OUString ModelData_Impl::GetRecommendedExtension( const OUString& aTypeName )
+{
+ if ( aTypeName.isEmpty() )
+ return OUString();
+
+ uno::Reference< container::XNameAccess > xTypeDetection(
+ comphelper::getProcessServiceFactory()->createInstance("com.sun.star.document.TypeDetection"),
+ uno::UNO_QUERY );
+ if ( xTypeDetection.is() )
+ {
+ uno::Sequence< beans::PropertyValue > aTypeNameProps;
+ if ( ( xTypeDetection->getByName( aTypeName ) >>= aTypeNameProps ) && aTypeNameProps.hasElements() )
+ {
+ ::comphelper::SequenceAsHashMap aTypeNamePropsHM( aTypeNameProps );
+ uno::Sequence< OUString > aExtensions = aTypeNamePropsHM.getUnpackedValueOrDefault(
+ "Extensions",
+ ::uno::Sequence< OUString >() );
+ if ( aExtensions.hasElements() )
+ return aExtensions[0];
+ }
+ }
+
+ return OUString();
+}
+
+
+OUString ModelData_Impl::GetRecommendedDir( const OUString& aSuggestedDir )
+{
+ if ( ( !aSuggestedDir.isEmpty() || GetStorable()->hasLocation() )
+ && !GetMediaDescr().getUnpackedValueOrDefault("RepairPackage", false ) )
+ {
+ INetURLObject aLocation;
+ if ( !aSuggestedDir.isEmpty() )
+ aLocation = INetURLObject( aSuggestedDir );
+ else
+ {
+ const OUString aOldURL = GetStorable()->getLocation();
+ if ( !aOldURL.isEmpty() )
+ {
+ INetURLObject aTmp( aOldURL );
+ if ( aTmp.removeSegment() )
+ aLocation = aTmp;
+ }
+
+ if ( aLocation.HasError() )
+ aLocation = INetURLObject();
+ }
+
+ OUString sLocationURL( aLocation.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+ bool bIsInTempPath( false );
+ OUString sSysTempPath;
+ if( osl::FileBase::getTempDirURL( sSysTempPath ) == osl::FileBase::E_None )
+ bIsInTempPath = !sSysTempPath.isEmpty() && sLocationURL.startsWith( sSysTempPath );
+#ifdef _WIN32
+ if( !bIsInTempPath )
+ {
+ wchar_t sPath[MAX_PATH+1];
+ HRESULT hRes = SHGetFolderPathW( nullptr, CSIDL_INTERNET_CACHE, nullptr, SHGFP_TYPE_CURRENT, sPath );
+ if( SUCCEEDED(hRes) )
+ {
+ OUString sTempINetFiles;
+ if( osl::FileBase::getFileURLFromSystemPath(OUString(o3tl::toU(sPath)), sTempINetFiles) == osl::FileBase::E_None )
+ bIsInTempPath = !sTempINetFiles.isEmpty() && sLocationURL.startsWith( sTempINetFiles );
+ }
+ }
+#endif
+ // Suggest somewhere other than the system's temp directory
+ if( bIsInTempPath )
+ aLocation = INetURLObject();
+
+ aLocation.setFinalSlash();
+ if ( !aLocation.HasError() )
+ return aLocation.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ return OUString();
+ }
+
+ return OUString();
+}
+
+
+OUString ModelData_Impl::GetRecommendedName( const OUString& aSuggestedName, const OUString& aTypeName )
+{
+ // the last used name might be provided by aSuggestedName from the old selection, or from the MediaDescriptor
+ if ( !aSuggestedName.isEmpty() )
+ return aSuggestedName;
+
+ OUString aRecommendedName{ INetURLObject(GetStorable()->getLocation())
+ .GetLastName(INetURLObject::DecodeMechanism::WithCharset) };
+ if ( aRecommendedName.isEmpty() )
+ {
+ try {
+ uno::Reference< frame::XTitle > xTitle( GetModel(), uno::UNO_QUERY_THROW );
+ aRecommendedName = xTitle->getTitle();
+ } catch( const uno::Exception& ) {}
+ }
+
+ if ( !aRecommendedName.isEmpty() && !aTypeName.isEmpty() )
+ {
+ // adjust the extension to the type
+ uno::Reference< container::XNameAccess > xTypeDetection(
+ comphelper::getProcessServiceFactory()->createInstance("com.sun.star.document.TypeDetection"),
+ uno::UNO_QUERY );
+ if ( xTypeDetection.is() )
+ {
+ INetURLObject aObj( rtl::Concat2View("c:/" + aRecommendedName), INetProtocol::File,
+ INetURLObject::EncodeMechanism::All, RTL_TEXTENCODING_UTF8, FSysStyle::Dos );
+
+ const OUString aExtension = GetRecommendedExtension( aTypeName );
+ if ( !aExtension.isEmpty() )
+ aObj.SetExtension( aExtension );
+
+ aRecommendedName = aObj.GetLastName(INetURLObject::DecodeMechanism::WithCharset);
+ }
+ }
+
+ return aRecommendedName;
+}
+
+SfxStoringHelper::SfxStoringHelper()
+ : m_bRemote(false)
+ , m_bPreselectPassword(false)
+ , m_bDialogUsed(false)
+ , m_bSetStandardName(false)
+ , m_nStoreMode(0)
+{
+}
+
+uno::Reference< container::XNameAccess > const & SfxStoringHelper::GetFilterConfiguration()
+{
+ if ( !m_xFilterCFG.is() )
+ {
+ m_xFilterCFG.set( comphelper::getProcessServiceFactory()->createInstance("com.sun.star.document.FilterFactory"),
+ uno::UNO_QUERY_THROW );
+ }
+
+ return m_xFilterCFG;
+}
+
+uno::Reference< container::XContainerQuery > const & SfxStoringHelper::GetFilterQuery()
+{
+ if ( !m_xFilterQuery.is() )
+ {
+ m_xFilterQuery.set( GetFilterConfiguration(), uno::UNO_QUERY_THROW );
+ }
+
+ return m_xFilterQuery;
+}
+
+uno::Reference< css::frame::XModuleManager2 > const & SfxStoringHelper::GetModuleManager()
+{
+ if ( !m_xModuleManager.is() )
+ {
+ m_xModuleManager = frame::ModuleManager::create(
+ comphelper::getProcessComponentContext() );
+ }
+
+ return m_xModuleManager;
+}
+
+bool SfxStoringHelper::GUIStoreModel( const uno::Reference< frame::XModel >& xModel,
+ std::u16string_view aSlotName,
+ uno::Sequence< beans::PropertyValue >& aArgsSequence,
+ bool bPreselectPassword,
+ SignatureState nDocumentSignatureState,
+ bool bIsAsync)
+{
+ m_xModelData = std::make_shared<ModelData_Impl>( *this, xModel, aArgsSequence );
+ m_aArgsSequence = aArgsSequence;
+ ModelData_Impl& aModelData = *m_xModelData;
+
+ m_bDialogUsed = false;
+
+ m_bSetStandardName = false; // can be set only for SaveAs
+ m_bPreselectPassword = bPreselectPassword;
+
+ // parse the slot name
+ m_bRemote = false;
+ m_nStoreMode = getStoreModeFromSlotName( aSlotName );
+
+ if ( m_nStoreMode == SAVEASREMOTE_REQUESTED )
+ {
+ m_nStoreMode = SAVEAS_REQUESTED;
+ m_bRemote = true;
+ }
+
+ sal_Int8 nStatusSave = STATUS_NO_ACTION;
+
+ ::comphelper::SequenceAsHashMap::const_iterator aSaveACopyIter =
+ aModelData.GetMediaDescr().find( OUString("SaveACopy") );
+ if ( aSaveACopyIter != aModelData.GetMediaDescr().end() )
+ {
+ bool bSaveACopy = false;
+ aSaveACopyIter->second >>= bSaveACopy;
+ if ( bSaveACopy )
+ m_nStoreMode = EXPORT_REQUESTED | SAVEACOPY_REQUESTED | WIDEEXPORT_REQUESTED;
+ }
+ // handle the special cases
+ if ( m_nStoreMode & SAVEAS_REQUESTED )
+ {
+ ::comphelper::SequenceAsHashMap::const_iterator aSaveToIter =
+ aModelData.GetMediaDescr().find( OUString("SaveTo") );
+ if ( aSaveToIter != aModelData.GetMediaDescr().end() )
+ {
+ bool bWideExport = false;
+ aSaveToIter->second >>= bWideExport;
+ if ( bWideExport )
+ m_nStoreMode = EXPORT_REQUESTED | WIDEEXPORT_REQUESTED;
+ }
+
+ // if saving is not acceptable the warning must be shown even in case of SaveAs operation
+ if ( ( m_nStoreMode & SAVEAS_REQUESTED ) && aModelData.CheckSaveAcceptable( STATUS_SAVEAS ) == STATUS_NO_ACTION )
+ throw task::ErrorCodeIOException(
+ "SfxStoringHelper::GUIStoreModel: ERRCODE_IO_ABORT",
+ uno::Reference< uno::XInterface >(), sal_uInt32(ERRCODE_IO_ABORT));
+ }
+ else if ( m_nStoreMode & SAVE_REQUESTED )
+ {
+ // if saving is not acceptable by the configuration the warning must be shown
+ nStatusSave = aModelData.CheckSaveAcceptable( STATUS_SAVE );
+
+ if ( nStatusSave == STATUS_NO_ACTION )
+ throw task::ErrorCodeIOException(
+ "SfxStoringHelper::GUIStoreModel: ERRCODE_IO_ABORT",
+ uno::Reference< uno::XInterface >(), sal_uInt32(ERRCODE_IO_ABORT));
+ else if ( nStatusSave == STATUS_SAVE )
+ {
+ // check whether it is possible to use save operation
+ nStatusSave = aModelData.CheckStateForSave();
+ }
+
+ if ( nStatusSave == STATUS_NO_ACTION )
+ {
+ throw task::ErrorCodeIOException(
+ "SfxStoringHelper::GUIStoreModel: ERRCODE_IO_ABORT",
+ uno::Reference< uno::XInterface >(), sal_uInt32(ERRCODE_IO_ABORT));
+ }
+ else if ( nStatusSave != STATUS_SAVE )
+ {
+ // this should be a usual SaveAs operation
+ m_nStoreMode = SAVEAS_REQUESTED;
+ if ( nStatusSave == STATUS_SAVEAS_STANDARDNAME )
+ m_bSetStandardName = true;
+ }
+ }
+
+ if (!comphelper::LibreOfficeKit::isActive() && !( m_nStoreMode & EXPORT_REQUESTED ) && SfxViewShell::Current() )
+ {
+ SfxObjectShell* pDocShell = SfxViewShell::Current()->GetObjectShell();
+
+ // if it is no export, warn user that the signature will be removed
+ if ( !pDocShell->IsRememberingSignature()
+ && (SignatureState::OK == nDocumentSignatureState
+ || SignatureState::INVALID == nDocumentSignatureState
+ || SignatureState::NOTVALIDATED == nDocumentSignatureState
+ || SignatureState::PARTIAL_OK == nDocumentSignatureState) )
+ {
+ std::unique_ptr<weld::MessageDialog> xMessageBox(Application::CreateMessageDialog(SfxStoringHelper::GetModelWindow(xModel),
+ VclMessageType::Question, VclButtonsType::YesNo, SfxResId(RID_SVXSTR_XMLSEC_QUERY_LOSINGSIGNATURE)));
+ if (xMessageBox->run() != RET_YES)
+ {
+ // the user has decided not to store the document
+ throw task::ErrorCodeIOException(
+ "SfxStoringHelper::GUIStoreModel: ERRCODE_IO_ABORT (Preserve Signature)",
+ uno::Reference< uno::XInterface >(), sal_uInt32(ERRCODE_IO_ABORT));
+ }
+ }
+ }
+
+ if ( m_nStoreMode & SAVE_REQUESTED && nStatusSave == STATUS_SAVE )
+ {
+ // Document properties can contain streams that should be freed before storing
+ aModelData.FreeDocumentProps();
+
+ if ( aModelData.GetStorable2().is() )
+ {
+ try
+ {
+ aModelData.GetStorable2()->storeSelf( aModelData.GetMediaDescr().getAsConstPropertyValueList() );
+ }
+ catch (const lang::IllegalArgumentException&)
+ {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "Ignoring parameters! ModelData considers this illegal");
+ aModelData.GetStorable()->store();
+ }
+ }
+ else
+ {
+ OSL_FAIL( "XStorable2 is not supported by the model!" );
+ aModelData.GetStorable()->store();
+ }
+
+ return false;
+ }
+
+ // preselect a filter for the storing process
+ uno::Sequence< beans::PropertyValue > aFilterProps = aModelData.GetPreselectedFilter_Impl( m_nStoreMode );
+
+ DBG_ASSERT( aFilterProps.hasElements(), "No filter for storing!\n" );
+ if ( !aFilterProps.hasElements() )
+ throw task::ErrorCodeIOException(
+ "SfxStoringHelper::GUIStoreModel: ERRCODE_IO_INVALIDPARAMETER",
+ uno::Reference< uno::XInterface >(), sal_uInt32(ERRCODE_IO_INVALIDPARAMETER));
+
+ ::comphelper::SequenceAsHashMap aFilterPropsHM( aFilterProps );
+ OUString aFilterName = aFilterPropsHM.getUnpackedValueOrDefault( "Name", OUString() );
+
+ const OUString aFilterFromMediaDescr = aModelData.GetMediaDescr().getUnpackedValueOrDefault( aFilterNameString, OUString() );
+ const OUString aOldFilterName = aModelData.GetDocProps().getUnpackedValueOrDefault( aFilterNameString, OUString() );
+
+ ::comphelper::SequenceAsHashMap::const_iterator aFileNameIter = aModelData.GetMediaDescr().find( OUString("URL") );
+
+ bool bPDFOptions = (m_nStoreMode & PDFEXPORT_REQUESTED) && !(m_nStoreMode & PDFDIRECTEXPORT_REQUESTED);
+ bool bEPUBOptions = (m_nStoreMode & EPUBEXPORT_REQUESTED) && !(m_nStoreMode & EPUBDIRECTEXPORT_REQUESTED);
+ if ( ( m_nStoreMode & EXPORT_REQUESTED ) && (bPDFOptions || bEPUBOptions) )
+ {
+ // this is PDF or EPUB export, the filter options dialog should be shown before the export
+ aModelData.GetMediaDescr()[aFilterNameString] <<= aFilterName;
+ if ( aModelData.GetMediaDescr().find( "FilterFlags" ) == aModelData.GetMediaDescr().end()
+ && aModelData.GetMediaDescr().find( aFilterOptionsString ) == aModelData.GetMediaDescr().end()
+ && aModelData.GetMediaDescr().find( aFilterDataString ) == aModelData.GetMediaDescr().end() )
+ {
+ // execute filter options dialog since no options are set in the media descriptor
+ if ( aModelData.ExecuteFilterDialog_Impl( aFilterName, bIsAsync ) )
+ m_bDialogUsed = true;
+ }
+ }
+
+ if (bIsAsync)
+ return false;
+
+ return SfxStoringHelper::FinishGUIStoreModel(aFileNameIter, aModelData, m_bRemote, m_nStoreMode, aFilterProps,
+ m_bSetStandardName, m_bPreselectPassword, m_bDialogUsed,
+ aFilterFromMediaDescr, aOldFilterName, aArgsSequence, aFilterName);
+}
+
+bool SfxStoringHelper::FinishGUIStoreModel(::comphelper::SequenceAsHashMap::const_iterator& aFileNameIter,
+ ModelData_Impl& aModelData, bool bRemote, sal_Int16 nStoreMode,
+ uno::Sequence< beans::PropertyValue >& aFilterProps,
+ bool bSetStandardName, bool bPreselectPassword, bool bDialogUsed,
+ std::u16string_view aFilterFromMediaDescr,
+ std::u16string_view aOldFilterName,
+ uno::Sequence< beans::PropertyValue >& aArgsSequence,
+ OUString aFilterName)
+{
+ const OUString sFilterNameString(aFilterNameString);
+ const OUString sFilterOptionsString(aFilterOptionsString);
+ const OUString sFilterDataString(aFilterDataString);
+ bool bUseFilterOptions = false;
+ INetURLObject aURL;
+
+ if ( aFileNameIter == aModelData.GetMediaDescr().end() )
+ {
+ sal_Int16 nDialog = SFX2_IMPL_DIALOG_CONFIG;
+
+ if( bRemote )
+ {
+ nDialog = SFX2_IMPL_DIALOG_REMOTE;
+ }
+ else
+ {
+ ::comphelper::SequenceAsHashMap::const_iterator aDlgIter =
+ aModelData.GetMediaDescr().find( OUString("UseSystemDialog") );
+ if ( aDlgIter != aModelData.GetMediaDescr().end() )
+ {
+ bool bUseSystemDialog = true;
+ if ( aDlgIter->second >>= bUseSystemDialog )
+ {
+ if ( bUseSystemDialog )
+ nDialog = SFX2_IMPL_DIALOG_SYSTEM;
+ else
+ nDialog = SFX2_IMPL_DIALOG_OOO;
+ }
+ }
+ }
+
+ // The Dispatch supports parameter FolderName that overwrites SuggestedSaveAsDir
+ OUString aSuggestedDir = aModelData.GetMediaDescr().getUnpackedValueOrDefault("FolderName", OUString() );
+ if ( aSuggestedDir.isEmpty() )
+ {
+ aSuggestedDir = aModelData.GetMediaDescr().getUnpackedValueOrDefault("SuggestedSaveAsDir", OUString() );
+ if ( aSuggestedDir.isEmpty() )
+ aSuggestedDir = aModelData.GetDocProps().getUnpackedValueOrDefault("SuggestedSaveAsDir", OUString() );
+ }
+
+ OUString aSuggestedName = aModelData.GetMediaDescr().getUnpackedValueOrDefault("SuggestedSaveAsName", OUString() );
+ if ( aSuggestedName.isEmpty() )
+ aSuggestedName = aModelData.GetDocProps().getUnpackedValueOrDefault("SuggestedSaveAsName", OUString() );
+
+ OUString sStandardDir;
+ ::comphelper::SequenceAsHashMap::const_iterator aStdDirIter =
+ aModelData.GetMediaDescr().find( OUString("StandardDir") );
+ if ( aStdDirIter != aModelData.GetMediaDescr().end() )
+ aStdDirIter->second >>= sStandardDir;
+
+ css::uno::Sequence< OUString > aDenyList;
+
+ ::comphelper::SequenceAsHashMap::const_iterator aDenyListIter =
+ aModelData.GetMediaDescr().find( OUString("DenyList") );
+ if ( aDenyListIter != aModelData.GetMediaDescr().end() )
+ aDenyListIter->second >>= aDenyList;
+
+ for (;;)
+ {
+ // in case the dialog is opened a second time the folder should be the same as previously navigated to by the user, not what was handed over by initial parameters
+ bUseFilterOptions = aModelData.OutputFileDialog( nStoreMode, aFilterProps, bSetStandardName, aSuggestedName, bPreselectPassword, aSuggestedDir, nDialog, sStandardDir, aDenyList );
+ if ( nStoreMode == SAVEAS_REQUESTED )
+ {
+ // in case of saving check filter for possible alien warning
+ const OUString aSelFilterName = aModelData.GetMediaDescr().getUnpackedValueOrDefault( sFilterNameString, OUString() );
+ sal_Int8 nStatusFilterSave = aModelData.CheckFilter( aSelFilterName );
+ if ( nStatusFilterSave == STATUS_SAVEAS_STANDARDNAME )
+ {
+ // switch to best filter
+ bSetStandardName = true;
+ }
+ else if ( nStatusFilterSave == STATUS_SAVE )
+ {
+ // user confirmed alien filter or "good" filter is used
+ break;
+ }
+ }
+ else
+ break;
+ }
+
+ bDialogUsed = true;
+ aFileNameIter = aModelData.GetMediaDescr().find( OUString("URL") );
+ }
+ else
+ {
+ // the target file name is provided so check if new filter options
+ // are provided or old options can be used
+ if ( aFilterFromMediaDescr == aOldFilterName )
+ {
+ ::comphelper::SequenceAsHashMap::const_iterator aIter =
+ aModelData.GetDocProps().find( sFilterOptionsString );
+ if ( aIter != aModelData.GetDocProps().end()
+ && aModelData.GetMediaDescr().find( sFilterOptionsString ) == aModelData.GetMediaDescr().end() )
+ aModelData.GetMediaDescr()[aIter->first] = aIter->second;
+
+ aIter = aModelData.GetDocProps().find( sFilterDataString );
+ if ( aIter != aModelData.GetDocProps().end()
+ && aModelData.GetMediaDescr().find( sFilterDataString ) == aModelData.GetMediaDescr().end() )
+ aModelData.GetMediaDescr()[aIter->first] = aIter->second;
+ }
+ }
+
+ if ( aFileNameIter != aModelData.GetMediaDescr().end() )
+ {
+ OUString aFileName;
+ aFileNameIter->second >>= aFileName;
+ aURL.SetURL( aFileName );
+ DBG_ASSERT( aURL.GetProtocol() != INetProtocol::NotValid, "Illegal URL!" );
+
+ ::comphelper::SequenceAsHashMap::const_iterator aIter =
+ aModelData.GetMediaDescr().find( sFilterNameString );
+
+ if ( aIter != aModelData.GetMediaDescr().end() )
+ aIter->second >>= aFilterName;
+ else
+ aModelData.GetMediaDescr()[sFilterNameString] <<= aFilterName;
+
+ DBG_ASSERT( !aFilterName.isEmpty(), "Illegal filter!" );
+ }
+ else
+ {
+ SAL_WARN( "sfx.doc", "This code must be unreachable!" );
+ throw task::ErrorCodeIOException(
+ "SfxStoringHelper::GUIStoreModel: ERRCODE_IO_INVALIDPARAMETER",
+ uno::Reference< uno::XInterface >(), sal_uInt32(ERRCODE_IO_INVALIDPARAMETER));
+ }
+
+ ::comphelper::SequenceAsHashMap::const_iterator aIter =
+ aModelData.GetMediaDescr().find( OUString("FilterFlags") );
+ bool bFilterFlagsSet = ( aIter != aModelData.GetMediaDescr().end() );
+
+ // check if the filter Dialog has not been called before
+ if( !( nStoreMode & PDFEXPORT_REQUESTED ) && !( nStoreMode & EPUBEXPORT_REQUESTED ) && !bFilterFlagsSet
+ && ( ( nStoreMode & EXPORT_REQUESTED ) || bUseFilterOptions ) )
+ {
+ // execute filter options dialog
+ if ( aModelData.ExecuteFilterDialog_Impl( aFilterName, false ) )
+ {
+ bDialogUsed = true;
+ // check if the file is a pdf or not and change the storing mode at convenience
+ if (aFilterName.endsWith("pdf_Export"))
+ nStoreMode = EXPORT_REQUESTED | PDFEXPORT_REQUESTED;
+ }
+ }
+
+ // so the arguments will not change any more and can be stored to the main location
+ aArgsSequence = aModelData.GetMediaDescr().getAsConstPropertyValueList();
+
+ // store the document and handle it's docinfo
+
+ DocumentSettingsGuard aSettingsGuard( aModelData.GetModel(), aModelData.IsRecommendReadOnly(), nStoreMode & EXPORT_REQUESTED );
+
+ // Treat attempted PDF export like a print: update document print statistics
+ if ((nStoreMode & PDFEXPORT_REQUESTED) && SfxViewShell::Current())
+ {
+ SfxObjectShell* pDocShell = SfxViewShell::Current()->GetObjectShell();
+ const bool bWasEnableSetModified = pDocShell && pDocShell->IsEnableSetModified();
+ bool bResetESM = false;
+
+ if (bWasEnableSetModified
+ && !officecfg::Office::Common::Print::PrintingModifiesDocument::get())
+ {
+ pDocShell->EnableSetModified(false); // don't let export mark document as modified
+ bResetESM = true;
+ }
+
+ uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
+ aModelData.GetModel(), uno::UNO_QUERY_THROW);
+ uno::Reference<document::XDocumentProperties> xDocProps(xDPS->getDocumentProperties());
+ xDocProps->setPrintDate(DateTime(DateTime::SYSTEM).GetUNODateTime());
+
+ OUString sPrintedBy(SfxResId(STR_SFX_FILTERNAME_PDF));
+ if (pDocShell && pDocShell->IsUseUserData())
+ {
+ const OUString& sFullName = SvtUserOptions().GetFullName();
+ if (!sFullName.isEmpty())
+ sPrintedBy += ": " + sFullName;
+ }
+ xDocProps->setPrintedBy(sPrintedBy);
+
+ if (bResetESM)
+ pDocShell->EnableSetModified(true);
+ }
+
+ OSL_ENSURE( aModelData.GetMediaDescr().find( OUString( "Password" ) ) == aModelData.GetMediaDescr().end(), "The Password property of MediaDescriptor should not be used here!" );
+ if ( officecfg::Office::Common::Save::Document::EditProperty::get()
+ && ( !aModelData.GetStorable()->hasLocation()
+ || INetURLObject( aModelData.GetStorable()->getLocation() ) != aURL ) )
+ {
+ // this is definitely not a Save operation
+ // so the document info can be updated
+
+ // on export document info must be preserved
+ uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
+ aModelData.GetModel(), uno::UNO_QUERY_THROW);
+ uno::Reference<util::XCloneable> xCloneable(
+ xDPS->getDocumentProperties(), uno::UNO_QUERY_THROW);
+ uno::Reference<document::XDocumentProperties> xOldDocProps(
+ xCloneable->createClone(), uno::UNO_QUERY_THROW);
+
+ // use dispatch API to show document info dialog
+ if ( aModelData.ShowDocumentInfoDialog() )
+ bDialogUsed = true;
+ else
+ {
+ OSL_FAIL( "Can't execute document info dialog!" );
+ }
+
+ try {
+ // Document properties can contain streams that should be freed before storing
+ aModelData.FreeDocumentProps();
+ if ( nStoreMode & EXPORT_REQUESTED )
+ aModelData.GetStorable()->storeToURL( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aArgsSequence );
+ else
+ aModelData.GetStorable()->storeAsURL( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aArgsSequence );
+ }
+ catch( const uno::Exception& )
+ {
+ if ( nStoreMode & EXPORT_REQUESTED )
+ {
+ SetDocInfoState(aModelData.GetModel(), xOldDocProps);
+ }
+ throw;
+ }
+
+ if ( nStoreMode & EXPORT_REQUESTED )
+ {
+ SetDocInfoState(aModelData.GetModel(), xOldDocProps);
+ }
+ }
+ else
+ {
+ // Document properties can contain streams that should be freed before storing
+ aModelData.FreeDocumentProps();
+
+ // this is actually a save operation with different parameters
+ // so storeTo or storeAs without DocInfo operations are used
+ if ( nStoreMode & EXPORT_REQUESTED )
+ aModelData.GetStorable()->storeToURL( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aArgsSequence );
+ else
+ aModelData.GetStorable()->storeAsURL( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aArgsSequence );
+ }
+
+ // Launch PDF viewer
+ if ( nStoreMode & PDFEXPORT_REQUESTED && !comphelper::LibreOfficeKit::isActive() )
+ {
+ FilterConfigItem aItem(u"Office.Common/Filter/PDF/Export/");
+ bool aViewPDF = aItem.ReadBool( "ViewPDFAfterExport", false );
+
+ if ( aViewPDF )
+ {
+ uno::Reference<XSystemShellExecute> xSystemShellExecute(SystemShellExecute::create( ::comphelper::getProcessComponentContext() ) );
+ xSystemShellExecute->execute( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), "", SystemShellExecuteFlags::URIS_ONLY );
+ }
+ }
+
+ if ( comphelper::LibreOfficeKit::isActive() )
+ {
+ if ( SfxViewShell* pShell = SfxViewShell::Current() )
+ {
+ OUString sURL = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ pShell->libreOfficeKitViewCallback( LOK_CALLBACK_EXPORT_FILE, sURL.toUtf8() );
+ }
+ }
+
+ return bDialogUsed;
+}
+
+
+// static
+bool SfxStoringHelper::CheckFilterOptionsAppearance(
+ const uno::Reference< container::XNameAccess >& xFilterCFG,
+ const OUString& aFilterName )
+{
+ bool bUseFilterOptions = false;
+
+ DBG_ASSERT( xFilterCFG.is(), "No filter configuration!\n" );
+ if( xFilterCFG.is() )
+ {
+ try {
+ uno::Sequence < beans::PropertyValue > aProps;
+ uno::Any aAny = xFilterCFG->getByName( aFilterName );
+ if ( aAny >>= aProps )
+ {
+ ::comphelper::SequenceAsHashMap aPropsHM( aProps );
+ if( !aPropsHM.getUnpackedValueOrDefault( "UIComponent", OUString() ).isEmpty() )
+ bUseFilterOptions = true;
+ }
+ }
+ catch( const uno::Exception& )
+ {
+ }
+ }
+
+ return bUseFilterOptions;
+}
+
+
+// static
+void SfxStoringHelper::SetDocInfoState(
+ const uno::Reference< frame::XModel >& xModel,
+ const uno::Reference< document::XDocumentProperties>& i_xOldDocProps )
+{
+ uno::Reference<document::XDocumentPropertiesSupplier> const
+ xModelDocPropsSupplier(xModel, uno::UNO_QUERY_THROW);
+ uno::Reference<document::XDocumentProperties> const xDocPropsToFill =
+ xModelDocPropsSupplier->getDocumentProperties();
+ uno::Reference< beans::XPropertySet > const xPropSet(
+ i_xOldDocProps->getUserDefinedProperties(), uno::UNO_QUERY_THROW);
+
+ uno::Reference< util::XModifiable > xModifiable( xModel, uno::UNO_QUERY );
+ if ( !xModifiable.is() )
+ throw uno::RuntimeException();
+
+ bool bIsModified = xModifiable->isModified();
+
+ try
+ {
+ uno::Reference< beans::XPropertySet > const xSet(
+ xDocPropsToFill->getUserDefinedProperties(), uno::UNO_QUERY);
+ uno::Reference< beans::XPropertyContainer > xContainer( xSet, uno::UNO_QUERY );
+ uno::Reference< beans::XPropertySetInfo > xSetInfo = xSet->getPropertySetInfo();
+ const uno::Sequence< beans::Property > lProps = xSetInfo->getProperties();
+ for (const beans::Property& rProp : lProps)
+ {
+ uno::Any aValue = xPropSet->getPropertyValue( rProp.Name );
+ if ( rProp.Attributes & css::beans::PropertyAttribute::REMOVABLE )
+ {
+ try
+ {
+ // QUESTION: DefaultValue?!
+ xContainer->addProperty( rProp.Name, rProp.Attributes, aValue );
+ }
+ catch (beans::PropertyExistException const&) {}
+ try
+ {
+ // it is possible that the propertysets from XML and binary files differ; we shouldn't break then
+ xSet->setPropertyValue( rProp.Name, aValue );
+ }
+ catch ( const uno::Exception& ) {}
+ }
+ }
+
+ // sigh... have to set these manually I'm afraid... wonder why
+ // SfxObjectShell doesn't handle this internally, should be easier
+ xDocPropsToFill->setAuthor(i_xOldDocProps->getAuthor());
+ xDocPropsToFill->setGenerator(i_xOldDocProps->getGenerator());
+ xDocPropsToFill->setCreationDate(i_xOldDocProps->getCreationDate());
+ xDocPropsToFill->setTitle(i_xOldDocProps->getTitle());
+ xDocPropsToFill->setSubject(i_xOldDocProps->getSubject());
+ xDocPropsToFill->setDescription(i_xOldDocProps->getDescription());
+ xDocPropsToFill->setKeywords(i_xOldDocProps->getKeywords());
+ xDocPropsToFill->setModifiedBy(i_xOldDocProps->getModifiedBy());
+ xDocPropsToFill->setModificationDate(i_xOldDocProps->getModificationDate());
+ xDocPropsToFill->setPrintedBy(i_xOldDocProps->getPrintedBy());
+ xDocPropsToFill->setPrintDate(i_xOldDocProps->getPrintDate());
+ xDocPropsToFill->setAutoloadURL(i_xOldDocProps->getAutoloadURL());
+ xDocPropsToFill->setAutoloadSecs(i_xOldDocProps->getAutoloadSecs());
+ xDocPropsToFill->setDefaultTarget(i_xOldDocProps->getDefaultTarget());
+ xDocPropsToFill->setEditingCycles(i_xOldDocProps->getEditingCycles());
+ xDocPropsToFill->setEditingDuration(i_xOldDocProps->getEditingDuration());
+ // other attributes e.g. DocumentStatistics are not editable from dialog
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_INFO_EXCEPTION("sfx.doc", "SetDocInfoState");
+ }
+
+ // set the modified flag back if required
+ if ( bIsModified != bool(xModifiable->isModified()) )
+ xModifiable->setModified( bIsModified );
+}
+
+
+// static
+bool SfxStoringHelper::WarnUnacceptableFormat( const uno::Reference< frame::XModel >& xModel,
+ std::u16string_view aOldUIName,
+ const OUString& aDefExtension,
+ bool bDefIsAlien )
+{
+ if ( !officecfg::Office::Common::Save::Document::WarnAlienFormat::get() )
+ return true;
+
+ weld::Window* pWin = SfxStoringHelper::GetModelWindow(xModel);
+ SfxAlienWarningDialog aDlg(pWin, aOldUIName, aDefExtension, bDefIsAlien);
+
+ return aDlg.run() == RET_OK;
+}
+
+uno::Reference<awt::XWindow> SfxStoringHelper::GetModelXWindow(const uno::Reference<frame::XModel>& xModel)
+{
+ try {
+ if ( xModel.is() )
+ {
+ uno::Reference< frame::XController > xController = xModel->getCurrentController();
+ if ( xController.is() )
+ {
+ uno::Reference< frame::XFrame > xFrame = xController->getFrame();
+ if ( xFrame.is() )
+ {
+ return xFrame->getContainerWindow();
+ }
+ }
+ }
+ }
+ catch ( const uno::Exception& )
+ {
+ }
+
+ return uno::Reference<awt::XWindow>();
+}
+
+weld::Window* SfxStoringHelper::GetModelWindow( const uno::Reference< frame::XModel >& xModel )
+{
+ weld::Window* pWin = nullptr;
+
+ try
+ {
+ pWin = Application::GetFrameWeld(GetModelXWindow(xModel));
+ }
+ catch (const uno::Exception&)
+ {
+ }
+
+ return pWin;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/iframe.cxx b/sfx2/source/doc/iframe.cxx
new file mode 100644
index 0000000000..0ca2726cbc
--- /dev/null
+++ b/sfx2/source/doc/iframe.cxx
@@ -0,0 +1,454 @@
+/* -*- 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 <sal/config.h>
+
+#include <com/sun/star/awt/XVclWindowPeer.hpp>
+#include <com/sun/star/frame/XDispatch.hpp>
+#include <com/sun/star/frame/Frame.hpp>
+#include <com/sun/star/frame/XFrame2.hpp>
+#include <com/sun/star/frame/XSynchronousFrameLoader.hpp>
+#include <com/sun/star/task/InteractionHandler.hpp>
+#include <com/sun/star/util/URLTransformer.hpp>
+#include <com/sun/star/util/XURLTransformer.hpp>
+#include <com/sun/star/util/XCloseable.hpp>
+#include <com/sun/star/lang/XEventListener.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp>
+#include <com/sun/star/embed/XEmbeddedObject.hpp>
+
+#include <comphelper/propertyvalue.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <svl/itemprop.hxx>
+#include <sfx2/docfile.hxx>
+#include <sfx2/frmdescr.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/sfxdlg.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+#include <utility>
+#include <vcl/window.hxx>
+#include <tools/debug.hxx>
+#include <macroloader.hxx>
+#include <eventsupplier.hxx>
+
+using namespace ::com::sun::star;
+
+namespace {
+
+class IFrameObject : public ::cppu::WeakImplHelper <
+ css::util::XCloseable,
+ css::lang::XEventListener,
+ css::frame::XSynchronousFrameLoader,
+ css::ui::dialogs::XExecutableDialog,
+ css::lang::XServiceInfo,
+ css::beans::XPropertySet >
+{
+ css::uno::Reference < css::uno::XComponentContext > mxContext;
+ css::uno::Reference < css::frame::XFrame2 > mxFrame;
+ css::uno::Reference < css::embed::XEmbeddedObject > mxObj;
+ SfxItemPropertyMap maPropMap;
+ SfxFrameDescriptor maFrmDescr;
+
+public:
+ /// @throws css::uno::Exception
+ /// @throws css::uno::RuntimeException
+ IFrameObject(css::uno::Reference < css::uno::XComponentContext> xContext, const css::uno::Sequence< css::uno::Any >& aArguments);
+
+ virtual OUString SAL_CALL getImplementationName() override
+ {
+ return "com.sun.star.comp.sfx2.IFrameObject";
+ }
+
+ 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.SpecialEmbeddedObject" };
+ return aSeq;
+ }
+
+ virtual sal_Bool SAL_CALL load( const css::uno::Sequence < css::beans::PropertyValue >& lDescriptor,
+ const css::uno::Reference < css::frame::XFrame >& xFrame ) override;
+ virtual void SAL_CALL cancel() override;
+ virtual void SAL_CALL close( sal_Bool bDeliverOwnership ) override;
+ virtual void SAL_CALL addCloseListener( const css::uno::Reference < css::util::XCloseListener >& xListener ) override;
+ virtual void SAL_CALL removeCloseListener( const css::uno::Reference < css::util::XCloseListener >& xListener ) override;
+ virtual void SAL_CALL disposing( const css::lang::EventObject& aEvent ) override ;
+ virtual void SAL_CALL setTitle( const OUString& aTitle ) override;
+ virtual ::sal_Int16 SAL_CALL execute( ) override;
+ virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override;
+ virtual void SAL_CALL addPropertyChangeListener(const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener > & aListener) override;
+ virtual void SAL_CALL removePropertyChangeListener(const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener > & aListener) override;
+ virtual void SAL_CALL addVetoableChangeListener(const OUString& aPropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener > & aListener) override;
+ virtual void SAL_CALL removeVetoableChangeListener(const OUString& aPropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener > & aListener) override;
+ virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override;
+ virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override;
+};
+
+class IFrameWindow_Impl : public vcl::Window
+{
+public:
+ IFrameWindow_Impl( vcl::Window *pParent, bool bHasBorder );
+};
+
+IFrameWindow_Impl::IFrameWindow_Impl( vcl::Window *pParent, bool bHasBorder )
+ : Window( pParent, WB_CLIPCHILDREN | WB_NODIALOGCONTROL )
+{
+ if ( !bHasBorder )
+ SetBorderStyle( WindowBorderStyle::NOBORDER );
+ else
+ SetBorderStyle( WindowBorderStyle::NORMAL );
+}
+
+#define PROPERTY_UNBOUND 0
+
+#define WID_FRAME_URL 1
+#define WID_FRAME_NAME 2
+#define WID_FRAME_IS_AUTO_SCROLL 3
+#define WID_FRAME_IS_SCROLLING_MODE 4
+#define WID_FRAME_IS_BORDER 5
+#define WID_FRAME_IS_AUTO_BORDER 6
+#define WID_FRAME_MARGIN_WIDTH 7
+#define WID_FRAME_MARGIN_HEIGHT 8
+
+std::span<const SfxItemPropertyMapEntry> lcl_GetIFramePropertyMap_Impl()
+{
+ static const SfxItemPropertyMapEntry aIFramePropertyMap_Impl[] =
+ {
+ { u"FrameIsAutoBorder"_ustr, WID_FRAME_IS_AUTO_BORDER, cppu::UnoType<bool>::get(), PROPERTY_UNBOUND, 0 },
+ { u"FrameIsAutoScroll"_ustr, WID_FRAME_IS_AUTO_SCROLL, cppu::UnoType<bool>::get(), PROPERTY_UNBOUND, 0 },
+ { u"FrameIsBorder"_ustr, WID_FRAME_IS_BORDER, cppu::UnoType<bool>::get(), PROPERTY_UNBOUND, 0 },
+ { u"FrameIsScrollingMode"_ustr, WID_FRAME_IS_SCROLLING_MODE,cppu::UnoType<bool>::get(), PROPERTY_UNBOUND, 0 },
+ { u"FrameMarginHeight"_ustr, WID_FRAME_MARGIN_HEIGHT, cppu::UnoType<sal_Int32>::get(), PROPERTY_UNBOUND, 0 },
+ { u"FrameMarginWidth"_ustr, WID_FRAME_MARGIN_WIDTH, cppu::UnoType<sal_Int32>::get(), PROPERTY_UNBOUND, 0 },
+ { u"FrameName"_ustr, WID_FRAME_NAME, cppu::UnoType<OUString>::get(), PROPERTY_UNBOUND, 0 },
+ { u"FrameURL"_ustr, WID_FRAME_URL, cppu::UnoType<OUString>::get(), PROPERTY_UNBOUND, 0 },
+ };
+ return aIFramePropertyMap_Impl;
+}
+
+IFrameObject::IFrameObject(uno::Reference < uno::XComponentContext > xContext, const css::uno::Sequence< css::uno::Any >& aArguments)
+ : mxContext(std::move( xContext ))
+ , maPropMap( lcl_GetIFramePropertyMap_Impl() )
+{
+ if ( aArguments.hasElements() )
+ aArguments[0] >>= mxObj;
+}
+
+sal_Bool SAL_CALL IFrameObject::load(
+ const uno::Sequence < css::beans::PropertyValue >& /*lDescriptor*/,
+ const uno::Reference < frame::XFrame >& xFrame )
+{
+ if ( officecfg::Office::Common::Misc::PluginsEnabled::get() )
+ {
+ util::URL aTargetURL;
+ aTargetURL.Complete = maFrmDescr.GetURL().GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ uno::Reference < util::XURLTransformer > xTrans( util::URLTransformer::create( mxContext ) );
+ xTrans->parseStrict( aTargetURL );
+
+ INetURLObject aURLObject(aTargetURL.Complete);
+ if (aURLObject.IsExoticProtocol())
+ {
+ SAL_WARN("sfx", "IFrameObject::load ignoring: " << aTargetURL.Complete);
+ return false;
+ }
+
+ uno::Reference<frame::XFramesSupplier> xParentFrame = xFrame->getCreator();
+ SfxObjectShell* pDoc = SfxMacroLoader::GetObjectShell(xParentFrame);
+
+ const bool bIsFactoryURL = aTargetURL.Complete.startsWith("private:factory/");
+ if (!bIsFactoryURL)
+ {
+ bool bUpdateAllowed(true);
+ if (pDoc)
+ {
+ comphelper::EmbeddedObjectContainer& rEmbeddedObjectContainer = pDoc->getEmbeddedObjectContainer();
+ bUpdateAllowed = rEmbeddedObjectContainer.getUserAllowsLinkUpdate();
+ }
+ if (!bUpdateAllowed)
+ return false;
+ }
+
+ OUString sReferer;
+ if (pDoc && pDoc->HasName())
+ sReferer = pDoc->GetMedium()->GetName();
+
+ uno::Reference<css::awt::XWindow> xParentWindow(xFrame->getContainerWindow());
+
+ if (!mxFrame.is())
+ {
+ VclPtr<vcl::Window> pParent = VCLUnoHelper::GetWindow(xParentWindow);
+ VclPtr<IFrameWindow_Impl> pWin = VclPtr<IFrameWindow_Impl>::Create( pParent, maFrmDescr.IsFrameBorderOn() );
+ pWin->SetSizePixel( pParent->GetOutputSizePixel() );
+ pWin->SetBackground();
+ pWin->Show();
+
+ uno::Reference < awt::XWindow > xWindow( pWin->GetComponentInterface(), uno::UNO_QUERY );
+ xFrame->setComponent( xWindow, uno::Reference < frame::XController >() );
+
+ // we must destroy the IFrame before the parent is destroyed
+ xWindow->addEventListener( this );
+
+ mxFrame = frame::Frame::create( mxContext );
+ uno::Reference < awt::XWindow > xWin( pWin->GetComponentInterface(), uno::UNO_QUERY );
+ mxFrame->initialize( xWin );
+ mxFrame->setName( maFrmDescr.GetName() );
+
+ uno::Reference < frame::XFramesSupplier > xFramesSupplier( xFrame, uno::UNO_QUERY );
+ if ( xFramesSupplier.is() )
+ mxFrame->setCreator( xFramesSupplier );
+ }
+
+ uno::Reference<task::XInteractionHandler> xInteractionHandler(task::InteractionHandler::createWithParent(mxContext, xParentWindow));
+ uno::Sequence < beans::PropertyValue > aProps{
+ comphelper::makePropertyValue("PluginMode", sal_Int16(2)),
+ comphelper::makePropertyValue("ReadOnly", true),
+ comphelper::makePropertyValue("InteractionHandler", xInteractionHandler),
+ comphelper::makePropertyValue("Referer", sReferer)
+ };
+ uno::Reference < frame::XDispatch > xDisp = mxFrame->queryDispatch( aTargetURL, "_self", 0 );
+ if ( xDisp.is() )
+ xDisp->dispatch( aTargetURL, aProps );
+
+ return true;
+ }
+
+ return false;
+}
+
+void SAL_CALL IFrameObject::cancel()
+{
+ try
+ {
+ uno::Reference < util::XCloseable > xClose( mxFrame, uno::UNO_QUERY );
+ if ( xClose.is() )
+ xClose->close( true );
+ mxFrame = nullptr;
+ }
+ catch (const uno::Exception&)
+ {
+ }
+}
+
+void SAL_CALL IFrameObject::close( sal_Bool /*bDeliverOwnership*/ )
+{
+}
+
+void SAL_CALL IFrameObject::addCloseListener( const css::uno::Reference < css::util::XCloseListener >& )
+{
+}
+
+void SAL_CALL IFrameObject::removeCloseListener( const css::uno::Reference < css::util::XCloseListener >& )
+{
+}
+
+void SAL_CALL IFrameObject::disposing( const css::lang::EventObject& )
+{
+ cancel();
+}
+
+uno::Reference< beans::XPropertySetInfo > SAL_CALL IFrameObject::getPropertySetInfo()
+{
+ static uno::Reference< beans::XPropertySetInfo > xInfo = new SfxItemPropertySetInfo( maPropMap );
+ return xInfo;
+}
+
+void SAL_CALL IFrameObject::setPropertyValue(const OUString& aPropertyName, const uno::Any& aAny)
+{
+ const SfxItemPropertyMapEntry* pEntry = maPropMap.getByName( aPropertyName );
+ if( !pEntry )
+ throw beans::UnknownPropertyException(aPropertyName);
+ switch( pEntry->nWID )
+ {
+ case WID_FRAME_URL:
+ {
+ OUString aURL;
+ aAny >>= aURL;
+ maFrmDescr.SetURL( aURL );
+ }
+ break;
+ case WID_FRAME_NAME:
+ {
+ OUString aName;
+ if ( aAny >>= aName )
+ maFrmDescr.SetName( aName );
+ }
+ break;
+ case WID_FRAME_IS_AUTO_SCROLL:
+ {
+ bool bIsAutoScroll;
+ if ( (aAny >>= bIsAutoScroll) && bIsAutoScroll )
+ maFrmDescr.SetScrollingMode( ScrollingMode::Auto );
+ }
+ break;
+ case WID_FRAME_IS_SCROLLING_MODE:
+ {
+ bool bIsScroll;
+ if ( aAny >>= bIsScroll )
+ maFrmDescr.SetScrollingMode( bIsScroll ? ScrollingMode::Yes : ScrollingMode::No );
+ }
+ break;
+ case WID_FRAME_IS_BORDER:
+ {
+ bool bIsBorder;
+ if ( aAny >>= bIsBorder )
+ maFrmDescr.SetFrameBorder( bIsBorder );
+ }
+ break;
+ case WID_FRAME_IS_AUTO_BORDER:
+ {
+ bool bIsAutoBorder;
+ if ( aAny >>= bIsAutoBorder )
+ {
+ bool bBorder = maFrmDescr.IsFrameBorderOn();
+ maFrmDescr.ResetBorder();
+ if ( bIsAutoBorder )
+ maFrmDescr.SetFrameBorder( bBorder );
+ }
+ }
+ break;
+ case WID_FRAME_MARGIN_WIDTH:
+ {
+ sal_Int32 nMargin = 0;
+ Size aSize = maFrmDescr.GetMargin();
+ if ( aAny >>= nMargin )
+ {
+ aSize.setWidth( nMargin );
+ maFrmDescr.SetMargin( aSize );
+ }
+ }
+ break;
+ case WID_FRAME_MARGIN_HEIGHT:
+ {
+ sal_Int32 nMargin = 0;
+ Size aSize = maFrmDescr.GetMargin();
+ if ( aAny >>= nMargin )
+ {
+ aSize.setHeight( nMargin );
+ maFrmDescr.SetMargin( aSize );
+ }
+ }
+ break;
+ default: ;
+ }
+}
+
+uno::Any SAL_CALL IFrameObject::getPropertyValue(const OUString& aPropertyName)
+{
+ const SfxItemPropertyMapEntry* pEntry = maPropMap.getByName( aPropertyName );
+ if( !pEntry )
+ throw beans::UnknownPropertyException(aPropertyName);
+ uno::Any aAny;
+ switch( pEntry->nWID )
+ {
+ case WID_FRAME_URL:
+ {
+ aAny <<= maFrmDescr.GetURL().GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ }
+ break;
+ case WID_FRAME_NAME:
+ {
+ aAny <<= maFrmDescr.GetName();
+ }
+ break;
+ case WID_FRAME_IS_AUTO_SCROLL:
+ {
+ bool bIsAutoScroll = ( maFrmDescr.GetScrollingMode() == ScrollingMode::Auto );
+ aAny <<= bIsAutoScroll;
+ }
+ break;
+ case WID_FRAME_IS_SCROLLING_MODE:
+ {
+ bool bIsScroll = ( maFrmDescr.GetScrollingMode() == ScrollingMode::Yes );
+ aAny <<= bIsScroll;
+ }
+ break;
+ case WID_FRAME_IS_BORDER:
+ {
+ bool bIsBorder = maFrmDescr.IsFrameBorderOn();
+ aAny <<= bIsBorder;
+ }
+ break;
+ case WID_FRAME_IS_AUTO_BORDER:
+ {
+ bool bIsAutoBorder = !maFrmDescr.IsFrameBorderSet();
+ aAny <<= bIsAutoBorder;
+ }
+ break;
+ case WID_FRAME_MARGIN_WIDTH:
+ {
+ aAny <<= static_cast<sal_Int32>(maFrmDescr.GetMargin().Width());
+ }
+ break;
+ case WID_FRAME_MARGIN_HEIGHT:
+ {
+ aAny <<= static_cast<sal_Int32>(maFrmDescr.GetMargin().Height());
+ }
+ break;
+ default: ;
+ }
+ return aAny;
+}
+
+void SAL_CALL IFrameObject::addPropertyChangeListener(const OUString&, const css::uno::Reference< css::beans::XPropertyChangeListener > & )
+{
+}
+
+void SAL_CALL IFrameObject::removePropertyChangeListener(const OUString&, const css::uno::Reference< css::beans::XPropertyChangeListener > & )
+{
+}
+
+void SAL_CALL IFrameObject::addVetoableChangeListener(const OUString&, const css::uno::Reference< css::beans::XVetoableChangeListener > & )
+{
+}
+
+void SAL_CALL IFrameObject::removeVetoableChangeListener(const OUString&, const css::uno::Reference< css::beans::XVetoableChangeListener > & )
+{
+}
+
+::sal_Int16 SAL_CALL IFrameObject::execute()
+{
+ SfxAbstractDialogFactory* pFact = SfxAbstractDialogFactory::Create();
+ //we really should set a parent here
+ ScopedVclPtr<VclAbstractDialog> pDlg(pFact->CreateEditObjectDialog(nullptr, ".uno:InsertObjectFloatingFrame", mxObj));
+ pDlg->Execute();
+ return 0;
+}
+
+void SAL_CALL IFrameObject::setTitle( const OUString& )
+{
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_sfx2_IFrameObject_get_implementation(
+ css::uno::XComponentContext *context,
+ css::uno::Sequence<css::uno::Any> const &arguments)
+{
+ return cppu::acquire(new IFrameObject(context, arguments));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/new.cxx b/sfx2/source/doc/new.cxx
new file mode 100644
index 0000000000..a4d2153f66
--- /dev/null
+++ b/sfx2/source/doc/new.cxx
@@ -0,0 +1,350 @@
+/* -*- 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/file.hxx>
+#include <sfx2/new.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/outdev.hxx>
+#include <svl/itemset.hxx>
+#include <svl/eitem.hxx>
+#include <svtools/sfxecode.hxx>
+#include <svtools/ehdl.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/debug.hxx>
+
+#include <sfx2/strings.hrc>
+#include <sfx2/sfxsids.hrc>
+#include <sfx2/app.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/sfxresid.hxx>
+#include <sfx2/docfile.hxx>
+#include <preview.hxx>
+#include <unotools/viewoptions.hxx>
+
+void SfxPreviewWin_Impl::SetObjectShell(SfxObjectShell const * pObj)
+{
+ std::shared_ptr<GDIMetaFile> xFile = pObj
+ ? pObj->GetPreviewMetaFile()
+ : std::shared_ptr<GDIMetaFile>();
+ xMetaFile = xFile;
+ Invalidate();
+}
+
+SfxPreviewWin_Impl::SfxPreviewWin_Impl()
+{
+}
+
+void SfxPreviewWin_Impl::ImpPaint(vcl::RenderContext& rRenderContext, GDIMetaFile* pFile)
+{
+ rRenderContext.SetLineColor();
+ rRenderContext.SetFillColor(COL_LIGHTGRAY);
+ rRenderContext.DrawRect(tools::Rectangle(Point(0,0), rRenderContext.GetOutputSize()));
+
+ Size aTmpSize = pFile ? pFile->GetPrefSize() : Size(1, 1);
+ DBG_ASSERT(!aTmpSize.IsEmpty(), "size of first page is 0, override GetFirstPageSize or set visible-area!");
+
+#define FRAME 4
+
+ tools::Long nWidth = rRenderContext.GetOutputSize().Width() - 2 * FRAME;
+ tools::Long nHeight = rRenderContext.GetOutputSize().Height() - 2 * FRAME;
+ if (nWidth <= 0 || nHeight <= 0)
+ return;
+
+ double dRatio = aTmpSize.Height() ? (double(aTmpSize.Width()) / aTmpSize.Height()) : 1;
+ double dRatioPreV = double(nWidth) / nHeight;
+ Size aSize;
+ Point aPoint;
+ if (dRatio > dRatioPreV)
+ {
+ aSize = Size(nWidth, sal_uInt16(nWidth / dRatio));
+ aPoint = Point(0, sal_uInt16((nHeight - aSize.Height()) / 2));
+ }
+ else
+ {
+ aSize = Size(sal_uInt16(nHeight * dRatio), nHeight);
+ aPoint = Point(sal_uInt16((nWidth - aSize.Width()) / 2), 0);
+ }
+ Point bPoint = Point(nWidth, nHeight) - aPoint;
+
+ if (pFile)
+ {
+ rRenderContext.SetLineColor(COL_BLACK);
+ rRenderContext.SetFillColor(COL_WHITE);
+ rRenderContext.DrawRect(tools::Rectangle(aPoint + Point(FRAME, FRAME), bPoint + Point(FRAME, FRAME)));
+ pFile->WindStart();
+ pFile->Play(rRenderContext, aPoint + Point(FRAME, FRAME), aSize);
+ }
+}
+
+void SfxPreviewWin_Impl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
+{
+ ImpPaint(rRenderContext, xMetaFile.get());
+}
+
+IMPL_LINK_NOARG(SfxNewFileDialog, Update, Timer*, void)
+{
+ if (m_xDocShell.Is())
+ {
+ if (m_xDocShell->GetProgress())
+ return;
+ m_xDocShell.Clear();
+ }
+
+ const sal_uInt16 nEntry = GetSelectedTemplatePos();
+ if (!nEntry)
+ {
+ m_xPreviewController->Invalidate();
+ m_xPreviewController->SetObjectShell(nullptr);
+ return;
+ }
+
+ if (!m_xMoreBt->get_expanded() || (m_nFlags != SfxNewFileDialogMode::Preview))
+ return;
+
+ OUString aFileName = m_aTemplates.GetPath(m_xRegionLb->get_selected_index(), nEntry - 1);
+ INetURLObject aTestObj(aFileName);
+ if (aTestObj.GetProtocol() == INetProtocol::NotValid)
+ {
+ // temp. fix until Templates are managed by UCB compatible service
+ // does NOT work with locally cached components !
+ OUString aTemp;
+ osl::FileBase::getFileURLFromSystemPath( aFileName, aTemp );
+ aFileName = aTemp;
+ }
+
+ INetURLObject aObj(aFileName);
+ for (SfxObjectShell* pTmp = SfxObjectShell::GetFirst(); pTmp; pTmp = SfxObjectShell::GetNext(*pTmp))
+ {
+ //! fsys bug op==
+ if (pTmp->GetMedium())
+ // ??? HasName() MM
+ if (INetURLObject( pTmp->GetMedium()->GetName() ) == aObj)
+ {
+ m_xDocShell = pTmp;
+ break;
+ }
+ }
+
+ if (!m_xDocShell.Is())
+ {
+ SfxErrorContext eEC(ERRCTX_SFX_LOADTEMPLATE, m_xDialog.get());
+ SfxApplication *pSfxApp = SfxGetpApp();
+ std::unique_ptr<SfxItemSet> pSet(new SfxAllItemSet(pSfxApp->GetPool()));
+ pSet->Put(SfxBoolItem(SID_TEMPLATE, true));
+ pSet->Put(SfxBoolItem(SID_PREVIEW, true));
+ ErrCodeMsg lErr = pSfxApp->LoadTemplate(m_xDocShell, aFileName, std::move(pSet));
+ if (lErr)
+ ErrorHandler::HandleError(lErr);
+ if (!m_xDocShell.Is())
+ {
+ m_xPreviewController->SetObjectShell(nullptr);
+ return;
+ }
+ }
+
+ m_xPreviewController->SetObjectShell(m_xDocShell);
+}
+
+IMPL_LINK( SfxNewFileDialog, RegionSelect, weld::TreeView&, rBox, void )
+{
+ if (m_xDocShell.Is() && m_xDocShell->GetProgress())
+ return;
+
+ const sal_uInt16 nRegion = rBox.get_selected_index();
+ const sal_uInt16 nCount = m_aTemplates.GetRegionCount() ? m_aTemplates.GetCount(nRegion): 0;
+ m_xTemplateLb->freeze();
+ m_xTemplateLb->clear();
+ OUString aSel = m_xRegionLb->get_selected_text();
+ sal_Int32 nc = aSel.indexOf('(');
+ if (nc != -1 && nc != 0)
+ aSel = aSel.replaceAt(nc-1, 1, u"");
+ if ( aSel.compareToIgnoreAsciiCase( SfxResId(STR_STANDARD) ) == 0 )
+ m_xTemplateLb->append_text(SfxResId(STR_NONE));
+ for (sal_uInt16 i = 0; i < nCount; ++i)
+ m_xTemplateLb->append_text(m_aTemplates.GetName(nRegion, i));
+ m_xTemplateLb->thaw();
+ if (nCount)
+ m_xTemplateLb->select(0);
+ TemplateSelect(*m_xTemplateLb);
+}
+
+IMPL_LINK_NOARG(SfxNewFileDialog, Expand, weld::Expander&, void)
+{
+ TemplateSelect(*m_xTemplateLb);
+}
+
+IMPL_LINK_NOARG(SfxNewFileDialog, TemplateSelect, weld::TreeView&, void)
+{
+ // Still loading
+ if (m_xDocShell && m_xDocShell->GetProgress())
+ return;
+
+ if (!m_xMoreBt->get_expanded())
+ {
+ // Dialog is not opened
+ return;
+ }
+
+ m_aPrevIdle.Start();
+}
+
+IMPL_LINK_NOARG( SfxNewFileDialog, DoubleClick, weld::TreeView&, bool )
+{
+ // Still loading
+ if (!m_xDocShell.Is() || !m_xDocShell->GetProgress())
+ m_xDialog->response(RET_OK);
+ return true;
+}
+
+sal_uInt16 SfxNewFileDialog::GetSelectedTemplatePos() const
+{
+ int nEntry = m_xTemplateLb->get_selected_index();
+ if (nEntry == -1)
+ return 0;
+ OUString aSel = m_xRegionLb->get_selected_text();
+ sal_Int32 nc = aSel.indexOf('(');
+ if (nc != -1 && nc != 0)
+ aSel = aSel.replaceAt(nc-1, 1, u"");
+ if ( aSel.compareToIgnoreAsciiCase(SfxResId(STR_STANDARD)) != 0 )
+ nEntry++;
+ return nEntry;
+}
+
+SfxNewFileDialog::SfxNewFileDialog(weld::Window *pParent, SfxNewFileDialogMode nFlags)
+ : SfxDialogController(pParent, "sfx/ui/loadtemplatedialog.ui", "LoadTemplateDialog")
+ , m_aPrevIdle("SfxNewFileDialog m_aPrevIdle")
+ , m_nFlags(nFlags)
+ , m_xPreviewController(new SfxPreviewWin_Impl)
+ , m_xRegionLb(m_xBuilder->weld_tree_view("categories"))
+ , m_xTemplateLb(m_xBuilder->weld_tree_view("templates"))
+ , m_xTextStyleCB(m_xBuilder->weld_check_button("text"))
+ , m_xFrameStyleCB(m_xBuilder->weld_check_button("frame"))
+ , m_xPageStyleCB(m_xBuilder->weld_check_button("pages"))
+ , m_xNumStyleCB(m_xBuilder->weld_check_button("numbering"))
+ , m_xMergeStyleCB(m_xBuilder->weld_check_button("overwrite"))
+ , m_xLoadFilePB(m_xBuilder->weld_button("fromfile"))
+ , m_xMoreBt(m_xBuilder->weld_expander("expander"))
+ , m_xPreviewWin(new weld::CustomWeld(*m_xBuilder, "image", *m_xPreviewController))
+ , m_xAltTitleFt(m_xBuilder->weld_label("alttitle"))
+{
+ const int nWidth = m_xRegionLb->get_approximate_digit_width() * 32;
+ const int nHeight = m_xRegionLb->get_height_rows(8);
+ m_xRegionLb->set_size_request(nWidth, nHeight);
+ m_xTemplateLb->set_size_request(nWidth, nHeight);
+ m_xPreviewWin->set_size_request(nWidth, nWidth);
+
+ if (nFlags == SfxNewFileDialogMode::NONE)
+ m_xMoreBt->hide();
+ else if(SfxNewFileDialogMode::LoadTemplate == nFlags)
+ {
+ m_xLoadFilePB->show();
+ m_xTextStyleCB->show();
+ m_xFrameStyleCB->show();
+ m_xPageStyleCB->show();
+ m_xNumStyleCB->show();
+ m_xMergeStyleCB->show();
+ m_xMoreBt->hide();
+ m_xTextStyleCB->set_active(true);
+ m_xDialog->set_title(m_xAltTitleFt->get_label());
+ }
+ else
+ {
+ m_xMoreBt->connect_expanded(LINK(this, SfxNewFileDialog, Expand));
+ m_xPreviewWin->show();
+ }
+
+ OUString sExtraData;
+ SvtViewOptions aDlgOpt(EViewType::Dialog, m_xDialog->get_help_id());
+ if (aDlgOpt.Exists())
+ {
+ css::uno::Any aUserItem = aDlgOpt.GetUserItem("UserItem");
+ aUserItem >>= sExtraData;
+ }
+
+ bool bExpand = !sExtraData.isEmpty() && sExtraData[0] == 'Y';
+ m_xMoreBt->set_expanded(bExpand && (nFlags != SfxNewFileDialogMode::NONE));
+
+ m_xTemplateLb->connect_changed(LINK(this, SfxNewFileDialog, TemplateSelect));
+ m_xTemplateLb->connect_row_activated(LINK(this, SfxNewFileDialog, DoubleClick));
+
+ // update the template configuration if necessary
+ {
+ weld::WaitObject aWaitCursor(m_xDialog.get());
+ m_aTemplates.Update();
+ }
+ // fill the list boxes
+ const sal_uInt16 nCount = m_aTemplates.GetRegionCount();
+ if (nCount)
+ {
+ for(sal_uInt16 i = 0; i < nCount; ++i)
+ m_xRegionLb->append_text(m_aTemplates.GetFullRegionName(i));
+ m_xRegionLb->connect_changed(LINK(this, SfxNewFileDialog, RegionSelect));
+ }
+
+ m_aPrevIdle.SetPriority( TaskPriority::LOWEST );
+ m_aPrevIdle.SetInvokeHandler( LINK( this, SfxNewFileDialog, Update));
+
+ m_xRegionLb->select(0);
+ RegionSelect(*m_xRegionLb);
+}
+
+SfxNewFileDialog::~SfxNewFileDialog()
+{
+ SvtViewOptions aDlgOpt(EViewType::Dialog, m_xDialog->get_help_id());
+ aDlgOpt.SetUserItem("UserItem", css::uno::Any(m_xMoreBt->get_expanded() ? OUString("Y") : OUString("N")));
+}
+
+bool SfxNewFileDialog::IsTemplate() const
+{
+ return GetSelectedTemplatePos()!=0;
+}
+
+OUString SfxNewFileDialog::GetTemplateFileName() const
+{
+ if (!IsTemplate() || !m_aTemplates.GetRegionCount())
+ return OUString();
+ return m_aTemplates.GetPath(m_xRegionLb->get_selected_index(),
+ GetSelectedTemplatePos()-1);
+}
+
+SfxTemplateFlags SfxNewFileDialog::GetTemplateFlags()const
+{
+ SfxTemplateFlags nRet = m_xTextStyleCB->get_active() ? SfxTemplateFlags::LOAD_TEXT_STYLES : SfxTemplateFlags::NONE;
+ if(m_xFrameStyleCB->get_active())
+ nRet |= SfxTemplateFlags::LOAD_FRAME_STYLES;
+ if(m_xPageStyleCB->get_active())
+ nRet |= SfxTemplateFlags::LOAD_PAGE_STYLES;
+ if(m_xNumStyleCB->get_active())
+ nRet |= SfxTemplateFlags::LOAD_NUM_STYLES;
+ if(m_xMergeStyleCB->get_active())
+ nRet |= SfxTemplateFlags::MERGE_STYLES;
+ return nRet;
+}
+
+void SfxNewFileDialog::SetTemplateFlags(SfxTemplateFlags nSet)
+{
+ m_xTextStyleCB->set_active( bool(nSet & SfxTemplateFlags::LOAD_TEXT_STYLES ));
+ m_xFrameStyleCB->set_active( bool(nSet & SfxTemplateFlags::LOAD_FRAME_STYLES));
+ m_xPageStyleCB->set_active( bool(nSet & SfxTemplateFlags::LOAD_PAGE_STYLES ));
+ m_xNumStyleCB->set_active( bool(nSet & SfxTemplateFlags::LOAD_NUM_STYLES ));
+ m_xMergeStyleCB->set_active( bool(nSet & SfxTemplateFlags::MERGE_STYLES ));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/objcont.cxx b/sfx2/source/doc/objcont.cxx
new file mode 100644
index 0000000000..156e1aed32
--- /dev/null
+++ b/sfx2/source/doc/objcont.cxx
@@ -0,0 +1,727 @@
+/* -*- 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 <com/sun/star/uno/Reference.hxx>
+
+#include <com/sun/star/document/DocumentProperties.hpp>
+#include <com/sun/star/document/XDocumentProperties.hpp>
+#include <com/sun/star/document/UpdateDocMode.hpp>
+#include <comphelper/fileurl.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <svl/style.hxx>
+
+#include <svl/intitem.hxx>
+#include <svl/ctloptions.hxx>
+#include <comphelper/processfactory.hxx>
+#include <unotools/securityoptions.hxx>
+#include <tools/datetime.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/helpers.hxx>
+#include <rtl/uri.hxx>
+
+#include <unotools/useroptions.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/gdimtf.hxx>
+
+#include <sfx2/app.hxx>
+#include <sfx2/dinfdlg.hxx>
+#include <sfx2/sfxresid.hxx>
+#include <appdata.hxx>
+#include <sfx2/docfac.hxx>
+#include <sfx2/viewsh.hxx>
+#include <sfx2/objsh.hxx>
+#include <objshimp.hxx>
+#include <sfx2/printer.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/doctempl.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <sfx2/strings.hrc>
+#include <sfx2/docfile.hxx>
+#include <sfx2/docfilt.hxx>
+#include <memory>
+#include <helpids.h>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+
+static
+bool operator> (const util::DateTime& i_rLeft, const util::DateTime& i_rRight)
+{
+ if ( i_rLeft.Year != i_rRight.Year )
+ return i_rLeft.Year > i_rRight.Year;
+
+ if ( i_rLeft.Month != i_rRight.Month )
+ return i_rLeft.Month > i_rRight.Month;
+
+ if ( i_rLeft.Day != i_rRight.Day )
+ return i_rLeft.Day > i_rRight.Day;
+
+ if ( i_rLeft.Hours != i_rRight.Hours )
+ return i_rLeft.Hours > i_rRight.Hours;
+
+ if ( i_rLeft.Minutes != i_rRight.Minutes )
+ return i_rLeft.Minutes > i_rRight.Minutes;
+
+ if ( i_rLeft.Seconds != i_rRight.Seconds )
+ return i_rLeft.Seconds > i_rRight.Seconds;
+
+ if ( i_rLeft.NanoSeconds != i_rRight.NanoSeconds )
+ return i_rLeft.NanoSeconds > i_rRight.NanoSeconds;
+
+ return false;
+}
+
+std::shared_ptr<GDIMetaFile>
+SfxObjectShell::GetPreviewMetaFile( bool bFullContent ) const
+{
+ auto xFile = std::make_shared<GDIMetaFile>();
+ ScopedVclPtrInstance< VirtualDevice > pDevice;
+ pDevice->EnableOutput( false );
+ if(!CreatePreview_Impl(bFullContent, pDevice, xFile.get()))
+ return std::shared_ptr<GDIMetaFile>();
+ return xFile;
+}
+
+BitmapEx SfxObjectShell::GetPreviewBitmap() const
+{
+ ScopedVclPtrInstance< VirtualDevice > pDevice;
+ pDevice->SetAntialiasing(AntialiasingFlags::Enable | pDevice->GetAntialiasing());
+ if(!CreatePreview_Impl(/*bFullContent*/false, pDevice, nullptr))
+ return BitmapEx();
+ Size size = pDevice->GetOutputSizePixel();
+ BitmapEx aBitmap = pDevice->GetBitmapEx( Point(), size);
+ // Scale down the image to the desired size from the 4*size from CreatePreview_Impl().
+ size = Size( size.Width() / 4, size.Height() / 4 );
+ aBitmap.Scale(size, BmpScaleFlag::BestQuality);
+ if (!aBitmap.IsEmpty())
+ aBitmap.Convert(BmpConversion::N24Bit);
+ return aBitmap;
+}
+
+bool SfxObjectShell::CreatePreview_Impl( bool bFullContent, VirtualDevice* pDevice, GDIMetaFile* pFile) const
+{
+ // DoDraw can only be called when no printing is done, otherwise
+ // the printer may be turned off
+ SfxViewFrame *pFrame = SfxViewFrame::GetFirst( this );
+ if ( pFrame && pFrame->GetViewShell() &&
+ pFrame->GetViewShell()->GetPrinter() &&
+ pFrame->GetViewShell()->GetPrinter()->IsPrinting() )
+ return false;
+
+ MapMode aMode( GetMapUnit() );
+ Size aTmpSize;
+ sal_Int8 nAspect;
+ if ( bFullContent )
+ {
+ nAspect = ASPECT_CONTENT;
+ aTmpSize = GetVisArea( nAspect ).GetSize();
+ }
+ else
+ {
+ nAspect = ASPECT_THUMBNAIL;
+ aTmpSize = GetFirstPageSize();
+ }
+
+ DBG_ASSERT( !aTmpSize.IsEmpty(),
+ "size of first page is 0, override GetFirstPageSize or set visible-area!" );
+
+ if(pFile)
+ {
+ pDevice->SetMapMode( aMode );
+ pFile->SetPrefMapMode( aMode );
+ pFile->SetPrefSize( aTmpSize );
+ pFile->Record( pDevice );
+ }
+ else
+ {
+ // Use pixel size, that's also what DoDraw() requires in this case,
+ // despite the metafile case (needlessly?) setting mapmode.
+ Size aSizePix = pDevice->LogicToPixel( aTmpSize, aMode );
+ // Code based on GDIMetaFile::CreateThumbnail().
+ sal_uInt32 nMaximumExtent = 512;
+ // determine size that has the same aspect ratio as image size and
+ // fits into the rectangle determined by nMaximumExtent
+ if ( aSizePix.Width() && aSizePix.Height()
+ && ( sal::static_int_cast< tools::ULong >(aSizePix.Width()) >
+ nMaximumExtent ||
+ sal::static_int_cast< tools::ULong >(aSizePix.Height()) >
+ nMaximumExtent ) )
+ {
+ double fWH = static_cast< double >( aSizePix.Width() ) / aSizePix.Height();
+ if ( fWH <= 1.0 )
+ {
+ aSizePix.setWidth( FRound( nMaximumExtent * fWH ) );
+ aSizePix.setHeight( nMaximumExtent );
+ }
+ else
+ {
+ aSizePix.setWidth( nMaximumExtent );
+ aSizePix.setHeight( FRound( nMaximumExtent / fWH ) );
+ }
+ }
+ // do it 4x larger to be able to scale it down & get beautiful antialias
+ aTmpSize = Size( aSizePix.Width() * 4, aSizePix.Height() * 4 );
+ pDevice->SetOutputSizePixel( aTmpSize );
+ }
+
+ LanguageType eLang;
+ if ( SvtCTLOptions::NUMERALS_HINDI == SvtCTLOptions::GetCTLTextNumerals() )
+ eLang = LANGUAGE_ARABIC_SAUDI_ARABIA;
+ else if ( SvtCTLOptions::NUMERALS_ARABIC == SvtCTLOptions::GetCTLTextNumerals() )
+ eLang = LANGUAGE_ENGLISH;
+ else
+ eLang = Application::GetSettings().GetLanguageTag().getLanguageType();
+
+ pDevice->SetDigitLanguage( eLang );
+
+ const_cast<SfxObjectShell*>(this)->DoDraw( pDevice, Point(0,0), aTmpSize, JobSetup(), nAspect );
+
+ if(pFile)
+ pFile->Stop();
+
+ return true;
+}
+
+
+void SfxObjectShell::UpdateDocInfoForSave()
+{
+ uno::Reference<document::XDocumentProperties> xDocProps(getDocProperties());
+
+ // clear user data if recommend (see 'Tools - Options - LibreOffice - Security')
+ if ( SvtSecurityOptions::IsOptionSet(
+ SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo ) && !SvtSecurityOptions::IsOptionSet(
+ SvtSecurityOptions::EOption::DocWarnKeepDocUserInfo))
+ {
+ xDocProps->resetUserData( OUString() );
+ }
+ else if ( IsModified() )
+ {
+ const OUString aUserName = SvtUserOptions().GetFullName();
+ if ( !IsUseUserData() )
+ {
+ // remove all data pointing to the current user
+ if (xDocProps->getAuthor() == aUserName) {
+ xDocProps->setAuthor( OUString() );
+ }
+ xDocProps->setModifiedBy( OUString() );
+ if (xDocProps->getPrintedBy() == aUserName) {
+ xDocProps->setPrintedBy( OUString() );
+ }
+ }
+ else
+ {
+ // update ModificationAuthor, revision and editing time
+ ::DateTime now( ::DateTime::SYSTEM );
+ xDocProps->setModificationDate( now.GetUNODateTime() );
+ xDocProps->setModifiedBy( aUserName );
+ UpdateTime_Impl( xDocProps );
+ }
+ }
+}
+
+
+static void
+lcl_add(util::Duration & rDur, tools::Time const& rTime)
+{
+ // here we don't care about overflow: rDur is converted back to seconds
+ // anyway, and tools::Time cannot store more than ~4000 hours
+ rDur.Hours += rTime.GetHour();
+ rDur.Minutes += rTime.GetMin();
+ rDur.Seconds += rTime.GetSec();
+}
+
+// Update the processing time
+void SfxObjectShell::UpdateTime_Impl(
+ const uno::Reference<document::XDocumentProperties> & i_xDocProps)
+{
+ // Get old time from documentinfo
+ const sal_Int32 secs = i_xDocProps->getEditingDuration();
+ util::Duration editDuration(false, 0, 0, 0,
+ secs/3600, (secs%3600)/60, secs%60, 0);
+
+ // Initialize some local member! It's necessary for follow operations!
+ DateTime aNow( DateTime::SYSTEM ); // Date and time at current moment
+ tools::Time n24Time (24,0,0,0) ; // Time-value for 24 hours - see follow calculation
+ tools::Time nAddTime (0) ; // Value to add on aOldTime
+
+ // Save impossible cases!
+ // User has changed time to the past between last editing and now... it's not possible!!!
+ DBG_ASSERT( !(aNow.GetDate()<pImpl->nTime.GetDate()), "Timestamp of last change is in the past!?..." );
+
+ // Do the follow only, if user has NOT changed time to the past.
+ // Else add a time of 0 to aOldTime... !!!
+ if (aNow.GetDate()>=pImpl->nTime.GetDate())
+ {
+ // Count of days between now and last editing
+ sal_Int32 nDays = aNow.GetSecFromDateTime(Date(pImpl->nTime.GetDate()))/86400 ;
+
+ if (nDays==0)
+ {
+ // If no day between now and last editing - calculate time directly.
+ nAddTime = static_cast<const tools::Time&>(aNow) - static_cast<const tools::Time&>(pImpl->nTime);
+ }
+ else if (nDays<=31)
+ {
+ // If time of working without save greater than 1 month (!)...
+ // we add 0 to aOldTime!
+
+ // If 1 or up to 31 days between now and last editing - calculate time indirectly.
+ // nAddTime = (24h - nTime) + (nDays * 24h) + aNow
+ --nDays;
+ nAddTime = tools::Time( nDays * n24Time.GetTime());
+ nAddTime += n24Time-static_cast<const tools::Time&>(pImpl->nTime);
+ nAddTime += aNow ;
+ }
+
+ lcl_add(editDuration, nAddTime);
+ }
+
+ pImpl->nTime = aNow;
+ try {
+ const sal_Int32 newSecs( (editDuration.Hours*3600)
+ + (editDuration.Minutes*60) + editDuration.Seconds);
+ i_xDocProps->setEditingDuration(newSecs);
+ i_xDocProps->setEditingCycles(i_xDocProps->getEditingCycles() + 1);
+ }
+ catch (const lang::IllegalArgumentException &)
+ {
+ // ignore overflow
+ }
+}
+
+std::shared_ptr<SfxDocumentInfoDialog> SfxObjectShell::CreateDocumentInfoDialog(weld::Window* pParent,
+ const SfxItemSet& rSet)
+{
+ return std::make_shared<SfxDocumentInfoDialog>(pParent, rSet);
+}
+
+std::optional<NamedColor> SfxObjectShell::GetRecentColor(sal_uInt16 nSlotId)
+{
+ auto it = pImpl->m_aRecentColors.find(nSlotId);
+ if (it != pImpl->m_aRecentColors.end())
+ return it->second;
+
+ return std::nullopt;
+}
+
+void SfxObjectShell::SetRecentColor(sal_uInt16 nSlotId, const NamedColor& rColor)
+{
+ pImpl->m_aRecentColors[nSlotId] = rColor;
+ Broadcast(SfxHint(SfxHintId::ColorsChanged));
+}
+
+std::set<Color> SfxObjectShell::GetDocColors()
+{
+ std::set<Color> empty;
+ return empty;
+}
+
+std::shared_ptr<model::ColorSet> SfxObjectShell::GetThemeColors() { return {}; }
+
+sfx::AccessibilityIssueCollection SfxObjectShell::runAccessibilityCheck()
+{
+ sfx::AccessibilityIssueCollection aCollection;
+ return aCollection;
+}
+
+SfxStyleSheetBasePool* SfxObjectShell::GetStyleSheetPool()
+{
+ return nullptr;
+}
+
+namespace {
+
+struct Styles_Impl
+{
+ SfxStyleSheetBase *pSource;
+ SfxStyleSheetBase *pDest;
+};
+
+}
+
+void SfxObjectShell::LoadStyles
+(
+ SfxObjectShell &rSource /* the document template from which
+ the styles are to be loaded */
+)
+
+/* [Description]
+
+ This method is called by the SFx if styles are to be loaded from a template.
+ Existing styles are in this case overwritten. The document must then be
+ re-formatted. Therefore, applications usually override this method
+ and call the implementation in the base class.
+*/
+
+{
+ SfxStyleSheetBasePool *pSourcePool = rSource.GetStyleSheetPool();
+ DBG_ASSERT(pSourcePool, "Source-DocumentShell without StyleSheetPool");
+ SfxStyleSheetBasePool *pMyPool = GetStyleSheetPool();
+ DBG_ASSERT(pMyPool, "Dest-DocumentShell without StyleSheetPool");
+ auto xIter = pSourcePool->CreateIterator(SfxStyleFamily::All);
+ std::unique_ptr<Styles_Impl[]> pFound(new Styles_Impl[xIter->Count()]);
+ sal_uInt16 nFound = 0;
+
+ SfxStyleSheetBase *pSource = xIter->First();
+ while ( pSource )
+ {
+ SfxStyleSheetBase *pDest =
+ pMyPool->Find( pSource->GetName(), pSource->GetFamily() );
+ if ( !pDest )
+ {
+ pDest = &pMyPool->Make( pSource->GetName(),
+ pSource->GetFamily(), pSource->GetMask());
+ // Setting of parents, the next style
+ }
+ pFound[nFound].pSource = pSource;
+ pFound[nFound].pDest = pDest;
+ ++nFound;
+ pSource = xIter->Next();
+ }
+
+ for ( sal_uInt16 i = 0; i < nFound; ++i )
+ {
+ pFound[i].pDest->GetItemSet().PutExtended(pFound[i].pSource->GetItemSet(), SfxItemState::DONTCARE, SfxItemState::DEFAULT);
+ if(pFound[i].pSource->HasParentSupport())
+ pFound[i].pDest->SetParent(pFound[i].pSource->GetParent());
+ if(pFound[i].pSource->HasFollowSupport())
+ pFound[i].pDest->SetFollow(pFound[i].pSource->GetParent());
+ }
+}
+
+sfx2::StyleManager* SfxObjectShell::GetStyleManager()
+{
+ return nullptr;
+}
+
+namespace
+{
+ class QueryTemplateBox
+ {
+ private:
+ std::unique_ptr<weld::MessageDialog> m_xQueryBox;
+ public:
+ QueryTemplateBox(weld::Window* pParent, const OUString& rMessage)
+ : m_xQueryBox(Application::CreateMessageDialog(pParent, VclMessageType::Question, VclButtonsType::NONE, rMessage))
+ {
+ m_xQueryBox->add_button(SfxResId(STR_QRYTEMPL_UPDATE_BTN), RET_YES);
+ m_xQueryBox->add_button(SfxResId(STR_QRYTEMPL_KEEP_BTN), RET_NO);
+ m_xQueryBox->set_default_response(RET_YES);
+ m_xQueryBox->set_help_id(HID_QUERY_LOAD_TEMPLATE);
+ }
+ short run() { return m_xQueryBox->run(); }
+ };
+}
+
+void SfxObjectShell::UpdateFromTemplate_Impl( )
+
+/* [Description]
+
+ This internal method checks whether the document was created from a
+ template, and if this is newer than the document. If this is the case,
+ the user is asked if the Templates (StyleSheets) should be updated.
+ If this is answered positively, the StyleSheets are updated.
+*/
+
+{
+ // Storage-medium?
+ SfxMedium *pFile = GetMedium();
+ DBG_ASSERT( pFile, "cannot UpdateFromTemplate without medium" );
+ if ( !pFile )
+ return;
+
+ if ( !comphelper::isFileUrl( pFile->GetName() ) )
+ // update only for documents loaded from the local file system
+ return;
+
+ // tdf#113935 - do not remove this line - somehow, it makes the process
+ // of switching from viewing a read-only document to opening it in writable
+ // mode much faster.
+ uno::Reference< embed::XStorage > xDocStor = pFile->GetStorage(false);
+
+ // only for own storage formats
+ if ( !pFile->GetFilter() || !pFile->GetFilter()->IsOwnFormat() )
+ return;
+
+ const SfxUInt16Item* pUpdateDocItem = pFile->GetItemSet().GetItem(SID_UPDATEDOCMODE, false);
+ sal_Int16 bCanUpdateFromTemplate = pUpdateDocItem ? pUpdateDocItem->GetValue() : document::UpdateDocMode::NO_UPDATE;
+
+ // created from template?
+ uno::Reference<document::XDocumentProperties> xDocProps(getDocProperties());
+ const OUString aTemplName( xDocProps->getTemplateName() );
+ OUString aTemplURL( xDocProps->getTemplateURL() );
+ OUString aFoundName;
+
+ if ( !aTemplName.isEmpty() || (!aTemplURL.isEmpty() && !IsReadOnly()) )
+ {
+ // try to locate template, first using filename this must be done
+ // because writer global document uses this "great" idea to manage
+ // the templates of all parts in the master document but it is NOT
+ // an error if the template filename points not to a valid file
+ SfxDocumentTemplates aTempl;
+ if (!aTemplURL.isEmpty())
+ {
+ try {
+ aFoundName = ::rtl::Uri::convertRelToAbs(GetMedium()->GetName(),
+ aTemplURL);
+ } catch (::rtl::MalformedUriException const&) {
+ assert(false); // don't think that's supposed to happen?
+ }
+ }
+
+ if( aFoundName.isEmpty() && !aTemplName.isEmpty() )
+ // if the template filename did not lead to success,
+ // try to get a file name for the logical template name
+ aTempl.GetFull( u"", aTemplName, aFoundName );
+ }
+
+ if ( aFoundName.isEmpty() )
+ return;
+
+ // check existence of template storage
+ aTemplURL = aFoundName;
+
+ // should the document checked against changes in the template ?
+ if ( !IsQueryLoadTemplate() )
+ return;
+
+ bool bLoad = false;
+
+ // load document properties of template
+ bool bOK = false;
+ util::DateTime aTemplDate;
+ try
+ {
+ Reference<document::XDocumentProperties> const
+ xTemplateDocProps( document::DocumentProperties::create(
+ ::comphelper::getProcessComponentContext()));
+ xTemplateDocProps->loadFromMedium(aTemplURL,
+ Sequence<beans::PropertyValue>());
+ aTemplDate = xTemplateDocProps->getModificationDate();
+ bOK = true;
+ }
+ catch (const Exception&)
+ {
+ TOOLS_INFO_EXCEPTION("sfx.doc", "");
+ }
+
+ // if modify date was read successfully
+ if ( bOK )
+ {
+ // compare modify data of template with the last check date of the document
+ const util::DateTime aInfoDate( xDocProps->getTemplateDate() );
+ if ( aTemplDate > aInfoDate )
+ {
+ // ask user
+ if( bCanUpdateFromTemplate == document::UpdateDocMode::QUIET_UPDATE
+ || bCanUpdateFromTemplate == document::UpdateDocMode::FULL_UPDATE )
+ bLoad = true;
+ else if ( bCanUpdateFromTemplate == document::UpdateDocMode::ACCORDING_TO_CONFIG )
+ {
+ const OUString sMessage( SfxResId(STR_QRYTEMPL_MESSAGE).replaceAll( "$(ARG1)", aTemplName ) );
+ QueryTemplateBox aBox(Application::GetFrameWeld(GetDialogParent()), sMessage);
+ if (RET_YES == aBox.run())
+ bLoad = true;
+ }
+
+ if( !bLoad )
+ {
+ // user refuses, so don't ask again for this document
+ SetQueryLoadTemplate(false);
+ SetModified();
+ }
+ }
+ }
+
+ if ( !bLoad )
+ return;
+
+ // styles should be updated, create document in organizer mode to read in the styles
+ //TODO: testen!
+ SfxObjectShellLock xTemplDoc = CreateObjectByFactoryName( GetFactory().GetFactoryName(), SfxObjectCreateMode::ORGANIZER );
+ xTemplDoc->DoInitNew();
+
+ // TODO/MBA: do we need a BaseURL? Then LoadFrom must be extended!
+ //xTemplDoc->SetBaseURL( aFoundName );
+
+ // TODO/LATER: make sure that we don't use binary templates!
+ SfxMedium aMedium( aFoundName, StreamMode::STD_READ );
+ if ( xTemplDoc->LoadFrom( aMedium ) )
+ {
+ // transfer styles from xTemplDoc to this document
+ // TODO/MBA: make sure that no BaseURL is needed in *this* document
+ LoadStyles(*xTemplDoc);
+
+ // remember date/time of check
+ xDocProps->setTemplateDate(aTemplDate);
+ // TODO/LATER: new functionality to store document info is required ( didn't work for SO7 XML format )
+ }
+}
+
+bool SfxObjectShell::IsHelpDocument() const
+{
+ std::shared_ptr<const SfxFilter> pFilter = GetMedium()->GetFilter();
+ return (pFilter && pFilter->GetFilterName() == "writer_web_HTML_help");
+}
+
+void SfxObjectShell::ResetFromTemplate( const OUString& rTemplateName, std::u16string_view rFileName )
+{
+ // only care about resetting this data for LibreOffice formats otherwise
+ if ( !IsOwnStorageFormat( *GetMedium()) )
+ return;
+
+ uno::Reference<document::XDocumentProperties> xDocProps(getDocProperties());
+ xDocProps->setTemplateURL( OUString() );
+ xDocProps->setTemplateName( OUString() );
+ xDocProps->setTemplateDate( util::DateTime() );
+ xDocProps->resetUserData( OUString() );
+
+ // TODO/REFACTOR:
+ // Title?
+
+ if( !comphelper::isFileUrl( rFileName ) )
+ return;
+
+ OUString aFoundName;
+ if( SfxGetpApp()->Get_Impl()->GetDocumentTemplates()->GetFull( u"", rTemplateName, aFoundName ) )
+ {
+ INetURLObject aObj( rFileName );
+ xDocProps->setTemplateURL( aObj.GetMainURL(INetURLObject::DecodeMechanism::ToIUri) );
+ xDocProps->setTemplateName( rTemplateName );
+
+ ::DateTime now( ::DateTime::SYSTEM );
+ xDocProps->setTemplateDate( now.GetUNODateTime() );
+
+ SetQueryLoadTemplate( true );
+ }
+}
+
+bool SfxObjectShell::IsQueryLoadTemplate() const
+{
+ return pImpl->bQueryLoadTemplate;
+}
+
+bool SfxObjectShell::IsUseUserData() const
+{
+ return pImpl->bUseUserData;
+}
+
+bool SfxObjectShell::IsUseThumbnailSave() const
+{
+ return pImpl->bUseThumbnailSave;
+}
+
+void SfxObjectShell::SetQueryLoadTemplate( bool bNew )
+{
+ if ( pImpl->bQueryLoadTemplate != bNew )
+ SetModified();
+ pImpl->bQueryLoadTemplate = bNew;
+}
+
+void SfxObjectShell::SetUseUserData( bool bNew )
+{
+ if ( pImpl->bUseUserData != bNew )
+ SetModified();
+ pImpl->bUseUserData = bNew;
+}
+
+void SfxObjectShell::SetUseThumbnailSave( bool _bNew )
+{
+ if ( pImpl->bUseThumbnailSave != _bNew )
+ SetModified();
+ pImpl->bUseThumbnailSave = _bNew;
+}
+
+bool SfxObjectShell::IsLoadReadonly() const
+{
+ return pImpl->bLoadReadonly;
+}
+
+bool SfxObjectShell::IsSaveVersionOnClose() const
+{
+ return pImpl->bSaveVersionOnClose;
+}
+
+void SfxObjectShell::SetLoadReadonly( bool bNew )
+{
+ if ( pImpl->bLoadReadonly != bNew )
+ SetModified();
+ pImpl->bLoadReadonly = bNew;
+}
+
+void SfxObjectShell::SetSaveVersionOnClose( bool bNew )
+{
+ if ( pImpl->bSaveVersionOnClose != bNew )
+ SetModified();
+ pImpl->bSaveVersionOnClose = bNew;
+}
+
+sal_uInt32 SfxObjectShell::GetModifyPasswordHash() const
+{
+ return pImpl->m_nModifyPasswordHash;
+}
+
+bool SfxObjectShell::SetModifyPasswordHash( sal_uInt32 nHash )
+{
+ if ( ( !IsReadOnly() && !IsReadOnlyUI() )
+ || !(pImpl->nFlagsInProgress & SfxLoadedFlags::MAINDOCUMENT ) )
+ {
+ // the hash can be changed only in editable documents,
+ // or during loading of document
+ pImpl->m_nModifyPasswordHash = nHash;
+ return true;
+ }
+
+ return false;
+}
+
+const uno::Sequence< beans::PropertyValue >& SfxObjectShell::GetModifyPasswordInfo() const
+{
+ return pImpl->m_aModifyPasswordInfo;
+}
+
+bool SfxObjectShell::SetModifyPasswordInfo( const uno::Sequence< beans::PropertyValue >& aInfo )
+{
+ if ( ( !IsReadOnly() && !IsReadOnlyUI() )
+ || !(pImpl->nFlagsInProgress & SfxLoadedFlags::MAINDOCUMENT ) )
+ {
+ // the hash can be changed only in editable documents,
+ // or during loading of document
+ pImpl->m_aModifyPasswordInfo = aInfo;
+ return true;
+ }
+
+ return false;
+}
+
+void SfxObjectShell::SetModifyPasswordEntered( bool bEntered )
+{
+ pImpl->m_bModifyPasswordEntered = bEntered;
+}
+
+bool SfxObjectShell::IsModifyPasswordEntered() const
+{
+ return pImpl->m_bModifyPasswordEntered;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/objembed.cxx b/sfx2/source/doc/objembed.cxx
new file mode 100644
index 0000000000..1be515a6e7
--- /dev/null
+++ b/sfx2/source/doc/objembed.cxx
@@ -0,0 +1,224 @@
+/* -*- 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 <sfx2/objsh.hxx>
+#include <sfx2/app.hxx>
+#include <objshimp.hxx>
+#include <sfx2/event.hxx>
+#include <sfx2/sfxbasemodel.hxx>
+
+#include <comphelper/fileformat.h>
+#include <tools/fract.hxx>
+#include <vcl/transfer.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/gdimtf.hxx>
+
+using namespace ::com::sun::star;
+
+
+Printer* SfxObjectShell::GetDocumentPrinter()
+{
+ SfxObjectShell* pParent = GetParentShell(GetModel());
+ if ( pParent )
+ return pParent->GetDocumentPrinter();
+ return nullptr;
+}
+
+
+OutputDevice* SfxObjectShell::GetDocumentRefDev()
+{
+ SfxObjectShell* pParent = GetParentShell(GetModel());
+ if ( pParent )
+ return pParent->GetDocumentRefDev();
+ return nullptr;
+}
+
+
+void SfxObjectShell::OnDocumentPrinterChanged( Printer* /*pNewPrinter*/ )
+{
+ // virtual method
+}
+
+
+tools::Rectangle SfxObjectShell::GetVisArea( sal_uInt16 nAspect ) const
+{
+ if( nAspect == ASPECT_CONTENT )
+ return pImpl->m_aVisArea;
+ else if( nAspect == ASPECT_THUMBNAIL )
+ {
+ tools::Rectangle aRect;
+ aRect.SetSize( OutputDevice::LogicToLogic( Size( 5000, 5000 ),
+ MapMode(MapUnit::Map100thMM), MapMode(GetMapUnit())));
+ return aRect;
+ }
+ return tools::Rectangle();
+}
+
+
+const tools::Rectangle& SfxObjectShell::GetVisArea() const
+{
+ pImpl->m_aVisArea = GetVisArea( ASPECT_CONTENT );
+ return pImpl->m_aVisArea;
+}
+
+
+void SfxObjectShell::SetVisArea( const tools::Rectangle & rVisArea )
+{
+ if( pImpl->m_aVisArea != rVisArea )
+ {
+ pImpl->m_aVisArea = rVisArea;
+ if ( GetCreateMode() == SfxObjectCreateMode::EMBEDDED )
+ {
+ if (IsEnableSetModified()
+ // Base forms use EMBEDDED but they actually live in their own
+ // frame - resizing that shouldn't set it to modified.
+ && pImpl->pBaseModel
+ && pImpl->pBaseModel->getIdentifier() != "com.sun.star.sdb.FormDesign")
+ {
+ SetModified();
+ }
+
+ SfxGetpApp()->NotifyEvent(SfxEventHint( SfxEventHintId::VisAreaChanged, GlobalEventConfig::GetEventName(GlobalEventId::VISAREACHANGED), this));
+ }
+ }
+}
+
+
+void SfxObjectShell::SetVisAreaSize( const Size & rVisSize )
+{
+ SetVisArea( tools::Rectangle( GetVisArea().TopLeft(), rVisSize ) );
+}
+
+
+MapUnit SfxObjectShell::GetMapUnit() const
+{
+ return pImpl->m_nMapUnit;
+}
+
+
+void SfxObjectShell::SetMapUnit( MapUnit nMapUnit )
+{
+ pImpl->m_nMapUnit = nMapUnit;
+}
+
+
+void SfxObjectShell::FillTransferableObjectDescriptor( TransferableObjectDescriptor& rDesc ) const
+{
+ SotClipboardFormatId nClipFormat;
+ FillClass( &rDesc.maClassName, &nClipFormat, &rDesc.maTypeName, SOFFICE_FILEFORMAT_CURRENT );
+
+ rDesc.mnViewAspect = ASPECT_CONTENT;
+ rDesc.maSize = OutputDevice::LogicToLogic(GetVisArea().GetSize(), MapMode(GetMapUnit()), MapMode(MapUnit::Map100thMM));
+ rDesc.maDragStartPos = Point();
+ rDesc.maDisplayName.clear();
+}
+
+void SfxObjectShell::DoDraw( OutputDevice* pDev,
+ const Point & rObjPos,
+ const Size & rSize,
+ const JobSetup & rSetup,
+ sal_uInt16 nAspect,
+ bool bOutputForScreen )
+{
+ if (!rSize.Width() || !rSize.Height())
+ return;
+
+ MapMode aMod = pDev->GetMapMode();
+ Size aSize = GetVisArea( nAspect ).GetSize();
+ MapMode aWilliMode( GetMapUnit() );
+ aSize = pDev->LogicToLogic( aSize, &aWilliMode, &aMod );
+ if( aSize.Width() && aSize.Height() )
+ {
+ Fraction aXF( rSize.Width(), aSize.Width() );
+ Fraction aYF( rSize.Height(), aSize.Height() );
+
+ DoDraw_Impl(pDev, rObjPos, aXF, aYF, rSetup, nAspect, bOutputForScreen);
+ }
+}
+
+void SfxObjectShell::DoDraw_Impl( OutputDevice* pDev,
+ const Point & rViewPos,
+ const Fraction & rScaleX,
+ const Fraction & rScaleY,
+ const JobSetup & rSetup,
+ sal_uInt16 nAspect,
+ bool bOutputForScreen )
+{
+ tools::Rectangle aVisArea = GetVisArea( nAspect );
+ // MapUnit of the target
+ MapMode aMapMode( GetMapUnit() );
+ aMapMode.SetScaleX( rScaleX );
+ aMapMode.SetScaleY( rScaleY );
+
+ // Target in Pixels
+ Point aOrg = pDev->LogicToLogic( rViewPos, nullptr, &aMapMode );
+ Point aDelta = aOrg - aVisArea.TopLeft();
+
+ // Origin moved according to the viewable area
+ // Origin set with Scale
+ aMapMode.SetOrigin( aDelta );
+
+ // Secure the Device settings
+ pDev->Push();
+
+ vcl::Region aRegion;
+ if( pDev->IsClipRegion() && pDev->GetOutDevType() != OUTDEV_PRINTER )
+ {
+ aRegion = pDev->GetClipRegion();
+ aRegion = pDev->LogicToPixel( aRegion );
+ }
+ pDev->SetRelativeMapMode( aMapMode );
+
+ GDIMetaFile * pMtf = pDev->GetConnectMetaFile();
+ if( pMtf )
+ {
+ if( pMtf->IsRecord() && pDev->GetOutDevType() != OUTDEV_PRINTER )
+ pMtf->Stop();
+ else
+ pMtf = nullptr;
+ }
+ if( pDev->IsClipRegion() && pDev->GetOutDevType() != OUTDEV_PRINTER )
+ {
+ aRegion = pDev->PixelToLogic( aRegion );
+ pDev->SetClipRegion( aRegion );
+ }
+ if( pMtf )
+ pMtf->Record( pDev );
+
+ Draw( pDev, rSetup, nAspect, bOutputForScreen );
+
+ // Restore Device settings
+ pDev->Pop();
+
+}
+
+comphelper::EmbeddedObjectContainer& SfxObjectShell::GetEmbeddedObjectContainer() const
+{
+ if ( !pImpl->mxObjectContainer )
+ pImpl->mxObjectContainer.reset(new comphelper::EmbeddedObjectContainer( const_cast<SfxObjectShell*>(this)->GetStorage(), GetModel() ));
+ return *pImpl->mxObjectContainer;
+}
+
+void SfxObjectShell::ClearEmbeddedObjects()
+{
+ // frees all space taken by embedded objects
+ pImpl->mxObjectContainer.reset();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/objitem.cxx b/sfx2/source/doc/objitem.cxx
new file mode 100644
index 0000000000..e776e66087
--- /dev/null
+++ b/sfx2/source/doc/objitem.cxx
@@ -0,0 +1,97 @@
+/* -*- 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 <sfx2/objsh.hxx>
+#include <sfx2/objitem.hxx>
+#include <com/sun/star/frame/XModel.hpp>
+
+
+SfxPoolItem* SfxObjectShellItem::CreateDefault() { return new SfxObjectShellItem; }
+
+SfxPoolItem* SfxObjectItem::CreateDefault() { return new SfxObjectItem; }
+
+bool SfxObjectShellItem::operator==( const SfxPoolItem &rItem ) const
+{
+ return SfxPoolItem::operator==(rItem) &&
+ static_cast<const SfxObjectShellItem&>(rItem).pObjSh == pObjSh;
+}
+
+SfxObjectShellItem* SfxObjectShellItem::Clone( SfxItemPool *) const
+{
+ return new SfxObjectShellItem( *this );
+}
+
+bool SfxObjectShellItem::QueryValue( css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const
+{
+ if ( pObjSh )
+ {
+ // This item MUST provide a model. Please don't change this, there are UNO-based
+ // implementations which need it!!
+ rVal <<= pObjSh->GetModel();
+ }
+ else
+ {
+ rVal <<= css::uno::Reference< css::frame::XModel >();
+ }
+ return true;
+}
+
+bool SfxObjectShellItem::PutValue( const css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ )
+{
+ // This item MUST have a model. Please don't change this, there are UNO-based
+ // implementations which need it!!
+ css::uno::Reference< css::frame::XModel > xModel;
+
+ if ( rVal >>= xModel )
+ {
+ pObjSh = SfxObjectShell::GetShellFromComponent(xModel);
+ return true;
+ }
+
+ return true;
+}
+
+SfxObjectItem::SfxObjectItem( sal_uInt16 nWhichId, SfxShell *pSh )
+: SfxPoolItem( nWhichId ),
+ _pSh( pSh )
+{}
+
+bool SfxObjectItem::operator==( const SfxPoolItem &rItem ) const
+{
+ return SfxPoolItem::operator==(rItem) &&
+ static_cast<const SfxObjectItem&>(rItem)._pSh == _pSh;
+}
+
+SfxObjectItem* SfxObjectItem::Clone( SfxItemPool *) const
+{
+ return new SfxObjectItem( *this );
+}
+
+bool SfxObjectItem::QueryValue(css::uno::Any&, sal_uInt8) const
+{
+ return false;
+}
+
+bool SfxObjectItem::PutValue(const css::uno::Any&, sal_uInt8)
+{
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/objmisc.cxx b/sfx2/source/doc/objmisc.cxx
new file mode 100644
index 0000000000..234ae799ca
--- /dev/null
+++ b/sfx2/source/doc/objmisc.cxx
@@ -0,0 +1,2037 @@
+/* -*- 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 <config_features.h>
+
+#include <tools/inetmsg.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <svl/eitem.hxx>
+#include <svl/stritem.hxx>
+#include <svl/intitem.hxx>
+#include <svtools/svparser.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <sal/log.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <com/sun/star/awt/XTopWindow.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/XChild.hpp>
+#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
+#include <com/sun/star/document/XDocumentProperties.hpp>
+#include <com/sun/star/document/MacroExecMode.hpp>
+#include <com/sun/star/document/XScriptInvocationContext.hpp>
+#include <com/sun/star/embed/EmbedStates.hpp>
+#include <com/sun/star/embed/XEmbeddedObject.hpp>
+#include <com/sun/star/script/provider/theMasterScriptProviderFactory.hpp>
+#include <com/sun/star/script/provider/XScript.hpp>
+#include <com/sun/star/script/provider/XScriptProvider.hpp>
+#include <com/sun/star/script/provider/XScriptProviderSupplier.hpp>
+#include <com/sun/star/uri/UriReferenceFactory.hpp>
+#include <com/sun/star/uri/XVndSunStarScriptUrl.hpp>
+#include <com/sun/star/util/XModifiable.hpp>
+
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/uno/Any.h>
+#include <com/sun/star/task/ErrorCodeRequest2.hpp>
+
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+
+#include <com/sun/star/security/DocumentDigitalSignatures.hpp>
+#include <com/sun/star/task/DocumentMacroConfirmationRequest.hpp>
+#include <com/sun/star/task/InteractionClassification.hpp>
+#include <com/sun/star/task/XInteractionHandler.hpp>
+#include <com/sun/star/frame/XModel.hpp>
+
+#include <basic/basmgr.hxx>
+#include <basic/sberrors.hxx>
+#include <utility>
+#include <vcl/weld.hxx>
+#include <basic/sbx.hxx>
+#include <svtools/sfxecode.hxx>
+
+#include <unotools/mediadescriptor.hxx>
+#include <unotools/ucbhelper.hxx>
+#include <tools/urlobj.hxx>
+#include <svl/sharecontrolfile.hxx>
+#include <rtl/uri.hxx>
+#include <vcl/svapp.hxx>
+#include <framework/interaction.hxx>
+#include <framework/documentundoguard.hxx>
+#include <comphelper/interaction.hxx>
+#include <comphelper/storagehelper.hxx>
+#include <comphelper/documentconstants.hxx>
+#include <comphelper/namedvaluecollection.hxx>
+#include <ucbhelper/simpleinteractionrequest.hxx>
+#include <officecfg/Office/Common.hxx>
+
+#include <sfx2/brokenpackageint.hxx>
+#include <sfx2/signaturestate.hxx>
+#include <sfx2/app.hxx>
+#include <appdata.hxx>
+#include <sfx2/request.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/sfxresid.hxx>
+#include <sfx2/docfile.hxx>
+#include <sfx2/objsh.hxx>
+#include <objshimp.hxx>
+#include <sfx2/event.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/sfxuno.hxx>
+#include <sfx2/module.hxx>
+#include <sfx2/docfac.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <sfx2/strings.hrc>
+#include <workwin.hxx>
+#include <sfx2/sfxdlg.hxx>
+#include <sfx2/infobar.hxx>
+#include <sfx2/sfxbasemodel.hxx>
+#include <openflag.hxx>
+#include "objstor.hxx"
+#include <appopen.hxx>
+
+#include <memory>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+using namespace ::com::sun::star::document;
+using namespace ::com::sun::star::frame;
+using namespace ::com::sun::star::script;
+using namespace ::com::sun::star::script::provider;
+using namespace ::com::sun::star::container;
+
+// class SfxHeaderAttributes_Impl ----------------------------------------
+
+namespace {
+
+class SfxHeaderAttributes_Impl : public SvKeyValueIterator
+{
+private:
+ SfxObjectShell* pDoc;
+ SvKeyValueIteratorRef xIter;
+ bool bAlert;
+
+public:
+ explicit SfxHeaderAttributes_Impl( SfxObjectShell* pSh ) :
+ pDoc( pSh ),
+ xIter( pSh->GetMedium()->GetHeaderAttributes_Impl() ),
+ bAlert( false ) {}
+
+ virtual bool GetFirst( SvKeyValue& rKV ) override { return xIter->GetFirst( rKV ); }
+ virtual bool GetNext( SvKeyValue& rKV ) override { return xIter->GetNext( rKV ); }
+ virtual void Append( const SvKeyValue& rKV ) override;
+
+ void ClearForSourceView() { xIter = new SvKeyValueIterator; bAlert = false; }
+ void SetAttributes();
+ void SetAttribute( const SvKeyValue& rKV );
+};
+
+}
+
+sal_uInt16 const aTitleMap_Impl[3][2] =
+{
+ // local remote
+ /* SFX_TITLE_CAPTION */ { SFX_TITLE_FILENAME, SFX_TITLE_TITLE },
+ /* SFX_TITLE_PICKLIST */ { 32, SFX_TITLE_FULLNAME },
+ /* SFX_TITLE_HISTORY */ { 32, SFX_TITLE_FULLNAME }
+};
+
+
+bool SfxObjectShell::IsAbortingImport() const
+{
+ return pImpl->bIsAbortingImport;
+}
+
+
+uno::Reference<document::XDocumentProperties>
+SfxObjectShell::getDocProperties() const
+{
+ uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
+ GetModel(), uno::UNO_QUERY_THROW);
+ uno::Reference<document::XDocumentProperties> xDocProps(
+ xDPS->getDocumentProperties());
+ DBG_ASSERT(xDocProps.is(),
+ "SfxObjectShell: model has no DocumentProperties");
+ return xDocProps;
+}
+
+
+void SfxObjectShell::DoFlushDocInfo()
+{
+}
+
+
+// Note: the only thing that calls this is the modification event handler
+// that is installed at the XDocumentProperties
+void SfxObjectShell::FlushDocInfo()
+{
+ if ( IsLoading() )
+ return;
+
+ SetModified();
+ uno::Reference<document::XDocumentProperties> xDocProps(getDocProperties());
+ DoFlushDocInfo(); // call template method
+ const OUString url(xDocProps->getAutoloadURL());
+ sal_Int32 delay(xDocProps->getAutoloadSecs());
+ SetAutoLoad( INetURLObject(url), delay * 1000,
+ (delay > 0) || !url.isEmpty() );
+}
+
+void SfxObjectShell::AppendInfoBarWhenReady(const OUString& sId, const OUString& sPrimaryMessage,
+ const OUString& sSecondaryMessage,
+ InfobarType aInfobarType, bool bShowCloseButton)
+{
+ InfobarData aInfobarData;
+ aInfobarData.msId = sId;
+ aInfobarData.msPrimaryMessage = sPrimaryMessage;
+ aInfobarData.msSecondaryMessage = sSecondaryMessage;
+ aInfobarData.maInfobarType = aInfobarType;
+ aInfobarData.mbShowCloseButton = bShowCloseButton;
+ Get_Impl()->m_aPendingInfobars.emplace_back(aInfobarData);
+}
+
+std::vector<InfobarData>& SfxObjectShell::getPendingInfobars()
+{
+ return Get_Impl()->m_aPendingInfobars;
+}
+
+void SfxObjectShell::SetError(const ErrCodeMsg& lErr)
+{
+ if (pImpl->lErr==ERRCODE_NONE)
+ {
+ pImpl->lErr=lErr;
+ }
+}
+
+ErrCodeMsg SfxObjectShell::GetErrorIgnoreWarning() const
+{
+ return GetErrorCode().IgnoreWarning();
+}
+
+ErrCodeMsg SfxObjectShell::GetErrorCode() const
+{
+ ErrCodeMsg lError=pImpl->lErr;
+ if(!lError && GetMedium())
+ lError=GetMedium()->GetErrorCode();
+ return lError;
+}
+
+void SfxObjectShell::ResetError()
+{
+ pImpl->lErr=ERRCODE_NONE;
+ SfxMedium * pMed = GetMedium();
+ if( pMed )
+ pMed->ResetError();
+}
+
+void SfxObjectShell::EnableSetModified( bool bEnable )
+{
+ SAL_INFO_IF( bEnable == pImpl->m_bEnableSetModified, "sfx", "SFX_PERSIST: EnableSetModified 2x called with the same value" );
+ pImpl->m_bEnableSetModified = bEnable;
+}
+
+
+bool SfxObjectShell::IsEnableSetModified() const
+{
+ // tdf#146547 read-only does not prevent modified, instead try to prevent
+ // setting "internal" documents that may be displayed in some dialog but
+ // which the user didn't load or activate to modified.
+ return pImpl->m_bEnableSetModified && !IsPreview()
+ && eCreateMode != SfxObjectCreateMode::ORGANIZER
+ && eCreateMode != SfxObjectCreateMode::INTERNAL;
+}
+
+
+bool SfxObjectShell::IsModified() const
+{
+ if ( pImpl->m_bIsModified )
+ return true;
+
+ if ( !pImpl->m_xDocStorage.is() || IsReadOnly() )
+ {
+ // if the document still has no storage and is not set to be modified explicitly it is not modified
+ // a readonly document is also not modified
+
+ return false;
+ }
+
+ if (pImpl->mxObjectContainer)
+ {
+ const uno::Sequence < OUString > aNames = GetEmbeddedObjectContainer().GetObjectNames();
+ for ( const auto& rName : aNames )
+ {
+ uno::Reference < embed::XEmbeddedObject > xObj = GetEmbeddedObjectContainer().GetEmbeddedObject( rName );
+ OSL_ENSURE( xObj.is(), "An empty entry in the embedded objects list!" );
+ if ( xObj.is() )
+ {
+ try
+ {
+ sal_Int32 nState = xObj->getCurrentState();
+ if ( nState != embed::EmbedStates::LOADED )
+ {
+ uno::Reference< util::XModifiable > xModifiable( xObj->getComponent(), uno::UNO_QUERY );
+ if ( xModifiable.is() && xModifiable->isModified() )
+ return true;
+ }
+ }
+ catch( uno::Exception& )
+ {}
+ }
+ }
+ }
+
+ return false;
+}
+
+
+void SfxObjectShell::SetModified( bool bModifiedP )
+{
+ SAL_INFO_IF( !bModifiedP && !IsEnableSetModified(), "sfx",
+ "SFX_PERSIST: SetModified( sal_False ), although IsEnableSetModified() == sal_False" );
+
+ if( !IsEnableSetModified() )
+ return;
+
+ if( pImpl->m_bIsModified != bModifiedP )
+ {
+ pImpl->m_bIsModified = bModifiedP;
+ ModifyChanged();
+ }
+}
+
+
+void SfxObjectShell::ModifyChanged()
+{
+ if ( pImpl->bClosing )
+ // SetModified dispose of the models!
+ return;
+
+
+ SfxViewFrame* pViewFrame = SfxViewFrame::Current();
+ if ( pViewFrame )
+ pViewFrame->GetBindings().Invalidate( SID_SAVEDOCS );
+
+ Invalidate( SID_SIGNATURE );
+ Invalidate( SID_MACRO_SIGNATURE );
+ Broadcast( SfxHint( SfxHintId::TitleChanged ) ); // xmlsec05, signed state might change in title...
+
+ SfxGetpApp()->NotifyEvent( SfxEventHint( SfxEventHintId::ModifyChanged, GlobalEventConfig::GetEventName(GlobalEventId::MODIFYCHANGED), this ) );
+}
+
+
+bool SfxObjectShell::IsReadOnlyUI() const
+
+/* [Description]
+
+ Returns sal_True if the document for the UI is treated as r/o. This is
+ regardless of the actual r/o, which can be checked with <IsReadOnly()>.
+*/
+
+{
+ return pImpl->bReadOnlyUI;
+}
+
+
+bool SfxObjectShell::IsReadOnlyMedium() const
+
+/* [Description]
+
+ Returns sal_True when the medium is r/o, for instance when opened as r/o.
+*/
+
+{
+ if ( !pMedium )
+ return true;
+ return pMedium->IsReadOnly();
+}
+
+bool SfxObjectShell::IsOriginallyReadOnlyMedium() const
+{
+ return pMedium == nullptr || pMedium->IsOriginallyReadOnly();
+}
+
+bool SfxObjectShell::IsOriginallyLoadedReadOnlyMedium() const
+{
+ return pMedium != nullptr && pMedium->IsOriginallyLoadedReadOnly();
+}
+
+
+void SfxObjectShell::SetReadOnlyUI( bool bReadOnly )
+
+/* [Description]
+
+ Turns the document in a r/o and r/w state respectively without reloading
+ it and without changing the open mode of the medium.
+*/
+
+{
+ if ( bReadOnly != pImpl->bReadOnlyUI )
+ {
+ pImpl->bReadOnlyUI = bReadOnly;
+ Broadcast( SfxHint(SfxHintId::ModeChanged) );
+ }
+}
+
+
+void SfxObjectShell::SetReadOnly()
+{
+ // Let the document be completely readonly, means that the
+ // medium open mode is adjusted accordingly, and the write lock
+ // on the file is removed.
+
+ if ( !pMedium || IsReadOnlyMedium() )
+ return;
+
+ bool bWasROUI = IsReadOnly();
+
+ pMedium->UnlockFile( false );
+
+ // the storage-based mediums are already based on the temporary file
+ // so UnlockFile has already closed the locking stream
+ if ( !pMedium->HasStorage_Impl() && IsLoadingFinished() )
+ pMedium->CloseInStream();
+
+ pMedium->SetOpenMode( SFX_STREAM_READONLY, true );
+ pMedium->GetItemSet().Put( SfxBoolItem( SID_DOC_READONLY, true ) );
+
+ if ( !bWasROUI )
+ Broadcast( SfxHint(SfxHintId::ModeChanged) );
+}
+
+
+bool SfxObjectShell::IsReadOnly() const
+{
+ return pImpl->bReadOnlyUI || pMedium == nullptr;
+}
+
+
+bool SfxObjectShell::IsInModalMode() const
+{
+ return pImpl->bModalMode || pImpl->bRunningMacro;
+}
+
+bool SfxObjectShell::AcceptStateUpdate() const
+{
+ return !IsInModalMode();
+}
+
+
+void SfxObjectShell::SetMacroMode_Impl( bool bModal )
+{
+ if ( !pImpl->bRunningMacro != !bModal )
+ {
+ pImpl->bRunningMacro = bModal;
+ Broadcast( SfxHint( SfxHintId::ModeChanged ) );
+ }
+}
+
+
+void SfxObjectShell::SetModalMode_Impl( bool bModal )
+{
+ // Broadcast only if modified, or otherwise it will possibly go into
+ // an endless loop
+ if ( pImpl->bModalMode == bModal )
+ return;
+
+ // Central count
+ sal_uInt16 &rDocModalCount = SfxGetpApp()->Get_Impl()->nDocModalMode;
+ if ( bModal )
+ ++rDocModalCount;
+ else
+ --rDocModalCount;
+
+ // Switch
+ pImpl->bModalMode = bModal;
+ Broadcast( SfxHint( SfxHintId::ModeChanged ) );
+}
+
+#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+
+bool SfxObjectShell::SwitchToShared( bool bShared, bool bSave )
+{
+ bool bResult = true;
+
+ if ( bShared != IsDocShared() )
+ {
+ OUString aOrigURL = GetMedium()->GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ if ( aOrigURL.isEmpty() && bSave )
+ {
+ // this is a new document, let it be stored before switching to the shared mode;
+ // the storing should be done without shared flag, since it is possible that the
+ // target location does not allow to create sharing control file;
+ // the shared flag will be set later after creation of sharing control file
+ SfxViewFrame* pViewFrame = SfxViewFrame::GetFirst( this );
+
+ if ( pViewFrame )
+ {
+ // TODO/LATER: currently the application guards against the reentrance problem
+ const SfxPoolItemHolder aItem(pViewFrame->GetBindings().ExecuteSynchron( HasName() ? SID_SAVEDOC : SID_SAVEASDOC ));
+ const SfxBoolItem* pResult(dynamic_cast<const SfxBoolItem*>(aItem.getItem()));
+ bResult = ( pResult && pResult->GetValue() );
+ if ( bResult )
+ aOrigURL = GetMedium()->GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ }
+ }
+
+ bool bOldValue = HasSharedXMLFlagSet();
+ SetSharedXMLFlag( bShared );
+
+ bool bRemoveEntryOnError = false;
+ if ( bResult && bShared )
+ {
+ try
+ {
+ ::svt::ShareControlFile aControlFile( aOrigURL );
+ aControlFile.InsertOwnEntry();
+ bRemoveEntryOnError = true;
+ }
+ catch( uno::Exception& )
+ {
+ bResult = false;
+ }
+ }
+
+ if ( bResult && bSave )
+ {
+ SfxViewFrame* pViewFrame = SfxViewFrame::GetFirst( this );
+
+ if ( pViewFrame )
+ {
+ // TODO/LATER: currently the application guards against the reentrance problem
+ SetModified(); // the modified flag has to be set to let the document be stored with the shared flag
+ try
+ {
+ // Do *not* use dispatch mechanism in this place - we don't want others (extensions etc.) to intercept this.
+ pImpl->pBaseModel->store();
+ bResult = true;
+ }
+ catch (...)
+ {
+ bResult = false;
+ }
+ }
+ }
+
+ if ( bResult )
+ {
+ // TODO/LATER: Is it possible that the following calls fail?
+ if ( bShared )
+ {
+ pImpl->m_aSharedFileURL = aOrigURL;
+ GetMedium()->SwitchDocumentToTempFile();
+ }
+ else
+ {
+ const OUString aTempFileURL = pMedium->GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ GetMedium()->SwitchDocumentToFile( GetSharedFileURL() );
+ pImpl->m_aSharedFileURL.clear();
+
+ // now remove the temporary file the document was based on
+ ::utl::UCBContentHelper::Kill( aTempFileURL );
+
+ try
+ {
+ // aOrigURL can not be used since it contains an old value
+ ::svt::ShareControlFile aControlFile( GetMedium()->GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+ aControlFile.RemoveFile();
+ }
+ catch( uno::Exception& )
+ {
+ }
+ }
+ }
+ else
+ {
+ // the saving has failed!
+ if ( bRemoveEntryOnError )
+ {
+ try
+ {
+ ::svt::ShareControlFile aControlFile( aOrigURL );
+ aControlFile.RemoveEntry();
+ }
+ catch( uno::Exception& )
+ {}
+ }
+
+ SetSharedXMLFlag( bOldValue );
+ }
+ }
+ else
+ bResult = false; // the second switch to the same mode
+
+ if ( bResult )
+ SetTitle( "" );
+
+ return bResult;
+}
+
+
+void SfxObjectShell::FreeSharedFile( const OUString& aTempFileURL )
+{
+ SetSharedXMLFlag( false );
+
+ if ( !IsDocShared() || aTempFileURL.isEmpty()
+ || ::utl::UCBContentHelper::EqualURLs( aTempFileURL, GetSharedFileURL() ) )
+ return;
+
+ if ( pImpl->m_bAllowShareControlFileClean )
+ {
+ try
+ {
+ ::svt::ShareControlFile aControlFile( GetSharedFileURL() );
+ aControlFile.RemoveEntry();
+ }
+ catch( uno::Exception& )
+ {
+ }
+ }
+
+ // the cleaning is forbidden only once
+ pImpl->m_bAllowShareControlFileClean = true;
+
+ // now remove the temporary file the document is based currently on
+ ::utl::UCBContentHelper::Kill( aTempFileURL );
+
+ pImpl->m_aSharedFileURL.clear();
+}
+
+
+void SfxObjectShell::DoNotCleanShareControlFile()
+{
+ pImpl->m_bAllowShareControlFileClean = false;
+}
+
+
+void SfxObjectShell::SetSharedXMLFlag( bool bFlag ) const
+{
+ pImpl->m_bSharedXMLFlag = bFlag;
+}
+
+
+bool SfxObjectShell::HasSharedXMLFlagSet() const
+{
+ return pImpl->m_bSharedXMLFlag;
+}
+
+#endif
+
+bool SfxObjectShell::IsDocShared() const
+{
+#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+ return ( !pImpl->m_aSharedFileURL.isEmpty() );
+#else
+ return false;
+#endif
+}
+
+
+OUString SfxObjectShell::GetSharedFileURL() const
+{
+#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+ return pImpl->m_aSharedFileURL;
+#else
+ return OUString();
+#endif
+}
+
+Size SfxObjectShell::GetFirstPageSize() const
+{
+ return GetVisArea(ASPECT_THUMBNAIL).GetSize();
+}
+
+
+IndexBitSet& SfxObjectShell::GetNoSet_Impl()
+{
+ return pImpl->aBitSet;
+}
+
+
+// changes the title of the document
+
+void SfxObjectShell::SetTitle
+(
+ const OUString& rTitle // the new Document Title
+)
+
+/* [Description]
+
+ With this method, the title of the document can be set.
+ This corresponds initially to the full file name. A setting of the
+ title does not affect the file name, but it will be shown in the
+ Caption-Bars of the MDI-window.
+*/
+
+{
+
+ // Nothing to do?
+ if ( ( ( HasName() && pImpl->aTitle == rTitle )
+ || ( !HasName() && GetTitle() == rTitle ) )
+ && !IsDocShared() )
+ return;
+
+ SfxApplication *pSfxApp = SfxGetpApp();
+
+ // If possible release the unnamed number.
+ if ( pImpl->bIsNamedVisible && USHRT_MAX != pImpl->nVisualDocumentNumber )
+ {
+ pSfxApp->ReleaseIndex(pImpl->nVisualDocumentNumber);
+ pImpl->bIsNamedVisible = false;
+ }
+
+ // Set Title
+ pImpl->aTitle = rTitle;
+
+ // Notification
+ if ( GetMedium() )
+ {
+ SfxShell::SetName( GetTitle(SFX_TITLE_APINAME) );
+ Broadcast( SfxHint(SfxHintId::TitleChanged) );
+ }
+}
+
+
+
+OUString SfxObjectShell::GetTitle( sal_uInt16 nMaxLength ) const
+
+/* [Description]
+
+ Returns the title or logical file name of the document, depending on the
+ 'nMaxLength'.
+
+ If the file name with path is used, the Name shortened by replacing one or
+ more directory names with "...", URLs are currently always returned
+ in complete form.
+*/
+
+{
+ SfxMedium *pMed = GetMedium();
+ if ( IsLoading() )
+ return OUString();
+
+ // Create Title?
+ if ( SFX_TITLE_DETECT == nMaxLength && pImpl->aTitle.isEmpty() )
+ {
+ static bool bRecur = false;
+ if ( bRecur )
+ return "-not available-";
+ bRecur = true;
+
+ OUString aTitle;
+
+ if ( pMed )
+ {
+ const SfxStringItem* pNameItem = pMed->GetItemSet().GetItem(SID_DOCINFO_TITLE, false);
+ if ( pNameItem )
+ aTitle = pNameItem->GetValue();
+ }
+
+ if ( aTitle.isEmpty() )
+ aTitle = GetTitle( SFX_TITLE_FILENAME );
+
+ bRecur = false;
+ return aTitle;
+ }
+
+ if (SFX_TITLE_APINAME == nMaxLength )
+ return GetAPIName();
+
+ // Picklist/Caption is mapped
+ if ( pMed && ( nMaxLength == SFX_TITLE_CAPTION || nMaxLength == SFX_TITLE_PICKLIST ) )
+ {
+ // If a specific title was given at open:
+ // important for URLs: use INetProtocol::File for which the set title is not
+ // considered. (See below, analysis of aTitleMap_Impl)
+ const SfxStringItem* pNameItem = pMed->GetItemSet().GetItem(SID_DOCINFO_TITLE, false);
+ if ( pNameItem )
+ return pNameItem->GetValue();
+ }
+
+ // Still unnamed?
+ DBG_ASSERT( !HasName() || pMed, "HasName() but no Medium?!?" );
+ if ( !HasName() || !pMed )
+ {
+ // Title already set?
+ if ( !pImpl->aTitle.isEmpty() )
+ return pImpl->aTitle;
+
+ // must it be numbered?
+ const OUString aNoName(SfxResId(STR_NONAME));
+ if (pImpl->bIsNamedVisible)
+ {
+ // Append number
+ return aNoName + " " + OUString::number(pImpl->nVisualDocumentNumber);
+ }
+
+ // Document called "Untitled" for the time being
+ return aNoName;
+ }
+ assert(pMed);
+
+ const INetURLObject aURL( IsDocShared() ? GetSharedFileURL() : GetMedium()->GetName() );
+ if ( nMaxLength > SFX_TITLE_CAPTION && nMaxLength <= SFX_TITLE_HISTORY )
+ {
+ sal_uInt16 nRemote;
+ if (aURL.GetProtocol() == INetProtocol::File)
+ nRemote = 0;
+ else
+ nRemote = 1;
+ nMaxLength = aTitleMap_Impl[nMaxLength-SFX_TITLE_CAPTION][nRemote];
+ }
+
+ // Local file?
+ if ( aURL.GetProtocol() == INetProtocol::File )
+ {
+ if ( nMaxLength == SFX_TITLE_FULLNAME )
+ return aURL.HasMark() ? INetURLObject( aURL.GetURLNoMark() ).PathToFileName() : aURL.PathToFileName();
+ if ( nMaxLength == SFX_TITLE_FILENAME )
+ return aURL.getName(INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset);
+ if ( pImpl->aTitle.isEmpty() )
+ pImpl->aTitle = aURL.getBase( INetURLObject::LAST_SEGMENT,
+ true, INetURLObject::DecodeMechanism::WithCharset );
+ }
+ else
+ {
+ if ( nMaxLength >= SFX_TITLE_MAXLEN )
+ {
+ const OUString aComplete( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+ if( aComplete.getLength() > nMaxLength )
+ return OUString::Concat("...") + aComplete.subView( aComplete.getLength() - nMaxLength + 3, nMaxLength - 3 );
+ return aComplete;
+ }
+ if ( nMaxLength == SFX_TITLE_FILENAME )
+ {
+ const OUString aName = INetURLObject::decode( aURL.GetBase(), INetURLObject::DecodeMechanism::WithCharset );
+ return aName.isEmpty() ? aURL.GetURLNoPass() : aName;
+ }
+ if ( nMaxLength == SFX_TITLE_FULLNAME )
+ return aURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri );
+
+ // Generate Title from file name if possible
+ if ( pImpl->aTitle.isEmpty() )
+ pImpl->aTitle = aURL.GetBase();
+
+ // workaround for the case when the name can not be retrieved from URL by INetURLObject
+ if ( pImpl->aTitle.isEmpty() )
+ pImpl->aTitle = aURL.GetMainURL( INetURLObject::DecodeMechanism::WithCharset );
+ }
+
+ // Complete Title
+ return pImpl->aTitle;
+}
+
+
+void SfxObjectShell::InvalidateName()
+
+/* [Description]
+
+ Returns the title of the new document, DocInfo-Title or
+ File name. Is required for loading from template or SaveAs.
+*/
+
+{
+ pImpl->aTitle.clear();
+ SetName( GetTitle( SFX_TITLE_APINAME ) );
+
+ Broadcast( SfxHint(SfxHintId::TitleChanged) );
+}
+
+
+void SfxObjectShell::SetNamedVisibility_Impl()
+{
+ if ( !pImpl->bIsNamedVisible )
+ {
+ pImpl->bIsNamedVisible = true;
+ if ( !HasName() && USHRT_MAX == pImpl->nVisualDocumentNumber && pImpl->aTitle.isEmpty() )
+ {
+ pImpl->nVisualDocumentNumber = SfxGetpApp()->GetFreeIndex();
+ Broadcast( SfxHint(SfxHintId::TitleChanged) );
+ }
+ }
+
+ SetName( GetTitle(SFX_TITLE_APINAME) );
+}
+
+void SfxObjectShell::SetNoName()
+{
+ bHasName = false;
+ GetModel()->attachResource( OUString(), GetModel()->getArgs() );
+}
+
+
+SfxProgress* SfxObjectShell::GetProgress() const
+{
+ return pImpl->pProgress;
+}
+
+
+void SfxObjectShell::SetProgress_Impl
+(
+ SfxProgress *pProgress /* to started <SfxProgress> or 0,
+ if the progress is to be reset */
+)
+
+/* [Description]
+
+ Internal method to set or reset the Progress modes for
+ SfxObjectShell.
+*/
+
+{
+ DBG_ASSERT( ( !pImpl->pProgress && pProgress ) ||
+ ( pImpl->pProgress && !pProgress ),
+ "Progress activation/deactivation mismatch" );
+ pImpl->pProgress = pProgress;
+}
+
+
+void SfxObjectShell::PostActivateEvent_Impl( SfxViewFrame const * pFrame )
+{
+ SfxApplication* pSfxApp = SfxGetpApp();
+ if ( pSfxApp->IsDowning() || IsLoading() || !pFrame || pFrame->GetFrame().IsClosing_Impl() )
+ return;
+
+ const SfxBoolItem* pHiddenItem = pMedium->GetItemSet().GetItem(SID_HIDDEN, false);
+ if ( !pHiddenItem || !pHiddenItem->GetValue() )
+ {
+ SfxEventHintId nId = pImpl->nEventId;
+ pImpl->nEventId = SfxEventHintId::NONE;
+ if ( nId == SfxEventHintId::OpenDoc )
+ pSfxApp->NotifyEvent(SfxViewEventHint( nId, GlobalEventConfig::GetEventName(GlobalEventId::OPENDOC), this, pFrame->GetFrame().GetController() ), false);
+ else if (nId == SfxEventHintId::CreateDoc )
+ pSfxApp->NotifyEvent(SfxViewEventHint( nId, GlobalEventConfig::GetEventName(GlobalEventId::CREATEDOC), this, pFrame->GetFrame().GetController() ), false);
+ }
+}
+
+
+void SfxObjectShell::SetActivateEvent_Impl(SfxEventHintId nId )
+{
+ pImpl->nEventId = nId;
+}
+
+bool SfxObjectShell::IsAutoLoadLocked() const
+
+/* Returns whether an Autoload is allowed to be executed. Before the
+ surrounding FrameSet of the AutoLoad is also taken into account as well.
+*/
+
+{
+ return !IsReadOnly();
+}
+
+
+void SfxObjectShell::BreakMacroSign_Impl( bool bBreakMacroSign )
+{
+ pImpl->m_bMacroSignBroken = bBreakMacroSign;
+}
+
+
+void SfxObjectShell::CheckSecurityOnLoading_Impl()
+{
+ // make sure LO evaluates the macro signatures, so it can be preserved
+ GetScriptingSignatureState();
+
+ uno::Reference< task::XInteractionHandler > xInteraction;
+ if ( GetMedium() )
+ xInteraction = GetMedium()->GetInteractionHandler();
+
+ // check if there is a broken signature...
+ CheckForBrokenDocSignatures_Impl();
+
+ CheckEncryption_Impl( xInteraction );
+
+ // check macro security
+ const bool bHasValidContentSignature = HasValidSignatures();
+ const bool bHasMacros = pImpl->aMacroMode.hasMacros();
+ pImpl->aMacroMode.checkMacrosOnLoading( xInteraction, bHasValidContentSignature, bHasMacros );
+ pImpl->m_bHadCheckedMacrosOnLoad = bHasMacros;
+}
+
+bool SfxObjectShell::GetHadCheckedMacrosOnLoad() const
+{
+ return pImpl->m_bHadCheckedMacrosOnLoad;
+}
+
+bool SfxObjectShell::AllowedLinkProtocolFromDocument(const OUString& rUrl, SfxObjectShell* pObjShell, weld::Window* pDialogParent)
+{
+ if (!INetURLObject(rUrl).IsExoticProtocol())
+ return true;
+ // Default to ignoring exotic protocols
+ bool bAllow = false;
+ if (pObjShell)
+ {
+ // If the document had macros when loaded then follow the allowed macro-mode
+ if (pObjShell->GetHadCheckedMacrosOnLoad())
+ bAllow = pObjShell->AdjustMacroMode();
+ else // otherwise ask the user, defaulting to cancel
+ {
+ //Reuse URITools::onOpenURI warning string
+ std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(pDialogParent,
+ VclMessageType::Warning, VclButtonsType::YesNo,
+ SfxResId(STR_DANGEROUS_TO_OPEN)));
+ xQueryBox->set_primary_text(xQueryBox->get_primary_text().replaceFirst("$(ARG1)",
+ INetURLObject::decode(rUrl, INetURLObject::DecodeMechanism::Unambiguous)));
+ xQueryBox->set_default_response(RET_NO);
+ bAllow = xQueryBox->run() == RET_YES;
+ }
+ }
+ SAL_WARN_IF(!bAllow, "sfx.appl", "SfxObjectShell::AllowedLinkProtocolFromDocument ignoring: " << rUrl);
+ return bAllow;
+}
+
+void SfxObjectShell::CheckEncryption_Impl( const uno::Reference< task::XInteractionHandler >& xHandler )
+{
+ OUString aVersion;
+ bool bIsEncrypted = false;
+ bool bHasNonEncrypted = false;
+
+ try
+ {
+ uno::Reference < beans::XPropertySet > xPropSet( GetStorage(), uno::UNO_QUERY_THROW );
+ xPropSet->getPropertyValue("Version") >>= aVersion;
+ xPropSet->getPropertyValue("HasEncryptedEntries") >>= bIsEncrypted;
+ xPropSet->getPropertyValue("HasNonEncryptedEntries") >>= bHasNonEncrypted;
+ }
+ catch( uno::Exception& )
+ {
+ }
+
+ if ( aVersion.compareTo( ODFVER_012_TEXT ) < 0 )
+ return;
+
+ // this is ODF1.2 or later
+ if ( !(bIsEncrypted && bHasNonEncrypted) )
+ return;
+
+ if ( !pImpl->m_bIncomplEncrWarnShown )
+ {
+ // this is an encrypted document with nonencrypted streams inside, show the warning
+ css::task::ErrorCodeRequest aErrorCode;
+ aErrorCode.ErrCode = sal_uInt32(ERRCODE_SFX_INCOMPLETE_ENCRYPTION);
+
+ SfxMedium::CallApproveHandler( xHandler, uno::Any( aErrorCode ), false );
+ pImpl->m_bIncomplEncrWarnShown = true;
+ }
+
+ // broken signatures imply no macro execution at all
+ pImpl->aMacroMode.disallowMacroExecution();
+}
+
+
+void SfxObjectShell::CheckForBrokenDocSignatures_Impl()
+{
+ SignatureState nSignatureState = GetDocumentSignatureState();
+ bool bSignatureBroken = ( nSignatureState == SignatureState::BROKEN );
+ if ( !bSignatureBroken )
+ return;
+
+ // broken signatures imply no macro execution at all
+ pImpl->aMacroMode.disallowMacroExecution();
+}
+
+
+void SfxObjectShell::SetAutoLoad(
+ const INetURLObject& rUrl, sal_uInt32 nTime, bool bReload )
+{
+ pImpl->pReloadTimer.reset();
+ if ( bReload )
+ {
+ pImpl->pReloadTimer.reset(new AutoReloadTimer_Impl(
+ rUrl.GetMainURL( INetURLObject::DecodeMechanism::ToIUri ),
+ nTime, this ));
+ pImpl->pReloadTimer->Start();
+ }
+}
+
+void SfxObjectShell::SetLoading(SfxLoadedFlags nFlags)
+{
+ pImpl->nLoadedFlags = nFlags;
+}
+
+bool SfxObjectShell::IsLoadingFinished() const
+{
+ return ( pImpl->nLoadedFlags == SfxLoadedFlags::ALL );
+}
+
+void SfxObjectShell::InitOwnModel_Impl()
+{
+ if ( pImpl->bModelInitialized )
+ return;
+
+ const SfxStringItem* pSalvageItem = pMedium->GetItemSet().GetItem(SID_DOC_SALVAGE, false);
+ if ( pSalvageItem )
+ {
+ pImpl->aTempName = pMedium->GetPhysicalName();
+ pMedium->GetItemSet().ClearItem( SID_DOC_SALVAGE );
+ pMedium->GetItemSet().ClearItem( SID_FILE_NAME );
+ pMedium->GetItemSet().Put( SfxStringItem( SID_FILE_NAME, pMedium->GetOrigURL() ) );
+ }
+ else
+ {
+ pMedium->GetItemSet().ClearItem( SID_PROGRESS_STATUSBAR_CONTROL );
+ pMedium->GetItemSet().ClearItem( SID_DOCUMENT );
+ }
+
+ pMedium->GetItemSet().ClearItem( SID_REFERER );
+ uno::Reference< frame::XModel > xModel = GetModel();
+ if ( xModel.is() )
+ {
+ SfxItemSet& rSet = GetMedium()->GetItemSet();
+ if ( !GetMedium()->IsReadOnly() )
+ rSet.ClearItem( SID_INPUTSTREAM );
+ uno::Sequence< beans::PropertyValue > aArgs;
+ TransformItems( SID_OPENDOC, rSet, aArgs );
+ xModel->attachResource( GetMedium()->GetOrigURL(), aArgs );
+ impl_addToModelCollection(xModel);
+ }
+
+ pImpl->bModelInitialized = true;
+}
+
+void SfxObjectShell::FinishedLoading( SfxLoadedFlags nFlags )
+{
+ bool bSetModifiedTRUE = false;
+ const SfxStringItem* pSalvageItem = pMedium->GetItemSet().GetItem(SID_DOC_SALVAGE, false);
+ if( ( nFlags & SfxLoadedFlags::MAINDOCUMENT ) && !(pImpl->nLoadedFlags & SfxLoadedFlags::MAINDOCUMENT )
+ && !(pImpl->nFlagsInProgress & SfxLoadedFlags::MAINDOCUMENT ))
+ {
+ pImpl->nFlagsInProgress |= SfxLoadedFlags::MAINDOCUMENT;
+ static_cast<SfxHeaderAttributes_Impl*>(GetHeaderAttributes())->SetAttributes();
+
+ if ( ( GetModifyPasswordHash() || GetModifyPasswordInfo().hasElements() ) && !IsModifyPasswordEntered() )
+ SetReadOnly();
+
+ // Salvage
+ if ( pSalvageItem )
+ bSetModifiedTRUE = true;
+
+ if ( !IsEnableSetModified() )
+ EnableSetModified();
+
+ if( !bSetModifiedTRUE && IsEnableSetModified() )
+ SetModified( false );
+
+ CheckSecurityOnLoading_Impl();
+
+ bHasName = true; // the document is loaded, so the name should already available
+ GetTitle( SFX_TITLE_DETECT );
+ InitOwnModel_Impl();
+
+ if (IsLoadReadonly())
+ {
+ OUString aFilterName;
+ if (const SfxStringItem* pFilterNameItem =
+ pMedium->GetItemSet().GetItem(SID_FILTER_NAME, false))
+ aFilterName = pFilterNameItem->GetValue();
+
+ OUString aFileName;
+ if (const SfxStringItem* pFileNameItem =
+ pMedium->GetItemSet().GetItem(SID_FILE_NAME, false))
+ {
+ const INetURLObject aURL(pFileNameItem->GetValue());
+ aFileName = aURL.getBase(INetURLObject::LAST_SEGMENT, true,
+ INetURLObject::DecodeMechanism::WithCharset);
+ }
+
+ bool bSilent = false;
+ if (const SfxBoolItem* pSilentNameItem =
+ pMedium->GetItemSet().GetItem(SID_SILENT, false))
+ bSilent = pSilentNameItem->GetValue();
+
+ if (!bSilent && aFilterName.indexOf("Excel") != -1)
+ {
+ Reference<task::XInteractionHandler> xHandler(pMedium->GetInteractionHandler());
+ if (xHandler.is())
+ {
+ beans::NamedValue aLoadReadOnlyRequest;
+ aLoadReadOnlyRequest.Name = "LoadReadOnlyRequest";
+ aLoadReadOnlyRequest.Value <<= aFileName;
+
+ Any aRequest(aLoadReadOnlyRequest);
+ rtl::Reference<ucbhelper::SimpleInteractionRequest> xRequest
+ = new ucbhelper::SimpleInteractionRequest(aRequest,
+ ContinuationFlags::Approve |
+ ContinuationFlags::Disapprove);
+
+ xHandler->handle(xRequest);
+
+ if (xRequest->getResponse() == ContinuationFlags::Disapprove)
+ {
+ SetSecurityOptOpenReadOnly(false);
+ pMedium->GetItemSet().Put(SfxBoolItem(SID_DOC_READONLY, false));
+ }
+ }
+ }
+ }
+
+ pImpl->nFlagsInProgress &= ~SfxLoadedFlags::MAINDOCUMENT;
+ }
+
+ if( ( nFlags & SfxLoadedFlags::IMAGES ) && !(pImpl->nLoadedFlags & SfxLoadedFlags::IMAGES )
+ && !(pImpl->nFlagsInProgress & SfxLoadedFlags::IMAGES ))
+ {
+ pImpl->nFlagsInProgress |= SfxLoadedFlags::IMAGES;
+ uno::Reference<document::XDocumentProperties> xDocProps(
+ getDocProperties());
+ const OUString url(xDocProps->getAutoloadURL());
+ sal_Int32 delay(xDocProps->getAutoloadSecs());
+ SetAutoLoad( INetURLObject(url), delay * 1000,
+ (delay > 0) || !url.isEmpty() );
+ if( !bSetModifiedTRUE && IsEnableSetModified() )
+ SetModified( false );
+ Invalidate( SID_SAVEASDOC );
+ pImpl->nFlagsInProgress &= ~SfxLoadedFlags::IMAGES;
+ }
+
+ pImpl->nLoadedFlags |= nFlags;
+
+ if ( pImpl->nFlagsInProgress != SfxLoadedFlags::NONE )
+ return;
+
+ // in case of reentrance calls the first called FinishedLoading() call on the stack
+ // should do the notification, in result the notification is done when all the FinishedLoading() calls are finished
+
+ if ( bSetModifiedTRUE )
+ SetModified();
+ else
+ SetModified( false );
+
+ if ( (pImpl->nLoadedFlags & SfxLoadedFlags::MAINDOCUMENT ) && (pImpl->nLoadedFlags & SfxLoadedFlags::IMAGES ) )
+ {
+ const SfxBoolItem* pTemplateItem = pMedium->GetItemSet().GetItem(SID_TEMPLATE, false);
+ bool bTemplate = pTemplateItem && pTemplateItem->GetValue();
+
+ // closing the streams on loading should be under control of SFX!
+ DBG_ASSERT( pMedium->IsOpen(), "Don't close the medium when loading documents!" );
+
+ if ( bTemplate )
+ {
+ TemplateDisconnectionAfterLoad();
+ }
+ else
+ {
+ // if a readonly medium has storage then it's stream is already based on temporary file
+ if( !(pMedium->GetOpenMode() & StreamMode::WRITE) && !pMedium->HasStorage_Impl() )
+ // don't lock file opened read only
+ pMedium->CloseInStream();
+ }
+ }
+
+ SetInitialized_Impl( false );
+
+ // Title is not available until loading has finished
+ Broadcast( SfxHint( SfxHintId::TitleChanged ) );
+ if ( pImpl->nEventId != SfxEventHintId::NONE )
+ PostActivateEvent_Impl(SfxViewFrame::GetFirst(this));
+}
+
+void SfxObjectShell::TemplateDisconnectionAfterLoad()
+{
+ // document is created from a template
+ //TODO/LATER: should the templates always be XML docs!
+
+ SfxMedium* pTmpMedium = pMedium;
+ if ( !pTmpMedium )
+ return;
+
+ const OUString aName( pTmpMedium->GetName() );
+ const SfxStringItem* pTemplNamItem = pTmpMedium->GetItemSet().GetItem(SID_TEMPLATE_NAME, false);
+ OUString aTemplateName;
+ if ( pTemplNamItem )
+ aTemplateName = pTemplNamItem->GetValue();
+ else
+ {
+ // !TODO/LATER: what's this?!
+ // Interactive ( DClick, Contextmenu ) no long name is included
+ aTemplateName = getDocProperties()->getTitle();
+ if ( aTemplateName.isEmpty() )
+ {
+ INetURLObject aURL( aName );
+ aURL.CutExtension();
+ aTemplateName = aURL.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
+ }
+ }
+
+ // set medium to noname
+ pTmpMedium->SetName( OUString(), true );
+ pTmpMedium->Init_Impl();
+
+ // drop resource
+ SetNoName();
+ InvalidateName();
+
+ if( IsPackageStorageFormat_Impl( *pTmpMedium ) )
+ {
+ // untitled document must be based on temporary storage
+ // the medium should not dispose the storage in this case
+ uno::Reference < embed::XStorage > xTmpStor = ::comphelper::OStorageHelper::GetTemporaryStorage();
+ GetStorage()->copyToStorage( xTmpStor );
+
+ // the medium should disconnect from the original location
+ // the storage should not be disposed since the document is still
+ // based on it, but in DoSaveCompleted it will be disposed
+ pTmpMedium->CanDisposeStorage_Impl( false );
+ pTmpMedium->Close();
+
+ // setting the new storage the medium will be based on
+ pTmpMedium->SetStorage_Impl( xTmpStor );
+
+ pMedium = nullptr;
+ bool ok = DoSaveCompleted( pTmpMedium );
+ assert(pMedium != nullptr);
+ if( ok )
+ {
+ const SfxStringItem* pSalvageItem = pMedium->GetItemSet().GetItem(SID_DOC_SALVAGE, false);
+ bool bSalvage = pSalvageItem != nullptr;
+
+ if ( !bSalvage )
+ {
+ // some further initializations for templates
+ SetTemplate_Impl( aName, aTemplateName, this );
+ }
+
+ // the medium should not dispose the storage, DoSaveCompleted() has let it to do so
+ pTmpMedium->CanDisposeStorage_Impl( false );
+ }
+ else
+ {
+ SetError(ERRCODE_IO_GENERAL);
+ }
+ }
+ else
+ {
+ // some further initializations for templates
+ SetTemplate_Impl( aName, aTemplateName, this );
+ pTmpMedium->CreateTempFile();
+ }
+
+ // templates are never readonly
+ pTmpMedium->GetItemSet().ClearItem( SID_DOC_READONLY );
+ pTmpMedium->SetOpenMode( SFX_STREAM_READWRITE, true );
+
+ // notifications about possible changes in readonly state and document info
+ Broadcast( SfxHint(SfxHintId::ModeChanged) );
+
+ // created untitled document can't be modified
+ SetModified( false );
+}
+
+
+bool SfxObjectShell::IsLoading() const
+/* [Description]
+
+ Has FinishedLoading been called?
+*/
+{
+ return !( pImpl->nLoadedFlags & SfxLoadedFlags::MAINDOCUMENT );
+}
+
+
+void SfxObjectShell::CancelTransfers()
+/* [Description]
+
+ Here can Transfers get canceled, which were not registered
+ by RegisterTransfer.
+*/
+{
+ if( ( pImpl->nLoadedFlags & SfxLoadedFlags::ALL ) != SfxLoadedFlags::ALL )
+ {
+ pImpl->bIsAbortingImport = true;
+ if( IsLoading() )
+ FinishedLoading();
+ }
+}
+
+
+AutoReloadTimer_Impl::AutoReloadTimer_Impl(
+ OUString _aURL, sal_uInt32 nTime, SfxObjectShell* pSh )
+ : Timer("sfx2 AutoReloadTimer_Impl"), aUrl(std::move( _aURL )), pObjSh( pSh )
+{
+ SetTimeout( nTime );
+}
+
+
+void AutoReloadTimer_Impl::Invoke()
+{
+ SfxViewFrame *pFrame = SfxViewFrame::GetFirst( pObjSh );
+
+ if ( pFrame )
+ {
+ // Not possible/meaningful at the moment?
+ if ( !pObjSh->CanReload_Impl() || pObjSh->IsAutoLoadLocked() || Application::IsUICaptured() )
+ {
+ // Allow a retry
+ Start();
+ return;
+ }
+
+ SfxAllItemSet aSet( SfxGetpApp()->GetPool() );
+ aSet.Put( SfxBoolItem( SID_AUTOLOAD, true ) );
+ if ( !aUrl.isEmpty() )
+ aSet.Put( SfxStringItem( SID_FILE_NAME, aUrl ) );
+ if (pObjSh->HasName()) {
+ aSet.Put(
+ SfxStringItem(SID_REFERER, pObjSh->GetMedium()->GetName()));
+ }
+ SfxRequest aReq( SID_RELOAD, SfxCallMode::SLOT, aSet );
+ // this will delete this
+ pObjSh->Get_Impl()->pReloadTimer.reset();
+ pFrame->ExecReload_Impl( aReq );
+ return;
+ }
+
+ // this will delete this
+ pObjSh->Get_Impl()->pReloadTimer.reset();
+}
+
+SfxModule* SfxObjectShell::GetModule() const
+{
+ return GetFactory().GetModule();
+}
+
+ErrCode SfxObjectShell::CallBasic( std::u16string_view rMacro,
+ std::u16string_view rBasic, SbxArray* pArgs,
+ SbxValue* pRet )
+{
+ SfxApplication* pApp = SfxGetpApp();
+ if( pApp->GetName() != rBasic )
+ {
+ if ( !AdjustMacroMode() )
+ return ERRCODE_IO_ACCESSDENIED;
+ }
+
+ BasicManager *pMgr = GetBasicManager();
+ if( pApp->GetName() == rBasic )
+ pMgr = SfxApplication::GetBasicManager();
+ ErrCode nRet = SfxApplication::CallBasic( OUString(rMacro), pMgr, pArgs, pRet );
+ return nRet;
+}
+
+bool SfxObjectShell::isScriptAccessAllowed( const Reference< XInterface >& _rxScriptContext )
+{
+ try
+ {
+ Reference< XEmbeddedScripts > xScripts( _rxScriptContext, UNO_QUERY );
+ if ( !xScripts.is() )
+ {
+ Reference< XScriptInvocationContext > xContext( _rxScriptContext, UNO_QUERY_THROW );
+ xScripts.set( xContext->getScriptContainer(), UNO_SET_THROW );
+ }
+
+ return xScripts->getAllowMacroExecution();
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("sfx.doc");
+ }
+ return false;
+}
+
+// don't allow LibreLogo to be used with our mouseover/etc dom-alike events
+bool SfxObjectShell::UnTrustedScript(const OUString& rScriptURL)
+{
+ if (!rScriptURL.startsWith("vnd.sun.star.script:"))
+ return false;
+
+ // ensure URL Escape Codes are decoded
+ css::uno::Reference<css::uri::XUriReference> uri(
+ css::uri::UriReferenceFactory::create(comphelper::getProcessComponentContext())->parse(rScriptURL));
+ css::uno::Reference<css::uri::XVndSunStarScriptUrl> sfUri(uri, css::uno::UNO_QUERY);
+
+ if (!sfUri.is())
+ return false;
+
+ // pyuno encodes path separator as |
+ OUString sScript = sfUri->getName().replace('|', '/');
+
+ // check if any path portion matches LibreLogo and ban it if it does
+ sal_Int32 nIndex = 0;
+ do
+ {
+ OUString aToken = sScript.getToken(0, '/', nIndex);
+ if (aToken.startsWithIgnoreAsciiCase("LibreLogo") || aToken.indexOf('~') != -1)
+ {
+ return true;
+ }
+ }
+ while (nIndex >= 0);
+
+ return false;
+}
+
+ErrCode SfxObjectShell::CallXScript( const Reference< XInterface >& _rxScriptContext, const OUString& _rScriptURL,
+ const Sequence< Any >& aParams, Any& aRet, Sequence< sal_Int16 >& aOutParamIndex, Sequence< Any >& aOutParam, bool bRaiseError, const css::uno::Any* pCaller )
+{
+ SAL_INFO("sfx", "in CallXScript" );
+ ErrCode nErr = ERRCODE_NONE;
+
+ bool bCaughtException = false;
+ Any aException;
+ try
+ {
+ if (!isScriptAccessAllowed(_rxScriptContext))
+ return ERRCODE_IO_ACCESSDENIED;
+
+ if ( UnTrustedScript(_rScriptURL) )
+ return ERRCODE_IO_ACCESSDENIED;
+
+ // obtain/create a script provider
+ Reference< provider::XScriptProvider > xScriptProvider;
+ Reference< provider::XScriptProviderSupplier > xSPS( _rxScriptContext, UNO_QUERY );
+ if ( xSPS.is() )
+ xScriptProvider.set( xSPS->getScriptProvider() );
+
+ if ( !xScriptProvider.is() )
+ {
+ Reference< provider::XScriptProviderFactory > xScriptProviderFactory =
+ provider::theMasterScriptProviderFactory::get( ::comphelper::getProcessComponentContext() );
+ xScriptProvider.set( xScriptProviderFactory->createScriptProvider( Any( _rxScriptContext ) ), UNO_SET_THROW );
+ }
+
+ // ry to protect the invocation context's undo manager (if present), just in case the script tampers with it
+ ::framework::DocumentUndoGuard aUndoGuard( _rxScriptContext );
+
+ // obtain the script, and execute it
+ Reference< provider::XScript > xScript( xScriptProvider->getScript( _rScriptURL ), UNO_SET_THROW );
+ if ( pCaller && pCaller->hasValue() )
+ {
+ Reference< beans::XPropertySet > xProps( xScript, uno::UNO_QUERY );
+ if ( xProps.is() )
+ {
+ Sequence< uno::Any > aArgs{ *pCaller };
+ xProps->setPropertyValue("Caller", uno::Any( aArgs ) );
+ }
+ }
+ aRet = xScript->invoke( aParams, aOutParamIndex, aOutParam );
+ }
+ catch ( const uno::Exception& )
+ {
+ aException = ::cppu::getCaughtException();
+ bCaughtException = true;
+ nErr = ERRCODE_BASIC_INTERNAL_ERROR;
+ }
+
+ if ( bCaughtException && bRaiseError )
+ {
+ SfxAbstractDialogFactory* pFact = SfxAbstractDialogFactory::Create();
+ pFact->ShowAsyncScriptErrorDialog( nullptr, aException );
+ }
+
+ SAL_INFO("sfx", "leaving CallXScript" );
+ return nErr;
+}
+
+// perhaps rename to CallScript once we get rid of the existing CallScript
+// and Call, CallBasic, CallStarBasic methods
+ErrCode SfxObjectShell::CallXScript( const OUString& rScriptURL,
+ const css::uno::Sequence< css::uno::Any >& aParams,
+ css::uno::Any& aRet,
+ css::uno::Sequence< sal_Int16 >& aOutParamIndex,
+ css::uno::Sequence< css::uno::Any >& aOutParam,
+ bool bRaiseError,
+ const css::uno::Any* pCaller )
+{
+ return CallXScript( GetModel(), rScriptURL, aParams, aRet, aOutParamIndex, aOutParam, bRaiseError, pCaller );
+}
+
+void SfxHeaderAttributes_Impl::SetAttributes()
+{
+ bAlert = true;
+ SvKeyValue aPair;
+ for( bool bCont = xIter->GetFirst( aPair ); bCont;
+ bCont = xIter->GetNext( aPair ) )
+ SetAttribute( aPair );
+}
+
+void SfxHeaderAttributes_Impl::SetAttribute( const SvKeyValue& rKV )
+{
+ const OUString& aValue = rKV.GetValue();
+ if( rKV.GetKey().equalsIgnoreAsciiCase("refresh") && !rKV.GetValue().isEmpty() )
+ {
+ sal_Int32 nIdx{ 0 };
+ const sal_Int32 nTime{ o3tl::toInt32(o3tl::getToken(aValue, 0, ';', nIdx )) };
+ const OUString aURL{ comphelper::string::strip(o3tl::getToken(aValue, 0, ';', nIdx ), ' ') };
+ uno::Reference<document::XDocumentProperties> xDocProps(
+ pDoc->getDocProperties());
+ if( aURL.startsWithIgnoreAsciiCase( "url=" ) )
+ {
+ try {
+ xDocProps->setAutoloadURL(
+ rtl::Uri::convertRelToAbs(pDoc->GetMedium()->GetName(), aURL.copy( 4 )) );
+ } catch (rtl::MalformedUriException &) {
+ TOOLS_WARN_EXCEPTION("sfx", "");
+ }
+ }
+ try
+ {
+ xDocProps->setAutoloadSecs( nTime );
+ }
+ catch (lang::IllegalArgumentException &)
+ {
+ // ignore
+ }
+ }
+ else if( rKV.GetKey().equalsIgnoreAsciiCase( "expires" ) )
+ {
+ DateTime aDateTime( DateTime::EMPTY );
+ if( INetMIMEMessage::ParseDateField( rKV.GetValue(), aDateTime ) )
+ {
+ aDateTime.ConvertToLocalTime();
+ pDoc->GetMedium()->SetExpired_Impl( aDateTime );
+ }
+ else
+ {
+ pDoc->GetMedium()->SetExpired_Impl( Date( 1, 1, 1970 ) );
+ }
+ }
+}
+
+void SfxHeaderAttributes_Impl::Append( const SvKeyValue& rKV )
+{
+ xIter->Append( rKV );
+ if( bAlert ) SetAttribute( rKV );
+}
+
+SvKeyValueIterator* SfxObjectShell::GetHeaderAttributes()
+{
+ if( !pImpl->xHeaderAttributes.is() )
+ {
+ DBG_ASSERT( pMedium, "No Medium" );
+ pImpl->xHeaderAttributes = new SfxHeaderAttributes_Impl( this );
+ }
+ return pImpl->xHeaderAttributes.get();
+}
+
+void SfxObjectShell::ClearHeaderAttributesForSourceViewHack()
+{
+ static_cast<SfxHeaderAttributes_Impl*>(GetHeaderAttributes())
+ ->ClearForSourceView();
+}
+
+
+void SfxObjectShell::SetHeaderAttributesForSourceViewHack()
+{
+ static_cast<SfxHeaderAttributes_Impl*>(GetHeaderAttributes())
+ ->SetAttributes();
+}
+
+bool SfxObjectShell::IsPreview() const
+{
+ if ( !pMedium )
+ return false;
+
+ bool bPreview = false;
+ const SfxStringItem* pFlags = pMedium->GetItemSet().GetItem(SID_OPTIONS, false);
+ if ( pFlags )
+ {
+ // Distributed values among individual items
+ const OUString aFileFlags = pFlags->GetValue().toAsciiUpperCase();
+ if ( -1 != aFileFlags.indexOf( 'B' ) )
+ bPreview = true;
+ }
+
+ if ( !bPreview )
+ {
+ const SfxBoolItem* pItem = pMedium->GetItemSet().GetItem(SID_PREVIEW, false);
+ if ( pItem )
+ bPreview = pItem->GetValue();
+ }
+
+ return bPreview;
+}
+
+void SfxObjectShell::SetWaitCursor( bool bSet ) const
+{
+ for( SfxViewFrame* pFrame = SfxViewFrame::GetFirst( this ); pFrame; pFrame = SfxViewFrame::GetNext( *pFrame, this ) )
+ {
+ if ( bSet )
+ pFrame->GetFrame().GetWindow().EnterWait();
+ else
+ pFrame->GetFrame().GetWindow().LeaveWait();
+ }
+}
+
+OUString SfxObjectShell::GetAPIName() const
+{
+ INetURLObject aURL( IsDocShared() ? GetSharedFileURL() : GetMedium()->GetName() );
+ OUString aName( aURL.GetBase() );
+ if( aName.isEmpty() )
+ aName = aURL.GetURLNoPass();
+ if ( aName.isEmpty() )
+ aName = GetTitle( SFX_TITLE_DETECT );
+ return aName;
+}
+
+void SfxObjectShell::Invalidate( sal_uInt16 nId )
+{
+ for( SfxViewFrame* pFrame = SfxViewFrame::GetFirst( this ); pFrame; pFrame = SfxViewFrame::GetNext( *pFrame, this ) )
+ Invalidate_Impl( pFrame->GetBindings(), nId );
+}
+
+bool SfxObjectShell::AdjustMacroMode()
+{
+ uno::Reference< task::XInteractionHandler > xInteraction;
+ if ( pMedium )
+ xInteraction = pMedium->GetInteractionHandler();
+
+ CheckForBrokenDocSignatures_Impl();
+
+ CheckEncryption_Impl( xInteraction );
+
+ return pImpl->aMacroMode.adjustMacroMode( xInteraction );
+}
+
+css::uno::Reference<css::awt::XWindow> SfxObjectShell::GetDialogParent( SfxMedium const * pLoadingMedium )
+{
+ css::uno::Reference<css::awt::XWindow> xWindow;
+ SfxItemSet& rSet = pLoadingMedium ? pLoadingMedium->GetItemSet() : GetMedium()->GetItemSet();
+ const SfxUnoFrameItem* pUnoItem = rSet.GetItem(SID_FILLFRAME, false);
+ if ( pUnoItem )
+ {
+ const uno::Reference < frame::XFrame >& xFrame( pUnoItem->GetFrame() );
+ xWindow = xFrame->getContainerWindow();
+ }
+
+ if (!xWindow)
+ {
+ SfxFrame* pFrame = nullptr;
+ const SfxFrameItem* pFrameItem = rSet.GetItem<SfxFrameItem>(SID_DOCFRAME, false);
+ if( pFrameItem && pFrameItem->GetFrame() )
+ // get target frame from ItemSet
+ pFrame = pFrameItem->GetFrame();
+ else
+ {
+ // try the current frame
+ SfxViewFrame* pView = SfxViewFrame::Current();
+ if ( !pView || pView->GetObjectShell() != this )
+ // get any visible frame
+ pView = SfxViewFrame::GetFirst(this);
+ if ( pView )
+ pFrame = &pView->GetFrame();
+ }
+
+ if ( pFrame )
+ {
+ // get topmost window
+ xWindow = pFrame->GetFrameInterface()->getContainerWindow();
+ }
+ }
+
+ if (xWindow)
+ {
+ // this frame may be invisible, show it if it is allowed
+ const SfxBoolItem* pHiddenItem = rSet.GetItem(SID_HIDDEN, false);
+ if ( !pHiddenItem || !pHiddenItem->GetValue() )
+ {
+ xWindow->setVisible(true);
+ css::uno::Reference<css::awt::XTopWindow> xTopWindow(xWindow, uno::UNO_QUERY);
+ SAL_WARN_IF(!xTopWindow, "sfx.appl", "XTopWindow not available from XWindow");
+ if (xTopWindow)
+ xTopWindow->toFront();
+ }
+ }
+
+ return xWindow;
+}
+
+void SfxObjectShell::SetCreateMode_Impl( SfxObjectCreateMode nMode )
+{
+ eCreateMode = nMode;
+}
+
+bool SfxObjectShell::IsInPlaceActive() const
+{
+ if ( eCreateMode != SfxObjectCreateMode::EMBEDDED )
+ return false;
+
+ SfxViewFrame* pFrame = SfxViewFrame::GetFirst( this );
+ return pFrame && pFrame->GetFrame().IsInPlace();
+}
+
+bool SfxObjectShell::IsUIActive() const
+{
+ if ( eCreateMode != SfxObjectCreateMode::EMBEDDED )
+ return false;
+
+ SfxViewFrame* pFrame = SfxViewFrame::GetFirst( this );
+ return pFrame && pFrame->GetFrame().IsInPlace() && pFrame->GetFrame().GetWorkWindow_Impl()->IsVisible_Impl();
+}
+
+bool SfxObjectShell::UseInteractionToHandleError(
+ const uno::Reference< task::XInteractionHandler >& xHandler,
+ const ErrCodeMsg& nError )
+{
+ bool bResult = false;
+
+ if ( xHandler.is() )
+ {
+ try
+ {
+ uno::Any aInteraction;
+ rtl::Reference<::comphelper::OInteractionAbort> pAbort = new ::comphelper::OInteractionAbort();
+ rtl::Reference<::comphelper::OInteractionApprove> pApprove = new ::comphelper::OInteractionApprove();
+ uno::Sequence< uno::Reference< task::XInteractionContinuation > > lContinuations{
+ pAbort, pApprove
+ };
+
+ task::ErrorCodeRequest2 aErrorCode(OUString(), uno::Reference<XInterface>(),
+ sal_Int32(sal_uInt32(nError.GetCode())), nError.GetArg1(), nError.GetArg2(),
+ static_cast<sal_Int16>(nError.GetDialogMask()));
+ aInteraction <<= aErrorCode;
+ xHandler->handle(::framework::InteractionRequest::CreateRequest (aInteraction,lContinuations));
+ bResult = pAbort->wasSelected();
+ }
+ catch( uno::Exception& )
+ {}
+ }
+
+ return bResult;
+}
+
+sal_Int16 SfxObjectShell_Impl::getCurrentMacroExecMode() const
+{
+ sal_Int16 nImposedExecMode( MacroExecMode::NEVER_EXECUTE );
+
+ const SfxMedium* pMedium( rDocShell.GetMedium() );
+ OSL_PRECOND( pMedium, "SfxObjectShell_Impl::getCurrentMacroExecMode: no medium!" );
+ if ( pMedium )
+ {
+ const SfxUInt16Item* pMacroModeItem = pMedium->GetItemSet().GetItem(SID_MACROEXECMODE, false);
+ if ( pMacroModeItem )
+ nImposedExecMode = pMacroModeItem->GetValue();
+ }
+ return nImposedExecMode;
+}
+
+void SfxObjectShell_Impl::setCurrentMacroExecMode( sal_uInt16 nMacroMode )
+{
+ const SfxMedium* pMedium( rDocShell.GetMedium() );
+ OSL_PRECOND( pMedium, "SfxObjectShell_Impl::getCurrentMacroExecMode: no medium!" );
+ if ( pMedium )
+ {
+ pMedium->GetItemSet().Put( SfxUInt16Item( SID_MACROEXECMODE, nMacroMode ) );
+ }
+}
+
+OUString SfxObjectShell_Impl::getDocumentLocation() const
+{
+ OUString sLocation;
+
+ const SfxMedium* pMedium( rDocShell.GetMedium() );
+ OSL_PRECOND( pMedium, "SfxObjectShell_Impl::getDocumentLocation: no medium!" );
+ if ( pMedium )
+ {
+ sLocation = pMedium->GetName();
+ if ( sLocation.isEmpty() )
+ {
+ // for documents made from a template: get the name of the template
+ sLocation = rDocShell.getDocProperties()->getTemplateURL();
+ }
+
+ // tdf#128006 take document base url as location
+ if (sLocation.isEmpty())
+ sLocation = rDocShell.getDocumentBaseURL();
+ }
+
+ return sLocation;
+}
+
+bool SfxObjectShell_Impl::documentStorageHasMacros() const
+{
+ return ::sfx2::DocumentMacroMode::storageHasMacros( m_xDocStorage );
+}
+
+bool SfxObjectShell_Impl::macroCallsSeenWhileLoading() const
+{
+ return rDocShell.GetMacroCallsSeenWhileLoading();
+}
+
+Reference< XEmbeddedScripts > SfxObjectShell_Impl::getEmbeddedDocumentScripts() const
+{
+ return Reference< XEmbeddedScripts >( rDocShell.GetModel(), UNO_QUERY );
+}
+
+SignatureState SfxObjectShell_Impl::getScriptingSignatureState()
+{
+ SignatureState nSignatureState( rDocShell.GetScriptingSignatureState() );
+
+ if ( nSignatureState != SignatureState::NOSIGNATURES && m_bMacroSignBroken )
+ {
+ // if there is a macro signature it must be handled as broken
+ nSignatureState = SignatureState::BROKEN;
+ }
+
+ return nSignatureState;
+}
+
+bool SfxObjectShell_Impl::hasTrustedScriptingSignature(
+ const css::uno::Reference<css::task::XInteractionHandler>& _rxInteraction)
+{
+ bool bResult = false;
+
+ try
+ {
+ if ( nScriptingSignatureState == SignatureState::UNKNOWN
+ || nScriptingSignatureState == SignatureState::OK
+ || nScriptingSignatureState == SignatureState::NOTVALIDATED )
+ {
+ OUString aVersion;
+ try
+ {
+ uno::Reference < beans::XPropertySet > xPropSet( rDocShell.GetStorage(), uno::UNO_QUERY_THROW );
+ xPropSet->getPropertyValue("Version") >>= aVersion;
+ }
+ catch( uno::Exception& )
+ {
+ }
+
+ uno::Reference< security::XDocumentDigitalSignatures > xSigner( security::DocumentDigitalSignatures::createWithVersion(comphelper::getProcessComponentContext(), aVersion) );
+
+ const uno::Sequence< security::DocumentSignatureInformation > aInfo = rDocShell.GetDocumentSignatureInformation( true, xSigner );
+
+ if ( aInfo.hasElements() )
+ {
+ if ( nScriptingSignatureState == SignatureState::UNKNOWN )
+ nScriptingSignatureState = DocumentSignatures::getSignatureState(aInfo);
+
+ if ( nScriptingSignatureState == SignatureState::OK
+ || nScriptingSignatureState == SignatureState::NOTVALIDATED )
+ {
+ bResult = std::any_of(aInfo.begin(), aInfo.end(),
+ [&xSigner](const security::DocumentSignatureInformation& rInfo) {
+ return xSigner->isAuthorTrusted( rInfo.Signer ); });
+
+ if (!bResult && _rxInteraction)
+ {
+ task::DocumentMacroConfirmationRequest aRequest;
+ aRequest.DocumentURL = getDocumentLocation();
+ aRequest.DocumentStorage = rDocShell.GetMedium()->GetScriptingStorageToSign_Impl();
+ aRequest.DocumentSignatureInformation = aInfo;
+ aRequest.DocumentVersion = aVersion;
+ aRequest.Classification = task::InteractionClassification_QUERY;
+ bResult = SfxMedium::CallApproveHandler( _rxInteraction, uno::Any( aRequest ), true );
+ }
+ }
+ }
+ }
+ }
+ catch( uno::Exception& )
+ {}
+
+ return bResult;
+}
+
+bool SfxObjectShell::IsContinueImportOnFilterExceptions()
+{
+ if (mbContinueImportOnFilterExceptions == undefined)
+ {
+ if (!pMedium)
+ {
+ mbContinueImportOnFilterExceptions = no;
+ return false;
+ }
+
+ if (utl::MediaDescriptor desc(pMedium->GetArgs());
+ !desc.getUnpackedValueOrDefault("RepairAllowed", true))
+ {
+ mbContinueImportOnFilterExceptions = no;
+ return false;
+ }
+
+ if (const SfxBoolItem* pRepairItem
+ = pMedium->GetItemSet().GetItem(SID_REPAIRPACKAGE, false);
+ pRepairItem && pRepairItem->GetValue())
+ {
+ mbContinueImportOnFilterExceptions = yes;
+ return true;
+ }
+
+ auto xInteractionHandler = pMedium->GetInteractionHandler();
+ if (!xInteractionHandler)
+ {
+ mbContinueImportOnFilterExceptions = no;
+ return false;
+ }
+
+ const OUString aDocName(pMedium->GetURLObject().getName(
+ INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset));
+ RequestPackageReparation aRequest(aDocName);
+ xInteractionHandler->handle(aRequest.GetRequest());
+ if (aRequest.isApproved())
+ {
+ mbContinueImportOnFilterExceptions = yes;
+ // lok: we want to overwrite file in jail, so don't use template flag
+ bool bIsLOK = comphelper::LibreOfficeKit::isActive();
+ // allow repair
+ pMedium->GetItemSet().Put(SfxBoolItem(SID_REPAIRPACKAGE, true));
+ pMedium->GetItemSet().Put(SfxBoolItem(SID_TEMPLATE, !bIsLOK));
+ pMedium->GetItemSet().Put(SfxStringItem(SID_DOCINFO_TITLE, aDocName));
+ }
+ else
+ mbContinueImportOnFilterExceptions = no;
+ }
+ return mbContinueImportOnFilterExceptions == yes;
+}
+
+bool SfxObjectShell::isEditDocLocked() const
+{
+ Reference<XModel3> xModel = GetModel();
+ if (!xModel.is())
+ return false;
+ if (!officecfg::Office::Common::Misc::AllowEditReadonlyDocs::get())
+ return true;
+ return comphelper::NamedValueCollection::getOrDefault(xModel->getArgs2( { "LockEditDoc" } ), u"LockEditDoc", false);
+}
+
+bool SfxObjectShell::isContentExtractionLocked() const
+{
+ Reference<XModel3> xModel = GetModel();
+ if (!xModel.is())
+ return false;
+ return comphelper::NamedValueCollection::getOrDefault(xModel->getArgs2( { "LockContentExtraction" } ), u"LockContentExtraction", false);
+}
+
+bool SfxObjectShell::isExportLocked() const
+{
+ Reference<XModel3> xModel = GetModel();
+ if (!xModel.is())
+ return false;
+ return comphelper::NamedValueCollection::getOrDefault(xModel->getArgs2( { "LockExport" } ), u"LockExport", false);
+}
+
+bool SfxObjectShell::isPrintLocked() const
+{
+ Reference<XModel3> xModel = GetModel();
+ if (!xModel.is())
+ return false;
+ return comphelper::NamedValueCollection::getOrDefault(xModel->getArgs2( { "LockPrint" } ), u"LockPrint", false);
+}
+
+bool SfxObjectShell::isSaveLocked() const
+{
+ Reference<XModel3> xModel = GetModel();
+ if (!xModel.is())
+ return false;
+ return comphelper::NamedValueCollection::getOrDefault(xModel->getArgs2( { "LockSave" } ), u"LockSave", false);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/objserv.cxx b/sfx2/source/doc/objserv.cxx
new file mode 100644
index 0000000000..fedbfb205d
--- /dev/null
+++ b/sfx2/source/doc/objserv.cxx
@@ -0,0 +1,2285 @@
+/* -*- 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 <config_features.h>
+
+#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
+#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
+#include <com/sun/star/util/CloseVetoException.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/document/XCmisDocument.hpp>
+#include <com/sun/star/drawing/LineStyle.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/security/XCertificate.hpp>
+#include <com/sun/star/task/ErrorCodeIOException.hpp>
+#include <com/sun/star/task/InteractionHandler.hpp>
+#include <com/sun/star/task/XStatusIndicator.hpp>
+#include <com/sun/star/task/XStatusIndicatorFactory.hpp>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/servicehelper.hxx>
+#include <com/sun/star/drawing/XDrawView.hpp>
+
+#include <com/sun/star/security/DocumentSignatureInformation.hpp>
+#include <com/sun/star/security/DocumentDigitalSignatures.hpp>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/urlobj.hxx>
+#include <svl/whiter.hxx>
+#include <svl/intitem.hxx>
+#include <svl/eitem.hxx>
+#include <svl/visitem.hxx>
+#include <svtools/sfxecode.hxx>
+#include <svtools/ehdl.hxx>
+#include <sal/log.hxx>
+#include <sfx2/app.hxx>
+
+#include <comphelper/string.hxx>
+#include <basic/sbxcore.hxx>
+#include <basic/sberrors.hxx>
+#include <unotools/moduleoptions.hxx>
+#include <unotools/saveopt.hxx>
+#include <unotools/securityoptions.hxx>
+#include <svtools/DocumentToGraphicRenderer.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <comphelper/documentconstants.hxx>
+#include <comphelper/storagehelper.hxx>
+#include <comphelper/lok.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <tools/link.hxx>
+
+#include <sfx2/signaturestate.hxx>
+#include <sfx2/sfxresid.hxx>
+#include <sfx2/request.hxx>
+#include <sfx2/printer.hxx>
+#include <sfx2/viewsh.hxx>
+#include <sfx2/dinfdlg.hxx>
+#include <sfx2/docfilt.hxx>
+#include <sfx2/docfile.hxx>
+#include <sfx2/dispatch.hxx>
+#include <sfx2/objitem.hxx>
+#include <sfx2/objsh.hxx>
+#include <objshimp.hxx>
+#include <sfx2/module.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <versdlg.hxx>
+#include <sfx2/strings.hrc>
+#include <sfx2/docfac.hxx>
+#include <sfx2/fcontnr.hxx>
+#include <sfx2/msgpool.hxx>
+#include <sfx2/objface.hxx>
+#include <checkin.hxx>
+#include <sfx2/infobar.hxx>
+#include <sfx2/sfxuno.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <SfxRedactionHelper.hxx>
+
+#include <com/sun/star/util/XCloseable.hpp>
+#include <com/sun/star/document/XDocumentProperties.hpp>
+
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/frame/XDesktop2.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+
+#include <guisaveas.hxx>
+#include <saveastemplatedlg.hxx>
+#include <memory>
+#include <cppuhelper/implbase.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <unotools/streamwrap.hxx>
+#include <comphelper/sequenceashashmap.hxx>
+#include <editeng/unoprnms.hxx>
+
+#include <autoredactdialog.hxx>
+
+#include <boost/property_tree/json_parser.hpp>
+
+#define ShellClass_SfxObjectShell
+#include <sfxslots.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::awt;
+using namespace ::com::sun::star::container;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::document;
+using namespace ::com::sun::star::security;
+using namespace ::com::sun::star::task;
+using namespace ::com::sun::star::graphic;
+
+SFX_IMPL_SUPERCLASS_INTERFACE(SfxObjectShell, SfxShell)
+
+void SfxObjectShell::InitInterface_Impl()
+{
+}
+
+namespace {
+
+class SfxClosePreventer_Impl : public ::cppu::WeakImplHelper< css::util::XCloseListener >
+{
+ bool m_bGotOwnership;
+ bool m_bPreventClose;
+
+public:
+ SfxClosePreventer_Impl();
+
+ bool HasOwnership() const { return m_bGotOwnership; }
+
+ void SetPreventClose( bool bPrevent ) { m_bPreventClose = bPrevent; }
+
+ virtual void SAL_CALL queryClosing( const lang::EventObject& aEvent, sal_Bool bDeliverOwnership ) override;
+
+ virtual void SAL_CALL notifyClosing( const lang::EventObject& aEvent ) override ;
+
+ virtual void SAL_CALL disposing( const lang::EventObject& aEvent ) override ;
+
+} ;
+
+}
+
+SfxClosePreventer_Impl::SfxClosePreventer_Impl()
+: m_bGotOwnership( false )
+, m_bPreventClose( true )
+{
+}
+
+void SAL_CALL SfxClosePreventer_Impl::queryClosing( const lang::EventObject&, sal_Bool bDeliverOwnership )
+{
+ if ( m_bPreventClose )
+ {
+ if ( !m_bGotOwnership )
+ m_bGotOwnership = bDeliverOwnership;
+
+ throw util::CloseVetoException();
+ }
+}
+
+void SAL_CALL SfxClosePreventer_Impl::notifyClosing( const lang::EventObject& )
+{}
+
+void SAL_CALL SfxClosePreventer_Impl::disposing( const lang::EventObject& )
+{}
+
+namespace {
+
+class SfxInstanceCloseGuard_Impl
+{
+ rtl::Reference<SfxClosePreventer_Impl> m_xPreventer;
+ uno::Reference< util::XCloseable > m_xCloseable;
+
+public:
+ SfxInstanceCloseGuard_Impl() {}
+
+ ~SfxInstanceCloseGuard_Impl();
+
+ bool Init_Impl( const uno::Reference< util::XCloseable >& xCloseable );
+};
+
+}
+
+bool SfxInstanceCloseGuard_Impl::Init_Impl( const uno::Reference< util::XCloseable >& xCloseable )
+{
+ bool bResult = false;
+
+ // do not allow reinit after the successful init
+ if ( xCloseable.is() && !m_xCloseable.is() )
+ {
+ try
+ {
+ m_xPreventer = new SfxClosePreventer_Impl();
+ xCloseable->addCloseListener( m_xPreventer );
+ m_xCloseable = xCloseable;
+ bResult = true;
+ }
+ catch( uno::Exception& )
+ {
+ OSL_FAIL( "Could not register close listener!" );
+ }
+ }
+
+ return bResult;
+}
+
+SfxInstanceCloseGuard_Impl::~SfxInstanceCloseGuard_Impl()
+{
+ if ( !m_xCloseable.is() || !m_xPreventer.is() )
+ return;
+
+ try
+ {
+ m_xCloseable->removeCloseListener( m_xPreventer );
+ }
+ catch( uno::Exception& )
+ {
+ }
+
+ try
+ {
+ if ( m_xPreventer.is() )
+ {
+ m_xPreventer->SetPreventClose( false );
+
+ if ( m_xPreventer->HasOwnership() )
+ m_xCloseable->close( true ); // TODO: do it asynchronously
+ }
+ }
+ catch( uno::Exception& )
+ {
+ }
+}
+
+
+void SfxObjectShell::PrintExec_Impl(SfxRequest &rReq)
+{
+ SfxViewFrame *pFrame = SfxViewFrame::GetFirst(this);
+ if ( pFrame )
+ {
+ rReq.SetSlot( SID_PRINTDOC );
+ pFrame->GetViewShell()->ExecuteSlot(rReq);
+ }
+}
+
+
+void SfxObjectShell::PrintState_Impl(SfxItemSet &rSet)
+{
+ bool bPrinting = false;
+ SfxViewFrame* pFrame = SfxViewFrame::GetFirst( this );
+ if ( pFrame )
+ {
+ SfxPrinter *pPrinter = pFrame->GetViewShell()->GetPrinter();
+ bPrinting = pPrinter && pPrinter->IsPrinting();
+ }
+ rSet.Put( SfxBoolItem( SID_PRINTOUT, bPrinting ) );
+}
+
+bool SfxObjectShell::APISaveAs_Impl(std::u16string_view aFileName, SfxItemSet& rItemSet,
+ const css::uno::Sequence<css::beans::PropertyValue>& rArgs)
+{
+ bool bOk = false;
+
+ if ( GetMedium() )
+ {
+ OUString aFilterName;
+ const SfxStringItem* pFilterNameItem = rItemSet.GetItem<SfxStringItem>(SID_FILTER_NAME, false);
+ if( pFilterNameItem )
+ {
+ aFilterName = pFilterNameItem->GetValue();
+ }
+ else
+ {
+ const SfxStringItem* pContentTypeItem = rItemSet.GetItem<SfxStringItem>(SID_CONTENTTYPE, false);
+ if ( pContentTypeItem )
+ {
+ std::shared_ptr<const SfxFilter> pFilter = SfxFilterMatcher( GetFactory().GetFactoryName() ).GetFilter4Mime( pContentTypeItem->GetValue(), SfxFilterFlags::EXPORT );
+ if ( pFilter )
+ aFilterName = pFilter->GetName();
+ }
+ }
+
+ // in case no filter defined use default one
+ if( aFilterName.isEmpty() )
+ {
+ std::shared_ptr<const SfxFilter> pFilt = SfxFilter::GetDefaultFilterFromFactory(GetFactory().GetFactoryName());
+
+ DBG_ASSERT( pFilt, "No default filter!\n" );
+ if( pFilt )
+ aFilterName = pFilt->GetFilterName();
+
+ rItemSet.Put(SfxStringItem(SID_FILTER_NAME, aFilterName));
+ }
+
+
+ {
+ SfxObjectShellRef xLock( this ); // ???
+
+ // use the title that is provided in the media descriptor
+ const SfxStringItem* pDocTitleItem = rItemSet.GetItem<SfxStringItem>(SID_DOCINFO_TITLE, false);
+ if ( pDocTitleItem )
+ getDocProperties()->setTitle( pDocTitleItem->GetValue() );
+
+ bOk = CommonSaveAs_Impl(INetURLObject(aFileName), aFilterName, rItemSet, rArgs);
+ }
+ }
+
+ return bOk;
+}
+
+void SfxObjectShell::CheckOut( )
+{
+ try
+ {
+ uno::Reference< document::XCmisDocument > xCmisDoc( GetModel(), uno::UNO_QUERY_THROW );
+ xCmisDoc->checkOut( );
+
+ // Remove the info bar
+ SfxViewFrame* pViewFrame = GetFrame();
+ pViewFrame->RemoveInfoBar( u"checkout" );
+ }
+ catch ( const uno::RuntimeException& e )
+ {
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(GetFrame()->GetFrameWeld(),
+ VclMessageType::Warning, VclButtonsType::Ok, e.Message));
+ xBox->run();
+ }
+}
+
+void SfxObjectShell::CancelCheckOut( )
+{
+ try
+ {
+ uno::Reference< document::XCmisDocument > xCmisDoc( GetModel(), uno::UNO_QUERY_THROW );
+ xCmisDoc->cancelCheckOut( );
+
+ uno::Reference< util::XModifiable > xModifiable( GetModel( ), uno::UNO_QUERY );
+ if ( xModifiable.is( ) )
+ xModifiable->setModified( false );
+ }
+ catch ( const uno::RuntimeException& e )
+ {
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(GetFrame()->GetFrameWeld(),
+ VclMessageType::Warning, VclButtonsType::Ok, e.Message));
+ xBox->run();
+ }
+}
+
+void SfxObjectShell::CheckIn( )
+{
+ try
+ {
+ uno::Reference< document::XCmisDocument > xCmisDoc( GetModel(), uno::UNO_QUERY_THROW );
+ // Pop up dialog to ask for comment and major
+ SfxCheckinDialog checkinDlg(GetFrame()->GetFrameWeld());
+ if (checkinDlg.run() == RET_OK)
+ {
+ xCmisDoc->checkIn(checkinDlg.IsMajor(), checkinDlg.GetComment());
+ uno::Reference< util::XModifiable > xModifiable( GetModel( ), uno::UNO_QUERY );
+ if ( xModifiable.is( ) )
+ xModifiable->setModified( false );
+ }
+ }
+ catch ( const uno::RuntimeException& e )
+ {
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(GetFrame()->GetFrameWeld(),
+ VclMessageType::Warning, VclButtonsType::Ok, e.Message));
+ xBox->run();
+ }
+}
+
+uno::Sequence< document::CmisVersion > SfxObjectShell::GetCmisVersions( ) const
+{
+ try
+ {
+ uno::Reference< document::XCmisDocument > xCmisDoc( GetModel(), uno::UNO_QUERY_THROW );
+ return xCmisDoc->getAllVersions( );
+ }
+ catch ( const uno::RuntimeException& e )
+ {
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(GetFrame()->GetFrameWeld(),
+ VclMessageType::Warning, VclButtonsType::Ok, e.Message));
+ xBox->run();
+ }
+ return uno::Sequence< document::CmisVersion > ( );
+}
+
+bool SfxObjectShell::IsSignPDF() const
+{
+ if (pMedium && !pMedium->IsOriginallyReadOnly())
+ {
+ const std::shared_ptr<const SfxFilter>& pFilter = pMedium->GetFilter();
+ if (pFilter && pFilter->GetName() == "draw_pdf_import")
+ return true;
+ }
+
+ return false;
+}
+
+uno::Reference<security::XCertificate> SfxObjectShell::GetSignPDFCertificate() const
+{
+ uno::Reference<frame::XModel> xModel = GetBaseModel();
+ if (!xModel.is())
+ {
+ return uno::Reference<security::XCertificate>();
+ }
+
+ uno::Reference<drawing::XShapes> xShapes(xModel->getCurrentSelection(), uno::UNO_QUERY);
+ if (!xShapes.is() || xShapes->getCount() < 1)
+ {
+ return uno::Reference<security::XCertificate>();
+ }
+
+ uno::Reference<beans::XPropertySet> xShapeProps(xShapes->getByIndex(0), uno::UNO_QUERY);
+ if (!xShapeProps.is())
+ {
+ return uno::Reference<security::XCertificate>();
+ }
+
+ if (!xShapeProps->getPropertySetInfo()->hasPropertyByName("InteropGrabBag"))
+ {
+ return uno::Reference<security::XCertificate>();
+ }
+
+ comphelper::SequenceAsHashMap aMap(xShapeProps->getPropertyValue("InteropGrabBag"));
+ auto it = aMap.find("SignatureCertificate");
+ if (it == aMap.end())
+ {
+ return uno::Reference<security::XCertificate>();
+ }
+
+ return uno::Reference<security::XCertificate>(it->second, uno::UNO_QUERY);
+}
+
+static void sendErrorToLOK(ErrCodeMsg error)
+{
+ if (error.GetCode().GetClass() == ErrCodeClass::NONE)
+ return;
+
+ boost::property_tree::ptree aTree;
+ aTree.put("code", error);
+ aTree.put("kind", "");
+ aTree.put("cmd", "");
+
+ OUString aErr;
+ if (ErrorStringFactory::CreateString(error, aErr))
+ aTree.put("message", aErr.toUtf8());
+
+ std::stringstream aStream;
+ boost::property_tree::write_json(aStream, aTree);
+
+ SfxViewShell::Current()->libreOfficeKitViewCallback(LOK_CALLBACK_ERROR, OString(aStream.str()));
+}
+
+namespace
+{
+void SetDocProperties(const uno::Reference<document::XDocumentProperties>& xDP,
+ const uno::Sequence<beans::PropertyValue>& rUpdatedProperties)
+{
+ comphelper::SequenceAsHashMap aMap(rUpdatedProperties);
+ OUString aNamePrefix;
+ auto it = aMap.find("NamePrefix");
+ if (it != aMap.end())
+ {
+ it->second >>= aNamePrefix;
+ }
+
+ uno::Sequence<beans::PropertyValue> aUserDefinedProperties;
+ it = aMap.find("UserDefinedProperties");
+ if (it != aMap.end())
+ {
+ it->second >>= aUserDefinedProperties;
+ }
+
+ uno::Reference<beans::XPropertyContainer> xUDP = xDP->getUserDefinedProperties();
+ if (!aNamePrefix.isEmpty())
+ {
+ uno::Reference<beans::XPropertySet> xSet(xUDP, UNO_QUERY);
+ uno::Reference<beans::XPropertySetInfo> xSetInfo = xSet->getPropertySetInfo();
+ const uno::Sequence<beans::Property> aProperties = xSetInfo->getProperties();
+ for (const auto& rProperty : aProperties)
+ {
+ if (!rProperty.Name.startsWith(aNamePrefix))
+ {
+ continue;
+ }
+
+ if (!(rProperty.Attributes & beans::PropertyAttribute::REMOVABLE))
+ {
+ continue;
+ }
+
+ xUDP->removeProperty(rProperty.Name);
+ }
+ }
+
+ for (const auto& rUserDefinedProperty : aUserDefinedProperties)
+ {
+ xUDP->addProperty(rUserDefinedProperty.Name, beans::PropertyAttribute::REMOVABLE,
+ rUserDefinedProperty.Value);
+ }
+}
+}
+
+void SfxObjectShell::ExecFile_Impl(SfxRequest &rReq)
+{
+ weld::Window* pDialogParent = rReq.GetFrameWeld();
+ if (!pDialogParent)
+ {
+ SfxViewFrame* pFrame = GetFrame();
+ if (!pFrame)
+ pFrame = SfxViewFrame::GetFirst(this);
+ if (pFrame)
+ pDialogParent = pFrame->GetFrameWeld();
+ }
+
+ sal_uInt16 nId = rReq.GetSlot();
+
+ bool bHaveWeSigned = false;
+
+ if( SID_SIGNATURE == nId || SID_MACRO_SIGNATURE == nId )
+ {
+ QueryHiddenInformation(HiddenWarningFact::WhenSigning);
+
+ if (SID_SIGNATURE == nId)
+ {
+ uno::Reference<security::XCertificate> xCertificate = GetSignPDFCertificate();
+ if (xCertificate.is())
+ {
+
+ bHaveWeSigned |= SignDocumentContentUsingCertificate(xCertificate);
+
+ // Reload to show how the PDF actually looks like after signing. This also
+ // changes "finish signing" on the infobar back to "sign document" as a side
+ // effect.
+ SfxViewFrame* pFrame = GetFrame();
+ if (pFrame)
+ {
+ // Store current page before reload.
+ SfxAllItemSet aSet(SfxGetpApp()->GetPool());
+ uno::Reference<drawing::XDrawView> xController(
+ GetBaseModel()->getCurrentController(), uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySet> xPage(xController->getCurrentPage(),
+ uno::UNO_QUERY);
+ sal_Int32 nPage{};
+ xPage->getPropertyValue("Number") >>= nPage;
+ if (nPage > 0)
+ {
+ // nPage is 1-based.
+ aSet.Put(SfxInt32Item(SID_PAGE_NUMBER, nPage - 1));
+ }
+ SfxRequest aReq(SID_RELOAD, SfxCallMode::SLOT, aSet);
+ pFrame->ExecReload_Impl(aReq);
+ }
+ }
+ else
+ {
+ bHaveWeSigned |= SignDocumentContent(pDialogParent);
+ }
+ }
+ else
+ {
+ bHaveWeSigned |= SignScriptingContent(pDialogParent);
+ }
+
+ if ( bHaveWeSigned && HasValidSignatures() )
+ {
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog( pDialogParent,
+ VclMessageType::Question, VclButtonsType::YesNo, SfxResId(STR_QUERY_REMEMBERSIGNATURE)));
+ if (xBox->run() == RET_YES)
+ {
+ rSignatureInfosRemembered = GetDocumentSignatureInformation(false);
+ bRememberSignature = true;
+ }
+ else
+ {
+ rSignatureInfosRemembered = uno::Sequence< security::DocumentSignatureInformation >();
+ bRememberSignature = false;
+ }
+ }
+
+ return;
+ }
+
+ if ( !GetMedium() && nId != SID_CLOSEDOC )
+ {
+ rReq.Ignore();
+ return;
+ }
+
+ // this guard is created here to have it destruction at the end of the method
+ SfxInstanceCloseGuard_Impl aModelGuard;
+
+ bool bIsPDFExport = false;
+ bool bIsAutoRedact = false;
+ bool bIsAsync = false;
+ std::vector<std::pair<RedactionTarget, OUString>> aRedactionTargets;
+ switch(nId)
+ {
+ case SID_VERSION:
+ {
+ SfxViewFrame* pFrame = GetFrame();
+ if ( !pFrame )
+ pFrame = SfxViewFrame::GetFirst( this );
+ if ( !pFrame )
+ return;
+
+ if ( !IsOwnStorageFormat( *GetMedium() ) )
+ return;
+
+ SfxVersionDialog aDlg(pDialogParent, pFrame, IsSaveVersionOnClose());
+ aDlg.run();
+ SetSaveVersionOnClose(aDlg.IsSaveVersionOnClose());
+ rReq.Done();
+ return;
+ }
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ case SID_DOCINFO:
+ {
+ const SfxDocumentInfoItem* pDocInfItem = rReq.GetArg<SfxDocumentInfoItem>(SID_DOCINFO);
+ if ( pDocInfItem )
+ {
+ // parameter, e.g. from replayed macro
+ pDocInfItem->UpdateDocumentInfo(getDocProperties(), true);
+ SetUseUserData( pDocInfItem->IsUseUserData() );
+ SetUseThumbnailSave( pDocInfItem->IsUseThumbnailSave() );
+ }
+ else if (const SfxUnoAnyItem* pItem = rReq.GetArg<SfxUnoAnyItem>(FN_PARAM_1))
+ {
+ uno::Sequence<beans::PropertyValue> aUpdatedProperties;
+ pItem->GetValue() >>= aUpdatedProperties;
+ SetDocProperties(getDocProperties(), aUpdatedProperties);
+ }
+ else
+ {
+ // no argument containing DocInfo; check optional arguments
+ bool bReadOnly = IsReadOnly();
+ const SfxBoolItem* pROItem = rReq.GetArg<SfxBoolItem>(SID_DOC_READONLY);
+ if ( pROItem )
+ // override readonly attribute of document
+ // e.g. if a readonly document is saved elsewhere and user asks for editing DocInfo before
+ bReadOnly = pROItem->GetValue();
+
+ // URL for dialog
+ const OUString aURL( HasName() ? GetMedium()->GetName() : GetFactory().GetFactoryURL() );
+
+ Reference< XCmisDocument > xCmisDoc( GetModel(), uno::UNO_QUERY );
+ uno::Sequence< document::CmisProperty> aCmisProperties = xCmisDoc->getCmisProperties();
+
+ SfxDocumentInfoItem aDocInfoItem( aURL, getDocProperties(), aCmisProperties,
+ IsUseUserData(), IsUseThumbnailSave() );
+ const SfxPoolItemHolder aSlotState(GetSlotState(SID_DOCTEMPLATE));
+ if (nullptr == aSlotState.getItem())
+ // templates not supported
+ aDocInfoItem.SetTemplate(false);
+
+ SfxItemSetFixed<SID_DOCINFO, SID_DOCINFO, SID_DOC_READONLY, SID_DOC_READONLY,
+ SID_EXPLORER_PROPS_START, SID_EXPLORER_PROPS_START, SID_BASEURL, SID_BASEURL>
+ aSet(GetPool());
+ aSet.Put( aDocInfoItem );
+ aSet.Put( SfxBoolItem( SID_DOC_READONLY, bReadOnly ) );
+ aSet.Put( SfxStringItem( SID_EXPLORER_PROPS_START, GetTitle() ) );
+ aSet.Put( SfxStringItem( SID_BASEURL, GetMedium()->GetBaseURL() ) );
+
+ // creating dialog is done via virtual method; application will
+ // add its own statistics page
+ std::shared_ptr<SfxDocumentInfoDialog> xDlg(CreateDocumentInfoDialog(rReq.GetFrameWeld(), aSet));
+ auto aFunc = [this, xDlg, xCmisDoc](sal_Int32 nResult, SfxRequest& rRequest)
+ {
+ if (RET_OK == nResult)
+ {
+ const SfxDocumentInfoItem* pDocInfoItem = SfxItemSet::GetItem(xDlg->GetOutputItemSet(), SID_DOCINFO, false);
+ if ( pDocInfoItem )
+ {
+ // user has done some changes to DocumentInfo
+ pDocInfoItem->UpdateDocumentInfo(getDocProperties());
+ const uno::Sequence< document::CmisProperty >& aNewCmisProperties =
+ pDocInfoItem->GetCmisProperties( );
+ if ( aNewCmisProperties.hasElements( ) )
+ xCmisDoc->updateCmisProperties( aNewCmisProperties );
+ SetUseUserData( pDocInfoItem->IsUseUserData() );
+ SetUseThumbnailSave( pDocInfoItem-> IsUseThumbnailSave() );
+ // add data from dialog for possible recording purpose
+ rRequest.AppendItem( SfxDocumentInfoItem( GetTitle(),
+ getDocProperties(), aNewCmisProperties, IsUseUserData(), IsUseThumbnailSave() ) );
+ }
+ rRequest.Done();
+ }
+ else
+ {
+ // nothing done; no recording
+ rRequest.Ignore();
+ }
+ };
+
+ if (!rReq.IsSynchronCall())
+ {
+ std::shared_ptr<SfxRequest> pReq = std::make_shared<SfxRequest>(rReq);
+ SfxTabDialogController::runAsync(xDlg, [pReq, aFunc](sal_Int32 nResult)
+ {
+ aFunc(nResult, *pReq);
+ });
+ rReq.Ignore();
+ }
+ else
+ {
+ aFunc(xDlg->run(), rReq);
+ }
+ }
+
+ return;
+ }
+
+ case SID_AUTOREDACTDOC:
+ {
+ // Actual redaction takes place on a newly generated Draw document
+ if (!SvtModuleOptions().IsModuleInstalled(SvtModuleOptions::EModule::DRAW))
+ {
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(
+ pDialogParent, VclMessageType::Warning, VclButtonsType::Ok,
+ SfxResId(STR_REDACTION_NO_DRAW_WARNING)));
+
+ xBox->run();
+
+ return;
+ }
+
+ SfxAutoRedactDialog aDlg(pDialogParent);
+ sal_Int16 nResult = aDlg.run();
+
+ if (nResult != RET_OK || !aDlg.hasTargets() || !aDlg.isValidState())
+ {
+ //Do nothing
+ return;
+ }
+
+ // else continue with normal redaction
+ bIsAutoRedact = true;
+ aDlg.getTargets(aRedactionTargets);
+
+ [[fallthrough]];
+ }
+
+ case SID_REDACTDOC:
+ {
+ css::uno::Reference<css::frame::XModel> xModel = GetModel();
+ if(!xModel.is())
+ return;
+
+ uno::Reference< lang::XComponent > xSourceDoc( xModel );
+
+ // Actual redaction takes place on a newly generated Draw document
+ if (!SvtModuleOptions().IsModuleInstalled(SvtModuleOptions::EModule::DRAW))
+ {
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(
+ pDialogParent, VclMessageType::Warning, VclButtonsType::Ok,
+ SfxResId(STR_REDACTION_NO_DRAW_WARNING)));
+
+ xBox->run();
+
+ return;
+ }
+
+ DocumentToGraphicRenderer aRenderer(xSourceDoc, false);
+
+ // Get the page margins of the original doc
+ PageMargins aPageMargins = {-1, -1, -1, -1};
+ if (aRenderer.isWriter())
+ aPageMargins = SfxRedactionHelper::getPageMarginsForWriter(xModel);
+ else if (aRenderer.isCalc())
+ aPageMargins = SfxRedactionHelper::getPageMarginsForCalc(xModel);
+
+ sal_Int32 nPages = aRenderer.getPageCount();
+ std::vector< GDIMetaFile > aMetaFiles;
+ std::vector< ::Size > aPageSizes;
+
+ // Convert the pages of the document to gdimetafiles
+ SfxRedactionHelper::getPageMetaFilesFromDoc(aMetaFiles, aPageSizes, nPages, aRenderer);
+
+ // Create an empty Draw component.
+ uno::Reference<frame::XDesktop2> xDesktop = css::frame::Desktop::create(comphelper::getProcessComponentContext());
+ uno::Reference<lang::XComponent> xComponent = xDesktop->loadComponentFromURL("private:factory/sdraw", "_default", 0, {});
+
+ if (!xComponent.is())
+ {
+ SAL_WARN("sfx.doc", "SID_REDACTDOC: Failed to load new draw component. loadComponentFromURL returned an empty reference.");
+
+ return;
+ }
+
+ // Add the doc pages to the new draw document
+ SfxRedactionHelper::addPagesToDraw(xComponent, nPages, aMetaFiles, aPageSizes, aPageMargins, aRedactionTargets, bIsAutoRedact);
+
+ // Show the Redaction toolbar
+ SfxViewFrame* pViewFrame = SfxViewFrame::Current();
+ if (!pViewFrame)
+ return;
+ SfxRedactionHelper::showRedactionToolbar(pViewFrame);
+
+ return;
+ }
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ case SID_DIRECTEXPORTDOCASPDF:
+ {
+ uno::Reference< lang::XComponent > xComponent( GetCurrentComponent(), uno::UNO_QUERY );
+ if (!xComponent.is())
+ return;
+
+ uno::Reference< lang::XServiceInfo > xServiceInfo( xComponent, uno::UNO_QUERY);
+
+ // Redaction finalization takes place in Draw
+ if ( xServiceInfo.is() && xServiceInfo->supportsService("com.sun.star.drawing.DrawingDocument")
+ && SfxRedactionHelper::isRedactMode(rReq) )
+ {
+ OUString sRedactionStyle(SfxRedactionHelper::getStringParam(rReq, SID_REDACTION_STYLE));
+
+ // Access the draw pages
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(xComponent, uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPages> xDrawPages = xDrawPagesSupplier->getDrawPages();
+
+ sal_Int32 nPageCount = xDrawPages->getCount();
+ for (sal_Int32 nPageNum = 0; nPageNum < nPageCount; ++nPageNum)
+ {
+ // Get the page
+ uno::Reference< drawing::XDrawPage > xPage( xDrawPages->getByIndex( nPageNum ), uno::UNO_QUERY );
+
+ if (!xPage.is())
+ continue;
+
+ // Go through all shapes
+ sal_Int32 nShapeCount = xPage->getCount();
+ for (sal_Int32 nShapeNum = 0; nShapeNum < nShapeCount; ++nShapeNum)
+ {
+ uno::Reference< drawing::XShape > xCurrShape(xPage->getByIndex(nShapeNum), uno::UNO_QUERY);
+ if (!xCurrShape.is())
+ continue;
+
+ uno::Reference< beans::XPropertySet > xPropSet(xCurrShape, uno::UNO_QUERY);
+ if (!xPropSet.is())
+ continue;
+
+ uno::Reference< beans::XPropertySetInfo> xInfo = xPropSet->getPropertySetInfo();
+ if (!xInfo.is())
+ continue;
+
+ OUString sShapeName;
+ if (xInfo->hasPropertyByName("Name"))
+ {
+ uno::Any aAnyShapeName = xPropSet->getPropertyValue("Name");
+ aAnyShapeName >>= sShapeName;
+ }
+ else
+ continue;
+
+ // Rectangle redaction
+ if (sShapeName == "RectangleRedactionShape"
+ && xInfo->hasPropertyByName("FillTransparence") && xInfo->hasPropertyByName("FillColor"))
+ {
+ xPropSet->setPropertyValue("FillTransparence", css::uno::Any(static_cast<sal_Int16>(0)));
+ if (sRedactionStyle == "White")
+ {
+ xPropSet->setPropertyValue("FillColor", css::uno::Any(COL_WHITE));
+ xPropSet->setPropertyValue("LineStyle", css::uno::Any(css::drawing::LineStyle::LineStyle_SOLID));
+ xPropSet->setPropertyValue("LineColor", css::uno::Any(COL_BLACK));
+ }
+ else
+ {
+ xPropSet->setPropertyValue("FillColor", css::uno::Any(COL_BLACK));
+ xPropSet->setPropertyValue("LineStyle", css::uno::Any(css::drawing::LineStyle::LineStyle_NONE));
+ }
+ }
+ // Freeform redaction
+ else if (sShapeName == "FreeformRedactionShape"
+ && xInfo->hasPropertyByName("LineTransparence") && xInfo->hasPropertyByName("LineColor"))
+ {
+ xPropSet->setPropertyValue("LineTransparence", css::uno::Any(static_cast<sal_Int16>(0)));
+
+ if (sRedactionStyle == "White")
+ {
+ xPropSet->setPropertyValue("LineColor", css::uno::Any(COL_WHITE));
+ }
+ else
+ {
+ xPropSet->setPropertyValue("LineColor", css::uno::Any(COL_BLACK));
+ }
+ }
+ }
+ }
+ }
+ }
+ [[fallthrough]];
+ case SID_EXPORTDOCASPDF:
+ bIsPDFExport = true;
+ [[fallthrough]];
+ case SID_EXPORTDOCASEPUB:
+ case SID_DIRECTEXPORTDOCASEPUB:
+ case SID_EXPORTDOC:
+ case SID_SAVEASDOC:
+ case SID_SAVEASREMOTE:
+ case SID_SAVEDOC:
+ {
+ // so far only pdf and epub support Async interface
+ if (comphelper::LibreOfficeKit::isActive() && rReq.GetCallMode() == SfxCallMode::ASYNCHRON
+ && (nId == SID_EXPORTDOCASEPUB || nId == SID_EXPORTDOCASPDF))
+ bIsAsync = true;
+
+ // derived class may decide to abort this
+ if( !QuerySlotExecutable( nId ) )
+ {
+ rReq.SetReturnValue( SfxBoolItem( 0, false ) );
+ return;
+ }
+
+ //!! detailed analysis of an error code
+ SfxObjectShellRef xLock( this );
+
+ // the model can not be closed till the end of this method
+ // if somebody tries to close it during this time the model will be closed
+ // at the end of the method
+ aModelGuard.Init_Impl( uno::Reference< util::XCloseable >( GetModel(), uno::UNO_QUERY ) );
+
+ ErrCodeMsg nErrorCode = ERRCODE_NONE;
+
+ // by default versions should be preserved always except in case of an explicit
+ // SaveAs via GUI, so the flag must be set accordingly
+ pImpl->bPreserveVersions = (nId == SID_SAVEDOC);
+
+ // do not save version infos --> (see 'Tools - Options - LibreOffice - Security')
+ if (SvtSecurityOptions::IsOptionSet(
+ SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo) && !SvtSecurityOptions::IsOptionSet(
+ SvtSecurityOptions::EOption::DocWarnKeepDocVersionInfo))
+ {
+ pImpl->bPreserveVersions = false;
+ }
+
+ try
+ {
+ SfxErrorContext aEc( ERRCTX_SFX_SAVEASDOC, GetTitle() ); // ???
+
+ if ( nId == SID_SAVEASDOC || nId == SID_SAVEASREMOTE )
+ {
+ // in case of plugin mode the SaveAs operation means SaveTo
+ const SfxBoolItem* pViewOnlyItem = GetMedium()->GetItemSet().GetItem(SID_VIEWONLY, false);
+ if ( pViewOnlyItem && pViewOnlyItem->GetValue() )
+ rReq.AppendItem( SfxBoolItem( SID_SAVETO, true ) );
+ }
+
+ // TODO/LATER: do the following GUI related actions in standalone method
+
+ // Introduce a status indicator for GUI operation
+ const SfxUnoAnyItem* pStatusIndicatorItem = rReq.GetArg<SfxUnoAnyItem>(SID_PROGRESS_STATUSBAR_CONTROL);
+ if ( !pStatusIndicatorItem )
+ {
+ // get statusindicator
+ uno::Reference< task::XStatusIndicator > xStatusIndicator;
+ uno::Reference < frame::XController > xCtrl( GetModel()->getCurrentController() );
+ if ( xCtrl.is() )
+ {
+ uno::Reference< task::XStatusIndicatorFactory > xStatFactory( xCtrl->getFrame(), uno::UNO_QUERY );
+ if( xStatFactory.is() )
+ xStatusIndicator = xStatFactory->createStatusIndicator();
+ }
+
+ OSL_ENSURE( xStatusIndicator.is(), "Can not retrieve default status indicator!" );
+
+ if ( xStatusIndicator.is() )
+ {
+ SfxUnoAnyItem aStatIndItem( SID_PROGRESS_STATUSBAR_CONTROL, uno::Any( xStatusIndicator ) );
+
+ if ( nId == SID_SAVEDOC )
+ {
+ // in case of saving it is not possible to transport the parameters from here
+ // but it is not clear here whether the saving will be done or saveAs operation
+ GetMedium()->GetItemSet().Put( aStatIndItem );
+ }
+
+ rReq.AppendItem( aStatIndItem );
+ }
+ }
+ else if ( nId == SID_SAVEDOC )
+ {
+ // in case of saving it is not possible to transport the parameters from here
+ // but it is not clear here whether the saving will be done or saveAs operation
+ GetMedium()->GetItemSet().Put( *pStatusIndicatorItem );
+ }
+
+ // Introduce an interaction handler for GUI operation
+ const SfxUnoAnyItem* pInteractionHandlerItem = rReq.GetArg<SfxUnoAnyItem>(SID_INTERACTIONHANDLER);
+ if ( !pInteractionHandlerItem )
+ {
+ uno::Reference<css::awt::XWindow> xParentWindow;
+ uno::Reference<frame::XController> xCtrl(GetModel()->getCurrentController());
+ if (xCtrl.is())
+ xParentWindow = xCtrl->getFrame()->getContainerWindow();
+
+ uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+
+ uno::Reference< task::XInteractionHandler2 > xInteract(
+ task::InteractionHandler::createWithParent(xContext, xParentWindow) );
+
+ SfxUnoAnyItem aInteractionItem( SID_INTERACTIONHANDLER, uno::Any( xInteract ) );
+ if ( nId == SID_SAVEDOC )
+ {
+ // in case of saving it is not possible to transport the parameters from here
+ // but it is not clear here whether the saving will be done or saveAs operation
+ GetMedium()->GetItemSet().Put( aInteractionItem );
+ }
+
+ rReq.AppendItem( aInteractionItem );
+ }
+ else if ( nId == SID_SAVEDOC )
+ {
+ // in case of saving it is not possible to transport the parameters from here
+ // but it is not clear here whether the saving will be done or saveAs operation
+ GetMedium()->GetItemSet().Put( *pInteractionHandlerItem );
+ }
+
+
+ const SfxStringItem* pOldPasswordItem = GetMedium()->GetItemSet().GetItem(SID_PASSWORD, false);
+ const SfxUnoAnyItem* pOldEncryptionDataItem = GetMedium()->GetItemSet().GetItem(SID_ENCRYPTIONDATA, false);
+ const bool bPreselectPassword
+ = pOldPasswordItem || pOldEncryptionDataItem
+ || (IsLoadReadonly()
+ && (GetModifyPasswordHash() || GetModifyPasswordInfo().hasElements()));
+
+ uno::Sequence< beans::PropertyValue > aDispatchArgs;
+ if ( rReq.GetArgs() )
+ TransformItems( nId,
+ *rReq.GetArgs(),
+ aDispatchArgs );
+
+ bool bForceSaveAs = nId == SID_SAVEDOC && IsReadOnlyMedium();
+
+ if (comphelper::LibreOfficeKit::isActive() && bForceSaveAs)
+ {
+ // Don't force save as in LOK but report that file cannot be written
+ // to avoid confusion with exporting for file download purpose
+
+ throw task::ErrorCodeIOException(
+ "SfxObjectShell::ExecFile_Impl: ERRCODE_IO_CANTWRITE",
+ uno::Reference< uno::XInterface >(), sal_uInt32(ERRCODE_IO_CANTWRITE));
+ }
+
+ const SfxSlot* pSlot = GetModule()->GetSlotPool()->GetSlot( bForceSaveAs ? SID_SAVEASDOC : nId );
+ if ( !pSlot )
+ throw uno::Exception("no slot", nullptr);
+
+ std::shared_ptr<SfxStoringHelper> xHelper = std::make_shared<SfxStoringHelper>();
+ if (bIsAsync && SfxViewShell::Current())
+ SfxViewShell::Current()->SetStoringHelper(xHelper);
+
+ QueryHiddenInformation(bIsPDFExport ? HiddenWarningFact::WhenCreatingPDF : HiddenWarningFact::WhenSaving);
+ SfxPoolItemHolder aItem;
+ if (SID_DIRECTEXPORTDOCASPDF == nId)
+ aItem = GetSlotState(SID_MAIL_PREPAREEXPORT);
+ const SfxBoolItem* pItem(dynamic_cast<const SfxBoolItem*>(aItem.getItem()));
+
+ // Fetch value from the pool item early, because GUIStoreModel() can free the pool
+ // item as part of spinning the main loop if a dialog is opened.
+ const bool bMailPrepareExport(nullptr != pItem && pItem->GetValue());
+ if (bMailPrepareExport)
+ {
+ SfxRequest aRequest(SID_MAIL_PREPAREEXPORT, SfxCallMode::SYNCHRON, GetPool());
+ aRequest.AppendItem(SfxBoolItem(FN_NOUPDATE, true));
+ ExecuteSlot(aRequest);
+ }
+
+ xHelper->GUIStoreModel( GetModel(),
+ pSlot->GetUnoName(),
+ aDispatchArgs,
+ bPreselectPassword,
+ GetDocumentSignatureState(),
+ bIsAsync );
+
+ if (bMailPrepareExport)
+ {
+ SfxRequest aRequest(SID_MAIL_EXPORT_FINISHED, SfxCallMode::SYNCHRON, GetPool());
+ ExecuteSlot(aRequest);
+ }
+
+ // merge aDispatchArgs to the request
+ SfxAllItemSet aResultParams( GetPool() );
+ TransformParameters( nId,
+ aDispatchArgs,
+ aResultParams );
+ rReq.SetArgs( aResultParams );
+
+ // the StoreAsURL/StoreToURL method have called this method with false
+ // so it has to be restored to true here since it is a call from GUI
+ GetMedium()->SetUpdatePickList( true );
+
+ // TODO: in future it must be done in following way
+ // if document is opened from GUI, it immediately appears in the picklist
+ // if the document is a new one then it appears in the picklist immediately
+ // after SaveAs operation triggered from GUI
+ }
+ catch( const task::ErrorCodeIOException& aErrorEx )
+ {
+ TOOLS_WARN_EXCEPTION_IF(ErrCode(aErrorEx.ErrCode) != ERRCODE_IO_ABORT, "sfx.doc", "Fatal IO error during save");
+ nErrorCode = { ErrCode(aErrorEx.ErrCode), aErrorEx.Message };
+ }
+ catch( Exception& e )
+ {
+ nErrorCode = { ERRCODE_IO_GENERAL, e.Message };
+ }
+
+ // by default versions should be preserved always except in case of an explicit
+ // SaveAs via GUI, so the flag must be reset to guarantee this
+ pImpl->bPreserveVersions = true;
+ ErrCodeMsg lErr=GetErrorCode();
+
+ if ( !lErr && nErrorCode )
+ lErr = nErrorCode;
+
+ if ( lErr && nErrorCode == ERRCODE_NONE )
+ {
+ const SfxBoolItem* pWarnItem = rReq.GetArg<SfxBoolItem>(SID_FAIL_ON_WARNING);
+ if ( pWarnItem && pWarnItem->GetValue() )
+ nErrorCode = lErr;
+ }
+
+ // may be nErrorCode should be shown in future
+ if ( lErr != ERRCODE_IO_ABORT )
+ {
+ if (comphelper::LibreOfficeKit::isActive())
+ sendErrorToLOK(lErr);
+ else if (!(lErr == ERRCODE_IO_GENERAL && bIsPDFExport))
+ {
+ SfxErrorContext aEc(ERRCTX_SFX_SAVEASDOC,GetTitle());
+ ErrorHandler::HandleError(lErr, pDialogParent);
+ }
+ }
+
+ if (nId == SID_DIRECTEXPORTDOCASPDF &&
+ SfxRedactionHelper::isRedactMode(rReq))
+ {
+ // Return the finalized redaction shapes back to normal (gray & transparent)
+ uno::Reference< lang::XComponent > xComponent( GetCurrentComponent(), uno::UNO_QUERY );
+ if (!xComponent.is())
+ return;
+
+ uno::Reference< lang::XServiceInfo > xServiceInfo( xComponent, uno::UNO_QUERY);
+
+ // Redaction finalization takes place in Draw
+ if ( xServiceInfo.is() && xServiceInfo->supportsService("com.sun.star.drawing.DrawingDocument") )
+ {
+ // Access the draw pages
+ uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(xComponent, uno::UNO_QUERY);
+ uno::Reference<drawing::XDrawPages> xDrawPages = xDrawPagesSupplier->getDrawPages();
+
+ sal_Int32 nPageCount = xDrawPages->getCount();
+ for (sal_Int32 nPageNum = 0; nPageNum < nPageCount; ++nPageNum)
+ {
+ // Get the page
+ uno::Reference< drawing::XDrawPage > xPage( xDrawPages->getByIndex( nPageNum ), uno::UNO_QUERY );
+
+ if (!xPage.is())
+ continue;
+
+ // Go through all shapes
+ sal_Int32 nShapeCount = xPage->getCount();
+ for (sal_Int32 nShapeNum = 0; nShapeNum < nShapeCount; ++nShapeNum)
+ {
+ uno::Reference< drawing::XShape > xCurrShape(xPage->getByIndex(nShapeNum), uno::UNO_QUERY);
+ if (!xCurrShape.is())
+ continue;
+
+ uno::Reference< beans::XPropertySet > xPropSet(xCurrShape, uno::UNO_QUERY);
+ if (!xPropSet.is())
+ continue;
+
+ uno::Reference< beans::XPropertySetInfo> xInfo = xPropSet->getPropertySetInfo();
+ if (!xInfo.is())
+ continue;
+
+ // Not a shape we converted?
+ if (!xInfo->hasPropertyByName("Name"))
+ continue;
+
+ OUString sShapeName;
+ if (xInfo->hasPropertyByName("Name"))
+ {
+ uno::Any aAnyShapeName = xPropSet->getPropertyValue("Name");
+ aAnyShapeName >>= sShapeName;
+ }
+ else
+ continue;
+
+ // Rectangle redaction
+ if (sShapeName == "RectangleRedactionShape"
+ && xInfo->hasPropertyByName("FillTransparence") && xInfo->hasPropertyByName("FillColor"))
+ {
+ xPropSet->setPropertyValue("FillTransparence", css::uno::Any(static_cast<sal_Int16>(50)));
+ xPropSet->setPropertyValue("FillColor", css::uno::Any(COL_GRAY7));
+ xPropSet->setPropertyValue("LineStyle", css::uno::Any(css::drawing::LineStyle::LineStyle_NONE));
+
+ }
+ // Freeform redaction
+ else if (sShapeName == "FreeformRedactionShape")
+ {
+ xPropSet->setPropertyValue("LineTransparence", css::uno::Any(static_cast<sal_Int16>(50)));
+ xPropSet->setPropertyValue("LineColor", css::uno::Any(COL_GRAY7));
+ }
+ }
+ }
+
+
+ }
+ }
+
+ if ( nId == SID_EXPORTDOCASPDF )
+ {
+ // This function is used by the SendMail function that needs information if an export
+ // file was written or not. This could be due to cancellation of the export
+ // or due to an error. So IO abort must be handled like an error!
+ nErrorCode = ( lErr != ERRCODE_IO_ABORT ) && ( nErrorCode == ERRCODE_NONE ) ? nErrorCode : lErr;
+ }
+
+ if ( ( nId == SID_SAVEASDOC || nId == SID_SAVEASREMOTE ) && nErrorCode == ERRCODE_NONE )
+ {
+ const SfxBoolItem* saveTo = rReq.GetArg<SfxBoolItem>(SID_SAVETO);
+ if (saveTo == nullptr || !saveTo->GetValue())
+ {
+ SfxViewFrame *pFrame = GetFrame();
+ if (pFrame)
+ pFrame->RemoveInfoBar(u"readonly");
+ SetReadOnlyUI(false);
+ }
+ }
+
+ if (nId == SID_SAVEDOC && bRememberSignature && rSignatureInfosRemembered.hasElements())
+ ResignDocument(rSignatureInfosRemembered);
+
+ rReq.SetReturnValue( SfxBoolItem(0, nErrorCode == ERRCODE_NONE ) );
+
+ ResetError();
+
+ Invalidate();
+ break;
+ }
+
+ case SID_SAVEACOPY:
+ {
+ SfxAllItemSet aArgs( GetPool() );
+ aArgs.Put( SfxBoolItem( SID_SAVEACOPYITEM, true ) );
+ SfxRequest aSaveACopyReq( SID_EXPORTDOC, SfxCallMode::API, aArgs );
+ ExecFile_Impl( aSaveACopyReq );
+ if ( !aSaveACopyReq.IsDone() )
+ {
+ rReq.Ignore();
+ return;
+ }
+ break;
+ }
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ case SID_CLOSEDOC:
+ {
+ // Evaluate Parameter
+ const SfxBoolItem* pSaveItem = rReq.GetArg<SfxBoolItem>(SID_CLOSEDOC_SAVE);
+ const SfxStringItem* pNameItem = rReq.GetArg<SfxStringItem>(SID_CLOSEDOC_FILENAME);
+ if ( pSaveItem )
+ {
+ if ( pSaveItem->GetValue() )
+ {
+ if ( !pNameItem )
+ {
+#if HAVE_FEATURE_SCRIPTING
+ SbxBase::SetError( ERRCODE_BASIC_WRONG_ARGS );
+#endif
+ rReq.Ignore();
+ return;
+ }
+ SfxAllItemSet aArgs( GetPool() );
+ SfxStringItem aTmpItem( SID_FILE_NAME, pNameItem->GetValue() );
+ aArgs.Put( aTmpItem, aTmpItem.Which() );
+ SfxRequest aSaveAsReq( SID_SAVEASDOC, SfxCallMode::API, aArgs );
+ ExecFile_Impl( aSaveAsReq );
+ if ( !aSaveAsReq.IsDone() )
+ {
+ rReq.Ignore();
+ return;
+ }
+ }
+ else
+ SetModified(false);
+ }
+
+ // Cancelled by the user?
+ if (!PrepareClose())
+ {
+ rReq.SetReturnValue( SfxBoolItem(0, false) );
+ rReq.Done();
+ return;
+ }
+
+ SetModified( false );
+ ErrCodeMsg lErr = GetErrorCode();
+
+ if (comphelper::LibreOfficeKit::isActive())
+ sendErrorToLOK(lErr);
+ else
+ ErrorHandler::HandleError(lErr, pDialogParent);
+
+ rReq.SetReturnValue( SfxBoolItem(0, true) );
+ rReq.Done();
+ rReq.ReleaseArgs(); // because the pool is destroyed in Close
+ DoClose();
+ return;
+ }
+
+ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ case SID_DOCTEMPLATE:
+ {
+ // save as document templates
+ SfxSaveAsTemplateDialog aDlg(pDialogParent, GetModel());
+ (void)aDlg.run();
+ break;
+ }
+
+ case SID_CHECKOUT:
+ {
+ CheckOut( );
+ break;
+ }
+ case SID_CANCELCHECKOUT:
+ {
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Question, VclButtonsType::YesNo, SfxResId(STR_QUERY_CANCELCHECKOUT)));
+ if (xBox->run() == RET_YES)
+ {
+ CancelCheckOut( );
+
+ // Reload the document as we may still have local changes
+ SfxViewFrame *pFrame = GetFrame();
+ if ( pFrame )
+ pFrame->GetDispatcher()->Execute(SID_RELOAD);
+ }
+ break;
+ }
+ case SID_CHECKIN:
+ {
+ CheckIn( );
+ break;
+ }
+ }
+
+ // Prevent entry in the Pick-lists
+ if ( rReq.IsAPI() )
+ GetMedium()->SetUpdatePickList( false );
+
+ // Ignore()-branches have already returned
+ rReq.Done();
+}
+
+
+void SfxObjectShell::GetState_Impl(SfxItemSet &rSet)
+{
+ SfxWhichIter aIter( rSet );
+
+ for ( sal_uInt16 nWhich = aIter.FirstWhich(); nWhich; nWhich = aIter.NextWhich() )
+ {
+ switch ( nWhich )
+ {
+ case SID_DOCTEMPLATE :
+ {
+ if ( isExportLocked())
+ rSet.DisableItem( nWhich );
+ break;
+ }
+
+ case SID_CHECKOUT:
+ {
+ bool bShow = false;
+ Reference< XCmisDocument > xCmisDoc( GetModel(), uno::UNO_QUERY );
+ const uno::Sequence< document::CmisProperty> aCmisProperties = xCmisDoc->getCmisProperties();
+
+ if ( xCmisDoc->isVersionable( ) && aCmisProperties.hasElements( ) )
+ {
+ // Loop over the CMIS Properties to find cmis:isVersionSeriesCheckedOut
+ bool bIsGoogleFile = false;
+ bool bCheckedOut = false;
+ for ( const auto& rCmisProperty : aCmisProperties )
+ {
+ if ( rCmisProperty.Id == "cmis:isVersionSeriesCheckedOut" )
+ {
+ uno::Sequence< sal_Bool > bTmp;
+ rCmisProperty.Value >>= bTmp;
+ bCheckedOut = bTmp[0];
+ }
+ // using title to know if it's a Google Drive file
+ // maybe there's a safer way.
+ if ( rCmisProperty.Name == "title" )
+ bIsGoogleFile = true;
+ }
+ bShow = !bCheckedOut && !bIsGoogleFile;
+ }
+
+ if ( !bShow )
+ {
+ rSet.DisableItem( nWhich );
+ rSet.Put( SfxVisibilityItem( nWhich, false ) );
+ }
+ }
+ break;
+
+ case SID_CANCELCHECKOUT:
+ case SID_CHECKIN:
+ {
+ bool bShow = false;
+ Reference< XCmisDocument > xCmisDoc( GetModel(), uno::UNO_QUERY );
+ const uno::Sequence< document::CmisProperty> aCmisProperties = xCmisDoc->getCmisProperties( );
+
+ if ( xCmisDoc->isVersionable( ) && aCmisProperties.hasElements( ) )
+ {
+ // Loop over the CMIS Properties to find cmis:isVersionSeriesCheckedOut
+ bool bCheckedOut = false;
+ auto pProp = std::find_if(aCmisProperties.begin(), aCmisProperties.end(),
+ [](const document::CmisProperty& rProp) { return rProp.Id == "cmis:isVersionSeriesCheckedOut"; });
+ if (pProp != aCmisProperties.end())
+ {
+ uno::Sequence< sal_Bool > bTmp;
+ pProp->Value >>= bTmp;
+ bCheckedOut = bTmp[0];
+ }
+ bShow = bCheckedOut;
+ }
+
+ if ( !bShow )
+ {
+ rSet.DisableItem( nWhich );
+ rSet.Put( SfxVisibilityItem( nWhich, false ) );
+ }
+ }
+ break;
+
+ case SID_VERSION:
+ {
+ SfxObjectShell *pDoc = this;
+ SfxViewFrame* pFrame = GetFrame();
+ if ( !pFrame )
+ pFrame = SfxViewFrame::GetFirst( this );
+
+ if ( !pFrame || !pDoc->HasName() ||
+ !IsOwnStorageFormat( *pDoc->GetMedium() ) )
+ rSet.DisableItem( nWhich );
+ break;
+ }
+ case SID_SAVEDOC:
+ {
+ if ( IsReadOnly() || isSaveLocked())
+ {
+ rSet.DisableItem(nWhich);
+ break;
+ }
+ rSet.Put(SfxStringItem(nWhich, SfxResId(STR_SAVEDOC)));
+ }
+ break;
+
+ case SID_DOCINFO:
+ break;
+
+ case SID_CLOSEDOC:
+ {
+ rSet.Put(SfxStringItem(nWhich, SfxResId(STR_CLOSEDOC)));
+ break;
+ }
+
+ case SID_SAVEASDOC:
+ {
+ if (!(pImpl->nLoadedFlags & SfxLoadedFlags::MAINDOCUMENT)
+ || isExportLocked())
+ {
+ rSet.DisableItem( nWhich );
+ break;
+ }
+ if ( /*!pCombinedFilters ||*/ !GetMedium() )
+ rSet.DisableItem( nWhich );
+ else
+ rSet.Put( SfxStringItem( nWhich, SfxResId(STR_SAVEASDOC) ) );
+ break;
+ }
+
+ case SID_SAVEACOPY:
+ {
+ if (!(pImpl->nLoadedFlags & SfxLoadedFlags::MAINDOCUMENT) || isExportLocked())
+ {
+ rSet.DisableItem( nWhich );
+ break;
+ }
+ if ( /*!pCombinedFilters ||*/ !GetMedium() )
+ rSet.DisableItem( nWhich );
+ else
+ rSet.Put( SfxStringItem( nWhich, SfxResId(STR_SAVEACOPY) ) );
+ break;
+ }
+
+ case SID_EXPORTDOC:
+ case SID_EXPORTDOCASPDF:
+ case SID_DIRECTEXPORTDOCASPDF:
+ case SID_EXPORTDOCASEPUB:
+ case SID_DIRECTEXPORTDOCASEPUB:
+ case SID_REDACTDOC:
+ case SID_AUTOREDACTDOC:
+ case SID_SAVEASREMOTE:
+ {
+ if (isExportLocked())
+ rSet.DisableItem( nWhich );
+ break;
+ }
+
+ case SID_DOC_MODIFIED:
+ {
+ rSet.Put( SfxBoolItem( SID_DOC_MODIFIED, IsModified() ) );
+ break;
+ }
+
+ case SID_MODIFIED:
+ {
+ rSet.Put( SfxBoolItem( SID_MODIFIED, IsModified() ) );
+ break;
+ }
+
+ case SID_DOCINFO_TITLE:
+ {
+ rSet.Put( SfxStringItem(
+ SID_DOCINFO_TITLE, getDocProperties()->getTitle() ) );
+ break;
+ }
+ case SID_FILE_NAME:
+ {
+ if( GetMedium() && HasName() )
+ rSet.Put( SfxStringItem(
+ SID_FILE_NAME, GetMedium()->GetName() ) );
+ break;
+ }
+ case SID_SIGNATURE:
+ {
+ SfxViewFrame *pFrame = SfxViewFrame::GetFirst(this);
+ if ( pFrame )
+ {
+ SignatureState eState = GetDocumentSignatureState();
+ InfobarType aInfobarType(InfobarType::INFO);
+ OUString sMessage("");
+
+ switch (eState)
+ {
+ case SignatureState::BROKEN:
+ sMessage = SfxResId(STR_SIGNATURE_BROKEN);
+ aInfobarType = InfobarType::DANGER;
+ break;
+ case SignatureState::INVALID:
+ // If we are remembering the certificates, it should be kept as valid
+ sMessage = SfxResId(bRememberSignature ? STR_SIGNATURE_OK : STR_SIGNATURE_INVALID);
+ // Warning only, I've tried Danger and it looked too scary
+ aInfobarType = ( bRememberSignature ? InfobarType::INFO : InfobarType::WARNING );
+ break;
+ case SignatureState::NOTVALIDATED:
+ sMessage = SfxResId(STR_SIGNATURE_NOTVALIDATED);
+ aInfobarType = InfobarType::WARNING;
+ break;
+ case SignatureState::PARTIAL_OK:
+ sMessage = SfxResId(STR_SIGNATURE_PARTIAL_OK);
+ aInfobarType = InfobarType::WARNING;
+ break;
+ case SignatureState::OK:
+ sMessage = SfxResId(STR_SIGNATURE_OK);
+ aInfobarType = InfobarType::INFO;
+ break;
+ case SignatureState::NOTVALIDATED_PARTIAL_OK:
+ sMessage = SfxResId(STR_SIGNATURE_NOTVALIDATED_PARTIAL_OK);
+ aInfobarType = InfobarType::WARNING;
+ break;
+ //FIXME SignatureState::Unknown, own message?
+ default:
+ break;
+ }
+
+ // new info bar
+ if ( !pFrame->HasInfoBarWithID(u"signature") )
+ {
+ if ( !sMessage.isEmpty() )
+ {
+ auto pInfoBar = pFrame->AppendInfoBar("signature", "", sMessage, aInfobarType);
+ if (pInfoBar == nullptr || pInfoBar->isDisposed())
+ return;
+ weld::Button& rBtn = pInfoBar->addButton();
+ rBtn.set_label(SfxResId(STR_SIGNATURE_SHOW));
+ rBtn.connect_clicked(LINK(this, SfxObjectShell, SignDocumentHandler));
+ }
+ }
+ else // info bar exists already
+ {
+ if ( eState == SignatureState::NOSIGNATURES )
+ pFrame->RemoveInfoBar(u"signature");
+ else
+ pFrame->UpdateInfoBar(u"signature", "", sMessage, aInfobarType);
+ }
+ }
+
+ rSet.Put( SfxUInt16Item( SID_SIGNATURE, static_cast<sal_uInt16>(GetDocumentSignatureState()) ) );
+ break;
+ }
+ case SID_MACRO_SIGNATURE:
+ {
+ // the slot makes sense only if there is a macro in the document
+ if ( pImpl->documentStorageHasMacros() || pImpl->aMacroMode.hasMacroLibrary() )
+ rSet.Put( SfxUInt16Item( SID_MACRO_SIGNATURE, static_cast<sal_uInt16>(GetScriptingSignatureState()) ) );
+ else
+ rSet.DisableItem( nWhich );
+ break;
+ }
+ case SID_DOC_REPAIR:
+ {
+ SfxUndoManager* pIUndoMgr = GetUndoManager();
+ if (pIUndoMgr)
+ rSet.Put( SfxBoolItem(nWhich, pIUndoMgr->IsEmptyActions()) );
+ else
+ rSet.DisableItem( nWhich );
+ break;
+ }
+ }
+ }
+}
+
+IMPL_LINK_NOARG(SfxObjectShell, SignDocumentHandler, weld::Button&, void)
+{
+ SfxViewFrame* pViewFrm = SfxViewFrame::GetFirst(this);
+ if (!pViewFrm)
+ {
+ SAL_WARN("sfx.appl", "There should be some SfxViewFrame associated here");
+ return;
+ }
+ SfxUnoFrameItem aDocFrame(SID_FILLFRAME, pViewFrm->GetFrame().GetFrameInterface());
+ pViewFrm->GetDispatcher()->ExecuteList(SID_SIGNATURE, SfxCallMode::SLOT, {}, { &aDocFrame });
+}
+
+void SfxObjectShell::ExecProps_Impl(SfxRequest &rReq)
+{
+ switch ( rReq.GetSlot() )
+ {
+ case SID_MODIFIED:
+ {
+ SetModified( rReq.GetArgs()->Get(SID_MODIFIED).GetValue() );
+ rReq.Done();
+ break;
+ }
+
+ case SID_DOCTITLE:
+ SetTitle( rReq.GetArgs()->Get(SID_DOCTITLE).GetValue() );
+ rReq.Done();
+ break;
+
+ case SID_DOCINFO_AUTHOR :
+ getDocProperties()->setAuthor( static_cast<const SfxStringItem&>(rReq.GetArgs()->Get(rReq.GetSlot())).GetValue() );
+ break;
+
+ case SID_DOCINFO_COMMENTS :
+ getDocProperties()->setDescription( static_cast<const SfxStringItem&>(rReq.GetArgs()->Get(rReq.GetSlot())).GetValue() );
+ break;
+
+ case SID_DOCINFO_KEYWORDS :
+ {
+ const OUString aStr = static_cast<const SfxStringItem&>(rReq.GetArgs()->Get(rReq.GetSlot())).GetValue();
+ getDocProperties()->setKeywords(
+ ::comphelper::string::convertCommaSeparated(aStr) );
+ break;
+ }
+ }
+}
+
+
+void SfxObjectShell::StateProps_Impl(SfxItemSet &rSet)
+{
+ SfxWhichIter aIter(rSet);
+ for ( sal_uInt16 nSID = aIter.FirstWhich(); nSID; nSID = aIter.NextWhich() )
+ {
+ switch ( nSID )
+ {
+ case SID_DOCINFO_AUTHOR :
+ {
+ rSet.Put( SfxStringItem( nSID,
+ getDocProperties()->getAuthor() ) );
+ break;
+ }
+
+ case SID_DOCINFO_COMMENTS :
+ {
+ rSet.Put( SfxStringItem( nSID,
+ getDocProperties()->getDescription()) );
+ break;
+ }
+
+ case SID_DOCINFO_KEYWORDS :
+ {
+ rSet.Put( SfxStringItem( nSID, ::comphelper::string::
+ convertCommaSeparated(getDocProperties()->getKeywords())) );
+ break;
+ }
+
+ case SID_DOCPATH:
+ {
+ OSL_FAIL( "Not supported anymore!" );
+ break;
+ }
+
+ case SID_DOCFULLNAME:
+ {
+ rSet.Put( SfxStringItem( SID_DOCFULLNAME, GetTitle(SFX_TITLE_FULLNAME) ) );
+ break;
+ }
+
+ case SID_DOCTITLE:
+ {
+ rSet.Put( SfxStringItem( SID_DOCTITLE, GetTitle() ) );
+ break;
+ }
+
+ case SID_DOC_READONLY:
+ {
+ rSet.Put( SfxBoolItem( SID_DOC_READONLY, IsReadOnly() ) );
+ break;
+ }
+
+ case SID_DOC_SAVED:
+ {
+ rSet.Put( SfxBoolItem( SID_DOC_SAVED, !IsModified() ) );
+ break;
+ }
+
+ case SID_CLOSING:
+ {
+ rSet.Put( SfxBoolItem( SID_CLOSING, false ) );
+ break;
+ }
+
+ case SID_DOC_LOADING:
+ rSet.Put( SfxBoolItem( nSID, ! ( pImpl->nLoadedFlags & SfxLoadedFlags::MAINDOCUMENT ) ) );
+ break;
+
+ case SID_IMG_LOADING:
+ rSet.Put( SfxBoolItem( nSID, ! ( pImpl->nLoadedFlags & SfxLoadedFlags::IMAGES ) ) );
+ break;
+ }
+ }
+}
+
+
+void SfxObjectShell::ExecView_Impl(SfxRequest &rReq)
+{
+ switch ( rReq.GetSlot() )
+ {
+ case SID_ACTIVATE:
+ {
+ SfxViewFrame *pFrame = SfxViewFrame::GetFirst( this );
+ if ( pFrame )
+ pFrame->GetFrame().Appear();
+ rReq.SetReturnValue( SfxObjectItem( 0, pFrame ) );
+ rReq.Done();
+ break;
+ }
+ }
+}
+
+
+void SfxObjectShell::StateView_Impl(SfxItemSet& /*rSet*/)
+{
+}
+
+/// Does this ZIP storage have a signature stream?
+static bool HasSignatureStream(const uno::Reference<embed::XStorage>& xStorage)
+{
+ if (!xStorage.is())
+ return false;
+
+ if (xStorage->hasByName("META-INF"))
+ {
+ // ODF case.
+ try
+ {
+ uno::Reference<embed::XStorage> xMetaInf
+ = xStorage->openStorageElement("META-INF", embed::ElementModes::READ);
+ if (xMetaInf.is())
+ {
+ return xMetaInf->hasByName("documentsignatures.xml")
+ || xMetaInf->hasByName("macrosignatures.xml")
+ || xMetaInf->hasByName("packagesignatures.xml");
+ }
+ }
+ catch (const css::io::IOException&)
+ {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "HasSignatureStream: failed to open META-INF");
+ }
+ }
+
+ // OOXML case.
+ return xStorage->hasByName("_xmlsignatures");
+}
+
+uno::Sequence< security::DocumentSignatureInformation > SfxObjectShell::GetDocumentSignatureInformation( bool bScriptingContent, const uno::Reference< security::XDocumentDigitalSignatures >& xSigner )
+{
+ uno::Sequence< security::DocumentSignatureInformation > aResult;
+ uno::Reference< security::XDocumentDigitalSignatures > xLocSigner = xSigner;
+
+ bool bSupportsSigning = GetMedium() && GetMedium()->GetFilter() && GetMedium()->GetFilter()->GetSupportsSigning();
+ if (GetMedium() && !GetMedium()->GetName().isEmpty() && ((IsOwnStorageFormat(*GetMedium()) && GetMedium()->GetStorage().is()) || bSupportsSigning))
+ {
+ try
+ {
+ if ( !xLocSigner.is() )
+ {
+ OUString aVersion;
+ try
+ {
+ uno::Reference < beans::XPropertySet > xPropSet( GetStorage(), uno::UNO_QUERY_THROW );
+ xPropSet->getPropertyValue("Version") >>= aVersion;
+ }
+ catch( uno::Exception& )
+ {
+ }
+
+ xLocSigner.set( security::DocumentDigitalSignatures::createWithVersion(comphelper::getProcessComponentContext(), aVersion) );
+
+ }
+
+ if ( bScriptingContent )
+ {
+ aResult = xLocSigner->verifyScriptingContentSignatures(
+ GetMedium()->GetScriptingStorageToSign_Impl(),
+ uno::Reference<io::XInputStream>());
+ }
+ else
+ {
+ if (GetMedium()->GetStorage(false).is())
+ {
+ // Something ZIP-based.
+ // Only call into xmlsecurity if we see a signature stream,
+ // as libxmlsec init is expensive.
+ if (HasSignatureStream(GetMedium()->GetZipStorageToSign_Impl()))
+ aResult = xLocSigner->verifyDocumentContentSignatures( GetMedium()->GetZipStorageToSign_Impl(),
+ uno::Reference< io::XInputStream >() );
+ }
+ else
+ {
+ // Not ZIP-based, e.g. PDF.
+
+ // Create temp file if needed.
+ GetMedium()->CreateTempFile(/*bReplace=*/false);
+
+ std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(GetMedium()->GetName(), StreamMode::READ));
+ uno::Reference<io::XStream> xStream(new utl::OStreamWrapper(*pStream));
+ uno::Reference<io::XInputStream> xInputStream(xStream, uno::UNO_QUERY);
+ aResult = xLocSigner->verifyDocumentContentSignatures(uno::Reference<embed::XStorage>(), xInputStream);
+ }
+ }
+ }
+ catch( css::uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "Failed to get document signature information");
+ }
+ }
+
+ return aResult;
+}
+
+SignatureState SfxObjectShell::ImplGetSignatureState( bool bScriptingContent )
+{
+ SignatureState* pState = bScriptingContent ? &pImpl->nScriptingSignatureState : &pImpl->nDocumentSignatureState;
+
+ if ( *pState == SignatureState::UNKNOWN )
+ {
+ *pState = SignatureState::NOSIGNATURES;
+
+ uno::Sequence< security::DocumentSignatureInformation > aInfos = GetDocumentSignatureInformation( bScriptingContent );
+ *pState = DocumentSignatures::getSignatureState(aInfos);
+ }
+
+ if ( *pState == SignatureState::OK || *pState == SignatureState::NOTVALIDATED
+ || *pState == SignatureState::PARTIAL_OK)
+ {
+ if ( IsModified() )
+ *pState = SignatureState::INVALID;
+ }
+
+ return *pState;
+}
+
+bool SfxObjectShell::PrepareForSigning(weld::Window* pDialogParent)
+{
+ // check whether the document is signed
+ ImplGetSignatureState(); // document signature
+ if (GetMedium() && GetMedium()->GetFilter() && GetMedium()->GetFilter()->IsOwnFormat())
+ ImplGetSignatureState( true ); // script signature
+ bool bHasSign = ( pImpl->nScriptingSignatureState != SignatureState::NOSIGNATURES || pImpl->nDocumentSignatureState != SignatureState::NOSIGNATURES );
+
+ // the target ODF version on saving (only valid when signing ODF of course)
+ SvtSaveOptions::ODFSaneDefaultVersion nVersion = GetODFSaneDefaultVersion();
+
+ // the document is not new and is not modified
+ OUString aODFVersion(comphelper::OStorageHelper::GetODFVersionFromStorage(GetStorage()));
+
+ if ( IsModified() || !GetMedium() || GetMedium()->GetName().isEmpty()
+ || (GetMedium()->GetFilter()->IsOwnFormat() && aODFVersion.compareTo(ODFVER_012_TEXT) < 0 && !bHasSign))
+ {
+ // the document might need saving ( new, modified or in ODF1.1 format without signature )
+
+ if (nVersion >= SvtSaveOptions::ODFSVER_012)
+ {
+ OUString sQuestion(bHasSign ? SfxResId(STR_XMLSEC_QUERY_SAVESIGNEDBEFORESIGN) : SfxResId(RID_SVXSTR_XMLSEC_QUERY_SAVEBEFORESIGN));
+ std::unique_ptr<weld::MessageDialog> xQuestion;
+
+ if (!bRememberSignature)
+ {
+ xQuestion = std::unique_ptr<weld::MessageDialog>(Application::CreateMessageDialog(pDialogParent,
+ VclMessageType::Question, VclButtonsType::YesNo, sQuestion));
+ }
+
+ if ( bRememberSignature || ( xQuestion != nullptr && xQuestion->run() == RET_YES ) )
+ {
+ sal_uInt16 nId = SID_SAVEDOC;
+ if ( !GetMedium() || GetMedium()->GetName().isEmpty() )
+ nId = SID_SAVEASDOC;
+ SfxRequest aSaveRequest( nId, SfxCallMode::SLOT, GetPool() );
+ //ToDo: Review. We needed to call SetModified, otherwise the document would not be saved.
+ SetModified();
+ ExecFile_Impl( aSaveRequest );
+
+ // Check if it is stored a format which supports signing
+ if (GetMedium() && GetMedium()->GetFilter() && !GetMedium()->GetName().isEmpty()
+ && ((!GetMedium()->GetFilter()->IsOwnFormat()
+ && !GetMedium()->GetFilter()->GetSupportsSigning())
+ || (GetMedium()->GetFilter()->IsOwnFormat()
+ && !GetMedium()->HasStorage_Impl())))
+ {
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(
+ pDialogParent, VclMessageType::Info, VclButtonsType::Ok,
+ SfxResId(STR_INFO_WRONGDOCFORMAT)));
+
+ xBox->run();
+ return false;
+ }
+ }
+ else
+ {
+ // When the document is modified then we must not show the
+ // digital signatures dialog
+ // If we have come here then the user denied to save.
+ if (!bHasSign)
+ return false;
+ }
+ }
+ else
+ {
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pDialogParent,
+ VclMessageType::Warning, VclButtonsType::Ok, SfxResId(STR_XMLSEC_ODF12_EXPECTED)));
+ xBox->run();
+ return false;
+ }
+
+ if ( IsModified() || !GetMedium() || GetMedium()->GetName().isEmpty() )
+ return false;
+ }
+
+ // the document is not modified currently, so it can not become modified after signing
+ pImpl->m_bAllowModifiedBackAfterSigning = false;
+ if ( IsEnableSetModified() || /*bRememberSignature == */true )
+ {
+ EnableSetModified( false );
+ pImpl->m_bAllowModifiedBackAfterSigning = true;
+ }
+
+ // we have to store to the original document, the original medium should be closed for this time
+ if ( ConnectTmpStorage_Impl( pMedium->GetStorage(), pMedium ) )
+ {
+ GetMedium()->CloseAndRelease();
+ return true;
+ }
+ return false;
+}
+
+void SfxObjectShell::RecheckSignature(bool bAlsoRecheckScriptingSignature)
+{
+ if (bAlsoRecheckScriptingSignature)
+ pImpl->nScriptingSignatureState = SignatureState::UNKNOWN; // Re-Check
+
+ pImpl->nDocumentSignatureState = SignatureState::UNKNOWN; // Re-Check
+
+ Invalidate(SID_SIGNATURE);
+ Invalidate(SID_MACRO_SIGNATURE);
+ Broadcast(SfxHint(SfxHintId::TitleChanged));
+}
+
+void SfxObjectShell::AfterSigning(bool bSignSuccess, bool bSignScriptingContent)
+{
+ pImpl->m_bSavingForSigning = true;
+ DoSaveCompleted( GetMedium() );
+ pImpl->m_bSavingForSigning = false;
+
+ if ( bSignSuccess )
+ RecheckSignature(bSignScriptingContent);
+
+ if ( pImpl->m_bAllowModifiedBackAfterSigning || /* bRememberSignature ==*/ true )
+ EnableSetModified();
+}
+
+bool SfxObjectShell::CheckIsReadonly(bool bSignScriptingContent, weld::Window* pDialogParent)
+{
+ // in LOK case we support only viewer / readonly mode so far
+ if (GetMedium()->IsOriginallyReadOnly() || comphelper::LibreOfficeKit::isActive())
+ {
+ // If the file is physically read-only, we just show the existing signatures
+ try
+ {
+ OUString aODFVersion(
+ comphelper::OStorageHelper::GetODFVersionFromStorage(GetStorage()));
+ uno::Reference<security::XDocumentDigitalSignatures> xSigner(
+ security::DocumentDigitalSignatures::createWithVersionAndValidSignature(
+ comphelper::getProcessComponentContext(), aODFVersion, HasValidSignatures()));
+
+ if (pDialogParent)
+ xSigner->setParentWindow(pDialogParent->GetXWindow());
+
+ if (bSignScriptingContent)
+ xSigner->showScriptingContentSignatures(GetMedium()->GetScriptingStorageToSign_Impl(),
+ uno::Reference<io::XInputStream>());
+ else
+ {
+ uno::Reference<embed::XStorage> xStorage = GetMedium()->GetZipStorageToSign_Impl();
+ if (xStorage.is())
+ xSigner->showDocumentContentSignatures(xStorage,
+ uno::Reference<io::XInputStream>());
+ else
+ {
+ std::unique_ptr<SvStream> pStream(
+ utl::UcbStreamHelper::CreateStream(GetName(), StreamMode::READ));
+
+ if (!pStream)
+ {
+ pStream = utl::UcbStreamHelper::CreateStream(GetMedium()->GetName(), StreamMode::READ);
+
+ if (!pStream)
+ {
+ SAL_WARN( "sfx.doc", "Couldn't use signing functionality!" );
+ return true;
+ }
+ }
+
+ uno::Reference<io::XInputStream> xStream(new utl::OStreamWrapper(*pStream));
+ xSigner->showDocumentContentSignatures(uno::Reference<embed::XStorage>(),
+ xStream);
+ }
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ SAL_WARN("sfx.doc", "Couldn't use signing functionality!");
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SfxObjectShell::HasValidSignatures() const
+{
+ return pImpl->nDocumentSignatureState == SignatureState::OK
+ || pImpl->nDocumentSignatureState == SignatureState::NOTVALIDATED
+ || pImpl->nDocumentSignatureState == SignatureState::PARTIAL_OK;
+}
+
+SignatureState SfxObjectShell::GetDocumentSignatureState()
+{
+ return ImplGetSignatureState();
+}
+
+bool SfxObjectShell::SignDocumentContent(weld::Window* pDialogParent)
+{
+ if (!PrepareForSigning(pDialogParent))
+ return false;
+
+ if (CheckIsReadonly(false, pDialogParent))
+ return false;
+
+ bool bSignSuccess = GetMedium()->SignContents_Impl(pDialogParent, false, HasValidSignatures());
+
+ AfterSigning(bSignSuccess, false);
+
+ return bSignSuccess;
+}
+
+bool SfxObjectShell::ResignDocument(uno::Sequence< security::DocumentSignatureInformation >& rSignaturesInfo)
+{
+ bool bSignSuccess = true;
+
+ // This should be at most one element, automatic iteration to avoid pointing issues in case no signs
+ for (auto & rInfo : rSignaturesInfo)
+ {
+ auto xCert = rInfo.Signer;
+ if (xCert.is())
+ {
+ bSignSuccess &= SignDocumentContentUsingCertificate(xCert);
+ }
+ }
+
+ return bSignSuccess;
+}
+
+bool SfxObjectShell::SignDocumentContentUsingCertificate(const Reference<XCertificate>& xCertificate)
+{
+ // 1. PrepareForSigning
+
+ // check whether the document is signed
+ ImplGetSignatureState(false); // document signature
+ if (GetMedium() && GetMedium()->GetFilter() && GetMedium()->GetFilter()->IsOwnFormat())
+ ImplGetSignatureState( true ); // script signature
+ bool bHasSign = ( pImpl->nScriptingSignatureState != SignatureState::NOSIGNATURES || pImpl->nDocumentSignatureState != SignatureState::NOSIGNATURES );
+
+ // the target ODF version on saving (only valid when signing ODF of course)
+ SvtSaveOptions::ODFSaneDefaultVersion nVersion = GetODFSaneDefaultVersion();
+
+ // the document is not new and is not modified
+ OUString aODFVersion(comphelper::OStorageHelper::GetODFVersionFromStorage(GetStorage()));
+
+ if (IsModified() || !GetMedium() || GetMedium()->GetName().isEmpty()
+ || (GetMedium()->GetFilter()->IsOwnFormat() && aODFVersion.compareTo(ODFVER_012_TEXT) < 0 && !bHasSign))
+ {
+ if (nVersion >= SvtSaveOptions::ODFSVER_012)
+ {
+ sal_uInt16 nId = SID_SAVEDOC;
+ if ( !GetMedium() || GetMedium()->GetName().isEmpty() )
+ nId = SID_SAVEASDOC;
+ SfxRequest aSaveRequest( nId, SfxCallMode::SLOT, GetPool() );
+ //ToDo: Review. We needed to call SetModified, otherwise the document would not be saved.
+ SetModified();
+ ExecFile_Impl( aSaveRequest );
+
+ // Check if it is stored a format which supports signing
+ if (GetMedium() && GetMedium()->GetFilter() && !GetMedium()->GetName().isEmpty()
+ && ((!GetMedium()->GetFilter()->IsOwnFormat()
+ && !GetMedium()->GetFilter()->GetSupportsSigning())
+ || (GetMedium()->GetFilter()->IsOwnFormat()
+ && !GetMedium()->HasStorage_Impl())))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+
+ if ( IsModified() || !GetMedium() || GetMedium()->GetName().isEmpty() )
+ return false;
+ }
+
+ // the document is not modified currently, so it can not become modified after signing
+ pImpl->m_bAllowModifiedBackAfterSigning = false;
+ if ( IsEnableSetModified() )
+ {
+ EnableSetModified( false );
+ pImpl->m_bAllowModifiedBackAfterSigning = true;
+ }
+
+ // we have to store to the original document, the original medium should be closed for this time
+ bool bResult = ConnectTmpStorage_Impl( pMedium->GetStorage(), pMedium);
+
+ if (!bResult)
+ return false;
+
+ GetMedium()->CloseAndRelease();
+
+ // 2. Check Read-Only
+ if (GetMedium()->IsOriginallyReadOnly())
+ return false;
+
+ // 3. Sign
+ bool bSignSuccess = GetMedium()->SignDocumentContentUsingCertificate(
+ GetBaseModel(), HasValidSignatures(), xCertificate);
+
+ // 4. AfterSigning
+ AfterSigning(bSignSuccess, false);
+
+ return true;
+}
+
+void SfxObjectShell::SignSignatureLine(weld::Window* pDialogParent,
+ const OUString& aSignatureLineId,
+ const Reference<XCertificate>& xCert,
+ const Reference<XGraphic>& xValidGraphic,
+ const Reference<XGraphic>& xInvalidGraphic,
+ const OUString& aComment)
+{
+ if (!PrepareForSigning(pDialogParent))
+ return;
+
+ if (CheckIsReadonly(false, pDialogParent))
+ return;
+
+ bool bSignSuccess = GetMedium()->SignContents_Impl(pDialogParent,
+ false, HasValidSignatures(), aSignatureLineId, xCert, xValidGraphic, xInvalidGraphic, aComment);
+
+ AfterSigning(bSignSuccess, false);
+
+ // Reload the document to get the updated graphic
+ // FIXME: Update just the signature line graphic instead of reloading the document
+ SfxViewFrame *pFrame = GetFrame();
+ if (pFrame)
+ pFrame->GetDispatcher()->Execute(SID_RELOAD);
+}
+
+SignatureState SfxObjectShell::GetScriptingSignatureState()
+{
+ return ImplGetSignatureState( true );
+}
+
+bool SfxObjectShell::SignScriptingContent(weld::Window* pDialogParent)
+{
+ if (!PrepareForSigning(pDialogParent))
+ return false;
+
+ if (CheckIsReadonly(true, pDialogParent))
+ return false;
+
+ bool bSignSuccess = GetMedium()->SignContents_Impl(pDialogParent, true, HasValidSignatures());
+
+ AfterSigning(bSignSuccess, true);
+
+ return bSignSuccess;
+}
+
+const uno::Sequence<sal_Int8>& SfxObjectShell::getUnoTunnelId()
+{
+ static const comphelper::UnoIdInit theSfxObjectShellUnoTunnelId;
+ return theSfxObjectShellUnoTunnelId.getSeq();
+}
+
+uno::Sequence< beans::PropertyValue > SfxObjectShell::GetDocumentProtectionFromGrabBag() const
+{
+ uno::Reference<frame::XModel> xModel = GetBaseModel();
+
+ if (!xModel.is())
+ {
+ return uno::Sequence< beans::PropertyValue>();
+ }
+
+ uno::Reference< beans::XPropertySet > xPropSet( xModel, uno::UNO_QUERY_THROW );
+ uno::Reference< beans::XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();
+ const OUString aGrabBagName = UNO_NAME_MISC_OBJ_INTEROPGRABBAG;
+ if ( xPropSetInfo->hasPropertyByName( aGrabBagName ) )
+ {
+ uno::Sequence< beans::PropertyValue > propList;
+ xPropSet->getPropertyValue( aGrabBagName ) >>= propList;
+ for( const auto& rProp : std::as_const(propList) )
+ {
+ if (rProp.Name == "DocumentProtection")
+ {
+ uno::Sequence< beans::PropertyValue > rAttributeList;
+ rProp.Value >>= rAttributeList;
+ return rAttributeList;
+ }
+ }
+ }
+
+ return uno::Sequence< beans::PropertyValue>();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/objstor.cxx b/sfx2/source/doc/objstor.cxx
new file mode 100644
index 0000000000..ea1063ea81
--- /dev/null
+++ b/sfx2/source/doc/objstor.cxx
@@ -0,0 +1,3968 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <config_features.h>
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <cassert>
+
+#include <svl/eitem.hxx>
+#include <svl/stritem.hxx>
+#include <svl/intitem.hxx>
+#include <com/sun/star/frame/theGlobalEventBroadcaster.hpp>
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/frame/XModule.hpp>
+#include <com/sun/star/document/XFilter.hpp>
+#include <com/sun/star/document/XImporter.hpp>
+#include <com/sun/star/document/XExporter.hpp>
+#include <com/sun/star/packages/zip/ZipIOException.hpp>
+#include <com/sun/star/task/XInteractionHandler.hpp>
+#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
+#include <com/sun/star/document/MacroExecMode.hpp>
+#include <com/sun/star/ui/dialogs/ExtendedFilePickerElementIds.hpp>
+#include <com/sun/star/beans/XPropertySetInfo.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/embed/ElementModes.hpp>
+#include <com/sun/star/embed/EmbedStates.hpp>
+#include <com/sun/star/embed/XTransactedObject.hpp>
+#include <com/sun/star/embed/XEmbeddedObject.hpp>
+#include <com/sun/star/embed/XEmbedPersist.hpp>
+#include <com/sun/star/embed/XOptimizedStorage.hpp>
+#include <com/sun/star/embed/XEncryptionProtectedStorage.hpp>
+#include <com/sun/star/io/WrongFormatException.hpp>
+#include <com/sun/star/io/XTruncate.hpp>
+#include <com/sun/star/util/XModifiable.hpp>
+#include <com/sun/star/util/RevisionTag.hpp>
+#include <com/sun/star/security/DocumentDigitalSignatures.hpp>
+#include <com/sun/star/text/XTextRange.hpp>
+#include <com/sun/star/xml/crypto/CipherID.hpp>
+#include <com/sun/star/xml/crypto/DigestID.hpp>
+#include <com/sun/star/xml/crypto/KDFID.hpp>
+
+#include <com/sun/star/document/XDocumentProperties.hpp>
+#include <com/sun/star/document/XDocumentPropertiesSupplier.hpp>
+#include <comphelper/fileformat.h>
+#include <comphelper/processfactory.hxx>
+#include <svtools/langtab.hxx>
+#include <svtools/sfxecode.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/streamwrap.hxx>
+
+#include <unotools/saveopt.hxx>
+#include <unotools/useroptions.hxx>
+#include <unotools/securityoptions.hxx>
+#include <tools/urlobj.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <unotools/ucbhelper.hxx>
+#include <unotools/tempfile.hxx>
+#include <unotools/docinfohelper.hxx>
+#include <unotools/mediadescriptor.hxx>
+#include <ucbhelper/content.hxx>
+#include <sot/storage.hxx>
+#include <sot/exchange.hxx>
+#include <sot/formats.hxx>
+#include <comphelper/storagehelper.hxx>
+#include <comphelper/documentconstants.hxx>
+#include <comphelper/string.hxx>
+#include <vcl/errinf.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <basic/modsizeexceeded.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <osl/file.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <comphelper/lok.hxx>
+#include <i18nlangtag/languagetag.hxx>
+
+#include <sfx2/signaturestate.hxx>
+#include <sfx2/app.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/sfxresid.hxx>
+#include <sfx2/docfile.hxx>
+#include <sfx2/fcontnr.hxx>
+#include <sfx2/docfilt.hxx>
+#include <sfx2/docfac.hxx>
+#include <appopen.hxx>
+#include <objshimp.hxx>
+#include <sfx2/lokhelper.hxx>
+#include <sfx2/strings.hrc>
+#include <sfx2/sfxsids.hrc>
+#include <sfx2/dispatch.hxx>
+#include <sfx2/sfxuno.hxx>
+#include <sfx2/event.hxx>
+#include <sfx2/infobar.hxx>
+#include <fltoptint.hxx>
+#include <sfx2/viewfrm.hxx>
+#include "graphhelp.hxx"
+#include <appbaslib.hxx>
+#include "objstor.hxx"
+#include "exoticfileloadexception.hxx"
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::container;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::ucb;
+using namespace ::com::sun::star::task;
+using namespace ::com::sun::star::document;
+using namespace ::cppu;
+
+
+void impl_addToModelCollection(const css::uno::Reference< css::frame::XModel >& xModel)
+{
+ if (!xModel.is())
+ return;
+
+ css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+ css::uno::Reference< css::frame::XGlobalEventBroadcaster > xModelCollection =
+ css::frame::theGlobalEventBroadcaster::get(xContext);
+ try
+ {
+ xModelCollection->insert(css::uno::Any(xModel));
+ }
+ catch ( uno::Exception& )
+ {
+ SAL_WARN( "sfx.doc", "The document seems to be in the collection already!" );
+ }
+}
+
+
+bool SfxObjectShell::Save()
+{
+ SaveChildren();
+ return true;
+}
+
+
+bool SfxObjectShell::SaveAs( SfxMedium& rMedium )
+{
+ return SaveAsChildren( rMedium );
+}
+
+
+bool SfxObjectShell::QuerySlotExecutable( sal_uInt16 /*nSlotId*/ )
+{
+ return true;
+}
+
+static bool UseODFWholesomeEncryption(SvtSaveOptions::ODFSaneDefaultVersion const nODFVersion)
+{
+ return nODFVersion == SvtSaveOptions::ODFSVER_LATEST_EXTENDED
+ && officecfg::Office::Common::Misc::ExperimentalMode::get();
+}
+
+bool GetEncryptionData_Impl( const SfxItemSet* pSet, uno::Sequence< beans::NamedValue >& o_rEncryptionData )
+{
+ bool bResult = false;
+ if ( pSet )
+ {
+ const SfxUnoAnyItem* pEncryptionDataItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pSet, SID_ENCRYPTIONDATA, false);
+ if ( pEncryptionDataItem )
+ {
+ pEncryptionDataItem->GetValue() >>= o_rEncryptionData;
+ bResult = true;
+ }
+ else
+ {
+ const SfxStringItem* pPasswordItem = SfxItemSet::GetItem<SfxStringItem>(pSet, SID_PASSWORD, false);
+ if ( pPasswordItem )
+ {
+ o_rEncryptionData = ::comphelper::OStorageHelper::CreatePackageEncryptionData( pPasswordItem->GetValue() );
+ bResult = true;
+ }
+ }
+ }
+
+ return bResult;
+}
+
+
+bool SfxObjectShell::PutURLContentsToVersionStream_Impl(
+ const OUString& aURL,
+ const uno::Reference< embed::XStorage >& xDocStorage,
+ const OUString& aStreamName )
+{
+ bool bResult = false;
+ try
+ {
+ uno::Reference< embed::XStorage > xVersion = xDocStorage->openStorageElement(
+ "Versions",
+ embed::ElementModes::READWRITE );
+
+ DBG_ASSERT( xVersion.is(),
+ "The method must throw an exception if the storage can not be opened!" );
+ if ( !xVersion.is() )
+ throw uno::RuntimeException();
+
+ uno::Reference< io::XStream > xVerStream = xVersion->openStreamElement(
+ aStreamName,
+ embed::ElementModes::READWRITE );
+ DBG_ASSERT( xVerStream.is(), "The method must throw an exception if the storage can not be opened!" );
+ if ( !xVerStream.is() )
+ throw uno::RuntimeException();
+
+ uno::Reference< io::XOutputStream > xOutStream = xVerStream->getOutputStream();
+ uno::Reference< io::XTruncate > xTrunc( xOutStream, uno::UNO_QUERY_THROW );
+
+ uno::Reference< io::XInputStream > xTmpInStream =
+ ::comphelper::OStorageHelper::GetInputStreamFromURL(
+ aURL, comphelper::getProcessComponentContext() );
+ assert( xTmpInStream.is() );
+
+ xTrunc->truncate();
+ ::comphelper::OStorageHelper::CopyInputToOutput( xTmpInStream, xOutStream );
+ xOutStream->closeOutput();
+
+ uno::Reference< embed::XTransactedObject > xTransact( xVersion, uno::UNO_QUERY );
+ DBG_ASSERT( xTransact.is(), "The storage must implement XTransacted interface!\n" );
+ if ( xTransact.is() )
+ xTransact->commit();
+
+ bResult = true;
+ }
+ catch( uno::Exception& )
+ {
+ // TODO/LATER: handle the error depending on exception
+ SetError(ERRCODE_IO_GENERAL);
+ }
+
+ return bResult;
+}
+
+
+OUString SfxObjectShell::CreateTempCopyOfStorage_Impl( const uno::Reference< embed::XStorage >& xStorage )
+{
+ OUString aTempURL = ::utl::CreateTempURL();
+
+ DBG_ASSERT( !aTempURL.isEmpty(), "Can't create a temporary file!\n" );
+ if ( !aTempURL.isEmpty() )
+ {
+ try
+ {
+ uno::Reference< embed::XStorage > xTempStorage =
+ ::comphelper::OStorageHelper::GetStorageFromURL( aTempURL, embed::ElementModes::READWRITE );
+
+ // the password will be transferred from the xStorage to xTempStorage by storage implementation
+ xStorage->copyToStorage( xTempStorage );
+
+ // the temporary storage was committed by the previous method and it will die by refcount
+ }
+ catch ( uno::Exception& )
+ {
+ SAL_WARN( "sfx.doc", "Creation of a storage copy is failed!" );
+ ::utl::UCBContentHelper::Kill( aTempURL );
+
+ aTempURL.clear();
+
+ // TODO/LATER: may need error code setting based on exception
+ SetError(ERRCODE_IO_GENERAL);
+ }
+ }
+
+ return aTempURL;
+}
+
+
+SvGlobalName const & SfxObjectShell::GetClassName() const
+{
+ return GetFactory().GetClassId();
+}
+
+
+void SfxObjectShell::SetupStorage( const uno::Reference< embed::XStorage >& xStorage,
+ sal_Int32 nVersion, bool bTemplate ) const
+{
+ uno::Reference< beans::XPropertySet > xProps( xStorage, uno::UNO_QUERY );
+
+ if ( !xProps.is() )
+ return;
+
+ SotClipboardFormatId nClipFormat = SotClipboardFormatId::NONE;
+
+ SvGlobalName aName;
+ OUString aFullTypeName;
+ FillClass( &aName, &nClipFormat, &aFullTypeName, nVersion, bTemplate );
+
+ if ( nClipFormat == SotClipboardFormatId::NONE )
+ return;
+
+ // basic doesn't have a ClipFormat
+ // without MediaType the storage is not really usable, but currently the BasicIDE still
+ // is an SfxObjectShell and so we can't take this as an error
+ datatransfer::DataFlavor aDataFlavor;
+ SotExchange::GetFormatDataFlavor( nClipFormat, aDataFlavor );
+ if ( aDataFlavor.MimeType.isEmpty() )
+ return;
+
+ try
+ {
+ xProps->setPropertyValue("MediaType", uno::Any( aDataFlavor.MimeType ) );
+ }
+ catch( uno::Exception& )
+ {
+ const_cast<SfxObjectShell*>( this )->SetError(ERRCODE_IO_GENERAL);
+ }
+
+ SvtSaveOptions::ODFSaneDefaultVersion nDefVersion = SvtSaveOptions::ODFSVER_013;
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ nDefVersion = GetODFSaneDefaultVersion();
+ }
+
+ // the default values, that should be used for ODF1.1 and older formats
+ uno::Sequence< beans::NamedValue > aEncryptionAlgs
+ {
+ { "StartKeyGenerationAlgorithm", css::uno::Any(xml::crypto::DigestID::SHA1) },
+ { "EncryptionAlgorithm", css::uno::Any(xml::crypto::CipherID::BLOWFISH_CFB_8) },
+ { "ChecksumAlgorithm", css::uno::Any(xml::crypto::DigestID::SHA1_1K) },
+ { "KeyDerivationFunction", css::uno::Any(xml::crypto::KDFID::PBKDF2) },
+ };
+
+ if (nDefVersion >= SvtSaveOptions::ODFSVER_012)
+ {
+ try
+ {
+ // older versions can not have this property set, it exists only starting from ODF1.2
+ uno::Reference<frame::XModule> const xModule(GetModel(), uno::UNO_QUERY);
+ bool const isBaseForm(xModule.is() &&
+ xModule->getIdentifier() == "com.sun.star.sdb.FormDesign");
+ SAL_INFO_IF(isBaseForm, "sfx.doc", "tdf#138209 force form export to ODF 1.2");
+ if (!isBaseForm && SvtSaveOptions::ODFSVER_013 <= nDefVersion)
+ {
+ xProps->setPropertyValue("Version", uno::Any(ODFVER_013_TEXT));
+ }
+ else
+ {
+ xProps->setPropertyValue("Version", uno::Any(ODFVER_012_TEXT));
+ }
+ }
+ catch( uno::Exception& )
+ {
+ }
+
+ auto pEncryptionAlgs = aEncryptionAlgs.getArray();
+ pEncryptionAlgs[0].Value <<= xml::crypto::DigestID::SHA256;
+ if (UseODFWholesomeEncryption(nDefVersion))
+ {
+ pEncryptionAlgs[1].Value <<= xml::crypto::CipherID::AES_GCM_W3C;
+ pEncryptionAlgs[2].Value.clear();
+ if (!getenv("LO_ARGON2_DISABLE"))
+ {
+ pEncryptionAlgs[3].Value <<= xml::crypto::KDFID::Argon2id;
+ }
+ }
+ else
+ {
+ pEncryptionAlgs[1].Value <<= xml::crypto::CipherID::AES_CBC_W3C_PADDING;
+ pEncryptionAlgs[2].Value <<= xml::crypto::DigestID::SHA256_1K;
+ }
+ }
+
+ try
+ {
+ // set the encryption algorithms accordingly;
+ // the setting does not trigger encryption,
+ // it just provides the format for the case that contents should be encrypted
+ uno::Reference< embed::XEncryptionProtectedStorage > xEncr( xStorage, uno::UNO_QUERY_THROW );
+ xEncr->setEncryptionAlgorithms( aEncryptionAlgs );
+ }
+ catch( uno::Exception& )
+ {
+ const_cast<SfxObjectShell*>( this )->SetError(ERRCODE_IO_GENERAL);
+ }
+}
+
+
+void SfxObjectShell::PrepareSecondTryLoad_Impl()
+{
+ // only for internal use
+ pImpl->m_xDocStorage.clear();
+ pImpl->m_bIsInit = false;
+ ResetError();
+}
+
+
+bool SfxObjectShell::GeneralInit_Impl( const uno::Reference< embed::XStorage >& xStorage,
+ bool bTypeMustBeSetAlready )
+{
+ if ( pImpl->m_bIsInit )
+ return false;
+
+ pImpl->m_bIsInit = true;
+ if ( xStorage.is() )
+ {
+ // no notification is required the storage is set the first time
+ pImpl->m_xDocStorage = xStorage;
+
+ try {
+ uno::Reference < beans::XPropertySet > xPropSet( xStorage, uno::UNO_QUERY_THROW );
+ Any a = xPropSet->getPropertyValue("MediaType");
+ OUString aMediaType;
+ if ( !(a>>=aMediaType) || aMediaType.isEmpty() )
+ {
+ if ( bTypeMustBeSetAlready )
+ {
+ SetError(ERRCODE_IO_BROKENPACKAGE);
+ return false;
+ }
+
+ SetupStorage( xStorage, SOFFICE_FILEFORMAT_CURRENT, false );
+ }
+ }
+ catch ( uno::Exception& )
+ {
+ SAL_WARN( "sfx.doc", "Can't check storage's mediatype!" );
+ }
+ }
+ else
+ pImpl->m_bCreateTempStor = true;
+
+ return true;
+}
+
+
+bool SfxObjectShell::InitNew( const uno::Reference< embed::XStorage >& xStorage )
+{
+ return GeneralInit_Impl( xStorage, false );
+}
+
+
+bool SfxObjectShell::Load( SfxMedium& rMedium )
+{
+ return GeneralInit_Impl(rMedium.GetStorage(), true);
+}
+
+void SfxObjectShell::DoInitUnitTest()
+{
+ pMedium = new SfxMedium;
+}
+
+bool SfxObjectShell::DoInitNew()
+/* [Description]
+
+ This from SvPersist inherited virtual method is called to initialize
+ the SfxObjectShell instance from a storage (PStore! = 0) or (PStore == 0)
+
+ Like with all Do...-methods there is a from a control, the actual
+ implementation is done by the virtual method in which also the
+ InitNew(SvStorate *) from the SfxObjectShell-Subclass is implemented.
+
+ For pStore == 0 the SfxObjectShell-instance is connected to an empty
+ SfxMedium, otherwise a SfxMedium, which refers to the SotStorage
+ passed as a parameter.
+
+ The object is only initialized correctly after InitNew() or Load().
+
+ [Return value]
+ true The object has been initialized.
+ false The object could not be initialized
+*/
+
+{
+ ModifyBlocker_Impl aBlock( this );
+ pMedium = new SfxMedium;
+
+ pMedium->CanDisposeStorage_Impl( true );
+
+ if ( InitNew( nullptr ) )
+ {
+ // empty documents always get their macros from the user, so there is no reason to restrict access
+ pImpl->aMacroMode.allowMacroExecution();
+ if ( SfxObjectCreateMode::EMBEDDED == eCreateMode )
+ SetTitle(SfxResId(STR_NONAME));
+
+ uno::Reference< frame::XModel > xModel = GetModel();
+ if ( xModel.is() )
+ {
+ SfxItemSet &rSet = GetMedium()->GetItemSet();
+ uno::Sequence< beans::PropertyValue > aArgs;
+ TransformItems( SID_OPENDOC, rSet, aArgs );
+ sal_Int32 nLength = aArgs.getLength();
+ aArgs.realloc( nLength + 1 );
+ auto pArgs = aArgs.getArray();
+ pArgs[nLength].Name = "Title";
+ pArgs[nLength].Value <<= GetTitle( SFX_TITLE_DETECT );
+ xModel->attachResource( OUString(), aArgs );
+ if (!utl::ConfigManager::IsFuzzing())
+ impl_addToModelCollection(xModel);
+ }
+
+ SetInitialized_Impl( true );
+ return true;
+ }
+
+ return false;
+}
+
+bool SfxObjectShell::ImportFromGeneratedStream_Impl(
+ const uno::Reference< io::XStream >& xStream,
+ const uno::Sequence< beans::PropertyValue >& rMediaDescr )
+{
+ if ( !xStream.is() )
+ return false;
+
+ if ( pMedium && pMedium->HasStorage_Impl() )
+ pMedium->CloseStorage();
+
+ bool bResult = false;
+
+ try
+ {
+ uno::Reference< embed::XStorage > xStorage =
+ ::comphelper::OStorageHelper::GetStorageFromStream( xStream );
+
+ if ( !xStorage.is() )
+ throw uno::RuntimeException();
+
+ if ( !pMedium )
+ pMedium = new SfxMedium( xStorage, OUString() );
+ else
+ pMedium->SetStorage_Impl( xStorage );
+
+ SfxAllItemSet aSet( SfxGetpApp()->GetPool() );
+ TransformParameters( SID_OPENDOC, rMediaDescr, aSet );
+ pMedium->GetItemSet().Put( aSet );
+ pMedium->CanDisposeStorage_Impl( false );
+ uno::Reference<text::XTextRange> xInsertTextRange;
+ for (const auto& rProp : rMediaDescr)
+ {
+ if (rProp.Name == "TextInsertModeRange")
+ {
+ rProp.Value >>= xInsertTextRange;
+ }
+ }
+
+ if (xInsertTextRange.is())
+ {
+ bResult = InsertGeneratedStream(*pMedium, xInsertTextRange);
+ }
+ else
+ {
+
+ // allow the subfilter to reinit the model
+ if ( pImpl->m_bIsInit )
+ pImpl->m_bIsInit = false;
+
+ if ( LoadOwnFormat( *pMedium ) )
+ {
+ bHasName = true;
+ if ( !IsReadOnly() && IsLoadReadonly() )
+ SetReadOnlyUI();
+
+ bResult = true;
+ OSL_ENSURE( pImpl->m_xDocStorage == xStorage, "Wrong storage is used!" );
+ }
+ }
+
+ // now the medium can be disconnected from the storage
+ // the medium is not allowed to dispose the storage so CloseStorage() can be used
+ pMedium->CloseStorage();
+ }
+ catch( uno::Exception& )
+ {
+ }
+
+ return bResult;
+}
+
+
+bool SfxObjectShell::DoLoad( SfxMedium *pMed )
+{
+ ModifyBlocker_Impl aBlock( this );
+
+ pMedium = pMed;
+ pMedium->CanDisposeStorage_Impl( true );
+
+ bool bOk = false;
+ std::shared_ptr<const SfxFilter> pFilter = pMed->GetFilter();
+ SfxItemSet& rSet = pMedium->GetItemSet();
+ if( pImpl->nEventId == SfxEventHintId::NONE )
+ {
+ const SfxBoolItem* pTemplateItem = rSet.GetItem(SID_TEMPLATE, false);
+ SetActivateEvent_Impl(
+ ( pTemplateItem && pTemplateItem->GetValue() )
+ ? SfxEventHintId::CreateDoc : SfxEventHintId::OpenDoc );
+ }
+
+ const SfxStringItem* pBaseItem = rSet.GetItem(SID_BASEURL, false);
+ OUString aBaseURL;
+ const SfxStringItem* pSalvageItem = rSet.GetItem(SID_DOC_SALVAGE, false);
+ if( pBaseItem )
+ aBaseURL = pBaseItem->GetValue();
+ else
+ {
+ if ( pSalvageItem )
+ {
+ osl::FileBase::getFileURLFromSystemPath( pMed->GetPhysicalName(), aBaseURL );
+ }
+ else
+ aBaseURL = pMed->GetBaseURL();
+ }
+ pMed->GetItemSet().Put( SfxStringItem( SID_DOC_BASEURL, aBaseURL ) );
+
+ pImpl->nLoadedFlags = SfxLoadedFlags::NONE;
+ pImpl->bModelInitialized = false;
+
+ if (pFilter && !pFilter->IsEnabled())
+ {
+ SetError( ERRCODE_IO_FILTERDISABLED );
+ }
+
+ if ( pFilter && pFilter->IsExoticFormat() && !QueryAllowExoticFormat_Impl( getInteractionHandler(), aBaseURL, pMed->GetFilter()->GetUIName() ) )
+ {
+ SetError( ERRCODE_IO_ABORT );
+ }
+
+ // initialize static language table so language-related extensions are learned before the document loads
+ (void)SvtLanguageTable::GetLanguageEntryCount();
+
+ //TODO/LATER: make a clear strategy how to handle "UsesStorage" etc.
+ bool bOwnStorageFormat = IsOwnStorageFormat( *pMedium );
+ bool bHasStorage = IsPackageStorageFormat_Impl( *pMedium );
+ if ( pMedium->GetFilter() )
+ {
+ ErrCode nError = HandleFilter( pMedium, this );
+ if ( nError != ERRCODE_NONE )
+ SetError(nError);
+
+ if (pMedium->GetFilter()->GetFilterFlags() & SfxFilterFlags::STARTPRESENTATION)
+ rSet.Put( SfxBoolItem( SID_DOC_STARTPRESENTATION, true) );
+ }
+
+ EnableSetModified( false );
+
+ pMedium->LockOrigFileOnDemand( true, false );
+ if ( GetErrorIgnoreWarning() == ERRCODE_NONE && bOwnStorageFormat && ( !pFilter || !( pFilter->GetFilterFlags() & SfxFilterFlags::STARONEFILTER ) ) )
+ {
+ uno::Reference< embed::XStorage > xStorage;
+ if ( pMedium->GetErrorIgnoreWarning() == ERRCODE_NONE )
+ xStorage = pMedium->GetStorage();
+
+ if( xStorage.is() && pMedium->GetLastStorageCreationState() == ERRCODE_NONE )
+ {
+ DBG_ASSERT( pFilter, "No filter for storage found!" );
+
+ try
+ {
+ bool bWarnMediaTypeFallback = false;
+ const SfxBoolItem* pRepairPackageItem = rSet.GetItem(SID_REPAIRPACKAGE, false);
+
+ // treat the package as broken if the mediatype was retrieved as a fallback
+ uno::Reference< beans::XPropertySet > xStorProps( xStorage, uno::UNO_QUERY_THROW );
+ xStorProps->getPropertyValue("MediaTypeFallbackUsed")
+ >>= bWarnMediaTypeFallback;
+
+ if ( pRepairPackageItem && pRepairPackageItem->GetValue() )
+ {
+ // the macros in repaired documents should be disabled
+ pMedium->GetItemSet().Put( SfxUInt16Item( SID_MACROEXECMODE, document::MacroExecMode::NEVER_EXECUTE ) );
+
+ // the mediatype was retrieved by using fallback solution but this is a repairing mode
+ // so it is acceptable to open the document if there is no contents that required manifest.xml
+ bWarnMediaTypeFallback = false;
+ }
+
+ if (bWarnMediaTypeFallback || !xStorage->getElementNames().hasElements())
+ SetError(ERRCODE_IO_BROKENPACKAGE);
+ }
+ catch( uno::Exception& )
+ {
+ // TODO/LATER: may need error code setting based on exception
+ SetError(ERRCODE_IO_GENERAL);
+ }
+
+ // Load
+ if ( !GetErrorIgnoreWarning() )
+ {
+ pImpl->nLoadedFlags = SfxLoadedFlags::NONE;
+ pImpl->bModelInitialized = false;
+ bOk = xStorage.is() && LoadOwnFormat( *pMed );
+ if ( bOk )
+ {
+ // the document loaded from template has no name
+ const SfxBoolItem* pTemplateItem = rSet.GetItem(SID_TEMPLATE, false);
+ if ( !pTemplateItem || !pTemplateItem->GetValue() )
+ bHasName = true;
+ }
+ else
+ SetError(ERRCODE_ABORT);
+ }
+ }
+ else
+ SetError(pMed->GetLastStorageCreationState());
+ }
+ else if ( GetErrorIgnoreWarning() == ERRCODE_NONE && InitNew(nullptr) )
+ {
+ // set name before ConvertFrom, so that GetSbxObject() already works
+ bHasName = true;
+ SetName( SfxResId(STR_NONAME) );
+
+ if( !bHasStorage )
+ pMedium->GetInStream();
+ else
+ pMedium->GetStorage();
+
+ if ( GetErrorIgnoreWarning() == ERRCODE_NONE )
+ {
+ // Experimental PDF importing using PDFium. This is currently enabled for LOK only and
+ // we handle it not via XmlFilterAdaptor but a new SdPdfFiler.
+#if !HAVE_FEATURE_POPPLER
+ constexpr bool bUsePdfium = true;
+#else
+ const bool bUsePdfium
+ = comphelper::LibreOfficeKit::isActive() || getenv("LO_IMPORT_USE_PDFIUM");
+#endif
+ const bool bPdfiumImport
+ = bUsePdfium && pMedium->GetFilter()
+ && (pMedium->GetFilter()->GetFilterName() == "draw_pdf_import");
+
+ pImpl->nLoadedFlags = SfxLoadedFlags::NONE;
+ pImpl->bModelInitialized = false;
+ if (pMedium->GetFilter()
+ && (pMedium->GetFilter()->GetFilterFlags() & SfxFilterFlags::STARONEFILTER)
+ && !bPdfiumImport)
+ {
+ uno::Reference < beans::XPropertySet > xSet( GetModel(), uno::UNO_QUERY );
+ static constexpr OUString sLockUpdates(u"LockUpdates"_ustr);
+ bool bSetProperty = true;
+ try
+ {
+ xSet->setPropertyValue( sLockUpdates, Any( true ) );
+ }
+ catch(const beans::UnknownPropertyException& )
+ {
+ bSetProperty = false;
+ }
+ bOk = ImportFrom(*pMedium, nullptr);
+ if(bSetProperty)
+ {
+ try
+ {
+ xSet->setPropertyValue( sLockUpdates, Any( false ) );
+ }
+ catch(const beans::UnknownPropertyException& )
+ {}
+ }
+ UpdateLinks();
+ FinishedLoading();
+ }
+ else
+ {
+ if (tools::isEmptyFileUrl(pMedium->GetName()))
+ {
+ // The import filter would fail with empty input.
+ bOk = true;
+ }
+ else
+ {
+ bOk = ConvertFrom(*pMedium);
+ }
+ InitOwnModel_Impl();
+ }
+ }
+ }
+
+ if ( bOk )
+ {
+ if ( IsReadOnlyMedium() || IsLoadReadonly() )
+ SetReadOnlyUI();
+
+ try
+ {
+ ::ucbhelper::Content aContent( pMedium->GetName(), utl::UCBContentHelper::getDefaultCommandEnvironment(), comphelper::getProcessComponentContext() );
+ css::uno::Reference < XPropertySetInfo > xProps = aContent.getProperties();
+ if ( xProps.is() )
+ {
+ static constexpr OUString aAuthor( u"Author"_ustr );
+ static constexpr OUString aKeywords( u"Keywords"_ustr );
+ static constexpr OUString aSubject( u"Subject"_ustr );
+ Any aAny;
+ OUString aValue;
+ uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
+ GetModel(), uno::UNO_QUERY_THROW);
+ uno::Reference<document::XDocumentProperties> xDocProps
+ = xDPS->getDocumentProperties();
+ if ( xProps->hasPropertyByName( aAuthor ) )
+ {
+ aAny = aContent.getPropertyValue( aAuthor );
+ if ( aAny >>= aValue )
+ xDocProps->setAuthor(aValue);
+ }
+ if ( xProps->hasPropertyByName( aKeywords ) )
+ {
+ aAny = aContent.getPropertyValue( aKeywords );
+ if ( aAny >>= aValue )
+ xDocProps->setKeywords(
+ ::comphelper::string::convertCommaSeparated(aValue));
+;
+ }
+ if ( xProps->hasPropertyByName( aSubject ) )
+ {
+ aAny = aContent.getPropertyValue( aSubject );
+ if ( aAny >>= aValue ) {
+ xDocProps->setSubject(aValue);
+ }
+ }
+ }
+ }
+ catch( Exception& )
+ {
+ }
+
+ // If not loaded asynchronously call FinishedLoading
+ if ( !( pImpl->nLoadedFlags & SfxLoadedFlags::MAINDOCUMENT ) &&
+ ( !pMedium->GetFilter() || pMedium->GetFilter()->UsesStorage() )
+ )
+ FinishedLoading( SfxLoadedFlags::MAINDOCUMENT );
+
+ Broadcast( SfxHint(SfxHintId::NameChanged) );
+
+ if ( SfxObjectCreateMode::EMBEDDED != eCreateMode )
+ {
+ const SfxBoolItem* pAsTempItem = rSet.GetItem(SID_TEMPLATE, false);
+ const SfxBoolItem* pPreviewItem = rSet.GetItem(SID_PREVIEW, false);
+ const SfxBoolItem* pHiddenItem = rSet.GetItem(SID_HIDDEN, false);
+ if( bOk && !pMedium->GetOrigURL().isEmpty()
+ && !( pAsTempItem && pAsTempItem->GetValue() )
+ && !( pPreviewItem && pPreviewItem->GetValue() )
+ && !( pHiddenItem && pHiddenItem->GetValue() ) )
+ {
+ AddToRecentlyUsedList();
+ }
+ }
+
+ const SfxBoolItem* pDdeReconnectItem = rSet.GetItem(SID_DDE_RECONNECT_ONLOAD, false);
+
+ bool bReconnectDde = true; // by default, we try to auto-connect DDE connections.
+ if (pDdeReconnectItem)
+ bReconnectDde = pDdeReconnectItem->GetValue();
+
+ if (bReconnectDde)
+ ReconnectDdeLinks(*this);
+ }
+
+ return bOk;
+}
+
+bool SfxObjectShell::DoLoadExternal( SfxMedium *pMed )
+{
+ pMedium = pMed;
+ return LoadExternal(*pMedium);
+}
+
+ErrCode SfxObjectShell::HandleFilter( SfxMedium* pMedium, SfxObjectShell const * pDoc )
+{
+ ErrCode nError = ERRCODE_NONE;
+ SfxItemSet& rSet = pMedium->GetItemSet();
+ const SfxStringItem* pOptions = rSet.GetItem(SID_FILE_FILTEROPTIONS, false);
+ const SfxUnoAnyItem* pData = rSet.GetItem(SID_FILTER_DATA, false);
+ const bool bTiledRendering = comphelper::LibreOfficeKit::isActive();
+ if ( !pData && (bTiledRendering || !pOptions) )
+ {
+ css::uno::Reference< XMultiServiceFactory > xServiceManager = ::comphelper::getProcessServiceFactory();
+ css::uno::Reference< XNameAccess > xFilterCFG;
+ if( xServiceManager.is() )
+ {
+ xFilterCFG.set( xServiceManager->createInstance("com.sun.star.document.FilterFactory"),
+ UNO_QUERY );
+ }
+
+ if( xFilterCFG.is() )
+ {
+ try {
+ bool bAbort = false;
+ std::shared_ptr<const SfxFilter> pFilter = pMedium->GetFilter();
+ Sequence < PropertyValue > aProps;
+ Any aAny = xFilterCFG->getByName( pFilter->GetName() );
+ if ( aAny >>= aProps )
+ {
+ auto pProp = std::find_if(std::cbegin(aProps), std::cend(aProps),
+ [](const PropertyValue& rProp) { return rProp.Name == "UIComponent"; });
+ if (pProp != std::cend(aProps))
+ {
+ OUString aServiceName;
+ pProp->Value >>= aServiceName;
+ if( !aServiceName.isEmpty() )
+ {
+ css::uno::Reference< XInteractionHandler > rHandler = pMedium->GetInteractionHandler();
+ if( rHandler.is() )
+ {
+ // we need some properties in the media descriptor, so we have to make sure that they are in
+ Any aStreamAny;
+ aStreamAny <<= pMedium->GetInputStream();
+ if ( rSet.GetItemState( SID_INPUTSTREAM ) < SfxItemState::SET )
+ rSet.Put( SfxUnoAnyItem( SID_INPUTSTREAM, aStreamAny ) );
+ if ( rSet.GetItemState( SID_FILE_NAME ) < SfxItemState::SET )
+ rSet.Put( SfxStringItem( SID_FILE_NAME, pMedium->GetName() ) );
+ if ( rSet.GetItemState( SID_FILTER_NAME ) < SfxItemState::SET )
+ rSet.Put( SfxStringItem( SID_FILTER_NAME, pFilter->GetName() ) );
+
+ Sequence< PropertyValue > rProperties;
+ TransformItems( SID_OPENDOC, rSet, rProperties );
+ rtl::Reference<RequestFilterOptions> pFORequest = new RequestFilterOptions( pDoc->GetModel(), rProperties );
+
+ rHandler->handle( pFORequest );
+
+ if ( !pFORequest->isAbort() )
+ {
+ SfxAllItemSet aNewParams( pDoc->GetPool() );
+ TransformParameters( SID_OPENDOC,
+ pFORequest->getFilterOptions(),
+ aNewParams );
+
+ const SfxStringItem* pFilterOptions = aNewParams.GetItem<SfxStringItem>(SID_FILE_FILTEROPTIONS, false);
+ if ( pFilterOptions )
+ rSet.Put( *pFilterOptions );
+
+ const SfxUnoAnyItem* pFilterData = aNewParams.GetItem<SfxUnoAnyItem>(SID_FILTER_DATA, false);
+ if ( pFilterData )
+ rSet.Put( *pFilterData );
+ }
+ else
+ bAbort = true;
+ }
+ }
+ }
+ }
+
+ if( bAbort )
+ {
+ // filter options were not entered
+ nError = ERRCODE_ABORT;
+ }
+ }
+ catch( NoSuchElementException& )
+ {
+ // the filter name is unknown
+ nError = ERRCODE_IO_INVALIDPARAMETER;
+ }
+ catch( Exception& )
+ {
+ nError = ERRCODE_ABORT;
+ }
+ }
+ }
+
+ return nError;
+}
+
+
+bool SfxObjectShell::IsOwnStorageFormat(const SfxMedium &rMedium)
+{
+ return !rMedium.GetFilter() || // Embedded
+ ( rMedium.GetFilter()->IsOwnFormat() &&
+ rMedium.GetFilter()->UsesStorage() &&
+ rMedium.GetFilter()->GetVersion() >= SOFFICE_FILEFORMAT_60 );
+}
+
+
+bool SfxObjectShell::IsPackageStorageFormat_Impl(const SfxMedium &rMedium)
+{
+ return !rMedium.GetFilter() || // Embedded
+ ( rMedium.GetFilter()->UsesStorage() &&
+ rMedium.GetFilter()->GetVersion() >= SOFFICE_FILEFORMAT_60 );
+}
+
+
+bool SfxObjectShell::DoSave()
+// DoSave is only invoked for OLE. Save your own documents in the SFX through
+// DoSave_Impl order to allow for the creation of backups.
+// Save in your own format again.
+{
+ bool bOk = false ;
+ {
+ ModifyBlocker_Impl aBlock( this );
+
+ pImpl->bIsSaving = true;
+
+ if (IsOwnStorageFormat(*GetMedium()))
+ {
+ SvtSaveOptions::ODFSaneDefaultVersion nDefVersion = SvtSaveOptions::ODFSVER_013;
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ nDefVersion = GetODFSaneDefaultVersion();
+ }
+ uno::Reference<beans::XPropertySet> const xProps(GetMedium()->GetStorage(), uno::UNO_QUERY);
+ assert(xProps.is());
+ if (nDefVersion >= SvtSaveOptions::ODFSVER_012) // property exists only since ODF 1.2
+ {
+ try // tdf#134582 set Version on embedded objects as they
+ { // could have been loaded with a different/old version
+ uno::Reference<frame::XModule> const xModule(GetModel(), uno::UNO_QUERY);
+ bool const isBaseForm(xModule.is() &&
+ xModule->getIdentifier() == "com.sun.star.sdb.FormDesign");
+ SAL_INFO_IF(isBaseForm, "sfx.doc", "tdf#138209 force form export to ODF 1.2");
+ if (!isBaseForm && SvtSaveOptions::ODFSVER_013 <= nDefVersion)
+ {
+ xProps->setPropertyValue("Version", uno::Any(ODFVER_013_TEXT));
+ }
+ else
+ {
+ xProps->setPropertyValue("Version", uno::Any(ODFVER_012_TEXT));
+ }
+ }
+ catch (uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "SfxObjectShell::DoSave");
+ }
+ }
+ }
+
+ if ( IsPackageStorageFormat_Impl( *GetMedium() ) )
+ {
+ GetMedium()->GetStorage(); // sets encryption properties if necessary
+ if (GetMedium()->GetErrorCode())
+ {
+ SetError(ERRCODE_IO_GENERAL);
+ }
+ else
+ {
+ bOk = true;
+ }
+#if HAVE_FEATURE_SCRIPTING
+ if ( HasBasic() )
+ {
+ try
+ {
+ // The basic and dialogs related contents are still not able to proceed with save operation ( saveTo only )
+ // so since the document storage is locked a workaround has to be used
+
+ uno::Reference< embed::XStorage > xTmpStorage = ::comphelper::OStorageHelper::GetTemporaryStorage();
+ DBG_ASSERT( xTmpStorage.is(), "If a storage can not be created an exception must be thrown!\n" );
+ if ( !xTmpStorage.is() )
+ throw uno::RuntimeException();
+
+ static constexpr OUString aBasicStorageName( u"Basic"_ustr );
+ static constexpr OUString aDialogsStorageName( u"Dialogs"_ustr );
+ if ( GetMedium()->GetStorage()->hasByName( aBasicStorageName ) )
+ GetMedium()->GetStorage()->copyElementTo( aBasicStorageName, xTmpStorage, aBasicStorageName );
+ if ( GetMedium()->GetStorage()->hasByName( aDialogsStorageName ) )
+ GetMedium()->GetStorage()->copyElementTo( aDialogsStorageName, xTmpStorage, aDialogsStorageName );
+
+ GetBasicManager();
+
+ // disconnect from the current storage
+ pImpl->aBasicManager.setStorage( xTmpStorage );
+
+ // store to the current storage
+ pImpl->aBasicManager.storeLibrariesToStorage( GetMedium()->GetStorage() );
+
+ // connect to the current storage back
+ pImpl->aBasicManager.setStorage( GetMedium()->GetStorage() );
+ }
+ catch( uno::Exception& )
+ {
+ SetError(ERRCODE_IO_GENERAL);
+ bOk = false;
+ }
+ }
+#endif
+ }
+
+ if (bOk)
+ bOk = Save();
+
+ if (bOk)
+ bOk = pMedium->Commit();
+ }
+
+ return bOk;
+}
+
+namespace
+{
+class LockUIGuard
+{
+public:
+ LockUIGuard(SfxObjectShell const* pDoc)
+ : m_pDoc(pDoc)
+ {
+ Lock_Impl();
+ }
+ ~LockUIGuard() { Unlock(); }
+
+ void Unlock()
+ {
+ if (m_bUnlock)
+ Lock_Impl();
+ }
+
+private:
+ void Lock_Impl()
+ {
+ SfxViewFrame* pFrame = SfxViewFrame::GetFirst(m_pDoc);
+ while (pFrame)
+ {
+ pFrame->GetDispatcher()->Lock(!m_bUnlock);
+ pFrame->Enable(m_bUnlock);
+ pFrame = SfxViewFrame::GetNext(*pFrame, m_pDoc);
+ }
+ m_bUnlock = !m_bUnlock;
+ }
+ SfxObjectShell const* m_pDoc;
+ bool m_bUnlock = false;
+};
+}
+
+static OUString lcl_strip_template(const OUString &aString)
+{
+ static constexpr OUString sPostfix(u"_template"_ustr);
+ OUString sRes(aString);
+ if (sRes.endsWith(sPostfix))
+ sRes = sRes.copy(0, sRes.getLength() - sPostfix.getLength());
+ return sRes;
+}
+
+bool SfxObjectShell::SaveTo_Impl
+(
+ SfxMedium &rMedium, // Medium, in which it will be stored
+ const SfxItemSet* pSet
+)
+
+/* [Description]
+
+ Writes the current contents to the medium rMedium. If the target medium is
+ no storage, then saving to a temporary storage, or directly if the medium
+ is transacted, if we ourselves have opened it, and if we are a server
+ either the container a transacted storage provides or created a
+ temporary storage by one self.
+*/
+
+{
+ SAL_INFO( "sfx.doc", "saving \"" << rMedium.GetName() << "\"" );
+
+ UpdateDocInfoForSave();
+
+ ModifyBlocker_Impl aMod(this);
+ // tdf#41063, tdf#135244: prevent jumping to cursor at any temporary modification
+ auto aViewGuard(LockAllViews());
+
+ uno::Reference<uno::XComponentContext> const xContext(
+ ::comphelper::getProcessComponentContext());
+
+ std::shared_ptr<const SfxFilter> pFilter = rMedium.GetFilter();
+ if ( !pFilter )
+ {
+ // if no filter was set, use the default filter
+ // this should be changed in the feature, it should be an error!
+ SAL_WARN( "sfx.doc","No filter set!");
+ pFilter = GetFactory().GetFilterContainer()->GetAnyFilter( SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT );
+ rMedium.SetFilter(pFilter);
+ }
+
+ bool bStorageBasedSource = IsPackageStorageFormat_Impl( *pMedium );
+ bool bStorageBasedTarget = IsPackageStorageFormat_Impl( rMedium );
+ bool bOwnSource = IsOwnStorageFormat( *pMedium );
+ bool bOwnTarget = IsOwnStorageFormat( rMedium );
+
+ // Examine target format to determine whether to query if any password
+ // protected libraries exceed the size we can handler
+ if ( bOwnTarget && !QuerySaveSizeExceededModules_Impl( rMedium.GetInteractionHandler() ) )
+ {
+ SetError(ERRCODE_IO_ABORT);
+ return false;
+ }
+
+ SvtSaveOptions::ODFSaneDefaultVersion nVersion(SvtSaveOptions::ODFSVER_LATEST_EXTENDED);
+ if (bOwnTarget && !utl::ConfigManager::IsFuzzing())
+ {
+ nVersion = GetODFSaneDefaultVersion();
+ }
+
+ bool bNeedsDisconnectionOnFail = false;
+
+ bool bStoreToSameLocation = false;
+
+ // the detection whether the script is changed should be done before saving
+ bool bTryToPreserveScriptSignature = false;
+ // no way to detect whether a filter is oasis format, have to wait for saving process
+ bool bNoPreserveForOasis = false;
+ if ( bOwnSource && bOwnTarget
+ && ( pImpl->nScriptingSignatureState == SignatureState::OK
+ || pImpl->nScriptingSignatureState == SignatureState::NOTVALIDATED
+ || pImpl->nScriptingSignatureState == SignatureState::INVALID ) )
+ {
+ // the checking of the library modified state iterates over the libraries, should be done only when required
+ // currently the check is commented out since it is broken, we have to check the signature every time we save
+ // TODO/LATER: let isAnyContainerModified() work!
+ bTryToPreserveScriptSignature = true; // !pImpl->pBasicManager->isAnyContainerModified();
+ if ( bTryToPreserveScriptSignature )
+ {
+ // check that the storage format stays the same
+
+ OUString aODFVersion;
+ try
+ {
+ uno::Reference < beans::XPropertySet > xPropSet( GetStorage(), uno::UNO_QUERY_THROW );
+ xPropSet->getPropertyValue("Version") >>= aODFVersion;
+ }
+ catch( uno::Exception& )
+ {}
+
+ // preserve only if the same filter has been used
+ // for templates, strip the _template from the filter name for comparison
+ const OUString aMediumFilter = lcl_strip_template(pMedium->GetFilter()->GetFilterName());
+ bTryToPreserveScriptSignature = pMedium->GetFilter() && pFilter && aMediumFilter == lcl_strip_template(pFilter->GetFilterName());
+
+ // signatures were specified in ODF 1.2 but were used since much longer.
+ // LO will still correctly validate an old style signature on an ODF 1.2
+ // document, but technically this is not correct, so this prevents old
+ // signatures to be copied over to a version 1.2 document
+ bNoPreserveForOasis = (
+ (0 <= aODFVersion.compareTo(ODFVER_012_TEXT) && nVersion < SvtSaveOptions::ODFSVER_012) ||
+ (aODFVersion.isEmpty() && nVersion >= SvtSaveOptions::ODFSVER_012)
+ );
+ }
+ }
+
+ uno::Reference<io::XStream> xODFDecryptedInnerPackageStream;
+ uno::Reference<embed::XStorage> xODFDecryptedInnerPackage;
+ uno::Sequence<beans::NamedValue> aEncryptionData;
+ if (GetEncryptionData_Impl(&rMedium.GetItemSet(), aEncryptionData))
+ {
+ assert(aEncryptionData.getLength() != 0);
+ if (bOwnTarget && UseODFWholesomeEncryption(nVersion))
+ {
+ // when embedded objects are stored here, it should be called from
+ // this function for the root document and encryption data was cleared
+ assert(GetCreateMode() != SfxObjectCreateMode::EMBEDDED);
+ // clear now to store inner package (+ embedded objects) unencrypted
+ rMedium.GetItemSet().ClearItem(SID_ENCRYPTIONDATA);
+ rMedium.GetItemSet().ClearItem(SID_PASSWORD);
+ xODFDecryptedInnerPackageStream.set(
+ xContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.comp.MemoryStream", xContext),
+ UNO_QUERY_THROW);
+ xODFDecryptedInnerPackage = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
+ PACKAGE_STORAGE_FORMAT_STRING, xODFDecryptedInnerPackageStream,
+ css::embed::ElementModes::WRITE, xContext, false);
+ assert(xODFDecryptedInnerPackage.is());
+ }
+ }
+
+ bool isStreamAndInputStreamCleared(false);
+ // use UCB for case sensitive/insensitive file name comparison
+ if ( !pMedium->GetName().equalsIgnoreAsciiCase("private:stream")
+ && !rMedium.GetName().equalsIgnoreAsciiCase("private:stream")
+ && ::utl::UCBContentHelper::EqualURLs( pMedium->GetName(), rMedium.GetName() ) )
+ {
+ // Do not unlock the file during saving.
+ // need to modify this for WebDAV if this method is called outside of
+ // the process of saving a file
+ pMedium->DisableUnlockWebDAV();
+ bStoreToSameLocation = true;
+
+ if ( pMedium->DocNeedsFileDateCheck() )
+ {
+ rMedium.CheckFileDate( pMedium->GetInitFileDate( false ) );
+ if (rMedium.GetErrorCode() == ERRCODE_ABORT)
+ {
+ // if user cancels the save, exit early to avoid resetting SfxMedium values that
+ // would cause an invalid subsequent filedate check
+ return false;
+ }
+ }
+
+ // before we overwrite the original file, we will make a backup if there is a demand for that
+ // if the backup is not created here it will be created internally and will be removed in case of successful saving
+ const bool bDoBackup = officecfg::Office::Common::Save::Document::CreateBackup::get() && !comphelper::LibreOfficeKit::isActive();
+ if ( bDoBackup )
+ {
+ rMedium.DoBackup_Impl(/*bForceUsingBackupPath=*/false);
+ if ( rMedium.GetErrorIgnoreWarning() )
+ {
+ SetError(rMedium.GetErrorCode());
+ rMedium.ResetError();
+ }
+ }
+
+ if ( bStorageBasedSource && bStorageBasedTarget )
+ {
+ // The active storage must be switched. The simple saving is not enough.
+ // The problem is that the target medium contains target MediaDescriptor.
+
+ // In future the switch of the persistence could be done on stream level:
+ // a new wrapper service will be implemented that allows to exchange
+ // persistence on the fly. So the real persistence will be set
+ // to that stream only after successful commit of the storage.
+ // TODO/LATER:
+ // create wrapper stream based on the URL
+ // create a new storage based on this stream
+ // store to this new storage
+ // commit the new storage
+ // call saveCompleted based with this new storage ( get rid of old storage and "frees" URL )
+ // commit the wrapper stream ( the stream will connect the URL only on commit, after that it will hold it )
+ // if the last step is failed the stream should stay to be transacted and should be committed on any flush
+ // so we can forget the stream in any way and the next storage commit will flush it
+
+ bNeedsDisconnectionOnFail = DisconnectStorage_Impl(
+ *pMedium, rMedium );
+ if ( bNeedsDisconnectionOnFail
+ || ConnectTmpStorage_Impl( pMedium->GetStorage(), pMedium ) )
+ {
+ pMedium->CloseAndRelease();
+ isStreamAndInputStreamCleared = true;
+
+ // TODO/LATER: for now the medium must be closed since it can already contain streams from old medium
+ // in future those streams should not be copied in case a valid target url is provided,
+ // if the url is not provided ( means the document is based on a stream ) this code is not
+ // reachable.
+ rMedium.CloseAndRelease();
+ rMedium.SetHasEmbeddedObjects(GetEmbeddedObjectContainer().HasEmbeddedObjects());
+ if (xODFDecryptedInnerPackageStream.is())
+ {
+ assert(!rMedium.GetItemSet().GetItem(SID_STREAM));
+ rMedium.SetInnerStorage_Impl(xODFDecryptedInnerPackage);
+ }
+ else
+ {
+ rMedium.GetOutputStorage();
+ }
+ rMedium.SetHasEmbeddedObjects(false);
+ }
+ }
+ else if ( !bStorageBasedSource && !bStorageBasedTarget )
+ {
+ // the source and the target formats are alien
+ // just disconnect the stream from the source format
+ // so that the target medium can use it
+
+ pMedium->CloseAndRelease();
+ rMedium.CloseAndRelease();
+ isStreamAndInputStreamCleared = true;
+ rMedium.CreateTempFileNoCopy();
+ rMedium.GetOutStream();
+ }
+ else if ( !bStorageBasedSource && bStorageBasedTarget )
+ {
+ // the source format is an alien one but the target
+ // format is an own one so just disconnect the source
+ // medium
+
+ pMedium->CloseAndRelease();
+ rMedium.CloseAndRelease();
+ isStreamAndInputStreamCleared = true;
+ if (xODFDecryptedInnerPackageStream.is())
+ {
+ assert(!rMedium.GetItemSet().GetItem(SID_STREAM));
+ rMedium.SetInnerStorage_Impl(xODFDecryptedInnerPackage);
+ }
+ else
+ {
+ rMedium.GetOutputStorage();
+ }
+ }
+ else // means if ( bStorageBasedSource && !bStorageBasedTarget )
+ {
+ // the source format is an own one but the target is
+ // an alien format, just connect the source to temporary
+ // storage
+
+ bNeedsDisconnectionOnFail = DisconnectStorage_Impl(
+ *pMedium, rMedium );
+ if ( bNeedsDisconnectionOnFail
+ || ConnectTmpStorage_Impl( pMedium->GetStorage(), pMedium ) )
+ {
+ pMedium->CloseAndRelease();
+ rMedium.CloseAndRelease();
+ isStreamAndInputStreamCleared = true;
+ rMedium.CreateTempFileNoCopy();
+ rMedium.GetOutStream();
+ }
+ }
+ pMedium->DisableUnlockWebDAV(false);
+ }
+ else
+ {
+ // This is SaveAs or export action, prepare the target medium
+ // the alien filters still might write directly to the file, that is of course a bug,
+ // but for now the framework has to be ready for it
+ // TODO/LATER: let the medium be prepared for alien formats as well
+
+ rMedium.CloseAndRelease();
+ isStreamAndInputStreamCleared = true;
+ if ( bStorageBasedTarget )
+ {
+ rMedium.SetHasEmbeddedObjects(GetEmbeddedObjectContainer().HasEmbeddedObjects());
+ if (xODFDecryptedInnerPackageStream.is())
+ {
+ assert(!rMedium.GetItemSet().GetItem(SID_STREAM));
+ // this should set only xStorage, all of the streams remain null
+ rMedium.SetInnerStorage_Impl(xODFDecryptedInnerPackage);
+ }
+ else
+ {
+ rMedium.GetOutputStorage();
+ }
+ rMedium.SetHasEmbeddedObjects(false);
+ }
+ }
+
+ // TODO/LATER: error handling
+ if( rMedium.GetErrorCode() || pMedium->GetErrorCode() || GetErrorCode() )
+ {
+ SAL_WARN("sfx.doc", "SfxObjectShell::SaveTo_Impl: very early error return");
+ return false;
+ }
+
+ // these have been cleared on all paths that don't take above error return
+ assert(isStreamAndInputStreamCleared); (void) isStreamAndInputStreamCleared;
+
+ rMedium.LockOrigFileOnDemand( false, false );
+
+ if ( bStorageBasedTarget )
+ {
+ if ( rMedium.GetErrorCode() )
+ return false;
+
+ // If the filter is a "cross export" filter ( f.e. a filter for exporting an impress document from
+ // a draw document ), the ClassId of the destination storage is different from the ClassId of this
+ // document. It can be retrieved from the default filter for the desired target format
+ SotClipboardFormatId nFormat = rMedium.GetFilter()->GetFormat();
+ SfxFilterMatcher& rMatcher = SfxGetpApp()->GetFilterMatcher();
+ std::shared_ptr<const SfxFilter> pFilt = rMatcher.GetFilter4ClipBoardId( nFormat );
+ if ( pFilt )
+ {
+ if ( pFilt->GetServiceName() != rMedium.GetFilter()->GetServiceName() )
+ {
+ datatransfer::DataFlavor aDataFlavor;
+ SotExchange::GetFormatDataFlavor( nFormat, aDataFlavor );
+
+ try
+ {
+ uno::Reference< beans::XPropertySet > xProps( rMedium.GetStorage(), uno::UNO_QUERY_THROW );
+ xProps->setPropertyValue("MediaType",
+ uno::Any( aDataFlavor.MimeType ) );
+ }
+ catch( uno::Exception& )
+ {
+ }
+ }
+ }
+ }
+
+ // TODO/LATER: error handling
+ if( rMedium.GetErrorCode() || pMedium->GetErrorCode() || GetErrorCode() )
+ return false;
+
+ bool bOldStat = pImpl->bForbidReload;
+ pImpl->bForbidReload = true;
+
+ // lock user interface while saving the document
+ LockUIGuard aLockUIGuard(this);
+
+ bool bCopyTo = false;
+ SfxItemSet& rMedSet = rMedium.GetItemSet();
+ const SfxBoolItem* pSaveToItem = rMedSet.GetItem(SID_SAVETO, false);
+ bCopyTo = GetCreateMode() == SfxObjectCreateMode::EMBEDDED ||
+ (pSaveToItem && pSaveToItem->GetValue());
+
+ bool bOk = true;
+ // TODO/LATER: get rid of bOk
+ if (bOwnTarget && pFilter && !(pFilter->GetFilterFlags() & SfxFilterFlags::STARONEFILTER))
+ {
+ uno::Reference< embed::XStorage > xMedStorage = rMedium.GetStorage();
+ if (!xMedStorage.is() || rMedium.GetErrorCode())
+ {
+ // no saving without storage
+ pImpl->bForbidReload = bOldStat;
+ return false;
+ }
+
+ // transfer password from the parameters to the storage
+ bool const bPasswdProvided(aEncryptionData.getLength() != 0);
+ pFilter = rMedium.GetFilter();
+
+ const SfxStringItem *pVersionItem = !rMedium.IsInCheckIn()? SfxItemSet::GetItem<SfxStringItem>(pSet, SID_DOCINFO_COMMENTS, false): nullptr;
+ OUString aTmpVersionURL;
+
+ if ( bOk )
+ {
+ bOk = false;
+ // currently the case that the storage is the same should be impossible
+ if ( xMedStorage == GetStorage() )
+ {
+ OSL_ENSURE( !pVersionItem, "This scenario is impossible currently!" );
+ // usual save procedure
+ bOk = Save();
+ }
+ else
+ {
+ // save to target
+ bOk = SaveAsOwnFormat( rMedium );
+ if ( bOk && pVersionItem )
+ {
+ aTmpVersionURL = CreateTempCopyOfStorage_Impl( xMedStorage );
+ bOk = !aTmpVersionURL.isEmpty();
+ }
+ }
+ }
+
+ //fdo#61320: only store thumbnail image if the corresponding option is enabled in the configuration
+ if ( bOk && officecfg::Office::Common::Save::Document::GenerateThumbnail::get()
+ && GetCreateMode() != SfxObjectCreateMode::EMBEDDED && !bPasswdProvided && IsUseThumbnailSave() )
+ {
+ // store the thumbnail representation image
+ // the thumbnail is not stored in case of encrypted document
+ if ( !GenerateAndStoreThumbnail( bPasswdProvided, xMedStorage ) )
+ {
+ // TODO: error handling
+ SAL_WARN( "sfx.doc", "Couldn't store thumbnail representation!" );
+ }
+ }
+
+ if ( bOk )
+ {
+ if ( pImpl->bIsSaving || pImpl->bPreserveVersions )
+ {
+ try
+ {
+ const Sequence < util::RevisionTag > aVersions = rMedium.GetVersionList();
+ if ( aVersions.hasElements() )
+ {
+ // copy the version streams
+ static constexpr OUString aVersionsName( u"Versions"_ustr );
+ uno::Reference< embed::XStorage > xNewVerStor = xMedStorage->openStorageElement(
+ aVersionsName,
+ embed::ElementModes::READWRITE );
+ uno::Reference< embed::XStorage > xOldVerStor = GetStorage()->openStorageElement(
+ aVersionsName,
+ embed::ElementModes::READ );
+ if ( !xNewVerStor.is() || !xOldVerStor.is() )
+ throw uno::RuntimeException();
+
+ for ( const auto& rVersion : aVersions )
+ {
+ if ( xOldVerStor->hasByName( rVersion.Identifier ) )
+ xOldVerStor->copyElementTo( rVersion.Identifier, xNewVerStor, rVersion.Identifier );
+ }
+
+ uno::Reference< embed::XTransactedObject > xTransact( xNewVerStor, uno::UNO_QUERY );
+ if ( xTransact.is() )
+ xTransact->commit();
+ }
+ }
+ catch( uno::Exception& )
+ {
+ SAL_WARN( "sfx.doc", "Couldn't copy versions!" );
+ bOk = false;
+ // TODO/LATER: a specific error could be set
+ }
+ }
+
+ if ( bOk && pVersionItem && !rMedium.IsInCheckIn() )
+ {
+ // store a version also
+ const SfxStringItem *pAuthorItem = SfxItemSet::GetItem<SfxStringItem>(pSet, SID_DOCINFO_AUTHOR, false);
+
+ // version comment
+ util::RevisionTag aInfo;
+ aInfo.Comment = pVersionItem->GetValue();
+
+ // version author
+ if ( pAuthorItem )
+ aInfo.Author = pAuthorItem->GetValue();
+ else
+ // if not transferred as a parameter, get it from user settings
+ aInfo.Author = SvtUserOptions().GetFullName();
+
+ DateTime aTime( DateTime::SYSTEM );
+ aInfo.TimeStamp.Day = aTime.GetDay();
+ aInfo.TimeStamp.Month = aTime.GetMonth();
+ aInfo.TimeStamp.Year = aTime.GetYear();
+ aInfo.TimeStamp.Hours = aTime.GetHour();
+ aInfo.TimeStamp.Minutes = aTime.GetMin();
+ aInfo.TimeStamp.Seconds = aTime.GetSec();
+
+ // add new version information into the versionlist and save the versionlist
+ // the version list must have been transferred from the "old" medium before
+ rMedium.AddVersion_Impl(aInfo);
+ rMedium.SaveVersionList_Impl();
+ bOk = PutURLContentsToVersionStream_Impl(aTmpVersionURL, xMedStorage,
+ aInfo.Identifier);
+ }
+ else if ( bOk && ( pImpl->bIsSaving || pImpl->bPreserveVersions ) )
+ {
+ rMedium.SaveVersionList_Impl();
+ }
+ }
+
+ if ( !aTmpVersionURL.isEmpty() )
+ ::utl::UCBContentHelper::Kill( aTmpVersionURL );
+ }
+ else
+ {
+ // it's a "SaveAs" in an alien format
+ if ( rMedium.GetFilter() && ( rMedium.GetFilter()->GetFilterFlags() & SfxFilterFlags::STARONEFILTER ) )
+ bOk = ExportTo( rMedium );
+ else
+ bOk = ConvertTo( rMedium );
+
+ // after saving the document, the temporary object storage must be updated
+ // if the old object storage was not a temporary one, it will be updated also, because it will be used
+ // as a source for copying the objects into the new temporary storage that will be created below
+ // updating means: all child objects must be stored into it
+ // ( same as on loading, where these objects are copied to the temporary storage )
+ // but don't commit these changes, because in the case when the old object storage is not a temporary one,
+ // all changes will be written into the original file !
+
+ if( bOk && !bCopyTo )
+ // we also don't touch any graphical replacements here
+ SaveChildren( true );
+ }
+
+ if ( bOk )
+ {
+ uno::Any mediaType;
+ if (xODFDecryptedInnerPackageStream.is())
+ { // before the signature copy closes it
+ mediaType = uno::Reference<beans::XPropertySet>(xODFDecryptedInnerPackage,
+ uno::UNO_QUERY_THROW)->getPropertyValue("MediaType");
+ }
+
+ // if ODF version of oasis format changes on saving the signature should not be preserved
+ if ( bTryToPreserveScriptSignature && bNoPreserveForOasis )
+ bTryToPreserveScriptSignature = ( SotStorage::GetVersion( rMedium.GetStorage() ) == SOFFICE_FILEFORMAT_60 );
+
+ uno::Reference< security::XDocumentDigitalSignatures > xDDSigns;
+ if (bTryToPreserveScriptSignature)
+ {
+ // if the scripting code was not changed and it is signed the signature should be preserved
+ // unfortunately at this point we have only information whether the basic code has changed or not
+ // so the only way is to check the signature if the basic was not changed
+ try
+ {
+ // get the ODF version of the new medium
+ OUString aVersion;
+ try
+ {
+ uno::Reference < beans::XPropertySet > xPropSet( rMedium.GetStorage(), uno::UNO_QUERY_THROW );
+ xPropSet->getPropertyValue("Version") >>= aVersion;
+ }
+ catch( uno::Exception& )
+ {
+ }
+
+ xDDSigns = security::DocumentDigitalSignatures::createWithVersion(comphelper::getProcessComponentContext(), aVersion);
+
+ const OUString aScriptSignName = xDDSigns->getScriptingContentSignatureDefaultStreamName();
+
+ if ( !aScriptSignName.isEmpty() )
+ {
+ // target medium is still not committed, it should not be closed
+ // commit the package storage and close it, but leave the streams open
+ rMedium.StorageCommit_Impl();
+ rMedium.CloseStorage();
+
+ // signature must use Zip storage, not Package storage
+ uno::Reference<embed::XStorage> const xReadOrig(
+ pMedium->GetScriptingStorageToSign_Impl());
+ uno::Reference<embed::XStorage> xTarget;
+ if (xODFDecryptedInnerPackageStream.is())
+ {
+ xTarget = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
+ ZIP_STORAGE_FORMAT_STRING, xODFDecryptedInnerPackageStream);
+ }
+ else
+ {
+ xTarget = rMedium.GetZipStorageToSign_Impl(false);
+ }
+
+ if ( !xReadOrig.is() )
+ throw uno::RuntimeException();
+ uno::Reference< embed::XStorage > xMetaInf = xReadOrig->openStorageElement(
+ "META-INF",
+ embed::ElementModes::READ );
+
+ if ( !xTarget.is() )
+ throw uno::RuntimeException();
+ uno::Reference< embed::XStorage > xTargetMetaInf = xTarget->openStorageElement(
+ "META-INF",
+ embed::ElementModes::READWRITE );
+
+ if ( xMetaInf.is() && xTargetMetaInf.is() )
+ {
+ xMetaInf->copyElementTo( aScriptSignName, xTargetMetaInf, aScriptSignName );
+
+ uno::Reference< embed::XTransactedObject > xTransact( xTargetMetaInf, uno::UNO_QUERY );
+ if ( xTransact.is() )
+ xTransact->commit();
+
+ xTargetMetaInf->dispose();
+
+ // now check the copied signature
+ uno::Sequence< security::DocumentSignatureInformation > aInfos =
+ xDDSigns->verifyScriptingContentSignatures( xTarget,
+ uno::Reference< io::XInputStream >() );
+ SignatureState nState = DocumentSignatures::getSignatureState(aInfos);
+ if ( nState == SignatureState::OK || nState == SignatureState::NOTVALIDATED
+ || nState == SignatureState::PARTIAL_OK)
+ {
+ rMedium.SetCachedSignatureState_Impl( nState );
+
+ // commit the ZipStorage from target medium
+ xTransact.set( xTarget, uno::UNO_QUERY );
+ if ( xTransact.is() )
+ xTransact->commit();
+ if (xODFDecryptedInnerPackageStream.is())
+ { // recreate, to have it with copied sig
+ xODFDecryptedInnerPackage = ::comphelper::OStorageHelper::GetStorageOfFormatFromStream(
+ PACKAGE_STORAGE_FORMAT_STRING, xODFDecryptedInnerPackageStream,
+ css::embed::ElementModes::WRITE, xContext, false);
+ }
+ }
+ else
+ {
+ // it should not happen, the copies signature is invalid!
+ // throw the changes away
+ SAL_WARN( "sfx.doc", "An invalid signature was copied!" );
+ }
+ }
+ }
+ }
+ catch( uno::Exception& )
+ {
+ }
+
+ rMedium.CloseZipStorage_Impl();
+ }
+
+ if (xODFDecryptedInnerPackageStream.is())
+ {
+ rMedium.StorageCommit_Impl();
+ // prevent dispose as inner storage will be needed later
+ assert(!rMedium.WillDisposeStorageOnClose_Impl());
+ rMedium.CloseStorage();
+ // restore encryption for outer package, note: disable for debugging
+ rMedium.GetItemSet().Put(SfxUnoAnyItem(SID_ENCRYPTIONDATA, uno::Any(aEncryptionData)));
+ assert(xODFDecryptedInnerPackageStream.is());
+ // now create the outer storage
+ uno::Reference<embed::XStorage> const xOuterStorage(rMedium.GetOutputStorage());
+ assert(xOuterStorage.is());
+ assert(!rMedium.GetErrorCode());
+ // the outer storage needs the same properties as the inner one
+ SetupStorage(xOuterStorage, SOFFICE_FILEFORMAT_CURRENT, false);
+
+ uno::Reference<io::XStream> const xEncryptedInnerPackage =
+ xOuterStorage->openStreamElement(
+ "encrypted-package", embed::ElementModes::WRITE);
+ uno::Reference<beans::XPropertySet> const xEncryptedPackageProps(
+ xEncryptedInnerPackage, uno::UNO_QUERY_THROW);
+ xEncryptedPackageProps->setPropertyValue("MediaType", mediaType);
+
+ // encryption: just copy into package stream
+ uno::Reference<io::XSeekable>(xODFDecryptedInnerPackageStream, uno::UNO_QUERY_THROW)->seek(0);
+ comphelper::OStorageHelper::CopyInputToOutput(
+ xODFDecryptedInnerPackageStream->getInputStream(),
+ xEncryptedInnerPackage->getOutputStream());
+ // rely on Commit() below
+ }
+
+ const OUString sName( rMedium.GetName( ) );
+ bOk = rMedium.Commit();
+ const OUString sNewName( rMedium.GetName( ) );
+
+ if ( sName != sNewName )
+ GetMedium( )->SwitchDocumentToFile( sNewName );
+
+ if (xODFDecryptedInnerPackageStream.is())
+ { // set the inner storage on the medium again, after Switch
+ rMedium.SetInnerStorage_Impl(xODFDecryptedInnerPackage);
+ }
+
+ if ( bOk )
+ {
+ // if the target medium is an alien format and the "old" medium was an own format and the "old" medium
+ // has a name, the object storage must be exchanged, because now we need a new temporary storage
+ // as object storage
+ if ( !bCopyTo && bStorageBasedSource && !bStorageBasedTarget )
+ {
+ if ( bStoreToSameLocation )
+ {
+ // if the old medium already disconnected from document storage, the storage still must
+ // be switched if backup file is used
+ if ( bNeedsDisconnectionOnFail )
+ ConnectTmpStorage_Impl( pImpl->m_xDocStorage, nullptr );
+ }
+ else if (!pMedium->GetName().isEmpty()
+ || ( pMedium->HasStorage_Impl() && pMedium->WillDisposeStorageOnClose_Impl() ) )
+ {
+ OSL_ENSURE(!pMedium->GetName().isEmpty(), "Fallback is used, the medium without name should not dispose the storage!");
+ // copy storage of old medium to new temporary storage and take this over
+ if( !ConnectTmpStorage_Impl( pMedium->GetStorage(), pMedium ) )
+ {
+ SAL_WARN( "sfx.doc", "Process after storing has failed." );
+ bOk = false;
+ }
+ }
+ }
+ }
+ else
+ {
+ SAL_WARN( "sfx.doc", "Storing has failed." );
+
+ // in case the document storage was connected to backup temporarily it must be disconnected now
+ if ( bNeedsDisconnectionOnFail )
+ ConnectTmpStorage_Impl( pImpl->m_xDocStorage, nullptr );
+ }
+ }
+
+ // unlock user interface
+ aLockUIGuard.Unlock();
+ pImpl->bForbidReload = bOldStat;
+
+ if ( bOk )
+ {
+ try
+ {
+ ::ucbhelper::Content aContent( rMedium.GetName(), utl::UCBContentHelper::getDefaultCommandEnvironment(), comphelper::getProcessComponentContext() );
+ css::uno::Reference < XPropertySetInfo > xProps = aContent.getProperties();
+ if ( xProps.is() )
+ {
+ static constexpr OUString aAuthor( u"Author"_ustr );
+ static constexpr OUString aKeywords( u"Keywords"_ustr );
+ static constexpr OUString aSubject( u"Subject"_ustr );
+
+ uno::Reference<document::XDocumentPropertiesSupplier> xDPS(
+ GetModel(), uno::UNO_QUERY_THROW);
+ uno::Reference<document::XDocumentProperties> xDocProps
+ = xDPS->getDocumentProperties();
+
+ if ( xProps->hasPropertyByName( aAuthor ) )
+ {
+ aContent.setPropertyValue( aAuthor, Any(xDocProps->getAuthor()) );
+ }
+ if ( xProps->hasPropertyByName( aKeywords ) )
+ {
+ Any aAny;
+ aAny <<= ::comphelper::string::convertCommaSeparated(
+ xDocProps->getKeywords());
+ aContent.setPropertyValue( aKeywords, aAny );
+ }
+ if ( xProps->hasPropertyByName( aSubject ) )
+ {
+ aContent.setPropertyValue( aSubject, Any(xDocProps->getSubject()) );
+ }
+ }
+ }
+ catch( Exception& )
+ {
+ }
+ }
+
+ return bOk;
+}
+
+bool SfxObjectShell::DisconnectStorage_Impl( SfxMedium& rSrcMedium, SfxMedium& rTargetMedium )
+{
+ // this method disconnects the storage from source medium, and attaches it to the backup created by the target medium
+
+ uno::Reference< embed::XStorage > xStorage = rSrcMedium.GetStorage();
+
+ bool bResult = false;
+ if ( xStorage == pImpl->m_xDocStorage )
+ {
+ try
+ {
+ uno::Reference< embed::XOptimizedStorage > xOptStorage( xStorage, uno::UNO_QUERY_THROW );
+ const OUString aBackupURL = rTargetMedium.GetBackup_Impl();
+ if ( aBackupURL.isEmpty() )
+ {
+ // the backup could not be created, try to disconnect the storage and close the source SfxMedium
+ // in this case the optimization is not possible, connect storage to a temporary file
+ rTargetMedium.ResetError();
+ xOptStorage->writeAndAttachToStream( uno::Reference< io::XStream >() );
+ rSrcMedium.CanDisposeStorage_Impl( false );
+ rSrcMedium.Close();
+
+ // now try to create the backup
+ rTargetMedium.GetBackup_Impl();
+ }
+ else
+ {
+ // the following call will only compare stream sizes
+ // TODO/LATER: this is a very risky part, since if the URL contents are different from the storage
+ // contents, the storage will be broken
+ xOptStorage->attachToURL( aBackupURL, true );
+
+ // the storage is successfully attached to backup, thus it is owned by the document not by the medium
+ rSrcMedium.CanDisposeStorage_Impl( false );
+ bResult = true;
+ }
+ }
+ catch ( uno::Exception& )
+ {}
+ }
+ return bResult;
+}
+
+
+bool SfxObjectShell::ConnectTmpStorage_Impl(
+ const uno::Reference< embed::XStorage >& xStorage,
+ SfxMedium* pMediumArg )
+
+/* [Description]
+
+ If the application operates on a temporary storage, then it may not take
+ the temporary storage from the SaveCompleted. Therefore the new storage
+ is connected already here in this case and SaveCompleted then does nothing.
+*/
+
+{
+ bool bResult = false;
+
+ if ( xStorage.is() )
+ {
+ try
+ {
+ // the empty argument means that the storage will create temporary stream itself
+ uno::Reference< embed::XOptimizedStorage > xOptStorage( xStorage, uno::UNO_QUERY_THROW );
+ xOptStorage->writeAndAttachToStream( uno::Reference< io::XStream >() );
+
+ // the storage is successfully disconnected from the original sources, thus the medium must not dispose it
+ if ( pMediumArg )
+ pMediumArg->CanDisposeStorage_Impl( false );
+
+ bResult = true;
+ }
+ catch( uno::Exception& )
+ {
+ }
+
+ // if switching of the storage does not work for any reason ( nonroot storage for example ) use the old method
+ if ( !bResult ) try
+ {
+ uno::Reference< embed::XStorage > xTmpStorage = ::comphelper::OStorageHelper::GetTemporaryStorage();
+
+ DBG_ASSERT( xTmpStorage.is(), "If a storage can not be created an exception must be thrown!\n" );
+ if ( !xTmpStorage.is() )
+ throw uno::RuntimeException();
+
+ // TODO/LATER: may be it should be done in SwitchPersistence also
+ // TODO/LATER: find faster way to copy storage; perhaps sharing with backup?!
+ xStorage->copyToStorage( xTmpStorage );
+ bResult = SaveCompleted( xTmpStorage );
+
+ if ( bResult )
+ {
+ pImpl->aBasicManager.setStorage( xTmpStorage );
+
+ // Get rid of this workaround after issue i113914 is fixed
+ try
+ {
+ uno::Reference< script::XStorageBasedLibraryContainer > xBasicLibraries( pImpl->xBasicLibraries, uno::UNO_QUERY_THROW );
+ xBasicLibraries->setRootStorage( xTmpStorage );
+ }
+ catch( uno::Exception& )
+ {}
+ try
+ {
+ uno::Reference< script::XStorageBasedLibraryContainer > xDialogLibraries( pImpl->xDialogLibraries, uno::UNO_QUERY_THROW );
+ xDialogLibraries->setRootStorage( xTmpStorage );
+ }
+ catch( uno::Exception& )
+ {}
+ }
+ }
+ catch( uno::Exception& )
+ {}
+
+ if ( !bResult )
+ {
+ // TODO/LATER: may need error code setting based on exception
+ SetError(ERRCODE_IO_GENERAL);
+ }
+ }
+ else if (!GetMedium()->GetFilter()->IsOwnFormat())
+ bResult = true;
+
+ return bResult;
+}
+
+
+bool SfxObjectShell::DoSaveObjectAs( SfxMedium& rMedium, bool bCommit )
+{
+ bool bOk = false;
+
+ ModifyBlocker_Impl aBlock( this );
+
+ uno::Reference < embed::XStorage > xNewStor = rMedium.GetStorage();
+ if ( !xNewStor.is() )
+ return false;
+
+ uno::Reference < beans::XPropertySet > xPropSet( xNewStor, uno::UNO_QUERY );
+ if ( !xPropSet.is() )
+ return false;
+
+ Any a = xPropSet->getPropertyValue("MediaType");
+ OUString aMediaType;
+ if ( !(a>>=aMediaType) || aMediaType.isEmpty() )
+ {
+ SAL_WARN( "sfx.doc", "The mediatype must be set already!" );
+ SetupStorage( xNewStor, SOFFICE_FILEFORMAT_CURRENT, false );
+ }
+
+ pImpl->bIsSaving = false;
+ bOk = SaveAsOwnFormat( rMedium );
+
+ if ( bCommit )
+ {
+ try {
+ uno::Reference< embed::XTransactedObject > xTransact( xNewStor, uno::UNO_QUERY_THROW );
+ xTransact->commit();
+ }
+ catch( uno::Exception& )
+ {
+ SAL_WARN( "sfx.doc", "The storage was not committed on DoSaveAs!" );
+ }
+ }
+
+ return bOk;
+}
+
+
+// TODO/LATER: may be the call must be removed completely
+bool SfxObjectShell::DoSaveAs( SfxMedium& rMedium )
+{
+ // here only root storages are included, which are stored via temp file
+ rMedium.CreateTempFileNoCopy();
+ SetError(rMedium.GetErrorCode());
+ if ( GetErrorIgnoreWarning() )
+ return false;
+
+ // copy version list from "old" medium to target medium, so it can be used on saving
+ if ( pImpl->bPreserveVersions )
+ rMedium.TransferVersionList_Impl( *pMedium );
+
+ bool bRet = SaveTo_Impl( rMedium, nullptr );
+ if ( !bRet )
+ SetError(rMedium.GetErrorCode());
+ return bRet;
+}
+
+
+bool SfxObjectShell::DoSaveCompleted( SfxMedium* pNewMed, bool bRegisterRecent )
+{
+ bool bOk = true;
+ bool bMedChanged = pNewMed && pNewMed!=pMedium;
+
+ DBG_ASSERT( !pNewMed || pNewMed->GetErrorIgnoreWarning() == ERRCODE_NONE, "DoSaveCompleted: Medium has error!" );
+
+ // delete Medium (and Storage!) after all notifications
+ SfxMedium* pOld = pMedium;
+ if ( bMedChanged )
+ {
+ pMedium = pNewMed;
+ pMedium->CanDisposeStorage_Impl( true );
+ }
+
+ std::shared_ptr<const SfxFilter> pFilter = pMedium ? pMedium->GetFilter() : nullptr;
+ if ( pNewMed )
+ {
+ if( bMedChanged )
+ {
+ if (!pNewMed->GetName().isEmpty())
+ bHasName = true;
+ Broadcast( SfxHint(SfxHintId::NameChanged) );
+ EnableSetModified(false);
+ getDocProperties()->setGenerator(
+ ::utl::DocInfoHelper::GetGeneratorString() );
+ EnableSetModified();
+ }
+
+ uno::Reference< embed::XStorage > xStorage;
+ if ( !pFilter || IsPackageStorageFormat_Impl( *pMedium ) )
+ {
+ uno::Reference < embed::XStorage > xOld = GetStorage();
+
+ // when the package based medium is broken and has no storage or if the storage
+ // is the same as the document storage the current document storage should be preserved
+ xStorage = pMedium->GetStorage();
+ bOk = SaveCompleted( xStorage );
+ if ( bOk && xStorage.is() && xOld != xStorage
+ && (!pOld || !pOld->HasStorage_Impl() || xOld != pOld->GetStorage() ) )
+ {
+ // old own storage was not controlled by old Medium -> dispose it
+ try {
+ xOld->dispose();
+ } catch( uno::Exception& )
+ {
+ // the storage is disposed already
+ // can happen during reload scenario when the medium has
+ // disposed it during the closing
+ // will be fixed in one of the next milestones
+ }
+ }
+ }
+ else
+ {
+ if (pImpl->m_bSavingForSigning && pFilter && pFilter->GetSupportsSigning())
+ // So that pMedium->pImpl->xStream becomes a non-empty
+ // reference, and at the end we attempt locking again in
+ // SfxMedium::LockOrigFileOnDemand().
+ pMedium->GetMedium_Impl();
+
+ if( pMedium->GetOpenMode() & StreamMode::WRITE )
+ pMedium->GetInStream();
+ xStorage = GetStorage();
+ }
+
+ // TODO/LATER: may be this code will be replaced, but not sure
+ // Set storage in document library containers
+ pImpl->aBasicManager.setStorage( xStorage );
+
+ // Get rid of this workaround after issue i113914 is fixed
+ try
+ {
+ uno::Reference< script::XStorageBasedLibraryContainer > xBasicLibraries( pImpl->xBasicLibraries, uno::UNO_QUERY_THROW );
+ xBasicLibraries->setRootStorage( xStorage );
+ }
+ catch( uno::Exception& )
+ {}
+ try
+ {
+ uno::Reference< script::XStorageBasedLibraryContainer > xDialogLibraries( pImpl->xDialogLibraries, uno::UNO_QUERY_THROW );
+ xDialogLibraries->setRootStorage( xStorage );
+ }
+ catch( uno::Exception& )
+ {}
+ }
+ else
+ {
+ if( pMedium )
+ {
+ if( pFilter && !IsPackageStorageFormat_Impl( *pMedium ) && (pMedium->GetOpenMode() & StreamMode::WRITE ))
+ {
+ pMedium->ReOpen();
+ bOk = SaveCompletedChildren();
+ }
+ else
+ bOk = SaveCompleted( nullptr );
+ }
+ // either Save or ConvertTo
+ else
+ bOk = SaveCompleted( nullptr );
+ }
+
+ if ( bOk && pNewMed )
+ {
+ if( bMedChanged )
+ {
+ delete pOld;
+
+ uno::Reference< frame::XModel > xModel = GetModel();
+ if ( xModel.is() )
+ {
+ const OUString& aURL {pNewMed->GetOrigURL()};
+ uno::Sequence< beans::PropertyValue > aMediaDescr;
+ TransformItems( SID_OPENDOC, pNewMed->GetItemSet(), aMediaDescr );
+ try
+ {
+ xModel->attachResource( aURL, aMediaDescr );
+ }
+ catch( uno::Exception& )
+ {}
+ }
+
+ const SfxBoolItem* pTemplateItem = pMedium->GetItemSet().GetItem(SID_TEMPLATE, false);
+ bool bTemplate = pTemplateItem && pTemplateItem->GetValue();
+
+ // before the title regenerated the document must lose the signatures
+ pImpl->nDocumentSignatureState = SignatureState::NOSIGNATURES;
+ if (!bTemplate)
+ {
+ pImpl->nScriptingSignatureState = pNewMed->GetCachedSignatureState_Impl();
+ OSL_ENSURE( pImpl->nScriptingSignatureState != SignatureState::BROKEN, "The signature must not be broken at this place" );
+
+ // TODO/LATER: in future the medium must control own signature state, not the document
+ pNewMed->SetCachedSignatureState_Impl( SignatureState::NOSIGNATURES ); // set the default value back
+ }
+ else
+ pNewMed->SetCachedSignatureState_Impl( pImpl->nScriptingSignatureState );
+
+ // Set new title
+ if (!pNewMed->GetName().isEmpty() && SfxObjectCreateMode::EMBEDDED != eCreateMode)
+ InvalidateName();
+ SetModified(false); // reset only by set medium
+ Broadcast( SfxHint(SfxHintId::ModeChanged) );
+
+ // this is the end of the saving process, it is possible that
+ // the file was changed
+ // between medium commit and this step (attributes change and so on)
+ // so get the file date again
+ if ( pNewMed->DocNeedsFileDateCheck() )
+ pNewMed->GetInitFileDate( true );
+ }
+ }
+
+ pMedium->ClearBackup_Impl();
+ pMedium->LockOrigFileOnDemand( true, false );
+
+ if (bRegisterRecent)
+ AddToRecentlyUsedList();
+
+ return bOk;
+}
+
+void SfxObjectShell::AddToRecentlyUsedList()
+{
+ INetURLObject aUrl( pMedium->GetOrigURL() );
+
+ if ( aUrl.GetProtocol() == INetProtocol::File )
+ {
+ std::shared_ptr<const SfxFilter> pOrgFilter = pMedium->GetFilter();
+ Application::AddToRecentDocumentList( aUrl.GetURLNoPass( INetURLObject::DecodeMechanism::NONE ),
+ pOrgFilter ? pOrgFilter->GetMimeType() : OUString(),
+ pOrgFilter ? pOrgFilter->GetServiceName() : OUString() );
+ }
+}
+
+
+bool SfxObjectShell::ConvertFrom
+(
+ SfxMedium& /*rMedium*/ /* <SfxMedium>, which describes the source file
+ (for example file name, <SfxFilter>,
+ Open-Modi and so on) */
+)
+
+/* [Description]
+
+ This method is called for loading of documents over all filters which are
+ not SfxFilterFlags::OWN or for which no clipboard format has been registered
+ (thus no storage format that is used). In other words, with this method
+ it is imported.
+
+ Files which are to be opened here should be opened through 'rMedium'
+ to guarantee the right open modes. Especially if the format is retained
+ (only possible with SfxFilterFlags::SIMULATE or SfxFilterFlags::OWN) file which must
+ be opened STREAM_SHARE_DENYWRITE.
+
+ [Return value]
+
+ bool true
+ The document could be loaded.
+
+ false
+ The document could not be loaded, an error code
+ received through <SvMedium::GetError()const>
+
+ [Example]
+
+ bool DocSh::ConvertFrom( SfxMedium &rMedium )
+ {
+ SvStreamRef xStream = rMedium.GetInStream();
+ if( xStream.is() )
+ {
+ xStream->SetBufferSize(4096);
+ *xStream >> ...;
+
+ // Do not call 'rMedium.CloseInStream()'! Keep File locked!
+ return ERRCODE_NONE == rMedium.GetError();
+ }
+
+ return false;
+ }
+
+ [Cross-references]
+
+ <SfxObjectShell::ConvertTo(SfxMedium&)>
+ <SfxFilterFlags::REGISTRATION>
+*/
+{
+ return false;
+}
+
+bool SfxObjectShell::ImportFrom(SfxMedium& rMedium,
+ css::uno::Reference<css::text::XTextRange> const& xInsertPosition)
+{
+ const OUString aFilterName( rMedium.GetFilter()->GetFilterName() );
+
+ uno::Reference< lang::XMultiServiceFactory > xMan = ::comphelper::getProcessServiceFactory();
+ uno::Reference < lang::XMultiServiceFactory > xFilterFact (
+ xMan->createInstance( "com.sun.star.document.FilterFactory" ), uno::UNO_QUERY );
+
+ uno::Sequence < beans::PropertyValue > aProps;
+ uno::Reference < container::XNameAccess > xFilters ( xFilterFact, uno::UNO_QUERY );
+ if ( xFilters->hasByName( aFilterName ) )
+ {
+ xFilters->getByName( aFilterName ) >>= aProps;
+ rMedium.GetItemSet().Put( SfxStringItem( SID_FILTER_NAME, aFilterName ) );
+ }
+
+ OUString aFilterImplName;
+ auto pProp = std::find_if(std::cbegin(aProps), std::cend(aProps),
+ [](const beans::PropertyValue& rFilterProp) { return rFilterProp.Name == "FilterService"; });
+ if (pProp != std::cend(aProps))
+ pProp->Value >>= aFilterImplName;
+
+ uno::Reference< document::XFilter > xLoader;
+ if ( !aFilterImplName.isEmpty() )
+ {
+ try
+ {
+ xLoader.set( xFilterFact->createInstanceWithArguments( aFilterName, uno::Sequence < uno::Any >() ), uno::UNO_QUERY );
+ }
+ catch(const uno::Exception&)
+ {
+ xLoader.clear();
+ }
+ }
+ if ( xLoader.is() )
+ {
+ // it happens that xLoader does not support xImporter!
+ try
+ {
+ uno::Reference< lang::XComponent > xComp( GetModel(), uno::UNO_QUERY_THROW );
+ uno::Reference< document::XImporter > xImporter( xLoader, uno::UNO_QUERY_THROW );
+ xImporter->setTargetDocument( xComp );
+
+ uno::Sequence < beans::PropertyValue > lDescriptor;
+ rMedium.GetItemSet().Put( SfxStringItem( SID_FILE_NAME, rMedium.GetName() ) );
+ TransformItems( SID_OPENDOC, rMedium.GetItemSet(), lDescriptor );
+
+ css::uno::Sequence < css::beans::PropertyValue > aArgs ( lDescriptor.getLength() );
+ css::beans::PropertyValue * pNewValue = aArgs.getArray();
+ const css::beans::PropertyValue * pOldValue = lDescriptor.getConstArray();
+ static constexpr OUString sInputStream ( u"InputStream"_ustr );
+
+ bool bHasInputStream = false;
+ bool bHasBaseURL = false;
+ sal_Int32 nEnd = lDescriptor.getLength();
+
+ for ( sal_Int32 i = 0; i < nEnd; i++ )
+ {
+ pNewValue[i] = pOldValue[i];
+ if ( pOldValue [i].Name == sInputStream )
+ bHasInputStream = true;
+ else if ( pOldValue[i].Name == "DocumentBaseURL" )
+ bHasBaseURL = true;
+ }
+
+ if ( !bHasInputStream )
+ {
+ aArgs.realloc ( ++nEnd );
+ auto pArgs = aArgs.getArray();
+ pArgs[nEnd-1].Name = sInputStream;
+ pArgs[nEnd-1].Value <<= css::uno::Reference < css::io::XInputStream > ( new utl::OSeekableInputStreamWrapper ( *rMedium.GetInStream() ) );
+ }
+
+ if ( !bHasBaseURL )
+ {
+ aArgs.realloc ( ++nEnd );
+ auto pArgs = aArgs.getArray();
+ pArgs[nEnd-1].Name = "DocumentBaseURL";
+ pArgs[nEnd-1].Value <<= rMedium.GetBaseURL();
+ }
+
+ if (xInsertPosition.is()) {
+ aArgs.realloc( nEnd += 2 );
+ auto pArgs = aArgs.getArray();
+ pArgs[nEnd-2].Name = "InsertMode";
+ pArgs[nEnd-2].Value <<= true;
+ pArgs[nEnd-1].Name = "TextInsertModeRange";
+ pArgs[nEnd-1].Value <<= xInsertPosition;
+ }
+
+ // #i119492# During loading, some OLE objects like chart will be set
+ // modified flag, so needs to reset the flag to false after loading
+ bool bRtn = xLoader->filter(aArgs);
+ const uno::Sequence < OUString > aNames = GetEmbeddedObjectContainer().GetObjectNames();
+ for ( const auto& rName : aNames )
+ {
+ uno::Reference < embed::XEmbeddedObject > xObj = GetEmbeddedObjectContainer().GetEmbeddedObject( rName );
+ OSL_ENSURE( xObj.is(), "An empty entry in the embedded objects list!" );
+ if ( xObj.is() )
+ {
+ sal_Int32 nState = xObj->getCurrentState();
+ if ( nState == embed::EmbedStates::LOADED || nState == embed::EmbedStates::RUNNING ) // means that the object is not active
+ {
+ uno::Reference< util::XModifiable > xModifiable( xObj->getComponent(), uno::UNO_QUERY );
+ if (xModifiable.is() && xModifiable->isModified())
+ {
+ uno::Reference<embed::XEmbedPersist> const xPers(xObj, uno::UNO_QUERY);
+ assert(xPers.is() && "Modified object without persistence!");
+ // store it before resetting modified!
+ xPers->storeOwn();
+ xModifiable->setModified(false);
+ }
+ }
+ }
+ }
+
+ // tdf#107690 import custom document property _MarkAsFinal as SecurityOptOpenReadonly
+ // (before this fix, LibreOffice opened read-only OOXML documents as editable,
+ // also saved and exported _MarkAsFinal=true silently, resulting unintended read-only
+ // warning info bar in MSO)
+ uno::Reference< document::XDocumentPropertiesSupplier > xPropSupplier(GetModel(), uno::UNO_QUERY_THROW);
+ uno::Reference<document::XDocumentProperties> xDocProps = xPropSupplier->getDocumentProperties() ;
+ uno::Reference<beans::XPropertyContainer> xPropertyContainer = xDocProps->getUserDefinedProperties();
+ if (xPropertyContainer.is())
+ {
+ uno::Reference<beans::XPropertySet> xPropertySet(xPropertyContainer, uno::UNO_QUERY);
+ if (xPropertySet.is())
+ {
+ uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
+ if (xPropertySetInfo.is() && xPropertySetInfo->hasPropertyByName("_MarkAsFinal"))
+ {
+ if (xPropertySet->getPropertyValue("_MarkAsFinal").get<bool>())
+ {
+ uno::Reference< lang::XMultiServiceFactory > xFactory(GetModel(), uno::UNO_QUERY);
+ uno::Reference< beans::XPropertySet > xSettings(xFactory->createInstance("com.sun.star.document.Settings"), uno::UNO_QUERY);
+ xSettings->setPropertyValue("LoadReadonly", uno::Any(true));
+ }
+ xPropertyContainer->removeProperty("_MarkAsFinal");
+ }
+ }
+ }
+
+ return bRtn;
+ }
+ catch (const packages::zip::ZipIOException&)
+ {
+ SetError(ERRCODE_IO_BROKENPACKAGE);
+ }
+ catch (const lang::WrappedTargetRuntimeException& rWrapped)
+ {
+ io::WrongFormatException e;
+ if (rWrapped.TargetException >>= e)
+ {
+ SetError(ErrCodeMsg(ERRCODE_SFX_FORMAT_ROWCOL,
+ e.Message, DialogMask::ButtonsOk | DialogMask::MessageError ));
+ }
+ }
+ catch (const css::io::IOException& e)
+ {
+ SetError(ErrCodeMsg(ERRCODE_SFX_FORMAT_ROWCOL,
+ e.Message, DialogMask::ButtonsOk | DialogMask::MessageError ));
+ }
+ catch (const std::exception& e)
+ {
+ const char *msg = e.what();
+ const OUString sError(msg, strlen(msg), RTL_TEXTENCODING_ASCII_US);
+ SAL_WARN("sfx.doc", "exception importing " << sError);
+ SetError(ErrCodeMsg(ERRCODE_SFX_DOLOADFAILED,
+ sError, DialogMask::ButtonsOk | DialogMask::MessageError));
+ }
+ catch (...)
+ {
+ std::abort(); // cannot happen
+ }
+ }
+
+ return false;
+}
+
+bool SfxObjectShell::ExportTo( SfxMedium& rMedium )
+{
+ const OUString aFilterName( rMedium.GetFilter()->GetFilterName() );
+ uno::Reference< document::XExporter > xExporter;
+
+ {
+ uno::Reference< lang::XMultiServiceFactory > xMan = ::comphelper::getProcessServiceFactory();
+ uno::Reference < lang::XMultiServiceFactory > xFilterFact (
+ xMan->createInstance( "com.sun.star.document.FilterFactory" ), uno::UNO_QUERY );
+
+ uno::Sequence < beans::PropertyValue > aProps;
+ uno::Reference < container::XNameAccess > xFilters ( xFilterFact, uno::UNO_QUERY );
+ if ( xFilters->hasByName( aFilterName ) )
+ xFilters->getByName( aFilterName ) >>= aProps;
+
+ OUString aFilterImplName;
+ auto pProp = std::find_if(std::cbegin(aProps), std::cend(aProps),
+ [](const beans::PropertyValue& rFilterProp) { return rFilterProp.Name == "FilterService"; });
+ if (pProp != std::cend(aProps))
+ pProp->Value >>= aFilterImplName;
+
+ if ( !aFilterImplName.isEmpty() )
+ {
+ try
+ {
+ xExporter.set( xFilterFact->createInstanceWithArguments( aFilterName, uno::Sequence < uno::Any >() ), uno::UNO_QUERY );
+ }
+ catch(const uno::Exception&)
+ {
+ xExporter.clear();
+ }
+ }
+ }
+
+ if ( xExporter.is() )
+ {
+ try{
+ uno::Reference< lang::XComponent > xComp( GetModel(), uno::UNO_QUERY_THROW );
+ uno::Reference< document::XFilter > xFilter( xExporter, uno::UNO_QUERY_THROW );
+ xExporter->setSourceDocument( xComp );
+
+ css::uno::Sequence < css::beans::PropertyValue > aOldArgs;
+ SfxItemSet& rItems = rMedium.GetItemSet();
+ TransformItems( SID_SAVEASDOC, rItems, aOldArgs );
+
+ const css::beans::PropertyValue * pOldValue = aOldArgs.getConstArray();
+ css::uno::Sequence < css::beans::PropertyValue > aArgs ( aOldArgs.getLength() );
+ css::beans::PropertyValue * pNewValue = aArgs.getArray();
+
+ // put in the REAL file name, and copy all PropertyValues
+ static constexpr OUString sOutputStream ( u"OutputStream"_ustr );
+ static constexpr OUString sStream ( u"StreamForOutput"_ustr );
+ bool bHasOutputStream = false;
+ bool bHasStream = false;
+ bool bHasBaseURL = false;
+ bool bHasFilterName = false;
+ bool bIsRedactMode = false;
+ bool bIsPreview = false;
+ sal_Int32 nEnd = aOldArgs.getLength();
+
+ for ( sal_Int32 i = 0; i < nEnd; i++ )
+ {
+ pNewValue[i] = pOldValue[i];
+ if ( pOldValue[i].Name == "FileName" )
+ pNewValue[i].Value <<= rMedium.GetName();
+ else if ( pOldValue[i].Name == sOutputStream )
+ bHasOutputStream = true;
+ else if ( pOldValue[i].Name == sStream )
+ bHasStream = true;
+ else if ( pOldValue[i].Name == "DocumentBaseURL" )
+ bHasBaseURL = true;
+ else if( pOldValue[i].Name == "FilterName" )
+ bHasFilterName = true;
+ }
+
+ const css::uno::Sequence<css::beans::PropertyValue>& rMediumArgs = rMedium.GetArgs();
+ for ( sal_Int32 i = 0; i < rMediumArgs.getLength(); i++ )
+ {
+ if( rMediumArgs[i].Name == "IsPreview" )
+ rMediumArgs[i].Value >>= bIsPreview;
+ }
+
+ // FIXME: Handle this inside TransformItems()
+ if (rItems.GetItemState(SID_IS_REDACT_MODE) == SfxItemState::SET)
+ bIsRedactMode = true;
+
+ if ( !bHasOutputStream )
+ {
+ aArgs.realloc ( ++nEnd );
+ auto pArgs = aArgs.getArray();
+ pArgs[nEnd-1].Name = sOutputStream;
+ pArgs[nEnd-1].Value <<= css::uno::Reference < css::io::XOutputStream > ( new utl::OOutputStreamWrapper ( *rMedium.GetOutStream() ) );
+ }
+
+ // add stream as well, for OOX export and maybe others
+ if ( !bHasStream )
+ {
+ aArgs.realloc ( ++nEnd );
+ auto pArgs = aArgs.getArray();
+ pArgs[nEnd-1].Name = sStream;
+ pArgs[nEnd-1].Value <<= css::uno::Reference < css::io::XStream > ( new utl::OStreamWrapper ( *rMedium.GetOutStream() ) );
+ }
+
+ if ( !bHasBaseURL )
+ {
+ aArgs.realloc ( ++nEnd );
+ auto pArgs = aArgs.getArray();
+ pArgs[nEnd-1].Name = "DocumentBaseURL";
+ pArgs[nEnd-1].Value <<= rMedium.GetBaseURL( true );
+ }
+
+ if( !bHasFilterName )
+ {
+ aArgs.realloc( ++nEnd );
+ auto pArgs = aArgs.getArray();
+ pArgs[nEnd-1].Name = "FilterName";
+ pArgs[nEnd-1].Value <<= aFilterName;
+ }
+
+ if (bIsRedactMode)
+ {
+ aArgs.realloc( ++nEnd );
+ auto pArgs = aArgs.getArray();
+ pArgs[nEnd-1].Name = "IsRedactMode";
+ pArgs[nEnd-1].Value <<= bIsRedactMode;
+ }
+
+ if (bIsPreview)
+ {
+ aArgs.realloc( ++nEnd );
+ auto pArgs = aArgs.getArray();
+ pArgs[nEnd-1].Name = "IsPreview";
+ pArgs[nEnd-1].Value <<= bIsPreview;
+ }
+
+ return xFilter->filter( aArgs );
+ }
+ catch (const css::uno::RuntimeException&)
+ {
+ css::uno::Any ex(cppu::getCaughtException());
+ TOOLS_INFO_EXCEPTION("sfx.doc", "exception: " << exceptionToString(ex));
+ }
+ catch (const std::exception& e)
+ {
+ TOOLS_INFO_EXCEPTION("sfx.doc", "exception: " << e.what());
+ }
+ catch(...)
+ {
+ TOOLS_INFO_EXCEPTION("sfx.doc", "Unknown exception!");
+ }
+ }
+
+ return false;
+}
+
+
+bool SfxObjectShell::ConvertTo
+(
+ SfxMedium& /*rMedium*/ /* <SfxMedium>, which describes the target file
+ (for example file name, <SfxFilter>,
+ Open-Modi and so on) */
+)
+
+/* [Description]
+
+ This method is called for saving of documents over all filters which are
+ not SfxFilterFlags::OWN or for which no clipboard format has been registered
+ (thus no storage format that is used). In other words, with this method
+ it is exported.
+
+ Files which are to be opened here should be opened through 'rMedium'
+ to guarantee the right open modes. Especially if the format is retained
+ (only possible with SfxFilterFlags::SIMULATE or SfxFilterFlags::OWN) file which must
+ be opened STREAM_SHARE_DENYWRITE.
+
+ [Return value]
+
+ bool true
+ The document could be saved.
+
+ false
+ The document could not be saved, an error code is
+ received by <SvMedium::GetError()const>
+
+
+ [Example]
+
+ bool DocSh::ConvertTo( SfxMedium &rMedium )
+ {
+ SvStreamRef xStream = rMedium.GetOutStream();
+ if ( xStream.is() )
+ {
+ xStream->SetBufferSize(4096);
+ *xStream << ...;
+
+ rMedium.CloseOutStream(); // opens the InStream automatically
+ return ERRCODE_NONE == rMedium.GetError();
+ }
+ return false ;
+ }
+
+ [Cross-references]
+
+ <SfxObjectShell::ConvertFrom(SfxMedium&)>
+ <SfxFilterFlags::REGISTRATION>
+*/
+
+{
+ return false;
+}
+
+
+bool SfxObjectShell::DoSave_Impl( const SfxItemSet* pArgs )
+{
+ SfxMedium* pRetrMedium = GetMedium();
+ std::shared_ptr<const SfxFilter> pFilter = pRetrMedium->GetFilter();
+
+ // copy the original itemset, but remove the "version" item, because pMediumTmp
+ // is a new medium "from scratch", so no version should be stored into it
+ std::shared_ptr<SfxItemSet> pSet = std::make_shared<SfxAllItemSet>(pRetrMedium->GetItemSet());
+ pSet->ClearItem( SID_VERSION );
+ pSet->ClearItem( SID_DOC_BASEURL );
+
+ // copy the version comment and major items for the checkin only
+ if ( pRetrMedium->IsInCheckIn( ) )
+ {
+ const SfxPoolItem* pMajor = pArgs->GetItem( SID_DOCINFO_MAJOR );
+ if ( pMajor )
+ pSet->Put( *pMajor );
+
+ const SfxPoolItem* pComments = pArgs->GetItem( SID_DOCINFO_COMMENTS );
+ if ( pComments )
+ pSet->Put( *pComments );
+ }
+
+ // create a medium as a copy; this medium is only for writing, because it
+ // uses the same name as the original one writing is done through a copy,
+ // that will be transferred to the target (of course after calling HandsOff)
+ SfxMedium* pMediumTmp = new SfxMedium( pRetrMedium->GetName(), pRetrMedium->GetOpenMode(), pFilter, std::move(pSet) );
+ pMediumTmp->SetInCheckIn( pRetrMedium->IsInCheckIn( ) );
+ pMediumTmp->SetLongName( pRetrMedium->GetLongName() );
+ if ( pMediumTmp->GetErrorCode() != ERRCODE_NONE )
+ {
+ SetError(pMediumTmp->GetErrorIgnoreWarning());
+ delete pMediumTmp;
+ return false;
+ }
+
+ // copy version list from "old" medium to target medium, so it can be used on saving
+ if (pImpl->bPreserveVersions)
+ pMediumTmp->TransferVersionList_Impl( *pRetrMedium );
+
+ // an interaction handler here can acquire only in case of GUI Saving
+ // and should be removed after the saving is done
+ Any aOriginalInteract;
+ css::uno::Reference< XInteractionHandler > xInteract;
+ const SfxUnoAnyItem* pxInteractionItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pArgs, SID_INTERACTIONHANDLER, false);
+ if ( pxInteractionItem && ( pxInteractionItem->GetValue() >>= xInteract ) && xInteract.is() )
+ {
+ if (const SfxUnoAnyItem *pItem = pMediumTmp->GetItemSet().GetItemIfSet(SID_INTERACTIONHANDLER, false))
+ aOriginalInteract = pItem->GetValue();
+ pMediumTmp->GetItemSet().Put( SfxUnoAnyItem( SID_INTERACTIONHANDLER, Any( xInteract ) ) );
+ }
+
+ const SfxBoolItem* pNoFileSync = pArgs->GetItem<SfxBoolItem>(SID_NO_FILE_SYNC, false);
+ if (pNoFileSync && pNoFileSync->GetValue())
+ pMediumTmp->DisableFileSync(true);
+
+ bool bSaved = false;
+ if( !GetErrorIgnoreWarning() && SaveTo_Impl( *pMediumTmp, pArgs ) )
+ {
+ bSaved = true;
+
+ if (aOriginalInteract.hasValue())
+ pMediumTmp->GetItemSet().Put(SfxUnoAnyItem(SID_INTERACTIONHANDLER, aOriginalInteract));
+ else
+ pMediumTmp->GetItemSet().ClearItem(SID_INTERACTIONHANDLER);
+ pMediumTmp->GetItemSet().ClearItem( SID_PROGRESS_STATUSBAR_CONTROL );
+
+ SetError(pMediumTmp->GetErrorCode());
+
+ bool bOpen = DoSaveCompleted( pMediumTmp );
+
+ DBG_ASSERT(bOpen,"Error handling for DoSaveCompleted not implemented");
+ }
+ else
+ {
+ // transfer error code from medium to objectshell
+ ErrCodeMsg errCode = pMediumTmp->GetErrorIgnoreWarning();
+ SetError(errCode);
+
+ if (errCode == ERRCODE_ABORT)
+ {
+ // avoid doing DoSaveCompleted() which updates the SfxMedium timestamp values
+ // and prevents subsequent filedate checks from being accurate
+ delete pMediumTmp;
+ return false;
+ }
+
+ // reconnect to object storage
+ DoSaveCompleted();
+
+ pRetrMedium->GetItemSet().ClearItem( SID_INTERACTIONHANDLER );
+ pRetrMedium->GetItemSet().ClearItem( SID_PROGRESS_STATUSBAR_CONTROL );
+
+ delete pMediumTmp;
+ }
+
+ SetModified( !bSaved );
+ return bSaved;
+}
+
+
+bool SfxObjectShell::Save_Impl( const SfxItemSet* pSet )
+{
+ if ( IsReadOnly() )
+ {
+ SetError(ERRCODE_SFX_DOCUMENTREADONLY);
+ return false;
+ }
+
+ pImpl->bIsSaving = true;
+ bool bSaved = false;
+ const SfxStringItem* pSalvageItem = GetMedium()->GetItemSet().GetItem(SID_DOC_SALVAGE, false);
+ if ( pSalvageItem )
+ {
+ const SfxStringItem* pFilterItem = GetMedium()->GetItemSet().GetItem(SID_FILTER_NAME, false);
+ std::shared_ptr<const SfxFilter> pFilter;
+ if ( pFilterItem )
+ pFilter = SfxFilterMatcher( GetFactory().GetFactoryName() ).GetFilter4FilterName( OUString() );
+
+ SfxMedium *pMed = new SfxMedium(
+ pSalvageItem->GetValue(), StreamMode::READWRITE | StreamMode::SHARE_DENYWRITE | StreamMode::TRUNC, pFilter );
+
+ const SfxStringItem* pPasswordItem = GetMedium()->GetItemSet().GetItem(SID_PASSWORD, false);
+ if ( pPasswordItem )
+ pMed->GetItemSet().Put( *pPasswordItem );
+
+ bSaved = DoSaveAs( *pMed );
+ if ( bSaved )
+ bSaved = DoSaveCompleted( pMed );
+ else
+ delete pMed;
+ }
+ else
+ bSaved = DoSave_Impl( pSet );
+ return bSaved;
+}
+
+bool SfxObjectShell::CommonSaveAs_Impl(const INetURLObject& aURL, const OUString& aFilterName,
+ SfxItemSet& rItemSet,
+ const uno::Sequence<beans::PropertyValue>& rArgs)
+{
+ if( aURL.HasError() )
+ {
+ SetError(ERRCODE_IO_INVALIDPARAMETER);
+ return false;
+ }
+
+ if ( aURL != INetURLObject( u"private:stream" ) )
+ {
+ // Is there already a Document with this name?
+ SfxObjectShell* pDoc = nullptr;
+ for ( SfxObjectShell* pTmp = SfxObjectShell::GetFirst();
+ pTmp && !pDoc;
+ pTmp = SfxObjectShell::GetNext(*pTmp) )
+ {
+ if( ( pTmp != this ) && pTmp->GetMedium() )
+ {
+ INetURLObject aCompare( pTmp->GetMedium()->GetName() );
+ if ( aCompare == aURL )
+ pDoc = pTmp;
+ }
+ }
+ if ( pDoc )
+ {
+ // Then error message: "already opened"
+ SetError(ERRCODE_SFX_ALREADYOPEN);
+ return false;
+ }
+ }
+
+ DBG_ASSERT( aURL.GetProtocol() != INetProtocol::NotValid, "Illegal URL!" );
+ DBG_ASSERT( rItemSet.Count() != 0, "Incorrect Parameter");
+
+ const SfxBoolItem* pSaveToItem = rItemSet.GetItem<SfxBoolItem>(SID_SAVETO, false);
+ bool bSaveTo = pSaveToItem && pSaveToItem->GetValue();
+
+ std::shared_ptr<const SfxFilter> pFilter = GetFactory().GetFilterContainer()->GetFilter4FilterName( aFilterName );
+ if ( !pFilter
+ || !pFilter->CanExport()
+ || (!bSaveTo && !pFilter->CanImport()) )
+ {
+ SetError(ERRCODE_IO_INVALIDPARAMETER);
+ return false;
+ }
+
+
+ const SfxBoolItem* pCopyStreamItem = rItemSet.GetItem(SID_COPY_STREAM_IF_POSSIBLE, false);
+ if ( bSaveTo && pCopyStreamItem && pCopyStreamItem->GetValue() && !IsModified() )
+ {
+ if (pMedium->TryDirectTransfer(aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE), rItemSet))
+ return true;
+ }
+ rItemSet.ClearItem( SID_COPY_STREAM_IF_POSSIBLE );
+
+ SfxMedium *pActMed = GetMedium();
+ const INetURLObject aActName(pActMed->GetName());
+
+ bool bWasReadonly = IsReadOnly();
+
+ if ( aURL == aActName && aURL != INetURLObject( u"private:stream" )
+ && IsReadOnly() )
+ {
+ SetError(ERRCODE_SFX_DOCUMENTREADONLY);
+ return false;
+ }
+
+ if (SfxItemState::SET != rItemSet.GetItemState(SID_UNPACK) && officecfg::Office::Common::Save::Document::Unpacked::get())
+ rItemSet.Put(SfxBoolItem(SID_UNPACK, false));
+
+#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+ OUString aTempFileURL;
+ if ( IsDocShared() )
+ aTempFileURL = pMedium->GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE );
+#endif
+
+ if (PreDoSaveAs_Impl(aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE), aFilterName,
+ rItemSet, rArgs))
+ {
+ // Update Data on media
+ SfxItemSet& rSet = GetMedium()->GetItemSet();
+ rSet.ClearItem( SID_INTERACTIONHANDLER );
+ rSet.ClearItem( SID_PROGRESS_STATUSBAR_CONTROL );
+ rSet.ClearItem( SID_STANDARD_DIR );
+ rSet.ClearItem( SID_PATH );
+
+ if ( !bSaveTo )
+ {
+ rSet.ClearItem( SID_REFERER );
+ rSet.ClearItem( SID_POSTDATA );
+ rSet.ClearItem( SID_TEMPLATE );
+ rSet.ClearItem( SID_DOC_READONLY );
+ rSet.ClearItem( SID_CONTENTTYPE );
+ rSet.ClearItem( SID_CHARSET );
+ rSet.ClearItem( SID_FILTER_NAME );
+ rSet.ClearItem( SID_OPTIONS );
+ rSet.ClearItem( SID_VERSION );
+ rSet.ClearItem( SID_EDITDOC );
+ rSet.ClearItem( SID_OVERWRITE );
+ rSet.ClearItem( SID_DEFAULTFILEPATH );
+ rSet.ClearItem( SID_DEFAULTFILENAME );
+
+ const SfxStringItem* pFilterItem = rItemSet.GetItem<SfxStringItem>(SID_FILTER_NAME, false);
+ if ( pFilterItem )
+ rSet.Put( *pFilterItem );
+
+ const SfxStringItem* pOptionsItem = rItemSet.GetItem<SfxStringItem>(SID_OPTIONS, false);
+ if ( pOptionsItem )
+ rSet.Put( *pOptionsItem );
+
+ const SfxStringItem* pFilterOptItem = rItemSet.GetItem<SfxStringItem>(SID_FILE_FILTEROPTIONS, false);
+ if ( pFilterOptItem )
+ rSet.Put( *pFilterOptItem );
+
+#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+ if ( IsDocShared() && !aTempFileURL.isEmpty() )
+ {
+ // this is a shared document that has to be disconnected from the old location
+ FreeSharedFile( aTempFileURL );
+
+ if ( pFilter->IsOwnFormat()
+ && pFilter->UsesStorage()
+ && pFilter->GetVersion() >= SOFFICE_FILEFORMAT_60 )
+ {
+ // the target format is the own format
+ // the target document must be shared
+ SwitchToShared( true, false );
+ }
+ }
+#endif
+ }
+
+ if ( bWasReadonly && !bSaveTo )
+ Broadcast( SfxHint(SfxHintId::ModeChanged) );
+
+ return true;
+ }
+ else
+ return false;
+}
+
+bool SfxObjectShell::PreDoSaveAs_Impl(const OUString& rFileName, const OUString& aFilterName,
+ SfxItemSet const& rItemSet,
+ const uno::Sequence<beans::PropertyValue>& rArgs)
+{
+ // copy all items stored in the itemset of the current medium
+ std::shared_ptr<SfxAllItemSet> xMergedParams = std::make_shared<SfxAllItemSet>( pMedium->GetItemSet() );
+
+ // in "SaveAs" title and password will be cleared ( maybe the new itemset contains new values, otherwise they will be empty )
+ // #i119366# - As the SID_ENCRYPTIONDATA and SID_PASSWORD are using for setting password together, we need to clear them both.
+ // Also, ( maybe the new itemset contains new values, otherwise they will be empty )
+ if (xMergedParams->HasItem(SID_ENCRYPTIONDATA))
+ {
+ bool bPasswordProtected = true;
+ const SfxUnoAnyItem* pEncryptionDataItem
+ = xMergedParams->GetItem<SfxUnoAnyItem>(SID_ENCRYPTIONDATA, false);
+ if (pEncryptionDataItem)
+ {
+ uno::Sequence<beans::NamedValue> aEncryptionData;
+ pEncryptionDataItem->GetValue() >>= aEncryptionData;
+ for (const auto& rItem : std::as_const(aEncryptionData))
+ {
+ if (rItem.Name == "CryptoType")
+ {
+ OUString aValue;
+ rItem.Value >>= aValue;
+ if (aValue != "StrongEncryptionDataSpace")
+ {
+ // This is not just a password protected document. Let's keep encryption data as is.
+ bPasswordProtected = false;
+ }
+ break;
+ }
+ }
+ }
+ if (bPasswordProtected)
+ {
+ // For password protected documents remove encryption data during "Save as..."
+ xMergedParams->ClearItem(SID_PASSWORD);
+ xMergedParams->ClearItem(SID_ENCRYPTIONDATA);
+ }
+ }
+
+ xMergedParams->ClearItem( SID_DOCINFO_TITLE );
+
+ xMergedParams->ClearItem( SID_INPUTSTREAM );
+ xMergedParams->ClearItem( SID_STREAM );
+ xMergedParams->ClearItem( SID_CONTENT );
+ xMergedParams->ClearItem( SID_DOC_READONLY );
+ xMergedParams->ClearItem( SID_DOC_BASEURL );
+
+ xMergedParams->ClearItem( SID_REPAIRPACKAGE );
+
+ // "SaveAs" will never store any version information - it's a complete new file !
+ xMergedParams->ClearItem( SID_VERSION );
+
+ // merge the new parameters into the copy
+ // all values present in both itemsets will be overwritten by the new parameters
+ xMergedParams->Put(rItemSet);
+
+ SAL_WARN_IF( xMergedParams->GetItemState( SID_DOC_SALVAGE) >= SfxItemState::SET,
+ "sfx.doc","Salvage item present in Itemset, check the parameters!");
+
+ // should be unnecessary - too hot to handle!
+ xMergedParams->ClearItem( SID_DOC_SALVAGE );
+
+ // create a medium for the target URL
+ SfxMedium *pNewFile = new SfxMedium( rFileName, StreamMode::READWRITE | StreamMode::SHARE_DENYWRITE | StreamMode::TRUNC, nullptr, xMergedParams );
+ pNewFile->SetArgs(rArgs);
+
+ const SfxBoolItem* pNoFileSync = xMergedParams->GetItem<SfxBoolItem>(SID_NO_FILE_SYNC, false);
+ if (pNoFileSync && pNoFileSync->GetValue())
+ pNewFile->DisableFileSync(true);
+
+ bool bUseThumbnailSave = IsUseThumbnailSave();
+ comphelper::ScopeGuard aThumbnailGuard(
+ [this, bUseThumbnailSave] { this->SetUseThumbnailSave(bUseThumbnailSave); });
+ const SfxBoolItem* pNoThumbnail = xMergedParams->GetItem<SfxBoolItem>(SID_NO_THUMBNAIL, false);
+ if (pNoThumbnail)
+ // Thumbnail generation should be avoided just for this save.
+ SetUseThumbnailSave(!pNoThumbnail->GetValue());
+ else
+ aThumbnailGuard.dismiss();
+
+ // set filter; if no filter is given, take the default filter of the factory
+ if ( !aFilterName.isEmpty() )
+ {
+ pNewFile->SetFilter( GetFactory().GetFilterContainer()->GetFilter4FilterName( aFilterName ) );
+
+ if (aFilterName == "writer_pdf_Export")
+ {
+ uno::Sequence< beans::PropertyValue > aSaveToFilterDataOptions(2);
+ auto pSaveToFilterDataOptions = aSaveToFilterDataOptions.getArray();
+ bool bRet = false;
+
+ for(int i = 0 ; i< rArgs.getLength() ; ++i)
+ {
+ auto aProp = rArgs[i];
+ if(aProp.Name == "EncryptFile")
+ {
+ pSaveToFilterDataOptions[0].Name = aProp.Name;
+ pSaveToFilterDataOptions[0].Value = aProp.Value;
+ bRet = true;
+ }
+ if(aProp.Name == "DocumentOpenPassword")
+ {
+ pSaveToFilterDataOptions[1].Name = aProp.Name;
+ pSaveToFilterDataOptions[1].Value = aProp.Value;
+ bRet = true;
+ }
+ }
+
+ if( bRet )
+ pNewFile->GetItemSet().Put( SfxUnoAnyItem(SID_FILTER_DATA, uno::Any(aSaveToFilterDataOptions)));
+ }
+ }
+ else
+ pNewFile->SetFilter( GetFactory().GetFilterContainer()->GetAnyFilter( SfxFilterFlags::IMPORT | SfxFilterFlags::EXPORT ) );
+
+ if ( pNewFile->GetErrorCode() != ERRCODE_NONE )
+ {
+ // creating temporary file failed ( f.e. floppy disk not inserted! )
+ SetError(pNewFile->GetErrorIgnoreWarning());
+ delete pNewFile;
+ return false;
+ }
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // Before saving, commit in-flight changes.
+ TerminateEditing();
+ }
+
+ // check if a "SaveTo" is wanted, no "SaveAs"
+ const SfxBoolItem* pSaveToItem = xMergedParams->GetItem<SfxBoolItem>(SID_SAVETO, false);
+ bool bCopyTo = GetCreateMode() == SfxObjectCreateMode::EMBEDDED || (pSaveToItem && pSaveToItem->GetValue());
+
+ // distinguish between "Save" and "SaveAs"
+ pImpl->bIsSaving = false;
+
+ // copy version list from "old" medium to target medium, so it can be used on saving
+ if ( pImpl->bPreserveVersions )
+ pNewFile->TransferVersionList_Impl( *pMedium );
+
+ // Save the document ( first as temporary file, then transfer to the target URL by committing the medium )
+ bool bOk = false;
+ if ( !pNewFile->GetErrorCode() && SaveTo_Impl( *pNewFile, nullptr ) )
+ {
+ // transfer a possible error from the medium to the document
+ SetError(pNewFile->GetErrorCode());
+
+ // notify the document that saving was done successfully
+ if ( !bCopyTo )
+ {
+ bOk = DoSaveCompleted( pNewFile );
+ }
+ else
+ bOk = DoSaveCompleted();
+
+ if( bOk )
+ {
+ if( !bCopyTo )
+ SetModified( false );
+ }
+ else
+ {
+ // TODO/LATER: the code below must be dead since the storage commit makes all the stuff
+ // and the DoSaveCompleted call should not be able to fail in general
+
+ DBG_ASSERT( !bCopyTo, "Error while reconnecting to medium, can't be handled!");
+ SetError(pNewFile->GetErrorCode());
+
+ if ( !bCopyTo )
+ {
+ // reconnect to the old medium
+ bool bRet = DoSaveCompleted( pMedium );
+ DBG_ASSERT( bRet, "Error in DoSaveCompleted, can't be handled!");
+ }
+
+ // TODO/LATER: disconnect the new file from the storage for the case when pure saving is done
+ // if storing has corrupted the file, probably it must be restored either here or
+ // by the storage
+ delete pNewFile;
+ pNewFile = nullptr;
+ }
+ }
+ else
+ {
+ SetError(pNewFile->GetErrorCode());
+
+ // reconnect to the old storage
+ DoSaveCompleted();
+
+ delete pNewFile;
+ pNewFile = nullptr;
+ }
+
+ if ( bCopyTo )
+ delete pNewFile;
+ else if( !bOk )
+ SetModified();
+
+ return bOk;
+}
+
+
+bool SfxObjectShell::LoadFrom( SfxMedium& /*rMedium*/ )
+{
+ SAL_WARN( "sfx.doc", "Base implementation, must not be called in general!" );
+ return true;
+}
+
+
+bool SfxObjectShell::CanReload_Impl()
+
+/* [Description]
+
+ Internal method for determining whether a reload of the document
+ (as RevertToSaved or last known version) is possible.
+*/
+
+{
+ return pMedium && HasName() && !IsInModalMode() && !pImpl->bForbidReload;
+}
+
+
+HiddenInformation SfxObjectShell::GetHiddenInformationState( HiddenInformation nStates )
+{
+ HiddenInformation nState = HiddenInformation::NONE;
+ if ( nStates & HiddenInformation::DOCUMENTVERSIONS )
+ {
+ if ( GetMedium()->GetVersionList().hasElements() )
+ nState |= HiddenInformation::DOCUMENTVERSIONS;
+ }
+
+ return nState;
+}
+
+void SfxObjectShell::QueryHiddenInformation(HiddenWarningFact eFact)
+{
+ SvtSecurityOptions::EOption eOption = SvtSecurityOptions::EOption();
+
+ switch ( eFact )
+ {
+ case HiddenWarningFact::WhenSaving :
+ {
+ eOption = SvtSecurityOptions::EOption::DocWarnSaveOrSend;
+ break;
+ }
+ case HiddenWarningFact::WhenPrinting :
+ {
+ eOption = SvtSecurityOptions::EOption::DocWarnPrint;
+ break;
+ }
+ case HiddenWarningFact::WhenSigning :
+ {
+ eOption = SvtSecurityOptions::EOption::DocWarnSigning;
+ break;
+ }
+ case HiddenWarningFact::WhenCreatingPDF :
+ {
+ eOption = SvtSecurityOptions::EOption::DocWarnCreatePdf;
+ break;
+ }
+ default:
+ assert(false); // this cannot happen
+ }
+
+ if ( SvtSecurityOptions::IsOptionSet( eOption ) )
+ {
+ OUString sMessage;
+ HiddenInformation nWantedStates = HiddenInformation::RECORDEDCHANGES | HiddenInformation::NOTES;
+ if ( eFact != HiddenWarningFact::WhenPrinting )
+ nWantedStates |= HiddenInformation::DOCUMENTVERSIONS;
+ HiddenInformation nStates = GetHiddenInformationState( nWantedStates );
+
+ if ( nStates & HiddenInformation::RECORDEDCHANGES )
+ {
+ sMessage += SfxResId(STR_HIDDENINFO_RECORDCHANGES) + "\n";
+ }
+ if ( nStates & HiddenInformation::NOTES )
+ {
+ sMessage += SfxResId(STR_HIDDENINFO_NOTES) + "\n";
+ }
+ if ( nStates & HiddenInformation::DOCUMENTVERSIONS )
+ {
+ sMessage += SfxResId(STR_HIDDENINFO_DOCVERSIONS) + "\n";
+ }
+
+ SfxViewFrame* pFrame = SfxViewFrame::GetFirst(this);
+ if (pFrame)
+ pFrame->HandleSecurityInfobar(!sMessage.isEmpty() ? sMessage.trim().replaceAll("\n", ", ") : sMessage);
+
+ }
+}
+
+bool SfxObjectShell::IsSecurityOptOpenReadOnly() const
+{
+ return IsLoadReadonly();
+}
+
+void SfxObjectShell::SetSecurityOptOpenReadOnly( bool _b )
+{
+ SetLoadReadonly( _b );
+}
+
+bool SfxObjectShell::LoadOwnFormat( SfxMedium& rMedium )
+{
+ SAL_INFO( "sfx.doc", "loading \" " << rMedium.GetName() << "\"" );
+
+ uno::Reference< embed::XStorage > xStorage = rMedium.GetStorage();
+ if ( xStorage.is() )
+ {
+ // Password
+ const SfxStringItem* pPasswdItem = rMedium.GetItemSet().GetItem(SID_PASSWORD, false);
+ if ( pPasswdItem || ERRCODE_IO_ABORT != CheckPasswd_Impl( this, pMedium ) )
+ {
+ // note: this could be needed in case no interaction handler is
+ // provided (which CheckPasswd_Impl needs) but a password item is,
+ // but it could be done in a better way
+ uno::Sequence< beans::NamedValue > aEncryptionData;
+ if ( GetEncryptionData_Impl(&pMedium->GetItemSet(), aEncryptionData) )
+ {
+ try
+ {
+ // the following code must throw an exception in case of failure
+ ::comphelper::OStorageHelper::SetCommonStorageEncryptionData( xStorage, aEncryptionData );
+ }
+ catch( uno::Exception& )
+ {
+ // TODO/LATER: handle the error code
+ }
+ }
+
+ // load document
+ return Load( rMedium );
+ }
+ return false;
+ }
+ else
+ return false;
+}
+
+bool SfxObjectShell::SaveAsOwnFormat( SfxMedium& rMedium )
+{
+ uno::Reference< embed::XStorage > xStorage = rMedium.GetStorage();
+ if( xStorage.is() )
+ {
+ sal_Int32 nVersion = rMedium.GetFilter()->GetVersion();
+
+ // OASIS templates have own mediatypes (SO7 also actually, but it is too late to use them here)
+ const bool bTemplate = rMedium.GetFilter()->IsOwnTemplateFormat()
+ && nVersion > SOFFICE_FILEFORMAT_60;
+
+ SetupStorage( xStorage, nVersion, bTemplate );
+#if HAVE_FEATURE_SCRIPTING
+ if ( HasBasic() )
+ {
+ // Initialize Basic
+ GetBasicManager();
+
+ // Save dialog/script container
+ pImpl->aBasicManager.storeLibrariesToStorage( xStorage );
+ }
+#endif
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // Because XMLTextFieldExport::ExportFieldDeclarations (called from SwXMLExport)
+ // calls SwXTextFieldMasters::getByName, which in turn maps property names by
+ // calling SwStyleNameMapper::GetTextUINameArray, which uses
+ // SvtSysLocale().GetUILanguageTag() to do the mapping, saving indirectly depends
+ // on the UI language. This is an unfortunate dependency. Here we use the loader's language.
+ const LanguageTag viewLanguage = comphelper::LibreOfficeKit::getLanguageTag();
+ const LanguageTag loadLanguage = SfxLokHelper::getLoadLanguage();
+
+ // Use the default language for saving and restore later if necessary.
+ bool restoreLanguage = false;
+ if (viewLanguage != loadLanguage)
+ {
+ restoreLanguage = true;
+ comphelper::LibreOfficeKit::setLanguageTag(loadLanguage);
+ }
+
+ // Restore the view's original language automatically and as necessary.
+ const ::comphelper::ScopeGuard aGuard(
+ [&viewLanguage, restoreLanguage]()
+ {
+ if (restoreLanguage
+ && viewLanguage != comphelper::LibreOfficeKit::getLanguageTag())
+ comphelper::LibreOfficeKit::setLanguageTag(viewLanguage);
+ });
+
+ return SaveAs(rMedium);
+ }
+
+ return SaveAs( rMedium );
+ }
+ else return false;
+}
+
+uno::Reference< embed::XStorage > const & SfxObjectShell::GetStorage()
+{
+ if ( !pImpl->m_xDocStorage.is() )
+ {
+ OSL_ENSURE( pImpl->m_bCreateTempStor, "The storage must exist already!" );
+ try {
+ // no notification is required the storage is set the first time
+ pImpl->m_xDocStorage = ::comphelper::OStorageHelper::GetTemporaryStorage();
+ OSL_ENSURE( pImpl->m_xDocStorage.is(), "The method must either return storage or throw exception!" );
+
+ SetupStorage( pImpl->m_xDocStorage, SOFFICE_FILEFORMAT_CURRENT, false );
+ pImpl->m_bCreateTempStor = false;
+ if (!utl::ConfigManager::IsFuzzing())
+ SfxGetpApp()->NotifyEvent( SfxEventHint( SfxEventHintId::StorageChanged, GlobalEventConfig::GetEventName(GlobalEventId::STORAGECHANGED), this ) );
+ }
+ catch( uno::Exception& )
+ {
+ // TODO/LATER: error handling?
+ TOOLS_WARN_EXCEPTION("sfx.doc", "SfxObjectShell::GetStorage");
+ }
+ }
+
+ OSL_ENSURE( pImpl->m_xDocStorage.is(), "The document storage must be created!" );
+ return pImpl->m_xDocStorage;
+}
+
+
+void SfxObjectShell::SaveChildren( bool bObjectsOnly )
+{
+ if ( pImpl->mxObjectContainer )
+ {
+ bool bOasis = ( SotStorage::GetVersion( GetStorage() ) > SOFFICE_FILEFORMAT_60 );
+ GetEmbeddedObjectContainer().StoreChildren(bOasis,bObjectsOnly);
+ }
+}
+
+bool SfxObjectShell::SaveAsChildren( SfxMedium& rMedium )
+{
+ uno::Reference < embed::XStorage > xStorage = rMedium.GetStorage();
+ if ( !xStorage.is() )
+ return false;
+
+ if ( xStorage == GetStorage() )
+ {
+ SaveChildren();
+ return true;
+ }
+
+ bool AutoSaveEvent = false;
+ utl::MediaDescriptor lArgs(rMedium.GetArgs());
+ lArgs[utl::MediaDescriptor::PROP_AUTOSAVEEVENT] >>= AutoSaveEvent;
+
+ if ( pImpl->mxObjectContainer )
+ {
+ bool bOasis = ( SotStorage::GetVersion( xStorage ) > SOFFICE_FILEFORMAT_60 );
+ GetEmbeddedObjectContainer().StoreAsChildren(bOasis,SfxObjectCreateMode::EMBEDDED == eCreateMode,AutoSaveEvent,xStorage);
+ }
+
+ uno::Sequence<OUString> aExceptions;
+ if (const SfxBoolItem* pNoEmbDS = rMedium.GetItemSet().GetItem(SID_NO_EMBEDDED_DS, false))
+ {
+ // Don't save data source in case a temporary is being saved for preview in MM wizard
+ if (pNoEmbDS->GetValue())
+ aExceptions = uno::Sequence<OUString>{ "EmbeddedDatabase" };
+ }
+
+ return CopyStoragesOfUnknownMediaType(GetStorage(), xStorage, aExceptions);
+}
+
+bool SfxObjectShell::SaveCompletedChildren()
+{
+ bool bResult = true;
+
+ if ( pImpl->mxObjectContainer )
+ {
+ const uno::Sequence < OUString > aNames = GetEmbeddedObjectContainer().GetObjectNames();
+ for ( const auto& rName : aNames )
+ {
+ uno::Reference < embed::XEmbeddedObject > xObj = GetEmbeddedObjectContainer().GetEmbeddedObject( rName );
+ OSL_ENSURE( xObj.is(), "An empty entry in the embedded objects list!" );
+ if ( xObj.is() )
+ {
+ uno::Reference< embed::XEmbedPersist > xPersist( xObj, uno::UNO_QUERY );
+ if ( xPersist.is() )
+ {
+ try
+ {
+ xPersist->saveCompleted( false/*bSuccess*/ );
+ }
+ catch( uno::Exception& )
+ {
+ // TODO/LATER: error handling
+ bResult = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return bResult;
+}
+
+bool SfxObjectShell::SwitchChildrenPersistence( const uno::Reference< embed::XStorage >& xStorage,
+ bool bForceNonModified )
+{
+ if ( !xStorage.is() )
+ {
+ // TODO/LATER: error handling
+ return false;
+ }
+
+ if ( pImpl->mxObjectContainer )
+ pImpl->mxObjectContainer->SetPersistentEntries(xStorage,bForceNonModified);
+
+ return true;
+}
+
+// Never call this method directly, always use the DoSaveCompleted call
+bool SfxObjectShell::SaveCompleted( const uno::Reference< embed::XStorage >& xStorage )
+{
+ bool bResult = false;
+ bool bSendNotification = false;
+ uno::Reference< embed::XStorage > xOldStorageHolder;
+
+ // check for wrong creation of object container
+ bool bHasContainer( pImpl->mxObjectContainer );
+
+ if ( !xStorage.is() || xStorage == GetStorage() )
+ {
+ // no persistence change
+ bResult = SaveCompletedChildren();
+ }
+ else
+ {
+ if ( pImpl->mxObjectContainer )
+ GetEmbeddedObjectContainer().SwitchPersistence( xStorage );
+
+ bResult = SwitchChildrenPersistence( xStorage, true );
+ }
+
+ if ( bResult )
+ {
+ if ( xStorage.is() && pImpl->m_xDocStorage != xStorage )
+ {
+ // make sure that until the storage is assigned the object
+ // container is not created by accident!
+ DBG_ASSERT( bHasContainer == (pImpl->mxObjectContainer != nullptr), "Wrong storage in object container!" );
+ xOldStorageHolder = pImpl->m_xDocStorage;
+ pImpl->m_xDocStorage = xStorage;
+ bSendNotification = true;
+
+ if ( IsEnableSetModified() )
+ SetModified( false );
+ }
+ }
+ else
+ {
+ if ( pImpl->mxObjectContainer )
+ GetEmbeddedObjectContainer().SwitchPersistence( pImpl->m_xDocStorage );
+
+ // let already successfully connected objects be switched back
+ SwitchChildrenPersistence( pImpl->m_xDocStorage, true );
+ }
+
+ if ( bSendNotification )
+ {
+ SfxGetpApp()->NotifyEvent( SfxEventHint( SfxEventHintId::StorageChanged, GlobalEventConfig::GetEventName(GlobalEventId::STORAGECHANGED), this ) );
+ }
+
+ return bResult;
+}
+
+static bool StoragesOfUnknownMediaTypeAreCopied_Impl( const uno::Reference< embed::XStorage >& xSource,
+ const uno::Reference< embed::XStorage >& xTarget )
+{
+ OSL_ENSURE( xSource.is() && xTarget.is(), "Source and/or target storages are not available!" );
+ if ( !xSource.is() || !xTarget.is() || xSource == xTarget )
+ return true;
+
+ try
+ {
+ const uno::Sequence< OUString > aSubElements = xSource->getElementNames();
+ for ( const auto& rSubElement : aSubElements )
+ {
+ if ( xSource->isStorageElement( rSubElement ) )
+ {
+ OUString aMediaType;
+ static constexpr OUString aMediaTypePropName( u"MediaType"_ustr );
+ bool bGotMediaType = false;
+
+ try
+ {
+ uno::Reference< embed::XOptimizedStorage > xOptStorage( xSource, uno::UNO_QUERY_THROW );
+ bGotMediaType =
+ ( xOptStorage->getElementPropertyValue( rSubElement, aMediaTypePropName ) >>= aMediaType );
+ }
+ catch( uno::Exception& )
+ {}
+
+ if ( !bGotMediaType )
+ {
+ uno::Reference< embed::XStorage > xSubStorage;
+ try {
+ xSubStorage = xSource->openStorageElement( rSubElement, embed::ElementModes::READ );
+ } catch( uno::Exception& )
+ {}
+
+ if ( !xSubStorage.is() )
+ {
+ xSubStorage = ::comphelper::OStorageHelper::GetTemporaryStorage();
+ xSource->copyStorageElementLastCommitTo( rSubElement, xSubStorage );
+ }
+
+ uno::Reference< beans::XPropertySet > xProps( xSubStorage, uno::UNO_QUERY_THROW );
+ xProps->getPropertyValue( aMediaTypePropName ) >>= aMediaType;
+ }
+
+ // TODO/LATER: there should be a way to detect whether an object with such a MediaType can exist
+ // probably it should be placed in the MimeType-ClassID table or in standalone table
+ if ( !aMediaType.isEmpty()
+ && aMediaType != "application/vnd.sun.star.oleobject" )
+ {
+ css::datatransfer::DataFlavor aDataFlavor;
+ aDataFlavor.MimeType = aMediaType;
+ SotClipboardFormatId nFormat = SotExchange::GetFormat( aDataFlavor );
+
+ switch ( nFormat )
+ {
+ case SotClipboardFormatId::STARWRITER_60 :
+ case SotClipboardFormatId::STARWRITERWEB_60 :
+ case SotClipboardFormatId::STARWRITERGLOB_60 :
+ case SotClipboardFormatId::STARDRAW_60 :
+ case SotClipboardFormatId::STARIMPRESS_60 :
+ case SotClipboardFormatId::STARCALC_60 :
+ case SotClipboardFormatId::STARCHART_60 :
+ case SotClipboardFormatId::STARMATH_60 :
+ case SotClipboardFormatId::STARWRITER_8:
+ case SotClipboardFormatId::STARWRITERWEB_8:
+ case SotClipboardFormatId::STARWRITERGLOB_8:
+ case SotClipboardFormatId::STARDRAW_8:
+ case SotClipboardFormatId::STARIMPRESS_8:
+ case SotClipboardFormatId::STARCALC_8:
+ case SotClipboardFormatId::STARCHART_8:
+ case SotClipboardFormatId::STARMATH_8:
+ break;
+
+ default:
+ {
+ if ( !xTarget->hasByName( rSubElement ) )
+ return false;
+ }
+ }
+ }
+ }
+ }
+ }
+ catch( uno::Exception& )
+ {
+ SAL_WARN( "sfx.doc", "Can not check storage consistency!" );
+ }
+
+ return true;
+}
+
+bool SfxObjectShell::SwitchPersistence( const uno::Reference< embed::XStorage >& xStorage )
+{
+ bool bResult = false;
+ // check for wrong creation of object container
+ bool bHasContainer( pImpl->mxObjectContainer );
+ if ( xStorage.is() )
+ {
+ if ( pImpl->mxObjectContainer )
+ GetEmbeddedObjectContainer().SwitchPersistence( xStorage );
+ bResult = SwitchChildrenPersistence( xStorage );
+
+ // TODO/LATER: substorages that have unknown mimetypes probably should be copied to the target storage here
+ OSL_ENSURE( StoragesOfUnknownMediaTypeAreCopied_Impl( pImpl->m_xDocStorage, xStorage ),
+ "Some of substorages with unknown mimetypes is lost!" );
+ }
+
+ if ( bResult )
+ {
+ // make sure that until the storage is assigned the object container is not created by accident!
+ DBG_ASSERT( bHasContainer == (pImpl->mxObjectContainer != nullptr), "Wrong storage in object container!" );
+ if ( pImpl->m_xDocStorage != xStorage )
+ DoSaveCompleted( new SfxMedium( xStorage, GetMedium()->GetBaseURL() ) );
+
+ if ( IsEnableSetModified() )
+ SetModified(); // ???
+ }
+
+ return bResult;
+}
+
+bool SfxObjectShell::CopyStoragesOfUnknownMediaType(const uno::Reference< embed::XStorage >& xSource,
+ const uno::Reference< embed::XStorage >& xTarget,
+ const uno::Sequence<OUString>& rExceptions)
+{
+ if (!xSource.is())
+ {
+ SAL_WARN( "sfx.doc", "SfxObjectShell::GetStorage() failed");
+ return false;
+ }
+
+ // This method does not commit the target storage and should not do it
+ bool bResult = true;
+
+ try
+ {
+ const css::uno::Sequence<OUString> aSubElementNames = xSource->getElementNames();
+ for (const OUString& rSubElement : aSubElementNames)
+ {
+ if (std::find(rExceptions.begin(), rExceptions.end(), rSubElement) != rExceptions.end())
+ continue;
+
+ if (rSubElement == "Configurations")
+ {
+ // The workaround for compatibility with SO7, "Configurations" substorage must be preserved
+ if (xSource->isStorageElement(rSubElement))
+ {
+ OSL_ENSURE(!xTarget->hasByName(rSubElement), "The target storage is an output "
+ "storage, the element should not "
+ "exist in the target!");
+
+ xSource->copyElementTo(rSubElement, xTarget, rSubElement);
+ }
+ }
+ else if (xSource->isStorageElement(rSubElement))
+ {
+ OUString aMediaType;
+ static constexpr OUString aMediaTypePropName( u"MediaType"_ustr );
+ bool bGotMediaType = false;
+
+ try
+ {
+ uno::Reference< embed::XOptimizedStorage > xOptStorage( xSource, uno::UNO_QUERY_THROW );
+ bGotMediaType = (xOptStorage->getElementPropertyValue(rSubElement, aMediaTypePropName)
+ >>= aMediaType);
+ }
+ catch( uno::Exception& )
+ {}
+
+ if ( !bGotMediaType )
+ {
+ uno::Reference< embed::XStorage > xSubStorage;
+ try {
+ xSubStorage
+ = xSource->openStorageElement(rSubElement, embed::ElementModes::READ);
+ } catch( uno::Exception& )
+ {}
+
+ if ( !xSubStorage.is() )
+ {
+ // TODO/LATER: as optimization in future a substorage of target storage could be used
+ // instead of the temporary storage; this substorage should be removed later
+ // if the MimeType is wrong
+ xSubStorage = ::comphelper::OStorageHelper::GetTemporaryStorage();
+ xSource->copyStorageElementLastCommitTo(rSubElement, xSubStorage);
+ }
+
+ uno::Reference< beans::XPropertySet > xProps( xSubStorage, uno::UNO_QUERY_THROW );
+ xProps->getPropertyValue( aMediaTypePropName ) >>= aMediaType;
+ }
+
+ // TODO/LATER: there should be a way to detect whether an object with such a MediaType can exist
+ // probably it should be placed in the MimeType-ClassID table or in standalone table
+ if ( !aMediaType.isEmpty()
+ && aMediaType != "application/vnd.sun.star.oleobject" )
+ {
+ css::datatransfer::DataFlavor aDataFlavor;
+ aDataFlavor.MimeType = aMediaType;
+ SotClipboardFormatId nFormat = SotExchange::GetFormat( aDataFlavor );
+
+ switch ( nFormat )
+ {
+ case SotClipboardFormatId::STARWRITER_60 :
+ case SotClipboardFormatId::STARWRITERWEB_60 :
+ case SotClipboardFormatId::STARWRITERGLOB_60 :
+ case SotClipboardFormatId::STARDRAW_60 :
+ case SotClipboardFormatId::STARIMPRESS_60 :
+ case SotClipboardFormatId::STARCALC_60 :
+ case SotClipboardFormatId::STARCHART_60 :
+ case SotClipboardFormatId::STARMATH_60 :
+ case SotClipboardFormatId::STARWRITER_8:
+ case SotClipboardFormatId::STARWRITERWEB_8:
+ case SotClipboardFormatId::STARWRITERGLOB_8:
+ case SotClipboardFormatId::STARDRAW_8:
+ case SotClipboardFormatId::STARIMPRESS_8:
+ case SotClipboardFormatId::STARCALC_8:
+ case SotClipboardFormatId::STARCHART_8:
+ case SotClipboardFormatId::STARMATH_8:
+ break;
+
+ default:
+ {
+ OSL_ENSURE(rSubElement == "Configurations2"
+ || nFormat == SotClipboardFormatId::STARBASE_8
+ || !xTarget->hasByName(rSubElement),
+ "The target storage is an output storage, the element "
+ "should not exist in the target!");
+
+ if (!xTarget->hasByName(rSubElement))
+ {
+ xSource->copyElementTo(rSubElement, xTarget, rSubElement);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ catch( uno::Exception& )
+ {
+ bResult = false;
+ // TODO/LATER: a specific error could be provided
+ }
+
+ return bResult;
+}
+
+bool SfxObjectShell::GenerateAndStoreThumbnail(bool bEncrypted, const uno::Reference<embed::XStorage>& xStorage)
+{
+ //optimize thumbnail generate and store procedure to improve odt saving performance, i120030
+ bIsInGenerateThumbnail = true;
+
+ bool bResult = false;
+
+ try
+ {
+ uno::Reference<embed::XStorage> xThumbnailStorage = xStorage->openStorageElement("Thumbnails", embed::ElementModes::READWRITE);
+
+ if (xThumbnailStorage.is())
+ {
+ uno::Reference<io::XStream> xStream = xThumbnailStorage->openStreamElement("thumbnail.png", embed::ElementModes::READWRITE);
+
+ if (xStream.is() && WriteThumbnail(bEncrypted, xStream))
+ {
+ uno::Reference<embed::XTransactedObject> xTransactedObject(xThumbnailStorage, uno::UNO_QUERY_THROW);
+ xTransactedObject->commit();
+ bResult = true;
+ }
+ }
+ }
+ catch( uno::Exception& )
+ {
+ }
+
+ //optimize thumbnail generate and store procedure to improve odt saving performance, i120030
+ bIsInGenerateThumbnail = false;
+
+ return bResult;
+}
+
+bool SfxObjectShell::WriteThumbnail(bool bEncrypted, const uno::Reference<io::XStream>& xStream)
+{
+ bool bResult = false;
+
+ if (!xStream.is())
+ return false;
+
+ try
+ {
+ uno::Reference<io::XTruncate> xTruncate(xStream->getOutputStream(), uno::UNO_QUERY_THROW);
+ xTruncate->truncate();
+
+ uno::Reference <beans::XPropertySet> xSet(xStream, uno::UNO_QUERY);
+ if (xSet.is())
+ xSet->setPropertyValue("MediaType", uno::Any(OUString("image/png")));
+ if (bEncrypted)
+ {
+ const OUString sResID = GraphicHelper::getThumbnailReplacementIDByFactoryName_Impl(
+ GetFactory().GetFactoryName());
+ if (!sResID.isEmpty())
+ bResult = GraphicHelper::getThumbnailReplacement_Impl(sResID, xStream);
+ }
+ else
+ {
+ BitmapEx bitmap = GetPreviewBitmap();
+ if (!bitmap.IsEmpty())
+ {
+ bResult = GraphicHelper::getThumbnailFormatFromBitmap_Impl(bitmap, xStream);
+ }
+ }
+ }
+ catch(uno::Exception&)
+ {}
+
+ return bResult;
+}
+
+void SfxObjectShell::UpdateLinks()
+{
+}
+
+bool SfxObjectShell::LoadExternal( SfxMedium& )
+{
+ // Not implemented. It's an error if the code path ever comes here.
+ assert(false);
+ return false;
+}
+
+bool SfxObjectShell::InsertGeneratedStream(SfxMedium&,
+ uno::Reference<text::XTextRange> const&)
+{
+ // Not implemented. It's an error if the code path ever comes here.
+ assert(false);
+ return false;
+}
+
+bool SfxObjectShell::IsConfigOptionsChecked() const
+{
+ return pImpl->m_bConfigOptionsChecked;
+}
+
+void SfxObjectShell::SetConfigOptionsChecked( bool bChecked )
+{
+ pImpl->m_bConfigOptionsChecked = bChecked;
+}
+
+void SfxObjectShell::SetMacroCallsSeenWhileLoading()
+{
+ pImpl->m_bMacroCallsSeenWhileLoading = true;
+}
+
+bool SfxObjectShell::GetMacroCallsSeenWhileLoading() const
+{
+ if (utl::ConfigManager::IsFuzzing() || officecfg::Office::Common::Security::Scripting::CheckDocumentEvents::get())
+ return pImpl->m_bMacroCallsSeenWhileLoading;
+ return false;
+}
+
+bool SfxObjectShell::QuerySaveSizeExceededModules_Impl( const uno::Reference< task::XInteractionHandler >& xHandler )
+{
+#if !HAVE_FEATURE_SCRIPTING
+ (void) xHandler;
+#else
+ if ( !HasBasic() )
+ return true;
+
+ if ( !pImpl->aBasicManager.isValid() )
+ GetBasicManager();
+ std::vector< OUString > sModules;
+ if ( xHandler.is() )
+ {
+ if( pImpl->aBasicManager.ImgVersion12PsswdBinaryLimitExceeded( sModules ) )
+ {
+ rtl::Reference<ModuleSizeExceeded> pReq = new ModuleSizeExceeded( sModules );
+ xHandler->handle( pReq );
+ return pReq->isApprove();
+ }
+ }
+#endif
+ // No interaction handler, default is to continue to save
+ return true;
+}
+
+bool SfxObjectShell::QueryAllowExoticFormat_Impl( const uno::Reference< task::XInteractionHandler >& xHandler, const OUString& rURL, const OUString& rFilterUIName )
+{
+ if ( SvtSecurityOptions::isTrustedLocationUri( rURL ) )
+ {
+ // Always load from trusted location
+ return true;
+ }
+ if ( officecfg::Office::Common::Security::LoadExoticFileFormats::get() == 0 )
+ {
+ // Refuse loading without question
+ return false;
+ }
+ else if ( officecfg::Office::Common::Security::LoadExoticFileFormats::get() == 2 )
+ {
+ // Always load without question
+ return true;
+ }
+ else if ( officecfg::Office::Common::Security::LoadExoticFileFormats::get() == 1 && xHandler.is() )
+ {
+ // Display a warning and let the user decide
+ rtl::Reference<ExoticFileLoadException> xException(new ExoticFileLoadException( rURL, rFilterUIName ));
+ xHandler->handle( xException );
+ return xException->isApprove();
+ }
+ // No interaction handler, default is to continue to load
+ return true;
+}
+
+uno::Reference< task::XInteractionHandler > SfxObjectShell::getInteractionHandler() const
+{
+ uno::Reference< task::XInteractionHandler > xRet;
+ if ( GetMedium() )
+ xRet = GetMedium()->GetInteractionHandler();
+ return xRet;
+}
+
+OUString SfxObjectShell::getDocumentBaseURL() const
+{
+ return GetMedium()->GetBaseURL();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/objstor.hxx b/sfx2/source/doc/objstor.hxx
new file mode 100644
index 0000000000..4692dbf71f
--- /dev/null
+++ b/sfx2/source/doc/objstor.hxx
@@ -0,0 +1,29 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SFX2_SOURCE_DOC_OBJSTOR_HXX
+#define INCLUDED_SFX2_SOURCE_DOC_OBJSTOR_HXX
+
+#include <com/sun/star/frame/XModel.hpp>
+
+void impl_addToModelCollection(const css::uno::Reference<css::frame::XModel>& xModel);
+
+#endif // INCLUDED_SFX2_SOURCE_DOC_OBJSTOR_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/objxtor.cxx b/sfx2/source/doc/objxtor.cxx
new file mode 100644
index 0000000000..11b38ced10
--- /dev/null
+++ b/sfx2/source/doc/objxtor.cxx
@@ -0,0 +1,1128 @@
+/* -*- 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 <config_features.h>
+#include <config_fuzzers.h>
+
+#include <map>
+
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/weakref.hxx>
+
+#include <com/sun/star/util/XCloseable.hpp>
+#include <com/sun/star/frame/XComponentLoader.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/util/XCloseListener.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/frame/XTitle.hpp>
+#include <osl/file.hxx>
+#include <sal/log.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/svapp.hxx>
+#include <svl/eitem.hxx>
+#include <basic/sbstar.hxx>
+#include <svl/stritem.hxx>
+#include <unotools/configmgr.hxx>
+#include <unotools/eventcfg.hxx>
+
+#include <sfx2/objsh.hxx>
+#include <sfx2/signaturestate.hxx>
+#include <sfx2/sfxmodelfactory.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/servicehelper.hxx>
+
+#include <com/sun/star/document/XStorageBasedDocument.hpp>
+#include <com/sun/star/script/DocumentDialogLibraryContainer.hpp>
+#include <com/sun/star/script/DocumentScriptLibraryContainer.hpp>
+#include <com/sun/star/document/XEmbeddedScripts.hpp>
+#include <com/sun/star/document/XScriptInvocationContext.hpp>
+#include <com/sun/star/ucb/ContentCreationException.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+
+#include <unotools/ucbhelper.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/globname.hxx>
+#include <tools/debug.hxx>
+
+#include <sfx2/app.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/docfile.hxx>
+#include <sfx2/event.hxx>
+#include <sfx2/viewsh.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/sfxresid.hxx>
+#include <objshimp.hxx>
+#include <sfx2/strings.hrc>
+#include <sfx2/sfxsids.hrc>
+#include <basic/basmgr.hxx>
+#include <sfx2/QuerySaveDocument.hxx>
+#include <appbaslib.hxx>
+#include <sfx2/sfxbasemodel.hxx>
+#include <sfx2/sfxuno.hxx>
+#include <sfx2/notebookbar/SfxNotebookBar.hxx>
+#include <sfx2/infobar.hxx>
+#include <svtools/svparser.hxx>
+
+#include <basic/basicmanagerrepository.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::script;
+using namespace ::com::sun::star::frame;
+using namespace ::com::sun::star::document;
+
+using ::basic::BasicManagerRepository;
+
+namespace {
+
+WeakReference< XInterface > theCurrentComponent;
+
+#if HAVE_FEATURE_SCRIPTING
+
+// remember all registered components for VBA compatibility, to be able to remove them on disposing the model
+typedef ::std::map< XInterface*, OUString > VBAConstantNameMap;
+VBAConstantNameMap s_aRegisteredVBAConstants;
+
+OUString lclGetVBAGlobalConstName( const Reference< XInterface >& rxComponent )
+{
+ OSL_ENSURE( rxComponent.is(), "lclGetVBAGlobalConstName - missing component" );
+
+ VBAConstantNameMap::iterator aIt = s_aRegisteredVBAConstants.find( rxComponent.get() );
+ if( aIt != s_aRegisteredVBAConstants.end() )
+ return aIt->second;
+
+ uno::Reference< beans::XPropertySet > xProps( rxComponent, uno::UNO_QUERY );
+ if( xProps.is() ) try
+ {
+ OUString aConstName;
+ xProps->getPropertyValue("VBAGlobalConstantName") >>= aConstName;
+ return aConstName;
+ }
+ catch (const uno::Exception&) // not supported
+ {
+ }
+ return OUString();
+}
+
+#endif
+
+class SfxModelListener_Impl : public ::cppu::WeakImplHelper< css::util::XCloseListener >
+{
+ SfxObjectShell* mpDoc;
+public:
+ explicit SfxModelListener_Impl( SfxObjectShell* pDoc ) : mpDoc(pDoc) {};
+ virtual void SAL_CALL queryClosing( const css::lang::EventObject& aEvent, sal_Bool bDeliverOwnership ) override ;
+ virtual void SAL_CALL notifyClosing( const css::lang::EventObject& aEvent ) override ;
+ virtual void SAL_CALL disposing( const css::lang::EventObject& aEvent ) override ;
+
+};
+
+} // namespace
+
+void SAL_CALL SfxModelListener_Impl::queryClosing( const css::lang::EventObject& , sal_Bool )
+{
+}
+
+void SAL_CALL SfxModelListener_Impl::notifyClosing( const css::lang::EventObject& )
+{
+ SolarMutexGuard aSolarGuard;
+ mpDoc->Broadcast( SfxHint(SfxHintId::Deinitializing) );
+}
+
+void SAL_CALL SfxModelListener_Impl::disposing( const css::lang::EventObject& _rEvent )
+{
+ // am I ThisComponent in AppBasic?
+ SolarMutexGuard aSolarGuard;
+ if ( SfxObjectShell::GetCurrentComponent() == _rEvent.Source )
+ {
+ // remove ThisComponent reference from AppBasic
+ SfxObjectShell::SetCurrentComponent( Reference< XInterface >() );
+ }
+
+#if HAVE_FEATURE_SCRIPTING
+ /* Remove VBA component from AppBasic. As every application registers its
+ own current component, the disposed component may not be the "current
+ component" of the SfxObjectShell. */
+ if ( _rEvent.Source.is() )
+ {
+ VBAConstantNameMap::iterator aIt = s_aRegisteredVBAConstants.find( _rEvent.Source.get() );
+ if ( aIt != s_aRegisteredVBAConstants.end() )
+ {
+ if ( BasicManager* pAppMgr = SfxApplication::GetBasicManager() )
+ pAppMgr->SetGlobalUNOConstant( aIt->second, Any( Reference< XInterface >() ) );
+ s_aRegisteredVBAConstants.erase( aIt );
+ }
+ }
+#endif
+
+ if ( !mpDoc->Get_Impl()->bClosing )
+ // GCC crashes when already in the destructor, so first query the Flag
+ mpDoc->DoClose();
+}
+
+
+SfxObjectShell_Impl::SfxObjectShell_Impl( SfxObjectShell& _rDocShell )
+ :rDocShell( _rDocShell )
+ ,aMacroMode( *this )
+ ,pProgress( nullptr)
+ ,nTime( DateTime::SYSTEM )
+ ,nVisualDocumentNumber( USHRT_MAX)
+ ,nDocumentSignatureState( SignatureState::UNKNOWN )
+ ,nScriptingSignatureState( SignatureState::UNKNOWN )
+ ,bClosing( false)
+ ,bIsSaving( false)
+ ,bIsNamedVisible( false)
+ ,bIsAbortingImport ( false)
+ ,bInPrepareClose( false )
+ ,bPreparedForClose( false )
+ ,bForbidReload( false )
+ ,bBasicInitialized( false )
+ ,bIsPrintJobCancelable( true )
+ ,bOwnsStorage( true )
+ ,bInitialized( false )
+ ,bModelInitialized( false )
+ ,bPreserveVersions( true )
+ ,m_bMacroSignBroken( false )
+ ,m_bNoBasicCapabilities( false )
+ ,m_bDocRecoverySupport( true )
+ ,bQueryLoadTemplate( true )
+ ,bLoadReadonly( false )
+ ,bUseUserData( true )
+ ,bUseThumbnailSave( true )
+ ,bSaveVersionOnClose( false )
+ ,m_bSharedXMLFlag( false )
+ ,m_bAllowShareControlFileClean( true )
+ ,m_bConfigOptionsChecked( false )
+ ,m_bMacroCallsSeenWhileLoading( false )
+ ,m_bHadCheckedMacrosOnLoad( false )
+ ,lErr(ERRCODE_NONE)
+ ,nEventId ( SfxEventHintId::NONE )
+ ,nLoadedFlags ( SfxLoadedFlags::ALL )
+ ,nFlagsInProgress( SfxLoadedFlags::NONE )
+ ,bModalMode( false )
+ ,bRunningMacro( false )
+ ,bReadOnlyUI( false )
+ ,nStyleFilter( 0 )
+ ,m_bEnableSetModified( true )
+ ,m_bIsModified( false )
+ ,m_nMapUnit( MapUnit::Map100thMM )
+ ,m_bCreateTempStor( false )
+ ,m_bIsInit( false )
+ ,m_bIncomplEncrWarnShown( false )
+ ,m_nModifyPasswordHash( 0 )
+ ,m_bModifyPasswordEntered( false )
+ ,m_bSavingForSigning( false )
+ ,m_bAllowModifiedBackAfterSigning( false )
+{
+ SfxObjectShell* pDoc = &_rDocShell;
+ std::vector<SfxObjectShell*> &rArr = SfxGetpApp()->GetObjectShells_Impl();
+ rArr.push_back( pDoc );
+}
+
+
+SfxObjectShell_Impl::~SfxObjectShell_Impl()
+{
+}
+
+
+SfxObjectShell::SfxObjectShell( const SfxModelFlags i_nCreationFlags )
+ : pImpl(new SfxObjectShell_Impl(*this))
+ , pMedium(nullptr)
+ , eCreateMode(SfxObjectCreateMode::STANDARD)
+ , bHasName(false)
+ , bIsInGenerateThumbnail (false)
+ , mbAvoidRecentDocs(false)
+ , bRememberSignature(false)
+{
+ if (i_nCreationFlags & SfxModelFlags::EMBEDDED_OBJECT)
+ eCreateMode = SfxObjectCreateMode::EMBEDDED;
+ else if (i_nCreationFlags & SfxModelFlags::EXTERNAL_LINK)
+ eCreateMode = SfxObjectCreateMode::INTERNAL;
+
+ const bool bScriptSupport = ( i_nCreationFlags & SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS ) == SfxModelFlags::NONE;
+ if ( !bScriptSupport )
+ pImpl->m_bNoBasicCapabilities = true;
+
+ const bool bDocRecovery = ( i_nCreationFlags & SfxModelFlags::DISABLE_DOCUMENT_RECOVERY ) == SfxModelFlags::NONE;
+ if ( !bDocRecovery )
+ pImpl->m_bDocRecoverySupport = false;
+}
+
+/** Constructor of the class SfxObjectShell.
+
+ @param eMode Purpose, to which the SfxObjectShell is created:
+ SfxObjectCreateMode::EMBEDDED (default) as SO-Server from within another Document
+ SfxObjectCreateMode::STANDARD, as a normal Document open stand-alone
+ SfxObjectCreateMode::ORGANIZER to be displayed in the Organizer, here nothing of the contents is used
+*/
+SfxObjectShell::SfxObjectShell(SfxObjectCreateMode eMode)
+ : pImpl(new SfxObjectShell_Impl(*this))
+ , pMedium(nullptr)
+ , eCreateMode(eMode)
+ , bHasName(false)
+ , bIsInGenerateThumbnail(false)
+ , mbAvoidRecentDocs(false)
+ , bRememberSignature(false)
+{
+}
+
+SfxObjectShell::~SfxObjectShell()
+{
+
+ if ( IsEnableSetModified() )
+ EnableSetModified( false );
+
+ SfxObjectShell::CloseInternal();
+ pImpl->pBaseModel.clear();
+
+ pImpl->pReloadTimer.reset();
+
+ SfxApplication *pSfxApp = SfxGetpApp();
+ if ( USHRT_MAX != pImpl->nVisualDocumentNumber && pSfxApp )
+ pSfxApp->ReleaseIndex(pImpl->nVisualDocumentNumber);
+
+ // Destroy Basic-Manager
+ pImpl->aBasicManager.reset(nullptr);
+
+ if ( pSfxApp && pSfxApp->GetDdeService() )
+ pSfxApp->RemoveDdeTopic( this );
+
+ pImpl->pBaseModel.clear();
+
+ // don't call GetStorage() here, in case of Load Failure it's possible that a storage was never assigned!
+ if ( pMedium && pMedium->HasStorage_Impl() && pMedium->GetStorage( false ) == pImpl->m_xDocStorage )
+ pMedium->CanDisposeStorage_Impl( false );
+
+ if ( pImpl->mxObjectContainer )
+ {
+ pImpl->mxObjectContainer->CloseEmbeddedObjects();
+ pImpl->mxObjectContainer.reset();
+ }
+
+ if ( pImpl->bOwnsStorage && pImpl->m_xDocStorage.is() )
+ pImpl->m_xDocStorage->dispose();
+
+ if ( pMedium )
+ {
+ pMedium->CloseAndReleaseStreams_Impl();
+
+#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+ if (IsDocShared())
+ FreeSharedFile( pMedium->GetURLObject().GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+#endif
+ delete pMedium;
+ pMedium = nullptr;
+ }
+
+ // The removing of the temporary file must be done as the latest step in the document destruction
+ if ( !pImpl->aTempName.isEmpty() )
+ {
+ OUString aTmp;
+ osl::FileBase::getFileURLFromSystemPath( pImpl->aTempName, aTmp );
+ ::utl::UCBContentHelper::Kill( aTmp );
+ }
+}
+
+
+void SfxObjectShell::Stamp_SetPrintCancelState(bool bState)
+{
+ pImpl->bIsPrintJobCancelable = bState;
+}
+
+
+bool SfxObjectShell::Stamp_GetPrintCancelState() const
+{
+ return pImpl->bIsPrintJobCancelable;
+}
+
+
+// closes the Object and all its views
+
+bool SfxObjectShell::Close()
+{
+ SfxObjectShellRef xKeepAlive(this);
+ return CloseInternal();
+}
+
+// variant that does not take a reference to itself, so we can call it during object destruction
+bool SfxObjectShell::CloseInternal()
+{
+ if ( !pImpl->bClosing )
+ {
+ // Do not close if a progress is still running
+ if ( GetProgress() )
+ return false;
+
+ pImpl->bClosing = true;
+ Reference< util::XCloseable > xCloseable( GetBaseModel(), UNO_QUERY );
+
+ if ( xCloseable.is() )
+ {
+ try
+ {
+ xCloseable->close( true );
+ }
+ catch (const Exception&)
+ {
+ pImpl->bClosing = false;
+ }
+ }
+
+ if ( pImpl->bClosing )
+ {
+ // remove from Document list
+ // If there is no App, there is no document to remove
+ // no need to call GetOrCreate here
+ SfxApplication *pSfxApp = SfxApplication::Get();
+ if(pSfxApp)
+ {
+ std::vector<SfxObjectShell*> &rDocs = pSfxApp->GetObjectShells_Impl();
+ auto it = std::find( rDocs.begin(), rDocs.end(), this );
+ if ( it != rDocs.end() )
+ rDocs.erase( it );
+ }
+ }
+ }
+
+ return true;
+}
+
+OUString SfxObjectShell::CreateShellID( const SfxObjectShell* pShell )
+{
+ if (!pShell)
+ return OUString();
+
+ OUString aShellID;
+
+ SfxMedium* pMedium = pShell->GetMedium();
+ if (pMedium)
+ aShellID = pMedium->GetBaseURL();
+
+ if (!aShellID.isEmpty())
+ return aShellID;
+
+ sal_Int64 nShellID = reinterpret_cast<sal_Int64>(pShell);
+ aShellID = "0x" + OUString::number(nShellID, 16);
+ return aShellID;
+}
+
+// returns a pointer the first SfxDocument of specified type
+
+SfxObjectShell* SfxObjectShell::GetFirst
+(
+ const std::function<bool ( const SfxObjectShell* )>& isObjectShell,
+ bool bOnlyVisible
+)
+{
+ std::vector<SfxObjectShell*> &rDocs = SfxGetpApp()->GetObjectShells_Impl();
+
+ // search for a SfxDocument of the specified type
+ for (SfxObjectShell* pSh : rDocs)
+ {
+ if ( bOnlyVisible && pSh->IsPreview() && pSh->IsReadOnly() )
+ continue;
+
+ if ( (!isObjectShell || isObjectShell( pSh)) &&
+ ( !bOnlyVisible || SfxViewFrame::GetFirst( pSh )))
+ return pSh;
+ }
+
+ return nullptr;
+}
+
+
+// returns a pointer to the next SfxDocument of specified type behind *pDoc
+
+SfxObjectShell* SfxObjectShell::GetNext
+(
+ const SfxObjectShell& rPrev,
+ const std::function<bool ( const SfxObjectShell* )>& isObjectShell,
+ bool bOnlyVisible
+)
+{
+ std::vector<SfxObjectShell*> &rDocs = SfxGetpApp()->GetObjectShells_Impl();
+
+ // refind the specified predecessor
+ size_t nPos;
+ for ( nPos = 0; nPos < rDocs.size(); ++nPos )
+ if ( rDocs[nPos] == &rPrev )
+ break;
+
+ // search for the next SfxDocument of the specified type
+ for ( ++nPos; nPos < rDocs.size(); ++nPos )
+ {
+ SfxObjectShell* pSh = rDocs[ nPos ];
+ if ( bOnlyVisible && pSh->IsPreview() && pSh->IsReadOnly() )
+ continue;
+
+ if ( (!isObjectShell || isObjectShell( pSh)) &&
+ ( !bOnlyVisible || SfxViewFrame::GetFirst( pSh )))
+ return pSh;
+ }
+ return nullptr;
+}
+
+
+SfxObjectShell* SfxObjectShell::Current()
+{
+ SfxViewFrame *pFrame = SfxViewFrame::Current();
+ return pFrame ? pFrame->GetObjectShell() : nullptr;
+}
+
+
+bool SfxObjectShell::IsInPrepareClose() const
+{
+ return pImpl->bInPrepareClose;
+}
+
+namespace {
+
+struct BoolEnv_Impl
+{
+ SfxObjectShell_Impl& rImpl;
+ explicit BoolEnv_Impl( SfxObjectShell_Impl& rImplP) : rImpl( rImplP )
+ { rImplP.bInPrepareClose = true; }
+ ~BoolEnv_Impl() { rImpl.bInPrepareClose = false; }
+};
+
+}
+
+bool SfxObjectShell::PrepareClose
+(
+ bool bUI // true: Dialog and so on is allowed
+ // false: silent-mode
+)
+{
+ if( pImpl->bInPrepareClose || pImpl->bPreparedForClose )
+ return true;
+ BoolEnv_Impl aBoolEnv( *pImpl );
+
+ // DocModalDialog?
+ if ( IsInModalMode() )
+ return false;
+
+ SfxViewFrame* pFirst = SfxViewFrame::GetFirst( this );
+ if( pFirst && !pFirst->GetFrame().PrepareClose_Impl( bUI ) )
+ return false;
+
+ // prepare views for closing
+ for ( SfxViewFrame* pFrm = SfxViewFrame::GetFirst( this );
+ pFrm; pFrm = SfxViewFrame::GetNext( *pFrm, this ) )
+ {
+ DBG_ASSERT(pFrm->GetViewShell(),"No Shell");
+ if ( pFrm->GetViewShell() )
+ {
+ bool bRet = pFrm->GetViewShell()->PrepareClose( bUI );
+ if ( !bRet )
+ return bRet;
+ }
+ }
+
+ SfxApplication *pSfxApp = SfxGetpApp();
+ pSfxApp->NotifyEvent( SfxEventHint(SfxEventHintId::PrepareCloseDoc, GlobalEventConfig::GetEventName(GlobalEventId::PREPARECLOSEDOC), this) );
+
+ if( GetCreateMode() == SfxObjectCreateMode::EMBEDDED )
+ {
+ pImpl->bPreparedForClose = true;
+ return true;
+ }
+
+ // Ask if possible if it should be saved
+ // only ask for the Document in the visible window
+ SfxViewFrame *pFrame = SfxObjectShell::Current() == this
+ ? SfxViewFrame::Current() : SfxViewFrame::GetFirst( this );
+
+ if ( bUI && IsModified() && pFrame )
+ {
+ // restore minimized
+ SfxFrame& rTop = pFrame->GetFrame();
+ SfxViewFrame::SetViewFrame( rTop.GetCurrentViewFrame() );
+ pFrame->GetFrame().Appear();
+
+ // Ask if to save
+ short nRet = RET_YES;
+ {
+ const Reference<XTitle> xTitle(*pImpl->pBaseModel, UNO_QUERY_THROW);
+ const OUString sTitle = xTitle->getTitle ();
+ nRet = ExecuteQuerySaveDocument(pFrame->GetFrameWeld(), sTitle);
+ }
+ /*HACK for plugin::destroy()*/
+
+ if ( RET_YES == nRet )
+ {
+ // Save by each Dispatcher
+ SfxPoolItemHolder aPoolItem;
+ if (IsReadOnly())
+ {
+ SfxBoolItem aWarnItem( SID_FAIL_ON_WARNING, bUI );
+ const SfxPoolItem* ppArgs[] = { &aWarnItem, nullptr };
+ aPoolItem = pFrame->GetBindings().ExecuteSynchron(SID_SAVEASDOC, ppArgs);
+ }
+ else if (IsSaveVersionOnClose())
+ {
+ SfxStringItem aItem( SID_DOCINFO_COMMENTS, SfxResId(STR_AUTOMATICVERSION) );
+ SfxBoolItem aWarnItem( SID_FAIL_ON_WARNING, bUI );
+ const SfxPoolItem* ppArgs[] = { &aItem, &aWarnItem, nullptr };
+ aPoolItem = pFrame->GetBindings().ExecuteSynchron( SID_SAVEDOC, ppArgs );
+ }
+ else
+ {
+ SfxBoolItem aWarnItem( SID_FAIL_ON_WARNING, bUI );
+ const SfxPoolItem* ppArgs[] = { &aWarnItem, nullptr };
+ aPoolItem = pFrame->GetBindings().ExecuteSynchron( SID_SAVEDOC, ppArgs );
+ }
+
+ if ( nullptr == aPoolItem.getItem() || aPoolItem.getItem()->isVoidItem() )
+ return false;
+ if ( auto pBoolItem = dynamic_cast< const SfxBoolItem *>( aPoolItem.getItem() ) )
+ if ( !pBoolItem->GetValue() )
+ return false;
+ }
+ else if ( RET_CANCEL == nRet )
+ // Cancelled
+ return false;
+ }
+
+ if ( pFrame )
+ sfx2::SfxNotebookBar::CloseMethod(pFrame->GetBindings());
+ pImpl->bPreparedForClose = true;
+ return true;
+}
+
+
+#if HAVE_FEATURE_SCRIPTING
+namespace
+{
+ BasicManager* lcl_getBasicManagerForDocument( const SfxObjectShell& _rDocument )
+ {
+ if ( !_rDocument.Get_Impl()->m_bNoBasicCapabilities )
+ {
+ if ( !_rDocument.Get_Impl()->bBasicInitialized )
+ const_cast< SfxObjectShell& >( _rDocument ).InitBasicManager_Impl();
+ return _rDocument.Get_Impl()->aBasicManager.get();
+ }
+
+ // assume we do not have Basic ourself, but we can refer to another
+ // document which does (by our model's XScriptInvocationContext::getScriptContainer).
+ // In this case, we return the BasicManager of this other document.
+
+ OSL_ENSURE( !Reference< XEmbeddedScripts >( _rDocument.GetModel(), UNO_QUERY ).is(),
+ "lcl_getBasicManagerForDocument: inconsistency: no Basic, but an XEmbeddedScripts?" );
+ Reference< XModel > xForeignDocument;
+ Reference< XScriptInvocationContext > xContext( _rDocument.GetModel(), UNO_QUERY );
+ if ( xContext.is() )
+ {
+ xForeignDocument.set( xContext->getScriptContainer(), UNO_QUERY );
+ OSL_ENSURE( xForeignDocument.is() && xForeignDocument != _rDocument.GetModel(),
+ "lcl_getBasicManagerForDocument: no Basic, but providing ourself as script container?" );
+ }
+
+ BasicManager* pBasMgr = nullptr;
+ if ( xForeignDocument.is() )
+ pBasMgr = ::basic::BasicManagerRepository::getDocumentBasicManager( xForeignDocument );
+
+ return pBasMgr;
+ }
+}
+#endif
+
+BasicManager* SfxObjectShell::GetBasicManager() const
+{
+ BasicManager* pBasMgr = nullptr;
+#if HAVE_FEATURE_SCRIPTING
+ try
+ {
+ pBasMgr = lcl_getBasicManagerForDocument( *this );
+ if ( !pBasMgr )
+ pBasMgr = SfxApplication::GetBasicManager();
+ }
+ catch (const css::ucb::ContentCreationException&)
+ {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "");
+ }
+#endif
+ return pBasMgr;
+}
+
+bool SfxObjectShell::HasBasic() const
+{
+#if !HAVE_FEATURE_SCRIPTING
+ return false;
+#else
+ if ( pImpl->m_bNoBasicCapabilities )
+ return false;
+
+ if ( !pImpl->bBasicInitialized )
+ const_cast< SfxObjectShell* >( this )->InitBasicManager_Impl();
+
+ return pImpl->aBasicManager.isValid();
+#endif
+}
+
+
+#if HAVE_FEATURE_SCRIPTING
+namespace
+{
+ const Reference< XLibraryContainer >&
+ lcl_getOrCreateLibraryContainer( bool _bScript, Reference< XLibraryContainer >& _rxContainer,
+ const Reference< XModel >& _rxDocument )
+ {
+ if ( !_rxContainer.is() )
+ {
+ try
+ {
+ Reference< XStorageBasedDocument > xStorageDoc( _rxDocument, UNO_QUERY );
+ const Reference< XComponentContext > xContext(
+ ::comphelper::getProcessComponentContext() );
+ _rxContainer.set ( _bScript
+ ? DocumentScriptLibraryContainer::create(
+ xContext, xStorageDoc )
+ : DocumentDialogLibraryContainer::create(
+ xContext, xStorageDoc )
+ , UNO_QUERY_THROW );
+ }
+ catch (const Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("sfx.doc");
+ }
+ }
+ return _rxContainer;
+ }
+}
+#endif
+
+Reference< XLibraryContainer > SfxObjectShell::GetDialogContainer()
+{
+#if HAVE_FEATURE_SCRIPTING
+ try
+ {
+ if ( !pImpl->m_bNoBasicCapabilities )
+ return lcl_getOrCreateLibraryContainer( false, pImpl->xDialogLibraries, GetModel() );
+
+ BasicManager* pBasMgr = lcl_getBasicManagerForDocument( *this );
+ if ( pBasMgr )
+ return pBasMgr->GetDialogLibraryContainer();
+ }
+ catch (const css::ucb::ContentCreationException&)
+ {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "");
+ }
+
+ SAL_WARN("sfx.doc", "SfxObjectShell::GetDialogContainer: falling back to the application - is this really expected here?");
+#endif
+ return SfxGetpApp()->GetDialogContainer();
+}
+
+Reference< XLibraryContainer > SfxObjectShell::GetBasicContainer()
+{
+#if HAVE_FEATURE_SCRIPTING
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ try
+ {
+ if ( !pImpl->m_bNoBasicCapabilities )
+ return lcl_getOrCreateLibraryContainer( true, pImpl->xBasicLibraries, GetModel() );
+
+ BasicManager* pBasMgr = lcl_getBasicManagerForDocument( *this );
+ if ( pBasMgr )
+ return pBasMgr->GetScriptLibraryContainer();
+ }
+ catch (const css::ucb::ContentCreationException&)
+ {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "");
+ }
+ }
+ SAL_WARN("sfx.doc", "SfxObjectShell::GetBasicContainer: falling back to the application - is this really expected here?");
+#endif
+ return SfxGetpApp()->GetBasicContainer();
+}
+
+StarBASIC* SfxObjectShell::GetBasic() const
+{
+#if !HAVE_FEATURE_SCRIPTING
+ return nullptr;
+#else
+ BasicManager * pMan = GetBasicManager();
+ return pMan ? pMan->GetLib(0) : nullptr;
+#endif
+}
+
+void SfxObjectShell::InitBasicManager_Impl()
+/* [Description]
+
+ Creates a document's BasicManager and loads it, if we are already based on
+ a storage.
+
+ [Note]
+
+ This method has to be called by implementations of <SvPersist::Load()>
+ (with its pStor parameter) and by implementations of <SvPersist::InitNew()>
+ (with pStor = 0).
+*/
+
+{
+ /* #163556# (DR) - Handling of recursive calls while creating the Basic
+ manager.
+
+ It is possible that (while creating the Basic manager) the code that
+ imports the Basic storage wants to access the Basic manager again.
+ Especially in VBA compatibility mode, there is code that wants to
+ access the "VBA Globals" object which is stored as global UNO constant
+ in the Basic manager.
+
+ To achieve correct handling of the recursive calls of this function
+ from lcl_getBasicManagerForDocument(), the implementation of the
+ function BasicManagerRepository::getDocumentBasicManager() has been
+ changed to return the Basic manager currently under construction, when
+ called repeatedly.
+
+ The variable pImpl->bBasicInitialized will be set to sal_True after
+ construction now, to ensure that the recursive call of the function
+ lcl_getBasicManagerForDocument() will be routed into this function too.
+
+ Calling BasicManagerHolder::reset() twice is not a big problem, as it
+ does not take ownership but stores only the raw pointer. Owner of all
+ Basic managers is the global BasicManagerRepository instance.
+ */
+#if HAVE_FEATURE_SCRIPTING
+ DBG_ASSERT( !pImpl->bBasicInitialized && !pImpl->aBasicManager.isValid(), "Local BasicManager already exists");
+ try
+ {
+ pImpl->aBasicManager.reset( BasicManagerRepository::getDocumentBasicManager( GetModel() ) );
+ }
+ catch (const css::ucb::ContentCreationException&)
+ {
+ TOOLS_WARN_EXCEPTION("sfx.doc", "");
+ }
+ DBG_ASSERT( pImpl->aBasicManager.isValid(), "SfxObjectShell::InitBasicManager_Impl: did not get a BasicManager!" );
+ pImpl->bBasicInitialized = true;
+#endif
+}
+
+
+bool SfxObjectShell::DoClose()
+{
+ return Close();
+}
+
+
+SfxObjectShell* SfxObjectShell::GetObjectShell()
+{
+ return this;
+}
+
+
+uno::Sequence< OUString > SfxObjectShell::GetEventNames()
+{
+ static uno::Sequence< OUString > s_EventNameContainer(rtl::Reference<GlobalEventConfig>(new GlobalEventConfig)->getElementNames());
+
+ return s_EventNameContainer;
+}
+
+
+css::uno::Reference< css::frame::XModel3 > SfxObjectShell::GetModel() const
+{
+ return GetBaseModel();
+}
+
+void SfxObjectShell::SetBaseModel( SfxBaseModel* pModel )
+{
+ OSL_ENSURE( !pImpl->pBaseModel.is() || pModel == nullptr, "Model already set!" );
+ pImpl->pBaseModel.set( pModel );
+ if ( pImpl->pBaseModel.is() )
+ {
+ pImpl->pBaseModel->addCloseListener( new SfxModelListener_Impl(this) );
+ }
+}
+
+
+css::uno::Reference< css::frame::XModel3 > SfxObjectShell::GetBaseModel() const
+{
+ return pImpl->pBaseModel;
+}
+
+void SfxObjectShell::SetAutoStyleFilterIndex(sal_uInt16 nSet)
+{
+ pImpl->nStyleFilter = nSet;
+}
+
+sal_uInt16 SfxObjectShell::GetAutoStyleFilterIndex() const
+{
+ return pImpl->nStyleFilter;
+}
+
+
+void SfxObjectShell::SetCurrentComponent( const Reference< XInterface >& _rxComponent )
+{
+ WeakReference< XInterface >& rTheCurrentComponent = theCurrentComponent;
+
+ Reference< XInterface > xOldCurrentComp(rTheCurrentComponent);
+ if ( _rxComponent == xOldCurrentComp )
+ // nothing to do
+ return;
+ // note that "_rxComponent.get() == s_xCurrentComponent.get().get()" is /sufficient/, but not
+ // /required/ for "_rxComponent == s_xCurrentComponent.get()".
+ // In other words, it's still possible that we here do something which is not necessary,
+ // but we should have filtered quite some unnecessary calls already.
+
+#if HAVE_FEATURE_SCRIPTING
+ BasicManager* pAppMgr = SfxApplication::GetBasicManager();
+ rTheCurrentComponent = _rxComponent;
+ if ( !pAppMgr )
+ return;
+
+ // set "ThisComponent" for Basic
+ pAppMgr->SetGlobalUNOConstant( "ThisComponent", Any( _rxComponent ) );
+
+ // set new current component for VBA compatibility
+ if ( _rxComponent.is() )
+ {
+ OUString aVBAConstName = lclGetVBAGlobalConstName( _rxComponent );
+ if ( !aVBAConstName.isEmpty() )
+ {
+ pAppMgr->SetGlobalUNOConstant( aVBAConstName, Any( _rxComponent ) );
+ s_aRegisteredVBAConstants[ _rxComponent.get() ] = aVBAConstName;
+ }
+ }
+ // no new component passed -> remove last registered VBA component
+ else if ( xOldCurrentComp.is() )
+ {
+ OUString aVBAConstName = lclGetVBAGlobalConstName( xOldCurrentComp );
+ if ( !aVBAConstName.isEmpty() )
+ {
+ pAppMgr->SetGlobalUNOConstant( aVBAConstName, Any( Reference< XInterface >() ) );
+ s_aRegisteredVBAConstants.erase( xOldCurrentComp.get() );
+ }
+ }
+#endif
+}
+
+Reference< XInterface > SfxObjectShell::GetCurrentComponent()
+{
+ return theCurrentComponent;
+}
+
+
+OUString SfxObjectShell::GetServiceNameFromFactory( const OUString& rFact )
+{
+ //! Remove everything behind name!
+ OUString aFact( rFact );
+ OUString aPrefix("private:factory/");
+ if ( aFact.startsWith( aPrefix ) )
+ aFact = aFact.copy( aPrefix.getLength() );
+ sal_Int32 nPos = aFact.indexOf( '?' );
+ if ( nPos != -1 )
+ {
+ aFact = aFact.copy( 0, nPos );
+ }
+ aFact = aFact.replaceAll("4", "");
+ aFact = aFact.toAsciiLowerCase();
+
+ // HACK: sometimes a real document service name is given here instead of
+ // a factory short name. Set return value directly to this service name as fallback
+ // in case next lines of code does nothing ...
+ // use rFact instead of normed aFact value !
+ OUString aServiceName = rFact;
+
+ if ( aFact == "swriter" )
+ {
+ aServiceName = "com.sun.star.text.TextDocument";
+ }
+ else if ( aFact == "sweb" || aFact == "swriter/web" )
+ {
+ aServiceName = "com.sun.star.text.WebDocument";
+ }
+ else if ( aFact == "sglobal" || aFact == "swriter/globaldocument" )
+ {
+ aServiceName = "com.sun.star.text.GlobalDocument";
+ }
+ else if ( aFact == "scalc" )
+ {
+ aServiceName = "com.sun.star.sheet.SpreadsheetDocument";
+ }
+ else if ( aFact == "sdraw" )
+ {
+ aServiceName = "com.sun.star.drawing.DrawingDocument";
+ }
+ else if ( aFact == "simpress" )
+ {
+ aServiceName = "com.sun.star.presentation.PresentationDocument";
+ }
+ else if ( aFact == "schart" )
+ {
+ aServiceName = "com.sun.star.chart.ChartDocument";
+ }
+ else if ( aFact == "smath" )
+ {
+ aServiceName = "com.sun.star.formula.FormulaProperties";
+ }
+#if HAVE_FEATURE_SCRIPTING
+ else if ( aFact == "sbasic" )
+ {
+ aServiceName = "com.sun.star.script.BasicIDE";
+ }
+#endif
+#if HAVE_FEATURE_DBCONNECTIVITY && !ENABLE_FUZZERS
+ else if ( aFact == "sdatabase" )
+ {
+ aServiceName = "com.sun.star.sdb.OfficeDatabaseDocument";
+ }
+#endif
+
+ return aServiceName;
+}
+
+SfxObjectShell* SfxObjectShell::CreateObjectByFactoryName( const OUString& rFact, SfxObjectCreateMode eMode )
+{
+ return CreateObject( GetServiceNameFromFactory( rFact ), eMode );
+}
+
+
+SfxObjectShell* SfxObjectShell::CreateObject( const OUString& rServiceName, SfxObjectCreateMode eCreateMode )
+{
+ if ( !rServiceName.isEmpty() )
+ {
+ uno::Reference < frame::XModel > xDoc( ::comphelper::getProcessServiceFactory()->createInstance( rServiceName ), UNO_QUERY );
+ if (SfxObjectShell* pRet = SfxObjectShell::GetShellFromComponent(xDoc))
+ {
+ pRet->SetCreateMode_Impl(eCreateMode);
+ return pRet;
+ }
+ }
+
+ return nullptr;
+}
+
+Reference<lang::XComponent> SfxObjectShell::CreateAndLoadComponent( const SfxItemSet& rSet )
+{
+ uno::Sequence < beans::PropertyValue > aProps;
+ TransformItems( SID_OPENDOC, rSet, aProps );
+ const SfxStringItem* pFileNameItem = rSet.GetItem<SfxStringItem>(SID_FILE_NAME, false);
+ const SfxStringItem* pTargetItem = rSet.GetItem<SfxStringItem>(SID_TARGETNAME, false);
+ OUString aURL;
+ OUString aTarget("_blank");
+ if ( pFileNameItem )
+ aURL = pFileNameItem->GetValue();
+ if ( pTargetItem )
+ aTarget = pTargetItem->GetValue();
+
+ uno::Reference < frame::XComponentLoader > xLoader =
+ frame::Desktop::create(comphelper::getProcessComponentContext());
+
+ Reference <lang::XComponent> xComp;
+ try
+ {
+ xComp = xLoader->loadComponentFromURL(aURL, aTarget, 0, aProps);
+ }
+ catch (const uno::Exception&)
+ {
+ }
+
+ return xComp;
+}
+
+SfxObjectShell* SfxObjectShell::GetShellFromComponent(const Reference<uno::XInterface>& xComp)
+{
+ try
+ {
+ Reference<lang::XUnoTunnel> xTunnel(xComp, UNO_QUERY);
+ if (!xTunnel)
+ return nullptr;
+ static const Sequence <sal_Int8> aSeq( SvGlobalName( SFX_GLOBAL_CLASSID ).GetByteSequence() );
+ return comphelper::getSomething_cast<SfxObjectShell>(xTunnel->getSomething(aSeq));
+ }
+ catch (const Exception&)
+ {
+ }
+
+ return nullptr;
+}
+
+SfxObjectShell* SfxObjectShell::GetParentShell(const css::uno::Reference<css::uno::XInterface>& xChild)
+{
+ SfxObjectShell* pResult = nullptr;
+
+ try
+ {
+ if (css::uno::Reference<css::container::XChild> xChildModel{ xChild, css::uno::UNO_QUERY })
+ pResult = GetShellFromComponent(xChildModel->getParent());
+ }
+ catch (const Exception&)
+ {
+ }
+
+ return pResult;
+}
+
+void SfxObjectShell::SetInitialized_Impl( const bool i_fromInitNew )
+{
+ pImpl->bInitialized = true;
+ if (utl::ConfigManager::IsFuzzing())
+ return;
+ if ( i_fromInitNew )
+ {
+ SetActivateEvent_Impl( SfxEventHintId::CreateDoc );
+ SfxGetpApp()->NotifyEvent( SfxEventHint( SfxEventHintId::DocCreated, GlobalEventConfig::GetEventName(GlobalEventId::DOCCREATED), this ) );
+ }
+ else
+ {
+ SfxGetpApp()->NotifyEvent( SfxEventHint( SfxEventHintId::LoadFinished, GlobalEventConfig::GetEventName(GlobalEventId::LOADFINISHED), this ) );
+ }
+}
+
+
+bool SfxObjectShell::IsChangeRecording() const
+{
+ // currently this function needs to be overwritten by Writer and Calc only
+ SAL_WARN( "sfx.doc", "function not implemented" );
+ return false;
+}
+
+
+bool SfxObjectShell::HasChangeRecordProtection() const
+{
+ // currently this function needs to be overwritten by Writer and Calc only
+ SAL_WARN( "sfx.doc", "function not implemented" );
+ return false;
+}
+
+
+void SfxObjectShell::SetChangeRecording( bool /*bActivate*/, bool /*bLockAllViews*/ )
+{
+ // currently this function needs to be overwritten by Writer and Calc only
+ SAL_WARN( "sfx.doc", "function not implemented" );
+}
+
+
+void SfxObjectShell::SetProtectionPassword( const OUString & /*rPassword*/ )
+{
+ // currently this function needs to be overwritten by Writer and Calc only
+ SAL_WARN( "sfx.doc", "function not implemented" );
+}
+
+
+bool SfxObjectShell::GetProtectionHash( /*out*/ css::uno::Sequence< sal_Int8 > & /*rPasswordHash*/ )
+{
+ // currently this function needs to be overwritten by Writer and Calc only
+ SAL_WARN( "sfx.doc", "function not implemented" );
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/oleprops.cxx b/sfx2/source/doc/oleprops.cxx
new file mode 100644
index 0000000000..4cde3ed014
--- /dev/null
+++ b/sfx2/source/doc/oleprops.cxx
@@ -0,0 +1,1241 @@
+/* -*- 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 "oleprops.hxx"
+
+#include <comphelper/types.hxx>
+#include <o3tl/safeint.hxx>
+#include <tools/datetime.hxx>
+#include <rtl/tencinfo.h>
+#include <sal/log.hxx>
+#include <utility>
+
+
+#define STREAM_BUFFER_SIZE 2048
+
+// usings
+using ::com::sun::star::uno::Any;
+
+using namespace ::com::sun::star;
+
+#define TIMESTAMP_INVALID_DATETIME ( DateTime ( Date ( 1, 1, 1601 ), tools::Time ( 0, 0, 0 ) ) ) /// Invalid value for date and time to create invalid instance of TimeStamp.
+/// Invalid value for date and time to create invalid instance of TimeStamp.
+#define TIMESTAMP_INVALID_UTILDATETIME (util::DateTime(0, 0, 0, 0, 1, 1, 1601, false))
+/// Invalid value for date to create invalid instance of TimeStamp.
+#define TIMESTAMP_INVALID_UTILDATE (util::Date(1, 1, 1601))
+
+namespace {
+
+/** Property representing a signed 32-bit integer value. */
+class SfxOleInt32Property : public SfxOlePropertyBase
+{
+public:
+ explicit SfxOleInt32Property( sal_Int32 nPropId, sal_Int32 nValue = 0 );
+
+ sal_Int32 GetValue() const { return mnValue; }
+
+private:
+ virtual void ImplLoad( SvStream& rStrm ) override;
+ virtual void ImplSave( SvStream& rStrm ) override;
+
+private:
+ sal_Int32 mnValue;
+};
+
+
+/** Property representing a floating-point value. */
+class SfxOleDoubleProperty : public SfxOlePropertyBase
+{
+public:
+ explicit SfxOleDoubleProperty( sal_Int32 nPropId, double fValue = 0.0 );
+
+ double GetValue() const { return mfValue; }
+
+private:
+ virtual void ImplLoad( SvStream& rStrm ) override;
+ virtual void ImplSave( SvStream& rStrm ) override;
+
+private:
+ double mfValue;
+};
+
+
+/** Property representing a boolean value. */
+class SfxOleBoolProperty : public SfxOlePropertyBase
+{
+public:
+ explicit SfxOleBoolProperty( sal_Int32 nPropId, bool bValue = false );
+
+ bool GetValue() const { return mbValue; }
+
+private:
+ virtual void ImplLoad( SvStream& rStrm ) override;
+ virtual void ImplSave( SvStream& rStrm ) override;
+
+private:
+ bool mbValue;
+};
+
+
+/** Base class for properties that contain a single string value. */
+class SfxOleStringPropertyBase : public SfxOlePropertyBase, public SfxOleStringHelper
+{
+public:
+ explicit SfxOleStringPropertyBase(
+ sal_Int32 nPropId, sal_Int32 nPropType,
+ const SfxOleTextEncoding& rTextEnc );
+ explicit SfxOleStringPropertyBase(
+ sal_Int32 nPropId, sal_Int32 nPropType,
+ const SfxOleTextEncoding& rTextEnc, OUString aValue );
+ explicit SfxOleStringPropertyBase(
+ sal_Int32 nPropId, sal_Int32 nPropType,
+ rtl_TextEncoding eTextEnc );
+
+ const OUString& GetValue() const { return maValue; }
+ void SetValue( const OUString& rValue ) { maValue = rValue; }
+
+private:
+ OUString maValue;
+};
+
+
+/** Property representing a bytestring value. */
+class SfxOleString8Property : public SfxOleStringPropertyBase
+{
+public:
+ explicit SfxOleString8Property(
+ sal_Int32 nPropId, const SfxOleTextEncoding& rTextEnc );
+ explicit SfxOleString8Property(
+ sal_Int32 nPropId, const SfxOleTextEncoding& rTextEnc,
+ const OUString& rValue );
+
+private:
+ virtual void ImplLoad( SvStream& rStrm ) override;
+ virtual void ImplSave( SvStream& rStrm ) override;
+};
+
+
+/** Property representing a Unicode string value. */
+class SfxOleString16Property : public SfxOleStringPropertyBase
+{
+public:
+ explicit SfxOleString16Property( sal_Int32 nPropId );
+
+private:
+ virtual void ImplLoad( SvStream& rStrm ) override;
+ virtual void ImplSave( SvStream& rStrm ) override;
+};
+
+
+/** Property representing a filetime value as defined by the Windows API. */
+class SfxOleFileTimeProperty : public SfxOlePropertyBase
+{
+public:
+ explicit SfxOleFileTimeProperty( sal_Int32 nPropId );
+ /** @param rDateTime Date and time as LOCAL time. */
+ explicit SfxOleFileTimeProperty( sal_Int32 nPropId, const util::DateTime& rDateTime );
+
+ /** Returns the time value as LOCAL time. */
+ const util::DateTime& GetValue() const { return maDateTime; }
+
+private:
+ virtual void ImplLoad( SvStream& rStrm ) override;
+ virtual void ImplSave( SvStream& rStrm ) override;
+
+private:
+ util::DateTime maDateTime;
+};
+
+/** Property representing a filetime value as defined by the Windows API. */
+class SfxOleDateProperty : public SfxOlePropertyBase
+{
+public:
+ explicit SfxOleDateProperty( sal_Int32 nPropId );
+
+ /** Returns the date value as LOCAL time. */
+ const util::Date& GetValue() const { return maDate; }
+
+private:
+ virtual void ImplLoad( SvStream& rStrm ) override;
+ virtual void ImplSave( SvStream& rStrm ) override;
+
+private:
+ util::Date maDate;
+};
+
+
+/** Property representing a thumbnail picture.
+
+ Currently, only saving this property is implemented.
+ */
+class SfxOleThumbnailProperty : public SfxOlePropertyBase
+{
+public:
+ explicit SfxOleThumbnailProperty( sal_Int32 nPropId,
+ const uno::Sequence<sal_Int8> & i_rData);
+
+ bool IsValid() const { return mData.hasElements(); }
+
+private:
+ virtual void ImplLoad( SvStream& rStrm ) override;
+ virtual void ImplSave( SvStream& rStrm ) override;
+
+private:
+ uno::Sequence<sal_Int8> mData;
+};
+
+
+/** Property representing a BLOB (which presumably stands for binary large
+ object).
+
+ Currently, only saving this property is implemented.
+ */
+class SfxOleBlobProperty : public SfxOlePropertyBase
+{
+public:
+ explicit SfxOleBlobProperty( sal_Int32 nPropId,
+ const uno::Sequence<sal_Int8> & i_rData);
+ bool IsValid() const { return mData.hasElements(); }
+
+private:
+ virtual void ImplLoad( SvStream& rStrm ) override;
+ virtual void ImplSave( SvStream& rStrm ) override;
+
+private:
+ uno::Sequence<sal_Int8> mData;
+};
+
+}
+
+sal_uInt16 SfxOleTextEncoding::GetCodePage() const
+{
+ sal_uInt16 nCodePage = IsUnicode() ? CODEPAGE_UNICODE :
+ static_cast< sal_uInt16 >( rtl_getWindowsCodePageFromTextEncoding( *mxTextEnc ) );
+ return (nCodePage == CODEPAGE_UNKNOWN) ? CODEPAGE_UTF8 : nCodePage;
+}
+
+void SfxOleTextEncoding::SetCodePage( sal_uInt16 nCodePage )
+{
+ if( nCodePage == CODEPAGE_UNICODE )
+ SetUnicode();
+ else
+ {
+ rtl_TextEncoding eTextEnc = rtl_getTextEncodingFromWindowsCodePage( nCodePage );
+ if( eTextEnc != RTL_TEXTENCODING_DONTKNOW )
+ *mxTextEnc = eTextEnc;
+ }
+}
+
+
+OUString SfxOleStringHelper::LoadString8( SvStream& rStrm ) const
+{
+ return IsUnicode() ? ImplLoadString16( rStrm ) : ImplLoadString8( rStrm );
+}
+
+void SfxOleStringHelper::SaveString8( SvStream& rStrm, std::u16string_view rValue ) const
+{
+ if( IsUnicode() )
+ ImplSaveString16( rStrm, rValue );
+ else
+ ImplSaveString8( rStrm, rValue );
+}
+
+OUString SfxOleStringHelper::LoadString16( SvStream& rStrm )
+{
+ return ImplLoadString16( rStrm );
+}
+
+void SfxOleStringHelper::SaveString16( SvStream& rStrm, std::u16string_view rValue )
+{
+ ImplSaveString16( rStrm, rValue );
+}
+
+OUString SfxOleStringHelper::ImplLoadString8( SvStream& rStrm ) const
+{
+ // read size field (signed 32-bit)
+ sal_Int32 nSize(0);
+ rStrm.ReadInt32( nSize );
+ // size field includes trailing NUL character
+ SAL_WARN_IF(nSize < 1 || nSize > 0xFFFF, "sfx.doc", "SfxOleStringHelper::ImplLoadString8 - invalid string of len " << nSize);
+ if (nSize < 1 || nSize > 0xFFFF)
+ return OUString();
+ // load character buffer
+ OString sValue(read_uInt8s_ToOString(rStrm, nSize - 1));
+ if (rStrm.good() && rStrm.remainingSize())
+ rStrm.SeekRel(1); // skip null-byte at end
+ return OStringToOUString(sValue, GetTextEncoding());
+}
+
+OUString SfxOleStringHelper::ImplLoadString16( SvStream& rStrm )
+{
+ // read size field (signed 32-bit), may be buffer size or character count
+ sal_Int32 nSize(0);
+ rStrm.ReadInt32(nSize);
+ SAL_WARN_IF(nSize < 1 || nSize > 0xFFFF, "sfx.doc", "SfxOleStringHelper::ImplLoadString16 - invalid string of len " << nSize);
+ // size field includes trailing NUL character
+ if (nSize < 1 || nSize > 0xFFFF)
+ return OUString();
+ // load character buffer
+ OUString aValue = read_uInt16s_ToOUString(rStrm, nSize - 1);
+ sal_Int32 nSkip(2); // skip null-byte at end
+ // stream is always padded to 32-bit boundary, skip 2 bytes on odd character count
+ if ((nSize & 1) == 1)
+ nSkip += 2;
+ nSkip = std::min<sal_uInt32>(nSkip, rStrm.remainingSize());
+ if (rStrm.good() && nSkip)
+ rStrm.SeekRel(nSkip);
+ return aValue;
+}
+
+void SfxOleStringHelper::ImplSaveString8( SvStream& rStrm, std::u16string_view rValue ) const
+{
+ // encode to byte string
+ OString aEncoded(OUStringToOString(rValue, GetTextEncoding()));
+ // write size field (including trailing NUL character)
+ sal_Int32 nSize = aEncoded.getLength() + 1;
+ rStrm.WriteInt32( nSize );
+ // write character array with trailing NUL character
+ rStrm.WriteBytes(aEncoded.getStr(), aEncoded.getLength());
+ rStrm.WriteUChar( 0 );
+}
+
+void SfxOleStringHelper::ImplSaveString16( SvStream& rStrm, std::u16string_view rValue )
+{
+ // write size field (including trailing NUL character)
+ sal_Int32 nSize = static_cast< sal_Int32 >( rValue.size() + 1 );
+ rStrm.WriteInt32( nSize );
+ // write character array with trailing NUL character
+ for( size_t nIdx = 0; nIdx < rValue.size(); ++nIdx )
+ rStrm.WriteUInt16( rValue[ nIdx ] );
+ rStrm.WriteUInt16( 0 );
+ // stream is always padded to 32-bit boundary, add 2 bytes on odd character count
+ if( (nSize & 1) == 1 )
+ rStrm.WriteUInt16( 0 );
+}
+
+
+SfxOleObjectBase::~SfxOleObjectBase()
+{
+}
+
+ErrCode const & SfxOleObjectBase::Load( SvStream& rStrm )
+{
+ mnErrCode = ERRCODE_NONE;
+ ImplLoad( rStrm );
+ SetError( rStrm.GetErrorCode() );
+ return GetError();
+}
+
+ErrCode const & SfxOleObjectBase::Save( SvStream& rStrm )
+{
+ mnErrCode = ERRCODE_NONE;
+ ImplSave( rStrm );
+ SetError( rStrm.GetErrorCode() );
+ return GetError();
+}
+
+void SfxOleObjectBase::LoadObject( SvStream& rStrm, SfxOleObjectBase& rObj )
+{
+ SetError( rObj.Load( rStrm ) );
+}
+
+void SfxOleObjectBase::SaveObject( SvStream& rStrm, SfxOleObjectBase& rObj )
+{
+ SetError( rObj.Save( rStrm ) );
+}
+
+
+SfxOleCodePageProperty::SfxOleCodePageProperty() :
+ SfxOlePropertyBase( PROPID_CODEPAGE, PROPTYPE_INT16 )
+{
+}
+
+void SfxOleCodePageProperty::ImplLoad(SvStream& rStrm)
+{
+ // property type is signed int16, but we use always unsigned int16 for codepages
+ sal_uInt16 nCodePage(0);
+ rStrm.ReadUInt16(nCodePage);
+ SetCodePage(nCodePage);
+}
+
+void SfxOleCodePageProperty::ImplSave( SvStream& rStrm )
+{
+ // property type is signed int16, but we use always unsigned int16 for codepages
+ rStrm.WriteUInt16( GetCodePage() );
+}
+
+
+SfxOleInt32Property::SfxOleInt32Property( sal_Int32 nPropId, sal_Int32 nValue ) :
+ SfxOlePropertyBase( nPropId, PROPTYPE_INT32 ),
+ mnValue( nValue )
+{
+}
+
+void SfxOleInt32Property::ImplLoad( SvStream& rStrm )
+{
+ rStrm.ReadInt32( mnValue );
+}
+
+void SfxOleInt32Property::ImplSave( SvStream& rStrm )
+{
+ rStrm.WriteInt32( mnValue );
+}
+
+
+SfxOleDoubleProperty::SfxOleDoubleProperty( sal_Int32 nPropId, double fValue ) :
+ SfxOlePropertyBase( nPropId, PROPTYPE_DOUBLE ),
+ mfValue( fValue )
+{
+}
+
+void SfxOleDoubleProperty::ImplLoad( SvStream& rStrm )
+{
+ rStrm.ReadDouble( mfValue );
+}
+
+void SfxOleDoubleProperty::ImplSave( SvStream& rStrm )
+{
+ rStrm.WriteDouble( mfValue );
+}
+
+
+SfxOleBoolProperty::SfxOleBoolProperty( sal_Int32 nPropId, bool bValue ) :
+ SfxOlePropertyBase( nPropId, PROPTYPE_BOOL ),
+ mbValue( bValue )
+{
+}
+
+void SfxOleBoolProperty::ImplLoad( SvStream& rStrm )
+{
+ sal_Int16 nValue(0);
+ rStrm.ReadInt16( nValue );
+ mbValue = nValue != 0;
+}
+
+void SfxOleBoolProperty::ImplSave( SvStream& rStrm )
+{
+ rStrm.WriteInt16( mbValue ? -1 : 0 );
+}
+
+
+SfxOleStringPropertyBase::SfxOleStringPropertyBase(
+ sal_Int32 nPropId, sal_Int32 nPropType, const SfxOleTextEncoding& rTextEnc ) :
+ SfxOlePropertyBase( nPropId, nPropType ),
+ SfxOleStringHelper( rTextEnc )
+{
+}
+
+SfxOleStringPropertyBase::SfxOleStringPropertyBase(
+ sal_Int32 nPropId, sal_Int32 nPropType, const SfxOleTextEncoding& rTextEnc, OUString aValue ) :
+ SfxOlePropertyBase( nPropId, nPropType ),
+ SfxOleStringHelper( rTextEnc ),
+ maValue(std::move( aValue ))
+{
+}
+
+SfxOleStringPropertyBase::SfxOleStringPropertyBase(
+ sal_Int32 nPropId, sal_Int32 nPropType, rtl_TextEncoding eTextEnc ) :
+ SfxOlePropertyBase( nPropId, nPropType ),
+ SfxOleStringHelper( eTextEnc )
+{
+}
+
+
+SfxOleString8Property::SfxOleString8Property(
+ sal_Int32 nPropId, const SfxOleTextEncoding& rTextEnc ) :
+ SfxOleStringPropertyBase( nPropId, PROPTYPE_STRING8, rTextEnc )
+{
+}
+
+SfxOleString8Property::SfxOleString8Property(
+ sal_Int32 nPropId, const SfxOleTextEncoding& rTextEnc, const OUString& rValue ) :
+ SfxOleStringPropertyBase( nPropId, PROPTYPE_STRING8, rTextEnc, rValue )
+{
+}
+
+void SfxOleString8Property::ImplLoad( SvStream& rStrm )
+{
+ SetValue( LoadString8( rStrm ) );
+}
+
+void SfxOleString8Property::ImplSave( SvStream& rStrm )
+{
+ SaveString8( rStrm, GetValue() );
+}
+
+
+SfxOleString16Property::SfxOleString16Property( sal_Int32 nPropId ) :
+ SfxOleStringPropertyBase( nPropId, PROPTYPE_STRING16, RTL_TEXTENCODING_UCS2 )
+{
+}
+
+void SfxOleString16Property::ImplLoad( SvStream& rStrm )
+{
+ SetValue( LoadString16( rStrm ) );
+}
+
+void SfxOleString16Property::ImplSave( SvStream& rStrm )
+{
+ SaveString16( rStrm, GetValue() );
+}
+
+
+SfxOleFileTimeProperty::SfxOleFileTimeProperty( sal_Int32 nPropId ) :
+ SfxOlePropertyBase( nPropId, PROPTYPE_FILETIME )
+{
+}
+
+SfxOleFileTimeProperty::SfxOleFileTimeProperty( sal_Int32 nPropId, const util::DateTime& rDateTime ) :
+ SfxOlePropertyBase( nPropId, PROPTYPE_FILETIME ),
+ maDateTime( rDateTime )
+{
+}
+
+void SfxOleFileTimeProperty::ImplLoad( SvStream& rStrm )
+{
+ sal_uInt32 nLower(0), nUpper(0);
+ rStrm.ReadUInt32( nLower ).ReadUInt32( nUpper );
+ ::DateTime aDateTime = DateTime::CreateFromWin32FileDateTime( nLower, nUpper );
+ // note: editing duration is stored as offset to TIMESTAMP_INVALID_DATETIME
+ // of course we should not convert the time zone of a duration!
+ // heuristic to detect editing durations (which we assume to be < 1 year):
+ // check only the year, not the entire date
+ if ( aDateTime.GetYear() != TIMESTAMP_INVALID_DATETIME.GetYear() )
+ aDateTime.ConvertToLocalTime();
+ maDateTime.Year = aDateTime.GetYear();
+ maDateTime.Month = aDateTime.GetMonth();
+ maDateTime.Day = aDateTime.GetDay();
+ maDateTime.Hours = aDateTime.GetHour();
+ maDateTime.Minutes = aDateTime.GetMin();
+ maDateTime.Seconds = aDateTime.GetSec();
+ maDateTime.NanoSeconds = aDateTime.GetNanoSec();
+ maDateTime.IsUTC = false;
+}
+
+void SfxOleFileTimeProperty::ImplSave( SvStream& rStrm )
+{
+ DateTime aDateTimeUtc(
+ Date(
+ maDateTime.Day,
+ maDateTime.Month,
+ static_cast< sal_uInt16 >( maDateTime.Year ) ),
+ tools::Time(
+ maDateTime.Hours,
+ maDateTime.Minutes,
+ maDateTime.Seconds,
+ maDateTime.NanoSeconds ) );
+ // invalid time stamp is not converted to UTC
+ // heuristic to detect editing durations (which we assume to be < 1 year):
+ // check only the year, not the entire date
+ if( aDateTimeUtc.IsValidAndGregorian()
+ && aDateTimeUtc.GetYear() != TIMESTAMP_INVALID_DATETIME.GetYear() ) {
+ aDateTimeUtc.ConvertToUTC();
+ }
+ sal_uInt32 nLower, nUpper;
+ aDateTimeUtc.GetWin32FileDateTime( nLower, nUpper );
+ rStrm.WriteUInt32( nLower ).WriteUInt32( nUpper );
+}
+
+SfxOleDateProperty::SfxOleDateProperty( sal_Int32 nPropId ) :
+ SfxOlePropertyBase( nPropId, PROPTYPE_DATE )
+{
+}
+
+void SfxOleDateProperty::ImplLoad( SvStream& rStrm )
+{
+ double fValue(0.0);
+ rStrm.ReadDouble( fValue );
+ //stored as number of days (not seconds) since December 31, 1899
+ sal_Int32 nDays = fValue;
+ sal_Int32 nStartDays = ::Date::DateToDays(31, 12, 1899);
+ if (o3tl::checked_add(nStartDays, nDays, nStartDays))
+ SAL_WARN("sfx.doc", "SfxOleDateProperty::ImplLoad bad date, ignored");
+ else
+ {
+ ::Date aDate(31, 12, 1899);
+ aDate.AddDays(nDays);
+ maDate.Day = aDate.GetDay();
+ maDate.Month = aDate.GetMonth();
+ maDate.Year = aDate.GetYear();
+ }
+}
+
+void SfxOleDateProperty::ImplSave( SvStream& rStrm )
+{
+ sal_Int32 nDays = ::Date::DateToDays(maDate.Day, maDate.Month, maDate.Year);
+ //number of days (not seconds) since December 31, 1899
+ sal_Int32 nStartDays = ::Date::DateToDays(31, 12, 1899);
+ double fValue = nDays-nStartDays;
+ rStrm.WriteDouble( fValue );
+}
+
+
+SfxOleThumbnailProperty::SfxOleThumbnailProperty(
+ sal_Int32 nPropId, const uno::Sequence<sal_Int8> & i_rData) :
+ SfxOlePropertyBase( nPropId, PROPTYPE_CLIPFMT ),
+ mData(i_rData)
+{
+}
+
+void SfxOleThumbnailProperty::ImplLoad( SvStream& )
+{
+ SAL_WARN( "sfx.doc", "SfxOleThumbnailProperty::ImplLoad - not implemented" );
+ SetError( SVSTREAM_INVALID_ACCESS );
+}
+
+void SfxOleThumbnailProperty::ImplSave( SvStream& rStrm )
+{
+ /* Type Contents
+ -----------------------------------------------------------------------
+ int32 size of following data
+ int32 clipboard format tag (see below)
+ byte[] clipboard data (see below)
+
+ Clipboard format tag:
+ -1 = Windows clipboard format
+ -2 = Macintosh clipboard format
+ -3 = GUID that contains a format identifier (FMTID)
+ >0 = custom clipboard format name plus data (see msdn site below)
+ 0 = no data
+
+ References:
+ http://msdn.microsoft.com/library/default.asp?url=/library/en-us/stg/stg/propvariant.asp
+ http://jakarta.apache.org/poi/hpsf/thumbnails.html
+ http://linux.com.hk/docs/poi/org/apache/poi/hpsf/Thumbnail.html
+ https://web.archive.org/web/20060126202945/http://sparks.discreet.com/knowledgebase/public/solutions/ExtractThumbnailImg.htm
+ */
+ if( IsValid() )
+ {
+ // clipboard size: clip_format_tag + data_format_tag + bitmap_len
+ sal_Int32 nClipSize = static_cast< sal_Int32 >( 4 + 4 + mData.getLength() );
+ rStrm.WriteInt32( nClipSize ).WriteInt32( CLIPFMT_WIN ).WriteInt32( CLIPDATAFMT_DIB );
+ rStrm.WriteBytes(mData.getConstArray(), mData.getLength());
+ }
+ else
+ {
+ SAL_WARN( "sfx.doc", "SfxOleThumbnailProperty::ImplSave - invalid thumbnail property" );
+ SetError( SVSTREAM_INVALID_ACCESS );
+ }
+}
+
+
+SfxOleBlobProperty::SfxOleBlobProperty( sal_Int32 nPropId,
+ const uno::Sequence<sal_Int8> & i_rData) :
+ SfxOlePropertyBase( nPropId, PROPTYPE_BLOB ),
+ mData(i_rData)
+{
+}
+
+void SfxOleBlobProperty::ImplLoad( SvStream& )
+{
+ SAL_WARN( "sfx.doc", "SfxOleBlobProperty::ImplLoad - not implemented" );
+ SetError( SVSTREAM_INVALID_ACCESS );
+}
+
+void SfxOleBlobProperty::ImplSave( SvStream& rStrm )
+{
+ if (IsValid()) {
+ rStrm.WriteBytes(mData.getConstArray(), mData.getLength());
+ } else {
+ SAL_WARN( "sfx.doc", "SfxOleBlobProperty::ImplSave - invalid BLOB property" );
+ SetError( SVSTREAM_INVALID_ACCESS );
+ }
+}
+
+
+SfxOleDictionaryProperty::SfxOleDictionaryProperty( const SfxOleTextEncoding& rTextEnc ) :
+ SfxOlePropertyBase( PROPID_DICTIONARY, 0 ),
+ SfxOleStringHelper( rTextEnc )
+{
+}
+
+OUString SfxOleDictionaryProperty::GetPropertyName( sal_Int32 nPropId ) const
+{
+ SfxOlePropNameMap::const_iterator aIt = maPropNameMap.find( nPropId );
+ return (aIt == maPropNameMap.end()) ? OUString() : aIt->second;
+}
+
+void SfxOleDictionaryProperty::SetPropertyName( sal_Int32 nPropId, const OUString& rPropName )
+{
+ maPropNameMap[ nPropId ] = rPropName;
+ // dictionary property contains number of pairs in property type field
+ SetPropType( static_cast< sal_Int32 >( maPropNameMap.size() ) );
+}
+
+void SfxOleDictionaryProperty::ImplLoad( SvStream& rStrm )
+{
+ // dictionary property contains number of pairs in property type field
+ sal_Int32 nNameCount = GetPropType();
+ // read property ID/name pairs
+ maPropNameMap.clear();
+ for (sal_Int32 nIdx = 0; nIdx < nNameCount && rStrm.good() && rStrm.remainingSize() >= 4; ++nIdx)
+ {
+ sal_Int32 nPropId(0);
+ rStrm.ReadInt32(nPropId);
+ // name always stored as byte string
+ maPropNameMap[nPropId] = LoadString8(rStrm);
+ }
+}
+
+void SfxOleDictionaryProperty::ImplSave( SvStream& rStrm )
+{
+ // write property ID/name pairs
+ for (auto const& propName : maPropNameMap)
+ {
+ rStrm.WriteInt32( propName.first );
+ // name always stored as byte string
+ SaveString8( rStrm, propName.second );
+ }
+}
+
+
+SfxOleSection::SfxOleSection( bool bSupportsDict ) :
+ maDictProp( maCodePageProp ),
+ mnStartPos( 0 ),
+ mbSupportsDict( bSupportsDict )
+{
+}
+
+SfxOlePropertyRef SfxOleSection::GetProperty( sal_Int32 nPropId ) const
+{
+ SfxOlePropertyRef xProp;
+ SfxOlePropMap::const_iterator aIt = maPropMap.find( nPropId );
+ if( aIt != maPropMap.end() )
+ xProp = aIt->second;
+ return xProp;
+}
+
+bool SfxOleSection::GetInt32Value( sal_Int32& rnValue, sal_Int32 nPropId ) const
+{
+ SfxOlePropertyRef xProp = GetProperty( nPropId );
+ const SfxOleInt32Property* pProp =
+ dynamic_cast< const SfxOleInt32Property* >( xProp.get() );
+ if( pProp )
+ rnValue = pProp->GetValue();
+ return pProp != nullptr;
+}
+
+bool SfxOleSection::GetDoubleValue( double& rfValue, sal_Int32 nPropId ) const
+{
+ SfxOlePropertyRef xProp = GetProperty( nPropId );
+ const SfxOleDoubleProperty* pProp =
+ dynamic_cast< const SfxOleDoubleProperty* >( xProp.get() );
+ if( pProp )
+ rfValue = pProp->GetValue();
+ return pProp != nullptr;
+}
+
+bool SfxOleSection::GetBoolValue( bool& rbValue, sal_Int32 nPropId ) const
+{
+ SfxOlePropertyRef xProp = GetProperty( nPropId );
+ const SfxOleBoolProperty* pProp =
+ dynamic_cast< const SfxOleBoolProperty* >( xProp.get() );
+ if( pProp )
+ rbValue = pProp->GetValue();
+ return pProp != nullptr;
+}
+
+bool SfxOleSection::GetStringValue( OUString& rValue, sal_Int32 nPropId ) const
+{
+ SfxOlePropertyRef xProp = GetProperty( nPropId );
+ const SfxOleStringPropertyBase* pProp =
+ dynamic_cast< const SfxOleStringPropertyBase* >( xProp.get() );
+ if( pProp )
+ rValue = pProp->GetValue();
+ return pProp != nullptr;
+}
+
+bool SfxOleSection::GetFileTimeValue( util::DateTime& rValue, sal_Int32 nPropId ) const
+{
+ SfxOlePropertyRef xProp = GetProperty( nPropId );
+ const SfxOleFileTimeProperty* pProp =
+ dynamic_cast< const SfxOleFileTimeProperty* >( xProp.get() );
+ if( pProp )
+ {
+ if ( pProp->GetValue() == TIMESTAMP_INVALID_UTILDATETIME )
+ rValue = util::DateTime();
+ else
+ rValue = pProp->GetValue();
+ }
+ return pProp != nullptr;
+}
+
+bool SfxOleSection::GetDateValue( util::Date& rValue, sal_Int32 nPropId ) const
+{
+ SfxOlePropertyRef xProp = GetProperty( nPropId );
+ const SfxOleDateProperty* pProp =
+ dynamic_cast< const SfxOleDateProperty* >( xProp.get() );
+ if( pProp )
+ {
+ if ( pProp->GetValue() == TIMESTAMP_INVALID_UTILDATE )
+ rValue = util::Date();
+ else
+ rValue = pProp->GetValue();
+ }
+ return pProp != nullptr;
+}
+
+void SfxOleSection::SetProperty( const SfxOlePropertyRef& xProp )
+{
+ if( xProp )
+ maPropMap[ xProp->GetPropId() ] = xProp;
+}
+
+void SfxOleSection::SetInt32Value( sal_Int32 nPropId, sal_Int32 nValue )
+{
+ SetProperty( std::make_shared<SfxOleInt32Property>( nPropId, nValue ) );
+}
+
+void SfxOleSection::SetDoubleValue( sal_Int32 nPropId, double fValue )
+{
+ SetProperty( std::make_shared<SfxOleDoubleProperty>( nPropId, fValue ) );
+}
+
+void SfxOleSection::SetBoolValue( sal_Int32 nPropId, bool bValue )
+{
+ SetProperty( std::make_shared<SfxOleBoolProperty>( nPropId, bValue ) );
+}
+
+bool SfxOleSection::SetStringValue( sal_Int32 nPropId, const OUString& rValue )
+{
+ bool bInserted = !rValue.isEmpty();
+ if( bInserted )
+ SetProperty( std::make_shared<SfxOleString8Property>( nPropId, maCodePageProp, rValue ) );
+ return bInserted;
+}
+
+void SfxOleSection::SetFileTimeValue( sal_Int32 nPropId, const util::DateTime& rValue )
+{
+ if ( rValue.Year == 0 || rValue.Month == 0 || rValue.Day == 0 )
+ SetProperty( std::make_shared<SfxOleFileTimeProperty>( nPropId, TIMESTAMP_INVALID_UTILDATETIME ) );
+ else
+ SetProperty( std::make_shared<SfxOleFileTimeProperty>( nPropId, rValue ) );
+}
+
+void SfxOleSection::SetDateValue( sal_Int32 nPropId, const util::Date& rValue )
+{
+ //Annoyingly MS2010 considers VT_DATE apparently as an invalid possibility, so here we use VT_FILETIME
+ //instead :-(
+ if ( rValue.Year == 0 || rValue.Month == 0 || rValue.Day == 0 )
+ SetProperty( std::make_shared<SfxOleFileTimeProperty>( nPropId, TIMESTAMP_INVALID_UTILDATETIME ) );
+ else
+ {
+ const util::DateTime aValue(0, 0, 0, 0, rValue.Day, rValue.Month,
+ rValue.Year, false );
+ SetProperty( std::make_shared<SfxOleFileTimeProperty>( nPropId, aValue ) );
+ }
+}
+
+void SfxOleSection::SetThumbnailValue( sal_Int32 nPropId,
+ const uno::Sequence<sal_Int8> & i_rData)
+{
+ auto pThumbnail = std::make_shared<SfxOleThumbnailProperty>( nPropId, i_rData );
+ if( pThumbnail->IsValid() )
+ SetProperty( pThumbnail );
+}
+
+void SfxOleSection::SetBlobValue( sal_Int32 nPropId,
+ const uno::Sequence<sal_Int8> & i_rData)
+{
+ auto pBlob = std::make_shared<SfxOleBlobProperty>( nPropId, i_rData );
+ if( pBlob->IsValid() )
+ SetProperty( pBlob );
+}
+
+Any SfxOleSection::GetAnyValue( sal_Int32 nPropId ) const
+{
+ Any aValue;
+ sal_Int32 nInt32 = 0;
+ double fDouble = 0.0;
+ bool bBool = false;
+ OUString aString;
+ css::util::DateTime aApiDateTime;
+ css::util::Date aApiDate;
+
+ if( GetInt32Value( nInt32, nPropId ) )
+ aValue <<= nInt32;
+ else if( GetDoubleValue( fDouble, nPropId ) )
+ aValue <<= fDouble;
+ else if( GetBoolValue( bBool, nPropId ) )
+ aValue <<= bBool;
+ else if( GetStringValue( aString, nPropId ) )
+ aValue <<= aString;
+ else if( GetFileTimeValue( aApiDateTime, nPropId ) )
+ {
+ aValue <<= aApiDateTime;
+ }
+ else if( GetDateValue( aApiDate, nPropId ) )
+ {
+ aValue <<= aApiDate;
+ }
+ return aValue;
+}
+
+bool SfxOleSection::SetAnyValue( sal_Int32 nPropId, const Any& rValue )
+{
+ bool bInserted = true;
+ sal_Int32 nInt32 = 0;
+ double fDouble = 0.0;
+ OUString aString;
+ css::util::DateTime aApiDateTime;
+ css::util::Date aApiDate;
+
+ if( rValue.getValueType() == cppu::UnoType<bool>::get() )
+ SetBoolValue( nPropId, ::comphelper::getBOOL( rValue ) );
+ else if( rValue >>= nInt32 )
+ SetInt32Value( nPropId, nInt32 );
+ else if( rValue >>= fDouble )
+ SetDoubleValue( nPropId, fDouble );
+ else if( rValue >>= aString )
+ bInserted = SetStringValue( nPropId, aString );
+ else if( rValue >>= aApiDateTime )
+ SetFileTimeValue( nPropId, aApiDateTime );
+ else if( rValue >>= aApiDate )
+ SetDateValue( nPropId, aApiDate );
+ else
+ bInserted = false;
+ return bInserted;
+}
+
+OUString SfxOleSection::GetPropertyName( sal_Int32 nPropId ) const
+{
+ return maDictProp.GetPropertyName( nPropId );
+}
+
+void SfxOleSection::SetPropertyName( sal_Int32 nPropId, const OUString& rPropName )
+{
+ maDictProp.SetPropertyName( nPropId, rPropName );
+}
+
+void SfxOleSection::GetPropertyIds( ::std::vector< sal_Int32 >& rPropIds ) const
+{
+ rPropIds.clear();
+ for (auto const& prop : maPropMap)
+ rPropIds.push_back(prop.first);
+}
+
+sal_Int32 SfxOleSection::GetFreePropertyId() const
+{
+ return maPropMap.empty() ? PROPID_FIRSTCUSTOM : (maPropMap.rbegin()->first + 1);
+}
+
+void SfxOleSection::ImplLoad( SvStream& rStrm )
+{
+ // read section header
+ mnStartPos = rStrm.Tell();
+ sal_uInt32 nSize(0);
+ sal_Int32 nPropCount(0);
+ rStrm.ReadUInt32( nSize ).ReadInt32( nPropCount );
+
+ // read property ID/position pairs
+ typedef ::std::map< sal_Int32, sal_uInt32 > SfxOlePropPosMap;
+ SfxOlePropPosMap aPropPosMap;
+ for (sal_Int32 nPropIdx = 0; nPropIdx < nPropCount && rStrm.good(); ++nPropIdx)
+ {
+ sal_Int32 nPropId(0);
+ sal_uInt32 nPropPos(0);
+ rStrm.ReadInt32( nPropId ).ReadUInt32( nPropPos );
+ aPropPosMap[ nPropId ] = nPropPos;
+ }
+
+ // read codepage property
+ SfxOlePropPosMap::iterator aCodePageIt = aPropPosMap.find( PROPID_CODEPAGE );
+ if( (aCodePageIt != aPropPosMap.end()) && SeekToPropertyPos( rStrm, aCodePageIt->second ) )
+ {
+ // codepage property must be of type signed int-16
+ sal_Int32 nPropType(0);
+ rStrm.ReadInt32( nPropType );
+ if( nPropType == PROPTYPE_INT16 )
+ LoadObject( rStrm, maCodePageProp );
+ // remove property position
+ aPropPosMap.erase( aCodePageIt );
+ }
+
+ // read dictionary property
+ SfxOlePropPosMap::iterator aDictIt = aPropPosMap.find( PROPID_DICTIONARY );
+ if( (aDictIt != aPropPosMap.end()) && SeekToPropertyPos( rStrm, aDictIt->second ) )
+ {
+ // #i66214# #i66428# applications may write broken dictionary properties in wrong sections
+ if( mbSupportsDict )
+ {
+ // dictionary property contains number of pairs in property type field
+ sal_Int32 nNameCount(0);
+ rStrm.ReadInt32( nNameCount );
+ maDictProp.SetNameCount( nNameCount );
+ LoadObject( rStrm, maDictProp );
+ }
+ // always remove position of dictionary property (do not try to read it again below)
+ aPropPosMap.erase( aDictIt );
+ }
+
+ // read other properties
+ maPropMap.clear();
+ for (auto const& propPos : aPropPosMap)
+ if( SeekToPropertyPos( rStrm, propPos.second ) )
+ LoadProperty( rStrm, propPos.first );
+}
+
+void SfxOleSection::ImplSave( SvStream& rStrm )
+{
+ /* Always export with UTF-8 encoding. All dependent properties (bytestring
+ and dictionary) will be updated automatically. */
+ maCodePageProp.SetTextEncoding( RTL_TEXTENCODING_UTF8 );
+
+ // write section header
+ mnStartPos = rStrm.Tell();
+ sal_Int32 nPropCount = static_cast< sal_Int32 >( maPropMap.size() + 1 );
+ if( maDictProp.HasPropertyNames() )
+ ++nPropCount;
+ rStrm.WriteUInt32( 0 ).WriteInt32( nPropCount );
+
+ // write placeholders for property ID/position pairs
+ sal_uInt64 nPropPosPos = rStrm.Tell();
+ rStrm.SeekRel( static_cast< sal_sSize >( 8 * nPropCount ) );
+
+ // write dictionary property
+ if( maDictProp.HasPropertyNames() )
+ SaveProperty( rStrm, maDictProp, nPropPosPos );
+ // write codepage property
+ SaveProperty( rStrm, maCodePageProp, nPropPosPos );
+ // write other properties
+ for (auto const& prop : maPropMap)
+ SaveProperty( rStrm, *prop.second, nPropPosPos );
+
+ // write section size (first field in section header)
+ sal_uInt32 nSectSize = static_cast< sal_uInt32 >( rStrm.TellEnd() - mnStartPos );
+ rStrm.Seek( mnStartPos );
+ rStrm.WriteUInt32( nSectSize );
+}
+
+bool SfxOleSection::SeekToPropertyPos( SvStream& rStrm, sal_uInt32 nPropPos ) const
+{
+ return checkSeek(rStrm, static_cast<std::size_t>(mnStartPos + nPropPos)) &&
+ rStrm.GetErrorCode() == ERRCODE_NONE;
+}
+
+void SfxOleSection::LoadProperty( SvStream& rStrm, sal_Int32 nPropId )
+{
+ // property data type
+ sal_Int32 nPropType(0);
+ rStrm.ReadInt32( nPropType );
+ // create empty property object
+ SfxOlePropertyRef xProp;
+ switch( nPropType )
+ {
+ case PROPTYPE_INT32:
+ xProp = std::make_shared<SfxOleInt32Property>( nPropId );
+ break;
+ case PROPTYPE_DOUBLE:
+ xProp = std::make_shared<SfxOleDoubleProperty>( nPropId );
+ break;
+ case PROPTYPE_BOOL:
+ xProp = std::make_shared<SfxOleBoolProperty>( nPropId );
+ break;
+ case PROPTYPE_STRING8:
+ xProp = std::make_shared<SfxOleString8Property>( nPropId, maCodePageProp );
+ break;
+ case PROPTYPE_STRING16:
+ xProp = std::make_shared<SfxOleString16Property>( nPropId );
+ break;
+ case PROPTYPE_FILETIME:
+ xProp = std::make_shared<SfxOleFileTimeProperty>( nPropId );
+ break;
+ case PROPTYPE_DATE:
+ xProp = std::make_shared<SfxOleDateProperty>( nPropId );
+ break;
+ }
+ // load property contents
+ if( xProp )
+ {
+ SetError( xProp->Load( rStrm ) );
+ maPropMap[ nPropId ] = xProp;
+ }
+}
+
+void SfxOleSection::SaveProperty( SvStream& rStrm, SfxOlePropertyBase& rProp, sal_uInt64 & rnPropPosPos )
+{
+ rStrm.Seek( STREAM_SEEK_TO_END );
+ sal_uInt32 nPropPos = static_cast< sal_uInt32 >( rStrm.Tell() - mnStartPos );
+ // property data type
+ rStrm.WriteInt32( rProp.GetPropType() );
+ // write property contents
+ SaveObject( rStrm, rProp );
+ // align to 32-bit
+ while( (rStrm.Tell() & 3) != 0 )
+ rStrm.WriteUChar( 0 );
+ // write property ID/position pair
+ rStrm.Seek( rnPropPosPos );
+ rStrm.WriteInt32( rProp.GetPropId() ).WriteUInt32( nPropPos );
+ rnPropPosPos = rStrm.Tell();
+}
+
+
+ErrCode const & SfxOlePropertySet::LoadPropertySet( SotStorage* pStrg, const OUString& rStrmName )
+{
+ if( pStrg )
+ {
+ tools::SvRef<SotStorageStream> xStrm = pStrg->OpenSotStream( rStrmName, StreamMode::STD_READ );
+ if( xStrm.is() && (xStrm->GetError() == ERRCODE_NONE) )
+ {
+ xStrm->SetBufferSize( STREAM_BUFFER_SIZE );
+ Load( *xStrm );
+ }
+ else
+ SetError( ERRCODE_IO_ACCESSDENIED );
+ }
+ else
+ SetError( ERRCODE_IO_ACCESSDENIED );
+ return GetError();
+}
+
+ErrCode const & SfxOlePropertySet::SavePropertySet( SotStorage* pStrg, const OUString& rStrmName )
+{
+ if( pStrg )
+ {
+ tools::SvRef<SotStorageStream> xStrm = pStrg->OpenSotStream( rStrmName, StreamMode::TRUNC | StreamMode::STD_WRITE );
+ if( xStrm.is() )
+ Save( *xStrm );
+ else
+ SetError( ERRCODE_IO_ACCESSDENIED );
+ }
+ else
+ SetError( ERRCODE_IO_ACCESSDENIED );
+ return GetError();
+}
+
+SfxOleSectionRef SfxOlePropertySet::GetSection( SfxOleSectionType eSection ) const
+{
+ return GetSection( GetSectionGuid( eSection ) );
+}
+
+SfxOleSectionRef SfxOlePropertySet::GetSection( const SvGlobalName& rSectionGuid ) const
+{
+ SfxOleSectionRef xSection;
+ SfxOleSectionMap::const_iterator aIt = maSectionMap.find( rSectionGuid );
+ if( aIt != maSectionMap.end() )
+ xSection = aIt->second;
+ return xSection;
+}
+
+SfxOleSection& SfxOlePropertySet::AddSection( SfxOleSectionType eSection )
+{
+ return AddSection( GetSectionGuid( eSection ) );
+}
+
+SfxOleSection& SfxOlePropertySet::AddSection( const SvGlobalName& rSectionGuid )
+{
+ SfxOleSectionRef xSection = GetSection( rSectionGuid );
+ if( !xSection )
+ {
+ // #i66214# #i66428# applications may write broken dictionary properties in wrong sections
+ bool bSupportsDict = rSectionGuid == GetSectionGuid( SECTION_CUSTOM );
+ xSection = std::make_shared<SfxOleSection>( bSupportsDict );
+ maSectionMap[ rSectionGuid ] = xSection;
+ }
+ return *xSection;
+}
+
+void SfxOlePropertySet::ImplLoad( SvStream& rStrm )
+{
+ // read property set header
+ sal_uInt16 nByteOrder;
+ sal_uInt16 nVersion;
+ sal_uInt16 nOsMinor;
+ sal_uInt16 nOsType;
+ SvGlobalName aGuid;
+ sal_Int32 nSectCount(0);
+ rStrm.ReadUInt16( nByteOrder ).ReadUInt16( nVersion ).ReadUInt16( nOsMinor ).ReadUInt16( nOsType );
+ rStrm >> aGuid;
+ rStrm.ReadInt32( nSectCount );
+
+ // read sections
+ sal_uInt64 nSectPosPos = rStrm.Tell();
+ for (sal_Int32 nSectIdx = 0; nSectIdx < nSectCount; ++nSectIdx)
+ {
+ // read section guid/position pair
+ rStrm.Seek(nSectPosPos);
+ SvGlobalName aSectGuid;
+ rStrm >> aSectGuid;
+ sal_uInt32 nSectPos(0);
+ rStrm.ReadUInt32(nSectPos);
+ if (!rStrm.good())
+ break;
+ nSectPosPos = rStrm.Tell();
+ // read section
+ if (!checkSeek(rStrm, nSectPos))
+ break;
+ LoadObject(rStrm, AddSection(aSectGuid));
+ if (!rStrm.good())
+ break;
+ }
+}
+
+void SfxOlePropertySet::ImplSave( SvStream& rStrm )
+{
+ // write property set header
+ SvGlobalName aGuid;
+ sal_Int32 nSectCount = static_cast< sal_Int32 >( maSectionMap.size() );
+ rStrm .WriteUInt16( 0xFFFE ) // byte order
+ .WriteUInt16( 0 ) // version
+ .WriteUInt16( 1 ) // OS minor version
+ .WriteUInt16( 2 ); // OS type always windows for text encoding
+ WriteSvGlobalName( rStrm, aGuid ); // unused guid
+ rStrm .WriteInt32( nSectCount ); // number of sections
+
+ // write placeholders for section guid/position pairs
+ sal_uInt64 nSectPosPos = rStrm.Tell();
+ rStrm.SeekRel( static_cast< sal_sSize >( 20 * nSectCount ) );
+
+ // write sections
+ for (auto const& section : maSectionMap)
+ {
+ SfxOleSection& rSection = *section.second;
+ rStrm.Seek( STREAM_SEEK_TO_END );
+ sal_uInt32 nSectPos = static_cast< sal_uInt32 >( rStrm.Tell() );
+ // write the section
+ SaveObject( rStrm, rSection );
+ // write section guid/position pair
+ rStrm.Seek( nSectPosPos );
+ WriteSvGlobalName( rStrm, section.first );
+ rStrm.WriteUInt32( nSectPos );
+ nSectPosPos = rStrm.Tell();
+ }
+}
+
+const SvGlobalName& SfxOlePropertySet::GetSectionGuid( SfxOleSectionType eSection )
+{
+ static const SvGlobalName saGlobalGuid( 0xF29F85E0, 0x4FF9, 0x1068, 0xAB, 0x91, 0x08, 0x00, 0x2B, 0x27, 0xB3, 0xD9 );
+ static const SvGlobalName saBuiltInGuid( 0xD5CDD502, 0x2E9C, 0x101B, 0x93, 0x97, 0x08, 0x00, 0x2B, 0x2C, 0xF9, 0xAE );
+ static const SvGlobalName saCustomGuid( 0xD5CDD505, 0x2E9C, 0x101B, 0x93, 0x97, 0x08, 0x00, 0x2B, 0x2C, 0xF9, 0xAE );
+ static const SvGlobalName saEmptyGuid;
+ switch( eSection )
+ {
+ case SECTION_GLOBAL: return saGlobalGuid;
+ case SECTION_BUILTIN: return saBuiltInGuid;
+ case SECTION_CUSTOM: return saCustomGuid;
+ default: SAL_WARN( "sfx.doc", "SfxOlePropertySet::GetSectionGuid - unknown section type" );
+ }
+ return saEmptyGuid;
+}
+
+
+//} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/oleprops.hxx b/sfx2/source/doc/oleprops.hxx
new file mode 100644
index 0000000000..5c486c8dad
--- /dev/null
+++ b/sfx2/source/doc/oleprops.hxx
@@ -0,0 +1,391 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SFX2_SOURCE_DOC_OLEPROPS_HXX
+#define INCLUDED_SFX2_SOURCE_DOC_OLEPROPS_HXX
+
+#include <map>
+#include <memory>
+#include <string_view>
+
+#include <osl/thread.h>
+#include <rtl/ustring.hxx>
+#include <sot/storage.hxx>
+
+#include <com/sun/star/util/DateTime.hpp>
+#include <com/sun/star/util/Date.hpp>
+
+
+//namespace {
+
+
+// property type IDs
+const sal_Int32 PROPTYPE_INT16 = 2;
+const sal_Int32 PROPTYPE_INT32 = 3;
+const sal_Int32 PROPTYPE_FLOAT = 4;
+const sal_Int32 PROPTYPE_DOUBLE = 5;
+const sal_Int32 PROPTYPE_DATE = 7;
+const sal_Int32 PROPTYPE_STRING = 8;
+const sal_Int32 PROPTYPE_STATUS = 10;
+const sal_Int32 PROPTYPE_BOOL = 11;
+const sal_Int32 PROPTYPE_VARIANT = 12;
+const sal_Int32 PROPTYPE_INT8 = 16;
+const sal_Int32 PROPTYPE_UINT8 = 17;
+const sal_Int32 PROPTYPE_UINT16 = 18;
+const sal_Int32 PROPTYPE_UINT32 = 19;
+const sal_Int32 PROPTYPE_INT64 = 20;
+const sal_Int32 PROPTYPE_UINT64 = 21;
+const sal_Int32 PROPTYPE_STRING8 = 30;
+const sal_Int32 PROPTYPE_STRING16 = 31;
+const sal_Int32 PROPTYPE_FILETIME = 64;
+const sal_Int32 PROPTYPE_BLOB = 65;
+const sal_Int32 PROPTYPE_CLIPFMT = 71;
+
+// static property IDs
+const sal_Int32 PROPID_DICTIONARY = 0;
+const sal_Int32 PROPID_CODEPAGE = 1;
+const sal_Int32 PROPID_FIRSTCUSTOM = 2;
+
+// property IDs for GlobalDocPropertySet
+const sal_Int32 PROPID_TITLE = 2;
+const sal_Int32 PROPID_SUBJECT = 3;
+const sal_Int32 PROPID_AUTHOR = 4;
+const sal_Int32 PROPID_KEYWORDS = 5;
+const sal_Int32 PROPID_COMMENTS = 6;
+const sal_Int32 PROPID_TEMPLATE = 7;
+const sal_Int32 PROPID_LASTAUTHOR = 8;
+const sal_Int32 PROPID_REVNUMBER = 9;
+const sal_Int32 PROPID_EDITTIME = 10;
+const sal_Int32 PROPID_LASTPRINTED = 11;
+const sal_Int32 PROPID_CREATED = 12;
+const sal_Int32 PROPID_LASTSAVED = 13;
+const sal_Int32 PROPID_THUMBNAIL = 17;
+
+// some Builtin properties
+const sal_Int32 PROPID_CATEGORY = 0x2;
+const sal_Int32 PROPID_COMPANY = 0xf;
+const sal_Int32 PROPID_MANAGER = 0xe;
+// predefined codepages
+const sal_uInt16 CODEPAGE_UNKNOWN = 0;
+const sal_uInt16 CODEPAGE_UNICODE = 1200;
+const sal_uInt16 CODEPAGE_UTF8 = 65001;
+
+// predefined clipboard format IDs
+const sal_Int32 CLIPFMT_WIN = -1;
+
+// predefined clipboard data format IDs
+const sal_Int32 CLIPDATAFMT_DIB = 8;
+
+
+/** Helper for classes that need text encoding settings.
+
+ Classes derived from this class will include functions to store and use
+ text encoding settings and to convert Windows codepage constants.
+ */
+class SfxOleTextEncoding
+{
+public:
+ explicit SfxOleTextEncoding() :
+ mxTextEnc( std::make_shared<rtl_TextEncoding>( osl_getThreadTextEncoding() ) ) {}
+ explicit SfxOleTextEncoding( rtl_TextEncoding eTextEnc ) :
+ mxTextEnc( std::make_shared<rtl_TextEncoding>( eTextEnc ) ) {}
+
+ /** Returns the current text encoding identifier. */
+ rtl_TextEncoding GetTextEncoding() const { return *mxTextEnc; }
+ /** Sets the passed text encoding. */
+ void SetTextEncoding( rtl_TextEncoding eTextEnc ) { *mxTextEnc = eTextEnc; }
+
+ /** Returns true, if this object contains Unicode text encoding. */
+ bool IsUnicode() const { return GetTextEncoding() == RTL_TEXTENCODING_UCS2; }
+ /** Sets Unicode text encoding to this object. */
+ void SetUnicode() { SetTextEncoding( RTL_TEXTENCODING_UCS2 ); }
+
+ /** Converts the current settings to a Windows codepage identifier. */
+ sal_uInt16 GetCodePage() const;
+ /** Sets the current text encoding from a Windows codepage identifier. */
+ void SetCodePage( sal_uInt16 nCodePage );
+
+private:
+ std::shared_ptr< rtl_TextEncoding > mxTextEnc;
+};
+
+
+/** Helper for classes that need to load or save string values.
+
+ Classes derived from this class contain functions to load and save string
+ values with the text encoding passed in the constructor.
+ */
+class SfxOleStringHelper : public SfxOleTextEncoding
+{
+public:
+ /** Creates a string helper object depending on an external text encoding. */
+ explicit SfxOleStringHelper( const SfxOleTextEncoding& rTextEnc ) :
+ SfxOleTextEncoding( rTextEnc ) {}
+ /** Creates a string helper object with own text encoding. */
+ explicit SfxOleStringHelper( rtl_TextEncoding eTextEnc ) :
+ SfxOleTextEncoding( eTextEnc ) {}
+
+ /** Loads a string from the passed stream with current encoding (maybe Unicode). */
+ OUString LoadString8( SvStream& rStrm ) const;
+ /** Saves a string to the passed stream with current encoding (maybe Unicode). */
+ void SaveString8( SvStream& rStrm, std::u16string_view rValue ) const;
+
+ /** Loads a Unicode string from the passed stream, ignores own encoding. */
+ static OUString LoadString16( SvStream& rStrm );
+ /** Saves a Unicode string to the passed stream, ignores own encoding. */
+ static void SaveString16( SvStream& rStrm, std::u16string_view rValue );
+
+private:
+ OUString ImplLoadString8( SvStream& rStrm ) const;
+ static OUString ImplLoadString16( SvStream& rStrm );
+ void ImplSaveString8( SvStream& rStrm, std::u16string_view rValue ) const;
+ static void ImplSaveString16( SvStream& rStrm, std::u16string_view rValue );
+};
+
+
+/** Base class for all classes related to OLE property sets.
+
+ Derived classes have to implement the pure virtual functions ImplLoad() and
+ ImplSave().
+ */
+class SfxOleObjectBase
+{
+public:
+ explicit SfxOleObjectBase() : mnErrCode( ERRCODE_NONE ) {}
+ virtual ~SfxOleObjectBase();
+
+ /** Returns the current error code. */
+ ErrCode const & GetError() const { return mnErrCode; }
+
+ /** Loads this object from the passed stream. Calls virtual ImplLoad(). */
+ ErrCode const & Load( SvStream& rStrm );
+ /** Saves this object to the passed stream. Calls virtual ImplSave(). */
+ ErrCode const & Save( SvStream& rStrm );
+
+protected:
+ /** Sets the passed error code. Will be returned by Load() and Save() functions.
+ Always the first error code is stored. Multiple calls have no effect. */
+ void SetError( ErrCode nErrCode ) { if( mnErrCode == ERRCODE_NONE ) mnErrCode = nErrCode; }
+ /** Loads the passed object from the stream. Sets returned error code as own error. */
+ void LoadObject( SvStream& rStrm, SfxOleObjectBase& rObj );
+ /** Saves the passed object to the stream. Sets returned error code as own error. */
+ void SaveObject( SvStream& rStrm, SfxOleObjectBase& rObj );
+
+private:
+ /** Derived classes implement loading the object from the passed steam. */
+ virtual void ImplLoad( SvStream& rStrm ) = 0;
+ /** Derived classes implement saving the object to the passed steam. */
+ virtual void ImplSave( SvStream& rStrm ) = 0;
+
+private:
+ ErrCode mnErrCode; /// Current error code.
+};
+
+
+/** Base class for all OLE property objects. */
+class SfxOlePropertyBase : public SfxOleObjectBase
+{
+public:
+ explicit SfxOlePropertyBase( sal_Int32 nPropId, sal_Int32 nPropType ) :
+ mnPropId( nPropId ), mnPropType( nPropType ) {}
+
+ sal_Int32 GetPropId() const { return mnPropId; }
+ sal_Int32 GetPropType() const { return mnPropType; }
+
+protected:
+ void SetPropType( sal_Int32 nPropType ) { mnPropType = nPropType; }
+
+private:
+ sal_Int32 mnPropId;
+ sal_Int32 mnPropType;
+};
+
+typedef std::shared_ptr< SfxOlePropertyBase > SfxOlePropertyRef;
+
+
+/** Property representing the codepage used to encode bytestrings in the entire property set. */
+class SfxOleCodePageProperty : public SfxOlePropertyBase, public SfxOleTextEncoding
+{
+public:
+ explicit SfxOleCodePageProperty();
+
+private:
+ virtual void ImplLoad( SvStream& rStrm ) override;
+ virtual void ImplSave( SvStream& rStrm ) override;
+};
+
+
+/** Property containing custom names for other properties in the property set. */
+class SfxOleDictionaryProperty : public SfxOlePropertyBase, public SfxOleStringHelper
+{
+public:
+ explicit SfxOleDictionaryProperty( const SfxOleTextEncoding& rTextEnc );
+
+ /** Returns true, if the property contains at least one custom property name. */
+ bool HasPropertyNames() const { return !maPropNameMap.empty(); }
+ /** Prepares the property for loading. Does not affect contained names for its own. */
+ void SetNameCount( sal_Int32 nNameCount ) { SetPropType( nNameCount ); }
+
+ /** Returns the custom name for the passed property ID, or an empty string, if name not found. */
+ OUString GetPropertyName( sal_Int32 nPropId ) const;
+ /** Sets a custom name for the passed property ID. */
+ void SetPropertyName( sal_Int32 nPropId, const OUString& rPropName );
+
+private:
+ virtual void ImplLoad( SvStream& rStrm ) override;
+ virtual void ImplSave( SvStream& rStrm ) override;
+
+private:
+ typedef ::std::map< sal_Int32, OUString > SfxOlePropNameMap;
+ SfxOlePropNameMap maPropNameMap;
+};
+
+
+/** A section in a property set. Contains properties with unique identifiers. */
+class SfxOleSection : public SfxOleObjectBase
+{
+private:
+ typedef ::std::map< sal_Int32, SfxOlePropertyRef > SfxOlePropMap;
+
+public:
+ explicit SfxOleSection( bool bSupportsDict );
+
+ /** Returns the property with the passed ID, or an empty reference, if nothing found. */
+ SfxOlePropertyRef GetProperty( sal_Int32 nPropId ) const;
+ /** Returns the value of a signed int32 property with the passed ID in rnValue.
+ @return true = Property found, rnValue is valid; false = Property not found. */
+ bool GetInt32Value( sal_Int32& rnValue, sal_Int32 nPropId ) const;
+ /** Returns the value of a floating-point property with the passed ID in rfValue.
+ @return true = Property found, rfValue is valid; false = Property not found. */
+ bool GetDoubleValue( double& rfValue, sal_Int32 nPropId ) const;
+ /** Returns the value of a boolean property with the passed ID in rbValue.
+ @return true = Property found, rbValue is valid; false = Property not found. */
+ bool GetBoolValue( bool& rbValue, sal_Int32 nPropId ) const;
+ /** Returns the value of a string property with the passed ID in rValue.
+ @return true = Property found, rValue is valid; false = Property not found. */
+ bool GetStringValue( OUString& rValue, sal_Int32 nPropId ) const;
+ /** Returns the value of a time stamp property with the passed ID in rValue.
+ @return true = Property found, rValue is valid; false = Property not found. */
+ bool GetFileTimeValue( css::util::DateTime& rValue, sal_Int32 nPropId ) const;
+ /** Returns the value of a date property with the passed ID in rValue.
+ @return true = Property found, rValue is valid; false = Property not found. */
+ bool GetDateValue( css::util::Date& rValue, sal_Int32 nPropId ) const;
+
+ /** Adds the passed property to the property set. Drops an existing old property. */
+ void SetProperty( const SfxOlePropertyRef& xProp );
+ /** Inserts a signed int32 property with the passed value. */
+ void SetInt32Value( sal_Int32 nPropId, sal_Int32 nValue );
+ /** Inserts a floating-point property with the passed value. */
+ void SetDoubleValue( sal_Int32 nPropId, double fValue );
+ /** Inserts a boolean property with the passed value. */
+ void SetBoolValue( sal_Int32 nPropId, bool bValue );
+ /** Inserts a string property with the passed value.
+ @return true = Property inserted; false = String was empty, property not inserted. */
+ bool SetStringValue( sal_Int32 nPropId, const OUString& rValue );
+ /** Inserts a time stamp property with the passed value. */
+ void SetFileTimeValue( sal_Int32 nPropId, const css::util::DateTime& rValue );
+ /** Inserts a date property with the passed value. */
+ void SetDateValue( sal_Int32 nPropId, const css::util::Date& rValue );
+ /** Inserts a thumbnail property from the passed meta file. */
+ void SetThumbnailValue( sal_Int32 nPropId,
+ const css::uno::Sequence<sal_Int8> & i_rData);
+ /** Inserts a BLOB property with the passed data. */
+ void SetBlobValue( sal_Int32 nPropId,
+ const css::uno::Sequence<sal_Int8> & i_rData);
+
+ /** Returns the value of the property with the passed ID in a UNO any. */
+ css::uno::Any GetAnyValue( sal_Int32 nPropId ) const;
+ /** Inserts a property created from the passed any.
+ @return true = Property converted and inserted; false = Property type not supported. */
+ bool SetAnyValue( sal_Int32 nPropId, const css::uno::Any& rValue );
+
+ /** Returns the custom name for the passed property ID, or an empty string, if name not found. */
+ OUString GetPropertyName( sal_Int32 nPropId ) const;
+ /** Sets a custom name for the passed property ID. */
+ void SetPropertyName( sal_Int32 nPropId, const OUString& rPropName );
+
+ /** Returns the identifiers of all existing properties in the passed vector. */
+ void GetPropertyIds( ::std::vector< sal_Int32 >& rPropIds ) const;
+ /** Returns a property identifier not used in this section. */
+ sal_Int32 GetFreePropertyId() const;
+
+private:
+ virtual void ImplLoad( SvStream& rStrm ) override;
+ virtual void ImplSave( SvStream& rStrm ) override;
+
+ bool SeekToPropertyPos( SvStream& rStrm, sal_uInt32 nPropPos ) const;
+ void LoadProperty( SvStream& rStrm, sal_Int32 nPropId );
+ void SaveProperty( SvStream& rStrm, SfxOlePropertyBase& rProp, sal_uInt64 & rnPropPosPos );
+
+private:
+ SfxOlePropMap maPropMap; /// All properties in this section, by identifier.
+ SfxOleCodePageProperty maCodePageProp; /// The codepage property.
+ SfxOleDictionaryProperty maDictProp; /// The dictionary property.
+ sal_uInt64 mnStartPos; /// Start stream position of the section.
+ bool mbSupportsDict; /// true = section supports dictionary.
+};
+
+typedef std::shared_ptr< SfxOleSection > SfxOleSectionRef;
+
+
+/** Enumerates different section types in OLE property sets. */
+enum SfxOleSectionType
+{
+ SECTION_GLOBAL, /// Globally defined properties.
+ SECTION_BUILTIN, /// Properties built into MS Office.
+ SECTION_CUSTOM /// Custom properties.
+};
+
+
+/** Represents a complete property set, may consist of several property sections. */
+class SfxOlePropertySet : public SfxOleObjectBase
+{
+public:
+ explicit SfxOlePropertySet() {}
+
+ /** Loads this object from the passed storage. */
+ ErrCode const & LoadPropertySet( SotStorage* pStrg, const OUString& rStrmName );
+ /** Saves this object to the passed storage. */
+ ErrCode const & SavePropertySet( SotStorage* pStrg, const OUString& rStrmName );
+
+ /** Returns the specified section, or an empty reference, if nothing found. */
+ SfxOleSectionRef GetSection( SfxOleSectionType eSection ) const;
+ /** Returns the specified section, or an empty reference, if nothing found. */
+ SfxOleSectionRef GetSection( const SvGlobalName& rSectionGuid ) const;
+
+ /** Creates and returns the specified section, or just returns it if it already exists. */
+ SfxOleSection& AddSection( SfxOleSectionType eSection );
+ /** Creates and returns the specified section, or just returns it if it already exists. */
+ SfxOleSection& AddSection( const SvGlobalName& rSectionGuid );
+
+private:
+ virtual void ImplLoad( SvStream& rStrm ) override;
+ virtual void ImplSave( SvStream& rStrm ) override;
+
+ /** Returns the GUID for the specified section. */
+ static const SvGlobalName& GetSectionGuid( SfxOleSectionType eSection );
+
+private:
+ typedef ::std::map< SvGlobalName, SfxOleSectionRef > SfxOleSectionMap;
+ SfxOleSectionMap maSectionMap;
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/ownsubfilterservice.cxx b/sfx2/source/doc/ownsubfilterservice.cxx
new file mode 100644
index 0000000000..7e317cc29e
--- /dev/null
+++ b/sfx2/source/doc/ownsubfilterservice.cxx
@@ -0,0 +1,115 @@
+/* -*- 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 <com/sun/star/frame/DoubleInitializationException.hpp>
+#include <com/sun/star/document/XFilter.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/io/XStream.hpp>
+
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <sfx2/objsh.hxx>
+
+using namespace css;
+
+namespace {
+
+class OwnSubFilterService : public cppu::WeakImplHelper < document::XFilter
+ ,lang::XServiceInfo >
+{
+ uno::Reference< frame::XModel > m_xModel;
+ uno::Reference< io::XStream > m_xStream;
+ SfxObjectShell* m_pObjectShell;
+
+public:
+ /// @throws css::uno::Exception
+ /// @throws css::uno::RuntimeException
+ explicit OwnSubFilterService(const css::uno::Sequence< css::uno::Any >& aArguments);
+
+ // XFilter
+ virtual sal_Bool SAL_CALL filter( const uno::Sequence< beans::PropertyValue >& aDescriptor ) override;
+ virtual void SAL_CALL cancel() override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override;
+};
+
+OwnSubFilterService::OwnSubFilterService(const css::uno::Sequence< css::uno::Any >& aArguments)
+ : m_pObjectShell( nullptr )
+{
+ if ( aArguments.getLength() != 2 )
+ throw lang::IllegalArgumentException();
+
+ if ( m_pObjectShell )
+ throw frame::DoubleInitializationException();
+
+ if ( ( aArguments[1] >>= m_xStream ) && m_xStream.is()
+ && ( aArguments[0] >>= m_xModel ) && m_xModel.is() )
+ {
+ m_pObjectShell = SfxObjectShell::GetShellFromComponent(m_xModel);
+ }
+
+ if ( !m_pObjectShell )
+ throw lang::IllegalArgumentException();
+}
+
+sal_Bool SAL_CALL OwnSubFilterService::filter( const uno::Sequence< beans::PropertyValue >& aDescriptor )
+{
+ if ( !m_pObjectShell )
+ throw uno::RuntimeException();
+
+ return m_pObjectShell->ImportFromGeneratedStream_Impl( m_xStream, aDescriptor );
+}
+
+void SAL_CALL OwnSubFilterService::cancel()
+{
+ // not implemented
+}
+
+OUString SAL_CALL OwnSubFilterService::getImplementationName()
+{
+ return "com.sun.star.comp.document.OwnSubFilter";
+}
+
+sal_Bool SAL_CALL OwnSubFilterService::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+uno::Sequence< OUString > SAL_CALL OwnSubFilterService::getSupportedServiceNames()
+{
+ return { "com.sun.star.document.OwnSubFilter", "com.sun.star.comp.document.OwnSubFilter" };
+}
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
+com_sun_star_comp_document_OwnSubFilter_get_implementation(
+ css::uno::XComponentContext *,
+ css::uno::Sequence<css::uno::Any> const &arguments)
+{
+ return cppu::acquire(new OwnSubFilterService(arguments));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/printhelper.cxx b/sfx2/source/doc/printhelper.cxx
new file mode 100644
index 0000000000..6ca15e6cdb
--- /dev/null
+++ b/sfx2/source/doc/printhelper.cxx
@@ -0,0 +1,798 @@
+/* -*- 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 "printhelper.hxx"
+
+#include <com/sun/star/view/XPrintJob.hpp>
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/view/PaperFormat.hpp>
+#include <com/sun/star/view/PaperOrientation.hpp>
+#include <com/sun/star/ucb/NameClash.hpp>
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/view/DuplexMode.hpp>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <svl/itemset.hxx>
+#include <svl/lstner.hxx>
+#include <unotools/tempfile.hxx>
+#include <osl/file.hxx>
+#include <osl/thread.hxx>
+#include <tools/urlobj.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <ucbhelper/content.hxx>
+#include <comphelper/interfacecontainer4.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <utility>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/viewsh.hxx>
+#include <sfx2/printer.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/event.hxx>
+
+#define SFX_PRINTABLESTATE_CANCELJOB css::view::PrintableState(-2)
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+struct IMPL_PrintListener_DataContainer : public SfxListener
+{
+ SfxObjectShellRef m_pObjectShell;
+ std::mutex m_aMutex;
+ comphelper::OInterfaceContainerHelper4<view::XPrintJobListener> m_aJobListeners;
+ uno::Reference< css::view::XPrintJob> m_xPrintJob;
+ css::uno::Sequence< css::beans::PropertyValue > m_aPrintOptions;
+
+ explicit IMPL_PrintListener_DataContainer()
+ {
+ }
+
+
+ void Notify( SfxBroadcaster& aBC ,
+ const SfxHint& aHint ) override ;
+};
+
+static awt::Size impl_Size_Object2Struct( const Size& aSize )
+{
+ awt::Size aReturnValue;
+ aReturnValue.Width = aSize.Width() ;
+ aReturnValue.Height = aSize.Height() ;
+ return aReturnValue ;
+}
+
+static Size impl_Size_Struct2Object( const awt::Size& aSize )
+{
+ Size aReturnValue;
+ aReturnValue.setWidth( aSize.Width ) ;
+ aReturnValue.setHeight( aSize.Height ) ;
+ return aReturnValue ;
+}
+
+namespace {
+
+class SfxPrintJob_Impl : public cppu::WeakImplHelper
+<
+ css::view::XPrintJob
+>
+{
+ IMPL_PrintListener_DataContainer* m_pData;
+
+public:
+ explicit SfxPrintJob_Impl( IMPL_PrintListener_DataContainer* pData );
+ virtual Sequence< css::beans::PropertyValue > SAL_CALL getPrintOptions( ) override;
+ virtual Sequence< css::beans::PropertyValue > SAL_CALL getPrinter( ) override;
+ virtual Reference< css::view::XPrintable > SAL_CALL getPrintable( ) override;
+ virtual void SAL_CALL cancelJob() override;
+};
+
+}
+
+SfxPrintJob_Impl::SfxPrintJob_Impl( IMPL_PrintListener_DataContainer* pData )
+ : m_pData( pData )
+{
+}
+
+Sequence< css::beans::PropertyValue > SAL_CALL SfxPrintJob_Impl::getPrintOptions()
+{
+ return m_pData->m_aPrintOptions;
+}
+
+Sequence< css::beans::PropertyValue > SAL_CALL SfxPrintJob_Impl::getPrinter()
+{
+ if( m_pData->m_pObjectShell.is() )
+ {
+ Reference < view::XPrintable > xPrintable( m_pData->m_pObjectShell->GetModel(), UNO_QUERY );
+ if ( xPrintable.is() )
+ return xPrintable->getPrinter();
+ }
+ return Sequence< css::beans::PropertyValue >();
+}
+
+Reference< css::view::XPrintable > SAL_CALL SfxPrintJob_Impl::getPrintable()
+{
+ Reference < view::XPrintable > xPrintable( m_pData->m_pObjectShell.is() ? m_pData->m_pObjectShell->GetModel() : nullptr, UNO_QUERY );
+ return xPrintable;
+}
+
+void SAL_CALL SfxPrintJob_Impl::cancelJob()
+{
+ // FIXME: how to cancel PrintJob via API?!
+ if( m_pData->m_pObjectShell.is() )
+ m_pData->m_pObjectShell->Broadcast( SfxPrintingHint( SFX_PRINTABLESTATE_CANCELJOB ) );
+}
+
+SfxPrintHelper::SfxPrintHelper()
+{
+ m_pData.reset(new IMPL_PrintListener_DataContainer());
+}
+
+void SAL_CALL SfxPrintHelper::initialize( const css::uno::Sequence< css::uno::Any >& aArguments )
+{
+ if ( !aArguments.hasElements() )
+ return;
+
+ css::uno::Reference < css::frame::XModel > xModel;
+ aArguments[0] >>= xModel;
+ m_pData->m_pObjectShell = SfxObjectShell::GetShellFromComponent(xModel);
+ if (m_pData->m_pObjectShell)
+ m_pData->StartListening(*m_pData->m_pObjectShell);
+}
+
+SfxPrintHelper::~SfxPrintHelper()
+{
+}
+
+namespace
+{
+ view::PaperFormat convertToPaperFormat(Paper eFormat)
+ {
+ view::PaperFormat eRet;
+ switch (eFormat)
+ {
+ case PAPER_A3:
+ eRet = view::PaperFormat_A3;
+ break;
+ case PAPER_A4:
+ eRet = view::PaperFormat_A4;
+ break;
+ case PAPER_A5:
+ eRet = view::PaperFormat_A5;
+ break;
+ case PAPER_B4_ISO:
+ eRet = view::PaperFormat_B4;
+ break;
+ case PAPER_B5_ISO:
+ eRet = view::PaperFormat_B5;
+ break;
+ case PAPER_LETTER:
+ eRet = view::PaperFormat_LETTER;
+ break;
+ case PAPER_LEGAL:
+ eRet = view::PaperFormat_LEGAL;
+ break;
+ case PAPER_TABLOID:
+ eRet = view::PaperFormat_TABLOID;
+ break;
+ case PAPER_USER:
+ default:
+ eRet = view::PaperFormat_USER;
+ break;
+ }
+ return eRet;
+ }
+
+ Paper convertToPaper(view::PaperFormat eFormat)
+ {
+ Paper eRet(PAPER_USER);
+ switch (eFormat)
+ {
+ case view::PaperFormat_A3:
+ eRet = PAPER_A3;
+ break;
+ case view::PaperFormat_A4:
+ eRet = PAPER_A4;
+ break;
+ case view::PaperFormat_A5:
+ eRet = PAPER_A5;
+ break;
+ case view::PaperFormat_B4:
+ eRet = PAPER_B4_ISO;
+ break;
+ case view::PaperFormat_B5:
+ eRet = PAPER_B5_ISO;
+ break;
+ case view::PaperFormat_LETTER:
+ eRet = PAPER_LETTER;
+ break;
+ case view::PaperFormat_LEGAL:
+ eRet = PAPER_LEGAL;
+ break;
+ case view::PaperFormat_TABLOID:
+ eRet = PAPER_TABLOID;
+ break;
+ case view::PaperFormat_USER:
+ eRet = PAPER_USER;
+ break;
+ case view::PaperFormat::PaperFormat_MAKE_FIXED_SIZE:
+ break;
+ //deliberate no default to force warn on a new papersize
+ }
+ return eRet;
+ }
+}
+
+
+// XPrintable
+
+
+uno::Sequence< beans::PropertyValue > SAL_CALL SfxPrintHelper::getPrinter()
+{
+ // object already disposed?
+ SolarMutexGuard aGuard;
+
+ // search for any view of this document that is currently printing
+ const Printer *pPrinter = nullptr;
+ SfxViewFrame *pViewFrm = m_pData->m_pObjectShell.is() ? SfxViewFrame::GetFirst( m_pData->m_pObjectShell.get(), false ) : nullptr;
+ SfxViewFrame* pFirst = pViewFrm;
+ while ( pViewFrm && !pPrinter )
+ {
+ pPrinter = pViewFrm->GetViewShell()->GetActivePrinter();
+ pViewFrm = SfxViewFrame::GetNext( *pViewFrm, m_pData->m_pObjectShell.get(), false );
+ }
+
+ // if no view is printing currently, use the permanent SfxPrinter instance
+ if ( !pPrinter && pFirst )
+ pPrinter = pFirst->GetViewShell()->GetPrinter(true);
+
+ if ( !pPrinter )
+ return uno::Sequence< beans::PropertyValue >();
+
+ return
+ {
+ comphelper::makePropertyValue("Name", pPrinter->GetName()),
+ comphelper::makePropertyValue("PaperOrientation", static_cast<view::PaperOrientation>(pPrinter->GetOrientation())),
+ comphelper::makePropertyValue("PaperFormat", convertToPaperFormat(pPrinter->GetPaper())),
+ comphelper::makePropertyValue("PaperSize", impl_Size_Object2Struct(pPrinter->GetPaperSize() )),
+ comphelper::makePropertyValue("IsBusy", pPrinter->IsPrinting()),
+ comphelper::makePropertyValue("CanSetPaperOrientation", pPrinter->HasSupport( PrinterSupport::SetOrientation )),
+ comphelper::makePropertyValue("CanSetPaperFormat", pPrinter->HasSupport( PrinterSupport::SetPaper )),
+ comphelper::makePropertyValue("CanSetPaperSize", pPrinter->HasSupport( PrinterSupport::SetPaperSize ))
+ };
+}
+
+
+// XPrintable
+
+
+void SfxPrintHelper::impl_setPrinter(const uno::Sequence< beans::PropertyValue >& rPrinter,
+ VclPtr<SfxPrinter>& pPrinter,
+ SfxPrinterChangeFlags& nChangeFlags,
+ SfxViewShell*& pViewSh)
+
+{
+ // Get old Printer
+ SfxViewFrame *pViewFrm = m_pData->m_pObjectShell.is() ?
+ SfxViewFrame::GetFirst( m_pData->m_pObjectShell.get(), false ) : nullptr;
+ if ( !pViewFrm )
+ return;
+
+ pViewSh = pViewFrm->GetViewShell();
+ pPrinter = pViewSh->GetPrinter(true);
+ if ( !pPrinter )
+ return;
+
+ // new Printer-Name available?
+ nChangeFlags = SfxPrinterChangeFlags::NONE;
+ sal_Int32 lDummy = 0;
+ auto pProp = std::find_if(rPrinter.begin(), rPrinter.end(),
+ [](const beans::PropertyValue &rProp) { return rProp.Name == "Name"; });
+ if (pProp != rPrinter.end())
+ {
+ OUString aPrinterName;
+ if ( ! ( pProp->Value >>= aPrinterName ) )
+ throw css::lang::IllegalArgumentException();
+
+ if ( aPrinterName != pPrinter->GetName() )
+ {
+ pPrinter = VclPtr<SfxPrinter>::Create( pPrinter->GetOptions().Clone(), aPrinterName );
+ nChangeFlags = SfxPrinterChangeFlags::PRINTER;
+ }
+ }
+
+ Size aSetPaperSize( 0, 0);
+ view::PaperFormat nPaperFormat = view::PaperFormat_USER;
+
+ // other properties
+ for ( const beans::PropertyValue &rProp : rPrinter )
+ {
+ // get Property-Value from printer description
+ // PaperOrientation-Property?
+ if ( rProp.Name == "PaperOrientation" )
+ {
+ view::PaperOrientation eOrient;
+ if ( !( rProp.Value >>= eOrient ) )
+ {
+ if ( !( rProp.Value >>= lDummy ) )
+ throw css::lang::IllegalArgumentException();
+ eOrient = static_cast<view::PaperOrientation>(lDummy);
+ }
+
+ if ( static_cast<Orientation>(eOrient) != pPrinter->GetOrientation() )
+ {
+ pPrinter->SetOrientation( static_cast<Orientation>(eOrient) );
+ nChangeFlags |= SfxPrinterChangeFlags::CHG_ORIENTATION;
+ }
+ }
+
+ // PaperFormat-Property?
+ else if ( rProp.Name == "PaperFormat" )
+ {
+ if ( !( rProp.Value >>= nPaperFormat ) )
+ {
+ if ( !( rProp.Value >>= lDummy ) )
+ throw css::lang::IllegalArgumentException();
+ nPaperFormat = static_cast<view::PaperFormat>(lDummy);
+ }
+
+ if ( convertToPaper(nPaperFormat) != pPrinter->GetPaper() )
+ {
+ pPrinter->SetPaper( convertToPaper(nPaperFormat) );
+ nChangeFlags |= SfxPrinterChangeFlags::CHG_SIZE;
+ }
+ }
+
+ // PaperSize-Property?
+ else if ( rProp.Name == "PaperSize" )
+ {
+ awt::Size aTempSize ;
+ if ( !( rProp.Value >>= aTempSize ) )
+ {
+ throw css::lang::IllegalArgumentException();
+ }
+ aSetPaperSize = impl_Size_Struct2Object(aTempSize);
+ }
+
+ // PrinterTray-Property
+ else if ( rProp.Name == "PrinterPaperTray" )
+ {
+ OUString aTmp;
+ if ( !( rProp.Value >>= aTmp ) )
+ throw css::lang::IllegalArgumentException();
+ const sal_uInt16 nCount = pPrinter->GetPaperBinCount();
+ for (sal_uInt16 nBin=0; nBin<nCount; nBin++)
+ {
+ OUString aName( pPrinter->GetPaperBinName(nBin) );
+ if ( aName == aTmp )
+ {
+ pPrinter->SetPaperBin(nBin);
+ break;
+ }
+ }
+ }
+ }
+
+ // The PaperSize may be set only when actually PAPER_USER
+ // applies, otherwise the driver could choose an invalid format.
+ if(nPaperFormat == view::PaperFormat_USER && aSetPaperSize.Width())
+ {
+ // Bug 56929 - MapMode of 100mm which recalculated when
+ // the device is set. Additionally only set if they were really changed.
+ aSetPaperSize = pPrinter->LogicToPixel(aSetPaperSize, MapMode(MapUnit::Map100thMM));
+ if( aSetPaperSize != pPrinter->GetPaperSizePixel() )
+ {
+ pPrinter->SetPaperSizeUser( pPrinter->PixelToLogic( aSetPaperSize ) );
+ nChangeFlags |= SfxPrinterChangeFlags::CHG_SIZE;
+ }
+ }
+
+ //wait until printing is done
+ SfxPrinter* pDocPrinter = pViewSh->GetPrinter();
+ while ( pDocPrinter->IsPrinting() && !Application::IsQuit())
+ Application::Yield();
+}
+
+void SAL_CALL SfxPrintHelper::setPrinter(const uno::Sequence< beans::PropertyValue >& rPrinter)
+{
+ // object already disposed?
+ SolarMutexGuard aGuard;
+
+ SfxViewShell* pViewSh = nullptr;
+ VclPtr<SfxPrinter> pPrinter;
+ SfxPrinterChangeFlags nChangeFlags = SfxPrinterChangeFlags::NONE;
+ impl_setPrinter(rPrinter,pPrinter,nChangeFlags,pViewSh);
+ // set new printer
+ if ( pViewSh && pPrinter )
+ pViewSh->SetPrinter( pPrinter, nChangeFlags );
+}
+
+
+// ImplPrintWatch thread for asynchronous printing with moving temp. file to ucb location
+
+namespace {
+
+/* This implements a thread which will be started to wait for asynchronous
+ print jobs to temp. locally files. If they finish we move the temp. files
+ to their right locations by using the ucb.
+ */
+class ImplUCBPrintWatcher : public ::osl::Thread
+{
+ private:
+ /// of course we must know the printer which execute the job
+ VclPtr<SfxPrinter> m_pPrinter;
+ /// this describes the target location for the printed temp file
+ OUString m_sTargetURL;
+ /// it holds the temp file alive, till the print job will finish and remove it from disk automatically if the object die
+ ::utl::TempFileNamed* m_pTempFile;
+
+ public:
+ /* initialize this watcher but don't start it */
+ ImplUCBPrintWatcher( SfxPrinter* pPrinter, ::utl::TempFileNamed* pTempFile, OUString sTargetURL )
+ : m_pPrinter ( pPrinter )
+ , m_sTargetURL(std::move( sTargetURL ))
+ , m_pTempFile ( pTempFile )
+ {}
+
+ /* waits for finishing of the print job and moves the temp file afterwards
+ Note: Starting of the job is done outside this thread!
+ But we have to free some of the given resources on heap!
+ */
+ void SAL_CALL run() override
+ {
+ osl_setThreadName("ImplUCBPrintWatcher");
+
+ /* SAFE { */
+ {
+ SolarMutexGuard aGuard;
+ while( m_pPrinter->IsPrinting() && !Application::IsQuit())
+ Application::Yield();
+ m_pPrinter.clear(); // don't delete it! It's borrowed only :-)
+ }
+ /* } SAFE */
+
+ // lock for further using of our member isn't necessary - because
+ // we run alone by definition. Nobody join for us nor use us...
+ moveAndDeleteTemp(&m_pTempFile,m_sTargetURL);
+
+ // finishing of this run() method will call onTerminate() automatically
+ // kill this thread there!
+ }
+
+ /* nobody wait for this thread. We must kill ourself ...
+ */
+ void SAL_CALL onTerminated() override
+ {
+ delete this;
+ }
+
+ /* static helper to move the temp. file to the target location by using the ucb
+ It's static to be usable from outside too. So it's not really necessary to start
+ the thread, if finishing of the job was detected outside this thread.
+ But it must be called without using a corresponding thread for the given parameter!
+ */
+ static void moveAndDeleteTemp( ::utl::TempFileNamed** ppTempFile, std::u16string_view sTargetURL )
+ {
+ // move the file
+ try
+ {
+ INetURLObject aSplitter(sTargetURL);
+ OUString sFileName = aSplitter.getName(
+ INetURLObject::LAST_SEGMENT,
+ true,
+ INetURLObject::DecodeMechanism::WithCharset);
+ if (aSplitter.removeSegment() && !sFileName.isEmpty())
+ {
+ ::ucbhelper::Content aSource(
+ (*ppTempFile)->GetURL(),
+ css::uno::Reference< css::ucb::XCommandEnvironment >(),
+ comphelper::getProcessComponentContext());
+
+ ::ucbhelper::Content aTarget(
+ aSplitter.GetMainURL(INetURLObject::DecodeMechanism::NONE),
+ css::uno::Reference< css::ucb::XCommandEnvironment >(),
+ comphelper::getProcessComponentContext());
+
+ aTarget.transferContent(
+ aSource,
+ ::ucbhelper::InsertOperation::Copy,
+ sFileName,
+ css::ucb::NameClash::OVERWRITE);
+ }
+ }
+ catch (const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "sfx.doc", "");
+ }
+
+ // kill the temp file!
+ delete *ppTempFile;
+ *ppTempFile = nullptr;
+ }
+};
+
+}
+
+// XPrintable
+
+void SAL_CALL SfxPrintHelper::print(const uno::Sequence< beans::PropertyValue >& rOptions)
+{
+ if( Application::GetSettings().GetMiscSettings().GetDisablePrinting() )
+ return;
+
+ // object already disposed?
+ // object already disposed?
+ SolarMutexGuard aGuard;
+
+ // get view for sfx printing capabilities
+ SfxViewFrame *pViewFrm = m_pData->m_pObjectShell.is() ?
+ SfxViewFrame::GetFirst( m_pData->m_pObjectShell.get(), false ) : nullptr;
+ if ( !pViewFrm )
+ return;
+ SfxViewShell* pView = pViewFrm->GetViewShell();
+ if ( !pView )
+ return;
+ bool bMonitor = false;
+ // We need this information at the end of this method, if we start the vcl printer
+ // by executing the slot. Because if it is a ucb relevant URL we must wait for
+ // finishing the print job and move the temporary local file by using the ucb
+ // to the right location. But in case of no file name is given or it is already
+ // a local one we can suppress this special handling. Because then vcl makes all
+ // right for us.
+ OUString sUcbUrl;
+ ::utl::TempFileNamed* pUCBPrintTempFile = nullptr;
+
+ uno::Sequence < beans::PropertyValue > aCheckedArgs( rOptions.getLength() );
+ auto pCheckedArgs = aCheckedArgs.getArray();
+ sal_Int32 nProps = 0;
+ bool bWaitUntilEnd = false;
+ sal_Int16 nDuplexMode = css::view::DuplexMode::UNKNOWN;
+ for ( const beans::PropertyValue &rProp : rOptions )
+ {
+ // get Property-Value from options
+ // FileName-Property?
+ if ( rProp.Name == "FileName" )
+ {
+ // unpack th URL and check for a valid and well known protocol
+ OUString sTemp;
+ if (
+ ( rProp.Value.getValueType()!=cppu::UnoType<OUString>::get()) ||
+ (!(rProp.Value>>=sTemp))
+ )
+ {
+ throw css::lang::IllegalArgumentException();
+ }
+
+ OUString sPath;
+ OUString sURL (sTemp);
+ INetURLObject aCheck(sURL );
+ if (aCheck.GetProtocol()==INetProtocol::NotValid)
+ {
+ // OK - it's not a valid URL. But may it's a simple
+ // system path directly. It will be supported for historical
+ // reasons. Otherwise we break too much external code...
+ // We try to convert it to a file URL. If it's possible
+ // we put the system path to the item set and let vcl work with it.
+ // No ucb or thread will be necessary then. In case it couldn't be
+ // converted it's not a URL nor a system path. Then we can't accept
+ // this parameter and have to throw an exception.
+ const OUString& sSystemPath(sTemp);
+ OUString sFileURL;
+ if (::osl::FileBase::getFileURLFromSystemPath(sSystemPath,sFileURL)!=::osl::FileBase::E_None)
+ throw css::lang::IllegalArgumentException();
+ pCheckedArgs[nProps].Name = rProp.Name;
+ pCheckedArgs[nProps++].Value <<= sFileURL;
+ // and append the local filename
+ aCheckedArgs.realloc( aCheckedArgs.getLength()+1 );
+ pCheckedArgs = aCheckedArgs.getArray();
+ pCheckedArgs[nProps].Name = "LocalFileName";
+ pCheckedArgs[nProps++].Value <<= sTemp;
+ }
+ else
+ // It's a valid URL. but now we must know, if it is a local one or not.
+ // It's a question of using ucb or not!
+ if (osl::FileBase::getSystemPathFromFileURL(sURL, sPath) == osl::FileBase::E_None)
+ {
+ // it's a local file, we can use vcl without special handling
+ // And we have to use the system notation of the incoming URL.
+ // But it into the descriptor and let the slot be executed at
+ // the end of this method.
+ pCheckedArgs[nProps].Name = rProp.Name;
+ pCheckedArgs[nProps++].Value <<= sTemp;
+ // and append the local filename
+ aCheckedArgs.realloc( aCheckedArgs.getLength()+1 );
+ pCheckedArgs = aCheckedArgs.getArray();
+ pCheckedArgs[nProps].Name = "LocalFileName";
+ pCheckedArgs[nProps++].Value <<= sPath;
+ }
+ else
+ {
+ // it's a ucb target. So we must use a temp. file for vcl
+ // and move it after printing by using the ucb.
+ // Create a temp file on the heap (because it must delete the
+ // real file on disk automatically if it die - bt we have to share it with
+ // some other sources ... e.g. the ImplUCBPrintWatcher).
+ // And we put the name of this temp file to the descriptor instead
+ // of the URL. The URL we save for later using separately.
+ // Execution of the print job will be done later by executing
+ // a slot ...
+ if(!pUCBPrintTempFile)
+ pUCBPrintTempFile = new ::utl::TempFileNamed();
+ pUCBPrintTempFile->EnableKillingFile();
+
+ //FIXME: does it work?
+ pCheckedArgs[nProps].Name = "LocalFileName";
+ pCheckedArgs[nProps++].Value <<= pUCBPrintTempFile->GetFileName();
+ sUcbUrl = sURL;
+ }
+ }
+
+ // CopyCount-Property
+ else if ( rProp.Name == "CopyCount" )
+ {
+ sal_Int32 nCopies = 0;
+ if ( !( rProp.Value >>= nCopies ) )
+ throw css::lang::IllegalArgumentException();
+ pCheckedArgs[nProps].Name = rProp.Name;
+ pCheckedArgs[nProps++].Value <<= nCopies;
+ }
+
+ // Collate-Property
+ // Sort-Property (deprecated)
+ else if ( rProp.Name == "Collate" || rProp.Name == "Sort" )
+ {
+ bool bTemp;
+ if ( !(rProp.Value >>= bTemp) )
+ throw css::lang::IllegalArgumentException();
+ pCheckedArgs[nProps].Name = "Collate";
+ pCheckedArgs[nProps++].Value <<= bTemp;
+ }
+
+ else if ( rProp.Name == "SinglePrintJobs" )
+ {
+ bool bTemp;
+ if ( !(rProp.Value >>= bTemp) )
+ throw css::lang::IllegalArgumentException();
+ pCheckedArgs[nProps].Name = "SinglePrintJobs";
+ pCheckedArgs[nProps++].Value <<= bTemp;
+ }
+
+ else if ( rProp.Name == "JobName" )
+ {
+ OUString sTemp;
+ if( !(rProp.Value >>= sTemp) )
+ throw css::lang::IllegalArgumentException();
+ pCheckedArgs[nProps].Name = rProp.Name;
+ pCheckedArgs[nProps++].Value <<= sTemp;
+ }
+
+ // Pages-Property
+ else if ( rProp.Name == "Pages" )
+ {
+ OUString sTemp;
+ if( !(rProp.Value >>= sTemp) )
+ throw css::lang::IllegalArgumentException();
+ pCheckedArgs[nProps].Name = rProp.Name;
+ pCheckedArgs[nProps++].Value <<= sTemp;
+ }
+
+ // MonitorVisible
+ else if ( rProp.Name == "MonitorVisible" )
+ {
+ if( !(rProp.Value >>= bMonitor) )
+ throw css::lang::IllegalArgumentException();
+ pCheckedArgs[nProps].Name = rProp.Name;
+ pCheckedArgs[nProps++].Value <<= bMonitor;
+ }
+
+ // Wait
+ else if ( rProp.Name == "Wait" )
+ {
+ if ( !(rProp.Value >>= bWaitUntilEnd) )
+ throw css::lang::IllegalArgumentException();
+ pCheckedArgs[nProps].Name = rProp.Name;
+ pCheckedArgs[nProps++].Value <<= bWaitUntilEnd;
+ }
+
+ else if ( rProp.Name == "DuplexMode" )
+ {
+ if ( !(rProp.Value >>= nDuplexMode ) )
+ throw css::lang::IllegalArgumentException();
+ pCheckedArgs[nProps].Name = rProp.Name;
+ pCheckedArgs[nProps++].Value <<= nDuplexMode;
+ }
+ }
+
+ if ( nProps != aCheckedArgs.getLength() )
+ aCheckedArgs.realloc(nProps);
+
+ // Execute the print request every time.
+ // It doesn't matter if it is a real printer used or we print to a local file
+ // nor if we print to a temp file and move it afterwards by using the ucb.
+ // That will be handled later. see pUCBPrintFile below!
+ pView->ExecPrint( aCheckedArgs, true, false );
+
+ // Ok - may be execution before has finished (or started!) printing.
+ // And may it was a printing to a file.
+ // Now we have to check if we can move the file (if necessary) via UCB to its right location.
+ // Cases:
+ // a) printing finished => move the file directly and forget the watcher thread
+ // b) printing is asynchron and runs currently => start watcher thread and exit this method
+ // This thread make all necessary things by itself.
+ if (!pUCBPrintTempFile)
+ return;
+
+ // a)
+ SfxPrinter* pPrinter = pView->GetPrinter();
+ if ( ! pPrinter->IsPrinting() )
+ ImplUCBPrintWatcher::moveAndDeleteTemp(&pUCBPrintTempFile,sUcbUrl);
+ // b)
+ else
+ {
+ // Note: we create(d) some resource on the heap (thread and temp file).
+ // They will be deleted by the thread automatically if it finishes its run() method.
+ ImplUCBPrintWatcher* pWatcher = new ImplUCBPrintWatcher( pPrinter, pUCBPrintTempFile, sUcbUrl );
+ pWatcher->create();
+ }
+}
+
+void IMPL_PrintListener_DataContainer::Notify( SfxBroadcaster& rBC, const SfxHint& rHint )
+{
+ const SfxPrintingHint* pPrintHint = dynamic_cast<const SfxPrintingHint*>(&rHint);
+ if ( &rBC != m_pObjectShell.get()
+ || !pPrintHint
+ || pPrintHint->GetWhich() == SFX_PRINTABLESTATE_CANCELJOB )
+ return;
+
+ if ( pPrintHint->GetWhich() == css::view::PrintableState_JOB_STARTED )
+ {
+ if ( !m_xPrintJob.is() )
+ m_xPrintJob = new SfxPrintJob_Impl( this );
+ m_aPrintOptions = pPrintHint->GetOptions();
+ }
+
+ std::unique_lock aGuard(m_aMutex);
+ if (!m_aJobListeners.getLength(aGuard))
+ return;
+ view::PrintJobEvent aEvent;
+ aEvent.Source = m_xPrintJob;
+ aEvent.State = pPrintHint->GetWhich();
+
+ comphelper::OInterfaceIteratorHelper4 pIterator(aGuard, m_aJobListeners);
+ aGuard.unlock();
+ while (pIterator.hasMoreElements())
+ pIterator.next()->printJobEvent( aEvent );
+}
+
+void SAL_CALL SfxPrintHelper::addPrintJobListener( const css::uno::Reference< css::view::XPrintJobListener >& xListener )
+{
+ std::unique_lock aGuard(m_pData->m_aMutex);
+ m_pData->m_aJobListeners.addInterface( aGuard, xListener );
+}
+
+void SAL_CALL SfxPrintHelper::removePrintJobListener( const css::uno::Reference< css::view::XPrintJobListener >& xListener )
+{
+ std::unique_lock aGuard(m_pData->m_aMutex);
+ m_pData->m_aJobListeners.removeInterface( aGuard, xListener );
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/printhelper.hxx b/sfx2/source/doc/printhelper.hxx
new file mode 100644
index 0000000000..a38e3d7a49
--- /dev/null
+++ b/sfx2/source/doc/printhelper.hxx
@@ -0,0 +1,68 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SFX2_SOURCE_DOC_PRINTHELPER_HXX
+#define INCLUDED_SFX2_SOURCE_DOC_PRINTHELPER_HXX
+
+#include <memory>
+#include <sal/config.h>
+#include <sfx2/viewsh.hxx>
+#include <sal/types.h>
+
+#include <com/sun/star/view/XPrintable.hpp>
+#include <com/sun/star/view/XPrintJobBroadcaster.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <cppuhelper/implbase.hxx>
+
+struct IMPL_PrintListener_DataContainer;
+class SfxViewShell;
+class SfxPrinter;
+
+class SfxPrintHelper : public cppu::WeakImplHelper
+ < css::view::XPrintable
+ , css::view::XPrintJobBroadcaster
+ , css::lang::XInitialization >
+{
+public:
+
+ SfxPrintHelper() ;
+ virtual ~SfxPrintHelper() override ;
+
+ void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+ virtual void SAL_CALL addPrintJobListener( const css::uno::Reference< css::view::XPrintJobListener >& xListener ) override;
+ virtual void SAL_CALL removePrintJobListener( const css::uno::Reference< css::view::XPrintJobListener >& xListener ) override;
+ virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getPrinter() override;
+ virtual void SAL_CALL setPrinter( const css::uno::Sequence< css::beans::PropertyValue >& seqPrinter ) override;
+ virtual void SAL_CALL print( const css::uno::Sequence< css::beans::PropertyValue >& seqOptions ) override;
+
+private:
+
+ std::unique_ptr<IMPL_PrintListener_DataContainer> m_pData ;
+ void impl_setPrinter(const css::uno::Sequence< css::beans::PropertyValue >& rPrinter,
+ VclPtr<SfxPrinter>& pPrinter,
+ SfxPrinterChangeFlags& nChangeFlags,
+ SfxViewShell*& pViewSh);
+} ;
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/saveastemplatedlg.cxx b/sfx2/source/doc/saveastemplatedlg.cxx
new file mode 100644
index 0000000000..0cc9c2fef5
--- /dev/null
+++ b/sfx2/source/doc/saveastemplatedlg.cxx
@@ -0,0 +1,179 @@
+/* -*- 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/.
+ */
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/string.hxx>
+#include <comphelper/storagehelper.hxx>
+#include <sfx2/sfxresid.hxx>
+#include <sfx2/app.hxx>
+#include <sfx2/fcontnr.hxx>
+#include <sfx2/docfac.hxx>
+#include <sfx2/doctempl.hxx>
+#include <sfx2/docfilt.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <sot/storage.hxx>
+
+#include <com/sun/star/frame/DocumentTemplates.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+
+#include <sfx2/strings.hrc>
+
+#include <saveastemplatedlg.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::frame;
+
+// Class SfxSaveAsTemplateDialog --------------------------------------------------
+
+SfxSaveAsTemplateDialog::SfxSaveAsTemplateDialog(weld::Window* pParent, uno::Reference<frame::XModel> xModel)
+ : GenericDialogController(pParent, "sfx/ui/saveastemplatedlg.ui", "SaveAsTemplateDialog")
+ , m_xLBCategory(m_xBuilder->weld_tree_view("categorylb"))
+ , m_xCBXDefault(m_xBuilder->weld_check_button("defaultcb"))
+ , m_xTemplateNameEdit(m_xBuilder->weld_entry("name_entry"))
+ , m_xOKButton(m_xBuilder->weld_button("ok"))
+ , mnRegionPos(0)
+ , m_xModel(std::move(xModel))
+{
+ m_xLBCategory->append_text(SfxResId(STR_CATEGORY_NONE));
+ initialize();
+ SetCategoryLBEntries(msCategories);
+
+ m_xTemplateNameEdit->connect_changed(LINK(this, SfxSaveAsTemplateDialog, TemplateNameEditHdl));
+ m_xLBCategory->connect_changed(LINK(this, SfxSaveAsTemplateDialog, SelectCategoryHdl));
+ m_xLBCategory->set_size_request(m_xLBCategory->get_approximate_digit_width() * 32,
+ m_xLBCategory->get_height_rows(8));
+ m_xOKButton->connect_clicked(LINK(this, SfxSaveAsTemplateDialog, OkClickHdl));
+
+ m_xOKButton->set_sensitive(false);
+ m_xOKButton->set_label(SfxResId(STR_SAVEDOC));
+}
+
+IMPL_LINK_NOARG(SfxSaveAsTemplateDialog, OkClickHdl, weld::Button&, void)
+{
+ std::unique_ptr<weld::MessageDialog> xQueryDlg(Application::CreateMessageDialog(m_xDialog.get(), VclMessageType::Question,
+ VclButtonsType::YesNo, OUString()));
+ if(!IsTemplateNameUnique())
+ {
+ OUString sQueryMsg(SfxResId(STR_QMSG_TEMPLATE_OVERWRITE));
+ sQueryMsg = sQueryMsg.replaceFirst("$1",msTemplateName);
+ xQueryDlg->set_primary_text(sQueryMsg.replaceFirst("$2", msSelectedCategory));
+
+ if (xQueryDlg->run() == RET_NO)
+ return;
+ }
+
+ if (SaveTemplate())
+ m_xDialog->response(RET_OK);
+ else
+ {
+ OUString sText( SfxResId(STR_ERROR_SAVEAS) );
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(), VclMessageType::Warning,
+ VclButtonsType::Ok, sText.replaceFirst("$1", msTemplateName)));
+ xBox->run();
+ }
+}
+
+IMPL_LINK_NOARG(SfxSaveAsTemplateDialog, TemplateNameEditHdl, weld::Entry&, void)
+{
+ msTemplateName = comphelper::string::strip(m_xTemplateNameEdit->get_text(), ' ');
+ SelectCategoryHdl(*m_xLBCategory);
+}
+
+IMPL_LINK_NOARG(SfxSaveAsTemplateDialog, SelectCategoryHdl, weld::TreeView&, void)
+{
+ if (m_xLBCategory->get_selected_index() == 0)
+ {
+ msSelectedCategory = OUString();
+ m_xOKButton->set_sensitive(false);
+ }
+ else
+ {
+ msSelectedCategory = m_xLBCategory->get_selected_text();
+ m_xOKButton->set_sensitive(!msTemplateName.isEmpty());
+ }
+}
+
+void SfxSaveAsTemplateDialog::initialize()
+{
+ sal_uInt16 nCount = maDocTemplates.GetRegionCount();
+ for (sal_uInt16 i = 0; i < nCount; ++i)
+ {
+ OUString sCategoryName(maDocTemplates.GetFullRegionName(i));
+ msCategories.push_back(sCategoryName);
+ }
+}
+
+void SfxSaveAsTemplateDialog::SetCategoryLBEntries(const std::vector<OUString>& rFolderNames)
+{
+ for (size_t i = 0, n = rFolderNames.size(); i < n; ++i)
+ m_xLBCategory->insert_text(i+1, rFolderNames[i]);
+ m_xLBCategory->select(0);
+}
+
+bool SfxSaveAsTemplateDialog::IsTemplateNameUnique()
+{
+ std::vector<OUString>::iterator it=find(msCategories.begin(), msCategories.end(), msSelectedCategory);
+ mnRegionPos = std::distance(msCategories.begin(), it);
+
+ sal_uInt16 nEntries = maDocTemplates.GetCount(mnRegionPos);
+ for(sal_uInt16 i = 0; i < nEntries; i++)
+ {
+ OUString aName = maDocTemplates.GetName(mnRegionPos, i);
+ if(aName == msTemplateName)
+ return false;
+ }
+
+ return true;
+}
+
+bool SfxSaveAsTemplateDialog::SaveTemplate()
+{
+ uno::Reference< frame::XStorable > xStorable(m_xModel, uno::UNO_QUERY_THROW );
+
+ uno::Reference< frame::XDocumentTemplates > xTemplates(frame::DocumentTemplates::create(comphelper::getProcessComponentContext()) );
+
+ if (!xTemplates->storeTemplate( msSelectedCategory, msTemplateName, xStorable ))
+ return false;
+
+ sal_uInt16 nDocId = maDocTemplates.GetCount(mnRegionPos);
+ OUString sURL = maDocTemplates.GetTemplateTargetURLFromComponent(msSelectedCategory, msTemplateName);
+ bool bIsSaved = maDocTemplates.InsertTemplate( mnRegionPos, nDocId, msTemplateName, sURL);
+
+ if (!bIsSaved)
+ return false;
+
+ if (!sURL.isEmpty() && m_xCBXDefault->get_active())
+ {
+ OUString aServiceName;
+ try
+ {
+ uno::Reference< embed::XStorage > xStorage =
+ comphelper::OStorageHelper::GetStorageFromURL( sURL, embed::ElementModes::READ );
+
+ SotClipboardFormatId nFormat = SotStorage::GetFormatID( xStorage );
+
+ std::shared_ptr<const SfxFilter> pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4ClipBoardId( nFormat );
+
+ if ( pFilter )
+ aServiceName = pFilter->GetServiceName();
+ }
+ catch( uno::Exception& )
+ {}
+
+ if(!aServiceName.isEmpty())
+ SfxObjectFactory::SetStandardTemplate(aServiceName, sURL);
+ }
+
+ maDocTemplates.Update();
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/sfxbasemodel.cxx b/sfx2/source/doc/sfxbasemodel.cxx
new file mode 100644
index 0000000000..985af53135
--- /dev/null
+++ b/sfx2/source/doc/sfxbasemodel.cxx
@@ -0,0 +1,4618 @@
+/* -*- 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 <sal/config.h>
+
+#include <algorithm>
+#include <chrono>
+#include <memory>
+#include <optional>
+#include <config_features.h>
+
+#include <sfx2/sfxbasemodel.hxx>
+
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/task/XInteractionHandler.hpp>
+#include <com/sun/star/task/ErrorCodeIOException.hpp>
+#include <com/sun/star/task/ErrorCodeRequest2.hpp>
+#include <com/sun/star/view/XSelectionSupplier.hpp>
+#include <com/sun/star/view/XPrintJobListener.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/NoSupportException.hpp>
+#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
+#include <com/sun/star/lang/NotInitializedException.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/IllegalArgumentIOException.hpp>
+#include <com/sun/star/frame/XUntitledNumbers.hpp>
+#include <com/sun/star/frame/DoubleInitializationException.hpp>
+#include <com/sun/star/embed/XStorage.hpp>
+#include <com/sun/star/document/XStorageChangeListener.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/XPropertySetInfo.hpp>
+#include <com/sun/star/container/XIndexContainer.hpp>
+#include <com/sun/star/script/provider/theMasterScriptProviderFactory.hpp>
+#include <com/sun/star/script/provider/XScriptProvider.hpp>
+#include <com/sun/star/ui/UIConfigurationManager.hpp>
+#include <com/sun/star/embed/ElementModes.hpp>
+#include <com/sun/star/embed/Aspects.hpp>
+#include <com/sun/star/document/DocumentProperties.hpp>
+#include <com/sun/star/frame/XTransientDocumentsDocumentContentFactory.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/ucb/ContentCreationException.hpp>
+#include <com/sun/star/ucb/CommandAbortedException.hpp>
+#include <com/sun/star/util/XCloneable.hpp>
+#include <com/sun/star/util/InvalidStateException.hpp>
+#include <com/sun/star/util/CloseVetoException.hpp>
+#include <comphelper/enumhelper.hxx>
+#include <comphelper/indexedpropertyvalues.hxx>
+#include <comphelper/interfacecontainer3.hxx>
+#include <comphelper/string.hxx>
+
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/lok.hxx>
+#include <comphelper/multicontainer2.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/sequenceashashmap.hxx>
+#include <comphelper/namedvaluecollection.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
+#include <svl/itemset.hxx>
+#include <svl/stritem.hxx>
+#include <svl/eitem.hxx>
+#include <svl/grabbagitem.hxx>
+#include <tools/urlobj.hxx>
+#include <tools/debug.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/svborder.hxx>
+#include <unotools/tempfile.hxx>
+#include <osl/mutex.hxx>
+#include <comphelper/errcode.hxx>
+#include <vcl/filter/SvmWriter.hxx>
+#include <vcl/salctype.hxx>
+#include <vcl/gdimtf.hxx>
+#include <comphelper/fileformat.h>
+#include <comphelper/servicehelper.hxx>
+#include <comphelper/storagehelper.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+#include <vcl/transfer.hxx>
+#include <svtools/ehdl.hxx>
+#include <svtools/sfxecode.hxx>
+#include <sal/log.hxx>
+#include <framework/configimporter.hxx>
+#include <framework/titlehelper.hxx>
+#include <comphelper/numberedcollection.hxx>
+#include <unotools/ucbhelper.hxx>
+#include <ucbhelper/content.hxx>
+
+#include <sfx2/sfxbasecontroller.hxx>
+#include <sfx2/viewfac.hxx>
+#include <workwin.hxx>
+#include <sfx2/signaturestate.hxx>
+#include <sfx2/sfxuno.hxx>
+#include <objshimp.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/viewsh.hxx>
+#include <sfx2/docfile.hxx>
+#include <sfx2/docfilt.hxx>
+#include <sfx2/dispatch.hxx>
+#include <sfx2/module.hxx>
+#include <basic/basmgr.hxx>
+#include <sfx2/event.hxx>
+#include <eventsupplier.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <sfx2/strings.hrc>
+#include <sfx2/app.hxx>
+#include <sfx2/docfac.hxx>
+#include <sfx2/fcontnr.hxx>
+#include <sfx2/docstoragemodifylistener.hxx>
+#include <sfx2/brokenpackageint.hxx>
+#include "graphhelp.hxx"
+#include <docundomanager.hxx>
+#include <openurlhint.hxx>
+#include <sfx2/msgpool.hxx>
+#include <sfx2/DocumentMetadataAccess.hxx>
+#include "printhelper.hxx"
+#include <sfx2/sfxresid.hxx>
+#include <sfx2/filedlghelper.hxx>
+#include <comphelper/profilezone.hxx>
+#include <vcl/threadex.hxx>
+#include <unotools/mediadescriptor.hxx>
+
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+
+// namespaces
+
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using ::com::sun::star::beans::PropertyValue;
+using ::com::sun::star::document::CmisProperty;
+using ::com::sun::star::frame::XFrame;
+using ::com::sun::star::frame::XController;
+using ::com::sun::star::frame::XController2;
+using ::com::sun::star::lang::IllegalArgumentException;
+using ::com::sun::star::io::IOException;
+using ::com::sun::star::uno::Sequence;
+using ::com::sun::star::document::XDocumentRecovery;
+using ::com::sun::star::document::XUndoManager;
+using ::com::sun::star::document::XUndoAction;
+using ::com::sun::star::frame::XModel;
+
+namespace {
+
+/** This Listener is used to get notified when the XDocumentProperties of the
+ XModel change.
+ */
+class SfxDocInfoListener_Impl : public ::cppu::WeakImplHelper<
+ util::XModifyListener >
+{
+
+public:
+ SfxObjectShell& m_rShell;
+
+ explicit SfxDocInfoListener_Impl( SfxObjectShell& i_rDoc )
+ : m_rShell(i_rDoc)
+ { };
+
+ virtual void SAL_CALL disposing( const lang::EventObject& ) override;
+ virtual void SAL_CALL modified( const lang::EventObject& ) override;
+};
+
+}
+
+void SAL_CALL SfxDocInfoListener_Impl::modified( const lang::EventObject& )
+{
+ SolarMutexGuard aSolarGuard;
+
+ // notify changes to the SfxObjectShell
+ m_rShell.FlushDocInfo();
+}
+
+void SAL_CALL SfxDocInfoListener_Impl::disposing( const lang::EventObject& )
+{
+}
+
+
+// impl. declarations
+
+
+struct IMPL_SfxBaseModel_DataContainer : public ::sfx2::IModifiableDocument
+{
+ // counter for SfxBaseModel instances created.
+ static sal_Int64 g_nInstanceCounter ;
+ SfxObjectShellRef m_pObjectShell ;
+ OUString m_sURL ;
+ OUString m_sRuntimeUID ;
+ OUString m_aPreusedFilterName ;
+ comphelper::OInterfaceContainerHelper3<view::XPrintJobListener> m_aPrintJobListeners;
+ comphelper::OInterfaceContainerHelper3<lang::XEventListener> m_aEventListeners;
+ comphelper::OInterfaceContainerHelper3<util::XModifyListener> m_aModifyListeners;
+ comphelper::OInterfaceContainerHelper3<document::XEventListener> m_aDocumentEventListeners1;
+ comphelper::OInterfaceContainerHelper3<document::XDocumentEventListener> m_aDocumentEventListeners2;
+ comphelper::OInterfaceContainerHelper3<document::XStorageChangeListener> m_aStorageChangeListeners;
+ comphelper::OInterfaceContainerHelper3<util::XCloseListener> m_aCloseListeners;
+ std::unordered_map<css::uno::Reference< css::drawing::XShape >,
+ std::vector<css::uno::Reference< css::document::XShapeEventListener >>> maShapeListeners;
+ Reference< XInterface > m_xParent ;
+ Reference< frame::XController > m_xCurrent ;
+ Reference< document::XDocumentProperties > m_xDocumentProperties ;
+ Reference< script::XStarBasicAccess > m_xStarBasicAccess ;
+ Reference< container::XNameReplace > m_xEvents ;
+ Sequence< beans::PropertyValue> m_seqArguments ;
+ std::vector< Reference< frame::XController > > m_seqControllers ;
+ Reference< container::XIndexAccess > m_contViewData ;
+ sal_uInt16 m_nControllerLockCount ;
+ bool m_bClosed ;
+ bool m_bClosing ;
+ bool m_bSaving ;
+ bool m_bSuicide ;
+ bool m_bExternalTitle ;
+ bool m_bDisposing ;
+ Reference< view::XPrintable> m_xPrintable ;
+ Reference< ui::XUIConfigurationManager2 > m_xUIConfigurationManager;
+ ::rtl::Reference< ::sfx2::DocumentStorageModifyListener > m_pStorageModifyListen ;
+ OUString m_sModuleIdentifier ;
+ Reference< frame::XTitle > m_xTitleHelper ;
+ Reference< frame::XUntitledNumbers > m_xNumberedControllers ;
+ Reference< rdf::XDocumentMetadataAccess> m_xDocumentMetadata ;
+ ::rtl::Reference< ::sfx2::DocumentUndoManager > m_pDocumentUndoManager ;
+ Sequence< document::CmisProperty> m_cmisProperties ;
+ std::shared_ptr<SfxGrabBagItem> m_xGrabBagItem ;
+ std::optional<std::chrono::steady_clock::time_point> m_oDirtyTimestamp ;
+
+ IMPL_SfxBaseModel_DataContainer( ::osl::Mutex& rMutex, SfxObjectShell* pObjectShell )
+ : m_pObjectShell ( pObjectShell )
+ , m_aPrintJobListeners ( rMutex )
+ , m_aEventListeners ( rMutex )
+ , m_aModifyListeners ( rMutex )
+ , m_aDocumentEventListeners1( rMutex )
+ , m_aDocumentEventListeners2( rMutex )
+ , m_aStorageChangeListeners ( rMutex )
+ , m_aCloseListeners ( rMutex )
+ , m_nControllerLockCount ( 0 )
+ , m_bClosed ( false )
+ , m_bClosing ( false )
+ , m_bSaving ( false )
+ , m_bSuicide ( false )
+ , m_bExternalTitle ( false )
+ , m_bDisposing ( false )
+ {
+ // increase global instance counter.
+ ++g_nInstanceCounter;
+ // set own Runtime UID
+ m_sRuntimeUID = OUString::number( g_nInstanceCounter );
+ }
+
+ virtual ~IMPL_SfxBaseModel_DataContainer()
+ {
+ }
+
+ // ::sfx2::IModifiableDocument
+ virtual void storageIsModified() override
+ {
+ if ( m_pObjectShell.is() && !m_pObjectShell->IsModified() )
+ m_pObjectShell->SetModified();
+ }
+
+ void impl_setDocumentProperties(
+ const Reference< document::XDocumentProperties >& );
+
+ Reference<rdf::XDocumentMetadataAccess> GetDMA()
+ {
+ if (!m_xDocumentMetadata.is())
+ {
+ OSL_ENSURE(m_pObjectShell.is(), "GetDMA: no object shell?");
+ if (!m_pObjectShell.is())
+ {
+ return nullptr;
+ }
+
+ const Reference<XComponentContext> xContext(
+ ::comphelper::getProcessComponentContext());
+ const Reference<frame::XModel> xModel(
+ m_pObjectShell->GetModel());
+ const Reference<lang::XMultiComponentFactory> xMsf(
+ xContext->getServiceManager());
+ const Reference<frame::
+ XTransientDocumentsDocumentContentFactory> xTDDCF(
+ xMsf->createInstanceWithContext(
+ "com.sun.star.frame.TransientDocumentsDocumentContentFactory",
+ xContext),
+ UNO_QUERY_THROW);
+ const Reference<ucb::XContent> xContent(
+ xTDDCF->createDocumentContent(xModel) );
+ OSL_ENSURE(xContent.is(), "GetDMA: cannot create DocumentContent");
+ if (!xContent.is())
+ {
+ return nullptr;
+ }
+ OUString uri = xContent->getIdentifier()->getContentIdentifier();
+ OSL_ENSURE(!uri.isEmpty(), "GetDMA: empty uri?");
+ if (!uri.isEmpty() && !uri.endsWith("/"))
+ {
+ uri += "/";
+ }
+
+ m_xDocumentMetadata = new ::sfx2::DocumentMetadataAccess(
+ xContext, *m_pObjectShell, uri);
+ }
+ return m_xDocumentMetadata;
+ }
+
+ Reference<rdf::XDocumentMetadataAccess> CreateDMAUninitialized()
+ {
+ return (m_pObjectShell.is())
+ ? new ::sfx2::DocumentMetadataAccess(
+ ::comphelper::getProcessComponentContext(), *m_pObjectShell)
+ : nullptr;
+ }
+
+ void setModifiedForAutoSave(bool val)
+ {
+ if (val)
+ {
+ if (!m_oDirtyTimestamp)
+ m_oDirtyTimestamp.emplace(std::chrono::steady_clock::now());
+ }
+ else
+ {
+ m_oDirtyTimestamp.reset();
+ }
+ }
+};
+
+// static member initialization.
+sal_Int64 IMPL_SfxBaseModel_DataContainer::g_nInstanceCounter = 0;
+
+namespace {
+
+// Listener that forwards notifications from the PrintHelper to the "real" listeners
+class SfxPrintHelperListener_Impl : public ::cppu::WeakImplHelper< view::XPrintJobListener >
+{
+public:
+ IMPL_SfxBaseModel_DataContainer* m_pData;
+ explicit SfxPrintHelperListener_Impl( IMPL_SfxBaseModel_DataContainer* pData )
+ : m_pData( pData )
+ {}
+
+ virtual void SAL_CALL disposing( const lang::EventObject& aEvent ) override ;
+ virtual void SAL_CALL printJobEvent( const view::PrintJobEvent& rEvent ) override;
+};
+
+}
+
+void SAL_CALL SfxPrintHelperListener_Impl::disposing( const lang::EventObject& )
+{
+ m_pData->m_xPrintable = nullptr;
+}
+
+void SAL_CALL SfxPrintHelperListener_Impl::printJobEvent( const view::PrintJobEvent& rEvent )
+{
+ if ( m_pData->m_aPrintJobListeners.getLength() )
+ {
+ m_pData->m_aPrintJobListeners.notifyEach(&view::XPrintJobListener::printJobEvent, rEvent);
+ }
+}
+
+namespace {
+
+// SfxOwnFramesLocker ====================================================================================
+// allows to lock all the frames related to the provided SfxObjectShell
+class SfxOwnFramesLocker
+{
+ Sequence< Reference< frame::XFrame > > m_aLockedFrames;
+
+ static vcl::Window* GetVCLWindow( const Reference< frame::XFrame >& xFrame );
+public:
+ explicit SfxOwnFramesLocker( SfxObjectShell const * ObjechShell );
+ ~SfxOwnFramesLocker();
+};
+
+}
+
+SfxOwnFramesLocker::SfxOwnFramesLocker( SfxObjectShell const * pObjectShell )
+{
+ if ( !pObjectShell )
+ return;
+
+ for ( SfxViewFrame *pFrame = SfxViewFrame::GetFirst( pObjectShell );
+ pFrame;
+ pFrame = SfxViewFrame::GetNext( *pFrame, pObjectShell )
+ )
+ {
+ SfxFrame& rSfxFrame = pFrame->GetFrame();
+ try
+ {
+ // get vcl window related to the frame and lock it if it is still not locked
+ const Reference< frame::XFrame >& xFrame = rSfxFrame.GetFrameInterface();
+ vcl::Window* pWindow = GetVCLWindow( xFrame );
+ if ( !pWindow )
+ throw RuntimeException();
+
+ if ( pWindow->IsEnabled() )
+ {
+ pWindow->Disable();
+
+ try
+ {
+ sal_Int32 nLen = m_aLockedFrames.getLength();
+ m_aLockedFrames.realloc( nLen + 1 );
+ m_aLockedFrames.getArray()[nLen] = xFrame;
+ }
+ catch( Exception& )
+ {
+ pWindow->Enable();
+ throw;
+ }
+ }
+ }
+ catch( Exception& )
+ {
+ OSL_FAIL( "Not possible to lock the frame window!" );
+ }
+ }
+}
+
+SfxOwnFramesLocker::~SfxOwnFramesLocker()
+{
+ for ( auto& rFrame : asNonConstRange(m_aLockedFrames) )
+ {
+ try
+ {
+ if ( rFrame.is() )
+ {
+ // get vcl window related to the frame and unlock it
+ vcl::Window* pWindow = GetVCLWindow( rFrame );
+ if ( !pWindow )
+ throw RuntimeException();
+
+ pWindow->Enable();
+
+ rFrame.clear();
+ }
+ }
+ catch( Exception& )
+ {
+ OSL_FAIL( "Can't unlock the frame window!" );
+ }
+ }
+}
+
+vcl::Window* SfxOwnFramesLocker::GetVCLWindow( const Reference< frame::XFrame >& xFrame )
+{
+ VclPtr<vcl::Window> pWindow;
+
+ if ( xFrame.is() )
+ {
+ Reference< awt::XWindow > xWindow = xFrame->getContainerWindow();
+ if ( xWindow.is() )
+ pWindow = VCLUnoHelper::GetWindow( xWindow );
+ }
+
+ return pWindow;
+}
+
+namespace {
+
+// SfxSaveGuard ====================================================================================
+class SfxSaveGuard
+{
+ private:
+ Reference< frame::XModel > m_xModel;
+ IMPL_SfxBaseModel_DataContainer* m_pData;
+ std::unique_ptr<SfxOwnFramesLocker> m_pFramesLock;
+
+ SfxSaveGuard(SfxSaveGuard const &) = delete;
+ void operator =(const SfxSaveGuard&) = delete;
+
+ public:
+ SfxSaveGuard(const Reference< frame::XModel >& xModel ,
+ IMPL_SfxBaseModel_DataContainer* pData);
+ ~SfxSaveGuard();
+};
+
+}
+
+SfxSaveGuard::SfxSaveGuard(const Reference< frame::XModel >& xModel ,
+ IMPL_SfxBaseModel_DataContainer* pData)
+ : m_xModel ( xModel )
+ , m_pData ( pData )
+{
+ if ( m_pData->m_bClosed )
+ throw lang::DisposedException("Object already disposed.");
+
+ m_pData->m_bSaving = true;
+ m_pFramesLock.reset(new SfxOwnFramesLocker( m_pData->m_pObjectShell.get() ));
+}
+
+SfxSaveGuard::~SfxSaveGuard()
+{
+ m_pFramesLock.reset();
+
+ m_pData->m_bSaving = false;
+
+ // m_bSuicide was set e.g. in case someone tried to close a document, while it was used for
+ // storing at the same time. Further m_bSuicide was set to sal_True only if close(sal_True) was called.
+ // So the ownership was delegated to the place where a veto exception was thrown.
+ // Now we have to call close() again and delegate the ownership to the next one, which
+ // can't accept that. Close(sal_False) can't work in this case. Because then the document will may be never closed...
+
+ if ( !m_pData->m_bSuicide )
+ return;
+
+ // Reset this state. In case the new close() request is not accepted by someone else...
+ // it's not a good idea to have two "owners" for close.-)
+ m_pData->m_bSuicide = false;
+ try
+ {
+ Reference< util::XCloseable > xClose(m_xModel, UNO_QUERY);
+ if (xClose.is())
+ xClose->close(true);
+ }
+ catch(const util::CloseVetoException&)
+ {}
+}
+
+SfxBaseModel::SfxBaseModel( SfxObjectShell *pObjectShell )
+: BaseMutex()
+, m_pData( std::make_shared<IMPL_SfxBaseModel_DataContainer>( m_aMutex, pObjectShell ) )
+, m_bSupportEmbeddedScripts( pObjectShell && pObjectShell->Get_Impl() && !pObjectShell->Get_Impl()->m_bNoBasicCapabilities )
+, m_bSupportDocRecovery( pObjectShell && pObjectShell->Get_Impl() && pObjectShell->Get_Impl()->m_bDocRecoverySupport )
+{
+ if ( pObjectShell != nullptr )
+ {
+ StartListening( *pObjectShell ) ;
+ }
+}
+
+// destructor
+SfxBaseModel::~SfxBaseModel()
+{
+}
+
+// XInterface
+Any SAL_CALL SfxBaseModel::queryInterface( const uno::Type& rType )
+{
+ if ( ( !m_bSupportEmbeddedScripts && rType.equals( cppu::UnoType<document::XEmbeddedScripts>::get() ) )
+ || ( !m_bSupportDocRecovery && (rType.equals( cppu::UnoType<XDocumentRecovery>::get() ) || rType.equals( cppu::UnoType<XDocumentRecovery2>::get() )) )
+ )
+ return Any();
+
+ return SfxBaseModel_Base::queryInterface( rType );
+}
+
+
+// XTypeProvider
+
+
+namespace
+{
+ void lcl_stripType( Sequence< uno::Type >& io_rTypes, const uno::Type& i_rTypeToStrip )
+ {
+ Sequence< uno::Type > aStrippedTypes( io_rTypes.getLength() - 1 );
+ ::std::remove_copy_if(
+ std::cbegin(io_rTypes),
+ std::cend(io_rTypes),
+ aStrippedTypes.getArray(),
+ [&i_rTypeToStrip](const uno::Type& aType) { return aType == i_rTypeToStrip; }
+ );
+ io_rTypes = aStrippedTypes;
+ }
+}
+
+Sequence< uno::Type > SAL_CALL SfxBaseModel::getTypes()
+{
+ Sequence< uno::Type > aTypes( SfxBaseModel_Base::getTypes() );
+
+ if ( !m_bSupportEmbeddedScripts )
+ lcl_stripType( aTypes, cppu::UnoType<document::XEmbeddedScripts>::get() );
+
+ if ( !m_bSupportDocRecovery )
+ lcl_stripType( aTypes, cppu::UnoType<XDocumentRecovery2>::get() );
+
+ return aTypes;
+}
+
+
+// XTypeProvider
+
+
+Sequence< sal_Int8 > SAL_CALL SfxBaseModel::getImplementationId()
+{
+ return css::uno::Sequence<sal_Int8>();
+}
+
+
+// XStarBasicAccess
+
+#if HAVE_FEATURE_SCRIPTING
+
+static Reference< script::XStarBasicAccess > implGetStarBasicAccess( SfxObjectShell const * pObjectShell )
+{
+ Reference< script::XStarBasicAccess > xRet;
+
+#if !HAVE_FEATURE_SCRIPTING
+ (void) pObjectShell;
+#else
+ if( pObjectShell )
+ {
+ BasicManager* pMgr = pObjectShell->GetBasicManager();
+ xRet = getStarBasicAccess( pMgr );
+ }
+#endif
+ return xRet;
+}
+
+#endif
+
+Reference< container::XNameContainer > SAL_CALL SfxBaseModel::getLibraryContainer()
+{
+#if !HAVE_FEATURE_SCRIPTING
+ Reference< container::XNameContainer > dummy;
+
+ return dummy;
+#else
+ SfxModelGuard aGuard( *this );
+
+ Reference< script::XStarBasicAccess >& rxAccess = m_pData->m_xStarBasicAccess;
+ if( !rxAccess.is() && m_pData->m_pObjectShell.is() )
+ rxAccess = implGetStarBasicAccess( m_pData->m_pObjectShell.get() );
+
+ Reference< container::XNameContainer > xRet;
+ if( rxAccess.is() )
+ xRet = rxAccess->getLibraryContainer();
+ return xRet;
+#endif
+}
+
+/**___________________________________________________________________________________________________
+ @seealso XStarBasicAccess
+*/
+void SAL_CALL SfxBaseModel::createLibrary( const OUString& LibName, const OUString& Password,
+ const OUString& ExternalSourceURL, const OUString& LinkTargetURL )
+{
+#if !HAVE_FEATURE_SCRIPTING
+ (void) LibName;
+ (void) Password;
+ (void) ExternalSourceURL;
+ (void) LinkTargetURL;
+#else
+ SfxModelGuard aGuard( *this );
+
+ Reference< script::XStarBasicAccess >& rxAccess = m_pData->m_xStarBasicAccess;
+ if( !rxAccess.is() && m_pData->m_pObjectShell.is() )
+ rxAccess = implGetStarBasicAccess( m_pData->m_pObjectShell.get() );
+
+ if( rxAccess.is() )
+ rxAccess->createLibrary( LibName, Password, ExternalSourceURL, LinkTargetURL );
+#endif
+}
+
+/**___________________________________________________________________________________________________
+ @seealso XStarBasicAccess
+*/
+void SAL_CALL SfxBaseModel::addModule( const OUString& LibraryName, const OUString& ModuleName,
+ const OUString& Language, const OUString& Source )
+{
+#if !HAVE_FEATURE_SCRIPTING
+ (void) LibraryName;
+ (void) ModuleName;
+ (void) Language;
+ (void) Source;
+#else
+ SfxModelGuard aGuard( *this );
+
+ Reference< script::XStarBasicAccess >& rxAccess = m_pData->m_xStarBasicAccess;
+ if( !rxAccess.is() && m_pData->m_pObjectShell.is() )
+ rxAccess = implGetStarBasicAccess( m_pData->m_pObjectShell.get() );
+
+ if( rxAccess.is() )
+ rxAccess->addModule( LibraryName, ModuleName, Language, Source );
+#endif
+}
+
+/**___________________________________________________________________________________________________
+ @seealso XStarBasicAccess
+*/
+void SAL_CALL SfxBaseModel::addDialog( const OUString& LibraryName, const OUString& DialogName,
+ const Sequence< sal_Int8 >& Data )
+{
+#if !HAVE_FEATURE_SCRIPTING
+ (void) LibraryName;
+ (void) DialogName;
+ (void) Data;
+#else
+ SfxModelGuard aGuard( *this );
+
+ Reference< script::XStarBasicAccess >& rxAccess = m_pData->m_xStarBasicAccess;
+ if( !rxAccess.is() && m_pData->m_pObjectShell.is() )
+ rxAccess = implGetStarBasicAccess( m_pData->m_pObjectShell.get() );
+
+ if( rxAccess.is() )
+ rxAccess->addDialog( LibraryName, DialogName, Data );
+#endif
+}
+
+
+// XChild
+
+
+Reference< XInterface > SAL_CALL SfxBaseModel::getParent()
+{
+ SfxModelGuard aGuard( *this );
+
+ return m_pData->m_xParent;
+}
+
+
+// XChild
+
+
+void SAL_CALL SfxBaseModel::setParent(const Reference< XInterface >& Parent)
+{
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+ m_pData->m_xParent = Parent;
+}
+
+
+// XChild
+
+
+void SAL_CALL SfxBaseModel::dispose()
+{
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+
+ if ( !m_pData->m_bClosed )
+ {
+ // gracefully accept wrong dispose calls instead of close call
+ // and try to make it work (may be really disposed later!)
+ try
+ {
+ close( true );
+ }
+ catch ( util::CloseVetoException& )
+ {
+ }
+
+ return;
+ }
+
+ if ( m_pData->m_bDisposing )
+ return;
+ m_pData->m_bDisposing = true;
+
+ if ( m_pData->m_pStorageModifyListen.is() )
+ {
+ m_pData->m_pStorageModifyListen->dispose();
+ m_pData->m_pStorageModifyListen = nullptr;
+ }
+
+ if ( m_pData->m_pDocumentUndoManager.is() )
+ {
+ m_pData->m_pDocumentUndoManager->disposing();
+ m_pData->m_pDocumentUndoManager = nullptr;
+ }
+
+ lang::EventObject aEvent( static_cast<frame::XModel *>(this) );
+ m_pData->m_aPrintJobListeners.disposeAndClear( aEvent );
+ m_pData->m_aEventListeners.disposeAndClear( aEvent );
+ m_pData->m_aModifyListeners.disposeAndClear( aEvent );
+ m_pData->m_aDocumentEventListeners1.disposeAndClear( aEvent );
+ m_pData->m_aDocumentEventListeners2.disposeAndClear( aEvent );
+ m_pData->m_aStorageChangeListeners.disposeAndClear( aEvent );
+ m_pData->m_aCloseListeners.disposeAndClear( aEvent );
+
+ m_pData->m_xDocumentProperties.clear();
+
+ m_pData->m_xDocumentMetadata.clear();
+
+ if ( m_pData->m_pObjectShell.is() )
+ {
+ EndListening( *m_pData->m_pObjectShell );
+ }
+
+ m_pData->m_xCurrent.clear();
+ m_pData->m_seqControllers.clear();
+
+ // m_pData member must be set to zero before delete is called to
+ // force disposed exception whenever someone tries to access our
+ // instance while in the dtor.
+ m_pData.reset();
+}
+
+
+// XChild
+
+
+void SAL_CALL SfxBaseModel::addEventListener( const Reference< lang::XEventListener >& aListener )
+{
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+ m_pData->m_aEventListeners.addInterface( aListener );
+}
+
+
+// XChild
+
+
+void SAL_CALL SfxBaseModel::removeEventListener( const Reference< lang::XEventListener >& aListener )
+{
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+ m_pData->m_aEventListeners.removeInterface( aListener );
+}
+
+void
+IMPL_SfxBaseModel_DataContainer::impl_setDocumentProperties(
+ const Reference< document::XDocumentProperties >& rxNewDocProps)
+{
+ m_xDocumentProperties.set(rxNewDocProps, UNO_SET_THROW);
+ if (m_pObjectShell.is())
+ {
+ Reference<util::XModifyBroadcaster> const xMB(
+ m_xDocumentProperties, UNO_QUERY_THROW);
+ xMB->addModifyListener(new SfxDocInfoListener_Impl(*m_pObjectShell));
+ }
+}
+
+// document::XDocumentPropertiesSupplier:
+Reference< document::XDocumentProperties > SAL_CALL
+SfxBaseModel::getDocumentProperties()
+{
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+ if ( !m_pData->m_xDocumentProperties.is() )
+ {
+ Reference< document::XDocumentProperties > xDocProps(
+ document::DocumentProperties::create( ::comphelper::getProcessComponentContext() ) );
+ m_pData->impl_setDocumentProperties(xDocProps);
+ }
+
+ return m_pData->m_xDocumentProperties;
+}
+
+
+// lang::XEventListener
+
+
+void SAL_CALL SfxBaseModel::disposing( const lang::EventObject& aObject )
+{
+ SolarMutexGuard aGuard;
+ if ( impl_isDisposed() )
+ return;
+
+ Reference< util::XModifyListener > xMod( aObject.Source, UNO_QUERY );
+ Reference< lang::XEventListener > xListener( aObject.Source, UNO_QUERY );
+ Reference< document::XEventListener > xDocListener( aObject.Source, UNO_QUERY );
+
+ if ( xMod.is() )
+ m_pData->m_aModifyListeners.removeInterface( xMod );
+ else if ( xListener.is() )
+ m_pData->m_aEventListeners.removeInterface( xListener );
+ else if ( xDocListener.is() )
+ m_pData->m_aDocumentEventListeners1.removeInterface( xDocListener );
+}
+
+
+// frame::XModel
+
+
+sal_Bool SAL_CALL SfxBaseModel::attachResource( const OUString& rURL ,
+ const Sequence< beans::PropertyValue >& rArgs )
+{
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+ if ( rURL.isEmpty() && rArgs.getLength() == 1 && rArgs[0].Name == "SetEmbedded" )
+ {
+ // allows to set a windowless document to EMBEDDED state
+ // but _only_ before load() or initNew() methods
+ if ( m_pData->m_pObjectShell.is() && !m_pData->m_pObjectShell->GetMedium() )
+ {
+ bool bEmb(false);
+ if ( ( rArgs[0].Value >>= bEmb ) && bEmb )
+ m_pData->m_pObjectShell->SetCreateMode_Impl( SfxObjectCreateMode::EMBEDDED );
+ }
+
+ return true;
+ }
+
+ if ( m_pData->m_pObjectShell.is() )
+ {
+ m_pData->m_sURL = rURL;
+
+ SfxObjectShell* pObjectShell = m_pData->m_pObjectShell.get();
+
+ Sequence< sal_Int32 > aWinExtent;
+ for (const beans::PropertyValue & rProp : rArgs)
+ {
+ if (rProp.Name == "WinExtent" && (rProp.Value >>= aWinExtent) && ( aWinExtent.getLength() == 4 ) )
+ {
+ tools::Rectangle aVisArea( aWinExtent[0], aWinExtent[1], aWinExtent[2], aWinExtent[3] );
+ aVisArea = OutputDevice::LogicToLogic(aVisArea, MapMode(MapUnit::Map100thMM), MapMode(pObjectShell->GetMapUnit()));
+ pObjectShell->SetVisArea( aVisArea );
+ }
+ bool bBreakMacroSign = false;
+ if ( rProp.Name == "BreakMacroSignature" && (rProp.Value >>= bBreakMacroSign) )
+ {
+ pObjectShell->BreakMacroSign_Impl( bBreakMacroSign );
+ }
+ bool bMacroEventRead = false;
+ if ( rProp.Name == "MacroEventRead" && (rProp.Value >>= bMacroEventRead) && bMacroEventRead)
+ {
+ pObjectShell->SetMacroCallsSeenWhileLoading();
+ }
+ }
+ Sequence<beans::PropertyValue> aStrippedArgs(rArgs.getLength());
+ beans::PropertyValue* pStripped = aStrippedArgs.getArray();
+ for (const beans::PropertyValue & rProp : rArgs)
+ {
+ if (rProp.Name == "WinExtent"
+ || rProp.Name == "BreakMacroSignature"
+ || rProp.Name == "MacroEventRead"
+ || rProp.Name == "Stream"
+ || rProp.Name == "InputStream"
+ || rProp.Name == "URL"
+ || rProp.Name == "Frame"
+ || rProp.Name == "Password"
+ || rProp.Name == "EncryptionData")
+ continue;
+ *pStripped++ = rProp;
+ }
+ aStrippedArgs.realloc(pStripped - aStrippedArgs.getArray());
+
+ // TODO/LATER: all the parameters that are accepted by ItemSet of the DocShell must be removed here
+
+ m_pData->m_seqArguments = aStrippedArgs;
+
+ SfxMedium* pMedium = pObjectShell->GetMedium();
+ if ( pMedium )
+ {
+ SfxAllItemSet aSet( pObjectShell->GetPool() );
+ TransformParameters( SID_OPENDOC, rArgs, aSet );
+
+ // the arguments are not allowed to reach the medium
+ aSet.ClearItem( SID_FILE_NAME );
+ aSet.ClearItem( SID_FILLFRAME );
+
+ pMedium->GetItemSet().Put( aSet );
+ const SfxStringItem* pItem = aSet.GetItem<SfxStringItem>(SID_FILTER_NAME, false);
+ if ( pItem )
+ pMedium->SetFilter(
+ pObjectShell->GetFactory().GetFilterContainer()->GetFilter4FilterName( pItem->GetValue() ) );
+
+ const SfxStringItem* pTitleItem = aSet.GetItem<SfxStringItem>(SID_DOCINFO_TITLE, false);
+ if ( pTitleItem )
+ {
+ SfxViewFrame* pFrame = SfxViewFrame::GetFirst( pObjectShell );
+ if ( pFrame )
+ pFrame->UpdateTitle();
+ }
+ }
+ }
+
+ return true ;
+}
+
+
+// frame::XModel
+
+
+OUString SAL_CALL SfxBaseModel::getURL()
+{
+ SfxModelGuard aGuard( *this );
+ return m_pData->m_sURL ;
+}
+
+
+// frame::XModel
+
+Sequence< beans::PropertyValue > SAL_CALL SfxBaseModel::getArgs()
+{
+ return getArgs2({});
+}
+
+// frame::XModel3
+
+Sequence< beans::PropertyValue > SAL_CALL SfxBaseModel::getArgs2(const Sequence<OUString> & requestedArgsSeq )
+{
+ SfxModelGuard aGuard( *this );
+
+ if (!SfxApplication::Get()) // tdf#113755
+ {
+ SAL_WARN("sfx.appl", "Unexpected operations on model");
+ return m_pData->m_seqArguments;
+ }
+
+ std::set<std::u16string_view> requestedArgs;
+ for (OUString const & s : requestedArgsSeq)
+ requestedArgs.insert(s);
+
+ if ( m_pData->m_pObjectShell.is() )
+ {
+ Sequence< beans::PropertyValue > seqArgsNew;
+ Sequence< beans::PropertyValue > seqArgsOld;
+ SfxAllItemSet aSet( m_pData->m_pObjectShell->GetPool() );
+
+ // we need to know which properties are supported by the transformer
+ // hopefully it is a temporary solution, I guess nonconvertable properties
+ // should not be supported so then there will be only ItemSet from medium
+
+ TransformItems( SID_OPENDOC, m_pData->m_pObjectShell->GetMedium()->GetItemSet(), seqArgsNew );
+ TransformParameters( SID_OPENDOC, m_pData->m_seqArguments, aSet );
+ TransformItems( SID_OPENDOC, aSet, seqArgsOld );
+
+ sal_Int32 nNewLength = seqArgsNew.getLength();
+
+ if (requestedArgs.empty() || requestedArgs.count(u"WinExtent"))
+ {
+ // "WinExtent" property should be updated always.
+ // We can store it now to overwrite an old value
+ // since it is not from ItemSet
+ tools::Rectangle aTmpRect = m_pData->m_pObjectShell->GetVisArea( ASPECT_CONTENT );
+ aTmpRect = OutputDevice::LogicToLogic(aTmpRect, MapMode(m_pData->m_pObjectShell->GetMapUnit()), MapMode(MapUnit::Map100thMM));
+
+ Sequence< sal_Int32 > aRectSeq
+ {
+ o3tl::narrowing<int>(aTmpRect.Left()),
+ o3tl::narrowing<int>(aTmpRect.Top()),
+ o3tl::narrowing<int>(aTmpRect.IsWidthEmpty() ? aTmpRect.Left() : aTmpRect.Right()),
+ o3tl::narrowing<int>(aTmpRect.IsHeightEmpty() ? aTmpRect.Top() : aTmpRect.Bottom())
+ };
+
+ seqArgsNew.realloc( ++nNewLength );
+ auto pseqArgsNew = seqArgsNew.getArray();
+ pseqArgsNew[ nNewLength - 1 ].Name = "WinExtent";
+ pseqArgsNew[ nNewLength - 1 ].Value <<= aRectSeq;
+ }
+
+ if (requestedArgs.empty() || requestedArgs.count(u"PreusedFilterName"))
+ {
+ if ( !m_pData->m_aPreusedFilterName.isEmpty() )
+ {
+ seqArgsNew.realloc( ++nNewLength );
+ auto pseqArgsNew = seqArgsNew.getArray();
+ pseqArgsNew[ nNewLength - 1 ].Name = "PreusedFilterName";
+ pseqArgsNew[ nNewLength - 1 ].Value <<= m_pData->m_aPreusedFilterName;
+ }
+ }
+
+ if (requestedArgs.empty() || requestedArgs.count(u"DocumentBorder"))
+ {
+ SfxViewFrame* pFrame = SfxViewFrame::GetFirst( m_pData->m_pObjectShell.get() );
+ if ( pFrame )
+ {
+ SvBorder aBorder = pFrame->GetBorderPixelImpl();
+
+ Sequence< sal_Int32 > aBorderSeq
+ {
+ o3tl::narrowing<int>(aBorder.Left()),
+ o3tl::narrowing<int>(aBorder.Top()),
+ o3tl::narrowing<int>(aBorder.Right()),
+ o3tl::narrowing<int>(aBorder.Bottom())
+ };
+
+ seqArgsNew.realloc( ++nNewLength );
+ auto pseqArgsNew = seqArgsNew.getArray();
+ pseqArgsNew[ nNewLength - 1 ].Name = "DocumentBorder";
+ pseqArgsNew[ nNewLength - 1 ].Value <<= aBorderSeq;
+ }
+ }
+
+ if (requestedArgs.empty())
+ {
+ // only the values that are not supported by the ItemSet must be cached here
+ Sequence< beans::PropertyValue > aFinalCache;
+ sal_Int32 nFinalLength = 0;
+
+ for ( const auto& rOrg : std::as_const(m_pData->m_seqArguments) )
+ {
+ auto bNew = std::none_of(std::cbegin(seqArgsOld), std::cend(seqArgsOld),
+ [&rOrg](const beans::PropertyValue& rOld){ return rOld.Name == rOrg.Name; });
+ if ( bNew )
+ {
+ // the entity with this name should be new for seqArgsNew
+ // since it is not supported by transformer
+
+ seqArgsNew.realloc( ++nNewLength );
+ seqArgsNew.getArray()[ nNewLength - 1 ] = rOrg;
+
+ aFinalCache.realloc( ++nFinalLength );
+ aFinalCache.getArray()[ nFinalLength - 1 ] = rOrg;
+ }
+ }
+
+ m_pData->m_seqArguments = aFinalCache;
+ }
+
+ return seqArgsNew;
+ }
+
+ return m_pData->m_seqArguments;
+}
+
+void SAL_CALL SfxBaseModel::setArgs(const Sequence<beans::PropertyValue>& aArgs)
+{
+ SfxModelGuard aGuard( *this );
+
+ SfxMedium* pMedium = m_pData->m_pObjectShell->GetMedium();
+ if (!pMedium)
+ {
+ throw util::InvalidStateException(
+ "Medium could not be retrieved, unable to execute setArgs");
+ }
+
+ for (const auto& rArg : aArgs)
+ {
+ OUString sValue;
+ bool bValue;
+ bool ok = false;
+ if (rArg.Name == "SuggestedSaveAsName")
+ {
+ if (rArg.Value >>= sValue)
+ {
+ pMedium->GetItemSet().Put(SfxStringItem(SID_SUGGESTEDSAVEASNAME, sValue));
+ ok = true;
+ }
+ }
+ else if (rArg.Name == "SuggestedSaveAsDir")
+ {
+ if (rArg.Value >>= sValue)
+ {
+ pMedium->GetItemSet().Put(SfxStringItem(SID_SUGGESTEDSAVEASDIR, sValue));
+ ok = true;
+ }
+ }
+ else if (rArg.Name == "LockContentExtraction")
+ {
+ if (rArg.Value >>= bValue)
+ {
+ pMedium->GetItemSet().Put(SfxBoolItem(SID_LOCK_CONTENT_EXTRACTION, bValue));
+ ok = true;
+ }
+ }
+ else if (rArg.Name == "LockExport")
+ {
+ if (rArg.Value >>= bValue)
+ {
+ pMedium->GetItemSet().Put(SfxBoolItem(SID_LOCK_EXPORT, bValue));
+ ok = true;
+ }
+ }
+ else if (rArg.Name == "LockPrint")
+ {
+ if (rArg.Value >>= bValue)
+ {
+ pMedium->GetItemSet().Put(SfxBoolItem(SID_LOCK_PRINT, bValue));
+ ok = true;
+ }
+ }
+ else if (rArg.Name == "LockSave")
+ {
+ if (rArg.Value >>= bValue)
+ {
+ pMedium->GetItemSet().Put(SfxBoolItem(SID_LOCK_SAVE, bValue));
+ ok = true;
+ }
+ }
+ else if (rArg.Name == "LockEditDoc")
+ {
+ if (rArg.Value >>= bValue)
+ {
+ pMedium->GetItemSet().Put(SfxBoolItem(SID_LOCK_EDITDOC, bValue));
+ ok = true;
+ }
+ }
+ else if (rArg.Name == "Replaceable")
+ {
+ if (rArg.Value >>= bValue)
+ {
+ pMedium->GetItemSet().Put(SfxBoolItem(SID_REPLACEABLE, bValue));
+ ok = true;
+ }
+ }
+ else if (rArg.Name == "EncryptionData")
+ {
+ pMedium->GetItemSet().Put(SfxUnoAnyItem(SID_ENCRYPTIONDATA, rArg.Value));
+ ok = true;
+ }
+ if (!ok)
+ {
+ throw lang::IllegalArgumentException("Setting property not supported: " + rArg.Name,
+ comphelper::getProcessComponentContext(), 0);
+ }
+ }
+}
+
+// frame::XModel
+
+
+void SAL_CALL SfxBaseModel::connectController( const Reference< frame::XController >& xController )
+{
+ SfxModelGuard aGuard( *this );
+ OSL_PRECOND( xController.is(), "SfxBaseModel::connectController: invalid controller!" );
+ if ( !xController.is() )
+ return;
+
+ m_pData->m_seqControllers.push_back(xController);
+
+ if ( m_pData->m_seqControllers.size() == 1 )
+ {
+ SfxViewFrame* pViewFrame = SfxViewFrame::Get( xController, GetObjectShell() );
+ ENSURE_OR_THROW( pViewFrame, "SFX document without SFX view!?" );
+ pViewFrame->UpdateDocument_Impl();
+ const OUString sDocumentURL = GetObjectShell()->GetMedium()->GetName();
+ if ( !sDocumentURL.isEmpty() )
+ SfxGetpApp()->Broadcast( SfxOpenUrlHint( sDocumentURL ) );
+ }
+}
+
+
+// frame::XModel
+
+
+void SAL_CALL SfxBaseModel::disconnectController( const Reference< frame::XController >& xController )
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( m_pData->m_seqControllers.empty() )
+ return;
+
+ auto& vec = m_pData->m_seqControllers;
+ std::erase(vec, xController);
+
+ if ( xController == m_pData->m_xCurrent )
+ m_pData->m_xCurrent.clear();
+}
+
+namespace
+{
+ class ControllerLockUndoAction : public ::cppu::WeakImplHelper< XUndoAction >
+ {
+ public:
+ ControllerLockUndoAction( const Reference< XModel >& i_model, const bool i_undoIsUnlock )
+ :m_xModel( i_model )
+ ,m_bUndoIsUnlock( i_undoIsUnlock )
+ {
+ }
+
+ // XUndoAction
+ virtual OUString SAL_CALL getTitle() override;
+ virtual void SAL_CALL undo( ) override;
+ virtual void SAL_CALL redo( ) override;
+
+ private:
+ const Reference< XModel > m_xModel;
+ const bool m_bUndoIsUnlock;
+ };
+
+ OUString SAL_CALL ControllerLockUndoAction::getTitle()
+ {
+ // this action is intended to be used within an UndoContext only, so nobody will ever see this title ...
+ return OUString();
+ }
+
+ void SAL_CALL ControllerLockUndoAction::undo( )
+ {
+ if ( m_bUndoIsUnlock )
+ m_xModel->unlockControllers();
+ else
+ m_xModel->lockControllers();
+ }
+
+ void SAL_CALL ControllerLockUndoAction::redo( )
+ {
+ if ( m_bUndoIsUnlock )
+ m_xModel->lockControllers();
+ else
+ m_xModel->unlockControllers();
+ }
+}
+
+
+// frame::XModel
+
+
+void SAL_CALL SfxBaseModel::lockControllers()
+{
+ SfxModelGuard aGuard( *this );
+
+ ++m_pData->m_nControllerLockCount ;
+
+ if ( m_pData->m_pDocumentUndoManager.is()
+ && m_pData->m_pDocumentUndoManager->isInContext()
+ && !m_pData->m_pDocumentUndoManager->isLocked()
+ )
+ {
+ m_pData->m_pDocumentUndoManager->addUndoAction( new ControllerLockUndoAction( this, true ) );
+ }
+}
+
+
+// frame::XModel
+
+
+void SAL_CALL SfxBaseModel::unlockControllers()
+{
+ SfxModelGuard aGuard( *this );
+
+ --m_pData->m_nControllerLockCount ;
+
+ if ( m_pData->m_pDocumentUndoManager.is()
+ && m_pData->m_pDocumentUndoManager->isInContext()
+ && !m_pData->m_pDocumentUndoManager->isLocked()
+ )
+ {
+ m_pData->m_pDocumentUndoManager->addUndoAction( new ControllerLockUndoAction( this, false ) );
+ }
+}
+
+
+// frame::XModel
+
+
+sal_Bool SAL_CALL SfxBaseModel::hasControllersLocked()
+{
+ SfxModelGuard aGuard( *this );
+ return ( m_pData->m_nControllerLockCount != 0 ) ;
+}
+
+
+// frame::XModel
+
+
+Reference< frame::XController > SAL_CALL SfxBaseModel::getCurrentController()
+{
+ SfxModelGuard aGuard( *this );
+
+ // get the last active controller of this model
+ if ( m_pData->m_xCurrent.is() )
+ return m_pData->m_xCurrent;
+
+ // get the first controller of this model
+ return !m_pData->m_seqControllers.empty() ? m_pData->m_seqControllers.front() : m_pData->m_xCurrent;
+}
+
+
+// frame::XModel
+
+
+void SAL_CALL SfxBaseModel::setCurrentController( const Reference< frame::XController >& xCurrentController )
+{
+ SfxModelGuard aGuard( *this );
+
+ m_pData->m_xCurrent = xCurrentController;
+}
+
+
+// frame::XModel
+
+
+Reference< XInterface > SAL_CALL SfxBaseModel::getCurrentSelection()
+{
+ SfxModelGuard aGuard( *this );
+
+ Reference< XInterface > xReturn;
+ Reference< frame::XController > xController = getCurrentController() ;
+
+ if ( xController.is() )
+ {
+ Reference< view::XSelectionSupplier > xDocView( xController, UNO_QUERY );
+ if ( xDocView.is() )
+ {
+ Any aSel = xDocView->getSelection();
+ aSel >>= xReturn ;
+ }
+ }
+
+ return xReturn ;
+}
+
+
+// XModifiable2
+
+
+sal_Bool SAL_CALL SfxBaseModel::disableSetModified()
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( !m_pData->m_pObjectShell.is() )
+ throw RuntimeException();
+
+ bool bResult = m_pData->m_pObjectShell->IsEnableSetModified();
+ m_pData->m_pObjectShell->EnableSetModified( false );
+
+ return bResult;
+}
+
+sal_Bool SAL_CALL SfxBaseModel::enableSetModified()
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( !m_pData->m_pObjectShell.is() )
+ throw RuntimeException();
+
+ bool bResult = m_pData->m_pObjectShell->IsEnableSetModified();
+ m_pData->m_pObjectShell->EnableSetModified();
+
+ return bResult;
+}
+
+sal_Bool SAL_CALL SfxBaseModel::isSetModifiedEnabled()
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( !m_pData->m_pObjectShell.is() )
+ throw RuntimeException();
+
+ return m_pData->m_pObjectShell->IsEnableSetModified();
+}
+
+
+// XModifiable
+
+
+sal_Bool SAL_CALL SfxBaseModel::isModified()
+{
+ SfxModelGuard aGuard( *this );
+
+ return m_pData->m_pObjectShell.is() && m_pData->m_pObjectShell->IsModified();
+}
+
+
+// XModifiable
+
+
+void SAL_CALL SfxBaseModel::setModified( sal_Bool bModified )
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( m_pData->m_pObjectShell.is() )
+ m_pData->m_pObjectShell->SetModified(bModified);
+}
+
+
+// XModifiable
+
+
+void SAL_CALL SfxBaseModel::addModifyListener(const Reference< util::XModifyListener >& xListener)
+{
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+
+ m_pData->m_aModifyListeners.addInterface( xListener );
+}
+
+
+// XModifiable
+
+
+void SAL_CALL SfxBaseModel::removeModifyListener(const Reference< util::XModifyListener >& xListener)
+{
+ SfxModelGuard aGuard( *this );
+
+ m_pData->m_aModifyListeners.removeInterface( xListener );
+}
+
+
+// XCloseable
+
+
+void SAL_CALL SfxBaseModel::close( sal_Bool bDeliverOwnership )
+{
+ SolarMutexGuard aGuard;
+ if ( impl_isDisposed() || m_pData->m_bClosed || m_pData->m_bClosing )
+ return;
+
+ Reference< XInterface > xSelfHold( getXWeak() );
+ lang::EventObject aSource ( getXWeak() );
+ if (m_pData->m_aCloseListeners.getLength())
+ {
+ comphelper::OInterfaceIteratorHelper3 pIterator(m_pData->m_aCloseListeners);
+ while (pIterator.hasMoreElements())
+ {
+ try
+ {
+ pIterator.next()->queryClosing( aSource, bDeliverOwnership );
+ }
+ catch( RuntimeException& )
+ {
+ pIterator.remove();
+ }
+ }
+ }
+
+ if ( m_pData->m_bSaving )
+ {
+ if (bDeliverOwnership)
+ m_pData->m_bSuicide = true;
+ throw util::CloseVetoException(
+ "Can not close while saving.",
+ static_cast< util::XCloseable* >(this));
+ }
+
+ // no own objections against closing!
+ m_pData->m_bClosing = true;
+ if (m_pData->m_aCloseListeners.getLength())
+ {
+ comphelper::OInterfaceIteratorHelper3 pCloseIterator(m_pData->m_aCloseListeners);
+ while (pCloseIterator.hasMoreElements())
+ {
+ try
+ {
+ pCloseIterator.next()->notifyClosing( aSource );
+ }
+ catch( RuntimeException& )
+ {
+ pCloseIterator.remove();
+ }
+ }
+ }
+
+ m_pData->m_bClosed = true;
+ m_pData->m_bClosing = false;
+
+ dispose();
+}
+
+
+// XCloseBroadcaster
+
+
+void SAL_CALL SfxBaseModel::addCloseListener( const Reference< util::XCloseListener >& xListener )
+{
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+
+ m_pData->m_aCloseListeners.addInterface( xListener );
+}
+
+
+// XCloseBroadcaster
+
+
+void SAL_CALL SfxBaseModel::removeCloseListener( const Reference< util::XCloseListener >& xListener )
+{
+ SfxModelGuard aGuard( *this );
+
+ m_pData->m_aCloseListeners.removeInterface( xListener );
+}
+
+
+// XPrintable
+
+
+Sequence< beans::PropertyValue > SAL_CALL SfxBaseModel::getPrinter()
+{
+ SfxModelGuard aGuard( *this );
+
+ impl_getPrintHelper();
+ return m_pData->m_xPrintable->getPrinter();
+}
+
+void SAL_CALL SfxBaseModel::setPrinter(const Sequence< beans::PropertyValue >& rPrinter)
+{
+ SfxModelGuard aGuard( *this );
+
+ impl_getPrintHelper();
+ m_pData->m_xPrintable->setPrinter( rPrinter );
+}
+
+void SAL_CALL SfxBaseModel::print(const Sequence< beans::PropertyValue >& rOptions)
+{
+ SfxModelGuard aGuard( *this );
+
+ impl_getPrintHelper();
+
+ // tdf#123728 Always print on main thread to avoid deadlocks
+ vcl::solarthread::syncExecute([this, &rOptions]() { m_pData->m_xPrintable->print(rOptions); });
+}
+
+// XStorable
+
+
+sal_Bool SAL_CALL SfxBaseModel::hasLocation()
+{
+ SfxModelGuard aGuard( *this );
+
+ return m_pData->m_pObjectShell.is() && m_pData->m_pObjectShell->HasName();
+}
+
+
+// XStorable
+
+
+OUString SAL_CALL SfxBaseModel::getLocation()
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( m_pData->m_pObjectShell.is() )
+ {
+ // TODO/LATER: is it correct that the shared document returns shared file location?
+ if ( m_pData->m_pObjectShell->IsDocShared() )
+ return m_pData->m_pObjectShell->GetSharedFileURL();
+ else
+ return m_pData->m_pObjectShell->GetMedium()->GetName();
+ }
+
+ return m_pData->m_sURL;
+}
+
+
+// XStorable
+
+
+sal_Bool SAL_CALL SfxBaseModel::isReadonly()
+{
+ SfxModelGuard aGuard( *this );
+
+ return !m_pData->m_pObjectShell.is() || m_pData->m_pObjectShell->IsReadOnly();
+}
+
+// XStorable2
+
+
+void SAL_CALL SfxBaseModel::storeSelf( const Sequence< beans::PropertyValue >& aSeqArgs )
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( !m_pData->m_pObjectShell.is() )
+ return;
+
+ SfxSaveGuard aSaveGuard(this, m_pData.get());
+
+ bool bCheckIn = false;
+ bool bOnMainThread = false;
+ for ( const auto& rArg : aSeqArgs )
+ {
+ // check that only acceptable parameters are provided here
+ if ( rArg.Name != "VersionComment" && rArg.Name != "Author"
+ && rArg.Name != "DontTerminateEdit"
+ && rArg.Name != "InteractionHandler" && rArg.Name != "StatusIndicator"
+ && rArg.Name != "VersionMajor"
+ && rArg.Name != "FailOnWarning"
+ && rArg.Name != "CheckIn"
+ && rArg.Name != "NoFileSync"
+ && rArg.Name != "OnMainThread" )
+ {
+ const OUString aMessage( "Unexpected MediaDescriptor parameter: " + rArg.Name );
+ throw lang::IllegalArgumentException( aMessage, Reference< XInterface >(), 1 );
+ }
+ else if ( rArg.Name == "CheckIn" )
+ {
+ rArg.Value >>= bCheckIn;
+ }
+ else if (rArg.Name == "OnMainThread")
+ {
+ rArg.Value >>= bOnMainThread;
+ }
+ }
+
+ // Remove CheckIn property if needed
+ sal_uInt16 nSlotId = SID_SAVEDOC;
+ Sequence< beans::PropertyValue > aArgs = aSeqArgs;
+ if ( bCheckIn )
+ {
+ nSlotId = SID_CHECKIN;
+ sal_Int32 nLength = aSeqArgs.getLength( );
+ aArgs = Sequence< beans::PropertyValue >( nLength - 1 );
+ std::copy_if(aSeqArgs.begin(), aSeqArgs.end(), aArgs.getArray(),
+ [](const beans::PropertyValue& rProp) { return rProp.Name != "CheckIn"; });
+ }
+
+ std::optional<SfxAllItemSet> pParams(SfxGetpApp()->GetPool() );
+ TransformParameters( nSlotId, aArgs, *pParams );
+
+ SfxGetpApp()->NotifyEvent( SfxEventHint( SfxEventHintId::SaveDoc, GlobalEventConfig::GetEventName(GlobalEventId::SAVEDOC), m_pData->m_pObjectShell.get() ) );
+
+ bool bRet = false;
+
+ // TODO/LATER: let the embedded case of saving be handled more careful
+ if ( m_pData->m_pObjectShell->GetCreateMode() == SfxObjectCreateMode::EMBEDDED )
+ {
+ // If this is an embedded object that has no URL based location it should be stored to own storage.
+ // An embedded object can have a location based on URL in case it is a link, then it should be
+ // stored in normal way.
+ if ( !hasLocation() || getLocation().startsWith("private:") )
+ {
+ // actually in this very rare case only UI parameters have sense
+ // TODO/LATER: should be done later, after integration of sb19
+ bRet = m_pData->m_pObjectShell->DoSave()
+ && m_pData->m_pObjectShell->DoSaveCompleted();
+ }
+ else
+ {
+ bRet = m_pData->m_pObjectShell->Save_Impl( &*pParams );
+ }
+ }
+ else
+ {
+ // Tell the SfxMedium if we are in checkin instead of normal save
+ m_pData->m_pObjectShell->GetMedium( )->SetInCheckIn( nSlotId == SID_CHECKIN );
+ if (bOnMainThread)
+ bRet = vcl::solarthread::syncExecute(
+ [this, &pParams] { return m_pData->m_pObjectShell->Save_Impl(&*pParams); });
+ else
+ bRet = m_pData->m_pObjectShell->Save_Impl(&*pParams);
+ m_pData->m_pObjectShell->GetMedium( )->SetInCheckIn( nSlotId != SID_CHECKIN );
+ }
+
+ pParams.reset();
+
+ ErrCodeMsg nErrCode = m_pData->m_pObjectShell->GetErrorIgnoreWarning();
+ m_pData->m_pObjectShell->ResetError();
+
+ if ( bRet )
+ {
+ m_pData->m_aPreusedFilterName = GetMediumFilterName_Impl();
+
+ SfxGetpApp()->NotifyEvent( SfxEventHint( SfxEventHintId::SaveDocDone, GlobalEventConfig::GetEventName(GlobalEventId::SAVEDOCDONE), m_pData->m_pObjectShell.get() ) );
+ }
+ else
+ {
+ if (!nErrCode)
+ nErrCode = ERRCODE_IO_CANTWRITE;
+ // write the contents of the logger to the file
+ SfxGetpApp()->NotifyEvent( SfxEventHint( SfxEventHintId::SaveDocFailed, GlobalEventConfig::GetEventName(GlobalEventId::SAVEDOCFAILED), m_pData->m_pObjectShell.get() ) );
+
+ throw task::ErrorCodeIOException(
+ "SfxBaseModel::storeSelf: " + nErrCode.toString(),
+ Reference< XInterface >(), sal_uInt32(nErrCode.GetCode()));
+ }
+}
+
+
+// XStorable
+
+
+void SAL_CALL SfxBaseModel::store()
+{
+ comphelper::ProfileZone aZone("store");
+ storeSelf( Sequence< beans::PropertyValue >() );
+}
+
+
+// XStorable
+
+
+void SAL_CALL SfxBaseModel::storeAsURL( const OUString& rURL ,
+ const Sequence< beans::PropertyValue >& rArgs )
+{
+ SfxModelGuard aGuard( *this );
+ comphelper::ProfileZone aZone("storeAs");
+
+ if ( !m_pData->m_pObjectShell.is() )
+ return;
+
+ SfxSaveGuard aSaveGuard(this, m_pData.get());
+
+ utl::MediaDescriptor aDescriptor(rArgs);
+ bool bOnMainThread = aDescriptor.getUnpackedValueOrDefault("OnMainThread", false);
+ if (bOnMainThread)
+ {
+ vcl::solarthread::syncExecute([this, rURL, rArgs]() { impl_store(rURL, rArgs, false); });
+ }
+ else
+ {
+ impl_store(rURL, rArgs, false);
+ }
+
+ Sequence< beans::PropertyValue > aSequence ;
+ TransformItems( SID_OPENDOC, m_pData->m_pObjectShell->GetMedium()->GetItemSet(), aSequence );
+ attachResource( rURL, aSequence );
+
+ loadCmisProperties( );
+
+#if OSL_DEBUG_LEVEL > 0
+ const SfxStringItem* pPasswdItem = m_pData->m_pObjectShell->GetMedium()->GetItemSet().GetItem(SID_PASSWORD, false);
+ OSL_ENSURE( !pPasswdItem, "There should be no Password property in the document MediaDescriptor!" );
+#endif
+}
+
+
+// XUndoManagerSupplier
+
+Reference< XUndoManager > SAL_CALL SfxBaseModel::getUndoManager( )
+{
+ SfxModelGuard aGuard( *this );
+ if ( !m_pData->m_pDocumentUndoManager.is() )
+ m_pData->m_pDocumentUndoManager.set( new ::sfx2::DocumentUndoManager( *this ) );
+ return m_pData->m_pDocumentUndoManager;
+}
+
+
+// XStorable
+
+
+void SAL_CALL SfxBaseModel::storeToURL( const OUString& rURL ,
+ const Sequence< beans::PropertyValue >& rArgs )
+{
+ SfxModelGuard aGuard( *this );
+ comphelper::ProfileZone aZone("storeToURL");
+
+ if ( !m_pData->m_pObjectShell.is() )
+ return;
+
+ SfxSaveGuard aSaveGuard(this, m_pData.get());
+ try {
+ utl::MediaDescriptor aDescriptor(rArgs);
+ bool bOnMainThread = aDescriptor.getUnpackedValueOrDefault("OnMainThread", false);
+ if (bOnMainThread)
+ vcl::solarthread::syncExecute([this, rURL, rArgs]() { impl_store(rURL, rArgs, true); });
+ else
+ impl_store(rURL, rArgs, true);
+ }
+ catch (const uno::Exception &e)
+ {
+ // convert to the exception we announce in the throw
+ // (eg. neon likes to throw InteractiveAugmentedIOException which
+ // is not an io::IOException)
+ throw io::IOException(e.Message, e.Context);
+ }
+}
+
+sal_Bool SAL_CALL SfxBaseModel::wasModifiedSinceLastSave()
+{
+ SfxModelGuard aGuard( *this );
+ return m_pData->m_oDirtyTimestamp.has_value();
+}
+
+void SAL_CALL SfxBaseModel::storeToRecoveryFile( const OUString& i_TargetLocation, const Sequence< PropertyValue >& i_MediaDescriptor )
+{
+ SfxModelGuard aGuard( *this );
+
+ // delegate
+ SfxSaveGuard aSaveGuard( this, m_pData.get() );
+ impl_store( i_TargetLocation, i_MediaDescriptor, true );
+
+ // no need for subsequent calls to storeToRecoveryFile, unless we're modified, again
+ m_pData->setModifiedForAutoSave(false);
+}
+
+sal_Int64 SAL_CALL SfxBaseModel::getModifiedStateDuration()
+{
+ SfxModelGuard aGuard(*this);
+ if (!m_pData->m_oDirtyTimestamp)
+ return -1;
+ auto ms = std::chrono::ceil<std::chrono::milliseconds>(std::chrono::steady_clock::now()
+ - *m_pData->m_oDirtyTimestamp);
+ return ms.count();
+}
+
+void SAL_CALL SfxBaseModel::recoverFromFile( const OUString& i_SourceLocation, const OUString& i_SalvagedFile, const Sequence< PropertyValue >& i_MediaDescriptor )
+{
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+
+ // delegate to our "load" method
+ ::comphelper::NamedValueCollection aMediaDescriptor( i_MediaDescriptor );
+
+ // our load implementation expects the SalvagedFile to be in the media descriptor
+ OSL_ENSURE( !aMediaDescriptor.has( "SalvagedFile" ) || ( aMediaDescriptor.getOrDefault( "SalvagedFile", OUString() ) == i_SalvagedFile ),
+ "SfxBaseModel::recoverFromFile: inconsistent information!" );
+ aMediaDescriptor.put( "SalvagedFile", i_SalvagedFile );
+
+ // similar for the to-be-loaded file
+ OSL_ENSURE( !aMediaDescriptor.has( "URL" ) || ( aMediaDescriptor.getOrDefault( "URL", OUString() ) == i_SourceLocation ),
+ "SfxBaseModel::recoverFromFile: inconsistent information!" );
+ aMediaDescriptor.put( "URL", i_SourceLocation );
+
+ load( aMediaDescriptor.getPropertyValues() );
+
+ // Note: The XDocumentRecovery interface specification requires us to do an attachResource after loading.
+ // However, we will not do this here, as we know that our load implementation (respectively some method
+ // called from there) already did so.
+ // In particular, the load process might already have modified some elements of the media
+ // descriptor, for instance the MacroExecMode (in case the user was involved to decide about it), and we do
+ // not want to overwrite it with the "old" elements passed to this method here.
+}
+
+
+// XLoadable
+
+
+void SAL_CALL SfxBaseModel::initNew()
+{
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+ if ( IsInitialized() )
+ throw frame::DoubleInitializationException( OUString(), *this );
+
+ // the object shell should exist always
+ DBG_ASSERT( m_pData->m_pObjectShell.is(), "Model is useless without an ObjectShell" );
+ if ( !m_pData->m_pObjectShell.is() )
+ return;
+
+ if( m_pData->m_pObjectShell->GetMedium() )
+ throw frame::DoubleInitializationException();
+
+ bool bRes = m_pData->m_pObjectShell->DoInitNew();
+ ErrCodeMsg nErrCode = m_pData->m_pObjectShell->GetErrorIgnoreWarning() ?
+ m_pData->m_pObjectShell->GetErrorIgnoreWarning() : ERRCODE_IO_CANTCREATE;
+ m_pData->m_pObjectShell->ResetError();
+
+ if ( !bRes )
+ throw task::ErrorCodeIOException(
+ "SfxBaseModel::initNew: " + nErrCode.toString(),
+ Reference< XInterface >(), sal_uInt32(nErrCode.GetCode()));
+}
+
+namespace {
+
+OUString getFilterProvider( SfxMedium const & rMedium )
+{
+ const std::shared_ptr<const SfxFilter>& pFilter = rMedium.GetFilter();
+ if (!pFilter)
+ return OUString();
+
+ return pFilter->GetProviderName();
+}
+
+void setUpdatePickList( SfxMedium* pMedium )
+{
+ if (!pMedium)
+ return;
+
+ bool bHidden = false;
+ const SfxBoolItem* pHidItem = pMedium->GetItemSet().GetItem(SID_HIDDEN, false);
+ if (pHidItem)
+ bHidden = pHidItem->GetValue();
+
+ pMedium->SetUpdatePickList(!bHidden);
+}
+
+}
+
+void SAL_CALL SfxBaseModel::load( const Sequence< beans::PropertyValue >& seqArguments )
+{
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+ if ( IsInitialized() )
+ throw frame::DoubleInitializationException( OUString(), *this );
+
+ // the object shell should exist always
+ DBG_ASSERT( m_pData->m_pObjectShell.is(), "Model is useless without an ObjectShell" );
+
+ if (!m_pData->m_pObjectShell.is())
+ return;
+
+ if( m_pData->m_pObjectShell->GetMedium() )
+ // if a Medium is present, the document is already initialized
+ throw frame::DoubleInitializationException();
+
+ SfxMedium* pMedium = new SfxMedium( seqArguments );
+
+ ErrCodeMsg nError = ERRCODE_NONE;
+ if (!getFilterProvider(*pMedium).isEmpty())
+ {
+ if (!m_pData->m_pObjectShell->DoLoadExternal(pMedium))
+ nError = ERRCODE_IO_GENERAL;
+
+ pMedium = handleLoadError(nError, pMedium);
+ setUpdatePickList(pMedium);
+ return;
+ }
+
+ OUString aFilterName;
+ const SfxStringItem* pFilterNameItem = pMedium->GetItemSet().GetItem(SID_FILTER_NAME, false);
+ if( pFilterNameItem )
+ aFilterName = pFilterNameItem->GetValue();
+ if( !m_pData->m_pObjectShell->GetFactory().GetFilterContainer()->GetFilter4FilterName( aFilterName ) )
+ {
+ // filtername is not valid
+ delete pMedium;
+ throw frame::IllegalArgumentIOException();
+ }
+
+ const SfxStringItem* pSalvageItem = pMedium->GetItemSet().GetItem(SID_DOC_SALVAGE, false);
+ bool bSalvage = pSalvageItem != nullptr;
+
+ // load document
+ if ( !m_pData->m_pObjectShell->DoLoad(pMedium) )
+ nError=ERRCODE_IO_GENERAL;
+
+ // QUESTION: if the following happens outside of DoLoad, something important is missing there!
+ Reference< task::XInteractionHandler > xHandler = pMedium->GetInteractionHandler();
+ if( m_pData->m_pObjectShell->GetErrorCode() )
+ {
+ nError = m_pData->m_pObjectShell->GetErrorCode();
+ if ( nError == ERRCODE_IO_BROKENPACKAGE && xHandler.is() )
+ {
+ const OUString aDocName( pMedium->GetURLObject().getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ) );
+ const SfxBoolItem* pRepairItem = pMedium->GetItemSet().GetItem(SID_REPAIRPACKAGE, false);
+ if ( !pRepairItem || !pRepairItem->GetValue() )
+ {
+ RequestPackageReparation aRequest( aDocName );
+ xHandler->handle( aRequest.GetRequest() );
+ if( aRequest.isApproved() )
+ {
+ // lok: we want to overwrite file in jail, so don't use template flag
+ bool bIsLOK = comphelper::LibreOfficeKit::isActive();
+ // broken package: try second loading and allow repair
+ pMedium->GetItemSet().Put( SfxBoolItem( SID_REPAIRPACKAGE, true ) );
+ pMedium->GetItemSet().Put( SfxBoolItem( SID_TEMPLATE, !bIsLOK ) );
+ pMedium->GetItemSet().Put( SfxStringItem( SID_DOCINFO_TITLE, aDocName ) );
+
+ // the error must be reset and the storage must be reopened in new mode
+ pMedium->ResetError();
+ pMedium->CloseStorage();
+ m_pData->m_pObjectShell->PrepareSecondTryLoad_Impl();
+ nError = ERRCODE_NONE;
+ if ( !m_pData->m_pObjectShell->DoLoad(pMedium) )
+ nError=ERRCODE_IO_GENERAL;
+ if (m_pData->m_pObjectShell->GetErrorCode())
+ nError = m_pData->m_pObjectShell->GetErrorCode();
+ }
+ }
+
+ if ( nError == ERRCODE_IO_BROKENPACKAGE )
+ {
+ // repair either not allowed or not successful
+ NotifyBrokenPackage aRequest( aDocName );
+ xHandler->handle( aRequest.GetRequest() );
+ }
+ }
+ }
+
+ if( m_pData->m_pObjectShell->IsAbortingImport() )
+ nError = ERRCODE_ABORT;
+
+ if( bSalvage )
+ {
+ // file recovery: restore original filter
+ const SfxStringItem* pFilterItem = pMedium->GetItemSet().GetItem(SID_FILTER_NAME, false);
+ SfxFilterMatcher& rMatcher = SfxGetpApp()->GetFilterMatcher();
+ std::shared_ptr<const SfxFilter> pSetFilter = rMatcher.GetFilter4FilterName( pFilterItem->GetValue() );
+ pMedium->SetFilter( pSetFilter );
+ m_pData->m_pObjectShell->SetModified();
+ }
+
+ // TODO/LATER: maybe the mode should be retrieved from outside and the preused filter should not be set
+ if ( m_pData->m_pObjectShell->GetCreateMode() == SfxObjectCreateMode::EMBEDDED )
+ {
+ const SfxStringItem* pFilterItem = pMedium->GetItemSet().GetItem(SID_FILTER_NAME, false);
+ if ( pFilterItem )
+ m_pData->m_aPreusedFilterName = pFilterItem->GetValue();
+ }
+
+ if ( !nError )
+ nError = pMedium->GetErrorIgnoreWarning();
+
+ m_pData->m_pObjectShell->ResetError();
+
+ pMedium = handleLoadError(nError, pMedium);
+ loadCmisProperties();
+ setUpdatePickList(pMedium);
+
+#if OSL_DEBUG_LEVEL > 0
+ const SfxStringItem* pPasswdItem = pMedium->GetItemSet().GetItem(SID_PASSWORD, false);
+ OSL_ENSURE( !pPasswdItem, "There should be no Password property in the document MediaDescriptor!" );
+#endif
+}
+
+
+// XTransferable
+
+
+Any SAL_CALL SfxBaseModel::getTransferData( const datatransfer::DataFlavor& aFlavor )
+{
+ SfxModelGuard aGuard( *this );
+
+ Any aAny;
+
+ if ( m_pData->m_pObjectShell.is() )
+ {
+ if ( aFlavor.MimeType == "application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star Object Descriptor (XML)\"" )
+ {
+ if ( aFlavor.DataType != cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ throw datatransfer::UnsupportedFlavorException();
+
+ TransferableObjectDescriptor aDesc;
+
+ aDesc.maClassName = m_pData->m_pObjectShell->GetClassName();
+ aDesc.maTypeName = aFlavor.HumanPresentableName;
+
+ // TODO/LATER: ViewAspect needs to be sal_Int64
+ aDesc.mnViewAspect = sal::static_int_cast< sal_uInt16 >( embed::Aspects::MSOLE_CONTENT );
+
+ Size aSize = m_pData->m_pObjectShell->GetVisArea().GetSize();
+
+ MapUnit aMapUnit = m_pData->m_pObjectShell->GetMapUnit();
+ aDesc.maSize = OutputDevice::LogicToLogic(aSize, MapMode(aMapUnit), MapMode(MapUnit::Map100thMM));
+ aDesc.maDragStartPos = Point();
+ aDesc.maDisplayName.clear();
+
+ SvMemoryStream aMemStm( 1024, 1024 );
+ WriteTransferableObjectDescriptor( aMemStm, aDesc );
+ aAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ), aMemStm.Tell() );
+ }
+ else if ( aFlavor.MimeType == "application/x-openoffice-embed-source;windows_formatname=\"Star EMBS\"" )
+ {
+ if ( aFlavor.DataType != cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ throw datatransfer::UnsupportedFlavorException();
+
+ try
+ {
+ utl::TempFileNamed aTmp;
+ aTmp.EnableKillingFile();
+ storeToURL( aTmp.GetURL(), Sequence < beans::PropertyValue >() );
+ std::unique_ptr<SvStream> pStream(aTmp.GetStream( StreamMode::READ ));
+ const sal_uInt32 nLen = pStream->TellEnd();
+ Sequence< sal_Int8 > aSeq( nLen );
+ pStream->ReadBytes(aSeq.getArray(), nLen);
+ if( aSeq.hasElements() )
+ aAny <<= aSeq;
+ }
+ catch ( Exception& )
+ {
+ }
+ }
+ else if ( aFlavor.MimeType == "application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\"" )
+ {
+ if ( aFlavor.DataType != cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ throw datatransfer::UnsupportedFlavorException();
+
+
+ std::shared_ptr<GDIMetaFile> xMetaFile =
+ m_pData->m_pObjectShell->GetPreviewMetaFile( true );
+
+ if (xMetaFile)
+ {
+ SvMemoryStream aMemStm( 65535, 65535 );
+ aMemStm.SetVersion( SOFFICE_FILEFORMAT_CURRENT );
+
+ SvmWriter aWriter( aMemStm );
+ aWriter.Write( *xMetaFile );
+ aAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ),
+ aMemStm.TellEnd() );
+ }
+ }
+ else if ( aFlavor.MimeType == "application/x-openoffice-highcontrast-gdimetafile;windows_formatname=\"GDIMetaFile\"" )
+ {
+ if ( aFlavor.DataType != cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ throw datatransfer::UnsupportedFlavorException();
+
+ std::shared_ptr<GDIMetaFile> xMetaFile =
+ m_pData->m_pObjectShell->GetPreviewMetaFile( true );
+
+ if (xMetaFile)
+ {
+ SvMemoryStream aMemStm( 65535, 65535 );
+ aMemStm.SetVersion( SOFFICE_FILEFORMAT_CURRENT );
+
+ SvmWriter aWriter( aMemStm );
+ aWriter.Write( *xMetaFile );
+ aAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( aMemStm.GetData() ),
+ aMemStm.TellEnd() );
+ }
+ }
+ else if ( aFlavor.MimeType == "application/x-openoffice-emf;windows_formatname=\"Image EMF\"" )
+ {
+ if ( aFlavor.DataType == cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ {
+ std::shared_ptr<GDIMetaFile> xMetaFile =
+ m_pData->m_pObjectShell->GetPreviewMetaFile( true );
+
+ if (xMetaFile)
+ {
+ std::unique_ptr<SvMemoryStream> xStream(
+ GraphicHelper::getFormatStrFromGDI_Impl(
+ xMetaFile.get(), ConvertDataFormat::EMF ) );
+ if (xStream)
+ {
+ xStream->SetVersion( SOFFICE_FILEFORMAT_CURRENT );
+ aAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( xStream->GetData() ),
+ xStream->TellEnd() );
+ }
+ }
+ }
+ else if ( GraphicHelper::supportsMetaFileHandle_Impl()
+ && aFlavor.DataType == cppu::UnoType<sal_uInt64>::get())
+ {
+ std::shared_ptr<GDIMetaFile> xMetaFile =
+ m_pData->m_pObjectShell->GetPreviewMetaFile( true );
+
+ if (xMetaFile)
+ {
+ aAny <<= reinterpret_cast< sal_uInt64 >(
+ GraphicHelper::getEnhMetaFileFromGDI_Impl( xMetaFile.get() ) );
+ }
+ }
+ else
+ throw datatransfer::UnsupportedFlavorException();
+ }
+ else if ( aFlavor.MimeType == "application/x-openoffice-wmf;windows_formatname=\"Image WMF\"" )
+ {
+ if ( aFlavor.DataType == cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ {
+ std::shared_ptr<GDIMetaFile> xMetaFile =
+ m_pData->m_pObjectShell->GetPreviewMetaFile( true );
+
+ if (xMetaFile)
+ {
+ std::unique_ptr<SvMemoryStream> xStream(
+ GraphicHelper::getFormatStrFromGDI_Impl(
+ xMetaFile.get(), ConvertDataFormat::WMF ) );
+
+ if (xStream)
+ {
+ xStream->SetVersion( SOFFICE_FILEFORMAT_CURRENT );
+ aAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( xStream->GetData() ),
+ xStream->TellEnd() );
+ }
+ }
+ }
+ else if ( GraphicHelper::supportsMetaFileHandle_Impl()
+ && aFlavor.DataType == cppu::UnoType<sal_uInt64>::get())
+ {
+ // means HGLOBAL handler to memory storage containing METAFILEPICT structure
+
+ std::shared_ptr<GDIMetaFile> xMetaFile =
+ m_pData->m_pObjectShell->GetPreviewMetaFile( true );
+
+ if (xMetaFile)
+ {
+ Size aMetaSize = xMetaFile->GetPrefSize();
+ aAny <<= reinterpret_cast< sal_uInt64 >(
+ GraphicHelper::getWinMetaFileFromGDI_Impl(
+ xMetaFile.get(), aMetaSize ) );
+ }
+ }
+ else
+ throw datatransfer::UnsupportedFlavorException();
+ }
+ else if ( aFlavor.MimeType == "application/x-openoffice-bitmap;windows_formatname=\"Bitmap\"" )
+ {
+ if ( aFlavor.DataType != cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ throw datatransfer::UnsupportedFlavorException();
+
+ std::shared_ptr<GDIMetaFile> xMetaFile =
+ m_pData->m_pObjectShell->GetPreviewMetaFile( true );
+
+ if (xMetaFile)
+ {
+ std::unique_ptr<SvMemoryStream> xStream(
+ GraphicHelper::getFormatStrFromGDI_Impl(
+ xMetaFile.get(), ConvertDataFormat::BMP ) );
+
+ if (xStream)
+ {
+ xStream->SetVersion( SOFFICE_FILEFORMAT_CURRENT );
+ aAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( xStream->GetData() ),
+ xStream->TellEnd() );
+ }
+ }
+ }
+ else if ( aFlavor.MimeType == "image/png" )
+ {
+ if ( aFlavor.DataType != cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ throw datatransfer::UnsupportedFlavorException();
+
+ std::shared_ptr<GDIMetaFile> xMetaFile =
+ m_pData->m_pObjectShell->GetPreviewMetaFile( true );
+
+ if (xMetaFile)
+ {
+ std::unique_ptr<SvMemoryStream> xStream(
+ GraphicHelper::getFormatStrFromGDI_Impl(
+ xMetaFile.get(), ConvertDataFormat::PNG ) );
+
+ if (xStream)
+ {
+ xStream->SetVersion( SOFFICE_FILEFORMAT_CURRENT );
+ aAny <<= Sequence< sal_Int8 >( static_cast< const sal_Int8* >( xStream->GetData() ),
+ xStream->TellEnd() );
+ }
+ }
+ }
+ else
+ throw datatransfer::UnsupportedFlavorException();
+ }
+
+ return aAny;
+}
+
+
+// XTransferable
+
+
+Sequence< datatransfer::DataFlavor > SAL_CALL SfxBaseModel::getTransferDataFlavors()
+{
+ SfxModelGuard aGuard( *this );
+
+ const sal_Int32 nSuppFlavors = GraphicHelper::supportsMetaFileHandle_Impl() ? 10 : 8;
+ Sequence< datatransfer::DataFlavor > aFlavorSeq( nSuppFlavors );
+ auto pFlavorSeq = aFlavorSeq.getArray();
+
+ pFlavorSeq[0].MimeType =
+ "application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\"";
+ pFlavorSeq[0].HumanPresentableName = "GDIMetaFile";
+ pFlavorSeq[0].DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ pFlavorSeq[1].MimeType =
+ "application/x-openoffice-highcontrast-gdimetafile;windows_formatname=\"GDIMetaFile\"";
+ pFlavorSeq[1].HumanPresentableName = "GDIMetaFile";
+ pFlavorSeq[1].DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ pFlavorSeq[2].MimeType =
+ "application/x-openoffice-emf;windows_formatname=\"Image EMF\"" ;
+ pFlavorSeq[2].HumanPresentableName = "Enhanced Windows MetaFile";
+ pFlavorSeq[2].DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ pFlavorSeq[3].MimeType =
+ "application/x-openoffice-wmf;windows_formatname=\"Image WMF\"";
+ pFlavorSeq[3].HumanPresentableName = "Windows MetaFile";
+ pFlavorSeq[3].DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ pFlavorSeq[4].MimeType =
+ "application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star Object Descriptor (XML)\"";
+ pFlavorSeq[4].HumanPresentableName = "Star Object Descriptor (XML)";
+ pFlavorSeq[4].DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ pFlavorSeq[5].MimeType =
+ "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"";
+ pFlavorSeq[5].HumanPresentableName = "Star Embed Source (XML)";
+ pFlavorSeq[5].DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ pFlavorSeq[6].MimeType =
+ "application/x-openoffice-bitmap;windows_formatname=\"Bitmap\"";
+ pFlavorSeq[6].HumanPresentableName = "Bitmap";
+ pFlavorSeq[6].DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ pFlavorSeq[7].MimeType = "image/png";
+ pFlavorSeq[7].HumanPresentableName = "PNG";
+ pFlavorSeq[7].DataType = cppu::UnoType<Sequence< sal_Int8 >>::get();
+
+ if ( nSuppFlavors == 10 )
+ {
+ pFlavorSeq[8].MimeType =
+ "application/x-openoffice-emf;windows_formatname=\"Image EMF\"";
+ pFlavorSeq[8].HumanPresentableName = "Enhanced Windows MetaFile";
+ pFlavorSeq[8].DataType = cppu::UnoType<sal_uInt64>::get();
+
+ pFlavorSeq[9].MimeType =
+ "application/x-openoffice-wmf;windows_formatname=\"Image WMF\"";
+ pFlavorSeq[9].HumanPresentableName = "Windows MetaFile";
+ pFlavorSeq[9].DataType = cppu::UnoType<sal_uInt64>::get();
+ }
+
+ return aFlavorSeq;
+}
+
+
+// XTransferable
+
+
+sal_Bool SAL_CALL SfxBaseModel::isDataFlavorSupported( const datatransfer::DataFlavor& aFlavor )
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( aFlavor.MimeType == "application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\"" )
+ {
+ if ( aFlavor.DataType == cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ return true;
+ }
+ else if ( aFlavor.MimeType == "application/x-openoffice-highcontrast-gdimetafile;windows_formatname=\"GDIMetaFile\"" )
+ {
+ if ( aFlavor.DataType == cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ return true;
+ }
+ else if ( aFlavor.MimeType == "application/x-openoffice-emf;windows_formatname=\"Image EMF\"" )
+ {
+ if ( aFlavor.DataType == cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ return true;
+ else if ( GraphicHelper::supportsMetaFileHandle_Impl()
+ && aFlavor.DataType == cppu::UnoType<sal_uInt64>::get())
+ return true;
+ }
+ else if ( aFlavor.MimeType == "application/x-openoffice-wmf;windows_formatname=\"Image WMF\"" )
+ {
+ if ( aFlavor.DataType == cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ return true;
+ else if ( GraphicHelper::supportsMetaFileHandle_Impl()
+ && aFlavor.DataType == cppu::UnoType<sal_uInt64>::get())
+ return true;
+ }
+ else if ( aFlavor.MimeType == "application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star Object Descriptor (XML)\"" )
+ {
+ if ( aFlavor.DataType == cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ return true;
+ }
+ else if ( aFlavor.MimeType == "application/x-openoffice-embed-source;windows_formatname=\"Star EMBS\"" )
+ {
+ if ( aFlavor.DataType == cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ return true;
+ }
+ else if ( aFlavor.MimeType == "application/x-openoffice-bitmap;windows_formatname=\"Bitmap\"" )
+ {
+ if ( aFlavor.DataType == cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ return true;
+ }
+ else if ( aFlavor.MimeType == "image/png" )
+ {
+ if ( aFlavor.DataType == cppu::UnoType<Sequence< sal_Int8 >>::get() )
+ return true;
+ }
+
+ return false;
+}
+
+
+// XEventsSupplier
+
+
+Reference< container::XNameReplace > SAL_CALL SfxBaseModel::getEvents()
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( ! m_pData->m_xEvents.is() )
+ {
+ m_pData->m_xEvents = new SfxEvents_Impl( m_pData->m_pObjectShell.get(), this );
+ }
+
+ return m_pData->m_xEvents;
+}
+
+
+// XEmbeddedScripts
+
+
+Reference< script::XStorageBasedLibraryContainer > SAL_CALL SfxBaseModel::getBasicLibraries()
+{
+ SfxModelGuard aGuard( *this );
+
+ Reference< script::XStorageBasedLibraryContainer > xBasicLibraries;
+ if ( m_pData->m_pObjectShell.is() )
+ xBasicLibraries.set(m_pData->m_pObjectShell->GetBasicContainer(), UNO_QUERY);
+ return xBasicLibraries;
+}
+
+Reference< script::XStorageBasedLibraryContainer > SAL_CALL SfxBaseModel::getDialogLibraries()
+{
+ SfxModelGuard aGuard( *this );
+
+ Reference< script::XStorageBasedLibraryContainer > xDialogLibraries;
+ if ( m_pData->m_pObjectShell.is() )
+ xDialogLibraries.set(m_pData->m_pObjectShell->GetDialogContainer(), UNO_QUERY);
+ return xDialogLibraries;
+}
+
+sal_Bool SAL_CALL SfxBaseModel::getAllowMacroExecution()
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( m_pData->m_pObjectShell.is() )
+ return m_pData->m_pObjectShell->AdjustMacroMode();
+ return false;
+}
+
+
+// XScriptInvocationContext
+
+
+Reference< document::XEmbeddedScripts > SAL_CALL SfxBaseModel::getScriptContainer()
+{
+ SfxModelGuard aGuard( *this );
+
+ Reference< document::XEmbeddedScripts > xDocumentScripts;
+
+ try
+ {
+ Reference< frame::XModel > xDocument( this );
+ xDocumentScripts.set( xDocument, UNO_QUERY );
+ while ( !xDocumentScripts.is() && xDocument.is() )
+ {
+ Reference< container::XChild > xDocAsChild( xDocument, UNO_QUERY );
+ if ( !xDocAsChild.is() )
+ {
+ xDocument = nullptr;
+ break;
+ }
+
+ xDocument.set( xDocAsChild->getParent(), UNO_QUERY );
+ xDocumentScripts.set( xDocument, UNO_QUERY );
+ }
+ }
+ catch( const Exception& )
+ {
+ DBG_UNHANDLED_EXCEPTION("sfx.doc");
+ xDocumentScripts = nullptr;
+ }
+
+ return xDocumentScripts;
+}
+
+
+// XEventBroadcaster
+
+
+void SAL_CALL SfxBaseModel::addEventListener( const Reference< document::XEventListener >& aListener )
+{
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+
+ m_pData->m_aDocumentEventListeners1.addInterface( aListener );
+}
+
+
+// XEventBroadcaster
+
+
+void SAL_CALL SfxBaseModel::removeEventListener( const Reference< document::XEventListener >& aListener )
+{
+ SfxModelGuard aGuard( *this );
+
+ m_pData->m_aEventListeners.removeInterface( aListener );
+}
+
+// XShapeEventBroadcaster
+
+void SAL_CALL SfxBaseModel::addShapeEventListener( const css::uno::Reference< css::drawing::XShape >& xShape, const Reference< document::XShapeEventListener >& xListener )
+{
+ assert(xShape.is() && "no shape?");
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+
+ m_pData->maShapeListeners[xShape].push_back(xListener);
+}
+
+
+// XShapeEventBroadcaster
+
+
+void SAL_CALL SfxBaseModel::removeShapeEventListener( const css::uno::Reference< css::drawing::XShape >& xShape, const Reference< document::XShapeEventListener >& xListener )
+{
+ SfxModelGuard aGuard( *this );
+
+ auto it = m_pData->maShapeListeners.find(xShape);
+ if (it != m_pData->maShapeListeners.end())
+ {
+ auto rVec = it->second;
+ auto it2 = std::find(rVec.begin(), rVec.end(), xListener);
+ if (it2 != rVec.end())
+ {
+ rVec.erase(it2);
+ if (rVec.empty())
+ m_pData->maShapeListeners.erase(it);
+ }
+ }
+}
+
+// XDocumentEventBroadcaster
+
+
+void SAL_CALL SfxBaseModel::addDocumentEventListener( const Reference< document::XDocumentEventListener >& aListener )
+{
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+ m_pData->m_aDocumentEventListeners2.addInterface( aListener );
+}
+
+
+void SAL_CALL SfxBaseModel::removeDocumentEventListener( const Reference< document::XDocumentEventListener >& aListener )
+{
+ SfxModelGuard aGuard( *this );
+ m_pData->m_aDocumentEventListeners2.removeInterface( aListener );
+}
+
+
+void SAL_CALL SfxBaseModel::notifyDocumentEvent( const OUString&, const Reference< frame::XController2 >&, const Any& )
+{
+ throw lang::NoSupportException("SfxBaseModel controls all the sent notifications itself!" );
+}
+
+Sequence<document::CmisProperty> SAL_CALL SfxBaseModel::getCmisProperties()
+{
+ if (impl_isDisposed())
+ return Sequence<document::CmisProperty>();
+ return m_pData->m_cmisProperties;
+}
+
+void SAL_CALL SfxBaseModel::setCmisProperties( const Sequence< document::CmisProperty >& _cmisproperties )
+{
+ m_pData->m_cmisProperties = _cmisproperties;
+}
+
+void SAL_CALL SfxBaseModel::updateCmisProperties( const Sequence< document::CmisProperty >& aProperties )
+{
+ SfxMedium* pMedium = m_pData->m_pObjectShell->GetMedium();
+ if ( !pMedium )
+ return;
+
+ try
+ {
+ ::ucbhelper::Content aContent( pMedium->GetName( ),
+ Reference<ucb::XCommandEnvironment>(),
+ comphelper::getProcessComponentContext() );
+
+ aContent.executeCommand( "updateProperties", uno::Any( aProperties ) );
+ loadCmisProperties( );
+ }
+ catch (const Exception & e)
+ {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException( e.Message,
+ e.Context, anyEx );
+ }
+
+}
+
+void SAL_CALL SfxBaseModel::checkOut( )
+{
+ SfxMedium* pMedium = m_pData->m_pObjectShell->GetMedium();
+ if ( !pMedium )
+ return;
+
+ try
+ {
+ ::ucbhelper::Content aContent( pMedium->GetName(),
+ Reference<ucb::XCommandEnvironment>(),
+ comphelper::getProcessComponentContext() );
+
+ Any aResult = aContent.executeCommand( "checkout", Any( ) );
+ OUString sURL;
+ aResult >>= sURL;
+
+ m_pData->m_pObjectShell->GetMedium( )->SetName( sURL );
+ m_pData->m_pObjectShell->GetMedium( )->GetMedium_Impl( );
+ m_pData->m_xDocumentProperties->setTitle( getTitle( ) );
+ Sequence< beans::PropertyValue > aSequence ;
+ TransformItems( SID_OPENDOC, pMedium->GetItemSet(), aSequence );
+ attachResource( sURL, aSequence );
+
+ // Reload the CMIS properties
+ loadCmisProperties( );
+ }
+ catch ( const Exception & e )
+ {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException( e.Message,
+ e.Context, anyEx );
+ }
+}
+
+void SAL_CALL SfxBaseModel::cancelCheckOut( )
+{
+ SfxMedium* pMedium = m_pData->m_pObjectShell->GetMedium();
+ if ( !pMedium )
+ return;
+
+ try
+ {
+ ::ucbhelper::Content aContent( pMedium->GetName(),
+ Reference<ucb::XCommandEnvironment>(),
+ comphelper::getProcessComponentContext() );
+
+ Any aResult = aContent.executeCommand( "cancelCheckout", Any( ) );
+ OUString sURL;
+ aResult >>= sURL;
+
+ m_pData->m_pObjectShell->GetMedium( )->SetName( sURL );
+ }
+ catch ( const Exception & e )
+ {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException( e.Message,
+ e.Context, anyEx );
+ }
+}
+
+void SAL_CALL SfxBaseModel::checkIn( sal_Bool bIsMajor, const OUString& rMessage )
+{
+ SfxMedium* pMedium = m_pData->m_pObjectShell->GetMedium();
+ if ( !pMedium )
+ return;
+
+ try
+ {
+ Sequence< beans::PropertyValue > aProps{
+ comphelper::makePropertyValue("VersionMajor", bIsMajor),
+ comphelper::makePropertyValue("VersionComment", rMessage),
+ comphelper::makePropertyValue("CheckIn", true)
+ };
+
+ const OUString sName( pMedium->GetName( ) );
+ storeSelf( aProps );
+
+ // Refresh pMedium as it has probably changed during the storeSelf call
+ pMedium = m_pData->m_pObjectShell->GetMedium( );
+ const OUString sNewName( pMedium->GetName( ) );
+
+ // URL has changed, update the document
+ if ( sName != sNewName )
+ {
+ m_pData->m_xDocumentProperties->setTitle( getTitle( ) );
+ Sequence< beans::PropertyValue > aSequence ;
+ TransformItems( SID_OPENDOC, pMedium->GetItemSet(), aSequence );
+ attachResource( sNewName, aSequence );
+
+ // Reload the CMIS properties
+ loadCmisProperties( );
+ }
+ }
+ catch ( const Exception & e )
+ {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException( e.Message,
+ e.Context, anyEx );
+ }
+}
+
+uno::Sequence< document::CmisVersion > SAL_CALL SfxBaseModel::getAllVersions( )
+{
+ uno::Sequence<document::CmisVersion> aVersions;
+ if (impl_isDisposed())
+ return aVersions;
+ SfxMedium* pMedium = m_pData->m_pObjectShell->GetMedium();
+ if ( pMedium )
+ {
+ try
+ {
+ ::ucbhelper::Content aContent( pMedium->GetName(),
+ Reference<ucb::XCommandEnvironment>(),
+ comphelper::getProcessComponentContext() );
+
+ Any aResult = aContent.executeCommand( "getAllVersions", Any( ) );
+ aResult >>= aVersions;
+ }
+ catch ( const Exception & e )
+ {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException( e.Message,
+ e.Context, anyEx );
+ }
+ }
+ return aVersions;
+}
+
+bool SfxBaseModel::getBoolPropertyValue( const OUString& rName )
+{
+ bool bValue = false;
+ if ( m_pData->m_pObjectShell.is() )
+ {
+ SfxMedium* pMedium = m_pData->m_pObjectShell->GetMedium();
+ if ( pMedium )
+ {
+ try
+ {
+ ::ucbhelper::Content aContent( pMedium->GetName( ),
+ utl::UCBContentHelper::getDefaultCommandEnvironment(),
+ comphelper::getProcessComponentContext() );
+ Reference < beans::XPropertySetInfo > xProps = aContent.getProperties();
+ if ( xProps->hasPropertyByName( rName ) )
+ {
+ aContent.getPropertyValue( rName ) >>= bValue;
+ }
+ }
+ catch ( const Exception & )
+ {
+ // Simply ignore it: it's likely the document isn't versionable in that case
+ bValue = false;
+ }
+ }
+ }
+ return bValue;
+}
+
+sal_Bool SAL_CALL SfxBaseModel::isVersionable( )
+{
+ return getBoolPropertyValue( "IsVersionable" );
+}
+
+sal_Bool SAL_CALL SfxBaseModel::canCheckOut( )
+{
+ return getBoolPropertyValue( "CanCheckOut" );
+}
+
+sal_Bool SAL_CALL SfxBaseModel::canCancelCheckOut( )
+{
+ return getBoolPropertyValue( "CanCancelCheckOut" );
+}
+
+sal_Bool SAL_CALL SfxBaseModel::canCheckIn( )
+{
+ return getBoolPropertyValue( "CanCheckIn" );
+}
+
+void SfxBaseModel::loadCmisProperties( )
+{
+ SfxMedium* pMedium = m_pData->m_pObjectShell->GetMedium();
+ if ( !pMedium )
+ return;
+
+ try
+ {
+ ::ucbhelper::Content aContent( pMedium->GetName( ),
+ utl::UCBContentHelper::getDefaultCommandEnvironment(),
+ comphelper::getProcessComponentContext() );
+ Reference < beans::XPropertySetInfo > xProps = aContent.getProperties();
+ static constexpr OUString aCmisProps( u"CmisProperties"_ustr );
+ if ( xProps->hasPropertyByName( aCmisProps ) )
+ {
+ Sequence< document::CmisProperty> aCmisProperties;
+ aContent.getPropertyValue( aCmisProps ) >>= aCmisProperties;
+ setCmisProperties( aCmisProperties );
+ }
+ }
+ catch (const ucb::ContentCreationException &)
+ {
+ }
+ catch (const ucb::CommandAbortedException &)
+ {
+ }
+}
+
+SfxMedium* SfxBaseModel::handleLoadError( const ErrCodeMsg& rError, SfxMedium* pMedium )
+{
+ if (!rError)
+ {
+ // No error condition.
+ return pMedium;
+ }
+
+ ErrCodeMsg nError = rError;
+ bool bSilent = false;
+ const SfxBoolItem* pSilentItem = pMedium->GetItemSet().GetItem(SID_SILENT, false);
+ if( pSilentItem )
+ bSilent = pSilentItem->GetValue();
+
+ bool bWarning = nError.IsWarning();
+ if ( nError != ERRCODE_IO_BROKENPACKAGE && !bSilent )
+ {
+ // broken package was handled already
+ if ( SfxObjectShell::UseInteractionToHandleError(pMedium->GetInteractionHandler(), nError) && !bWarning)
+ {
+ // abort loading (except for warnings)
+ nError = ERRCODE_IO_ABORT;
+ }
+ }
+
+ if ( m_pData->m_pObjectShell->GetMedium() != pMedium )
+ {
+ // for whatever reason document now has another medium
+ OSL_FAIL("Document has rejected the medium?!");
+ delete pMedium;
+ pMedium = nullptr;
+ }
+
+ if ( !bWarning ) // #i30711# don't abort loading if it's only a warning
+ {
+ nError = nError ? nError : ERRCODE_IO_CANTREAD;
+ throw task::ErrorCodeIOException(
+ "SfxBaseModel::handleLoadError: 0x" + nError.toString(),
+ Reference< XInterface >(), sal_uInt32(nError.GetCode()));
+ }
+ else
+ pMedium->SetWarningError(nError);
+
+ return pMedium;
+}
+
+
+// SfxListener
+
+
+static void addTitle_Impl( Sequence < beans::PropertyValue >& rSeq, const OUString& rTitle )
+{
+ auto [begin, end] = asNonConstRange(rSeq);
+ auto pProp = std::find_if(begin, end,
+ [](const beans::PropertyValue& rProp) { return rProp.Name == "Title"; });
+ if (pProp != end)
+ {
+ pProp->Value <<= rTitle;
+ }
+ else
+ {
+ sal_Int32 nCount = rSeq.getLength();
+ rSeq.realloc( nCount+1 );
+ auto& el = rSeq.getArray()[nCount];
+ el.Name = "Title";
+ el.Value <<= rTitle;
+ }
+}
+
+void SfxBaseModel::Notify( SfxBroadcaster& rBC ,
+ const SfxHint& rHint )
+{
+ if ( !m_pData )
+ return;
+
+ if ( &rBC != m_pData->m_pObjectShell.get() )
+ return;
+
+ if ( rHint.GetId() == SfxHintId::DocChanged )
+ changing();
+ else if (rHint.GetId() == SfxHintId::ThisIsAnSfxEventHint)
+ {
+ const SfxEventHint& rNamedHint = static_cast<const SfxEventHint&>(rHint);
+ switch (rNamedHint.GetEventId())
+ {
+ case SfxEventHintId::StorageChanged:
+ {
+ if ( m_pData->m_xUIConfigurationManager.is()
+ && m_pData->m_pObjectShell->GetCreateMode() != SfxObjectCreateMode::EMBEDDED )
+ {
+ Reference< embed::XStorage > xConfigStorage;
+ static constexpr OUString aUIConfigFolderName( u"Configurations2"_ustr );
+
+ xConfigStorage = getDocumentSubStorage( aUIConfigFolderName, embed::ElementModes::READWRITE );
+ if ( !xConfigStorage.is() )
+ xConfigStorage = getDocumentSubStorage( aUIConfigFolderName, embed::ElementModes::READ );
+
+ if ( xConfigStorage.is() || !m_pData->m_pObjectShell->GetStorage()->hasByName( aUIConfigFolderName ) )
+ {
+ // the storage is different, since otherwise it could not be opened, so it must be exchanged
+ m_pData->m_xUIConfigurationManager->setStorage( xConfigStorage );
+ }
+ else
+ {
+ OSL_FAIL( "Unexpected scenario!" );
+ }
+ }
+
+ ListenForStorage_Impl( m_pData->m_pObjectShell->GetStorage() );
+ }
+ break;
+
+ case SfxEventHintId::LoadFinished:
+ {
+ impl_getPrintHelper();
+ ListenForStorage_Impl( m_pData->m_pObjectShell->GetStorage() );
+ m_pData->setModifiedForAutoSave(false);
+ }
+ break;
+
+ case SfxEventHintId::SaveAsDocDone:
+ {
+ m_pData->m_sURL = m_pData->m_pObjectShell->GetMedium()->GetName();
+
+ Sequence< beans::PropertyValue > aArgs;
+ TransformItems( SID_SAVEASDOC, m_pData->m_pObjectShell->GetMedium()->GetItemSet(), aArgs );
+ addTitle_Impl( aArgs, m_pData->m_pObjectShell->GetTitle() );
+ attachResource( m_pData->m_pObjectShell->GetMedium()->GetName(), aArgs );
+ }
+ break;
+
+ case SfxEventHintId::DocCreated:
+ {
+ impl_getPrintHelper();
+ m_pData->setModifiedForAutoSave(false);
+ }
+ break;
+
+ case SfxEventHintId::ModifyChanged:
+ {
+ m_pData->setModifiedForAutoSave(isModified());
+ }
+ break;
+ default: break;
+ }
+
+ Any aSupplement;
+ if (const SfxPrintingHint* pPrintingHint = dynamic_cast<const SfxPrintingHint*>(&rHint))
+ aSupplement <<= pPrintingHint->GetWhich();
+ const SfxViewEventHint* pViewHint = dynamic_cast<const SfxViewEventHint*>(&rHint);
+ postEvent_Impl( rNamedHint.GetEventName(), pViewHint ? pViewHint->GetController() : Reference< frame::XController2 >(), aSupplement );
+ }
+ else if ( rHint.GetId() == SfxHintId::TitleChanged )
+ {
+ addTitle_Impl( m_pData->m_seqArguments, m_pData->m_pObjectShell->GetTitle() );
+ postEvent_Impl( GlobalEventConfig::GetEventName( GlobalEventId::TITLECHANGED ) );
+ }
+ else if ( rHint.GetId() == SfxHintId::ModeChanged )
+ {
+ postEvent_Impl( GlobalEventConfig::GetEventName( GlobalEventId::MODECHANGED ) );
+ }
+}
+
+
+// public impl.
+
+
+void SfxBaseModel::NotifyModifyListeners_Impl() const
+{
+ if ( m_pData->m_aModifyListeners.getLength() )
+ {
+ lang::EventObject aEvent( static_cast<frame::XModel *>(const_cast<SfxBaseModel *>(this)) );
+ m_pData->m_aModifyListeners.notifyEach( &util::XModifyListener::modified, aEvent );
+ }
+
+ // this notification here is done too generously, we cannot simply assume that we're really modified
+ // now, but we need to check it ...
+ m_pData->setModifiedForAutoSave(const_cast<SfxBaseModel*>(this)->isModified());
+}
+
+void SfxBaseModel::changing()
+{
+ SfxModelGuard aGuard( *this );
+
+ // the notification should not be sent if the document can not be modified
+ if ( !m_pData->m_pObjectShell.is() || !m_pData->m_pObjectShell->IsEnableSetModified() )
+ return;
+
+ NotifyModifyListeners_Impl();
+}
+
+
+// public impl.
+
+
+SfxObjectShell* SfxBaseModel::GetObjectShell() const
+{
+ return m_pData ? m_pData->m_pObjectShell.get() : nullptr;
+}
+
+
+// public impl.
+
+
+bool SfxBaseModel::IsInitialized() const
+{
+ if ( !m_pData || !m_pData->m_pObjectShell.is() )
+ {
+ OSL_FAIL( "SfxBaseModel::IsInitialized: this should have been caught earlier!" );
+ return false;
+ }
+
+ return m_pData->m_pObjectShell->GetMedium() != nullptr;
+}
+
+void SfxBaseModel::MethodEntryCheck( const bool i_mustBeInitialized ) const
+{
+ if ( impl_isDisposed() )
+ throw lang::DisposedException( OUString(), *const_cast< SfxBaseModel* >( this ) );
+ if ( i_mustBeInitialized && !IsInitialized() )
+ throw lang::NotInitializedException( OUString(), *const_cast< SfxBaseModel* >( this ) );
+}
+
+bool SfxBaseModel::impl_isDisposed() const
+{
+ return ( m_pData == nullptr ) ;
+}
+
+
+// private impl.
+
+
+OUString SfxBaseModel::GetMediumFilterName_Impl() const
+{
+ std::shared_ptr<const SfxFilter> pFilter;
+ SfxMedium* pMedium = m_pData->m_pObjectShell->GetMedium();
+ if ( pMedium )
+ pFilter = pMedium->GetFilter();
+
+ if ( pFilter )
+ return pFilter->GetName();
+
+ return OUString();
+}
+
+void SfxBaseModel::impl_store( const OUString& sURL ,
+ const Sequence< beans::PropertyValue >& seqArguments ,
+ bool bSaveTo )
+{
+ if( sURL.isEmpty() )
+ throw frame::IllegalArgumentIOException();
+
+ if (!m_pData->m_pObjectShell)
+ return;
+
+ ::comphelper::SequenceAsHashMap aArgHash(seqArguments);
+ if ( !bSaveTo && !sURL.isEmpty()
+ && !sURL.startsWith( "private:stream" )
+ && ::utl::UCBContentHelper::EqualURLs( getLocation(), sURL ) )
+ {
+ // this is the same file URL as the current document location, try to use storeOwn if possible
+
+ static constexpr OUString aFilterString( u"FilterName"_ustr );
+ const OUString aFilterName( aArgHash.getUnpackedValueOrDefault( aFilterString, OUString() ) );
+ if ( !aFilterName.isEmpty() )
+ {
+ SfxMedium* pMedium = m_pData->m_pObjectShell->GetMedium();
+ if ( pMedium )
+ {
+ const std::shared_ptr<const SfxFilter>& pFilter = pMedium->GetFilter();
+ if ( pFilter && aFilterName == pFilter->GetFilterName() )
+ {
+ // #i119366# - If the former file saving with password, do not trying in StoreSelf anyway...
+ bool bFormerPassword = false;
+ {
+ uno::Sequence< beans::NamedValue > aOldEncryptionData;
+ if (GetEncryptionData_Impl( &pMedium->GetItemSet(), aOldEncryptionData ))
+ {
+ bFormerPassword = true;
+ }
+ }
+ if ( !bFormerPassword )
+ {
+ aArgHash.erase( aFilterString );
+ aArgHash.erase( "URL" );
+
+ try
+ {
+ storeSelf( aArgHash.getAsConstPropertyValueList() );
+ return;
+ }
+ catch( const lang::IllegalArgumentException& )
+ {
+#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT
+ // some additional arguments do not allow to use saving, SaveAs should be done
+ // but only for normal documents, the shared documents would be overwritten in this case
+ // that would mean an information loss
+ // TODO/LATER: need a new interaction for this case
+ if ( m_pData->m_pObjectShell->IsDocShared() )
+ {
+ uno::Sequence< beans::NamedValue > aNewEncryptionData = aArgHash.getUnpackedValueOrDefault("EncryptionData", uno::Sequence< beans::NamedValue >() );
+ if ( !aNewEncryptionData.hasElements() )
+ {
+ aNewEncryptionData = ::comphelper::OStorageHelper::CreatePackageEncryptionData( aArgHash.getUnpackedValueOrDefault("Password", OUString()) );
+ }
+
+ uno::Sequence< beans::NamedValue > aOldEncryptionData;
+ (void)GetEncryptionData_Impl( &pMedium->GetItemSet(), aOldEncryptionData );
+
+ if ( !aOldEncryptionData.hasElements() && !aNewEncryptionData.hasElements() )
+ throw;
+ else
+ {
+ // if the password is changed a special error should be used in case of shared document
+ throw task::ErrorCodeIOException("Can not change password for shared document.", uno::Reference< uno::XInterface >(), sal_uInt32(ERRCODE_SFX_SHARED_NOPASSWORDCHANGE) );
+ }
+ }
+#endif
+ }
+ }
+ }
+ }
+ }
+ }
+
+ SfxGetpApp()->NotifyEvent( SfxEventHint( bSaveTo ? SfxEventHintId::SaveToDoc : SfxEventHintId::SaveAsDoc, GlobalEventConfig::GetEventName( bSaveTo ? GlobalEventId::SAVETODOC : GlobalEventId::SAVEASDOC ),
+ m_pData->m_pObjectShell.get() ) );
+
+ const OUString aFilterName(aArgHash.getUnpackedValueOrDefault("FilterName", OUString()));
+ OUString aPassword, aPasswordToModify;
+ if (!aArgHash.getUnpackedValueOrDefault("EncryptionData", Sequence<beans::NamedValue>())
+ .hasElements())
+ aPassword = aArgHash.getUnpackedValueOrDefault("Password", OUString());
+ if (!aArgHash.getUnpackedValueOrDefault("ModifyPasswordInfo", Sequence<beans::PropertyValue>())
+ .hasElements()
+ && aArgHash.getUnpackedValueOrDefault("ModifyPasswordInfo", static_cast<sal_Int32>(0)) == 0)
+ aPasswordToModify = aArgHash.getUnpackedValueOrDefault("PasswordToModify", OUString());
+ aArgHash.erase("PasswordToModify");
+
+ std::optional<SfxAllItemSet> pItemSet(SfxGetpApp()->GetPool());
+ pItemSet->Put(SfxStringItem(SID_FILE_NAME, sURL));
+ if ( bSaveTo )
+ pItemSet->Put(SfxBoolItem(SID_SAVETO, true));
+
+ if (!aFilterName.isEmpty() && (!aPassword.isEmpty() || !aPasswordToModify.isEmpty()))
+ sfx2::SetPassword(SfxGetpApp()->GetFilterMatcher().GetFilter4FilterName(aFilterName),
+ &*pItemSet, aPassword, aPasswordToModify, false);
+
+ TransformParameters(SID_SAVEASDOC, seqArguments, *pItemSet);
+
+ const SfxBoolItem* pCopyStreamItem = pItemSet->GetItem<SfxBoolItem>(SID_COPY_STREAM_IF_POSSIBLE, false);
+
+ if ( pCopyStreamItem && pCopyStreamItem->GetValue() && !bSaveTo )
+ {
+ throw frame::IllegalArgumentIOException(
+ "CopyStreamIfPossible parameter is not acceptable for storeAsURL() call!" );
+ }
+
+ sal_uInt32 nModifyPasswordHash = 0;
+ Sequence< beans::PropertyValue > aModifyPasswordInfo;
+ const SfxUnoAnyItem* pModifyPasswordInfoItem = pItemSet->GetItem<SfxUnoAnyItem>(SID_MODIFYPASSWORDINFO, false);
+ if ( pModifyPasswordInfoItem )
+ {
+ // it contains either a simple hash or a set of PropertyValues
+ // TODO/LATER: the sequence of PropertyValue should replace the hash completely in future
+ sal_Int32 nMPHTmp = 0;
+ pModifyPasswordInfoItem->GetValue() >>= nMPHTmp;
+ nModifyPasswordHash = static_cast<sal_uInt32>(nMPHTmp);
+ pModifyPasswordInfoItem->GetValue() >>= aModifyPasswordInfo;
+ }
+ pItemSet->ClearItem(SID_MODIFYPASSWORDINFO);
+ sal_uInt32 nOldModifyPasswordHash = m_pData->m_pObjectShell->GetModifyPasswordHash();
+ m_pData->m_pObjectShell->SetModifyPasswordHash( nModifyPasswordHash );
+ Sequence< beans::PropertyValue > aOldModifyPasswordInfo = m_pData->m_pObjectShell->GetModifyPasswordInfo();
+ m_pData->m_pObjectShell->SetModifyPasswordInfo( aModifyPasswordInfo );
+
+ // since saving a document modifies its DocumentProperties, the current
+ // DocumentProperties must be saved on "SaveTo", so it can be restored
+ // after saving
+ bool bCopyTo = bSaveTo ||
+ m_pData->m_pObjectShell->GetCreateMode() == SfxObjectCreateMode::EMBEDDED;
+ Reference<document::XDocumentProperties> xOldDocProps;
+ if ( bCopyTo )
+ {
+ xOldDocProps = getDocumentProperties();
+ const Reference<util::XCloneable> xCloneable(xOldDocProps,
+ UNO_QUERY_THROW);
+ const Reference<document::XDocumentProperties> xNewDocProps(
+ xCloneable->createClone(), UNO_QUERY_THROW);
+ m_pData->m_xDocumentProperties = xNewDocProps;
+ }
+
+ bool bRet = m_pData->m_pObjectShell->APISaveAs_Impl(sURL, *pItemSet, seqArguments);
+
+ if ( bCopyTo )
+ {
+ // restore DocumentProperties if a copy was created
+ m_pData->m_xDocumentProperties = xOldDocProps;
+ }
+
+ Reference < task::XInteractionHandler > xHandler;
+ const SfxUnoAnyItem* pItem = pItemSet->GetItem<SfxUnoAnyItem>(SID_INTERACTIONHANDLER, false);
+ if ( pItem )
+ pItem->GetValue() >>= xHandler;
+
+ pItemSet.reset();
+
+ ErrCodeMsg nErrCode = m_pData->m_pObjectShell->GetErrorCode();
+ if ( !bRet && !nErrCode )
+ {
+ SAL_WARN("sfx.doc", "Storing has failed, no error is set!");
+ nErrCode = ERRCODE_IO_CANTWRITE;
+ }
+ m_pData->m_pObjectShell->ResetError();
+
+ if ( bRet )
+ {
+ if ( nErrCode )
+ {
+ // must be a warning - use Interactionhandler if possible or abandon
+ if ( xHandler.is() )
+ {
+ // TODO/LATER: a general way to set the error context should be available
+ SfxErrorContext aEc( ERRCTX_SFX_SAVEASDOC, m_pData->m_pObjectShell->GetTitle() );
+
+ task::ErrorCodeRequest2 aErrorCode(OUString(), uno::Reference<XInterface>(),
+ sal_Int32(sal_uInt32(nErrCode.GetCode())), nErrCode.GetArg1(), nErrCode.GetArg2(),
+ static_cast<sal_Int16>(nErrCode.GetDialogMask()));
+ SfxMedium::CallApproveHandler( xHandler, Any( aErrorCode ), false );
+ }
+ }
+
+ if ( !bSaveTo )
+ {
+ m_pData->m_aPreusedFilterName = GetMediumFilterName_Impl();
+ m_pData->m_pObjectShell->SetModifyPasswordEntered();
+
+ SfxGetpApp()->NotifyEvent( SfxEventHint( SfxEventHintId::SaveAsDocDone, GlobalEventConfig::GetEventName(GlobalEventId::SAVEASDOCDONE), m_pData->m_pObjectShell.get() ) );
+ }
+ else
+ {
+ m_pData->m_pObjectShell->SetModifyPasswordHash( nOldModifyPasswordHash );
+ m_pData->m_pObjectShell->SetModifyPasswordInfo( aOldModifyPasswordInfo );
+
+ SfxGetpApp()->NotifyEvent( SfxEventHint( SfxEventHintId::SaveToDocDone, GlobalEventConfig::GetEventName(GlobalEventId::SAVETODOCDONE), m_pData->m_pObjectShell.get() ) );
+ }
+ }
+ else
+ {
+ m_pData->m_pObjectShell->SetModifyPasswordHash( nOldModifyPasswordHash );
+ m_pData->m_pObjectShell->SetModifyPasswordInfo( aOldModifyPasswordInfo );
+
+
+ SfxGetpApp()->NotifyEvent( SfxEventHint( bSaveTo ? SfxEventHintId::SaveToDocFailed : SfxEventHintId::SaveAsDocFailed, GlobalEventConfig::GetEventName( bSaveTo ? GlobalEventId::SAVETODOCFAILED : GlobalEventId::SAVEASDOCFAILED),
+ m_pData->m_pObjectShell.get() ) );
+
+ if ( comphelper::LibreOfficeKit::isActive() && SfxViewShell::Current() )
+ SfxViewShell::Current()->libreOfficeKitViewCallback( LOK_CALLBACK_EXPORT_FILE, "ERROR"_ostr );
+
+ std::stringstream aErrCode;
+ aErrCode << nErrCode;
+ throw task::ErrorCodeIOException(
+ "SfxBaseModel::impl_store <" + sURL + "> failed: " + OUString::fromUtf8(aErrCode.str()),
+ Reference< XInterface >(), sal_uInt32(nErrCode.GetCode()));
+ }
+}
+
+
+namespace {
+template< typename ListenerT, typename EventT >
+class NotifySingleListenerIgnoreRE
+{
+private:
+ typedef void ( SAL_CALL ListenerT::*NotificationMethod )( const EventT& );
+ NotificationMethod m_pMethod;
+ const EventT& m_rEvent;
+public:
+ NotifySingleListenerIgnoreRE( NotificationMethod method, const EventT& event ) : m_pMethod( method ), m_rEvent( event ) { }
+
+ void operator()( const Reference<ListenerT>& listener ) const
+ {
+ try
+ {
+ (listener.get()->*m_pMethod)( m_rEvent );
+ }
+ catch( RuntimeException& )
+ {
+ // this exception is ignored to avoid problems with invalid listeners, the listener should be probably thrown away in future
+ }
+ }
+};
+} // anonymous namespace
+
+void SfxBaseModel::postEvent_Impl( const OUString& aName, const Reference< frame::XController2 >& xController, const Any& supplement )
+{
+ // object already disposed?
+ if ( impl_isDisposed() )
+ return;
+
+ // keep m_pData alive, if notified target would dispose the document
+ std::shared_ptr<IMPL_SfxBaseModel_DataContainer> xKeepAlive(m_pData);
+
+ // also make sure this object doesn't self-destruct while notifying
+ rtl::Reference<SfxBaseModel> xHoldAlive(this);
+
+ DBG_ASSERT( !aName.isEmpty(), "Empty event name!" );
+ if (aName.isEmpty())
+ return;
+
+ if ( xKeepAlive->m_aDocumentEventListeners2.getLength() )
+ {
+ SAL_INFO("sfx.doc", "SfxDocumentEvent: " + aName);
+
+ document::DocumentEvent aDocumentEvent( static_cast<frame::XModel*>(this), aName, xController, supplement );
+
+ xKeepAlive->m_aDocumentEventListeners2.forEach(
+ NotifySingleListenerIgnoreRE< document::XDocumentEventListener, document::DocumentEvent >(
+ &document::XDocumentEventListener::documentEventOccured,
+ aDocumentEvent ) );
+ }
+
+ if ( xKeepAlive->m_aDocumentEventListeners1.getLength() )
+ {
+ SAL_INFO("sfx.doc", "SfxEvent: " + aName);
+
+ document::EventObject aEvent( static_cast<frame::XModel*>(this), aName );
+
+ xKeepAlive->m_aDocumentEventListeners1.forEach(
+ NotifySingleListenerIgnoreRE< document::XEventListener, document::EventObject >(
+ &document::XEventListener::notifyEvent,
+ aEvent ) );
+ }
+
+}
+
+Reference < container::XIndexAccess > SAL_CALL SfxBaseModel::getViewData()
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( m_pData->m_pObjectShell.is() && !m_pData->m_contViewData.is() )
+ {
+ SfxViewFrame *pActFrame = SfxViewFrame::Current();
+ if ( !pActFrame || pActFrame->GetObjectShell() != m_pData->m_pObjectShell.get() )
+ pActFrame = SfxViewFrame::GetFirst( m_pData->m_pObjectShell.get() );
+
+ if ( !pActFrame || !pActFrame->GetViewShell() )
+ // currently no frame for this document at all or View is under construction
+ return Reference < container::XIndexAccess >();
+
+ m_pData->m_contViewData = new comphelper::IndexedPropertyValuesContainer();
+
+ if ( !m_pData->m_contViewData.is() )
+ {
+ // error: no container class available!
+ return Reference < container::XIndexAccess >();
+ }
+
+ Reference < container::XIndexContainer > xCont( m_pData->m_contViewData, UNO_QUERY );
+ sal_Int32 nCount = 0;
+ Sequence < beans::PropertyValue > aSeq;
+ for ( SfxViewFrame *pFrame = SfxViewFrame::GetFirst( m_pData->m_pObjectShell.get() ); pFrame;
+ pFrame = SfxViewFrame::GetNext( *pFrame, m_pData->m_pObjectShell.get() ) )
+ {
+ bool bIsActive = ( pFrame == pActFrame );
+ pFrame->GetViewShell()->WriteUserDataSequence( aSeq );
+ xCont->insertByIndex( bIsActive ? 0 : nCount, Any(aSeq) );
+ nCount++;
+ }
+ }
+
+ return m_pData->m_contViewData;
+}
+
+void SAL_CALL SfxBaseModel::setViewData( const Reference < container::XIndexAccess >& aData )
+{
+ SfxModelGuard aGuard( *this );
+
+ m_pData->m_contViewData = aData;
+}
+
+/** calls all XEventListeners */
+void SfxBaseModel::notifyEvent( const document::EventObject& aEvent ) const
+{
+ // object already disposed?
+ if ( impl_isDisposed() )
+ return;
+
+ if( !m_pData->m_aDocumentEventListeners1.getLength() )
+
+ return;
+
+ comphelper::OInterfaceIteratorHelper3 aIt( m_pData->m_aDocumentEventListeners1 );
+ while( aIt.hasMoreElements() )
+ {
+ try
+ {
+ aIt.next()->notifyEvent( aEvent );
+ }
+ catch( RuntimeException& )
+ {
+ aIt.remove();
+ }
+ }
+ // for right now, we're only doing the event that this particular performance problem needed
+ if (aEvent.EventName == "ShapeModified")
+ {
+ uno::Reference<drawing::XShape> xShape(aEvent.Source, uno::UNO_QUERY);
+ if (xShape.is())
+ {
+ auto it = m_pData->maShapeListeners.find(xShape);
+ if (it != m_pData->maShapeListeners.end())
+ for (auto const & rListenerUnoRef : it->second)
+ rListenerUnoRef->notifyShapeEvent(aEvent);
+ }
+ }
+}
+
+/** returns true if someone added a XEventListener to this XEventBroadcaster */
+bool SfxBaseModel::hasEventListeners() const
+{
+ return !impl_isDisposed()
+ && ( m_pData->m_aDocumentEventListeners1.getLength() != 0
+ || !m_pData->maShapeListeners.empty());
+}
+
+void SAL_CALL SfxBaseModel::addPrintJobListener( const Reference< view::XPrintJobListener >& xListener )
+{
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+
+ impl_getPrintHelper();
+ Reference < view::XPrintJobBroadcaster > xPJB( m_pData->m_xPrintable, UNO_QUERY );
+ if ( xPJB.is() )
+ xPJB->addPrintJobListener( xListener );
+}
+
+void SAL_CALL SfxBaseModel::removePrintJobListener( const Reference< view::XPrintJobListener >& xListener )
+{
+ SfxModelGuard aGuard( *this );
+
+ impl_getPrintHelper();
+ Reference < view::XPrintJobBroadcaster > xPJB( m_pData->m_xPrintable, UNO_QUERY );
+ if ( xPJB.is() )
+ xPJB->removePrintJobListener( xListener );
+}
+
+sal_Int64 SAL_CALL SfxBaseModel::getSomething( const Sequence< sal_Int8 >& aIdentifier )
+{
+ SvGlobalName aName( aIdentifier );
+ if (aName == SvGlobalName( SFX_GLOBAL_CLASSID ))
+ {
+ SolarMutexGuard aGuard;
+ SfxObjectShell *const pObjectShell(GetObjectShell());
+ if (pObjectShell)
+ {
+ return comphelper::getSomething_cast(pObjectShell);
+ }
+ }
+
+ return 0;
+}
+
+
+// XDocumentSubStorageSupplier
+
+
+void SfxBaseModel::ListenForStorage_Impl( const Reference< embed::XStorage >& xStorage )
+{
+ Reference< util::XModifiable > xModifiable( xStorage, UNO_QUERY );
+ if ( xModifiable.is() )
+ {
+ if ( !m_pData->m_pStorageModifyListen.is() )
+ {
+ m_pData->m_pStorageModifyListen = new ::sfx2::DocumentStorageModifyListener( *m_pData, Application::GetSolarMutex() );
+ }
+
+ // no need to deregister the listening for old storage since it should be disposed automatically
+ xModifiable->addModifyListener( m_pData->m_pStorageModifyListen );
+ }
+}
+
+Reference< embed::XStorage > SAL_CALL SfxBaseModel::getDocumentSubStorage( const OUString& aStorageName, sal_Int32 nMode )
+{
+ SfxModelGuard aGuard( *this );
+
+ Reference< embed::XStorage > xResult;
+ if ( m_pData->m_pObjectShell.is() )
+ {
+ Reference< embed::XStorage > xStorage = m_pData->m_pObjectShell->GetStorage();
+ if ( xStorage.is() )
+ {
+ try
+ {
+ xResult = xStorage->openStorageElement( aStorageName, nMode );
+ }
+ catch ( Exception& )
+ {
+ }
+ }
+ }
+
+ return xResult;
+}
+
+Sequence< OUString > SAL_CALL SfxBaseModel::getDocumentSubStoragesNames()
+{
+ SfxModelGuard aGuard( *this );
+
+ Sequence< OUString > aResult;
+ bool bSuccess = false;
+ if ( m_pData->m_pObjectShell.is() )
+ {
+ Reference < embed::XStorage > xStorage = m_pData->m_pObjectShell->GetStorage();
+ if ( xStorage.is() )
+ {
+ const Sequence< OUString > aTemp = xStorage->getElementNames();
+ sal_Int32 nResultSize = 0;
+ for ( const auto& rName : aTemp )
+ {
+ if ( xStorage->isStorageElement( rName ) )
+ {
+ aResult.realloc( ++nResultSize );
+ aResult.getArray()[ nResultSize - 1 ] = rName;
+ }
+ }
+
+ bSuccess = true;
+ }
+ }
+
+ if ( !bSuccess )
+ throw io::IOException();
+
+ return aResult;
+}
+
+
+// XScriptProviderSupplier
+
+
+Reference< script::provider::XScriptProvider > SAL_CALL SfxBaseModel::getScriptProvider()
+{
+ SfxModelGuard aGuard( *this );
+
+ Reference< script::provider::XScriptProviderFactory > xScriptProviderFactory =
+ script::provider::theMasterScriptProviderFactory::get( ::comphelper::getProcessComponentContext() );
+
+ Reference< XScriptInvocationContext > xScriptContext( this );
+
+ Reference< script::provider::XScriptProvider > xScriptProvider(
+ xScriptProviderFactory->createScriptProvider( Any( xScriptContext ) ),
+ UNO_SET_THROW );
+
+ return xScriptProvider;
+}
+
+
+// XUIConfigurationManagerSupplier
+
+
+OUString const & SfxBaseModel::getRuntimeUID() const
+{
+ OSL_ENSURE( !m_pData->m_sRuntimeUID.isEmpty(),
+ "SfxBaseModel::getRuntimeUID - ID is empty!" );
+ return m_pData->m_sRuntimeUID;
+}
+
+bool SfxBaseModel::hasValidSignatures() const
+{
+ SolarMutexGuard aGuard;
+ if ( m_pData->m_pObjectShell.is() )
+ return ( m_pData->m_pObjectShell->ImplGetSignatureState() == SignatureState::OK );
+ return false;
+}
+
+void SfxBaseModel::getGrabBagItem(css::uno::Any& rVal) const
+{
+ if (m_pData->m_xGrabBagItem)
+ m_pData->m_xGrabBagItem->QueryValue(rVal);
+ else
+ rVal <<= uno::Sequence<beans::PropertyValue>();
+}
+
+void SfxBaseModel::setGrabBagItem(const css::uno::Any& rVal)
+{
+ if (!m_pData->m_xGrabBagItem)
+ m_pData->m_xGrabBagItem = std::make_shared<SfxGrabBagItem>();
+
+ m_pData->m_xGrabBagItem->PutValue(rVal, 0);
+}
+
+static void GetCommandFromSequence( OUString& rCommand, sal_Int32& nIndex, const Sequence< beans::PropertyValue >& rSeqPropValue )
+{
+ nIndex = -1;
+
+ auto pPropValue = std::find_if(rSeqPropValue.begin(), rSeqPropValue.end(),
+ [](const beans::PropertyValue& rPropValue) { return rPropValue.Name == "Command"; });
+ if (pPropValue != rSeqPropValue.end())
+ {
+ pPropValue->Value >>= rCommand;
+ nIndex = static_cast<sal_Int32>(std::distance(rSeqPropValue.begin(), pPropValue));
+ }
+}
+
+static void ConvertSlotsToCommands( SfxObjectShell const * pDoc, Reference< container::XIndexContainer > const & rToolbarDefinition )
+{
+ if ( !pDoc )
+ return;
+
+ SfxModule* pModule( pDoc->GetFactory().GetModule() );
+ Sequence< beans::PropertyValue > aSeqPropValue;
+
+ for ( sal_Int32 i = 0; i < rToolbarDefinition->getCount(); i++ )
+ {
+ if ( rToolbarDefinition->getByIndex( i ) >>= aSeqPropValue )
+ {
+ OUString aCommand;
+ sal_Int32 nIndex( -1 );
+ GetCommandFromSequence( aCommand, nIndex, aSeqPropValue );
+ if ( nIndex >= 0 && aCommand.startsWith( "slot:" ) )
+ {
+ const sal_uInt16 nSlot = o3tl::toInt32(aCommand.subView( 5 ));
+
+ // We have to replace the old "slot-Command" with our new ".uno:-Command"
+ const SfxSlot* pSlot = pModule->GetSlotPool()->GetSlot( nSlot );
+ if ( pSlot )
+ {
+ aCommand = pSlot->GetCommand();
+ aSeqPropValue.getArray()[nIndex].Value <<= aCommand;
+ rToolbarDefinition->replaceByIndex( i, Any( aSeqPropValue ));
+ }
+ }
+ }
+ }
+}
+
+Reference< ui::XUIConfigurationManager > SAL_CALL SfxBaseModel::getUIConfigurationManager()
+{
+ return Reference< ui::XUIConfigurationManager >( getUIConfigurationManager2(), UNO_QUERY_THROW );
+}
+
+Reference< ui::XUIConfigurationManager2 > SfxBaseModel::getUIConfigurationManager2()
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( !m_pData->m_xUIConfigurationManager.is() )
+ {
+ Reference< ui::XUIConfigurationManager2 > xNewUIConfMan =
+ ui::UIConfigurationManager::create( comphelper::getProcessComponentContext() );
+
+ Reference< embed::XStorage > xConfigStorage;
+
+ OUString aUIConfigFolderName( "Configurations2" );
+ // First try to open with READWRITE and then READ
+ xConfigStorage = getDocumentSubStorage( aUIConfigFolderName, embed::ElementModes::READWRITE );
+ if ( xConfigStorage.is() )
+ {
+ static constexpr OUString aMediaTypeProp( u"MediaType"_ustr );
+ OUString aMediaType;
+ Reference< beans::XPropertySet > xPropSet( xConfigStorage, UNO_QUERY );
+ Any a = xPropSet->getPropertyValue( aMediaTypeProp );
+ if ( !( a >>= aMediaType ) || aMediaType.isEmpty())
+ {
+ xPropSet->setPropertyValue( aMediaTypeProp, Any(OUString("application/vnd.sun.xml.ui.configuration")) );
+ }
+ }
+ else
+ xConfigStorage = getDocumentSubStorage( aUIConfigFolderName, embed::ElementModes::READ );
+
+ // initialize ui configuration manager with document substorage
+ xNewUIConfMan->setStorage( xConfigStorage );
+
+ // embedded objects did not support local configuration data until OOo 3.0, so there's nothing to
+ // migrate
+ if ( m_pData->m_pObjectShell->GetCreateMode() != SfxObjectCreateMode::EMBEDDED )
+ {
+ // Import old UI configuration from OOo 1.x
+
+ // Try to open with READ
+ Reference< embed::XStorage > xOOo1ConfigStorage = getDocumentSubStorage( "Configurations", embed::ElementModes::READ );
+ if ( xOOo1ConfigStorage.is() )
+ {
+ Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
+ std::vector< Reference< container::XIndexContainer > > rToolbars;
+
+ bool bImported = framework::UIConfigurationImporterOOo1x::ImportCustomToolbars(
+ xNewUIConfMan, rToolbars, xContext, xOOo1ConfigStorage );
+ if ( bImported )
+ {
+ SfxObjectShell* pObjShell = SfxBaseModel::GetObjectShell();
+
+ for ( size_t i = 0; i < rToolbars.size(); i++ )
+ {
+ const OUString sId(OUString::number( i + 1 ));
+ const OUString aCustomTbxName = "private:resource/toolbar/custom_OOo1x_" + sId;
+
+ Reference< container::XIndexContainer > xToolbar = rToolbars[i];
+ ConvertSlotsToCommands( pObjShell, xToolbar );
+ if ( !xNewUIConfMan->hasSettings( aCustomTbxName ))
+ {
+ // Set UIName for the toolbar with container property
+ Reference< beans::XPropertySet > xPropSet( xToolbar, UNO_QUERY );
+ if ( xPropSet.is() )
+ {
+ try
+ {
+ xPropSet->setPropertyValue( "UIName", Any( "Toolbar " + sId ) );
+ }
+ catch ( beans::UnknownPropertyException& )
+ {
+ }
+ }
+
+ xNewUIConfMan->insertSettings( aCustomTbxName, xToolbar );
+ xNewUIConfMan->store();
+ }
+ }
+ }
+ }
+ }
+
+ m_pData->m_xUIConfigurationManager = xNewUIConfMan;
+ }
+
+ return m_pData->m_xUIConfigurationManager;
+}
+
+
+// XVisualObject
+
+
+void SAL_CALL SfxBaseModel::setVisualAreaSize( sal_Int64 nAspect, const awt::Size& aSize )
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( !m_pData->m_pObjectShell.is() )
+ throw Exception("no object shell", nullptr); // TODO: error handling
+
+ SfxViewFrame* pViewFrm = SfxViewFrame::GetFirst( m_pData->m_pObjectShell.get(), false );
+ if ( pViewFrm && m_pData->m_pObjectShell->GetCreateMode() == SfxObjectCreateMode::EMBEDDED && !pViewFrm->GetFrame().IsInPlace() )
+ {
+ VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( pViewFrm->GetFrame().GetFrameInterface()->getContainerWindow() );
+ Size aWinSize = pWindow->GetSizePixel();
+ awt::Size aCurrent = getVisualAreaSize( nAspect );
+ Size aDiff( aSize.Width-aCurrent.Width, aSize.Height-aCurrent.Height );
+ aDiff = pViewFrm->GetViewShell()->GetWindow()->LogicToPixel( aDiff );
+ aWinSize.AdjustWidth(aDiff.Width() );
+ aWinSize.AdjustHeight(aDiff.Height() );
+ pWindow->SetSizePixel( aWinSize );
+ }
+ else
+ {
+ tools::Rectangle aTmpRect = m_pData->m_pObjectShell->GetVisArea( ASPECT_CONTENT );
+ aTmpRect.SetSize( Size( aSize.Width, aSize.Height ) );
+ m_pData->m_pObjectShell->SetVisArea( aTmpRect );
+ }
+}
+
+awt::Size SAL_CALL SfxBaseModel::getVisualAreaSize( sal_Int64 /*nAspect*/ )
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( !m_pData->m_pObjectShell.is() )
+ throw Exception("no object shell", nullptr); // TODO: error handling
+
+ tools::Rectangle aTmpRect = m_pData->m_pObjectShell->GetVisArea( ASPECT_CONTENT );
+
+ return awt::Size( aTmpRect.GetWidth(), aTmpRect.GetHeight() );
+}
+
+
+sal_Int32 SAL_CALL SfxBaseModel::getMapUnit( sal_Int64 /*nAspect*/ )
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( !m_pData->m_pObjectShell.is() )
+ throw Exception("no object shell", nullptr); // TODO: error handling
+
+ return VCLUnoHelper::VCL2UnoEmbedMapUnit( m_pData->m_pObjectShell->GetMapUnit() );
+}
+
+embed::VisualRepresentation SAL_CALL SfxBaseModel::getPreferredVisualRepresentation( ::sal_Int64 /*nAspect*/ )
+{
+ SfxModelGuard aGuard( *this );
+
+ datatransfer::DataFlavor aDataFlavor(
+ "application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\"",
+ "GDIMetaFile",
+ cppu::UnoType<Sequence< sal_Int8 >>::get() );
+
+ embed::VisualRepresentation aVisualRepresentation;
+ aVisualRepresentation.Data = getTransferData( aDataFlavor );
+ aVisualRepresentation.Flavor = aDataFlavor;
+
+ return aVisualRepresentation;
+}
+
+
+// XStorageBasedDocument
+
+
+void SAL_CALL SfxBaseModel::loadFromStorage( const Reference< embed::XStorage >& xStorage,
+ const Sequence< beans::PropertyValue >& aMediaDescriptor )
+{
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+ if ( IsInitialized() )
+ throw frame::DoubleInitializationException( OUString(), *this );
+
+ // after i36090 is fixed the pool from object shell can be used
+ // SfxAllItemSet aSet( m_pData->m_pObjectShell->GetPool() );
+ SfxAllItemSet aSet( SfxGetpApp()->GetPool() );
+
+ // the BaseURL is part of the ItemSet
+ SfxMedium* pMedium = new SfxMedium( xStorage, OUString() );
+ TransformParameters( SID_OPENDOC, aMediaDescriptor, aSet );
+ pMedium->GetItemSet().Put( aSet );
+
+ // allow to use an interactionhandler (if there is one)
+ pMedium->UseInteractionHandler( true );
+
+ const SfxBoolItem* pTemplateItem = aSet.GetItem<SfxBoolItem>(SID_TEMPLATE, false);
+ bool bTemplate = pTemplateItem && pTemplateItem->GetValue();
+ m_pData->m_pObjectShell->SetActivateEvent_Impl( bTemplate ? SfxEventHintId::CreateDoc : SfxEventHintId::OpenDoc );
+ m_pData->m_pObjectShell->Get_Impl()->bOwnsStorage = false;
+
+ // load document
+ if ( !m_pData->m_pObjectShell->DoLoad(pMedium) )
+ {
+ ErrCodeMsg nError = m_pData->m_pObjectShell->GetErrorCode();
+ nError = nError ? nError : ERRCODE_IO_CANTREAD;
+ throw task::ErrorCodeIOException(
+ "SfxBaseModel::loadFromStorage: " + nError.toString(),
+ Reference< XInterface >(), sal_uInt32(nError.GetCode()));
+ }
+ loadCmisProperties( );
+}
+
+void SAL_CALL SfxBaseModel::storeToStorage( const Reference< embed::XStorage >& xStorage,
+ const Sequence< beans::PropertyValue >& aMediaDescriptor )
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( !m_pData->m_pObjectShell.is() )
+ throw io::IOException(); // TODO:
+
+ auto xSet = std::make_shared<SfxAllItemSet>(m_pData->m_pObjectShell->GetPool());
+ TransformParameters( SID_SAVEASDOC, aMediaDescriptor, *xSet );
+
+ // TODO/LATER: maybe a special URL "private:storage" should be used
+ const SfxStringItem* pItem = xSet->GetItem<SfxStringItem>(SID_FILTER_NAME, false);
+ sal_Int32 nVersion = SOFFICE_FILEFORMAT_CURRENT;
+ if( pItem )
+ {
+ std::shared_ptr<const SfxFilter> pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4FilterName( pItem->GetValue() );
+ if ( pFilter && pFilter->UsesStorage() )
+ nVersion = pFilter->GetVersion();
+ }
+
+ bool bSuccess = false;
+ if ( xStorage == m_pData->m_pObjectShell->GetStorage() )
+ {
+ // storing to the own storage
+ bSuccess = m_pData->m_pObjectShell->DoSave();
+ }
+ else
+ {
+ // TODO/LATER: if the provided storage has some data inside the storing might fail, probably the storage must be truncated
+ // TODO/LATER: is it possible to have a template here?
+ m_pData->m_pObjectShell->SetupStorage( xStorage, nVersion, false );
+
+ // BaseURL is part of the ItemSet
+ SfxMedium aMedium( xStorage, OUString(), xSet );
+ aMedium.CanDisposeStorage_Impl( false );
+ if ( aMedium.GetFilter() )
+ {
+ // storing without a valid filter will often crash
+ bSuccess = m_pData->m_pObjectShell->DoSaveObjectAs( aMedium, true );
+ m_pData->m_pObjectShell->DoSaveCompleted();
+ }
+ }
+
+ ErrCodeMsg nError = m_pData->m_pObjectShell->GetErrorCode();
+ m_pData->m_pObjectShell->ResetError();
+
+ // the warnings are currently not transported
+ if ( !bSuccess )
+ {
+ nError = nError ? nError : ERRCODE_IO_GENERAL;
+ throw task::ErrorCodeIOException(
+ "SfxBaseModel::storeToStorage: " + nError.toString(),
+ Reference< XInterface >(), sal_uInt32(nError.GetCode()));
+ }
+}
+
+void SAL_CALL SfxBaseModel::switchToStorage( const Reference< embed::XStorage >& xStorage )
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( !m_pData->m_pObjectShell.is() )
+ throw io::IOException(); // TODO:
+
+ // the persistence should be switched only if the storage is different
+ if ( xStorage != m_pData->m_pObjectShell->GetStorage() )
+ {
+ if ( !m_pData->m_pObjectShell->SwitchPersistence( xStorage ) )
+ {
+ ErrCodeMsg nError = m_pData->m_pObjectShell->GetErrorCode();
+ nError = nError ? nError : ERRCODE_IO_GENERAL;
+ throw task::ErrorCodeIOException(
+ "SfxBaseModel::switchToStorage: " + nError.toString(),
+ Reference< XInterface >(), sal_uInt32(nError.GetCode()));
+ }
+ else
+ {
+ // UICfgMgr has a reference to the old storage, update it
+ getUIConfigurationManager2()->setStorage( xStorage );
+ }
+ }
+ m_pData->m_pObjectShell->Get_Impl()->bOwnsStorage = false;
+}
+
+Reference< embed::XStorage > SAL_CALL SfxBaseModel::getDocumentStorage()
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( !m_pData->m_pObjectShell.is() )
+ throw io::IOException(); // TODO
+
+ return m_pData->m_pObjectShell->GetStorage();
+}
+
+void SAL_CALL SfxBaseModel::addStorageChangeListener(
+ const Reference< document::XStorageChangeListener >& xListener )
+{
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+
+ m_pData->m_aStorageChangeListeners.addInterface( xListener );
+}
+
+void SAL_CALL SfxBaseModel::removeStorageChangeListener(
+ const Reference< document::XStorageChangeListener >& xListener )
+{
+ SfxModelGuard aGuard( *this );
+
+ m_pData->m_aStorageChangeListeners.removeInterface( xListener );
+}
+
+void SfxBaseModel::impl_getPrintHelper()
+{
+ if ( m_pData->m_xPrintable.is() )
+ return;
+ m_pData->m_xPrintable = new SfxPrintHelper();
+ Reference < lang::XInitialization > xInit( m_pData->m_xPrintable, UNO_QUERY );
+ xInit->initialize( { Any(Reference < frame::XModel > (this)) } );
+ Reference < view::XPrintJobBroadcaster > xBrd( m_pData->m_xPrintable, UNO_QUERY );
+ xBrd->addPrintJobListener( new SfxPrintHelperListener_Impl( m_pData.get() ) );
+}
+
+
+// css.frame.XModule
+ void SAL_CALL SfxBaseModel::setIdentifier(const OUString& Identifier)
+{
+ SfxModelGuard aGuard( *this );
+ m_pData->m_sModuleIdentifier = Identifier;
+}
+
+
+// css.frame.XModule
+ OUString SAL_CALL SfxBaseModel::getIdentifier()
+{
+ SfxModelGuard aGuard( *this );
+ if (!m_pData->m_sModuleIdentifier.isEmpty())
+ return m_pData->m_sModuleIdentifier;
+ if (m_pData->m_pObjectShell.is())
+ return m_pData->m_pObjectShell->GetFactory().GetDocumentServiceName();
+ return OUString();
+}
+
+
+Reference< frame::XTitle > SfxBaseModel::impl_getTitleHelper ()
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( ! m_pData->m_xTitleHelper.is ())
+ {
+ Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext();
+ Reference< frame::XUntitledNumbers > xDesktop( frame::Desktop::create(xContext), UNO_QUERY_THROW);
+
+ m_pData->m_xTitleHelper = new ::framework::TitleHelper(xContext, Reference< frame::XModel >(this), xDesktop);
+ }
+
+ return m_pData->m_xTitleHelper;
+}
+
+
+Reference< frame::XUntitledNumbers > SfxBaseModel::impl_getUntitledHelper ()
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( ! m_pData->m_xNumberedControllers.is ())
+ {
+ rtl::Reference<::comphelper::NumberedCollection> pHelper = new ::comphelper::NumberedCollection();
+ m_pData->m_xNumberedControllers = pHelper;
+ pHelper->setOwner (Reference< frame::XModel >(this));
+ pHelper->setUntitledPrefix (" : ");
+ }
+
+ return m_pData->m_xNumberedControllers;
+}
+
+
+// css.frame.XTitle
+OUString SAL_CALL SfxBaseModel::getTitle()
+{
+ // SYNCHRONIZED ->
+ SfxModelGuard aGuard( *this );
+
+ OUString aResult = impl_getTitleHelper()->getTitle ();
+ if ( !m_pData->m_bExternalTitle && m_pData->m_pObjectShell )
+ {
+ SfxMedium* pMedium = m_pData->m_pObjectShell->GetMedium();
+ if ( pMedium )
+ {
+ try {
+ ::ucbhelper::Content aContent( pMedium->GetName(),
+ utl::UCBContentHelper::getDefaultCommandEnvironment(),
+ comphelper::getProcessComponentContext() );
+ const Reference < beans::XPropertySetInfo > xProps
+ = aContent.getProperties();
+ if ( xProps.is() )
+ {
+ static constexpr OUString aServerTitle( u"TitleOnServer"_ustr );
+ if ( xProps->hasPropertyByName( aServerTitle ) )
+ {
+ Any aAny = aContent.getPropertyValue( aServerTitle );
+ aAny >>= aResult;
+ }
+ }
+ }
+ catch (const ucb::ContentCreationException &)
+ {
+ }
+ catch (const ucb::CommandAbortedException &)
+ {
+ }
+ const SfxBoolItem* pRepairedDocItem = pMedium->GetItemSet().GetItem(SID_REPAIRPACKAGE, false);
+ if ( pRepairedDocItem && pRepairedDocItem->GetValue() )
+ aResult += SfxResId(STR_REPAIREDDOCUMENT);
+ }
+
+ if ( m_pData->m_pObjectShell->IsReadOnlyUI() || (pMedium && pMedium->IsReadOnly()) )
+ aResult += SfxResId(STR_READONLY);
+ else if ( m_pData->m_pObjectShell->IsDocShared() )
+ aResult += SfxResId(STR_SHARED);
+
+ if ( m_pData->m_pObjectShell->GetDocumentSignatureState() == SignatureState::OK )
+ aResult += SfxResId(RID_XMLSEC_DOCUMENTSIGNED);
+ }
+
+ return aResult;
+}
+
+
+// css.frame.XTitle
+void SAL_CALL SfxBaseModel::setTitle( const OUString& sTitle )
+{
+ // SYNCHRONIZED ->
+ SfxModelGuard aGuard( *this );
+
+ impl_getTitleHelper()->setTitle (sTitle);
+ m_pData->m_bExternalTitle = true;
+}
+
+
+// css.frame.XTitleChangeBroadcaster
+void SAL_CALL SfxBaseModel::addTitleChangeListener( const Reference< frame::XTitleChangeListener >& xListener )
+{
+ // SYNCHRONIZED ->
+ SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING );
+
+ Reference< frame::XTitleChangeBroadcaster > xBroadcaster(impl_getTitleHelper(), UNO_QUERY);
+ if (xBroadcaster.is ())
+ xBroadcaster->addTitleChangeListener (xListener);
+}
+
+
+// css.frame.XTitleChangeBroadcaster
+void SAL_CALL SfxBaseModel::removeTitleChangeListener( const Reference< frame::XTitleChangeListener >& xListener )
+{
+ // SYNCHRONIZED ->
+ SfxModelGuard aGuard( *this );
+
+ Reference< frame::XTitleChangeBroadcaster > xBroadcaster(impl_getTitleHelper(), UNO_QUERY);
+ if (xBroadcaster.is ())
+ xBroadcaster->removeTitleChangeListener (xListener);
+}
+
+
+// css.frame.XUntitledNumbers
+::sal_Int32 SAL_CALL SfxBaseModel::leaseNumber( const Reference< XInterface >& xComponent )
+{
+ SfxModelGuard aGuard( *this );
+
+ return impl_getUntitledHelper ()->leaseNumber (xComponent);
+}
+
+
+// css.frame.XUntitledNumbers
+void SAL_CALL SfxBaseModel::releaseNumber( ::sal_Int32 nNumber )
+{
+ SfxModelGuard aGuard( *this );
+ impl_getUntitledHelper ()->releaseNumber (nNumber);
+}
+
+
+// css.frame.XUntitledNumbers
+void SAL_CALL SfxBaseModel::releaseNumberForComponent( const Reference< XInterface >& xComponent )
+{
+ SfxModelGuard aGuard( *this );
+ impl_getUntitledHelper ()->releaseNumberForComponent (xComponent);
+}
+
+
+// css.frame.XUntitledNumbers
+OUString SAL_CALL SfxBaseModel::getUntitledPrefix()
+{
+ SfxModelGuard aGuard( *this );
+ return impl_getUntitledHelper ()->getUntitledPrefix ();
+}
+
+
+// frame::XModel2
+Reference< container::XEnumeration > SAL_CALL SfxBaseModel::getControllers()
+{
+ SfxModelGuard aGuard( *this );
+
+ sal_Int32 c = m_pData->m_seqControllers.size();
+ Sequence< Any > lEnum(c);
+ std::transform(m_pData->m_seqControllers.begin(), m_pData->m_seqControllers.end(),
+ lEnum.getArray(), [](const auto& x) { return css::uno::Any(x); });
+
+ return new ::comphelper::OAnyEnumeration(lEnum);
+}
+
+
+// frame::XModel2
+Sequence< OUString > SAL_CALL SfxBaseModel::getAvailableViewControllerNames()
+{
+ SfxModelGuard aGuard( *this );
+
+ const SfxObjectFactory& rDocumentFactory = GetObjectShell()->GetFactory();
+ const sal_Int16 nViewFactoryCount = rDocumentFactory.GetViewFactoryCount();
+
+ Sequence< OUString > aViewNames( nViewFactoryCount );
+ auto aViewNamesRange = asNonConstRange(aViewNames);
+ for ( sal_Int16 nViewNo = 0; nViewNo < nViewFactoryCount; ++nViewNo )
+ aViewNamesRange[nViewNo] = rDocumentFactory.GetViewFactory( nViewNo ).GetAPIViewName();
+ return aViewNames;
+}
+
+
+// frame::XModel2
+Reference< frame::XController2 > SAL_CALL SfxBaseModel::createDefaultViewController( const Reference< frame::XFrame >& i_rFrame )
+{
+ SfxModelGuard aGuard( *this );
+
+ const SfxObjectFactory& rDocumentFactory = GetObjectShell()->GetFactory();
+ const OUString sDefaultViewName = rDocumentFactory.GetViewFactory().GetAPIViewName();
+
+ aGuard.clear();
+
+ return createViewController( sDefaultViewName, Sequence< PropertyValue >(), i_rFrame );
+}
+
+
+namespace sfx::intern {
+
+ /** a class which, in its dtor, cleans up various objects (well, at the moment only the frame) collected during
+ the creation of a document view, unless the creation was successful.
+ */
+ class ViewCreationGuard
+ {
+ public:
+ ViewCreationGuard()
+ :m_bSuccess( false )
+ {
+ }
+
+ ~ViewCreationGuard()
+ {
+ if ( !m_bSuccess && m_aWeakFrame && !m_aWeakFrame->GetCurrentDocument() )
+ {
+ m_aWeakFrame->SetFrameInterface_Impl( nullptr );
+ m_aWeakFrame->DoClose();
+ }
+ }
+
+ void takeFrameOwnership( SfxFrame* i_pFrame )
+ {
+ OSL_PRECOND( !m_aWeakFrame, "ViewCreationGuard::takeFrameOwnership: already have a frame!" );
+ OSL_PRECOND( i_pFrame != nullptr, "ViewCreationGuard::takeFrameOwnership: invalid frame!" );
+ m_aWeakFrame = i_pFrame;
+ }
+
+ void releaseAll()
+ {
+ m_bSuccess = true;
+ }
+
+ private:
+ bool m_bSuccess;
+ SfxFrameWeakRef m_aWeakFrame;
+ };
+}
+
+
+SfxViewFrame* SfxBaseModel::FindOrCreateViewFrame_Impl( const Reference< XFrame >& i_rFrame, ::sfx::intern::ViewCreationGuard& i_rGuard ) const
+{
+ SfxViewFrame* pViewFrame = nullptr;
+ for ( pViewFrame = SfxViewFrame::GetFirst( GetObjectShell(), false );
+ pViewFrame;
+ pViewFrame= SfxViewFrame::GetNext( *pViewFrame, GetObjectShell(), false )
+ )
+ {
+ if ( pViewFrame->GetFrame().GetFrameInterface() == i_rFrame )
+ break;
+ }
+ if ( !pViewFrame )
+ {
+ #if OSL_DEBUG_LEVEL > 0
+ for ( SfxFrame* pCheckFrame = SfxFrame::GetFirst();
+ pCheckFrame;
+ pCheckFrame = SfxFrame::GetNext( *pCheckFrame )
+ )
+ {
+ if ( pCheckFrame->GetFrameInterface() == i_rFrame )
+ {
+ if ( ( pCheckFrame->GetCurrentViewFrame() != nullptr )
+ || ( pCheckFrame->GetCurrentDocument() != nullptr )
+ )
+ // Note that it is perfectly legitimate that during loading into an XFrame which already contains
+ // a document, there exist two SfxFrame instances bound to this XFrame - the old one, which will be
+ // destroyed later, and the new one, which we're going to create
+ continue;
+
+ OSL_FAIL( "SfxBaseModel::FindOrCreateViewFrame_Impl: there already is an SfxFrame for the given XFrame, but no view in it!" );
+ // nowadays, we're the only instance allowed to create an SfxFrame for an XFrame, so this case here should not happen
+ break;
+ }
+ }
+ #endif
+
+ SfxFrame* pTargetFrame = SfxFrame::Create( i_rFrame );
+ ENSURE_OR_THROW( pTargetFrame, "could not create an SfxFrame" );
+ i_rGuard.takeFrameOwnership( pTargetFrame );
+
+ // prepare it
+ pTargetFrame->PrepareForDoc_Impl( *GetObjectShell() );
+
+ // create view frame
+ pViewFrame = new SfxViewFrame( *pTargetFrame, GetObjectShell() );
+ }
+ return pViewFrame;
+}
+
+
+// frame::XModel2
+Reference< frame::XController2 > SAL_CALL SfxBaseModel::createViewController(
+ const OUString& i_rViewName, const Sequence< PropertyValue >& i_rArguments, const Reference< XFrame >& i_rFrame )
+{
+ SfxModelGuard aGuard( *this );
+
+ if ( !i_rFrame.is() )
+ throw lang::IllegalArgumentException( OUString(), *this, 3 );
+
+ // find the proper SFX view factory
+ SfxViewFactory* pViewFactory = GetObjectShell()->GetFactory().GetViewFactoryByViewName( i_rViewName );
+ if ( !pViewFactory )
+ throw IllegalArgumentException( OUString(), *this, 1 );
+
+ // determine previous shell (used in some special cases)
+ Reference< XController > xPreviousController( i_rFrame->getController() );
+ const Reference< XModel > xMe( this );
+ if ( ( xPreviousController.is() )
+ && ( xMe != xPreviousController->getModel() )
+ )
+ {
+ xPreviousController.clear();
+ }
+ SfxViewShell* pOldViewShell = SfxViewShell::Get( xPreviousController );
+ OSL_ENSURE( !xPreviousController.is() || ( pOldViewShell != nullptr ),
+ "SfxBaseModel::createViewController: invalid old controller!" );
+
+ // a guard which will clean up in case of failure
+ ::sfx::intern::ViewCreationGuard aViewCreationGuard;
+
+ // determine the ViewFrame belonging to the given XFrame
+ SfxViewFrame* pViewFrame = FindOrCreateViewFrame_Impl( i_rFrame, aViewCreationGuard );
+ assert(pViewFrame && "SfxBaseModel::createViewController: no frame");
+
+ // delegate to SFX' view factory
+ pViewFrame->GetBindings().ENTERREGISTRATIONS();
+ SfxViewShell* pViewShell = pViewFactory->CreateInstance(*pViewFrame, pOldViewShell);
+ pViewFrame->GetBindings().LEAVEREGISTRATIONS();
+ ENSURE_OR_THROW( pViewShell, "invalid view shell provided by factory" );
+
+ // by setting the ViewShell it is prevented that disposing the Controller will destroy this ViewFrame also
+ pViewFrame->GetDispatcher()->SetDisableFlags( SfxDisableFlags::NONE );
+ pViewFrame->SetViewShell_Impl( pViewShell );
+
+ // remember ViewID
+ pViewFrame->SetCurViewId_Impl( pViewFactory->GetOrdinal() );
+
+ // ensure a default controller, if the view shell did not provide an own implementation
+ if ( !pViewShell->GetController().is() )
+ pViewShell->SetController( new SfxBaseController( pViewShell ) );
+
+ // pass the creation arguments to the controller
+ SfxBaseController* pBaseController = pViewShell->GetBaseController_Impl();
+ ENSURE_OR_THROW( pBaseController, "invalid controller implementation!" );
+ pBaseController->SetCreationArguments_Impl( i_rArguments );
+
+ // some initial view settings, coming from our most recent attachResource call
+ ::comphelper::NamedValueCollection aDocumentLoadArgs( getArgs2( { "ViewOnly", "PluginMode" } ) );
+ if ( aDocumentLoadArgs.getOrDefault( "ViewOnly", false ) )
+ pViewFrame->GetFrame().SetMenuBarOn_Impl( false );
+
+ const sal_Int16 nPluginMode = aDocumentLoadArgs.getOrDefault( "PluginMode", sal_Int16( 0 ) );
+ if ( nPluginMode == 1 )
+ {
+ pViewFrame->ForceOuterResize_Impl();
+ pViewFrame->GetBindings().HidePopups();
+
+ SfxFrame& rFrame = pViewFrame->GetFrame();
+ // MBA: layoutmanager of inplace frame starts locked and invisible
+ rFrame.GetWorkWindow_Impl()->MakeVisible_Impl( false );
+ rFrame.GetWorkWindow_Impl()->Lock_Impl( true );
+
+ rFrame.GetWindow().SetBorderStyle( WindowBorderStyle::NOBORDER );
+ pViewFrame->GetWindow().SetBorderStyle( WindowBorderStyle::NOBORDER );
+ }
+
+ // tell the guard we were successful
+ aViewCreationGuard.releaseAll();
+
+ // outta here
+ return pBaseController;
+}
+
+
+// RDF DocumentMetadataAccess
+
+// rdf::XRepositorySupplier:
+Reference< rdf::XRepository > SAL_CALL
+SfxBaseModel::getRDFRepository()
+{
+ SfxModelGuard aGuard( *this );
+
+ const Reference<rdf::XDocumentMetadataAccess> xDMA(m_pData->GetDMA());
+ if (!xDMA.is()) {
+ throw RuntimeException( "model has no document metadata", *this );
+ }
+
+ return xDMA->getRDFRepository();
+}
+
+// rdf::XNode:
+OUString SAL_CALL
+SfxBaseModel::getStringValue()
+{
+ SfxModelGuard aGuard( *this );
+
+ const Reference<rdf::XDocumentMetadataAccess> xDMA(m_pData->GetDMA());
+ if (!xDMA.is()) {
+ throw RuntimeException( "model has no document metadata", *this );
+ }
+
+ return xDMA->getStringValue();
+}
+
+// rdf::XURI:
+OUString SAL_CALL
+SfxBaseModel::getNamespace()
+{
+ SfxModelGuard aGuard( *this );
+
+ const Reference<rdf::XDocumentMetadataAccess> xDMA(m_pData->GetDMA());
+ if (!xDMA.is()) {
+ throw RuntimeException( "model has no document metadata", *this );
+ }
+
+ return xDMA->getNamespace();
+}
+
+OUString SAL_CALL
+SfxBaseModel::getLocalName()
+{
+ SfxModelGuard aGuard( *this );
+
+ const Reference<rdf::XDocumentMetadataAccess> xDMA(m_pData->GetDMA());
+ if (!xDMA.is()) {
+ throw RuntimeException( "model has no document metadata", *this );
+ }
+
+ return xDMA->getLocalName();
+}
+
+// rdf::XDocumentMetadataAccess:
+Reference< rdf::XMetadatable > SAL_CALL
+SfxBaseModel::getElementByMetadataReference(
+ const beans::StringPair & i_rReference)
+{
+ SfxModelGuard aGuard( *this );
+
+ const Reference<rdf::XDocumentMetadataAccess> xDMA(m_pData->GetDMA());
+ if (!xDMA.is()) {
+ throw RuntimeException( "model has no document metadata", *this );
+ }
+
+ return xDMA->getElementByMetadataReference(i_rReference);
+}
+
+Reference< rdf::XMetadatable > SAL_CALL
+SfxBaseModel::getElementByURI(const Reference< rdf::XURI > & i_xURI)
+{
+ SfxModelGuard aGuard( *this );
+
+ const Reference<rdf::XDocumentMetadataAccess> xDMA(m_pData->GetDMA());
+ if (!xDMA.is()) {
+ throw RuntimeException( "model has no document metadata", *this );
+ }
+
+ return xDMA->getElementByURI(i_xURI);
+}
+
+Sequence< Reference< rdf::XURI > > SAL_CALL
+SfxBaseModel::getMetadataGraphsWithType(
+ const Reference<rdf::XURI> & i_xType)
+{
+ SfxModelGuard aGuard( *this );
+
+ const Reference<rdf::XDocumentMetadataAccess> xDMA(m_pData->GetDMA());
+ if (!xDMA.is()) {
+ throw RuntimeException( "model has no document metadata", *this );
+ }
+
+ return xDMA->getMetadataGraphsWithType(i_xType);
+}
+
+Reference<rdf::XURI> SAL_CALL
+SfxBaseModel::addMetadataFile(const OUString & i_rFileName,
+ const Sequence < Reference< rdf::XURI > > & i_rTypes)
+{
+ SfxModelGuard aGuard( *this );
+
+ const Reference<rdf::XDocumentMetadataAccess> xDMA(m_pData->GetDMA());
+ if (!xDMA.is()) {
+ throw RuntimeException( "model has no document metadata", *this );
+ }
+
+ return xDMA->addMetadataFile(i_rFileName, i_rTypes);
+}
+
+Reference<rdf::XURI> SAL_CALL
+SfxBaseModel::importMetadataFile(::sal_Int16 i_Format,
+ const Reference< io::XInputStream > & i_xInStream,
+ const OUString & i_rFileName,
+ const Reference< rdf::XURI > & i_xBaseURI,
+ const Sequence < Reference< rdf::XURI > > & i_rTypes)
+{
+ SfxModelGuard aGuard( *this );
+
+ const Reference<rdf::XDocumentMetadataAccess> xDMA(m_pData->GetDMA());
+ if (!xDMA.is()) {
+ throw RuntimeException( "model has no document metadata", *this );
+ }
+
+ return xDMA->importMetadataFile(i_Format,
+ i_xInStream, i_rFileName, i_xBaseURI, i_rTypes);
+}
+
+void SAL_CALL
+SfxBaseModel::removeMetadataFile(
+ const Reference< rdf::XURI > & i_xGraphName)
+{
+ SfxModelGuard aGuard( *this );
+
+ const Reference<rdf::XDocumentMetadataAccess> xDMA(m_pData->GetDMA());
+ if (!xDMA.is()) {
+ throw RuntimeException( "model has no document metadata", *this );
+ }
+
+ return xDMA->removeMetadataFile(i_xGraphName);
+}
+
+void SAL_CALL
+SfxBaseModel::addContentOrStylesFile(const OUString & i_rFileName)
+{
+ SfxModelGuard aGuard( *this );
+
+ const Reference<rdf::XDocumentMetadataAccess> xDMA(m_pData->GetDMA());
+ if (!xDMA.is()) {
+ throw RuntimeException( "model has no document metadata", *this );
+ }
+
+ return xDMA->addContentOrStylesFile(i_rFileName);
+}
+
+void SAL_CALL
+SfxBaseModel::removeContentOrStylesFile(const OUString & i_rFileName)
+{
+ SfxModelGuard aGuard( *this );
+
+ const Reference<rdf::XDocumentMetadataAccess> xDMA(m_pData->GetDMA());
+ if (!xDMA.is()) {
+ throw RuntimeException( "model has no document metadata", *this );
+ }
+
+ return xDMA->removeContentOrStylesFile(i_rFileName);
+}
+
+void SAL_CALL
+SfxBaseModel::loadMetadataFromStorage(
+ Reference< embed::XStorage > const & i_xStorage,
+ Reference<rdf::XURI> const & i_xBaseURI,
+ Reference<task::XInteractionHandler> const & i_xHandler)
+{
+ SfxModelGuard aGuard( *this );
+
+ const Reference<rdf::XDocumentMetadataAccess> xDMA(
+ m_pData->CreateDMAUninitialized());
+ if (!xDMA.is()) {
+ throw RuntimeException( "model has no document metadata", *this );
+ }
+
+ try {
+ xDMA->loadMetadataFromStorage(i_xStorage, i_xBaseURI, i_xHandler);
+ } catch (lang::IllegalArgumentException &) {
+ throw; // not initialized
+ } catch (Exception &) {
+ // UGLY: if it's a RuntimeException, we can't be sure DMA is initialized
+ m_pData->m_xDocumentMetadata = xDMA;
+ throw;
+ }
+ m_pData->m_xDocumentMetadata = xDMA;
+
+}
+
+void SAL_CALL
+SfxBaseModel::storeMetadataToStorage(
+ Reference< embed::XStorage > const & i_xStorage)
+{
+ SfxModelGuard aGuard( *this );
+
+ const Reference<rdf::XDocumentMetadataAccess> xDMA(m_pData->GetDMA());
+ if (!xDMA.is()) {
+ throw RuntimeException( "model has no document metadata", *this );
+ }
+
+ return xDMA->storeMetadataToStorage(i_xStorage);
+}
+
+void SAL_CALL
+SfxBaseModel::loadMetadataFromMedium(
+ const Sequence< beans::PropertyValue > & i_rMedium)
+{
+ SfxModelGuard aGuard( *this );
+
+ const Reference<rdf::XDocumentMetadataAccess> xDMA(
+ m_pData->CreateDMAUninitialized());
+ if (!xDMA.is()) {
+ throw RuntimeException( "model has no document metadata", *this );
+ }
+
+ try {
+ xDMA->loadMetadataFromMedium(i_rMedium);
+ } catch (lang::IllegalArgumentException &) {
+ throw; // not initialized
+ } catch (Exception &) {
+ // UGLY: if it's a RuntimeException, we can't be sure DMA is initialized
+ m_pData->m_xDocumentMetadata = xDMA;
+ throw;
+ }
+ m_pData->m_xDocumentMetadata = xDMA;
+}
+
+void SAL_CALL
+SfxBaseModel::storeMetadataToMedium(
+ const Sequence< beans::PropertyValue > & i_rMedium)
+{
+ SfxModelGuard aGuard( *this );
+
+ const Reference<rdf::XDocumentMetadataAccess> xDMA(m_pData->GetDMA());
+ if (!xDMA.is()) {
+ throw RuntimeException( "model has no document metadata", *this );
+ }
+
+ return xDMA->storeMetadataToMedium(i_rMedium);
+}
+
+
+// = SfxModelSubComponent
+
+
+SfxModelSubComponent::~SfxModelSubComponent()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/sfxmodelfactory.cxx b/sfx2/source/doc/sfxmodelfactory.cxx
new file mode 100644
index 0000000000..a14ff68b1a
--- /dev/null
+++ b/sfx2/source/doc/sfxmodelfactory.cxx
@@ -0,0 +1,110 @@
+/* -*- 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 <sfx2/sfxmodelfactory.hxx>
+
+#include <com/sun/star/beans/NamedValue.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+
+#include <comphelper/namedvaluecollection.hxx>
+
+#include <osl/diagnose.h>
+
+#include <algorithm>
+
+
+namespace sfx2
+{
+
+
+ using ::com::sun::star::uno::Reference;
+ using ::com::sun::star::uno::XInterface;
+ using ::com::sun::star::uno::UNO_QUERY;
+ using ::com::sun::star::uno::Any;
+ using ::com::sun::star::uno::Sequence;
+ using ::com::sun::star::beans::NamedValue;
+ using ::com::sun::star::beans::PropertyValue;
+ using ::com::sun::star::lang::XInitialization;
+
+
+
+ namespace
+ {
+ struct IsSpecialArgument
+ {
+ static bool isSpecialArgumentName( std::u16string_view _rValueName )
+ {
+ return _rValueName == u"EmbeddedObject" || _rValueName == u"EmbeddedScriptSupport" || _rValueName == u"DocumentRecoverySupport";
+ }
+
+ bool operator()( const Any& _rArgument ) const
+ {
+ NamedValue aNamedValue;
+ if ( ( _rArgument >>= aNamedValue ) && isSpecialArgumentName( aNamedValue.Name ) )
+ return true;
+ PropertyValue aPropertyValue;
+ return ( _rArgument >>= aPropertyValue ) && isSpecialArgumentName( aPropertyValue.Name );
+ }
+ };
+ }
+
+
+ css::uno::Reference<css::uno::XInterface> createSfxModelInstance(
+ const css::uno::Sequence<css::uno::Any> & _rArguments,
+ std::function<css::uno::Reference<css::uno::XInterface>(SfxModelFlags)> creationFunc)
+ {
+ ::comphelper::NamedValueCollection aArgs( _rArguments );
+ const bool bEmbeddedObject = aArgs.getOrDefault( "EmbeddedObject", false );
+ const bool bScriptSupport = aArgs.getOrDefault( "EmbeddedScriptSupport", true );
+ const bool bDocRecoverySupport = aArgs.getOrDefault( "DocumentRecoverySupport", true );
+
+ SfxModelFlags nCreationFlags =
+ ( bEmbeddedObject ? SfxModelFlags::EMBEDDED_OBJECT : SfxModelFlags::NONE )
+ | ( bScriptSupport ? SfxModelFlags::NONE : SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS )
+ | ( bDocRecoverySupport ? SfxModelFlags::NONE : SfxModelFlags::DISABLE_DOCUMENT_RECOVERY );
+
+ Reference< XInterface > xInstance( creationFunc(nCreationFlags ) );
+
+ // to mimic the behaviour of the default factory's createInstanceWithArguments, we initialize
+ // the object with the given arguments, stripped by the three special ones
+ Sequence< Any > aStrippedArguments( _rArguments.getLength() );
+ Any* pStrippedArgs = aStrippedArguments.getArray();
+ Any* pStrippedArgsEnd = ::std::remove_copy_if(
+ _rArguments.begin(),
+ _rArguments.end(),
+ pStrippedArgs,
+ IsSpecialArgument()
+ );
+ aStrippedArguments.realloc( pStrippedArgsEnd - pStrippedArgs );
+
+ if ( aStrippedArguments.hasElements() )
+ {
+ Reference< XInitialization > xModelInit( xInstance, UNO_QUERY );
+ OSL_ENSURE( xModelInit.is(), "SfxModelFactory::createInstanceWithArguments: no XInitialization!" );
+ if ( xModelInit.is() )
+ xModelInit->initialize( aStrippedArguments );
+ }
+
+ return xInstance;
+ }
+
+
+} // namespace sfx2
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/signaturestate.cxx b/sfx2/source/doc/signaturestate.cxx
new file mode 100644
index 0000000000..d511fa31af
--- /dev/null
+++ b/sfx2/source/doc/signaturestate.cxx
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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/.
+ */
+
+#include <sfx2/signaturestate.hxx>
+
+#include <com/sun/star/security/CertificateValidity.hpp>
+#include <com/sun/star/security/DocumentSignatureInformation.hpp>
+
+using namespace css;
+
+namespace DocumentSignatures
+{
+SignatureState
+getSignatureState(const uno::Sequence<security::DocumentSignatureInformation>& aSigInfo)
+{
+ bool bCertValid = true;
+ SignatureState nResult = SignatureState::NOSIGNATURES;
+ bool bCompleteSignature = true;
+ if (!aSigInfo.hasElements())
+ return nResult;
+
+ nResult = SignatureState::OK;
+ for (const auto& rInfo : aSigInfo)
+ {
+ if (bCertValid)
+ {
+ sal_Int32 nCertStat = rInfo.CertificateStatus;
+ bCertValid = nCertStat == security::CertificateValidity::VALID;
+ }
+
+ if (!rInfo.SignatureIsValid)
+ {
+ nResult = SignatureState::BROKEN;
+ break;
+ }
+ bCompleteSignature &= !rInfo.PartialDocumentSignature;
+ }
+
+ if (nResult == SignatureState::OK && !bCertValid && !bCompleteSignature)
+ nResult = SignatureState::NOTVALIDATED_PARTIAL_OK;
+ else if (nResult == SignatureState::OK && !bCertValid)
+ nResult = SignatureState::NOTVALIDATED;
+ else if (nResult == SignatureState::OK && bCertValid && !bCompleteSignature)
+ nResult = SignatureState::PARTIAL_OK;
+
+ // this code must not check whether the document is modified
+ // it should only check the provided info
+
+ return nResult;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/sfx2/source/doc/syspath.cxx b/sfx2/source/doc/syspath.cxx
new file mode 100644
index 0000000000..fb949def0f
--- /dev/null
+++ b/sfx2/source/doc/syspath.cxx
@@ -0,0 +1,37 @@
+/* -*- 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 "syspath.hxx"
+#include "syspathw32.hxx"
+
+namespace SystemPath
+{
+bool GetUserTemplateLocation(sal_Unicode* pFolder, int nSize)
+{
+#ifdef _WIN32
+ return ::GetUserTemplateLocation(pFolder, nSize);
+#else
+ (void)pFolder;
+ (void)nSize;
+ return false;
+#endif
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/syspath.hxx b/sfx2/source/doc/syspath.hxx
new file mode 100644
index 0000000000..9c135993cb
--- /dev/null
+++ b/sfx2/source/doc/syspath.hxx
@@ -0,0 +1,32 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SFX2_SOURCE_DOC_SYSPATH_HXX
+#define INCLUDED_SFX2_SOURCE_DOC_SYSPATH_HXX
+
+#include <sal/types.h>
+
+namespace SystemPath
+{
+bool GetUserTemplateLocation(sal_Unicode*, int nSize);
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/syspathw32.cxx b/sfx2/source/doc/syspathw32.cxx
new file mode 100644
index 0000000000..f60f459829
--- /dev/null
+++ b/sfx2/source/doc/syspathw32.cxx
@@ -0,0 +1,69 @@
+/* -*- 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 <sal/config.h>
+#include <sal/types.h>
+#include <o3tl/char16_t2wchar_t.hxx>
+
+#ifdef _WIN32
+
+#undef WB_LEFT
+#undef WB_RIGHT
+
+#include <shlobj.h>
+
+#include "syspathw32.hxx"
+
+static bool SHGetSpecialFolderW32( int nFolderID, WCHAR* pszFolder, int nSize )
+{
+ LPITEMIDLIST pidl;
+ HRESULT hHdl = SHGetSpecialFolderLocation( nullptr, nFolderID, &pidl );
+
+ if( hHdl == NOERROR )
+ {
+ WCHAR *lpFolder = static_cast< WCHAR* >( HeapAlloc( GetProcessHeap(), 0, 16000 ));
+
+ SHGetPathFromIDListW( pidl, lpFolder );
+ wcsncpy( pszFolder, lpFolder, nSize );
+
+ HeapFree( GetProcessHeap(), 0, lpFolder );
+ IMalloc *pMalloc;
+ if( NOERROR == SHGetMalloc(&pMalloc) )
+ {
+ pMalloc->Free( pidl );
+ pMalloc->Release();
+ }
+ }
+ return true;
+}
+
+#endif
+
+bool GetUserTemplateLocation(sal_Unicode* pFolder, int nSize)
+{
+#ifdef _WIN32
+ return SHGetSpecialFolderW32( CSIDL_TEMPLATES, o3tl::toW(pFolder), nSize );
+#else
+ (void)pFolder;
+ (void)nSize;
+ return false;
+#endif
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/syspathw32.hxx b/sfx2/source/doc/syspathw32.hxx
new file mode 100644
index 0000000000..7a06950c5f
--- /dev/null
+++ b/sfx2/source/doc/syspathw32.hxx
@@ -0,0 +1,33 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SFX2_SOURCE_DOC_SYSPATHW32_HXX
+#define INCLUDED_SFX2_SOURCE_DOC_SYSPATHW32_HXX
+
+#include <sal/config.h>
+
+#include <sal/types.h>
+
+#if defined _WIN32
+bool GetUserTemplateLocation(sal_Unicode*, int nSize);
+#endif
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/templatedlg.cxx b/sfx2/source/doc/templatedlg.cxx
new file mode 100644
index 0000000000..65873895d7
--- /dev/null
+++ b/sfx2/source/doc/templatedlg.cxx
@@ -0,0 +1,1394 @@
+/* -*- 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/.
+ */
+
+#include <sfx2/templatedlg.hxx>
+
+#include <sfx2/inputdlg.hxx>
+#include <sfx2/module.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/string.hxx>
+#include <comphelper/storagehelper.hxx>
+#include <sfx2/app.hxx>
+#include <sfx2/docfac.hxx>
+#include <sfx2/docfilt.hxx>
+#include <sfx2/fcontnr.hxx>
+#include <sfx2/filedlghelper.hxx>
+#include <sfx2/objsh.hxx>
+#include <sfx2/sfxresid.hxx>
+#include <sfx2/templatedlglocalview.hxx>
+#include <templatecontaineritem.hxx>
+#include <templateviewitem.hxx>
+#include <sfx2/thumbnailviewitem.hxx>
+#include <sot/storage.hxx>
+#include <tools/urlobj.hxx>
+#include <unotools/moduleoptions.hxx>
+#include <unotools/pathoptions.hxx>
+#include <unotools/viewoptions.hxx>
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+
+#include <com/sun/star/beans/NamedValue.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/document/MacroExecMode.hpp>
+#include <com/sun/star/document/UpdateDocMode.hpp>
+#include <com/sun/star/embed/XStorage.hpp>
+#include <com/sun/star/embed/ElementModes.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp>
+#include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
+#include <com/sun/star/ui/dialogs/XFolderPicker2.hpp>
+#include <com/sun/star/task/InteractionHandler.hpp>
+#include <comphelper/dispatchcommand.hxx>
+
+#include <sfx2/strings.hrc>
+#include <bitmaps.hlst>
+
+constexpr OUString TM_SETTING_MANAGER = u"TemplateManager"_ustr;
+constexpr OUString TM_SETTING_LASTFOLDER = u"LastFolder"_ustr;
+constexpr OUString TM_SETTING_LASTAPPLICATION = u"LastApplication"_ustr;
+constexpr OUString TM_SETTING_VIEWMODE = u"ViewMode"_ustr;
+
+constexpr OUString MNI_ACTION_NEW_FOLDER = u"new"_ustr;
+constexpr OUString MNI_ACTION_RENAME_FOLDER = u"rename"_ustr;
+constexpr OUString MNI_ACTION_DELETE_FOLDER = u"delete"_ustr;
+constexpr OUString MNI_ACTION_DEFAULT = u"default"_ustr;
+constexpr OUString MNI_ACTION_DEFAULT_WRITER = u"default_writer"_ustr;
+constexpr OUString MNI_ACTION_DEFAULT_CALC = u"default_calc"_ustr;
+constexpr OUString MNI_ACTION_DEFAULT_IMPRESS = u"default_impress"_ustr;
+constexpr OUString MNI_ACTION_DEFAULT_DRAW = u"default_draw"_ustr;
+constexpr OUString MNI_ACTION_IMPORT = u"import_template"_ustr;
+constexpr OUString MNI_ACTION_EXTENSIONS = u"extensions"_ustr;
+#define MNI_ALL_APPLICATIONS 0
+#define MNI_WRITER 1
+#define MNI_CALC 2
+#define MNI_IMPRESS 3
+#define MNI_DRAW 4
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::embed;
+using namespace ::com::sun::star::frame;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ui::dialogs;
+using namespace ::com::sun::star::document;
+
+static bool lcl_getServiceName (const OUString &rFileURL, OUString &rName );
+
+static std::vector<OUString> lcl_getAllFactoryURLs ();
+
+namespace {
+
+class SearchView_Keyword
+{
+public:
+
+ SearchView_Keyword (const OUString &rKeyword, FILTER_APPLICATION App)
+ : maKeyword(rKeyword.toAsciiLowerCase()), meApp(App)
+ {}
+
+ bool operator() (const TemplateItemProperties &rItem)
+ {
+ bool bRet = true;
+
+ INetURLObject aUrl(rItem.aPath);
+ OUString aExt = aUrl.getExtension();
+
+ if (meApp == FILTER_APPLICATION::WRITER)
+ {
+ bRet = aExt == "ott" || aExt == "stw" || aExt == "oth" || aExt == "dot" || aExt == "dotx";
+ }
+ else if (meApp == FILTER_APPLICATION::CALC)
+ {
+ bRet = aExt == "ots" || aExt == "stc" || aExt == "xlt" || aExt == "xltm" || aExt == "xltx";
+ }
+ else if (meApp == FILTER_APPLICATION::IMPRESS)
+ {
+ bRet = aExt == "otp" || aExt == "sti" || aExt == "pot" || aExt == "potm" || aExt == "potx";
+ }
+ else if (meApp == FILTER_APPLICATION::DRAW)
+ {
+ bRet = aExt == "otg" || aExt == "std";
+ }
+
+ return bRet && MatchSubstring(rItem.aName);
+ }
+
+ bool MatchSubstring( OUString const & sItemName )
+ {
+ if(maKeyword.isEmpty())
+ return false;
+ return sItemName.toAsciiLowerCase().indexOf(maKeyword) >= 0;
+ }
+
+private:
+
+ OUString maKeyword;
+ FILTER_APPLICATION meApp;
+};
+
+}
+
+/***
+ *
+ * Order items in ascending order (useful for the selection sets and move/copy operations since the associated ids
+ * change when processed by the SfxDocumentTemplates class so we want to process to ones with higher id first)
+ *
+ ***/
+
+static bool cmpSelectionItems (const ThumbnailViewItem *pItem1, const ThumbnailViewItem *pItem2)
+{
+ return pItem1->mnId > pItem2->mnId;
+}
+
+SfxTemplateManagerDlg::SfxTemplateManagerDlg(weld::Window *pParent)
+ : GenericDialogController(pParent, "sfx/ui/templatedlg.ui", "TemplateDialog")
+ , maSelTemplates(cmpSelectionItems)
+ , mxDesktop(Desktop::create(comphelper::getProcessComponentContext()))
+ , m_aUpdateDataTimer( "SfxTemplateManagerDlg UpdateDataTimer" )
+ , mxSearchFilter(m_xBuilder->weld_entry("search_filter"))
+ , mxCBApp(m_xBuilder->weld_combo_box("filter_application"))
+ , mxCBFolder(m_xBuilder->weld_combo_box("filter_folder"))
+ , mxOKButton(m_xBuilder->weld_button("ok"))
+ , mxCBXHideDlg(m_xBuilder->weld_check_button("hidedialogcb"))
+ , mxActionBar(m_xBuilder->weld_menu_button("action_menu"))
+ , mxLocalView(new TemplateDlgLocalView(m_xBuilder->weld_scrolled_window("scrolllocal", true),
+ m_xBuilder->weld_menu("contextmenu"),
+ m_xBuilder->weld_tree_view("tree_list")))
+ , mxLocalViewWeld(new weld::CustomWeld(*m_xBuilder, "template_view", *mxLocalView))
+ , mxListViewButton(m_xBuilder->weld_toggle_button("list_view_btn"))
+ , mxThumbnailViewButton(m_xBuilder->weld_toggle_button("thumbnail_view_btn"))
+ , mViewMode(TemplateViewMode::eThumbnailView)
+{
+ // Create popup menus
+ mxActionBar->append_item(MNI_ACTION_NEW_FOLDER, SfxResId(STR_CATEGORY_NEW), BMP_ACTION_NEW_CATEGORY);
+ mxActionBar->append_item(MNI_ACTION_RENAME_FOLDER, SfxResId(STR_CATEGORY_RENAME), BMP_ACTION_RENAME);
+ mxActionBar->append_item(MNI_ACTION_DELETE_FOLDER, SfxResId(STR_CATEGORY_DELETE), BMP_ACTION_DELETE_CATEGORY);
+ mxActionBar->append_separator("separator");
+ mxActionBar->append_item(MNI_ACTION_DEFAULT, SfxResId(STR_ACTION_RESET_ALL_DEFAULT_TEMPLATES));
+ mxActionBar->append_item(MNI_ACTION_DEFAULT_WRITER, SfxResId(STR_ACTION_RESET_WRITER_TEMPLATE), BMP_ACTION_DEFAULT_WRITER);
+ mxActionBar->append_item(MNI_ACTION_DEFAULT_CALC, SfxResId(STR_ACTION_RESET_CALC_TEMPLATE), BMP_ACTION_DEFAULT_CALC);
+ mxActionBar->append_item(MNI_ACTION_DEFAULT_IMPRESS, SfxResId(STR_ACTION_RESET_IMPRESS_TEMPLATE), BMP_ACTION_DEFAULT_IMPRESS);
+ mxActionBar->append_item(MNI_ACTION_DEFAULT_DRAW, SfxResId(STR_ACTION_RESET_DRAW_TEMPLATE), BMP_ACTION_DEFAULT_DRAW);
+ mxActionBar->append_separator("separator2");
+ mxActionBar->append_item(MNI_ACTION_IMPORT, SfxResId(STR_ACTION_IMPORT), BMP_ACTION_IMPORT);
+ mxActionBar->append_item(MNI_ACTION_EXTENSIONS, SfxResId(STR_ACTION_EXTENSIONS), BMP_ACTION_EXTENSIONS);
+
+ mxActionBar->connect_selected(LINK(this,SfxTemplateManagerDlg,MenuSelectHdl));
+
+ mxLocalView->setItemMaxTextLength(TEMPLATE_ITEM_MAX_TEXT_LENGTH);
+ mxLocalView->setItemDimensions(TEMPLATE_ITEM_MAX_WIDTH,TEMPLATE_ITEM_THUMBNAIL_MAX_HEIGHT,
+ TEMPLATE_ITEM_MAX_HEIGHT-TEMPLATE_ITEM_THUMBNAIL_MAX_HEIGHT,
+ TEMPLATE_ITEM_PADDING);
+
+ mxLocalView->setItemStateHdl(LINK(this,SfxTemplateManagerDlg,TVItemStateHdl));
+ mxLocalView->setCreateContextMenuHdl(LINK(this,SfxTemplateManagerDlg, CreateContextMenuHdl));
+ mxLocalView->setOpenRegionHdl(LINK(this,SfxTemplateManagerDlg, OpenRegionHdl));
+ mxLocalView->setOpenTemplateHdl(LINK(this,SfxTemplateManagerDlg, OpenTemplateHdl));
+ mxLocalView->setEditTemplateHdl(LINK(this,SfxTemplateManagerDlg, EditTemplateHdl));
+ mxLocalView->setDeleteTemplateHdl(LINK(this,SfxTemplateManagerDlg, DeleteTemplateHdl));
+ mxLocalView->setDefaultTemplateHdl(LINK(this,SfxTemplateManagerDlg, DefaultTemplateHdl));
+ mxLocalView->setMoveTemplateHdl(LINK(this,SfxTemplateManagerDlg, MoveTemplateHdl));
+ mxLocalView->setExportTemplateHdl(LINK(this,SfxTemplateManagerDlg, ExportTemplateHdl));
+
+ mxLocalView->ShowTooltips(true);
+
+ // Set width and height of the templates thumbnail viewer to accommodate 3 rows and 4 columns of items
+ mxLocalViewWeld->set_size_request(TEMPLATE_ITEM_MAX_WIDTH * 5, TEMPLATE_ITEM_MAX_HEIGHT_SUB * 3);
+
+ mxOKButton->connect_clicked(LINK(this, SfxTemplateManagerDlg, OkClickHdl));
+ // FIXME: rather than disabling make dispatchCommand(".uno:AdditionsDialog") work in start center
+ if ( !SfxModule::GetActiveModule() )
+ mxActionBar->set_item_sensitive(MNI_ACTION_EXTENSIONS, false);
+ else
+ mxActionBar->set_item_sensitive(MNI_ACTION_EXTENSIONS, true);
+ mxListViewButton->connect_toggled(LINK(this, SfxTemplateManagerDlg, ListViewHdl));
+ mxThumbnailViewButton->connect_toggled(LINK(this, SfxTemplateManagerDlg, ThumbnailViewHdl));
+
+ mxSearchFilter->connect_changed(LINK(this, SfxTemplateManagerDlg, SearchUpdateHdl));
+ mxSearchFilter->connect_focus_in(LINK( this, SfxTemplateManagerDlg, GetFocusHdl ));
+ mxSearchFilter->connect_focus_out(LINK( this, SfxTemplateManagerDlg, LoseFocusHdl ));
+ mxSearchFilter->connect_key_press(LINK( this, SfxTemplateManagerDlg, KeyInputHdl));
+
+ mxActionBar->show();
+
+ mxLocalView->Populate();
+ mxLocalView->filterItems(ViewFilter_Application(FILTER_APPLICATION::NONE));
+
+ mxCBApp->set_active(0);
+ fillFolderComboBox();
+
+ mxActionBar->set_item_visible(MNI_ACTION_EXTENSIONS, true);
+ mxActionBar->set_item_visible(MNI_ACTION_IMPORT, true);
+ mxActionBar->set_item_visible(MNI_ACTION_NEW_FOLDER, true);
+
+ mxOKButton->set_label(SfxResId(STR_OPEN));
+
+ mxCBApp->connect_changed(LINK(this, SfxTemplateManagerDlg, SelectApplicationHdl));
+ mxCBFolder->connect_changed(LINK(this, SfxTemplateManagerDlg, SelectRegionHdl));
+
+ mxLocalView->Show();
+
+ m_aUpdateDataTimer.SetInvokeHandler(LINK(this, SfxTemplateManagerDlg, ImplUpdateDataHdl));
+ m_aUpdateDataTimer.SetTimeout(EDIT_UPDATEDATA_TIMEOUT);
+
+ mxLocalView->connect_focus_rect(LINK(this, SfxTemplateManagerDlg, FocusRectLocalHdl));
+ bMakeSelItemVisible = false;
+}
+
+SfxTemplateManagerDlg::~SfxTemplateManagerDlg()
+{
+ writeSettings();
+
+ // Ignore view events since we are cleaning the object
+ mxLocalView->setItemStateHdl(Link<const ThumbnailViewItem*,void>());
+ mxLocalView->setOpenRegionHdl(Link<void*,void>());
+ mxLocalView->setOpenTemplateHdl(Link<ThumbnailViewItem*, void>());
+}
+
+short SfxTemplateManagerDlg::run()
+{
+ //use application specific settings if there's no previous setting
+ getApplicationSpecificSettings();
+ readSettings();
+ updateMenuItems();
+
+ return weld::GenericDialogController::run();
+}
+
+IMPL_LINK(SfxTemplateManagerDlg, KeyInputHdl, const KeyEvent&, rKeyEvent, bool)
+{
+ if (mxSearchFilter != nullptr && !mxSearchFilter->get_text().isEmpty())
+ {
+ vcl::KeyCode aKeyCode = rKeyEvent.GetKeyCode();
+ sal_uInt16 nKeyCode = aKeyCode.GetCode();
+
+ if ( nKeyCode == KEY_ESCAPE )
+ {
+ mxSearchFilter->set_text("");
+ SearchUpdateHdl(*mxSearchFilter);
+ return true;
+ }
+ }
+ return false;
+}
+
+void SfxTemplateManagerDlg::setDocumentModel(const uno::Reference<frame::XModel> &rModel)
+{
+ m_xModel = rModel;
+}
+
+void SfxTemplateManagerDlg::setTemplateViewMode(TemplateViewMode eViewMode)
+{
+ if(eViewMode == TemplateViewMode::eThumbnailView && mViewMode != TemplateViewMode::eThumbnailView)
+ {
+ mxThumbnailViewButton->set_state(TRISTATE_TRUE);
+ mxListViewButton->set_state(TRISTATE_FALSE);
+ mxLocalView->ThumbnailView::GrabFocus();
+ mViewMode = eViewMode;
+ mxLocalView->setTemplateViewMode(eViewMode);
+ mxLocalView->Show();
+ }
+ if(eViewMode == TemplateViewMode::eListView && mViewMode != TemplateViewMode::eListView)
+ {
+ mxListViewButton->set_state(TRISTATE_TRUE);
+ mxThumbnailViewButton->set_state(TRISTATE_FALSE);
+ mxLocalView->ListView::grab_focus();
+ mViewMode = eViewMode;
+ mxLocalView->setTemplateViewMode(eViewMode);
+ mxLocalView->Show();
+ }
+}
+
+TemplateViewMode SfxTemplateManagerDlg::getTemplateViewMode() const
+{
+ return mViewMode;
+}
+
+
+FILTER_APPLICATION SfxTemplateManagerDlg::getCurrentApplicationFilter() const
+{
+ const sal_Int16 nCurAppId = mxCBApp->get_active();
+
+ if (nCurAppId == MNI_WRITER)
+ return FILTER_APPLICATION::WRITER;
+ else if (nCurAppId == MNI_IMPRESS)
+ return FILTER_APPLICATION::IMPRESS;
+ else if (nCurAppId == MNI_CALC)
+ return FILTER_APPLICATION::CALC;
+ else if (nCurAppId == MNI_DRAW)
+ return FILTER_APPLICATION::DRAW;
+
+ return FILTER_APPLICATION::NONE;
+}
+
+void SfxTemplateManagerDlg::fillFolderComboBox()
+{
+ std::vector<OUString> aFolderNames = mxLocalView->getFolderNames();
+
+ for (size_t i = 0, n = aFolderNames.size(); i < n; ++i)
+ mxCBFolder->append_text(aFolderNames[i]);
+ mxCBFolder->set_active(0);
+ mxActionBar->set_item_sensitive(MNI_ACTION_RENAME_FOLDER, false);
+ mxActionBar->set_item_sensitive(MNI_ACTION_DELETE_FOLDER, false);
+}
+
+void SfxTemplateManagerDlg::getApplicationSpecificSettings()
+{
+ if ( ! m_xModel.is() )
+ {
+ mxCBApp->set_active(0);
+ mxCBFolder->set_active(0);
+ mxActionBar->set_item_sensitive(MNI_ACTION_RENAME_FOLDER, false);
+ mxActionBar->set_item_sensitive(MNI_ACTION_DELETE_FOLDER, false);
+ mxLocalView->filterItems(ViewFilter_Application(getCurrentApplicationFilter()));
+ mxLocalView->showAllTemplates();
+ return;
+ }
+
+ SvtModuleOptions::EFactory eFactory = SvtModuleOptions::ClassifyFactoryByModel(m_xModel);
+
+ switch(eFactory)
+ {
+ case SvtModuleOptions::EFactory::WRITER:
+ case SvtModuleOptions::EFactory::WRITERWEB:
+ case SvtModuleOptions::EFactory::WRITERGLOBAL:
+ mxCBApp->set_active(MNI_WRITER);
+ break;
+ case SvtModuleOptions::EFactory::CALC:
+ mxCBApp->set_active(MNI_CALC);
+ break;
+ case SvtModuleOptions::EFactory::IMPRESS:
+ mxCBApp->set_active(MNI_IMPRESS);
+ break;
+ case SvtModuleOptions::EFactory::DRAW:
+ mxCBApp->set_active(MNI_DRAW);
+ break;
+ default:
+ mxCBApp->set_active(0);
+ break;
+ }
+
+ mxLocalView->filterItems(ViewFilter_Application(getCurrentApplicationFilter()));
+ mxCBFolder->set_active(0);
+ mxActionBar->set_item_sensitive(MNI_ACTION_RENAME_FOLDER, false);
+ mxActionBar->set_item_sensitive(MNI_ACTION_DELETE_FOLDER, false);
+ mxLocalView->showAllTemplates();
+}
+
+void SfxTemplateManagerDlg::readSettings ()
+{
+ OUString aLastFolder;
+ SvtViewOptions aViewSettings( EViewType::Dialog, TM_SETTING_MANAGER );
+ sal_Int16 nViewMode = -1;
+
+ if ( aViewSettings.Exists() )
+ {
+ sal_uInt16 nTmp = 0;
+ aViewSettings.GetUserItem(TM_SETTING_LASTFOLDER) >>= aLastFolder;
+ aViewSettings.GetUserItem(TM_SETTING_LASTAPPLICATION) >>= nTmp;
+ aViewSettings.GetUserItem(TM_SETTING_VIEWMODE) >>= nViewMode;
+
+ //open last remembered application only when application model is not set
+ if(!m_xModel.is())
+ {
+ switch (nTmp)
+ {
+ case MNI_WRITER:
+ mxCBApp->set_active(MNI_WRITER);
+ break;
+ case MNI_CALC:
+ mxCBApp->set_active(MNI_CALC);
+ break;
+ case MNI_IMPRESS:
+ mxCBApp->set_active(MNI_IMPRESS);
+ break;
+ case MNI_DRAW:
+ mxCBApp->set_active(MNI_DRAW);
+ break;
+ default:
+ mxCBApp->set_active(0);
+ break;
+ }
+ }
+ }
+
+ mxLocalView->filterItems(ViewFilter_Application(getCurrentApplicationFilter()));
+
+ if (aLastFolder.isEmpty())
+ {
+ //show all categories
+ mxCBFolder->set_active(0);
+ mxActionBar->set_item_sensitive(MNI_ACTION_RENAME_FOLDER, false);
+ mxActionBar->set_item_sensitive(MNI_ACTION_DELETE_FOLDER, false);
+ mxLocalView->showAllTemplates();
+ }
+ else
+ {
+ mxCBFolder->set_active_text(aLastFolder);
+ mxLocalView->showRegion(aLastFolder);
+ bool bIsBuiltInRegion = mxLocalView->IsBuiltInRegion(aLastFolder);
+ mxActionBar->set_item_sensitive(MNI_ACTION_RENAME_FOLDER, !bIsBuiltInRegion);
+ mxActionBar->set_item_sensitive(MNI_ACTION_DELETE_FOLDER, !bIsBuiltInRegion);
+ }
+
+ if(nViewMode == static_cast<sal_Int16>(TemplateViewMode::eListView) ||
+ nViewMode == static_cast<sal_Int16>(TemplateViewMode::eThumbnailView))
+ {
+ TemplateViewMode eViewMode = static_cast<TemplateViewMode>(nViewMode);
+ setTemplateViewMode(eViewMode);
+ }
+ else
+ {
+ //Default ViewMode
+ setTemplateViewMode(TemplateViewMode::eThumbnailView);
+ }
+}
+
+void SfxTemplateManagerDlg::writeSettings ()
+{
+ OUString aLastFolder;
+
+ if (mxLocalView->getCurRegionId())
+ aLastFolder = mxLocalView->getRegionName(mxLocalView->getCurRegionId()-1);
+
+ // last folder
+ Sequence< NamedValue > aSettings
+ {
+ { TM_SETTING_LASTFOLDER, css::uno::Any(aLastFolder) },
+ { TM_SETTING_LASTAPPLICATION, css::uno::Any(sal_uInt16(mxCBApp->get_active())) },
+ { TM_SETTING_VIEWMODE, css::uno::Any(static_cast<sal_Int16>(getTemplateViewMode()))}
+ };
+
+ // write
+ SvtViewOptions aViewSettings(EViewType::Dialog, TM_SETTING_MANAGER);
+ aViewSettings.SetUserData(aSettings);
+}
+
+IMPL_LINK_NOARG(SfxTemplateManagerDlg, SelectApplicationHdl, weld::ComboBox&, void)
+{
+ mxLocalView->filterItems(ViewFilter_Application(getCurrentApplicationFilter()));
+ SelectRegionHdl(*mxCBFolder);
+ updateMenuItems();
+}
+
+IMPL_LINK_NOARG(SfxTemplateManagerDlg, SelectRegionHdl, weld::ComboBox&, void)
+{
+ const OUString sSelectedRegion = mxCBFolder->get_active_text();
+
+ if(mxCBFolder->get_active() == 0)
+ {
+ mxActionBar->set_item_sensitive(MNI_ACTION_RENAME_FOLDER, false);
+ mxActionBar->set_item_sensitive(MNI_ACTION_DELETE_FOLDER, false);
+ }
+ else
+ {
+ bool bIsBuiltInRegion = mxLocalView->IsBuiltInRegion(sSelectedRegion);
+ mxActionBar->set_item_sensitive(MNI_ACTION_RENAME_FOLDER, !bIsBuiltInRegion);
+ mxActionBar->set_item_sensitive(MNI_ACTION_DELETE_FOLDER, !bIsBuiltInRegion);
+ }
+ SearchUpdate();
+}
+
+IMPL_LINK(SfxTemplateManagerDlg, TVItemStateHdl, const ThumbnailViewItem*, pItem, void)
+{
+ const TemplateViewItem *pViewItem = dynamic_cast<const TemplateViewItem*>(pItem);
+
+ if (pViewItem)
+ OnTemplateState(pItem);
+}
+
+IMPL_LINK(SfxTemplateManagerDlg, MenuSelectHdl, const OUString&, rIdent, void)
+{
+ if (rIdent == MNI_ACTION_NEW_FOLDER)
+ OnCategoryNew();
+ else if (rIdent == MNI_ACTION_RENAME_FOLDER)
+ OnCategoryRename();
+ else if (rIdent == MNI_ACTION_DELETE_FOLDER)
+ OnCategoryDelete();
+ else if (rIdent == MNI_ACTION_DEFAULT)
+ {
+ DefaultTemplateMenuSelectHdl(MNI_ACTION_DEFAULT_WRITER);
+ DefaultTemplateMenuSelectHdl(MNI_ACTION_DEFAULT_CALC);
+ DefaultTemplateMenuSelectHdl(MNI_ACTION_DEFAULT_IMPRESS);
+ DefaultTemplateMenuSelectHdl(MNI_ACTION_DEFAULT_DRAW);
+ }
+ else if(rIdent == MNI_ACTION_DEFAULT_WRITER || rIdent == MNI_ACTION_DEFAULT_CALC ||
+ rIdent == MNI_ACTION_DEFAULT_IMPRESS || rIdent == MNI_ACTION_DEFAULT_DRAW )
+ DefaultTemplateMenuSelectHdl(rIdent);
+ else if(rIdent == MNI_ACTION_IMPORT)
+ ImportActionHdl();
+ else if(rIdent == MNI_ACTION_EXTENSIONS)
+ ExtensionsActionHdl();
+}
+
+void SfxTemplateManagerDlg::DefaultTemplateMenuSelectHdl(std::u16string_view rIdent)
+{
+ SvtModuleOptions aModOpt;
+ OUString aFactoryURL;
+ if (rIdent == MNI_ACTION_DEFAULT_WRITER)
+ aFactoryURL = aModOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::WRITER);
+ else if (rIdent == MNI_ACTION_DEFAULT_CALC)
+ aFactoryURL = aModOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::CALC);
+ else if (rIdent == MNI_ACTION_DEFAULT_IMPRESS)
+ aFactoryURL = aModOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::IMPRESS);
+ else if (rIdent == MNI_ACTION_DEFAULT_DRAW)
+ aFactoryURL = aModOpt.GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory::DRAW);
+ else
+ return;
+
+ OUString aServiceName = SfxObjectShell::GetServiceNameFromFactory(aFactoryURL);
+ OUString sPrevDefault = SfxObjectFactory::GetStandardTemplate( aServiceName );
+ if(!sPrevDefault.isEmpty())
+ {
+ mxLocalView->RemoveDefaultTemplateIcon(sPrevDefault);
+ }
+
+ SfxObjectFactory::SetStandardTemplate( aServiceName, OUString() );
+ mxLocalView->refreshDefaultColumn();
+ updateMenuItems();
+}
+
+IMPL_LINK_NOARG(SfxTemplateManagerDlg, OkClickHdl, weld::Button&, void)
+{
+ OnTemplateOpen();
+ m_xDialog->response(RET_OK);
+}
+
+IMPL_LINK_NOARG(SfxTemplateManagerDlg, MoveTemplateHdl, void*, void)
+{
+ // modal dialog to select templates category
+ SfxTemplateCategoryDialog aDlg(m_xDialog.get());
+ aDlg.SetCategoryLBEntries(mxLocalView->getFolderNames());
+
+ size_t nItemId = 0;
+
+ if (aDlg.run() != RET_OK)
+ return;
+
+ const OUString& sCategory = aDlg.GetSelectedCategory();
+ bool bIsNewCategory = aDlg.IsNewCategoryCreated();
+ if(bIsNewCategory)
+ {
+ if (!sCategory.isEmpty())
+ {
+ nItemId = mxLocalView->createRegion(sCategory);
+ if(nItemId)
+ mxCBFolder->append_text(sCategory);
+ }
+ }
+ else
+ nItemId = mxLocalView->getRegionId(sCategory);
+
+ if(nItemId)
+ {
+ localMoveTo(nItemId);
+ }
+
+ mxLocalView->reload();
+ SearchUpdate();
+}
+IMPL_LINK_NOARG(SfxTemplateManagerDlg, ExportTemplateHdl, void*, void)
+{
+ OnTemplateExport();
+}
+
+void SfxTemplateManagerDlg::ImportActionHdl()
+{
+ if(mxCBFolder->get_active() == 0)
+ {
+ //Modal Dialog to select Category
+ SfxTemplateCategoryDialog aDlg(m_xDialog.get());
+ aDlg.SetCategoryLBEntries(mxLocalView->getFolderNames());
+
+ if (aDlg.run() == RET_OK)
+ {
+ const OUString& sCategory = aDlg.GetSelectedCategory();
+ bool bIsNewCategory = aDlg.IsNewCategoryCreated();
+ if(bIsNewCategory)
+ {
+ if(mxLocalView->createRegion(sCategory))
+ {
+ mxCBFolder->append_text(sCategory);
+ OnTemplateImportCategory(sCategory);
+ }
+ else
+ {
+ OUString aMsg( SfxResId(STR_CREATE_ERROR) );
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(),
+ VclMessageType::Warning, VclButtonsType::Ok,
+ aMsg.replaceFirst("$1", sCategory)));
+ xBox->run();
+ return;
+ }
+ }
+ else
+ OnTemplateImportCategory(sCategory);
+ }
+ }
+ else
+ {
+ const auto sCategory = mxCBFolder->get_active_text();
+ OnTemplateImportCategory(sCategory);
+ }
+ mxLocalView->reload();
+ SearchUpdate();
+}
+
+void SfxTemplateManagerDlg::ExtensionsActionHdl()
+{
+ uno::Sequence<beans::PropertyValue> aArgs{ comphelper::makePropertyValue(
+ "AdditionsTag", OUString("Templates")) };
+ comphelper::dispatchCommand(".uno:AdditionsDialog", aArgs);
+}
+
+IMPL_LINK_NOARG(SfxTemplateManagerDlg, OpenRegionHdl, void*, void)
+{
+ maSelTemplates.clear();
+ mxOKButton->set_sensitive(false);
+ mxActionBar->show();
+}
+
+IMPL_LINK(SfxTemplateManagerDlg, CreateContextMenuHdl, ThumbnailViewItem*, pItem, void)
+{
+ const TemplateViewItem *pViewItem = dynamic_cast<TemplateViewItem*>(pItem);
+ bool bIsDefault = false;
+ bool bIsInternal = false;
+ std::vector<const TemplateViewItem*> aSelTemplates;
+ for(const auto& aSelTmpl : maSelTemplates)
+ {
+ const TemplateViewItem *aItem = dynamic_cast<const TemplateViewItem*>(aSelTmpl);
+ aSelTemplates.push_back(aItem);
+ }
+
+ for(const auto& aSelTmpl : aSelTemplates)
+ {
+ if(aSelTmpl->IsDefaultTemplate())
+ bIsDefault = true;
+ if(TemplateLocalView::IsInternalTemplate(aSelTmpl->getPath()))
+ {
+ bIsInternal = true;
+ if(bIsDefault)
+ break;
+ }
+ }
+
+ if (!pViewItem)
+ return;
+
+ bool bIsSingleSel = maSelTemplates.size() == 1;
+ OUString aDefaultImg;
+ INetURLObject aUrl(pViewItem->getPath());
+ if (ViewFilter_Application::isFilteredExtension(FILTER_APPLICATION::WRITER, aUrl.getExtension()))
+ aDefaultImg = BMP_ACTION_DEFAULT_WRITER;
+ else if (ViewFilter_Application::isFilteredExtension(FILTER_APPLICATION::CALC, aUrl.getExtension()))
+ aDefaultImg = BMP_ACTION_DEFAULT_CALC;
+ else if (ViewFilter_Application::isFilteredExtension(FILTER_APPLICATION::IMPRESS, aUrl.getExtension()))
+ aDefaultImg = BMP_ACTION_DEFAULT_IMPRESS;
+ else if (ViewFilter_Application::isFilteredExtension(FILTER_APPLICATION::DRAW, aUrl.getExtension()))
+ aDefaultImg = BMP_ACTION_DEFAULT_DRAW;
+ mxLocalView->createContextMenu(bIsDefault, bIsInternal, bIsSingleSel, aDefaultImg);
+}
+
+IMPL_LINK(SfxTemplateManagerDlg, OpenTemplateHdl, ThumbnailViewItem*, pItem, void)
+{
+ uno::Sequence< PropertyValue > aArgs{
+ comphelper::makePropertyValue("AsTemplate", true),
+ comphelper::makePropertyValue("MacroExecutionMode", MacroExecMode::USE_CONFIG),
+ comphelper::makePropertyValue("UpdateDocMode", UpdateDocMode::ACCORDING_TO_CONFIG),
+ comphelper::makePropertyValue("InteractionHandler", task::InteractionHandler::createWithParent( ::comphelper::getProcessComponentContext(), nullptr )),
+ comphelper::makePropertyValue("ReadOnly", true)
+ };
+
+ TemplateViewItem *pTemplateItem = static_cast<TemplateViewItem*>(pItem);
+
+ try
+ {
+ mxDesktop->loadComponentFromURL(pTemplateItem->getPath(),"_default", 0, aArgs );
+ }
+ catch( const uno::Exception& )
+ {
+ }
+
+ m_xDialog->response(RET_OK);
+}
+
+IMPL_LINK(SfxTemplateManagerDlg, EditTemplateHdl, ThumbnailViewItem*, pItem, void)
+{
+ uno::Sequence< PropertyValue > aArgs{
+ comphelper::makePropertyValue("AsTemplate", false),
+ comphelper::makePropertyValue("MacroExecutionMode", MacroExecMode::USE_CONFIG),
+ comphelper::makePropertyValue("UpdateDocMode", UpdateDocMode::ACCORDING_TO_CONFIG)
+ };
+
+ uno::Reference< XStorable > xStorable;
+ TemplateViewItem *pViewItem = static_cast<TemplateViewItem*>(pItem);
+
+ try
+ {
+ xStorable.set( mxDesktop->loadComponentFromURL(pViewItem->getPath(),"_default", 0, aArgs ),
+ uno::UNO_QUERY );
+ }
+ catch( const uno::Exception& )
+ {
+ }
+
+ m_xDialog->response(RET_OK);
+}
+
+IMPL_LINK_NOARG(SfxTemplateManagerDlg, DeleteTemplateHdl, void*, void)
+{
+ std::set<const ThumbnailViewItem*,selection_cmp_fn> aSelTemplates = maSelTemplates;
+ OUString aDeletedTemplate;
+
+ for (auto const& pItem : aSelTemplates)
+ {
+ const TemplateViewItem *pViewItem = static_cast<const TemplateViewItem*>(pItem);
+ sal_uInt16 nRegionItemId = mxLocalView->getRegionId(pViewItem->mnRegionId);
+
+ if (!mxLocalView->removeTemplate(pViewItem->mnDocId + 1, nRegionItemId))//mnId w.r.t. region is mnDocId + 1;
+ {
+ aDeletedTemplate += pItem->maTitle+"\n";
+ }
+ }
+
+ if (!aDeletedTemplate.isEmpty())
+ {
+ OUString aMsg( SfxResId(STR_MSG_ERROR_DELETE_TEMPLATE) );
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(),
+ VclMessageType::Warning, VclButtonsType::Ok,
+ aMsg.replaceFirst("$1",aDeletedTemplate)));
+ xBox->run();
+ }
+}
+
+IMPL_LINK(SfxTemplateManagerDlg, DefaultTemplateHdl, ThumbnailViewItem*, pItem, void)
+{
+ TemplateViewItem *pViewItem = static_cast<TemplateViewItem*>(pItem);
+ OUString aServiceName;
+
+ if(!pViewItem->IsDefaultTemplate())
+ {
+ if (lcl_getServiceName(pViewItem->getPath(),aServiceName))
+ {
+ OUString sPrevDefault = SfxObjectFactory::GetStandardTemplate( aServiceName );
+ if(!sPrevDefault.isEmpty())
+ {
+ mxLocalView->RemoveDefaultTemplateIcon(sPrevDefault);
+ }
+ SfxObjectFactory::SetStandardTemplate(aServiceName,pViewItem->getPath());
+ pViewItem->showDefaultIcon(true);
+ }
+ }
+ else
+ {
+ if(lcl_getServiceName(pViewItem->getPath(),aServiceName))
+ {
+ SfxObjectFactory::SetStandardTemplate( aServiceName, OUString() );
+ pViewItem->showDefaultIcon(false);
+ }
+ }
+
+ updateMenuItems();
+}
+
+IMPL_LINK_NOARG(SfxTemplateManagerDlg, SearchUpdateHdl, weld::Entry&, void)
+{
+ m_aUpdateDataTimer.Start();
+}
+
+IMPL_LINK_NOARG(SfxTemplateManagerDlg, ImplUpdateDataHdl, Timer*, void)
+{
+ SearchUpdate();
+}
+
+IMPL_LINK_NOARG(SfxTemplateManagerDlg, LoseFocusHdl, weld::Widget&, void)
+{
+ if (m_aUpdateDataTimer.IsActive())
+ {
+ m_aUpdateDataTimer.Stop();
+ m_aUpdateDataTimer.Invoke();
+ }
+}
+
+IMPL_LINK_NOARG ( SfxTemplateManagerDlg, ListViewHdl, weld::Toggleable&, void )
+{
+ setTemplateViewMode(TemplateViewMode::eListView);
+}
+
+IMPL_LINK_NOARG ( SfxTemplateManagerDlg, ThumbnailViewHdl, weld::Toggleable&, void )
+{
+ setTemplateViewMode(TemplateViewMode::eThumbnailView);
+ bMakeSelItemVisible = true;
+}
+
+IMPL_LINK_NOARG(SfxTemplateManagerDlg, FocusRectLocalHdl, weld::Widget&, tools::Rectangle)
+{
+ if(bMakeSelItemVisible && !maSelTemplates.empty())
+ mxLocalView->MakeItemVisible((*maSelTemplates.begin())->mnId);
+ bMakeSelItemVisible = false;
+ return tools::Rectangle();
+}
+
+void SfxTemplateManagerDlg::SearchUpdate()
+{
+ const OUString sSelectedRegion = mxCBFolder->get_active_text();
+ mxLocalView->setCurRegionId(mxLocalView->getRegionId(sSelectedRegion));
+ OUString aKeyword = mxSearchFilter->get_text();
+ mxLocalView->Clear();
+ std::function<bool(const TemplateItemProperties &)> aFunc =
+ [&](const TemplateItemProperties &rItem)->bool
+ {
+ return aKeyword.isEmpty() || SearchView_Keyword(aKeyword, getCurrentApplicationFilter())(rItem);
+ };
+
+ std::vector<TemplateItemProperties> aItems = mxLocalView->getFilteredItems(aFunc);
+ mxLocalView->insertItems(aItems, mxCBFolder->get_active()!=0, true);
+ mxLocalView->Invalidate();
+}
+
+IMPL_LINK_NOARG(SfxTemplateManagerDlg, GetFocusHdl, weld::Widget&, void)
+{
+ mxLocalView->deselectItems();
+ maSelTemplates.clear();
+}
+
+void SfxTemplateManagerDlg::OnTemplateState (const ThumbnailViewItem *pItem)
+{
+ bool bInSelection = maSelTemplates.find(pItem) != maSelTemplates.end();
+
+ if (pItem->isSelected())
+ {
+ if (maSelTemplates.empty())
+ {
+ mxOKButton->set_sensitive(true);
+ }
+ else if (maSelTemplates.size() != 1 || !bInSelection)
+ {
+ mxOKButton->set_sensitive(false);
+ }
+
+ if (!bInSelection)
+ maSelTemplates.insert(pItem);
+ }
+ else
+ {
+ if (bInSelection)
+ {
+ maSelTemplates.erase(pItem);
+
+ if (maSelTemplates.empty())
+ {
+ mxOKButton->set_sensitive(false);
+ }
+ else if (maSelTemplates.size() == 1)
+ {
+ mxOKButton->set_sensitive(true);
+ }
+ }
+ }
+
+}
+
+void SfxTemplateManagerDlg::OnTemplateImportCategory(std::u16string_view sCategory)
+{
+ sfx2::FileDialogHelper aFileDlg(css::ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE,
+ FileDialogFlags::MultiSelection, m_xDialog.get());
+ aFileDlg.SetContext(sfx2::FileDialogHelper::TemplateImport);
+
+ // add "All" filter
+ aFileDlg.AddFilter( SfxResId(STR_SFX_FILTERNAME_ALL),
+ FILEDIALOG_FILTER_ALL );
+
+ // add template filter
+ OUString sFilterExt;
+ OUString sFilterName( SfxResId( STR_TEMPLATE_FILTER ) );
+
+ // add filters of modules which are installed
+ SvtModuleOptions aModuleOpt;
+ if ( aModuleOpt.IsModuleInstalled( SvtModuleOptions::EModule::WRITER ) )
+ sFilterExt += "*.ott;*.stw;*.oth;*.dotx;*.dot";
+
+ if ( aModuleOpt.IsModuleInstalled( SvtModuleOptions::EModule::CALC ) )
+ {
+ if ( !sFilterExt.isEmpty() )
+ sFilterExt += ";";
+
+ sFilterExt += "*.ots;*.stc;*.xltx;*.xlt";
+ }
+
+ if ( aModuleOpt.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS ) )
+ {
+ if ( !sFilterExt.isEmpty() )
+ sFilterExt += ";";
+
+ sFilterExt += "*.otp;*.sti;*.pot;*.potx";
+ }
+
+ if ( aModuleOpt.IsModuleInstalled( SvtModuleOptions::EModule::DRAW ) )
+ {
+ if ( !sFilterExt.isEmpty() )
+ sFilterExt += ";";
+
+ sFilterExt += "*.otg;*.std";
+ }
+
+ if ( !sFilterExt.isEmpty() )
+ sFilterExt += ";";
+
+ sFilterExt += "*.vor";
+
+ sFilterName += " (" + sFilterExt + ")";
+
+ aFileDlg.AddFilter( sFilterName, sFilterExt );
+ aFileDlg.SetCurrentFilter( sFilterName );
+
+ ErrCode nCode = aFileDlg.Execute();
+
+ if ( nCode != ERRCODE_NONE )
+ return;
+
+ const css::uno::Sequence<OUString> aFiles = aFileDlg.GetSelectedFiles();
+
+ if (!aFiles.hasElements())
+ return;
+
+ //Import to the selected regions
+ TemplateContainerItem* pContItem = mxLocalView->getRegion(sCategory);
+ if(!pContItem)
+ return;
+
+ OUString aTemplateList;
+
+ for (const auto& rFile : aFiles)
+ {
+ if(!mxLocalView->copyFrom(pContItem, rFile))
+ {
+ if (aTemplateList.isEmpty())
+ aTemplateList = rFile;
+ else
+ aTemplateList += "\n" + rFile;
+ }
+ }
+
+ if (!aTemplateList.isEmpty())
+ {
+ OUString aMsg(SfxResId(STR_MSG_ERROR_IMPORT));
+ aMsg = aMsg.replaceFirst("$1",pContItem->maTitle);
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(),
+ VclMessageType::Warning, VclButtonsType::Ok,
+ aMsg.replaceFirst("$2",aTemplateList)));
+ xBox->run();
+ }
+}
+
+void SfxTemplateManagerDlg::OnTemplateExport()
+{
+ uno::Reference<XComponentContext> xContext(comphelper::getProcessComponentContext());
+ uno::Reference<XFolderPicker2> xFolderPicker = sfx2::createFolderPicker(xContext, m_xDialog.get());
+
+ xFolderPicker->setDisplayDirectory(SvtPathOptions().GetWorkPath());
+
+ sal_Int16 nResult = xFolderPicker->execute();
+ sal_Int16 nCount = maSelTemplates.size();
+
+ if( nResult != ExecutableDialogResults::OK )
+ return;
+
+ OUString aTemplateList;
+ INetURLObject aPathObj(xFolderPicker->getDirectory());
+ aPathObj.setFinalSlash();
+
+ // export templates from the current view
+
+ sal_uInt16 i = 1;
+ auto aSelTemplates = maSelTemplates;
+ for (auto const& selTemplate : aSelTemplates)
+ {
+ const TemplateViewItem *pItem = static_cast<const TemplateViewItem*>(selTemplate);
+
+ INetURLObject aItemPath(pItem->getPath());
+
+ if ( 1 == i )
+ aPathObj.Append(aItemPath.getName());
+ else
+ aPathObj.setName(aItemPath.getName());
+
+ OUString aPath = aPathObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+
+ if (!mxLocalView->exportTo(pItem->mnDocId + 1, //mnId w.r.t. region = mDocId + 1
+ mxLocalView->getRegionId(pItem->mnRegionId), //pItem->mnRegionId does not store actual region Id
+ aPath))
+ {
+ if (aTemplateList.isEmpty())
+ aTemplateList = pItem->maTitle;
+ else
+ aTemplateList += "\n" + pItem->maTitle;
+ }
+ ++i;
+ mxLocalView->deselectItems();
+ }
+
+ if (!aTemplateList.isEmpty())
+ {
+ OUString aText( SfxResId(STR_MSG_ERROR_EXPORT) );
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(),
+ VclMessageType::Warning, VclButtonsType::Ok,
+ aText.replaceFirst("$1",aTemplateList)));
+ xBox->run();
+ }
+ else
+ {
+ OUString sText( SfxResId(STR_MSG_EXPORT_SUCCESS) );
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(),
+ VclMessageType::Info, VclButtonsType::Ok,
+ sText.replaceFirst("$1", OUString::number(nCount))));
+ xBox->run();
+ }
+}
+
+void SfxTemplateManagerDlg::OnTemplateOpen ()
+{
+ ThumbnailViewItem *pItem = const_cast<ThumbnailViewItem*>(*maSelTemplates.begin());
+
+ OpenTemplateHdl(pItem);
+}
+
+void SfxTemplateManagerDlg::OnCategoryNew()
+{
+ InputDialog dlg(m_xDialog.get(), SfxResId(STR_INPUT_NEW));
+ dlg.set_title(SfxResId(STR_WINDOW_TITLE_RENAME_NEW_CATEGORY));
+ int ret = dlg.run();
+
+ if (!ret)
+ return;
+
+ OUString aName = dlg.GetEntryText();
+
+ if(mxLocalView->createRegion(aName))
+ mxCBFolder->append_text(aName);
+ else
+ {
+ OUString aMsg( SfxResId(STR_CREATE_ERROR) );
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(),
+ VclMessageType::Warning, VclButtonsType::Ok,
+ aMsg.replaceFirst("$1", aName)));
+ xBox->run();
+ }
+}
+
+void SfxTemplateManagerDlg::OnCategoryRename()
+{
+ OUString sCategory = mxCBFolder->get_active_text();
+ InputDialog dlg(m_xDialog.get(), SfxResId(STR_INPUT_NEW));
+ dlg.set_title(SfxResId(STR_WINDOW_TITLE_RENAME_CATEGORY));
+ dlg.SetEntryText(sCategory);
+ int ret = dlg.run();
+
+ if (!ret)
+ return;
+
+ OUString aName = dlg.GetEntryText();
+
+ if(mxLocalView->renameRegion(sCategory, aName))
+ {
+ sal_Int32 nPos = mxCBFolder->find_text(sCategory);
+ mxCBFolder->remove(nPos);
+ mxCBFolder->insert_text(nPos, aName);
+ mxCBFolder->set_active(nPos);
+
+ mxLocalView->reload();
+ SearchUpdate();
+ }
+ else
+ {
+ OUString aMsg( SfxResId(STR_CREATE_ERROR) );
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(),
+ VclMessageType::Warning, VclButtonsType::Ok,
+ aMsg.replaceFirst("$1", aName)));
+ xBox->run();
+ }
+}
+
+void SfxTemplateManagerDlg::OnCategoryDelete()
+{
+ const auto sCategory = mxCBFolder->get_active_text();
+ std::unique_ptr<weld::MessageDialog> popupDlg(Application::CreateMessageDialog(m_xDialog.get(),
+ VclMessageType::Question, VclButtonsType::YesNo,
+ SfxResId(STR_QMSG_SEL_FOLDER_DELETE).replaceFirst("$1",sCategory)));
+ if (popupDlg->run() != RET_YES)
+ return;
+
+ sal_Int16 nItemId = mxLocalView->getRegionId(sCategory);
+
+ if (!mxLocalView->removeRegion(nItemId))
+ {
+ OUString sMsg( SfxResId(STR_MSG_ERROR_DELETE_FOLDER) );
+ std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(),
+ VclMessageType::Warning, VclButtonsType::Ok,
+ sMsg.replaceFirst("$1",sCategory)));
+ xBox->run();
+ }
+ else
+ {
+ mxCBFolder->remove_text(sCategory);
+ }
+
+ mxLocalView->reload();
+ mxLocalView->showAllTemplates();
+ mxCBApp->set_active(0);
+ mxCBFolder->set_active(0);
+ SearchUpdate();
+ mxActionBar->set_item_sensitive(MNI_ACTION_RENAME_FOLDER, false);
+ mxActionBar->set_item_sensitive(MNI_ACTION_DELETE_FOLDER, false);
+ updateMenuItems();
+}
+
+void SfxTemplateManagerDlg::updateMenuItems ()
+{
+
+ mxActionBar->set_item_visible(MNI_ACTION_DEFAULT, false);
+ mxActionBar->set_item_visible(MNI_ACTION_DEFAULT_WRITER, false);
+ mxActionBar->set_item_visible(MNI_ACTION_DEFAULT_CALC, false);
+ mxActionBar->set_item_visible(MNI_ACTION_DEFAULT_IMPRESS, false);
+ mxActionBar->set_item_visible(MNI_ACTION_DEFAULT_DRAW, false);
+ mxActionBar->set_item_sensitive(MNI_ACTION_DEFAULT, false);
+ mxActionBar->set_item_sensitive(MNI_ACTION_DEFAULT_WRITER, false);
+ mxActionBar->set_item_sensitive(MNI_ACTION_DEFAULT_CALC, false);
+ mxActionBar->set_item_sensitive(MNI_ACTION_DEFAULT_IMPRESS, false);
+ mxActionBar->set_item_sensitive(MNI_ACTION_DEFAULT_DRAW, false);
+
+ SvtModuleOptions aModOpt;
+ if( mxCBApp->get_active() == MNI_WRITER)
+ {
+ mxActionBar->set_item_visible(MNI_ACTION_DEFAULT_WRITER, true);
+ if(!aModOpt.GetFactoryStandardTemplate( SvtModuleOptions::EFactory::WRITER).isEmpty())
+ mxActionBar->set_item_sensitive(MNI_ACTION_DEFAULT_WRITER, true);
+ }
+ else if( mxCBApp->get_active() == MNI_CALC )
+ {
+ mxActionBar->set_item_visible(MNI_ACTION_DEFAULT_CALC, true);
+ if(!aModOpt.GetFactoryStandardTemplate( SvtModuleOptions::EFactory::CALC).isEmpty())
+ mxActionBar->set_item_sensitive(MNI_ACTION_DEFAULT_CALC, true);
+ }
+ else if(mxCBApp->get_active() == MNI_IMPRESS)
+ {
+ mxActionBar->set_item_visible(MNI_ACTION_DEFAULT_IMPRESS, true);
+ if(!aModOpt.GetFactoryStandardTemplate( SvtModuleOptions::EFactory::IMPRESS).isEmpty())
+ mxActionBar->set_item_sensitive(MNI_ACTION_DEFAULT_IMPRESS, true);
+ }
+ else if(mxCBApp->get_active() == MNI_DRAW)
+ {
+ mxActionBar->set_item_visible(MNI_ACTION_DEFAULT_DRAW, true);
+ if(!aModOpt.GetFactoryStandardTemplate( SvtModuleOptions::EFactory::DRAW).isEmpty())
+ mxActionBar->set_item_sensitive(MNI_ACTION_DEFAULT_DRAW, true);
+ }
+ else if(mxCBApp->get_active() == MNI_ALL_APPLICATIONS)
+ {
+ mxActionBar->set_item_visible(MNI_ACTION_DEFAULT, true);
+ if(!lcl_getAllFactoryURLs().empty())
+ mxActionBar->set_item_sensitive(MNI_ACTION_DEFAULT, true);
+ }
+}
+
+void SfxTemplateManagerDlg::localMoveTo(sal_uInt16 nItemId)
+{
+ if (nItemId)
+ {
+ // Move templates to desired folder if for some reason move fails
+ // try copying them.
+ mxLocalView->moveTemplates(maSelTemplates,nItemId);
+ }
+}
+
+static bool lcl_getServiceName ( const OUString &rFileURL, OUString &rName )
+{
+ bool bRet = false;
+
+ if ( !rFileURL.isEmpty() )
+ {
+ try
+ {
+ uno::Reference< embed::XStorage > xStorage =
+ comphelper::OStorageHelper::GetStorageFromURL( rFileURL, embed::ElementModes::READ );
+
+ SotClipboardFormatId nFormat = SotStorage::GetFormatID( xStorage );
+
+ std::shared_ptr<const SfxFilter> pFilter = SfxGetpApp()->GetFilterMatcher().GetFilter4ClipBoardId( nFormat );
+
+ if ( pFilter )
+ {
+ rName = pFilter->GetServiceName();
+ bRet = true;
+ }
+ }
+ catch( uno::Exception& )
+ {}
+ }
+
+ return bRet;
+}
+
+static std::vector<OUString> lcl_getAllFactoryURLs ()
+{
+ SvtModuleOptions aModOpt;
+ std::vector<OUString> aList;
+ const css::uno::Sequence<OUString> &aServiceNames = aModOpt.GetAllServiceNames();
+
+ for( const auto& rServiceName : aServiceNames )
+ {
+ if ( ! SfxObjectFactory::GetStandardTemplate( rServiceName ).isEmpty() )
+ {
+ SvtModuleOptions::EFactory eFac = SvtModuleOptions::EFactory::WRITER;
+ SvtModuleOptions::ClassifyFactoryByName( rServiceName, eFac );
+ aList.push_back(aModOpt.GetFactoryEmptyDocumentURL(eFac));
+ }
+ }
+
+ return aList;
+}
+
+
+// Class SfxTemplateCategoryDialog --------------------------------------------------
+
+SfxTemplateCategoryDialog::SfxTemplateCategoryDialog(weld::Window* pParent)
+ : GenericDialogController(pParent, "sfx/ui/templatecategorydlg.ui", "TemplatesCategoryDialog")
+ , mbIsNewCategory(false)
+ , mxLBCategory(m_xBuilder->weld_tree_view("categorylb"))
+ , mxNewCategoryEdit(m_xBuilder->weld_entry("category_entry"))
+ , mxOKButton(m_xBuilder->weld_button("ok"))
+{
+ mxLBCategory->append_text(SfxResId(STR_CATEGORY_NONE));
+ mxNewCategoryEdit->connect_changed(LINK(this, SfxTemplateCategoryDialog, NewCategoryEditHdl));
+ mxLBCategory->set_size_request(mxLBCategory->get_approximate_digit_width() * 32,
+ mxLBCategory->get_height_rows(8));
+ mxLBCategory->connect_changed(LINK(this, SfxTemplateCategoryDialog, SelectCategoryHdl));
+ mxOKButton->set_sensitive(false);
+}
+
+SfxTemplateCategoryDialog::~SfxTemplateCategoryDialog()
+{
+}
+
+IMPL_LINK_NOARG(SfxTemplateCategoryDialog, NewCategoryEditHdl, weld::Entry&, void)
+{
+ OUString sParam = comphelper::string::strip(mxNewCategoryEdit->get_text(), ' ');
+ mxLBCategory->set_sensitive(sParam.isEmpty());
+ if(!sParam.isEmpty())
+ {
+ msSelectedCategory = sParam;
+ mbIsNewCategory = true;
+ mxOKButton->set_sensitive(true);
+ }
+ else
+ {
+ SelectCategoryHdl(*mxLBCategory);
+ mbIsNewCategory = false;
+ }
+}
+
+IMPL_LINK_NOARG(SfxTemplateCategoryDialog, SelectCategoryHdl, weld::TreeView&, void)
+{
+ if (mxLBCategory->get_selected_index() == 0)
+ {
+ msSelectedCategory = OUString();
+ mxOKButton->set_sensitive(false);
+ mxNewCategoryEdit->set_sensitive(true);
+ }
+ else
+ {
+ msSelectedCategory = mxLBCategory->get_selected_text();
+ mxNewCategoryEdit->set_sensitive(false);
+ mxOKButton->set_sensitive(true);
+ }
+
+ mbIsNewCategory = false;
+}
+
+void SfxTemplateCategoryDialog::SetCategoryLBEntries(std::vector<OUString> aFolderNames)
+{
+ for (size_t i = 0, n = aFolderNames.size(); i < n; ++i)
+ mxLBCategory->append_text(aFolderNames[i]);
+ mxLBCategory->select(0);
+}
+
+// SfxTemplateSelectionDialog -----------------------------------------------------------------
+
+SfxTemplateSelectionDlg::SfxTemplateSelectionDlg(weld::Window* pParent)
+ : SfxTemplateManagerDlg(pParent)
+ , maIdle("sfx2 SfxTemplateManagerDlg maIdle")
+{
+ mxCBApp->set_active(MNI_IMPRESS);
+ mxCBFolder->set_active(0);
+ m_xDialog->set_title(SfxResId(STR_TEMPLATE_SELECTION));
+
+ if (mxLocalView->IsVisible())
+ {
+ mxLocalView->filterItems(ViewFilter_Application(getCurrentApplicationFilter()));
+ mxLocalView->showAllTemplates();
+ }
+
+ mxCBApp->set_sensitive(false);
+ mxActionBar->show();
+ mxCBXHideDlg->show();
+ mxCBXHideDlg->set_active(true);
+
+ mxLocalView->setOpenTemplateHdl(LINK(this,SfxTemplateSelectionDlg, OpenTemplateHdl));
+ mxOKButton->connect_clicked(LINK(this, SfxTemplateSelectionDlg, OkClickHdl));
+ updateMenuItems();
+}
+
+SfxTemplateSelectionDlg::~SfxTemplateSelectionDlg()
+{
+ maIdle.Stop();
+}
+
+short SfxTemplateSelectionDlg::run()
+{
+ // tdf#124597 at startup this dialog is launched before its parent window
+ // has taken its final size. The parent size request is processed during
+ // the dialogs event loop so configure this dialog to center to
+ // the parents pending geometry request
+ m_xDialog->set_centered_on_parent(true);
+
+ // tdf#125079 toggle off the size tracking at some future idle point
+ maIdle.SetPriority(TaskPriority::LOWEST);
+ maIdle.SetInvokeHandler(LINK(this,SfxTemplateSelectionDlg,TimeOut));
+ maIdle.Start();
+ setTemplateViewMode(TemplateViewMode::eThumbnailView);
+
+ return weld::GenericDialogController::run();
+}
+
+IMPL_LINK_NOARG(SfxTemplateSelectionDlg, TimeOut, Timer*, void)
+{
+ m_xDialog->set_centered_on_parent(false);
+}
+
+IMPL_LINK(SfxTemplateSelectionDlg, OpenTemplateHdl, ThumbnailViewItem*, pItem, void)
+{
+ TemplateViewItem *pViewItem = static_cast<TemplateViewItem*>(pItem);
+ msTemplatePath = pViewItem->getPath();
+
+ m_xDialog->response(RET_OK);
+}
+
+IMPL_LINK_NOARG(SfxTemplateSelectionDlg, OkClickHdl, weld::Button&, void)
+{
+ TemplateViewItem *pViewItem = static_cast<TemplateViewItem*>(const_cast<ThumbnailViewItem*>(*maSelTemplates.begin()));
+ msTemplatePath = pViewItem->getPath();
+
+ m_xDialog->response(RET_OK);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sfx2/source/doc/watermarkitem.cxx b/sfx2/source/doc/watermarkitem.cxx
new file mode 100644
index 0000000000..4e64afd7ff
--- /dev/null
+++ b/sfx2/source/doc/watermarkitem.cxx
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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/.
+ */
+
+#include <sfx2/watermarkitem.hxx>
+#include <sfx2/sfxsids.hrc>
+#include <comphelper/propertysequence.hxx>
+
+SfxWatermarkItem::SfxWatermarkItem()
+: SfxPoolItem( SID_WATERMARK )
+, m_aText( "" )
+, m_aFont( "Liberation Sans" )
+, m_nAngle( 45 )
+, m_nTransparency( 50 )
+, m_nColor( 0xc0c0c0 )
+{
+}
+
+SfxPoolItem* SfxWatermarkItem::CreateDefault()
+{
+ return new SfxWatermarkItem();
+}
+
+bool SfxWatermarkItem::operator==( const SfxPoolItem& rCmp ) const
+{
+ return ( SfxPoolItem::operator==( rCmp ) &&
+ m_aText == static_cast<const SfxWatermarkItem&>(rCmp).m_aText &&
+ m_aFont == static_cast<const SfxWatermarkItem&>(rCmp).m_aFont &&
+ m_nAngle == static_cast<const SfxWatermarkItem&>(rCmp).m_nAngle &&
+ m_nTransparency == static_cast<const SfxWatermarkItem&>(rCmp).m_nTransparency &&
+ m_nColor == static_cast<const SfxWatermarkItem&>(rCmp).m_nColor );
+}
+
+SfxWatermarkItem* SfxWatermarkItem::Clone( SfxItemPool *) const
+{
+ return new SfxWatermarkItem(*this);
+}
+
+bool SfxWatermarkItem::QueryValue( css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const
+{
+ rVal <<= comphelper::InitPropertySequence( {
+ { "Text", css::uno::Any( m_aText ) },
+ { "Font", css::uno::Any( m_aFont ) },
+ { "Angle", css::uno::Any( m_nAngle ) },
+ { "Transparency", css::uno::Any( m_nTransparency ) },
+ { "Color", css::uno::Any( m_nColor ) },
+ } );
+
+ return true;
+}
+
+bool SfxWatermarkItem::PutValue( const css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ )
+{
+ css::uno::Sequence<css::beans::PropertyValue> aSequence;
+
+ if ( rVal >>= aSequence )
+ {
+ for(const auto& aEntry : std::as_const(aSequence))
+ {
+ if(aEntry.Name == "Text")
+ aEntry.Value >>= m_aText;
+ if(aEntry.Name == "Font")
+ aEntry.Value >>= m_aFont;
+ if(aEntry.Name == "Angle")
+ aEntry.Value >>= m_nAngle;
+ if(aEntry.Name == "Transparency")
+ aEntry.Value >>= m_nTransparency;
+ if(aEntry.Name == "Color")
+ aEntry.Value >>= m_nColor;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/sfx2/source/doc/zoomitem.cxx b/sfx2/source/doc/zoomitem.cxx
new file mode 100644
index 0000000000..e4c160712b
--- /dev/null
+++ b/sfx2/source/doc/zoomitem.cxx
@@ -0,0 +1,172 @@
+/* -*- 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 <sfx2/zoomitem.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+
+#include <comphelper/propertyvalue.hxx>
+#include <osl/diagnose.h>
+
+#include <cassert>
+
+
+SfxPoolItem* SvxZoomItem::CreateDefault() { return new SvxZoomItem; }
+
+constexpr OUString ZOOM_PARAM_VALUE = u"Value"_ustr;
+constexpr OUString ZOOM_PARAM_VALUESET = u"ValueSet"_ustr;
+constexpr OUString ZOOM_PARAM_TYPE = u"Type"_ustr;
+#define ZOOM_PARAMS 3
+
+
+SvxZoomItem::SvxZoomItem
+(
+ SvxZoomType eZoomType,
+ sal_uInt16 nVal,
+ TypedWhichId<SvxZoomItem> _nWhich
+)
+: SfxUInt16Item( _nWhich, nVal ),
+ nValueSet( SvxZoomEnableFlags::ALL ),
+ eType( eZoomType )
+{
+}
+
+SvxZoomItem* SvxZoomItem::Clone( SfxItemPool * /*pPool*/ ) const
+{
+ return new SvxZoomItem( *this );
+}
+
+bool SvxZoomItem::operator==( const SfxPoolItem& rAttr ) const
+{
+ assert(SfxPoolItem::operator==(rAttr));
+
+ const SvxZoomItem& rItem = static_cast<const SvxZoomItem&>(rAttr);
+
+ return ( GetValue() == rItem.GetValue() &&
+ nValueSet == rItem.GetValueSet() &&
+ eType == rItem.GetType() );
+}
+
+bool SvxZoomItem::QueryValue( css::uno::Any& rVal, sal_uInt8 nMemberId ) const
+{
+ nMemberId &= ~CONVERT_TWIPS;
+ switch( nMemberId )
+ {
+ case 0:
+ {
+ css::uno::Sequence< css::beans::PropertyValue > aSeq{
+ comphelper::makePropertyValue(ZOOM_PARAM_VALUE, sal_Int32( GetValue() )),
+ comphelper::makePropertyValue(ZOOM_PARAM_VALUESET, sal_Int16( nValueSet )),
+ comphelper::makePropertyValue(ZOOM_PARAM_TYPE, sal_Int16( eType ))
+ };
+ assert(aSeq.getLength() == ZOOM_PARAMS);
+ rVal <<= aSeq;
+ break;
+ }
+
+ case MID_VALUE: rVal <<= static_cast<sal_Int32>(GetValue()); break;
+ case MID_VALUESET: rVal <<= static_cast<sal_Int16>(nValueSet); break;
+ case MID_TYPE: rVal <<= static_cast<sal_Int16>(eType); break;
+ default:
+ OSL_FAIL("sfx2::SvxZoomItem::QueryValue(), Wrong MemberId!");
+ return false;
+ }
+
+ return true;
+}
+
+bool SvxZoomItem::PutValue( const css::uno::Any& rVal, sal_uInt8 nMemberId )
+{
+ nMemberId &= ~CONVERT_TWIPS;
+ switch( nMemberId )
+ {
+ case 0:
+ {
+ css::uno::Sequence< css::beans::PropertyValue > aSeq;
+ if (( rVal >>= aSeq ) && ( aSeq.getLength() == ZOOM_PARAMS ))
+ {
+ sal_Int32 nValueTmp( 0 );
+ sal_Int16 nValueSetTmp( 0 );
+ sal_Int16 nTypeTmp( 0 );
+ bool bAllConverted( true );
+ sal_Int16 nConvertedCount( 0 );
+ for ( const auto& rProp : std::as_const(aSeq) )
+ {
+ if ( rProp.Name == ZOOM_PARAM_VALUE )
+ {
+ bAllConverted &= ( rProp.Value >>= nValueTmp );
+ ++nConvertedCount;
+ }
+ else if ( rProp.Name == ZOOM_PARAM_VALUESET )
+ {
+ bAllConverted &= ( rProp.Value >>= nValueSetTmp );
+ ++nConvertedCount;
+ }
+ else if ( rProp.Name == ZOOM_PARAM_TYPE )
+ {
+ bAllConverted &= ( rProp.Value >>= nTypeTmp );
+ ++nConvertedCount;
+ }
+ }
+
+ if ( bAllConverted && nConvertedCount == ZOOM_PARAMS )
+ {
+ SetValue( static_cast<sal_uInt16>(nValueTmp) );
+ nValueSet = static_cast<SvxZoomEnableFlags>(nValueSetTmp);
+ eType = static_cast<SvxZoomType>(nTypeTmp);
+ return true;
+ }
+ }
+ return false;
+ }
+ case MID_VALUE:
+ {
+ sal_Int32 nVal = 0;
+ if ( rVal >>= nVal )
+ {
+ SetValue( static_cast<sal_uInt16>(nVal) );
+ return true;
+ }
+ else
+ return false;
+ }
+
+ case MID_VALUESET:
+ case MID_TYPE:
+ {
+ sal_Int16 nVal;
+ if ( rVal >>= nVal )
+ {
+ if ( nMemberId == MID_VALUESET )
+ nValueSet = static_cast<SvxZoomEnableFlags>(nVal);
+ else if ( nMemberId == MID_TYPE )
+ eType = static_cast<SvxZoomType>(nVal);
+ return true;
+ }
+ else
+ return false;
+ }
+
+ default:
+ OSL_FAIL("sfx2::SvxZoomItem::PutValue(), Wrong MemberId!");
+ return false;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */