diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sfx2/source/doc | |
parent | Initial commit. (diff) | |
download | libreoffice-cb75148ebd0135178ff46f89a30139c44f8d2040.tar.xz libreoffice-cb75148ebd0135178ff46f89a30139c44f8d2040.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sfx2/source/doc')
51 files changed, 40791 insertions, 0 deletions
diff --git a/sfx2/source/doc/DocumentMetadataAccess.cxx b/sfx2/source/doc/DocumentMetadataAccess.cxx new file mode 100644 index 000000000..3bb670c7b --- /dev/null +++ b/sfx2/source/doc/DocumentMetadataAccess.cxx @@ -0,0 +1,1381 @@ +/* -*- 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 <tools/diagnose_ex.h> + +#include <libxml/tree.h> + +#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 OUStringLiteral s_content = u"content.xml"; +constexpr OUStringLiteral s_styles = u"styles.xml"; +constexpr OUStringLiteral s_manifest = u"manifest.rdf"; +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.matchIgnoreAsciiCase("vnd.sun.star.expand:")) + { + // expand it here (makeAbsolute requires hierarchical URI) + pkgURI = pkgURI.copy( RTL_CONSTASCII_LENGTH("vnd.sun.star.expand:") ); + 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(i_rSubDocument); + buf.append('/'); + } + 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> const& i_xContext, + SfxObjectShell const & i_rRegistrySupplier) + : m_xContext(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(OUString const & i_XmlId, + OUString & o_StreamName, OUString& o_Idref ) +{ + const sal_Int32 idx(i_XmlId.indexOf(u'#')); + if ((idx <= 0) || (idx >= i_XmlId.getLength() - 1)) { + return false; + } else { + o_StreamName = i_XmlId.copy(0, idx); + o_Idref = i_XmlId.copy(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) +{ + ucb::InteractiveAugmentedIOException iaioe; + iaioe.Message = i_rMessage; + iaioe.Classification = task::InteractionClassification_ERROR; + iaioe.Code = i_ErrorCode; + + 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)); + iaioe.Arguments = { uno::Any(uriProp), uno::Any(rnProp) }; + return iaioe; +} + +/** 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.copy(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 ) { + ErrCode nError = aMedium.GetError(); + if ( nError == ERRCODE_NONE ) { + nError = ERRCODE_IO_GENERAL; + } + task::ErrorCodeIOException ex( + "DocumentMetadataAccess::storeMetadataToMedium Commit failed: " + nError.toHexString(), + uno::Reference< uno::XInterface >(), sal_uInt32(nError)); + 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 000000000..0106a6477 --- /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 000000000..1906967ca --- /dev/null +++ b/sfx2/source/doc/Metadatable.cxx @@ -0,0 +1,1602 @@ +/* -*- 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 <vcl/svapp.hxx> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <comphelper/random.hxx> +#include <tools/diagnose_ex.h> + +#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 OUStringLiteral s_content = u"content.xml"; +constexpr OUStringLiteral s_styles = u"styles.xml"; + +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 ); + rVector.erase(std::remove(rVector.begin(), rVector.end(), &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 ? OUString(s_content) : OUString(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 const& i_rStream, + OUString const& i_rXmlId, + std::shared_ptr<MetadatableClipboard> const& i_pLink + = std::shared_ptr<MetadatableClipboard>()) + : m_Stream(i_rStream), m_XmlId(i_rXmlId), m_xLink(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 ? OUString(s_content) : OUString(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() ? OUString(s_content) : OUString(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 000000000..4abc612dc --- /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 000000000..6392bd4aa --- /dev/null +++ b/sfx2/source/doc/SfxDocumentMetaData.cxx @@ -0,0 +1,2214 @@ +/* -*- 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/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 <tools/diagnose_ex.h> +#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::XDocumentProperties, + 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; + + // 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 OUStringLiteral sMetaPageCount = u"meta:page-count"; +constexpr OUStringLiteral sMetaTableCount = u"meta:table-count"; +constexpr OUStringLiteral sMetaDrawCount = u"meta:draw-count"; +constexpr OUStringLiteral sMetaImageCount = u"meta:image-count"; +constexpr OUStringLiteral sMetaObjectCount = u"meta:object-count"; +constexpr OUStringLiteral sMetaOleObjectCount = u"meta:ole-object-count"; +constexpr OUStringLiteral sMetaParagraphCount = u"meta:paragraph-count"; +constexpr OUStringLiteral sMetaWordCount = u"meta:word-count"; +constexpr OUStringLiteral sMetaCharacterCount = u"meta:character-count"; +constexpr OUStringLiteral sMetaRowCount = u"meta:row-count"; +constexpr OUStringLiteral sMetaFrameCount = u"meta:frame-count"; +constexpr OUStringLiteral sMetaSentenceCount = u"meta:sentence-count"; +constexpr OUStringLiteral sMetaSyllableCount = u"meta:syllable-count"; +constexpr OUStringLiteral sMetaNonWhitespaceCharacterCount = u"meta:non-whitespace-character-count"; +constexpr OUStringLiteral sMetaCellCount = u"meta:cell-count"; + +// NB: keep these two arrays in sync! +constexpr rtl::OUStringConstExpr 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! + nullptr +}; + +constexpr OUStringLiteral sMetaKeyword = u"meta:keyword"; +constexpr OUStringLiteral sMetaUserDefined = u"meta:user-defined"; +constexpr rtl::OUStringConstExpr s_stdMetaList[] { + sMetaKeyword, // string* + sMetaUserDefined, // ...* +}; + +constexpr OUStringLiteral s_nsXLink = u"http://www.w3.org/1999/xlink"; +constexpr OUStringLiteral s_nsDC = u"http://purl.org/dc/elements/1.1/"; +constexpr OUStringLiteral s_nsODF = u"urn:oasis:names:tc:opendocument:xmlns:office:1.0"; +constexpr OUStringLiteral s_nsODFMeta = u"urn:oasis:names:tc:opendocument:xmlns:meta:1.0"; +// constexpr OUStringLiteral s_nsOOo = "http://openoffice.org/2004/office"; // not used (yet?) + +constexpr OUStringLiteral s_meta = u"meta.xml"; + +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(OUString(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 OUStringLiteral vt = u"meta:value-type"; + + // 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, OUString(name)); + m_metaList[name] = nodes; + } + + // initialize members corresponding to attributes from DOM nodes + static constexpr OUStringLiteral sMetaTemplate = u"meta:template"; + static constexpr OUStringLiteral sMetaAutoReload = u"meta:auto-reload"; + 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( + static_cast< ::cppu::OWeakObject* >(this))); + 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::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 ) { + ErrCode nError = aMedium.GetError(); + if ( nError == ERRCODE_NONE ) { + nError = ERRCODE_IO_GENERAL; + } + + throw css::task::ErrorCodeIOException( + "SfxDocumentMetaData::storeToMedium <" + URL + "> Commit failed: " + nError.toHexString(), + css::uno::Reference< css::uno::XInterface >(), sal_uInt32(nError)); + + } +} + +// 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 000000000..beb96b55d --- /dev/null +++ b/sfx2/source/doc/SfxRedactionHelper.cxx @@ -0,0 +1,562 @@ +/* -*- 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 <tools/diagnose_ex.h> + +#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.algorithmType = util::SearchAlgorithms_REGEXP; + rSearchOpt.AlgorithmType2 = util::SearchAlgorithms2::REGEXP; + } + else + { + rSearchOpt.algorithmType = util::SearchAlgorithms_ABSOLUTE; + 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 000000000..6509f8c47 --- /dev/null +++ b/sfx2/source/doc/autoredactdialog.cxx @@ -0,0 +1,770 @@ +/* -*- 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_xTargetsBox->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_xTargetsBox->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_xTargetsBox->InsertTarget(redactiontarget); + + // Actually add to the targets vector + if (m_xTargetsBox->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_xTargetsBox->get_selected_index(); + + // No selection, nothing to edit + if (nSelectedRow < 0) + return; + + // Only one entry should be selected for editing + if (m_xTargetsBox->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_xTargetsBox->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_xTargetsBox->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_xTargetsBox->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_xTargetsBox->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_xTargetsBox->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").c_str()); + RedactionTargetType eType + = static_cast<RedactionTargetType>(atoi(rValue.second.get<std::string>("eType").c_str())); + OUString sContent = OUString::fromUtf8(rValue.second.get<std::string>("sContent").c_str()); + bool bCaseSensitive + = OUString::fromUtf8(rValue.second.get<std::string>("bCaseSensitive").c_str()).toBoolean(); + bool bWholeWords + = OUString::fromUtf8(rValue.second.get<std::string>("bWholeWords").c_str()).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).getStr()); + + 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).getStr()); + + 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_xTargetsBox->InsertTarget(pTarget.get()); + + // Actually add to the targets vector + auto name = pTarget->sName; + if (m_xTargetsBox->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_xTargetsBox->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_xRedactionTargetsLabel(m_xBuilder->weld_label("labelRedactionTargets")) + , m_xTargetsBox(new TargetsTable(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, + OStringToOUString(m_xDialog->get_help_id(), RTL_TEXTENCODING_UTF8)); + + 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_xTargetsBox->connect_row_activated(LINK(this, SfxAutoRedactDialog, DoubleClickEditHdl)); +} + +SfxAutoRedactDialog::~SfxAutoRedactDialog() +{ + if (m_aTableTargets.empty()) + { + // Clear the dialog data + SvtViewOptions aDlgOpt(EViewType::Dialog, + OStringToOUString(m_xDialog->get_help_id(), RTL_TEXTENCODING_UTF8)); + 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().c_str())); + + // Store the dialog data + SvtViewOptions aDlgOpt(EViewType::Dialog, + OStringToOUString(m_xDialog->get_help_id(), RTL_TEXTENCODING_UTF8)); + 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 000000000..a222ef7e4 --- /dev/null +++ b/sfx2/source/doc/docfac.cxx @@ -0,0 +1,354 @@ +/* -*- 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> + +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, + const OUString& sName +) : m_sFactoryName( 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 OUStringLiteral PROP_DEF_TEMPL_CHANGED + = u"ooSetupFactorySystemDefaultTemplateChanged"; + + 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 000000000..fafe05d15 --- /dev/null +++ b/sfx2/source/doc/docfile.cxx @@ -0,0 +1,4752 @@ +/* -*- 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/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/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/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/ucb/InsertCommandArgument.hpp> +#include <com/sun/star/ucb/NameClash.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 <tools/diagnose_ex.h> +#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(pMutex) + , _pIsDestructed(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; + ErrCode m_eError; + + ::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; + + 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::TempFile> 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; + + ErrCode 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), + 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(); +} + +ErrCode const & SfxMedium::GetLastStorageCreationState() const +{ + return pImpl->nLastStorageError; +} + +void SfxMedium::SetError(ErrCode nError) +{ + pImpl->m_eError = nError; +} + +ErrCode SfxMedium::GetErrorCode() const +{ + ErrCode 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 ) +{ + OUString aBaseURL; + const SfxStringItem* pBaseURLItem = GetItemSet()->GetItem<SfxStringItem>(SID_DOC_BASEURL); + if ( pBaseURLItem ) + aBaseURL = pBaseURLItem->GetValue(); + else 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 ); + } + + if ( bForSaving ) + { + bool bIsRemote = IsRemote(); + if( (bIsRemote && !officecfg::Office::Common::Save::URL::Internet::get()) + || (!pImpl->m_bRemote && !officecfg::Office::Common::Save::URL::FileSystem::get()) ) + return OUString(); + } + + 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 ( GetError() ) + 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 ( GetError() == ERRCODE_NONE ) + { + // does something only in case there is a temporary file ( means aName points to different location than aLogicName ) + Transfer_Impl(); + } + + bool bResult = ( GetError() == 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 = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_PREVIEW, false); + if ( pPreview ) + bPreview = pPreview->GetValue(); + else + { + const SfxStringItem* pFlags = SfxItemSet::GetItem<SfxStringItem>(GetItemSet(), 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 ( GetError() ) + 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_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(); +} + + +void SfxMedium::SetEncryptionDataToStorage_Impl() +{ + // in case media-descriptor contains password it should be used on opening + if ( !pImpl->xStorage.is() || !pImpl->m_pSet ) + return; + + uno::Sequence< beans::NamedValue > aEncryptionData; + if ( !GetEncryptionData_Impl( pImpl->m_pSet.get(), aEncryptionData ) ) + return; + + // 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" ); + // TODO/LATER: set the error code in case of problem + // SetError(ERRCODE_IO_GENERAL); + } +} + +#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; + } +} + +#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 = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), 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 ); + 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 && GetError() == 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 = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), 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 (GetError() != 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 && GetError() == 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 +} + + +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 ( GetError() ) + return pImpl->xStorage; + + const SfxBoolItem* pRepairItem = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), 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 = SfxItemSet::GetItem<SfxUnoAnyItem>(GetItemSet(), 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 + } + + if( ( pImpl->nLastStorageError = GetError() ) != ERRCODE_NONE ) + { + pImpl->xStorage = nullptr; + if ( pImpl->m_pInStream ) + pImpl->m_pInStream->Seek(0); + return uno::Reference< embed::XStorage >(); + } + + pImpl->m_bTriedStorage = true; + + // TODO/LATER: Get versionlist on demand + if ( pImpl->xStorage.is() ) + { + SetEncryptionDataToStorage_Impl(); + 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 + ::utl::TempFile aTempFile; + const OUString& aTmpName = aTempFile.GetURL(); + 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(); + if ( pImpl->m_pInStream ) + pImpl->m_pInStream->Seek( 0 ); + } + + pImpl->bIsStorage = pImpl->xStorage.is(); + return pImpl->xStorage; +} + + +uno::Reference< embed::XStorage > const & SfxMedium::GetZipStorageToSign_Impl( bool bReadOnly ) +{ + if ( !GetError() && !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 ( GetError() ) // 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(); + } +} + +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->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 ( !GetError() ) + { + 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 (!GetError()) + 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 = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), 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 ) + { + UseBackupToRestore_Impl( aOriginalContent, xDummyEnv ); + } + } + else + pImpl->m_eError = ERRCODE_IO_CANTREAD; +} + + +bool SfxMedium::TryDirectTransfer( const OUString& aURL, SfxItemSet const & aTargetSet ) +{ + if ( GetError() ) + 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<SfxStringItem>(SID_PASSWORD, false); + const SfxStringItem* pOldPassItem = SfxItemSet::GetItem<SfxStringItem>(GetItemSet(), SID_PASSWORD, false); + if ( ( !pNewPassItem && !pOldPassItem ) + || ( pNewPassItem && pOldPassItem && pNewPassItem->GetValue() == pOldPassItem->GetValue() ) ) + { + // the filter must be the same + const SfxStringItem* pNewFilterItem = aTargetSet.GetItem<SfxStringItem>(SID_FILTER_NAME, false); + const SfxStringItem* pOldFilterItem = SfxItemSet::GetItem<SfxStringItem>(GetItemSet(), 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; + Any aAny = aDestContent.getPropertyValue("Title"); + aAny >>= aFileName; + aAny = aDestContent.getPropertyValue( "ObjectId" ); + OUString sObjectId; + aAny >>= sObjectId; + 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 = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), 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 = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), SID_DOCINFO_MAJOR, false); + bMajor = pMajor && pMajor->GetValue( ); + const SfxStringItem* pComments = SfxItemSet::GetItem<SfxStringItem>(GetItemSet(), 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, + const OUString& aExtension, + const OUString& aDestDir ) +{ + if ( !pImpl->m_aBackupURL.isEmpty() ) + return; // the backup was done already + + ::utl::TempFile 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() +{ + // 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; + + // get path for backups + OUString 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() ); + aDest.setExtension( u"bak" ); + 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 ) + { + 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 ( !GetError() && !pImpl->xStream.is() && !pImpl->xInputStream.is() ) + SetError(ERRCODE_IO_ACCESSDENIED); + + if ( !GetError() && !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(); + ErrCode nError = GetError(); + pImpl->aDoneLink.Call( reinterpret_cast<void*>(sal_uInt32(nError)) ); +} + +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(); + } +} + + +void SfxMedium::Init_Impl() +/* [Description] + Includes a valid:: sun:: com:: star:: util:: URL (If a file name was + previously in there) in the logical name and if available sets the + physical name as the file name. + */ + +{ + 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 && !pSalvageItem->GetValue().isEmpty() ) + { + 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; + + ::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& ) + { + try + { + // erase the empty or corrupt file + aLockFile.RemoveFileDirectly(); + } + catch( const uno::Exception& ) + {} + } + catch( const uno::Exception& ) + {} + + if(!pImpl->m_bMSOLockFileCreated) + return; + + ::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& ) + { + try + { + // erase the empty or corrupt file + aMSOLockFile.RemoveFileDirectly(); + } + catch( const uno::Exception& ) + {} + } + 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::TempFile> pTmpFile; + if ( pImpl->pTempFile ) + { + pTmpFile = std::move(pImpl->pTempFile); + pImpl->m_aName.clear(); + } + + GetMedium_Impl(); + + if ( GetError() ) + { + 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 >& rStor ) +{ + pImpl->xStorage = rStor; +} + + +SfxItemSet* SfxMedium::GetItemSet() const +{ + // this method *must* return an ItemSet, returning NULL can cause crashes + if (!pImpl->m_pSet) + pImpl->m_pSet = std::make_shared<SfxAllItemSet>( SfxGetpApp()->GetPool() ); + return pImpl->m_pSet.get(); +} + + +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 = SfxItemSet::GetItem<SfxBoolItem>(GetItemSet(), 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::TempFile(&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::TempFile(&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() || GetError()) + { + 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& ) + { + SAL_WARN( "sfx.doc", "Couldn't use signing functionality!" ); + } + + CloseAndRelease(); + + ResetError(); + + return bChanges; +} + +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() || GetError()) + { + 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 + { + 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 ); + + if ( xSigner->signScriptingContent( GetZipStorageToSign_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(); + + // 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& ) + { + SAL_WARN( "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( '.' ); + OUString aExt = ( nPrefixLen == std::u16string_view::npos ) ? OUString() : OUString(aURL.substr( nPrefixLen )); + + OUString aNewTempFileURL = ::utl::TempFile( u"", true, &aExt ).GetURL(); + 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( '.' ); + OUString const aExt = (nPrefixLen == -1) + ? OUString() + : aOrigURL.copy(nPrefixLen); + OUString aNewURL = ::utl::TempFile( u"", true, &aExt ).GetURL(); + + // 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 000000000..7a23a2842 --- /dev/null +++ b/sfx2/source/doc/docfilt.cxx @@ -0,0 +1,199 @@ +/* -*- 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> + +using namespace ::com::sun::star; + +SfxFilter::SfxFilter( const OUString& rProvider, const OUString &rFilterName ) : + maFilterName(rFilterName), + maProvider(rProvider), + nFormatType(SfxFilterFlags::NONE), + nVersion(0), + lFormat(SotClipboardFormatId::NONE), + mbEnabled(true) +{ +} + +SfxFilter::SfxFilter( const OUString &rName, + std::u16string_view rWildCard, + SfxFilterFlags nType, + SotClipboardFormatId lFmt, + const OUString &rTypNm, + const OUString &rMimeType, + const OUString &rUsrDat, + const OUString &rServiceName, + bool bEnabled ): + aWildCard(rWildCard, ';'), + aTypeName(rTypNm), + aUserData(rUsrDat), + aServiceName(rServiceName), + aMimeType(rMimeType), + maFilterName(rName), + 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 000000000..949e1f19f --- /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 OUStringLiteral STREAM_SUMMARYINFO = u"\005SummaryInformation"; +constexpr OUStringLiteral STREAM_DOCSUMMARYINFO = u"\005DocumentSummaryInformation"; + +// 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 000000000..d8fe42621 --- /dev/null +++ b/sfx2/source/doc/docinsert.cxx @@ -0,0 +1,292 @@ +/* -*- 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 <tools/diagnose_ex.h> + +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, const OUString& rFactory, const Mode mode) + : m_pParent ( pParent ) + , m_sDocFactory ( rFactory ) + , 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 000000000..3a8684113 --- /dev/null +++ b/sfx2/source/doc/docmacromode.cxx @@ -0,0 +1,431 @@ +/* -*- 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 <tools/diagnose_ex.h> +#include <tools/urlobj.hxx> + + +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::task::ErrorCodeRequest; + 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_bMacroDisabledMessageShown; + bool m_bDocMacroDisabledMessageShown; + + explicit DocumentMacroMode_Data( IMacroDocumentAccess& rDocumentAccess ) + :m_rDocumentAccess( rDocumentAccess ) + ,m_bMacroDisabledMessageShown( false ) + ,m_bDocMacroDisabledMessageShown( false ) + { + } + }; + + + //= helper + + namespace + { + + void lcl_showGeneralSfxErrorOnce( const Reference< XInteractionHandler >& rxHandler, ErrCode nSfxErrorCode, bool& rbAlreadyShown ) + { + if ( rbAlreadyShown ) + return; + + ErrorCodeRequest aErrorCodeRequest; + aErrorCodeRequest.ErrCode = sal_uInt32(nSfxErrorCode); + + SfxMedium::CallApproveHandler( rxHandler, Any( aErrorCodeRequest ), false ); + rbAlreadyShown = true; + } + + + void lcl_showMacrosDisabledError( const Reference< XInteractionHandler >& rxHandler, bool& rbAlreadyShown ) + { + lcl_showGeneralSfxErrorOnce( rxHandler, ERRCODE_SFX_MACROS_SUPPORT_DISABLED, rbAlreadyShown ); + } + + + void lcl_showDocumentMacrosDisabledError( const Reference< XInteractionHandler >& rxHandler, bool& rbAlreadyShown ) + { +#ifdef MACOSX + lcl_showGeneralSfxErrorOnce( rxHandler, ERRCODE_SFX_DOCUMENT_MACRO_DISABLED_MAC, rbAlreadyShown ); +#else + lcl_showGeneralSfxErrorOnce( rxHandler, ERRCODE_SFX_DOCUMENT_MACRO_DISABLED, rbAlreadyShown ); +#endif + } + + void lcl_showMacrosDisabledUnsignedContentError( const Reference< XInteractionHandler >& rxHandler, bool& rbAlreadyShown ) + { + lcl_showGeneralSfxErrorOnce( rxHandler, ERRCODE_SFX_DOCUMENT_MACRO_DISABLED_CONTENT_UNSIGNED, rbAlreadyShown ); + } + + 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 ) + { + sal_uInt16 nMacroExecutionMode = m_xData->m_rDocumentAccess.getCurrentMacroExecMode(); + + if ( SvtSecurityOptions::IsMacroDisabled() ) + { + // no macro should be executed at all + lcl_showMacrosDisabledError( rxInteraction, m_xData->m_bMacroDisabledMessageShown ); + return disallowMacroExecution(); + } + + // get setting from configuration if required + enum AutoConfirmation + { + eNoAutoConfirm, + eAutoConfirmApprove, + eAutoConfirmReject + }; + AutoConfirmation eAutoConfirm( eNoAutoConfirm ); + + 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: + nMacroExecutionMode = MacroExecMode::FROM_LIST_NO_WARN; + break; + case 2: + nMacroExecutionMode = MacroExecMode::FROM_LIST_AND_SIGNED_WARN; + break; + case 1: + nMacroExecutionMode = MacroExecMode::ALWAYS_EXECUTE; + break; + case 0: + 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 false; + + if ( nMacroExecutionMode == MacroExecMode::ALWAYS_EXECUTE_NO_WARN ) + return true; + + 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( m_xData->m_rDocumentAccess.getDocumentLocation() ); + + OUString aLocation; + if ( aURLReferer.removeSegment() ) + 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 ) + { + lcl_showDocumentMacrosDisabledError( rxInteraction, m_xData->m_bDocMacroDisabledMessageShown ); + return disallowMacroExecution(); + } + + // check whether the document is signed with trusted certificate + if ( nMacroExecutionMode != MacroExecMode::FROM_LIST ) + { + // the trusted macro check will also retrieve the signature state ( small optimization ) + const bool bAllowUIToAddAuthor = nMacroExecutionMode != MacroExecMode::FROM_LIST_AND_SIGNED_NO_WARN + && (nMacroExecutionMode == MacroExecMode::ALWAYS_EXECUTE + || !SvtSecurityOptions::IsReadOnly(SvtSecurityOptions::EOption::MacroTrustedAuthors)); + const bool bHasTrustedMacroSignature = m_xData->m_rDocumentAccess.hasTrustedScriptingSignature(bAllowUIToAddAuthor); + + SignatureState nSignatureState = m_xData->m_rDocumentAccess.getScriptingSignatureState(); + if ( nSignatureState == SignatureState::BROKEN ) + { + if (!bAllowUIToAddAuthor) + lcl_showDocumentMacrosDisabledError(rxInteraction, m_xData->m_bDocMacroDisabledMessageShown); + return disallowMacroExecution(); + } + else if ( m_xData->m_rDocumentAccess.macroCallsSeenWhileLoading() && + bHasTrustedMacroSignature && + !bHasValidContentSignature) + { + // When macros are signed, and the document has events which call macros, the document content needs to be signed too. + lcl_showMacrosDisabledUnsignedContentError(rxInteraction, m_xData->m_bDocMacroDisabledMessageShown); + return disallowMacroExecution(); + } + else 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 (!bAllowUIToAddAuthor) + lcl_showDocumentMacrosDisabledError(rxInteraction, m_xData->m_bDocMacroDisabledMessageShown); + return disallowMacroExecution(); + } + } + + // 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 ) + ) + { + if ( nMacroExecutionMode == MacroExecMode::FROM_LIST_AND_SIGNED_WARN ) + lcl_showDocumentMacrosDisabledError( rxInteraction, m_xData->m_bDocMacroDisabledMessageShown ); + + return disallowMacroExecution(); + } + } + catch ( const Exception& ) + { + if ( ( nMacroExecutionMode == MacroExecMode::FROM_LIST_NO_WARN ) + || ( nMacroExecutionMode == MacroExecMode::FROM_LIST_AND_SIGNED_WARN ) + || ( nMacroExecutionMode == MacroExecMode::FROM_LIST_AND_SIGNED_NO_WARN ) + ) + { + return disallowMacroExecution(); + } + } + + // confirmation is required + bool bSecure = false; + + if ( eAutoConfirm == eNoAutoConfirm ) + { + OUString sReferrer( m_xData->m_rDocumentAccess.getDocumentLocation() ); + + OUString aSystemFileURL; + if ( osl::FileBase::getSystemPathFromFileURL( sReferrer, aSystemFileURL ) == osl::FileBase::E_None ) + sReferrer = aSystemFileURL; + + 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 const OUStringLiteral aStdLibName( u"Standard" ); + static const 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::storageHasMacros( const Reference< XStorage >& rxStorage ) + { + bool bHasMacros = false; + if ( rxStorage.is() ) + { + try + { + static constexpr OUStringLiteral s_sBasicStorageName( u"Basic" ); + static constexpr OUStringLiteral s_sScriptsStorageName( u"Scripts" ); + + 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::checkMacrosOnLoading( const Reference< XInteractionHandler >& rxInteraction, bool bHasValidContentSignature ) + { + bool bAllow = false; + if ( SvtSecurityOptions::IsMacroDisabled() ) + { + // no macro should be executed at all + bAllow = disallowMacroExecution(); + } + else + { + if (m_xData->m_rDocumentAccess.documentStorageHasMacros() || hasMacroLibrary() || m_xData->m_rDocumentAccess.macroCallsSeenWhileLoading()) + { + 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 000000000..5c68c2bb8 --- /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 000000000..ebf2ddce4 --- /dev/null +++ b/sfx2/source/doc/doctempl.cxx @@ -0,0 +1,1745 @@ +/* -*- 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 <string_view> + +#include <com/sun/star/uno/Any.h> +#include <osl/mutex.hxx> +#include <sal/log.hxx> + +#include <unotools/pathoptions.hxx> +#include <tools/urlobj.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#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> + +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; + + +#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 <vector> + + +constexpr OUStringLiteral TITLE = u"Title"; +constexpr OUStringLiteral TARGET_URL = u"TargetURL"; + +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, + const OUString& rTitle ); + + 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; + + ::osl::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( Content &rTemplRoot ); + void ReInitFromComponent(); + void AddRegion( 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 rtl::OUStringConstExpr 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, + }; + + 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, + }; + + 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, + const OUString& rTitle ) + : mpParent(pParent), maTitle(rTitle) +{ +} + + +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() +{ + ::osl::MutexGuard aGuard( maMutex ); + mnLockCounter++; +} + + +void SfxDocTemplate_Impl::DecrementLock() +{ + ::osl::MutexGuard 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( 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( 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( xRow->getString( 1 ), aContent ); + } + } + catch ( Exception& ) {} +} + + +bool SfxDocTemplate_Impl::Construct( ) +{ + ::osl::MutexGuard 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( 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(); + CreateFromHierarchy( aTemplRoot ); + } +} + + +bool SfxDocTemplate_Impl::InsertRegion( std::unique_ptr<RegionData_Impl> pNew, size_t nPos ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + // 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() ); + CreateFromHierarchy( 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() +{ + ::osl::MutexGuard 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 000000000..6005aa5fd --- /dev/null +++ b/sfx2/source/doc/doctemplates.cxx @@ -0,0 +1,2734 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <osl/mutex.hxx> +#include <tools/diagnose_ex.h> +#include <tools/urlobj.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <vcl/svapp.hxx> +#include <vcl/wrkwin.hxx> +#include <unotools/pathoptions.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/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 OUStringLiteral TITLE = u"Title"; +constexpr OUStringLiteral IS_FOLDER = u"IsFolder"; +constexpr OUStringLiteral IS_DOCUMENT = u"IsDocument"; +constexpr OUStringLiteral TARGET_URL = u"TargetURL"; +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 OUStringLiteral TYPE_FSYS_FOLDER = u"application/vnd.sun.staroffice.fsys-folder"; +constexpr OUStringLiteral TYPE_FSYS_FILE = u"application/vnd.sun.staroffice.fsys-file"; + +constexpr OUStringLiteral PROPERTY_DIRLIST = u"DirectoryList"; +constexpr OUStringLiteral PROPERTY_NEEDSUPDATE = u"NeedsUpdate"; +constexpr OUStringLiteral PROPERTY_TYPE = u"TypeDescription"; + +constexpr OUStringLiteral TARGET_DIR_URL = u"TargetDirURL"; +constexpr OUStringLiteral COMMAND_DELETE = u"delete"; + +constexpr OUStringLiteral STANDARD_FOLDER = u"standard"; + +#define C_DELIM ';' + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::document; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::util; + +using namespace ::ucbhelper; +using namespace ::comphelper; + +using ::std::vector; + +namespace { + +class WaitWindow_Impl : public WorkWindow +{ + tools::Rectangle maRect; + OUString maText; + 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( const uno::Reference< task::XInteractionHandler>& rxInteractionHandler ) + : m_xInteractionHandler( rxInteractionHandler ) + {} + + virtual uno::Reference<task::XInteractionHandler> SAL_CALL getInteractionHandler() override + { return m_xInteractionHandler; } + + virtual uno::Reference<ucb::XProgressHandler> SAL_CALL getProgressHandler() override + { return uno::Reference<ucb::XProgressHandler>(); } +}; + +class SfxDocTplService_Impl +{ + uno::Reference< XComponentContext > mxContext; + uno::Reference< XCommandEnvironment > maCmdEnv; + uno::Reference< XDocumentProperties> m_xDocProps; + uno::Reference< XTypeDetection > mxType; + + ::osl::Mutex maMutex; + Sequence< OUString > maTemplateDirs; + Sequence< OUString > maInternalTemplateDirs; + OUString maRootURL; + std::vector< 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, + const OUString& 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; +public: + explicit SfxDocTplService_Impl( const uno::Reference< XComponentContext > & xContext ); + ~SfxDocTplService_Impl(); + + bool init() { if ( !mbIsInitialized ) init_Impl(); return mbIsInitialized; } + const Content& getContent() const { return maRootContent; } + + void setLocale( const lang::Locale & rLocale ); + lang::Locale getLocale(); + + bool storeTemplate( const OUString& rGroupName, + const OUString& rTemplateName, + const uno::Reference< frame::XStorable >& rStorable ); + + bool addTemplate( const OUString& rGroupName, + const OUString& rTemplateName, + const OUString& rSourceURL ); + bool removeTemplate( std::u16string_view rGroupName, + std::u16string_view rTemplateName ); + bool renameTemplate( std::u16string_view rGroupName, + std::u16string_view rOldName, + const OUString& rNewName ); + + bool addGroup( const OUString& rGroupName ); + bool removeGroup( std::u16string_view rGroupName ); + bool renameGroup( std::u16string_view rOldName, + const OUString& rNewName ); + + void update(); + void doUpdate(); +}; + + +class DocTemplates_EntryData_Impl +{ + OUString maTitle; + OUString maType; + OUString maTargetURL; + OUString maHierarchyURL; + + bool mbInHierarchy : 1; + bool mbInUse : 1; + bool mbUpdateType : 1; + bool mbUpdateLink : 1; + +public: + explicit DocTemplates_EntryData_Impl( const OUString& rTitle ); + + void setInUse() { mbInUse = true; } + void setHierarchy( bool bInHierarchy ) { mbInHierarchy = bInHierarchy; } + void setUpdateLink( bool bUpdateLink ) { mbUpdateLink = bUpdateLink; } + void setUpdateType( bool bUpdateType ) { mbUpdateType = bUpdateType; } + + bool getInUse() const { return mbInUse; } + bool getInHierarchy() const { return mbInHierarchy; } + bool getUpdateLink() const { return mbUpdateLink; } + bool getUpdateType() const { return mbUpdateType; } + + const OUString& getHierarchyURL() const { return maHierarchyURL; } + const OUString& getTargetURL() const { return maTargetURL; } + const OUString& getTitle() const { return maTitle; } + const OUString& getType() const { return maType; } + + void setHierarchyURL( const OUString& rURL ) { maHierarchyURL = rURL; } + void setTargetURL( const OUString& rURL ) { maTargetURL = rURL; } + void setType( const OUString& rType ) { maType = rType; } +}; + + +class GroupData_Impl +{ + std::vector< std::unique_ptr<DocTemplates_EntryData_Impl> > maEntries; + OUString maTitle; + OUString maHierarchyURL; + OUString maTargetURL; + bool mbInUse : 1; + bool mbInHierarchy : 1; + +public: + explicit GroupData_Impl( const OUString& rTitle ); + + void setInUse() { mbInUse = true; } + void setHierarchy( bool bInHierarchy ) { mbInHierarchy = bInHierarchy; } + void setHierarchyURL( const OUString& rURL ) { maHierarchyURL = rURL; } + void setTargetURL( const OUString& rURL ) { maTargetURL = rURL; } + + bool getInUse() const { return mbInUse; } + bool getInHierarchy() const { return mbInHierarchy; } + const OUString& getHierarchyURL() const { return maHierarchyURL; } + const OUString& getTargetURL() const { return maTargetURL; } + const OUString& getTitle() const { return maTitle; } + + DocTemplates_EntryData_Impl* addEntry( const OUString& rTitle, + const OUString& rTargetURL, + const OUString& rType, + const OUString& rHierURL ); + size_t count() { return maEntries.size(); } + DocTemplates_EntryData_Impl* getEntry( size_t nPos ) { return maEntries[ nPos ].get(); } +}; + + +// private SfxDocTplService_Impl + +void SfxDocTplService_Impl::init_Impl() +{ + uno::Reference< uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + uno::Reference < task::XInteractionHandler > xInteractionHandler( + task::InteractionHandler::createWithParent(xContext, nullptr), uno::UNO_QUERY_THROW ); + maCmdEnv = new TplTaskEnvironment( xInteractionHandler ); + + ::osl::ClearableMutexGuard aGuard( maMutex ); + bool bIsInitialized = false; + bool bNeedsUpdate = false; + + if ( !mbLocaleSet ) + getDefaultLocale(); + + // convert locale to string + // set maRootContent to the root of the templates hierarchy. Create the + // entry if necessary + + maRootURL = TEMPLATE_ROOT_URL + "/" + LanguageTag::convertToBcp47(maLocale); + + const OUString aTemplVersPropName( TEMPLATE_VERSION ); + const OUString aTemplVers( TEMPLATE_VERSION_VALUE ); + if ( Content::create( maRootURL, maCmdEnv, comphelper::getProcessComponentContext(), maRootContent ) ) + { + uno::Any aValue; + OUString aPropValue; + if ( getProperty( maRootContent, aTemplVersPropName, aValue ) + && ( aValue >>= aPropValue ) + && aPropValue == aTemplVers ) + { + bIsInitialized = true; + } + else + removeContent( maRootContent ); + } + + if ( !bIsInitialized ) + { + if ( createFolder( maRootURL, true, false, maRootContent ) + && setProperty( maRootContent, aTemplVersPropName, uno::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); + update(); + } + SolarMutexGuard aSecondSolarGuard; + + pWin.disposeAndClear(); + } + else if ( needsUpdate() ) + // the UI should be shown only on the first update + update(); + } + else + { + SAL_WARN( "sfx.doc", "init_Impl(): Could not create root" ); + } + + mbIsInitialized = bIsInitialized; +} + + +void SfxDocTplService_Impl::getDefaultLocale() +{ + if ( !mbLocaleSet ) + { + ::osl::MutexGuard aGuard( maMutex ); + if ( !mbLocaleSet ) + { + maLocale = LanguageTag::convertToLocale( utl::ConfigManager::getUILocale(), false); + mbLocaleSet = true; + } + } +} + +const char* TEMPLATE_SHORT_NAMES_ARY[] = +{ + "standard", + "styles", + "officorr", + "offimisc", + "personal", + "forms", + "finance", + "educate", + "layout", + "presnt", + "misc", + "labels" +}; + +void SfxDocTplService_Impl::readFolderList() +{ + SolarMutexGuard aGuard; + + static_assert( SAL_N_ELEMENTS(TEMPLATE_SHORT_NAMES_ARY) == SAL_N_ELEMENTS(TEMPLATE_LONG_NAMES_ARY), "mismatch array lengths" ); + const size_t nCount = std::min(SAL_N_ELEMENTS(TEMPLATE_SHORT_NAMES_ARY), SAL_N_ELEMENTS(TEMPLATE_LONG_NAMES_ARY)); + for (size_t i = 0; i < nCount; ++i) + { + 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_Impl::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_Impl::getDirList() +{ + Any aValue; + + // Get the template dir list + // TODO/LATER: let use service, register listener + INetURLObject aURL; + OUString aDirs = SvtPathOptions().GetTemplatePath(); + sal_Int32 nCount = comphelper::string::getTokenCount(aDirs, C_DELIM); + + maTemplateDirs = Sequence< OUString >( nCount ); + + uno::Reference< util::XMacroExpander > xExpander = util::theMacroExpander::get(mxContext); + static const OUStringLiteral aPrefix( + u"vnd.sun.star.expand:" ); + + 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.is() ) + { + const sal_Int32 nIndex{ rTemplateDir.indexOf( aPrefix ) }; + if (nIndex<0) + continue; + + rTemplateDir = rTemplateDir.replaceAt(nIndex, aPrefix.getLength(), u""); + 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_Impl::needsUpdate() +{ + bool bNeedsUpdate = true; + Any aValue; + + // Get the template dir list + bool bHasProperty = getProperty( maRootContent, PROPERTY_NEEDSUPDATE, aValue ); + + if ( bHasProperty ) + aValue >>= bNeedsUpdate; + + // the old template component also checks this state, but it is initialized from this component + // so if this component was already updated the old component does not need such an update + ::svt::TemplateFolderCache aTempCache; + if ( !bNeedsUpdate ) + bNeedsUpdate = aTempCache.needsUpdate(); + + if ( bNeedsUpdate ) + aTempCache.storeState(); + + return bNeedsUpdate; +} + + +bool SfxDocTplService_Impl::setTitleForURL( const OUString& rURL, const OUString& aTitle ) +{ + if (m_xDocProps.is()) + { + try + { + m_xDocProps->loadFromMedium(rURL, Sequence<PropertyValue>()); + m_xDocProps->setTitle(aTitle); + + uno::Reference< embed::XStorage > xStorage = ::comphelper::OStorageHelper::GetStorageFromURL( + rURL, embed::ElementModes::READWRITE); + + uno::Sequence<beans::PropertyValue> medium( comphelper::InitPropertySequence({ + { "DocumentBaseURL", Any(rURL) }, + { "URL", Any(rURL) } + })); + + m_xDocProps->storeToStorage(xStorage, medium); + return true; + } + catch ( Exception& ) + { + } + } + return false; +} + + +void SfxDocTplService_Impl::getTitleFromURL( const OUString& rURL, OUString& aTitle, OUString& aType, bool& bDocHasTitle ) +{ + bDocHasTitle = false; + + if (m_xDocProps.is()) + { + try + { + m_xDocProps->loadFromMedium(rURL, Sequence<PropertyValue>()); + aTitle = m_xDocProps->getTitle(); + } + catch ( Exception& ) + { + } + } + + if ( aType.isEmpty() && mxType.is() ) + { + const OUString aDocType {mxType->queryTypeByURL( rURL )}; + if ( !aDocType.isEmpty() ) + try + { + uno::Reference< container::XNameAccess > xTypeDetection( mxType, uno::UNO_QUERY_THROW ); + SequenceAsHashMap aTypeProps( xTypeDetection->getByName( aDocType ) ); + aType = aTypeProps.getUnpackedValueOrDefault( + "MediaType", + OUString() ); + } + catch( uno::Exception& ) + {} + } + + if ( aTitle.isEmpty() ) + { + INetURLObject aURL( rURL ); + aURL.CutExtension(); + aTitle = aURL.getName( INetURLObject::LAST_SEGMENT, true, + INetURLObject::DecodeMechanism::WithCharset ); + } + else + bDocHasTitle = true; +} + + +bool SfxDocTplService_Impl::addEntry( Content& rParentFolder, + const OUString& rTitle, + const OUString& rTargetURL, + const OUString& rType ) +{ + bool bAddedEntry = false; + + INetURLObject aLinkObj( rParentFolder.getURL() ); + aLinkObj.insertName( rTitle, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + const OUString aLinkURL {aLinkObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + Content aLink; + + if ( ! Content::create( aLinkURL, maCmdEnv, comphelper::getProcessComponentContext(), aLink ) ) + { + Sequence< 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_Impl::createFolder( const OUString& rNewFolderURL, + bool bCreateParent, + bool bFsysFolder, + Content &rNewFolder ) +{ + Content aParent; + bool bCreatedFolder = false; + INetURLObject aParentURL( rNewFolderURL ); + const OUString aFolderName {aParentURL.getName( INetURLObject::LAST_SEGMENT, true, + INetURLObject::DecodeMechanism::WithCharset )}; + + // compute the parent folder url from the new folder url + // and remove the final slash, because Content::create doesn't + // like it + aParentURL.removeSegment(); + if ( aParentURL.getSegmentCount() >= 1 ) + aParentURL.removeFinalSlash(); + + // if the parent exists, we can continue with the creation of the + // new folder, we have to create the parent otherwise ( as long as + // bCreateParent is set to true ) + if ( Content::create( aParentURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), maCmdEnv, comphelper::getProcessComponentContext(), aParent ) ) + { + try + { + Sequence< 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_Impl::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_Impl::CreateNewUniqueFileWithPrefix( std::u16string_view aPath, + const OUString& aPrefix, + const OUString& aExt ) +{ + OUString aNewFileURL; + INetURLObject aDirPath( aPath ); + + Content aParent; + + uno::Reference< XCommandEnvironment > aQuietEnv; + if ( Content::create( aDirPath.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aQuietEnv, comphelper::getProcessComponentContext(), aParent ) ) + { + for ( sal_Int32 nInd = 0; nInd < 32000; nInd++ ) + { + Content aNewFile; + bool bCreated = false; + OUString aTryName = aPrefix; + if ( nInd ) + aTryName += OUString::number( nInd ); + if ( aExt.toChar() != '.' ) + aTryName += "."; + aTryName += aExt; + + try + { + Sequence< 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_Impl::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_Impl::removeContent( const OUString& rContentURL ) +{ + Content aContent; + + if ( Content::create( rContentURL, maCmdEnv, comphelper::getProcessComponentContext(), aContent ) ) + return removeContent( aContent ); + return false; +} + + +bool SfxDocTplService_Impl::setProperty( Content& rContent, + const OUString& rPropName, + const Any& rPropValue ) +{ + bool bPropertySet = false; + + // Store the property + try + { + Any aPropValue( rPropValue ); + uno::Reference< XPropertySetInfo > aPropInfo = rContent.getProperties(); + + // check, whether or not the property exists, create it, when not + if ( !aPropInfo.is() || !aPropInfo->hasPropertyByName( rPropName ) ) + { + uno::Reference< XPropertyContainer > xProperties( rContent.get(), UNO_QUERY ); + if ( xProperties.is() ) + { + try + { + xProperties->addProperty( rPropName, PropertyAttribute::MAYBEVOID, rPropValue ); + } + catch( PropertyExistException& ) {} + catch( IllegalTypeException& ) { + 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_Impl::getProperty(Content& rContent, const OUString& rPropName, Any& rPropValue) +{ + bool bGotProperty = false; + + // Get the property + try + { + uno::Reference< XPropertySetInfo > aPropInfo = rContent.getProperties(); + + // check, whether or not the property exists + if ( !aPropInfo.is() || !aPropInfo->hasPropertyByName( rPropName ) ) + { + return false; + } + + // now get the property + + rPropValue = rContent.getPropertyValue( rPropName ); + + // To ensure a reloctable office installation, the path to the + // office 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_Impl::SfxDocTplService_Impl( const uno::Reference< XComponentContext > & xContext ) + : mxContext(xContext), mbIsInitialized(false), mbLocaleSet(false), maRelocator(xContext) +{ +} + + +SfxDocTplService_Impl::~SfxDocTplService_Impl() +{ + ::osl::MutexGuard aGuard( maMutex ); + maNames.clear(); +} + + +lang::Locale SfxDocTplService_Impl::getLocale() +{ + ::osl::MutexGuard aGuard( maMutex ); + + if ( !mbLocaleSet ) + getDefaultLocale(); + + return maLocale; +} + + +void SfxDocTplService_Impl::setLocale( const lang::Locale &rLocale ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + if ( mbLocaleSet && ( + ( maLocale.Language != rLocale.Language ) || + ( maLocale.Country != rLocale.Country ) || + ( maLocale.Variant != rLocale.Variant ) ) ) + mbIsInitialized = false; + + maLocale = rLocale; + mbLocaleSet = true; +} + + +void SfxDocTplService_Impl::update() +{ + ::osl::MutexGuard aGuard( maMutex ); + + doUpdate(); +} + + +void SfxDocTplService_Impl::doUpdate() +{ + ::osl::MutexGuard aGuard( maMutex ); + + const OUString aPropName( PROPERTY_NEEDSUPDATE ); + Any aValue; + + aValue <<= true; + setProperty( maRootContent, aPropName, aValue ); + + GroupList_Impl aGroupList; + + // get the entries from the hierarchy + createFromContent( aGroupList, maRootContent, true, false ); + + // get the entries from the template directories + sal_Int32 nCountDir = maTemplateDirs.getLength(); + 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_Impl::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_Impl::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_Impl::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_Impl::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_Impl::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_Impl::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; +} + + +bool SfxDocTplService_Impl::addGroup( const OUString& rGroupName ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + // Check, whether or not there is a group with this name + Content aNewGroup; + OUString aNewGroupURL; + INetURLObject aNewGroupObj( maRootURL ); + + aNewGroupObj.insertName( rGroupName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + + aNewGroupURL = aNewGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + if ( Content::create( aNewGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aNewGroup ) || + ! createFolder( aNewGroupURL, false, false, aNewGroup ) ) + { + // if there already was a group with this name or the new group + // could not be created, we return here + return false; + } + + // Get the user template path entry ( new group will always + // be added in the user template path ) + sal_Int32 nIndex; + OUString aUserPath; + + nIndex = maTemplateDirs.getLength(); + if ( nIndex ) + nIndex--; + else + return false; // We don't know where to add the group + + aUserPath = maTemplateDirs[ nIndex ]; + + // create a new folder with the given name + Content aNewFolder; + OUString aNewFolderName; + OUString aNewFolderURL; + + // the Fsys name instead of GroupName should be used, the groupuinames must be added also + if ( !CreateNewUniqueFolderWithPrefix( aUserPath, + rGroupName, + aNewFolderName, + aNewFolderURL, + aNewFolder ) + && !CreateNewUniqueFolderWithPrefix( aUserPath, + "UserGroup", + aNewFolderName, + aNewFolderURL, + aNewFolder ) ) + { + // we could not create the folder, so we delete the group in the + // hierarchy and return + removeContent( aNewGroup ); + return false; + } + + if ( !UpdateUINamesForTemplateDir_Impl( aUserPath, rGroupName, aNewFolderName ) ) + { + // we could not create the groupuinames for the folder, so we delete the group in the + // hierarchy, the folder and return + removeContent( aNewGroup ); + removeContent( aNewFolder ); + return false; + } + + // Now set the target url for this group and we are done + Any aValue( aNewFolderURL ); + + if ( ! setProperty( aNewGroup, TARGET_DIR_URL, aValue ) ) + { + removeContent( aNewGroup ); + removeContent( aNewFolder ); + return false; + } + + return true; +} + + +bool SfxDocTplService_Impl::removeGroup( std::u16string_view rGroupName ) +{ + // remove all the elements that have the prefix aTargetURL + // if the group does not have other elements remove it + + ::osl::MutexGuard aGuard( maMutex ); + + bool bResult = false; + + // create the group url + INetURLObject aGroupObj( maRootURL ); + aGroupObj.insertName( rGroupName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + + // Get the target url + Content aGroup; + const OUString aGroupURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + if ( Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + { + const OUString aPropName( TARGET_DIR_URL ); + Any aValue; + + OUString aGroupTargetURL; + if ( getProperty( aGroup, aPropName, aValue ) ) + aValue >>= aGroupTargetURL; + + if ( aGroupTargetURL.isEmpty() ) + return false; // nothing is allowed to be removed + + if ( !maTemplateDirs.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; +} + + +bool SfxDocTplService_Impl::renameGroup( std::u16string_view rOldName, + const OUString& rNewName ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + // create the group url + Content aGroup; + INetURLObject aGroupObj( maRootURL ); + aGroupObj.insertName( rNewName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + OUString aGroupURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + // Check, if there is a group with the new name, return false + // if there is one. + if ( Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + return false; + + aGroupObj.removeSegment(); + aGroupObj.insertName( rOldName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + aGroupURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + // When there is no group with the old name, we can't rename it + if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + return false; + + OUString aGroupTargetURL; + // there is no need to check whether target dir url is in target path, since if the target path is changed + // the target dir url should be already generated new + Any aValue; + if ( getProperty( aGroup, TARGET_DIR_URL, aValue ) ) + aValue >>= aGroupTargetURL; + + if ( aGroupTargetURL.isEmpty() ) + return false; + + if ( !maTemplateDirs.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; +} + + +bool SfxDocTplService_Impl::storeTemplate( const OUString& rGroupName, + const OUString& rTemplateName, + const uno::Reference< frame::XStorable >& rStorable ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + // Check, whether or not there is a group with this name + // Return false, if there is no group with the given name + Content aGroup, 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; + } +} + + +bool SfxDocTplService_Impl::addTemplate( const OUString& rGroupName, + const OUString& rTemplateName, + const OUString& rSourceURL ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + // Check, whether or not there is a group with this name + // Return false, if there is no group with the given name + Content aGroup, aTemplate, aTargetGroup; + INetURLObject aGroupObj( maRootURL ); + + aGroupObj.insertName( rGroupName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + const OUString aGroupURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + return false; + + // Check, if there's a template with the given name in this group + // Return false, if there already is a template + aGroupObj.insertName( rTemplateName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + const OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + if ( Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) ) + return false; + + // get the target url of the group + OUString aTargetURL; + Any aValue; + + if ( getProperty( aGroup, TARGET_DIR_URL, aValue ) ) + aValue >>= aTargetURL; + + if ( aTargetURL.isEmpty() ) + { + aTargetURL = CreateNewGroupFsys( rGroupName, aGroup ); + + if ( aTargetURL.isEmpty() ) + return false; + } + + // Get the content type + OUString aTitle, aType; + + bool bDocHasTitle = false; + getTitleFromURL( rSourceURL, aTitle, aType, bDocHasTitle ); + + INetURLObject aSourceObj( rSourceURL ); + if ( rTemplateName == aTitle ) + { + // addTemplate will sometimes be called just to add an entry in the + // hierarchy; the target URL and the source URL will be the same in + // this scenario + // TODO/LATER: get rid of this old hack + + INetURLObject aTargetObj( aTargetURL ); + + aTargetObj.insertName( rTemplateName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + aTargetObj.setExtension( aSourceObj.getExtension() ); + + const OUString aTargetURL2 = aTargetObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + if ( aTargetURL2 == rSourceURL ) + return addEntry( aGroup, rTemplateName, aTargetURL2, aType ); + } + + // copy the template into the new group (targeturl) + + INetURLObject aTmpURL( aSourceObj ); + aTmpURL.CutExtension(); + const OUString aPattern {aTmpURL.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset )}; + + const OUString aNewTemplateTargetURL {CreateNewUniqueFileWithPrefix( aTargetURL, aPattern, aSourceObj.getExtension() )}; + INetURLObject aNewTemplateTargetObj( aNewTemplateTargetURL ); + const OUString aNewTemplateTargetName {aNewTemplateTargetObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset )}; + if ( aNewTemplateTargetURL.isEmpty() || aNewTemplateTargetName.isEmpty() ) + return false; + + // get access to source file + Content aSourceContent; + uno::Reference < ucb::XCommandEnvironment > xEnv; + INetURLObject aSourceURL( rSourceURL ); + if( ! Content::create( aSourceURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xEnv, comphelper::getProcessComponentContext(), aSourceContent ) ) + return false; + + if( ! Content::create( aTargetURL, xEnv, comphelper::getProcessComponentContext(), aTargetGroup ) ) + return false; + + // transfer source file + try + { + aTargetGroup.transferContent( aSourceContent, + InsertOperation::Copy, + aNewTemplateTargetName, + NameClash::OVERWRITE, + aType ); + + // allow to edit the added template + Content aResultContent; + if ( Content::create( aNewTemplateTargetURL, xEnv, comphelper::getProcessComponentContext(), aResultContent ) ) + { + static const OUStringLiteral aPropertyName( u"IsReadOnly" ); + 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_Impl::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_Impl::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(); +} + +bool SfxDocTplService_Impl::removeTemplate( std::u16string_view rGroupName, + std::u16string_view rTemplateName ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + // Check, whether or not there is a group with this name + // Return false, if there is no group with the given name + Content aGroup, aTemplate; + INetURLObject aGroupObj( maRootURL ); + + aGroupObj.insertName( rGroupName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + const OUString aGroupURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + return false; + + // Check, if there's a template with the given name in this group + // Return false, if there is no template + aGroupObj.insertName( rTemplateName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + const OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + if ( !Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) ) + return false; + + // get the target URL from the template + OUString aTargetURL; + Any aValue; + + if ( getProperty( aTemplate, TARGET_URL, aValue ) ) + aValue >>= aTargetURL; + + // delete the target template + if ( !aTargetURL.isEmpty() ) + { + if (isInternalTemplateDir(aTargetURL)) + return false; + + removeContent( aTargetURL ); + } + + // delete the template entry + return removeContent( aTemplate ); +} + + +bool SfxDocTplService_Impl::renameTemplate( std::u16string_view rGroupName, + std::u16string_view rOldName, + const OUString& rNewName ) +{ + ::osl::MutexGuard aGuard( maMutex ); + + // Check, whether or not there is a group with this name + // Return false, if there is no group with the given name + Content aGroup, aTemplate; + INetURLObject aGroupObj( maRootURL ); + + aGroupObj.insertName( rGroupName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + const OUString aGroupURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + if ( ! Content::create( aGroupURL, maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + return false; + + // Check, if there's a template with the new name in this group + // Return false, if there is one + aGroupObj.insertName( rNewName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + if ( Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) ) + return false; + + // Check, if there's a template with the old name in this group + // Return false, if there is no template + aGroupObj.removeSegment(); + aGroupObj.insertName( rOldName, false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + aTemplateURL = aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + if ( !Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) ) + return false; + + OUString aTemplateTargetURL; + Any aTargetValue; + + if ( getProperty( aTemplate, TARGET_URL, aTargetValue ) ) + aTargetValue >>= aTemplateTargetURL; + + if ( !setTitleForURL( aTemplateTargetURL, rNewName ) ) + return false; + + // rename the template entry in the cache + Any aTitleValue; + aTitleValue <<= rNewName; + + return setProperty( aTemplate, TITLE, aTitleValue ); +} + + +class SfxDocTplService: public ::cppu::WeakImplHelper< css::lang::XLocalizable, css::frame::XDocumentTemplates, css::lang::XServiceInfo > +{ + std::unique_ptr<SfxDocTplService_Impl> pImp; + +public: + explicit SfxDocTplService( const css::uno::Reference < uno::XComponentContext >& xContext ); + + virtual OUString SAL_CALL getImplementationName() override + { + return "com.sun.star.comp.sfx2.DocumentTemplates"; + } + + virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override + { + return cppu::supportsService(this, ServiceName); + } + + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override + { + css::uno::Sequence< OUString > aSeq { "com.sun.star.frame.DocumentTemplates" }; + return aSeq; + } + + + // --- XLocalizable --- + void SAL_CALL setLocale( const css::lang::Locale & eLocale ) override; + css::lang::Locale SAL_CALL getLocale() override; + + // --- XDocumentTemplates --- + css::uno::Reference< css::ucb::XContent > SAL_CALL getContent() override; + sal_Bool SAL_CALL storeTemplate( const OUString& GroupName, + const OUString& TemplateName, + const css::uno::Reference< css::frame::XStorable >& Storable ) override; + sal_Bool SAL_CALL addTemplate( const OUString& GroupName, + const OUString& TemplateName, + const OUString& SourceURL ) override; + sal_Bool SAL_CALL removeTemplate( const OUString& GroupName, + const OUString& TemplateName ) override; + sal_Bool SAL_CALL renameTemplate( const OUString& GroupName, + const OUString& OldTemplateName, + const OUString& NewTemplateName ) override; + sal_Bool SAL_CALL addGroup( const OUString& GroupName ) override; + sal_Bool SAL_CALL removeGroup( const OUString& GroupName ) override; + sal_Bool SAL_CALL renameGroup( const OUString& OldGroupName, + const OUString& NewGroupName ) override; + void SAL_CALL update() override; +}; + + +SfxDocTplService::SfxDocTplService( const uno::Reference< XComponentContext >& xContext ) +{ + pImp.reset( new SfxDocTplService_Impl(xContext) ); +} + + + +//--- XLocalizable --- + + +lang::Locale SAL_CALL SfxDocTplService::getLocale() +{ + return pImp->getLocale(); +} + + +void SAL_CALL SfxDocTplService::setLocale( const lang::Locale & rLocale ) +{ + pImp->setLocale( rLocale ); +} + + +//--- XDocumentTemplates --- + +uno::Reference< ucb::XContent > SAL_CALL SfxDocTplService::getContent() +{ + if ( pImp->init() ) + return pImp->getContent().get(); + return nullptr; +} + + +sal_Bool SAL_CALL SfxDocTplService::storeTemplate( const OUString& GroupName, + const OUString& TemplateName, + const uno::Reference< frame::XStorable >& Storable ) +{ + return pImp->init() && pImp->storeTemplate( GroupName, TemplateName, Storable ); +} + + +sal_Bool SAL_CALL SfxDocTplService::addTemplate( const OUString& rGroupName, + const OUString& rTemplateName, + const OUString& rSourceURL ) +{ + return pImp->init() && pImp->addTemplate( rGroupName, rTemplateName, rSourceURL ); +} + + +sal_Bool SAL_CALL SfxDocTplService::removeTemplate( const OUString& rGroupName, + const OUString& rTemplateName ) +{ + return pImp->init() && pImp->removeTemplate( rGroupName, rTemplateName ); +} + + +sal_Bool SAL_CALL SfxDocTplService::renameTemplate( const OUString& rGroupName, + const OUString& rOldName, + const OUString& rNewName ) +{ + if ( rOldName == rNewName ) + return true; + + return pImp->init() && pImp->renameTemplate( rGroupName, rOldName, rNewName ); +} + + +sal_Bool SAL_CALL SfxDocTplService::addGroup( const OUString& rGroupName ) +{ + return pImp->init() && pImp->addGroup( rGroupName ); +} + + +sal_Bool SAL_CALL SfxDocTplService::removeGroup( const OUString& rGroupName ) +{ + return pImp->init() && pImp->removeGroup( rGroupName ); +} + + +sal_Bool SAL_CALL SfxDocTplService::renameGroup( const OUString& rOldName, + const OUString& rNewName ) +{ + if ( rOldName == rNewName ) + return true; + + return pImp->init() && pImp->renameGroup( rOldName, rNewName ); +} + + +void SAL_CALL SfxDocTplService::update() +{ + if ( pImp->init() ) + pImp->update(); +} + +WaitWindow_Impl::WaitWindow_Impl() : WorkWindow(nullptr, WB_BORDER | WB_3DLOOK) +{ + tools::Rectangle aRect(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_Impl::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_Impl::addFsysGroup( GroupList_Impl& rList, + const OUString& rTitle, + const OUString& rUITitle, + const OUString& rOwnURL, + bool bWriteableGroup ) +{ + OUString aTitle; + + if ( rUITitle.isEmpty() ) + { + // reserved FS names that should not be used + if ( rTitle == "wizard" ) + return; + else if ( rTitle == "internal" ) + return; + + aTitle = getLongName( rTitle ); + } + else + aTitle = rUITitle; + + if ( aTitle.isEmpty() ) + return; + + GroupData_Impl* pGroup = nullptr; + for (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_Impl::createFromContent( GroupList_Impl& rList, + Content &rContent, + bool bHierarchy, + bool bWriteableContent ) +{ + const OUString aTargetURL {rContent.get()->getIdentifier()->getContentIdentifier()}; + + // when scanning the file system, we have to add the 'standard' group, too + if ( ! bHierarchy ) + { + const OUString aUIStdTitle {getLongName( STANDARD_FOLDER )}; + addFsysGroup( rList, OUString(), aUIStdTitle, aTargetURL, bWriteableContent ); + } + + // search for predefined UI names + INetURLObject aLayerObj( aTargetURL ); + + // TODO/LATER: Use hashmap in future + std::vector< beans::StringPair > aUINames; + if ( !bHierarchy ) + aUINames = ReadUINamesForTemplateDir_Impl( aLayerObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + + uno::Reference< XResultSet > xResultSet; + Sequence< OUString > aProps { TITLE }; + + try + { + xResultSet = rContent.createCursor( aProps, INCLUDE_FOLDERS_ONLY ); + } + catch ( Exception& ) {} + + if ( !xResultSet.is() ) + 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_Impl::removeFromHierarchy( DocTemplates_EntryData_Impl const *pData ) +{ + Content aTemplate; + + if ( Content::create( pData->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) ) + { + removeContent( aTemplate ); + } +} + + +void SfxDocTplService_Impl::addToHierarchy( GroupData_Impl const *pGroup, + DocTemplates_EntryData_Impl const *pData ) +{ + Content aGroup, aTemplate; + + if ( ! Content::create( pGroup->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + return; + + // Check, if there's a template with the given name in this group + // Return if there is already a template + INetURLObject aGroupObj( pGroup->getHierarchyURL() ); + + aGroupObj.insertName( pData->getTitle(), false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + + const OUString aTemplateURL {aGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + if ( Content::create( aTemplateURL, maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) ) + return; + + addEntry( aGroup, pData->getTitle(), + pData->getTargetURL(), + pData->getType() ); +} + + +void SfxDocTplService_Impl::updateData( DocTemplates_EntryData_Impl const *pData ) +{ + Content aTemplate; + + if ( ! Content::create( pData->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aTemplate ) ) + return; + + if ( pData->getUpdateType() ) + { + setProperty( aTemplate, PROPERTY_TYPE, Any( pData->getType() ) ); + } + + if ( pData->getUpdateLink() ) + { + setProperty( aTemplate, TARGET_URL, Any( pData->getTargetURL() ) ); + } +} + + +void SfxDocTplService_Impl::addGroupToHierarchy( GroupData_Impl *pGroup ) +{ + Content aGroup; + + INetURLObject aNewGroupObj( maRootURL ); + aNewGroupObj.insertName( pGroup->getTitle(), false, + INetURLObject::LAST_SEGMENT, + INetURLObject::EncodeMechanism::All ); + + const OUString aNewGroupURL {aNewGroupObj.GetMainURL( INetURLObject::DecodeMechanism::NONE )}; + + if ( createFolder( aNewGroupURL, false, false, aGroup ) ) + { + setProperty( aGroup, TARGET_DIR_URL, 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_Impl::removeFromHierarchy( GroupData_Impl const *pGroup ) +{ + Content aGroup; + + if ( Content::create( pGroup->getHierarchyURL(), maCmdEnv, comphelper::getProcessComponentContext(), aGroup ) ) + { + removeContent( aGroup ); + } +} + + +GroupData_Impl::GroupData_Impl( const OUString& rTitle ) + : maTitle(rTitle), mbInUse(false), mbInHierarchy(false) +{ +} + + +DocTemplates_EntryData_Impl* GroupData_Impl::addEntry( const OUString& rTitle, + const OUString& rTargetURL, + const OUString& rType, + const OUString& rHierURL ) +{ + DocTemplates_EntryData_Impl* pData = nullptr; + bool EntryFound = false; + + for (auto const & p : maEntries) + { + pData = p.get(); + if ( pData->getTitle() == rTitle ) + { + EntryFound = true; + break; + } + } + + if ( !EntryFound ) + { + pData = new DocTemplates_EntryData_Impl( rTitle ); + pData->setTargetURL( rTargetURL ); + pData->setType( rType ); + if ( !rHierURL.isEmpty() ) + { + pData->setHierarchyURL( rHierURL ); + pData->setHierarchy( true ); + } + maEntries.emplace_back( pData ); + } + else + { + if ( !rHierURL.isEmpty() ) + { + pData->setHierarchyURL( rHierURL ); + pData->setHierarchy( true ); + } + + if ( pData->getInHierarchy() ) + pData->setInUse(); + + if ( rTargetURL != pData->getTargetURL() ) + { + pData->setTargetURL( rTargetURL ); + pData->setUpdateLink( true ); + } + } + + return pData; +} + + +DocTemplates_EntryData_Impl::DocTemplates_EntryData_Impl( const OUString& rTitle ) + : maTitle(rTitle), 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( const uno::Reference< XComponentContext > & xContext ) +: mxContext( 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 000000000..5a55df72a --- /dev/null +++ b/sfx2/source/doc/doctemplateslocal.cxx @@ -0,0 +1,213 @@ +/* -*- 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 OUStringLiteral g_sGroupListElement(u"groupuinames:template-group-list"); +constexpr OUStringLiteral g_sGroupElement(u"groupuinames:template-group"); +constexpr OUStringLiteral g_sNameAttr(u"groupuinames:name"); +constexpr OUStringLiteral g_sUINameAttr(u"groupuinames:default-ui-name"); + +} + +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 const OUStringLiteral aCDATAString( u"CDATA" ); + static const OUStringLiteral aWhiteSpace( u" " ); + + // write the namespace + rtl::Reference<::comphelper::AttributeList> pRootAttrList = new ::comphelper::AttributeList; + pRootAttrList->AddAttribute( + "xmlns:groupuinames", + aCDATAString, + "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, aCDATAString, i.First ); + pAttrList->AddAttribute( g_sUINameAttr, aCDATAString, 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 000000000..466c847db --- /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 000000000..ab7438a7b --- /dev/null +++ b/sfx2/source/doc/docundomanager.cxx @@ -0,0 +1,423 @@ +/* -*- 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 <tools/diagnose_ex.h> +#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::RuntimeException; + 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 000000000..91dc9c273 --- /dev/null +++ b/sfx2/source/doc/exoticfileloadexception.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/. + */ + +#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 +{ + comphelper::OInteractionApprove* pBase + = static_cast<comphelper::OInteractionApprove*>(m_xApprove.get()); + return pBase->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 000000000..8204d6f55 --- /dev/null +++ b/sfx2/source/doc/exoticfileloadexception.hxx @@ -0,0 +1,43 @@ +/* -*- 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 <cppuhelper/implbase.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; + css::uno::Reference<css::task::XInteractionContinuation> m_xAbort; + css::uno::Reference<css::task::XInteractionContinuation> 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 000000000..43183986d --- /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 000000000..136e3ac9a --- /dev/null +++ b/sfx2/source/doc/graphhelp.cxx @@ -0,0 +1,260 @@ +/* -*- 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 ) + { + OUString const aStr(".emf"); + ::utl::TempFile aTempFile( u"", true, &aStr ); + + 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_Int32 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 000000000..1945661fc --- /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 000000000..23c75f296 --- /dev/null +++ b/sfx2/source/doc/guisaveas.cxx @@ -0,0 +1,1857 @@ +/* -*- 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/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 <tools/diagnose_ex.h> +#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 <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <o3tl/char16_t2wchar_t.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 <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 OUStringLiteral aFilterNameString = u"FilterName"; +constexpr OUStringLiteral aFilterOptionsString = u"FilterOptions"; +constexpr OUStringLiteral aFilterDataString = u"FilterData"; + +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; + + ::comphelper::SequenceAsHashMap m_aMediaDescrHM; + + bool m_bRecommendReadOnly; + +public: + ModelData_Impl( SfxStoringHelper& aOwner, + const 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 ); + + 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, + const uno::Reference< frame::XModel >& xModel, + const uno::Sequence< beans::PropertyValue >& aMediaDescr ) +: m_pOwner( &aOwner ) +, m_xModel( xModel ) +, m_aMediaDescrHM( aMediaDescr ) +, m_bRecommendReadOnly( false ) +{ + CheckInteractionHandler(); +} + + +ModelData_Impl::~ModelData_Impl() +{ + FreeDocumentProps(); + m_pDocumentPropsHM.reset(); + m_pModulePropsHM.reset(); +} + + +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() +{ + const OUString sInteractionHandler {"InteractionHandler"}; + ::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 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< ui::dialogs::XExecutableDialog > xFilterDialog( + comphelper::getProcessServiceFactory()->createInstanceWithArguments(aServiceName, aDialogArgs), uno::UNO_QUERY ); + uno::Reference< beans::XPropertyAccess > xFilterProperties( xFilterDialog, uno::UNO_QUERY ); + + if( xFilterDialog.is() && xFilterProperties.is() ) + { + bDialogUsed = true; + + uno::Reference< document::XExporter > xExporter( xFilterDialog, uno::UNO_QUERY ); + if( xExporter.is() ) + xExporter->setSourceDocument( GetModel() ); + + uno::Sequence< beans::PropertyValue > aPropsForDialog; + GetMediaDescr() >> aPropsForDialog; + xFilterProperties->setPropertyValues( aPropsForDialog ); + + 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; +} + + +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 const OUStringLiteral aVersionCommentString(u"VersionComment"); + static const OUStringLiteral aAuthorString(u"Author"); + static const OUStringLiteral aDontTerminateEdit(u"DontTerminateEdit"); + static const OUStringLiteral aInteractionHandlerString(u"InteractionHandler"); + static const OUStringLiteral aStatusIndicatorString(u"StatusIndicator"); + static const OUStringLiteral aFailOnWarningString(u"FailOnWarning"); + static const OUStringLiteral aNoFileSync(u"NoFileSync"); + + 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; + if ( pFileDlg->Execute( pDialogParams, aFilterName ) != ERRCODE_NONE ) + { + throw task::ErrorCodeIOException( + "ModelData_Impl::OutputFileDialog: ERRCODE_IO_ABORT", + uno::Reference< uno::XInterface >(), sal_uInt32(ERRCODE_IO_ABORT)); + } + + // 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() ); + // 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() ); + + const OUString sFilterOptionsString(aFilterOptionsString); + const OUString sFilterDataString(aFilterDataString); + + 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( sFilterOptionsString ); + if ( aIter != GetDocProps().end() + && GetMediaDescr().find( sFilterOptionsString ) == GetMediaDescr().end() ) + GetMediaDescr()[aIter->first] = aIter->second; + + aIter = GetDocProps().find( sFilterDataString ); + if ( aIter != GetDocProps().end() + && GetMediaDescr().find( sFilterDataString ) == GetMediaDescr().end() ) + GetMediaDescr()[aIter->first] = aIter->second; + } + } + else + { + GetMediaDescr().erase( sFilterDataString ); + GetMediaDescr().erase( sFilterOptionsString ); + + if ( aFilterName == aOldFilterName ) + { + // merge filter option of the document filter + + ::comphelper::SequenceAsHashMap::const_iterator aIter = + GetDocProps().find( sFilterOptionsString ); + if ( aIter != GetDocProps().end() ) + GetMediaDescr()[aIter->first] = aIter->second; + + aIter = GetDocProps().find( sFilterDataString ); + 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( sFilterDataString ) == GetMediaDescr().end() + && GetMediaDescr().find( sFilterOptionsString ) == 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::OUStringConcatenation("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() +{ +} + + +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 ) +{ + ModelData_Impl aModelData( *this, xModel, aArgsSequence ); + + bool bDialogUsed = false; + + INetURLObject aURL; + + bool bSetStandardName = false; // can be set only for SaveAs + + // parse the slot name + bool bRemote = false; + sal_Int16 nStoreMode = getStoreModeFromSlotName( aSlotName ); + + if ( nStoreMode == SAVEASREMOTE_REQUESTED ) + { + nStoreMode = SAVEAS_REQUESTED; + 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 ) + nStoreMode = EXPORT_REQUESTED | SAVEACOPY_REQUESTED | WIDEEXPORT_REQUESTED; + } + // handle the special cases + if ( 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 ) + nStoreMode = EXPORT_REQUESTED | WIDEEXPORT_REQUESTED; + } + + // if saving is not acceptable the warning must be shown even in case of SaveAs operation + if ( ( 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 ( 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 + nStoreMode = SAVEAS_REQUESTED; + if ( nStatusSave == STATUS_SAVEAS_STANDARDNAME ) + bSetStandardName = true; + } + } + + if (!comphelper::LibreOfficeKit::isActive() && !( nStoreMode & EXPORT_REQUESTED ) ) + { + // if it is no export, warn user that the signature will be removed + if ( 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 ( 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( 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 sFilterNameString(aFilterNameString); + + const OUString aFilterFromMediaDescr = aModelData.GetMediaDescr().getUnpackedValueOrDefault( sFilterNameString, OUString() ); + const OUString aOldFilterName = aModelData.GetDocProps().getUnpackedValueOrDefault( sFilterNameString, OUString() ); + + bool bUseFilterOptions = false; + ::comphelper::SequenceAsHashMap::const_iterator aFileNameIter = aModelData.GetMediaDescr().find( OUString("URL") ); + + const OUString sFilterOptionsString(aFilterOptionsString); + const OUString sFilterDataString(aFilterDataString); + + bool bPDFOptions = (nStoreMode & PDFEXPORT_REQUESTED) && !(nStoreMode & PDFDIRECTEXPORT_REQUESTED); + bool bEPUBOptions = (nStoreMode & EPUBEXPORT_REQUESTED) && !(nStoreMode & EPUBDIRECTEXPORT_REQUESTED); + if ( ( nStoreMode & EXPORT_REQUESTED ) && (bPDFOptions || bEPUBOptions) ) + { + // this is PDF or EPUB export, the filter options dialog should be shown before the export + aModelData.GetMediaDescr()[sFilterNameString] <<= aFilterName; + if ( aModelData.GetMediaDescr().find( "FilterFlags" ) == aModelData.GetMediaDescr().end() + && aModelData.GetMediaDescr().find( sFilterOptionsString ) == aModelData.GetMediaDescr().end() + && aModelData.GetMediaDescr().find( sFilterDataString ) == aModelData.GetMediaDescr().end() ) + { + // execute filter options dialog since no options are set in the media descriptor + if ( aModelData.ExecuteFilterDialog_Impl( aFilterName ) ) + bDialogUsed = true; + } + } + + 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 ) ) + { + 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 ); + + 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 ) + { + 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 ); + } + } + + 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 000000000..52962c4be --- /dev/null +++ b/sfx2/source/doc/iframe.cxx @@ -0,0 +1,444 @@ +/* -*- 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/XWindowPeer.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/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 <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(const css::uno::Reference < css::uno::XComponentContext>& rxContext, 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 + +const SfxItemPropertyMapEntry* lcl_GetIFramePropertyMap_Impl() +{ + static const SfxItemPropertyMapEntry aIFramePropertyMap_Impl[] = + { + { u"FrameIsAutoBorder", WID_FRAME_IS_AUTO_BORDER, cppu::UnoType<bool>::get(), PROPERTY_UNBOUND, 0 }, + { u"FrameIsAutoScroll", WID_FRAME_IS_AUTO_SCROLL, cppu::UnoType<bool>::get(), PROPERTY_UNBOUND, 0 }, + { u"FrameIsBorder", WID_FRAME_IS_BORDER, cppu::UnoType<bool>::get(), PROPERTY_UNBOUND, 0 }, + { u"FrameIsScrollingMode", WID_FRAME_IS_SCROLLING_MODE,cppu::UnoType<bool>::get(), PROPERTY_UNBOUND, 0 }, + { u"FrameMarginHeight", WID_FRAME_MARGIN_HEIGHT, cppu::UnoType<sal_Int32>::get(), PROPERTY_UNBOUND, 0 }, + { u"FrameMarginWidth", WID_FRAME_MARGIN_WIDTH, cppu::UnoType<sal_Int32>::get(), PROPERTY_UNBOUND, 0 }, + { u"FrameName", WID_FRAME_NAME, cppu::UnoType<OUString>::get(), PROPERTY_UNBOUND, 0 }, + { u"FrameURL", WID_FRAME_URL, cppu::UnoType<OUString>::get(), PROPERTY_UNBOUND, 0 }, + { u"", 0, css::uno::Type(), 0, 0 } + }; + return aIFramePropertyMap_Impl; +} + +IFrameObject::IFrameObject(const uno::Reference < uno::XComponentContext >& rxContext, const css::uno::Sequence< css::uno::Any >& aArguments) + : mxContext( rxContext ) + , 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.GetProtocol() == INetProtocol::Macro || aURLObject.isSchemeEqualTo(u"vnd.sun.star.script")) + return false; + + uno::Reference<frame::XFramesSupplier> xParentFrame = xFrame->getCreator(); + SfxObjectShell* pDoc = SfxMacroLoader::GetObjectShell(xParentFrame); + + 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::Sequence < beans::PropertyValue > aProps{ + comphelper::makePropertyValue("PluginMode", sal_Int16(2)), + comphelper::makePropertyValue("ReadOnly", true), + 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 000000000..a6922befc --- /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)); + ErrCode 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, OStringToOUString(m_xDialog->get_help_id(), RTL_TEXTENCODING_UTF8)); + 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, OStringToOUString(m_xDialog->get_help_id(), RTL_TEXTENCODING_UTF8)); + 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 000000000..8313cfc3d --- /dev/null +++ b/sfx2/source/doc/objcont.cxx @@ -0,0 +1,712 @@ +/* -*- 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 <tools/diagnose_ex.h> +#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; + SvtCTLOptions aCTLOptions; + if ( SvtCTLOptions::NUMERALS_HINDI == aCTLOptions.GetCTLTextNumerals() ) + eLang = LANGUAGE_ARABIC_SAUDI_ARABIA; + else if ( SvtCTLOptions::NUMERALS_ARABIC == aCTLOptions.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 ) ) + { + 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::set<Color> SfxObjectShell::GetDocColors() +{ + std::set<Color> empty; + return empty; +} + +std::vector<Color> SfxObjectShell::GetThemeColors() { return std::vector<Color>(); } + +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 = SfxItemSet::GetItem<SfxUInt16Item>(pFile->GetItemSet(), 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, const OUString& 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 000000000..91e886c8a --- /dev/null +++ b/sfx2/source/doc/objembed.cxx @@ -0,0 +1,217 @@ +/* -*- 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 <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() ) + 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 ) +{ + 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 ); + } +} + + +void SfxObjectShell::DoDraw_Impl( OutputDevice* pDev, + const Point & rViewPos, + const Fraction & rScaleX, + const Fraction & rScaleY, + const JobSetup & rSetup, + sal_uInt16 nAspect ) +{ + 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 ); + + // 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 000000000..e776e6608 --- /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 000000000..873ab8b9b --- /dev/null +++ b/sfx2/source/doc/objmisc.cxx @@ -0,0 +1,1920 @@ +/* -*- 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 <tools/diagnose_ex.h> +#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/ErrorCodeRequest.hpp> + +#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 <vcl/weld.hxx> +#include <basic/sbx.hxx> +#include <svtools/sfxecode.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 <officecfg/Office/Common.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(ErrCode lErr) +{ + if (pImpl->lErr==ERRCODE_NONE) + { + pImpl->lErr=lErr; + } +} + +ErrCode SfxObjectShell::GetError() const +{ + return GetErrorCode().IgnoreWarning(); +} + +ErrCode SfxObjectShell::GetErrorCode() const +{ + ErrCode 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 +{ + return pImpl->m_bEnableSetModified && !IsReadOnly(); +} + + +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 SfxPoolItem* pItem = pViewFrame->GetBindings().ExecuteSynchron( HasName() ? SID_SAVEDOC : SID_SAVEASDOC ); + const SfxBoolItem* pResult = dynamic_cast<const SfxBoolItem*>( pItem ); + 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 = SfxItemSet::GetItem<SfxStringItem>(pMed->GetItemSet(), 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 = SfxItemSet::GetItem<SfxStringItem>(pMed->GetItemSet(), 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 = SfxItemSet::GetItem<SfxBoolItem>(pMedium->GetItemSet(), 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(); + pImpl->aMacroMode.checkMacrosOnLoading( xInteraction, bHasValidContentSignature ); +} + + +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 = SfxItemSet::GetItem<SfxStringItem>(pMedium->GetItemSet(), 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 *pSet = GetMedium()->GetItemSet(); + if ( !GetMedium()->IsReadOnly() ) + pSet->ClearItem( SID_INPUTSTREAM ); + uno::Sequence< beans::PropertyValue > aArgs; + TransformItems( SID_OPENDOC, *pSet, aArgs ); + xModel->attachResource( GetMedium()->GetOrigURL(), aArgs ); + impl_addToModelCollection(xModel); + } + + pImpl->bModelInitialized = true; +} + +void SfxObjectShell::FinishedLoading( SfxLoadedFlags nFlags ) +{ + bool bSetModifiedTRUE = false; + const SfxStringItem* pSalvageItem = SfxItemSet::GetItem<SfxStringItem>(pMedium->GetItemSet(), 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(); + 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 = SfxItemSet::GetItem<SfxBoolItem>(pMedium->GetItemSet(), 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 = SfxItemSet::GetItem<SfxStringItem>(pTmpMedium->GetItemSet(), 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 = SfxItemSet::GetItem<SfxStringItem>(pMedium->GetItemSet(), 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( + const OUString& rURL, sal_uInt32 nTime, SfxObjectShell* pSh ) + : Timer("sfx2 AutoReloadTimer_Impl"), aUrl( rURL ), 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 static_cast<SvKeyValueIterator*>( 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 = SfxItemSet::GetItem<SfxStringItem>(pMedium->GetItemSet(), 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 = SfxItemSet::GetItem<SfxBoolItem>(pMedium->GetItemSet(), 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* pSet = pLoadingMedium ? pLoadingMedium->GetItemSet() : GetMedium()->GetItemSet(); + const SfxUnoFrameItem* pUnoItem = SfxItemSet::GetItem<SfxUnoFrameItem>(pSet, SID_FILLFRAME, false); + if ( pUnoItem ) + { + const uno::Reference < frame::XFrame >& xFrame( pUnoItem->GetFrame() ); + xWindow = xFrame->getContainerWindow(); + } + + if (!xWindow) + { + SfxFrame* pFrame = nullptr; + const SfxFrameItem* pFrameItem = SfxItemSet::GetItem<SfxFrameItem>(pSet, 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 = SfxItemSet::GetItem<SfxBoolItem>(pSet, 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, + ErrCode 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::ErrorCodeRequest aErrorCode; + aErrorCode.ErrCode = sal_uInt32(nError); + 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 = SfxItemSet::GetItem<SfxUInt16Item>(pMedium->GetItemSet(), 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( bool bAllowUIToAddAuthor ) +{ + bool bResult = false; + + try + { + 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) ); + + if ( nScriptingSignatureState == SignatureState::UNKNOWN + || nScriptingSignatureState == SignatureState::OK + || nScriptingSignatureState == SignatureState::NOTVALIDATED ) + { + 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 && bAllowUIToAddAuthor ) + { + uno::Reference< task::XInteractionHandler > xInteraction; + if ( rDocShell.GetMedium() ) + xInteraction = rDocShell.GetMedium()->GetInteractionHandler(); + + if ( xInteraction.is() ) + { + task::DocumentMacroConfirmationRequest aRequest; + aRequest.DocumentURL = getDocumentLocation(); + aRequest.DocumentStorage = rDocShell.GetMedium()->GetZipStorageToSign_Impl(); + aRequest.DocumentSignatureInformation = aInfo; + aRequest.DocumentVersion = aVersion; + aRequest.Classification = task::InteractionClassification_QUERY; + bResult = SfxMedium::CallApproveHandler( xInteraction, uno::Any( aRequest ), true ); + } + } + } + } + } + } + catch( uno::Exception& ) + {} + + return bResult; +} + +bool SfxObjectShell::IsContinueImportOnFilterExceptions(std::u16string_view aErrMessage) +{ + if (mbContinueImportOnFilterExceptions == undefined) + { + if (Application::GetDialogCancelMode() == DialogCancelMode::Off) + { + // Ask the user to try to continue or abort loading + OUString aMessage = SfxResId(STR_QMSG_ERROR_OPENING_FILE); + if (!aErrMessage.empty()) + aMessage += SfxResId(STR_QMSG_ERROR_OPENING_FILE_DETAILS) + aErrMessage; + aMessage += SfxResId(STR_QMSG_ERROR_OPENING_FILE_CONTINUE); + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Question, VclButtonsType::YesNo, aMessage)); + mbContinueImportOnFilterExceptions = (xBox->run() == RET_YES) ? yes : no; + } + 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 000000000..3a6a0ceea --- /dev/null +++ b/sfx2/source/doc/objserv.cxx @@ -0,0 +1,2131 @@ +/* -*- 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/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 <tools/diagnose_ex.h> +#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 <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> + +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; + +#define ShellClass_SfxObjectShell +#include <sfxslots.hxx> + +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(ErrCode error) +{ + if (error.GetClass() == ErrCodeClass::NONE) + return; + + boost::property_tree::ptree aTree; + aTree.put("code", error); + aTree.put("kind", ""); + aTree.put("cmd", ""); + + std::unique_ptr<ErrorInfo> pInfo = ErrorInfo::GetErrorInfo(error); + OUString aErr; + if (ErrorStringFactory::CreateString(pInfo.get(), aErr)) + aTree.put("message", aErr.toUtf8()); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + + SfxViewShell::Current()->libreOfficeKitViewCallback(LOK_CALLBACK_ERROR, aStream.str().c_str()); +} + +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(); + + if( SID_SIGNATURE == nId || SID_MACRO_SIGNATURE == nId ) + { + if ( QueryHiddenInformation( HiddenWarningFact::WhenSigning, nullptr ) == RET_YES ) + { + if (SID_SIGNATURE == nId) + { + uno::Reference<security::XCertificate> xCertificate = GetSignPDFCertificate(); + if (xCertificate.is()) + { + 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 + { + SignDocumentContent(pDialogParent); + } + } + else + { + SignScriptingContent(pDialogParent); + } + } + 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; + 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 + { + // 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() ); + if ( !GetSlotState( SID_DOCTEMPLATE ) ) + // 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: + { + // 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 ) ); + + ErrCode 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); + 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 = SfxItemSet::GetItem<SfxBoolItem>(GetMedium()->GetItemSet(), 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 = SfxItemSet::GetItem<SfxStringItem>(GetMedium()->GetItemSet(), SID_PASSWORD, false); + const SfxUnoAnyItem* pOldEncryptionDataItem = SfxItemSet::GetItem<SfxUnoAnyItem>(GetMedium()->GetItemSet(), SID_ENCRYPTIONDATA, false); + bool bPreselectPassword = (pOldPasswordItem && pOldEncryptionDataItem); + + uno::Sequence< beans::PropertyValue > aDispatchArgs; + if ( rReq.GetArgs() ) + TransformItems( nId, + *rReq.GetArgs(), + aDispatchArgs ); + + bool bForceSaveAs = nId == SID_SAVEDOC && IsReadOnlyMedium(); + const SfxSlot* pSlot = GetModule()->GetSlotPool()->GetSlot( bForceSaveAs ? SID_SAVEASDOC : nId ); + if ( !pSlot ) + throw uno::Exception("no slot", nullptr); + + SfxStoringHelper aHelper; + + if ( QueryHiddenInformation( bIsPDFExport ? HiddenWarningFact::WhenCreatingPDF : HiddenWarningFact::WhenSaving, nullptr ) != RET_YES ) + { + // the user has decided not to store the document + throw task::ErrorCodeIOException( + "SfxObjectShell::ExecFile_Impl: ERRCODE_IO_ABORT", + uno::Reference< uno::XInterface >(), sal_uInt32(ERRCODE_IO_ABORT)); + } + + aHelper.GUIStoreModel( GetModel(), + OUString::createFromAscii( pSlot->GetUnoName() ), + aDispatchArgs, + bPreselectPassword, + GetDocumentSignatureState() ); + + + // 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( "sfx.doc", "Fatal IO error during save"); + nErrorCode = ErrCode(aErrorEx.ErrCode); + } + catch( Exception& ) + { + nErrorCode = ERRCODE_IO_GENERAL; + } + + // 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; + ErrCode 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 + { + 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); + } + } + + 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 ); + ErrCode 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: + sMessage = SfxResId(STR_SIGNATURE_INVALID); + // Warning only, I've tried Danger and it looked too scary + aInfobarType = 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()->GetZipStorageToSign_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(Application::CreateMessageDialog(pDialogParent, + VclMessageType::Question, VclButtonsType::YesNo, sQuestion)); + + + if (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() ) + { + 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 ) + 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()->GetZipStorageToSign_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(); +} + +void SfxObjectShell::SignDocumentContent(weld::Window* pDialogParent) +{ + if (!PrepareForSigning(pDialogParent)) + return; + + if (CheckIsReadonly(false, pDialogParent)) + return; + + bool bSignSuccess = GetMedium()->SignContents_Impl(pDialogParent, false, HasValidSignatures()); + + AfterSigning(bSignSuccess, false); +} + +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 ); +} + +void SfxObjectShell::SignScriptingContent(weld::Window* pDialogParent) +{ + if (!PrepareForSigning(pDialogParent)) + return; + + if (CheckIsReadonly(true, pDialogParent)) + return; + + bool bSignSuccess = GetMedium()->SignContents_Impl(pDialogParent, true, HasValidSignatures()); + + AfterSigning(bSignSuccess, true); +} + +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 000000000..288b6a7ae --- /dev/null +++ b/sfx2/source/doc/objstor.cxx @@ -0,0 +1,3791 @@ +/* -*- 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/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 <tools/diagnose_ex.h> +#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 <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/strings.hrc> +#include <sfx2/sfxsids.hrc> +#include <sfx2/dispatch.hxx> +#include <sfx2/sfxuno.hxx> +#include <sfx2/event.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; +} + + +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::TempFile().GetURL(); + + 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) } + }; + + 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(OUString(ODFVER_013_TEXT))); + } + else + { + xProps->setPropertyValue("Version", uno::Any(OUString(ODFVER_012_TEXT))); + } + } + catch( uno::Exception& ) + { + } + + auto pEncryptionAlgs = aEncryptionAlgs.getArray(); + pEncryptionAlgs[0].Value <<= xml::crypto::DigestID::SHA256; + pEncryptionAlgs[2].Value <<= xml::crypto::DigestID::SHA256_1K; + pEncryptionAlgs[1].Value <<= xml::crypto::CipherID::AES_CBC_W3C_PADDING; + } + + 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 *pSet = GetMedium()->GetItemSet(); + uno::Sequence< beans::PropertyValue > aArgs; + TransformItems( SID_OPENDOC, *pSet, 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* pSet = pMedium->GetItemSet(); + if( pImpl->nEventId == SfxEventHintId::NONE ) + { + const SfxBoolItem* pTemplateItem = SfxItemSet::GetItem<SfxBoolItem>(pSet, SID_TEMPLATE, false); + SetActivateEvent_Impl( + ( pTemplateItem && pTemplateItem->GetValue() ) + ? SfxEventHintId::CreateDoc : SfxEventHintId::OpenDoc ); + } + + const SfxStringItem* pBaseItem = SfxItemSet::GetItem<SfxStringItem>(pSet, SID_BASEURL, false); + OUString aBaseURL; + const SfxStringItem* pSalvageItem = SfxItemSet::GetItem<SfxStringItem>(pMedium->GetItemSet(), 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) + pSet->Put( SfxBoolItem( SID_DOC_STARTPRESENTATION, true) ); + } + + EnableSetModified( false ); + + pMedium->LockOrigFileOnDemand( true, false ); + if ( GetError() == ERRCODE_NONE && bOwnStorageFormat && ( !pFilter || !( pFilter->GetFilterFlags() & SfxFilterFlags::STARONEFILTER ) ) ) + { + uno::Reference< embed::XStorage > xStorage; + if ( pMedium->GetError() == 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 = SfxItemSet::GetItem<SfxBoolItem>(pMedium->GetItemSet(), 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 ( !GetError() ) + { + 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 = SfxItemSet::GetItem<SfxBoolItem>(pMedium->GetItemSet(), SID_TEMPLATE, false); + if ( !pTemplateItem || !pTemplateItem->GetValue() ) + bHasName = true; + } + else + SetError(ERRCODE_ABORT); + } + } + else + SetError(pMed->GetLastStorageCreationState()); + } + else if ( GetError() == 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 ( GetError() == 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 const OUStringLiteral sLockUpdates(u"LockUpdates"); + 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 const OUStringLiteral aAuthor( u"Author" ); + static const OUStringLiteral aKeywords( u"Keywords" ); + static const OUStringLiteral aSubject( u"Subject" ); + 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 = SfxItemSet::GetItem<SfxBoolItem>(pMedium->GetItemSet(), SID_TEMPLATE, false); + const SfxBoolItem* pPreviewItem = SfxItemSet::GetItem<SfxBoolItem>(pMedium->GetItemSet(), SID_PREVIEW, false); + const SfxBoolItem* pHiddenItem = SfxItemSet::GetItem<SfxBoolItem>(pMedium->GetItemSet(), SID_HIDDEN, false); + if( bOk && !pMedium->GetOrigURL().isEmpty() + && !( pAsTempItem && pAsTempItem->GetValue() ) + && !( pPreviewItem && pPreviewItem->GetValue() ) + && !( pHiddenItem && pHiddenItem->GetValue() ) ) + { + AddToRecentlyUsedList(); + } + } + + const SfxBoolItem* pDdeReconnectItem = SfxItemSet::GetItem<SfxBoolItem>(pMedium->GetItemSet(), 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* pSet = pMedium->GetItemSet(); + const SfxStringItem* pOptions = SfxItemSet::GetItem<SfxStringItem>(pSet, SID_FILE_FILTEROPTIONS, false); + const SfxUnoAnyItem* pData = SfxItemSet::GetItem<SfxUnoAnyItem>(pSet, 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 ( pSet->GetItemState( SID_INPUTSTREAM ) < SfxItemState::SET ) + pSet->Put( SfxUnoAnyItem( SID_INPUTSTREAM, aStreamAny ) ); + if ( pSet->GetItemState( SID_FILE_NAME ) < SfxItemState::SET ) + pSet->Put( SfxStringItem( SID_FILE_NAME, pMedium->GetName() ) ); + if ( pSet->GetItemState( SID_FILTER_NAME ) < SfxItemState::SET ) + pSet->Put( SfxStringItem( SID_FILTER_NAME, pFilter->GetName() ) ); + + Sequence< PropertyValue > rProperties; + TransformItems( SID_OPENDOC, *pSet, 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 ) + pSet->Put( *pFilterOptions ); + + const SfxUnoAnyItem* pFilterData = aNewParams.GetItem<SfxUnoAnyItem>(SID_FILTER_DATA, false); + if ( pFilterData ) + pSet->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(OUString(ODFVER_013_TEXT))); + } + else + { + xProps->setPropertyValue("Version", uno::Any(OUString(ODFVER_012_TEXT))); + } + } + catch (uno::Exception&) + { + TOOLS_WARN_EXCEPTION("sfx.doc", "SfxObjectShell::DoSave"); + } + } + } + + uno::Sequence< beans::NamedValue > aEncryptionData; + if ( IsPackageStorageFormat_Impl( *GetMedium() ) ) + { + if ( GetEncryptionData_Impl( GetMedium()->GetItemSet(), aEncryptionData ) ) + { + try + { + //TODO/MBA: GetOutputStorage?! Special mode, because it's "Save"?! + ::comphelper::OStorageHelper::SetCommonStorageEncryptionData( GetMedium()->GetStorage(), aEncryptionData ); + bOk = true; + } + catch( uno::Exception& ) + { + SetError(ERRCODE_IO_GENERAL); + } + + DBG_ASSERT( bOk, "The root storage must allow to set common password!\n" ); + } + 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 const OUStringLiteral aBasicStorageName( u"Basic" ); + static const OUStringLiteral aDialogsStorageName( u"Dialogs" ); + 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 OUStringLiteral sPostfix(u"_template"); + 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()); + + 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; + } + + 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 + SvtSaveOptions::ODFSaneDefaultVersion nVersion = GetODFSaneDefaultVersion(); + + 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) + ); + } + } + + // 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 ) ); + + // 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(); + if ( bDoBackup ) + { + rMedium.DoBackup_Impl(); + if ( rMedium.GetError() ) + { + 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(); + + // 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()); + 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(); + 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(); + 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(); + 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(); + if ( bStorageBasedTarget ) + { + rMedium.SetHasEmbeddedObjects(GetEmbeddedObjectContainer().HasEmbeddedObjects()); + 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; + } + + 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 *pMedSet = rMedium.GetItemSet(); + if( pMedSet ) + { + const SfxBoolItem* pSaveToItem = SfxItemSet::GetItem<SfxBoolItem>(pMedSet, SID_SAVETO, false); + bCopyTo = GetCreateMode() == SfxObjectCreateMode::EMBEDDED || + (pSaveToItem && pSaveToItem->GetValue()); + } + + bool bOk = false; + // TODO/LATER: get rid of bOk + if (bOwnTarget && pFilter && !(pFilter->GetFilterFlags() & SfxFilterFlags::STARONEFILTER)) + { + uno::Reference< embed::XStorage > xMedStorage = rMedium.GetStorage(); + if ( !xMedStorage.is() ) + { + // no saving without storage + pImpl->bForbidReload = bOldStat; + return false; + } + + // transfer password from the parameters to the storage + uno::Sequence< beans::NamedValue > aEncryptionData; + bool bPasswdProvided = false; + if ( GetEncryptionData_Impl( rMedium.GetItemSet(), aEncryptionData ) ) + { + bPasswdProvided = true; + try { + ::comphelper::OStorageHelper::SetCommonStorageEncryptionData( xMedStorage, aEncryptionData ); + bOk = true; + } + catch( uno::Exception& ) + { + SAL_WARN( "sfx.doc", "Setting of common encryption key failed!" ); + SetError(ERRCODE_IO_GENERAL); + } + } + else + bOk = true; + + 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 const OUStringLiteral aVersionsName( u"Versions" ); + 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 ) + { + // 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(); + + uno::Reference< embed::XStorage > xReadOrig = pMedium->GetZipStorageToSign_Impl(); + if ( !xReadOrig.is() ) + throw uno::RuntimeException(); + uno::Reference< embed::XStorage > xMetaInf = xReadOrig->openStorageElement( + "META-INF", + embed::ElementModes::READ ); + + uno::Reference< embed::XStorage > xTarget = rMedium.GetZipStorageToSign_Impl( false ); + 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(); + } + 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(); + } + + const OUString sName( rMedium.GetName( ) ); + bOk = rMedium.Commit(); + const OUString sNewName( rMedium.GetName( ) ); + + if ( sName != sNewName ) + GetMedium( )->SwitchDocumentToFile( sNewName ); + + 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 const OUStringLiteral aAuthor( u"Author" ); + static const OUStringLiteral aKeywords( u"Keywords" ); + static const OUStringLiteral aSubject( u"Subject" ); + + 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 ( GetError() ) + 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->GetError() == 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 = SfxItemSet::GetItem<SfxBoolItem>(pMedium->GetItemSet(), 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 const OUStringLiteral sInputStream ( u"InputStream" ); + + 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(*new StringErrorInfo(ERRCODE_SFX_FORMAT_ROWCOL, + e.Message, DialogMask::ButtonsOk | DialogMask::MessageError )); + } + } + catch (const css::io::IOException& e) + { + SetError(*new StringErrorInfo(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); + SetError(*new StringErrorInfo(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* pItems = rMedium.GetItemSet(); + TransformItems( SID_SAVEASDOC, *pItems, 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 const OUStringLiteral sOutputStream ( u"OutputStream" ); + static const OUStringLiteral sStream ( u"StreamForOutput" ); + 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 (pItems->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(...) + {} + } + + 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->GetError()); + delete pMediumTmp; + return false; + } + + // copy version list from "old" medium to target medium, so it can be used on saving + 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 + css::uno::Reference< XInteractionHandler > xInteract; + const SfxUnoAnyItem* pxInteractionItem = SfxItemSet::GetItem<SfxUnoAnyItem>(pArgs, SID_INTERACTIONHANDLER, false); + if ( pxInteractionItem && ( pxInteractionItem->GetValue() >>= xInteract ) && xInteract.is() ) + 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( !GetError() && SaveTo_Impl( *pMediumTmp, pArgs ) ) + { + bSaved = true; + + if( pMediumTmp->GetItemSet() ) + { + 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 + SetError(pMediumTmp->GetError()); + + // reconnect to object storage + DoSaveCompleted(); + + if( pRetrMedium->GetItemSet() ) + { + 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 = SfxItemSet::GetItem<SfxStringItem>(GetMedium()->GetItemSet(), SID_DOC_SALVAGE, false); + if ( pSalvageItem ) + { + const SfxStringItem* pFilterItem = SfxItemSet::GetItem<SfxStringItem>(GetMedium()->GetItemSet(), 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 = SfxItemSet::GetItem<SfxStringItem>(GetMedium()->GetItemSet(), 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 *pSet = GetMedium()->GetItemSet(); + pSet->ClearItem( SID_INTERACTIONHANDLER ); + pSet->ClearItem( SID_PROGRESS_STATUSBAR_CONTROL ); + pSet->ClearItem( SID_STANDARD_DIR ); + pSet->ClearItem( SID_PATH ); + + if ( !bSaveTo ) + { + pSet->ClearItem( SID_REFERER ); + pSet->ClearItem( SID_POSTDATA ); + pSet->ClearItem( SID_TEMPLATE ); + pSet->ClearItem( SID_DOC_READONLY ); + pSet->ClearItem( SID_CONTENTTYPE ); + pSet->ClearItem( SID_CHARSET ); + pSet->ClearItem( SID_FILTER_NAME ); + pSet->ClearItem( SID_OPTIONS ); + pSet->ClearItem( SID_VERSION ); + pSet->ClearItem( SID_EDITDOC ); + pSet->ClearItem( SID_OVERWRITE ); + pSet->ClearItem( SID_DEFAULTFILEPATH ); + pSet->ClearItem( SID_DEFAULTFILENAME ); + + const SfxStringItem* pFilterItem = rItemSet.GetItem<SfxStringItem>(SID_FILTER_NAME, false); + if ( pFilterItem ) + pSet->Put( *pFilterItem ); + + const SfxStringItem* pOptionsItem = rItemSet.GetItem<SfxStringItem>(SID_OPTIONS, false); + if ( pOptionsItem ) + pSet->Put( *pOptionsItem ); + + const SfxStringItem* pFilterOptItem = rItemSet.GetItem<SfxStringItem>(SID_FILE_FILTEROPTIONS, false); + if ( pFilterOptItem ) + pSet->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" && pNewFile->GetItemSet()) + { + 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->GetError()); + 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; +} + +sal_Int16 SfxObjectShell::QueryHiddenInformation(HiddenWarningFact eFact, weld::Window* pParent) +{ + sal_Int16 nRet = RET_YES; + TranslateId pResId; + SvtSecurityOptions::EOption eOption = SvtSecurityOptions::EOption(); + + switch ( eFact ) + { + case HiddenWarningFact::WhenSaving : + { + pResId = STR_HIDDENINFO_CONTINUE_SAVING; + eOption = SvtSecurityOptions::EOption::DocWarnSaveOrSend; + break; + } + case HiddenWarningFact::WhenPrinting : + { + pResId = STR_HIDDENINFO_CONTINUE_PRINTING; + eOption = SvtSecurityOptions::EOption::DocWarnPrint; + break; + } + case HiddenWarningFact::WhenSigning : + { + pResId = STR_HIDDENINFO_CONTINUE_SIGNING; + eOption = SvtSecurityOptions::EOption::DocWarnSigning; + break; + } + case HiddenWarningFact::WhenCreatingPDF : + { + pResId = STR_HIDDENINFO_CONTINUE_CREATEPDF; + eOption = SvtSecurityOptions::EOption::DocWarnCreatePdf; + break; + } + default: + assert(false); // this cannot happen + } + + if ( SvtSecurityOptions::IsOptionSet( eOption ) ) + { + OUString sMessage( SfxResId(STR_HIDDENINFO_CONTAINS) ); + HiddenInformation nWantedStates = HiddenInformation::RECORDEDCHANGES | HiddenInformation::NOTES; + if ( eFact != HiddenWarningFact::WhenPrinting ) + nWantedStates |= HiddenInformation::DOCUMENTVERSIONS; + HiddenInformation nStates = GetHiddenInformationState( nWantedStates ); + bool bWarning = false; + + if ( nStates & HiddenInformation::RECORDEDCHANGES ) + { + sMessage += SfxResId(STR_HIDDENINFO_RECORDCHANGES) + "\n"; + bWarning = true; + } + if ( nStates & HiddenInformation::NOTES ) + { + sMessage += SfxResId(STR_HIDDENINFO_NOTES) + "\n"; + bWarning = true; + } + if ( nStates & HiddenInformation::DOCUMENTVERSIONS ) + { + sMessage += SfxResId(STR_HIDDENINFO_DOCVERSIONS) + "\n"; + bWarning = true; + } + + if ( bWarning ) + { + sMessage += "\n" + SfxResId(pResId); + std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(pParent, + VclMessageType::Warning, VclButtonsType::YesNo, sMessage)); + xWarn->set_default_response(RET_NO); + nRet = xWarn->run(); + } + } + + return nRet; +} + +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 = SfxItemSet::GetItem<SfxStringItem>(rMedium.GetItemSet(), SID_PASSWORD, false); + if ( pPasswdItem || ERRCODE_IO_ABORT != CheckPasswd_Impl( this, pMedium ) ) + { + 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 + 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 + = SfxItemSet::GetItem(rMedium.GetItemSet(), 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 const OUStringLiteral aMediaTypePropName( u"MediaType" ); + 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) +{ + // 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 const OUStringLiteral aMediaTypePropName( u"MediaType" ); + 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 (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.LegacyPsswdBinaryLimitExceeded( 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 000000000..4692dbf71 --- /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 000000000..c83a446a8 --- /dev/null +++ b/sfx2/source/doc/objxtor.cxx @@ -0,0 +1,1118 @@ +/* -*- 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 <tools/diagnose_ex.h> +#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 <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 ) + ,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) +{ + 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) +{ +} + +SfxObjectShell::~SfxObjectShell() +{ + + if ( IsEnableSetModified() ) + EnableSetModified( false ); + + SfxObjectShell::CloseInternal(); + pImpl->pBaseModel.set( nullptr ); + + 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.set( nullptr ); + + // 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 + const SfxPoolItem *pPoolItem; + if ( IsSaveVersionOnClose() ) + { + SfxStringItem aItem( SID_DOCINFO_COMMENTS, SfxResId(STR_AUTOMATICVERSION) ); + SfxBoolItem aWarnItem( SID_FAIL_ON_WARNING, bUI ); + const SfxPoolItem* ppArgs[] = { &aItem, &aWarnItem, nullptr }; + pPoolItem = pFrame->GetBindings().ExecuteSynchron( SID_SAVEDOC, ppArgs ); + } + else + { + SfxBoolItem aWarnItem( SID_FAIL_ON_WARNING, bUI ); + const SfxPoolItem* ppArgs[] = { &aWarnItem, nullptr }; + pPoolItem = pFrame->GetBindings().ExecuteSynchron( SID_SAVEDOC, ppArgs ); + } + + if ( !pPoolItem || pPoolItem->IsVoidItem() ) + return false; + if ( auto pBoolItem = dynamic_cast< const SfxBoolItem *>( pPoolItem ) ) + 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 000000000..6de4aace5 --- /dev/null +++ b/sfx2/source/doc/oleprops.cxx @@ -0,0 +1,1240 @@ +/* -*- 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> + + +#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, const OUString& rValue ); + 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, const OUString& rValue ) const +{ + if( IsUnicode() ) + ImplSaveString16( rStrm, rValue ); + else + ImplSaveString8( rStrm, rValue ); +} + +OUString SfxOleStringHelper::LoadString16( SvStream& rStrm ) +{ + return ImplLoadString16( rStrm ); +} + +void SfxOleStringHelper::SaveString16( SvStream& rStrm, const OUString& 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, const OUString& rValue ) +{ + // write size field (including trailing NUL character) + sal_Int32 nSize = static_cast< sal_Int32 >( rValue.getLength() + 1 ); + rStrm.WriteInt32( nSize ); + // write character array with trailing NUL character + for( sal_Int32 nIdx = 0; nIdx < rValue.getLength(); ++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, const OUString& rValue ) : + SfxOlePropertyBase( nPropId, nPropType ), + SfxOleStringHelper( rTextEnc ), + maValue( rValue ) +{ +} + +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 000000000..81eb744ea --- /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, const OUString& 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, const OUString& 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, const OUString& 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 000000000..7e317cc29 --- /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 000000000..7a1891037 --- /dev/null +++ b/sfx2/source/doc/printhelper.cxx @@ -0,0 +1,807 @@ +/* -*- 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 <svl/itemset.hxx> +#include <svl/lstner.hxx> +#include <unotools/tempfile.hxx> +#include <osl/file.hxx> +#include <osl/thread.hxx> +#include <tools/urlobj.hxx> +#include <tools/diagnose_ex.h> +#include <ucbhelper/content.hxx> +#include <comphelper/interfacecontainer4.hxx> +#include <cppuhelper/implbase.hxx> +#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 >(); + + uno::Sequence< beans::PropertyValue > aPrinter(8); + + aPrinter.getArray()[7].Name = "CanSetPaperSize"; + aPrinter.getArray()[7].Value <<= pPrinter->HasSupport( PrinterSupport::SetPaperSize ); + + aPrinter.getArray()[6].Name = "CanSetPaperFormat"; + aPrinter.getArray()[6].Value <<= pPrinter->HasSupport( PrinterSupport::SetPaper ); + + aPrinter.getArray()[5].Name = "CanSetPaperOrientation"; + aPrinter.getArray()[5].Value <<= pPrinter->HasSupport( PrinterSupport::SetOrientation ); + + aPrinter.getArray()[4].Name = "IsBusy"; + aPrinter.getArray()[4].Value <<= pPrinter->IsPrinting(); + + aPrinter.getArray()[3].Name = "PaperSize"; + awt::Size aSize = impl_Size_Object2Struct(pPrinter->GetPaperSize() ); + aPrinter.getArray()[3].Value <<= aSize; + + aPrinter.getArray()[2].Name = "PaperFormat"; + view::PaperFormat eFormat = convertToPaperFormat(pPrinter->GetPaper()); + aPrinter.getArray()[2].Value <<= eFormat; + + aPrinter.getArray()[1].Name = "PaperOrientation"; + view::PaperOrientation eOrient = static_cast<view::PaperOrientation>(pPrinter->GetOrientation()); + aPrinter.getArray()[1].Value <<= eOrient; + + aPrinter.getArray()[0].Name = "Name"; + OUString sStringTemp = pPrinter->GetName() ; + aPrinter.getArray()[0].Value <<= sStringTemp; + + return aPrinter; +} + + +// 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::TempFile* m_pTempFile; + + public: + /* initialize this watcher but don't start it */ + ImplUCBPrintWatcher( SfxPrinter* pPrinter, ::utl::TempFile* pTempFile, const OUString& sTargetURL ) + : m_pPrinter ( pPrinter ) + , m_sTargetURL( 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::TempFile** 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::TempFile* 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::TempFile(); + 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; + } + + // 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 000000000..a38e3d7a4 --- /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 000000000..6f9fd1b47 --- /dev/null +++ b/sfx2/source/doc/saveastemplatedlg.cxx @@ -0,0 +1,178 @@ +/* -*- 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 <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, const uno::Reference<frame::XModel> &rModel) + : 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(rModel) +{ + 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 000000000..dc9a056e3 --- /dev/null +++ b/sfx2/source/doc/sfxbasemodel.cxx @@ -0,0 +1,4561 @@ +/* -*- 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 <memory> +#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/ErrorCodeRequest.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/string.hxx> + +#include <cppuhelper/implbase.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 <tools/diagnose_ex.h> +#include <tools/svborder.hxx> +#include <unotools/tempfile.hxx> +#include <osl/mutex.hxx> +#include <vcl/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 <comphelper/profilezone.hxx> +#include <vcl/threadex.hxx> +#include <unotools/mediadescriptor.hxx> + +// 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::OMultiTypeInterfaceContainerHelper2 m_aInterfaceContainer ; + 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_bModifiedSinceLastSave ; + 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 ; + + IMPL_SfxBaseModel_DataContainer( ::osl::Mutex& rMutex, SfxObjectShell* pObjectShell ) + : m_pObjectShell ( pObjectShell ) + , m_aInterfaceContainer ( rMutex ) + , m_nControllerLockCount ( 0 ) + , m_bClosed ( false ) + , m_bClosing ( false ) + , m_bSaving ( false ) + , m_bSuicide ( false ) + , m_bExternalTitle ( false ) + , m_bModifiedSinceLastSave( 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; + } +}; + +// 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 ) +{ + ::comphelper::OInterfaceContainerHelper2* pContainer = m_pData->m_aInterfaceContainer.getContainer( cppu::UnoType<view::XPrintJobListener>::get()); + if ( pContainer!=nullptr ) + { + ::comphelper::OInterfaceIteratorHelper2 pIterator(*pContainer); + while (pIterator.hasMoreElements()) + static_cast<view::XPrintJobListener*>(pIterator.next())->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() ) ) + ) + 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<XDocumentRecovery>::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_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_aInterfaceContainer.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_aInterfaceContainer.addInterface( cppu::UnoType<lang::XEventListener>::get(), aListener ); +} + + +// XChild + + +void SAL_CALL SfxBaseModel::removeEventListener( const Reference< lang::XEventListener >& aListener ) +{ + SfxModelGuard aGuard( *this, SfxModelGuard::E_INITIALIZING ); + m_pData->m_aInterfaceContainer.removeInterface( cppu::UnoType<lang::XEventListener>::get(), 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_aInterfaceContainer.removeInterface( cppu::UnoType<util::XModifyListener>::get(), xMod ); + else if ( xListener.is() ) + m_pData->m_aInterfaceContainer.removeInterface( cppu::UnoType<lang::XEventListener>::get(), xListener ); + else if ( xDocListener.is() ) + m_pData->m_aInterfaceContainer.removeInterface( cppu::UnoType<document::XEventListener>::get(), xListener ); +} + + +// 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; + vec.erase(std::remove(vec.begin(), vec.end(), xController), vec.end()); + + 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_aInterfaceContainer.addInterface( cppu::UnoType<util::XModifyListener>::get(),xListener ); +} + + +// XModifiable + + +void SAL_CALL SfxBaseModel::removeModifyListener(const Reference< util::XModifyListener >& xListener) +{ + SfxModelGuard aGuard( *this ); + + m_pData->m_aInterfaceContainer.removeInterface( cppu::UnoType<util::XModifyListener>::get(), 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( static_cast< ::cppu::OWeakObject* >(this) ); + lang::EventObject aSource ( static_cast< ::cppu::OWeakObject* >(this) ); + comphelper::OInterfaceContainerHelper2* pContainer = m_pData->m_aInterfaceContainer.getContainer( cppu::UnoType<util::XCloseListener>::get()); + if (pContainer!=nullptr) + { + comphelper::OInterfaceIteratorHelper2 pIterator(*pContainer); + while (pIterator.hasMoreElements()) + { + try + { + static_cast<util::XCloseListener*>(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; + pContainer = m_pData->m_aInterfaceContainer.getContainer( cppu::UnoType<util::XCloseListener>::get()); + if (pContainer!=nullptr) + { + comphelper::OInterfaceIteratorHelper2 pCloseIterator(*pContainer); + while (pCloseIterator.hasMoreElements()) + { + try + { + static_cast<util::XCloseListener*>(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_aInterfaceContainer.addInterface( cppu::UnoType<util::XCloseListener>::get(), xListener ); +} + + +// XCloseBroadcaster + + +void SAL_CALL SfxBaseModel::removeCloseListener( const Reference< util::XCloseListener >& xListener ) +{ + SfxModelGuard aGuard( *this ); + + m_pData->m_aInterfaceContainer.removeInterface( cppu::UnoType<util::XCloseListener>::get(), 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(); + + ErrCode nErrCode = m_pData->m_pObjectShell->GetError() ? m_pData->m_pObjectShell->GetError() + : ERRCODE_IO_CANTWRITE; + 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 + { + // 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.toHexString(), + Reference< XInterface >(), sal_uInt32(nErrCode)); + } +} + + +// 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 = SfxItemSet::GetItem<SfxStringItem>(m_pData->m_pObjectShell->GetMedium()->GetItemSet(), 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_bModifiedSinceLastSave; +} + +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->m_bModifiedSinceLastSave = false; +} + +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(); + ErrCode nErrCode = m_pData->m_pObjectShell->GetError() ? + m_pData->m_pObjectShell->GetError() : ERRCODE_IO_CANTCREATE; + m_pData->m_pObjectShell->ResetError(); + + if ( !bRes ) + throw task::ErrorCodeIOException( + "SfxBaseModel::initNew: " + nErrCode.toHexString(), + Reference< XInterface >(), sal_uInt32(nErrCode)); +} + +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 = SfxItemSet::GetItem<SfxBoolItem>(pMedium->GetItemSet(), 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 ); + + ErrCode 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 = SfxItemSet::GetItem<SfxStringItem>(pMedium->GetItemSet(), 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 = SfxItemSet::GetItem<SfxStringItem>(pMedium->GetItemSet(), 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 = SfxItemSet::GetItem<SfxBoolItem>(pMedium->GetItemSet(), SID_REPAIRPACKAGE, false); + if ( !pRepairItem || !pRepairItem->GetValue() ) + { + RequestPackageReparation aRequest( aDocName ); + xHandler->handle( aRequest.GetRequest() ); + if( aRequest.isApproved() ) + { + // broken package: try second loading and allow repair + pMedium->GetItemSet()->Put( SfxBoolItem( SID_REPAIRPACKAGE, true ) ); + pMedium->GetItemSet()->Put( SfxBoolItem( SID_TEMPLATE, true ) ); + 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 = SfxItemSet::GetItem<SfxStringItem>(pMedium->GetItemSet(), 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 = SfxItemSet::GetItem<SfxStringItem>(pMedium->GetItemSet(), SID_FILTER_NAME, false); + if ( pFilterItem ) + m_pData->m_aPreusedFilterName = pFilterItem->GetValue(); + } + + if ( !nError ) + nError = pMedium->GetError(); + + m_pData->m_pObjectShell->ResetError(); + + pMedium = handleLoadError(nError, pMedium); + loadCmisProperties(); + setUpdatePickList(pMedium); + +#if OSL_DEBUG_LEVEL > 0 + const SfxStringItem* pPasswdItem = SfxItemSet::GetItem<SfxStringItem>(pMedium->GetItemSet(), 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::TempFile 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_aInterfaceContainer.addInterface( cppu::UnoType<document::XEventListener>::get(), aListener ); +} + + +// XEventBroadcaster + + +void SAL_CALL SfxBaseModel::removeEventListener( const Reference< document::XEventListener >& aListener ) +{ + SfxModelGuard aGuard( *this ); + + m_pData->m_aInterfaceContainer.removeInterface( cppu::UnoType<document::XEventListener>::get(), 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_aInterfaceContainer.addInterface( cppu::UnoType<document::XDocumentEventListener>::get(), aListener ); +} + + +void SAL_CALL SfxBaseModel::removeDocumentEventListener( const Reference< document::XDocumentEventListener >& aListener ) +{ + SfxModelGuard aGuard( *this ); + m_pData->m_aInterfaceContainer.removeInterface( cppu::UnoType<document::XDocumentEventListener>::get(), 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 const OUStringLiteral aCmisProps( u"CmisProperties" ); + 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( ErrCode nError, SfxMedium* pMedium ) +{ + if (!nError) + { + // No error condition. + return pMedium; + } + + bool bSilent = false; + const SfxBoolItem* pSilentItem = SfxItemSet::GetItem<SfxBoolItem>(pMedium->GetItemSet(), 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.toHexString(), + Reference< XInterface >(), sal_uInt32(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(); + + const SfxEventHint* pNamedHint = dynamic_cast<const SfxEventHint*>(&rHint); + if ( pNamedHint ) + { + + switch ( pNamedHint->GetEventId() ) + { + case SfxEventHintId::StorageChanged: + { + if ( m_pData->m_xUIConfigurationManager.is() + && m_pData->m_pObjectShell->GetCreateMode() != SfxObjectCreateMode::EMBEDDED ) + { + Reference< embed::XStorage > xConfigStorage; + static const OUStringLiteral aUIConfigFolderName( u"Configurations2" ); + + 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->m_bModifiedSinceLastSave = false; + } + break; + + case SfxEventHintId::SaveAsDocDone: + { + m_pData->m_sURL = m_pData->m_pObjectShell->GetMedium()->GetName(); + + SfxItemSet *pSet = m_pData->m_pObjectShell->GetMedium()->GetItemSet(); + Sequence< beans::PropertyValue > aArgs; + TransformItems( SID_SAVEASDOC, *pSet, 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->m_bModifiedSinceLastSave = false; + } + break; + + case SfxEventHintId::ModifyChanged: + { + m_pData->m_bModifiedSinceLastSave = 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( pNamedHint->GetEventName(), pViewHint ? pViewHint->GetController() : Reference< frame::XController2 >(), aSupplement ); + } + + 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 +{ + comphelper::OInterfaceContainerHelper2* pIC = m_pData->m_aInterfaceContainer.getContainer( cppu::UnoType<util::XModifyListener>::get()); + if ( pIC ) + { + lang::EventObject aEvent( static_cast<frame::XModel *>(const_cast<SfxBaseModel *>(this)) ); + pIC->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->m_bModifiedSinceLastSave = 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(); + + bool bSaved = false; + if ( !bSaveTo && m_pData->m_pObjectShell.is() && !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 + + ::comphelper::SequenceAsHashMap aArgHash( seqArguments ); + static const OUStringLiteral aFilterString( u"FilterName" ); + 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() ); + bSaved = true; + } + 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 + } + } + } + } + } + } + + if ( bSaved || !m_pData->m_pObjectShell.is() ) + return; + + SfxGetpApp()->NotifyEvent( SfxEventHint( bSaveTo ? SfxEventHintId::SaveToDoc : SfxEventHintId::SaveAsDoc, GlobalEventConfig::GetEventName( bSaveTo ? GlobalEventId::SAVETODOC : GlobalEventId::SAVEASDOC ), + m_pData->m_pObjectShell.get() ) ); + + std::optional<SfxAllItemSet> pItemSet(SfxGetpApp()->GetPool()); + pItemSet->Put(SfxStringItem(SID_FILE_NAME, sURL)); + if ( bSaveTo ) + pItemSet->Put(SfxBoolItem(SID_SAVETO, true)); + + 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(); + + ErrCode 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::ErrorCodeRequest aErrorCode; + aErrorCode.ErrCode = sal_uInt32(nErrCode); + 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() ) ); + + std::stringstream aErrCode; + aErrCode << nErrCode; + throw task::ErrorCodeIOException( + "SfxBaseModel::impl_store <" + sURL + "> failed: " + OUString::fromUtf8(aErrCode.str().c_str()), + Reference< XInterface >(), sal_uInt32(nErrCode)); + } +} + + +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; + + comphelper::OInterfaceContainerHelper2* pIC = + m_pData->m_aInterfaceContainer.getContainer( cppu::UnoType<document::XDocumentEventListener>::get()); + if ( pIC ) + { + SAL_INFO("sfx.doc", "SfxDocumentEvent: " + aName); + + document::DocumentEvent aDocumentEvent( static_cast<frame::XModel*>(this), aName, xController, supplement ); + + pIC->forEach< document::XDocumentEventListener, NotifySingleListenerIgnoreRE< document::XDocumentEventListener, document::DocumentEvent > >( + NotifySingleListenerIgnoreRE< document::XDocumentEventListener, document::DocumentEvent >( + &document::XDocumentEventListener::documentEventOccured, + aDocumentEvent ) ); + } + + pIC = m_pData->m_aInterfaceContainer.getContainer( cppu::UnoType<document::XEventListener>::get()); + if ( pIC ) + { + SAL_INFO("sfx.doc", "SfxEvent: " + aName); + + document::EventObject aEvent( static_cast<frame::XModel*>(this), aName ); + + pIC->forEach< document::XEventListener, NotifySingleListenerIgnoreRE< document::XEventListener, document::EventObject > >( + 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; + + comphelper::OInterfaceContainerHelper2* pIC = m_pData->m_aInterfaceContainer.getContainer( + cppu::UnoType<document::XEventListener>::get()); + if( !pIC ) + + return; + + comphelper::OInterfaceIteratorHelper2 aIt( *pIC ); + while( aIt.hasMoreElements() ) + { + try + { + static_cast<document::XEventListener *>(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() + && ( (nullptr != m_pData->m_aInterfaceContainer.getContainer( cppu::UnoType<document::XEventListener>::get()) ) + || !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 ) + { + OUStringBuffer aStrBuf( ".uno:" ); + aStrBuf.appendAscii( pSlot->GetUnoName() ); + + aCommand = aStrBuf.makeStringAndClear(); + 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 const OUStringLiteral aMediaTypeProp( u"MediaType" ); + 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) ) + { + ErrCode nError = m_pData->m_pObjectShell->GetErrorCode(); + nError = nError ? nError : ERRCODE_IO_CANTREAD; + throw task::ErrorCodeIOException( + "SfxBaseModel::loadFromStorage: " + nError.toHexString(), + Reference< XInterface >(), sal_uInt32(nError)); + } + 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(); + } + } + + ErrCode 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.toHexString(), + Reference< XInterface >(), sal_uInt32(nError)); + } +} + +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 ) ) + { + ErrCode nError = m_pData->m_pObjectShell->GetErrorCode(); + nError = nError ? nError : ERRCODE_IO_GENERAL; + throw task::ErrorCodeIOException( + "SfxBaseModel::switchToStorage: " + nError.toHexString(), + Reference< XInterface >(), sal_uInt32(nError)); + } + 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_aInterfaceContainer.addInterface( + cppu::UnoType<document::XStorageChangeListener>::get(), xListener ); +} + +void SAL_CALL SfxBaseModel::removeStorageChangeListener( + const Reference< document::XStorageChangeListener >& xListener ) +{ + SfxModelGuard aGuard( *this ); + + m_pData->m_aInterfaceContainer.removeInterface( + cppu::UnoType<document::XStorageChangeListener>::get(), 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 const OUStringLiteral aServerTitle( u"TitleOnServer" ); + if ( xProps->hasPropertyByName( aServerTitle ) ) + { + Any aAny = aContent.getPropertyValue( aServerTitle ); + aAny >>= aResult; + } + } + } + catch (const ucb::ContentCreationException &) + { + } + catch (const ucb::CommandAbortedException &) + { + } + const SfxBoolItem* pRepairedDocItem = SfxItemSet::GetItem<SfxBoolItem>(pMedium->GetItemSet(), 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 ); + SAL_WARN_IF( !pViewFrame , "sfx.doc", "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 000000000..a14ff68b1 --- /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 000000000..d511fa31a --- /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 000000000..fb949def0 --- /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 000000000..9c135993c --- /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 000000000..f60f45982 --- /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 000000000..7a06950c5 --- /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 000000000..42e570e75 --- /dev/null +++ b/sfx2/source/doc/templatedlg.cxx @@ -0,0 +1,1396 @@ +/* -*- 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 OUStringLiteral TM_SETTING_MANAGER = u"TemplateManager"; +constexpr OUStringLiteral TM_SETTING_LASTFOLDER = u"LastFolder"; +constexpr OUStringLiteral TM_SETTING_LASTAPPLICATION = u"LastApplication"; +constexpr OUStringLiteral TM_SETTING_VIEWMODE = u"ViewMode"; + +#define MNI_ACTION_NEW_FOLDER "new" +#define MNI_ACTION_RENAME_FOLDER "rename" +#define MNI_ACTION_DELETE_FOLDER "delete" +#define MNI_ACTION_DEFAULT "default" +#define MNI_ACTION_DEFAULT_WRITER "default_writer" +#define MNI_ACTION_DEFAULT_CALC "default_calc" +#define MNI_ACTION_DEFAULT_IMPRESS "default_impress" +#define MNI_ACTION_DEFAULT_DRAW "default_draw" +#define MNI_ACTION_IMPORT "import_template" +#define MNI_ACTION_EXTENSIONS "extensions" +#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 acommodate 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 OString&, 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::string_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")) + , mxSelectLabel(m_xBuilder->weld_label("select_label")) + , mxNewCategoryEdit(m_xBuilder->weld_entry("category_entry")) + , mxCreateLabel(m_xBuilder->weld_label("create_label")) + , 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 000000000..4e64afd7f --- /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 000000000..60f193e85 --- /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 OUStringLiteral ZOOM_PARAM_VALUE = u"Value"; +constexpr OUStringLiteral ZOOM_PARAM_VALUESET = u"ValueSet"; +constexpr OUStringLiteral ZOOM_PARAM_TYPE = u"Type"; +#define ZOOM_PARAMS 3 + + +SvxZoomItem::SvxZoomItem +( + SvxZoomType eZoomType, + sal_uInt16 nVal, + sal_uInt16 _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: */ |