/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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::uno; using namespace ::com::sun::star::util; using namespace ::com::sun::star::system; namespace { // - class PrepareListener_Impl ------------------------------------------ class PrepareListener_Impl : public ::cppu::WeakImplHelper< css::frame::XStatusListener > { bool m_bState; public: PrepareListener_Impl(); // css.frame.XStatusListener virtual void SAL_CALL statusChanged(const css::frame::FeatureStateEvent& aEvent) override; // css.lang.XEventListener virtual void SAL_CALL disposing(const css::lang::EventObject& aEvent) override; bool IsSet() const {return m_bState;} }; } PrepareListener_Impl::PrepareListener_Impl() : m_bState( false ) { } void PrepareListener_Impl::statusChanged(const css::frame::FeatureStateEvent& rEvent) { if( rEvent.IsEnabled ) rEvent.State >>= m_bState; else m_bState = false; } void PrepareListener_Impl::disposing(const css::lang::EventObject& /*rEvent*/) { } // class SfxMailModel ----------------------------------------------- const char16_t PDF_DOCUMENT_TYPE[] = u"pdf_Portable_Document_Format"; SfxMailModel::SaveResult SfxMailModel::ShowFilterOptionsDialog( const uno::Reference< lang::XMultiServiceFactory >& xSMGR, const uno::Reference< frame::XModel >& xModel, const OUString& rFilterName, std::u16string_view rType, bool bModified, sal_Int32& rNumArgs, css::uno::Sequence< css::beans::PropertyValue >& rArgs ) { SaveResult eRet( SAVE_ERROR ); try { uno::Sequence < beans::PropertyValue > aProps; css::uno::Reference< css::container::XNameAccess > xFilterCFG( xSMGR->createInstance( u"com.sun.star.document.FilterFactory"_ustr ), uno::UNO_QUERY ); css::uno::Reference< css::util::XModifiable > xModifiable( xModel, css::uno::UNO_QUERY ); if ( !xFilterCFG.is() ) return eRet; uno::Any aAny = xFilterCFG->getByName( rFilterName ); if ( aAny >>= aProps ) { for (const auto& rProp : aProps) { if( rProp.Name == "UIComponent" ) { OUString aServiceName; rProp.Value >>= aServiceName; if( !aServiceName.isEmpty() ) { uno::Reference< ui::dialogs::XExecutableDialog > xFilterDialog( xSMGR->createInstance( aServiceName ), uno::UNO_QUERY ); uno::Reference< beans::XPropertyAccess > xFilterProperties( xFilterDialog, uno::UNO_QUERY ); if( xFilterDialog.is() && xFilterProperties.is() ) { uno::Reference< document::XExporter > xExporter( xFilterDialog, uno::UNO_QUERY ); if ( rType == PDF_DOCUMENT_TYPE ) { //add an internal property, used to tell the dialog we want to set a different //string for the ok button //used in filter/source/pdf/impdialog.cxx uno::Sequence< beans::PropertyValue > aFilterDataValue{ comphelper::makePropertyValue(u"_OkButtonString"_ustr, SfxResId(STR_PDF_EXPORT_SEND )) }; //add to the filterdata property, the only one the PDF export filter dialog will care for uno::Sequence< beans::PropertyValue > aPropsForDialog{ comphelper::makePropertyValue(u"FilterData"_ustr, aFilterDataValue) }; //when executing the dialog will merge the persistent FilterData properties xFilterProperties->setPropertyValues( aPropsForDialog ); } if( xExporter.is() ) xExporter->setSourceDocument( xModel ); if( xFilterDialog->execute() ) { //get the filter data const uno::Sequence< beans::PropertyValue > aPropsFromDialog = xFilterProperties->getPropertyValues(); //add them to the args auto pProp = std::find_if(aPropsFromDialog.begin(), aPropsFromDialog.end(), [](const beans::PropertyValue& rDialogProp) { return rDialogProp.Name == "FilterData"; }); if (pProp != aPropsFromDialog.end()) { //found the filterdata, add to the storing argument rArgs.realloc( ++rNumArgs ); auto pArgs = rArgs.getArray(); pArgs[rNumArgs-1].Name = pProp->Name; pArgs[rNumArgs-1].Value = pProp->Value; } eRet = SAVE_SUCCESSFUL; } else { // cancel from dialog, then do not send // If the model is not modified, it could be modified by the dispatch calls. // Therefore set back to modified = false. This should not hurt if we call // on a non-modified model. if ( !bModified ) { try { xModifiable->setModified( false ); } catch( css::beans::PropertyVetoException& ) { } } eRet = SAVE_CANCELLED; } } break; } } } } } catch( css::uno::RuntimeException& ) { throw; } catch( uno::Exception& ) { } return eRet; } bool SfxMailModel::IsEmpty() const { return maAttachedDocuments.empty(); } SfxMailModel::SaveResult SfxMailModel::SaveDocumentAsFormat( const OUString& aSaveFileName, const css::uno::Reference< css::uno::XInterface >& xFrameOrModel, const OUString& rType, OUString& rFileNamePath ) { SaveResult eRet( SAVE_ERROR ); bool bSendAsPDF = ( rType == PDF_DOCUMENT_TYPE ); css::uno::Reference< css::lang::XMultiServiceFactory > xSMGR = ::comphelper::getProcessServiceFactory(); const css::uno::Reference< css::uno::XComponentContext >& xContext = ::comphelper::getProcessComponentContext(); if (!xContext.is()) return eRet; css::uno::Reference< css::frame::XModuleManager2 > xModuleManager( css::frame::ModuleManager::create(xContext) ); OUString aModule; try { aModule = xModuleManager->identify( xFrameOrModel ); } catch ( css::uno::RuntimeException& ) { throw; } catch ( css::uno::Exception& ) { } css::uno::Reference< css::frame::XFrame > xFrame( xFrameOrModel, css::uno::UNO_QUERY ); css::uno::Reference< css::frame::XModel > xModel( xFrameOrModel, css::uno::UNO_QUERY ); if ( xFrame.is() ) { css::uno::Reference< css::frame::XController > xController = xFrame->getController(); if ( xController.is() ) xModel = xController->getModel(); } // We need at least a valid module name and model reference if ( !aModule.isEmpty() && xModel.is() ) { bool bModified( false ); bool bHasLocation( false ); bool bStoreTo( false ); css::uno::Reference< css::util::XModifiable > xModifiable( xModel, css::uno::UNO_QUERY ); css::uno::Reference< css::frame::XStorable > xStorable( xModel, css::uno::UNO_QUERY ); if ( xModifiable.is() ) bModified = xModifiable->isModified(); if ( xStorable.is() ) { OUString aLocation = xStorable->getLocation(); INetURLObject aFileObj( aLocation ); bool bPrivateProtocol = ( aFileObj.GetProtocol() == INetProtocol::PrivSoffice ); bHasLocation = !aLocation.isEmpty() && !bPrivateProtocol; OSL_ASSERT( !bPrivateProtocol ); } if ( !rType.isEmpty() ) bStoreTo = true; if ( xStorable.is() ) { OUString aFilterName; OUString aTypeName( rType ); OUString aFileName; OUString aExtension; css::uno::Reference< css::container::XContainerQuery > xContainerQuery( xSMGR->createInstance( u"com.sun.star.document.FilterFactory"_ustr ), css::uno::UNO_QUERY ); if ( bStoreTo ) { // Retrieve filter from type css::uno::Sequence< css::beans::NamedValue > aQuery( bSendAsPDF ? 3 : 2 ); auto pQuery = aQuery.getArray(); pQuery[0].Name = "Type"; pQuery[0].Value <<= aTypeName; pQuery[1].Name = "DocumentService"; pQuery[1].Value <<= aModule; if( bSendAsPDF ) { // #i91419# // FIXME: we want just an export filter. However currently we need // exact flag value as detailed in the filter configuration to get it // this seems to be a bug // without flags we get an import filter here, which is also unwanted pQuery[2].Name = "Flags"; pQuery[2].Value <<= sal_Int32(0x80042); // SfxFilterFlags: EXPORT ALIEN 3RDPARTY } css::uno::Reference< css::container::XEnumeration > xEnumeration = xContainerQuery->createSubSetEnumerationByProperties( aQuery ); if ( xEnumeration->hasMoreElements() ) { ::comphelper::SequenceAsHashMap aFilterPropsHM( xEnumeration->nextElement() ); aFilterName = aFilterPropsHM.getUnpackedValueOrDefault( u"Name"_ustr, OUString() ); } if ( bHasLocation ) { // Retrieve filter from media descriptor ::comphelper::SequenceAsHashMap aMediaDescrPropsHM( xModel->getArgs() ); OUString aOrgFilterName = aMediaDescrPropsHM.getUnpackedValueOrDefault( u"FilterName"_ustr, OUString() ); if ( aOrgFilterName == aFilterName ) { // We should save the document in the original format. Therefore this // is not a storeTo operation. To support signing in this case, reset // bStoreTo flag. bStoreTo = false; } } } else { if ( bHasLocation ) { // Retrieve filter from media descriptor ::comphelper::SequenceAsHashMap aMediaDescrPropsHM( xModel->getArgs() ); aFilterName = aMediaDescrPropsHM.getUnpackedValueOrDefault( u"FilterName"_ustr, OUString() ); } if ( !bHasLocation || aFilterName.isEmpty()) { // Retrieve the user defined default filter try { ::comphelper::SequenceAsHashMap aFilterPropsHM( xModuleManager->getByName( aModule ) ); aFilterName = aFilterPropsHM.getUnpackedValueOrDefault( u"ooSetupFactoryDefaultFilter"_ustr, OUString() ); css::uno::Reference< css::container::XNameAccess > xNameAccess( xContainerQuery, css::uno::UNO_QUERY ); if ( xNameAccess.is() ) { ::comphelper::SequenceAsHashMap aFilterPropsHM2( xNameAccess->getByName( aFilterName ) ); aTypeName = aFilterPropsHM2.getUnpackedValueOrDefault( u"Type"_ustr, OUString() ); } } catch ( css::container::NoSuchElementException& ) { } catch ( css::beans::UnknownPropertyException& ) { } } } // No filter found => error // No type and no location => error if (( aFilterName.isEmpty() ) || ( aTypeName.isEmpty() && !bHasLocation )) return eRet; // Determine file name and extension if ( bHasLocation && !bStoreTo ) { INetURLObject aFileObj( xStorable->getLocation() ); aExtension = aFileObj.getExtension(); } else { css::uno::Reference< container::XNameAccess > xTypeDetection( xSMGR->createInstance( u"com.sun.star.document.TypeDetection"_ustr ), css::uno::UNO_QUERY ); if ( xTypeDetection.is() ) { try { ::comphelper::SequenceAsHashMap aTypeNamePropsHM( xTypeDetection->getByName( aTypeName ) ); uno::Sequence< OUString > aExtensions = aTypeNamePropsHM.getUnpackedValueOrDefault( u"Extensions"_ustr, ::uno::Sequence< OUString >() ); if ( aExtensions.hasElements() ) aExtension = aExtensions[0]; } catch ( css::container::NoSuchElementException& ) { } } } // Use provided save file name. If empty determine file name aFileName = aSaveFileName; if ( aFileName.isEmpty() ) { if ( !bHasLocation ) { // Create a noname file name with the correct extension aFileName = "noname"; } else { // Determine file name from model INetURLObject aFileObj( xStorable->getLocation() ); aFileName = aFileObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::NONE ); } } // No file name => error if ( aFileName.isEmpty() ) return eRet; OSL_ASSERT( !aFilterName.isEmpty() ); OSL_ASSERT( !aFileName.isEmpty() ); // Creates a temporary directory to store a predefined file into it. // This makes it possible to store the file for "send document as e-mail" // with the original file name. We cannot use the original file as // some mail programs need exclusive access. INetURLObject aFilePathObj( ::utl::CreateTempURL(nullptr, true) ); aFilePathObj.insertName( aFileName ); aFilePathObj.setExtension( aExtension ); OUString aFileURL = aFilePathObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); sal_Int32 nNumArgs(1); static constexpr OUString aPasswordPropName( u"Password"_ustr ); css::uno::Sequence< css::beans::PropertyValue > aArgs{ comphelper::makePropertyValue( u"FilterName"_ustr, aFilterName) }; ::comphelper::SequenceAsHashMap aMediaDescrPropsHM( xModel->getArgs() ); OUString aPassword = aMediaDescrPropsHM.getUnpackedValueOrDefault( aPasswordPropName, OUString() ); if ( !aPassword.isEmpty() ) { aArgs.realloc( ++nNumArgs ); auto pArgs = aArgs.getArray(); pArgs[nNumArgs-1].Name = aPasswordPropName; pArgs[nNumArgs-1].Value <<= aPassword; } bool bNeedsPreparation = false; css::util::URL aPrepareURL; css::uno::Reference< css::frame::XDispatch > xPrepareDispatch; css::uno::Reference< css::frame::XDispatchProvider > xDispatchProvider( xFrame, css::uno::UNO_QUERY ); css::uno::Reference< css::util::XURLTransformer > xURLTransformer( css::util::URLTransformer::create( xContext ) ); if( !bSendAsPDF ) { try { // check if the document needs to be prepared for sending as mail (embedding of links, removal of invisible content) aPrepareURL.Complete = ".uno:PrepareMailExport"; xURLTransformer->parseStrict( aPrepareURL ); if ( xDispatchProvider.is() ) { xPrepareDispatch.set( xDispatchProvider->queryDispatch( aPrepareURL, OUString(), 0 )); if ( xPrepareDispatch.is() ) { rtl::Reference pPrepareListener = new PrepareListener_Impl; xPrepareDispatch->addStatusListener( pPrepareListener, aPrepareURL ); bNeedsPreparation = pPrepareListener->IsSet(); xPrepareDispatch->removeStatusListener( pPrepareListener, aPrepareURL ); } } } catch ( css::uno::RuntimeException& ) { throw; } catch ( css::uno::Exception& ) { } } if ( bModified || !bHasLocation || bStoreTo || bNeedsPreparation ) { // Document is modified, is newly created or should be stored in a special format try { if( bNeedsPreparation && xPrepareDispatch.is() ) { try { css::uno::Sequence< css::beans::PropertyValue > aDispatchArgs; xPrepareDispatch->dispatch( aPrepareURL, aDispatchArgs ); } catch ( css::uno::RuntimeException& ) { throw; } catch ( css::uno::Exception& ) { } } //check if this is the pdf output filter (i#64555) if( bSendAsPDF ) { SaveResult eShowPDFFilterDialog = ShowFilterOptionsDialog( xSMGR, xModel, aFilterName, rType, bModified, nNumArgs, aArgs ); // don't continue on dialog cancel or error if ( eShowPDFFilterDialog != SAVE_SUCCESSFUL ) return eShowPDFFilterDialog; } xStorable->storeToURL( aFileURL, aArgs ); rFileNamePath = aFileURL; eRet = SAVE_SUCCESSFUL; if( !bSendAsPDF ) { css::util::URL aURL; // #i30432# notify that export is finished - the Writer may want to restore removed content aURL.Complete = ".uno:MailExportFinished"; xURLTransformer->parseStrict( aURL ); if ( xDispatchProvider.is() ) { css::uno::Reference< css::frame::XDispatch > xDispatch( xDispatchProvider->queryDispatch( aURL, OUString(), 0 )); if ( xDispatch.is() ) { try { css::uno::Sequence< css::beans::PropertyValue > aDispatchArgs; xDispatch->dispatch( aURL, aDispatchArgs ); } catch ( css::uno::RuntimeException& ) { throw; } catch ( css::uno::Exception& ) { } } } } // If the model is not modified, it could be modified by the dispatch calls. // Therefore set back to modified = false. This should not hurt if we call // on a non-modified model. if ( !bModified ) { try { xModifiable->setModified( false ); } catch( css::beans::PropertyVetoException& ) { } } } catch ( css::io::IOException& ) { eRet = SAVE_ERROR; } } else { // We need 1:1 copy of the document to preserve an added signature. aArgs.realloc( ++nNumArgs ); auto pArgs = aArgs.getArray(); pArgs[nNumArgs-1].Name = "CopyStreamIfPossible"; pArgs[nNumArgs-1].Value <<= true; try { xStorable->storeToURL( aFileURL, aArgs ); rFileNamePath = aFileURL; eRet = SAVE_SUCCESSFUL; } catch ( css::io::IOException& ) { eRet = SAVE_ERROR; } } } } return eRet; } SfxMailModel::SfxMailModel() { } SfxMailModel::~SfxMailModel() { } void SfxMailModel::AddToAddress( const OUString& rAddress ) { // don't add an empty address if ( !rAddress.isEmpty() ) { if ( !mpToList ) // create the list mpToList.reset(new AddressList_Impl); // add address to list mpToList->push_back( rAddress ); } } SfxMailModel::SendMailResult SfxMailModel::AttachDocument( const css::uno::Reference< css::uno::XInterface >& xFrameOrModel, const OUString& sAttachmentTitle ) { OUString sFileName; SaveResult eSaveResult = SaveDocumentAsFormat( sAttachmentTitle, xFrameOrModel, OUString()/*sDocumentType*/, sFileName ); if ( eSaveResult == SAVE_SUCCESSFUL && !sFileName.isEmpty() ) maAttachedDocuments.push_back(sFileName); return eSaveResult == SAVE_SUCCESSFUL ? SEND_MAIL_OK : SEND_MAIL_ERROR; } SfxMailModel::SendMailResult SfxMailModel::Send( const css::uno::Reference< css::frame::XFrame >& xFrame ) { OSL_ENSURE(!maAttachedDocuments.empty(),"No document added!"); SendMailResult eResult = SEND_MAIL_ERROR; if ( !maAttachedDocuments.empty() ) { const css::uno::Reference < XComponentContext >& xContext = ::comphelper::getProcessComponentContext(); css::uno::Reference< XSimpleMailClientSupplier > xSimpleMailClientSupplier; // Prefer the SimpleSystemMail service if available try { xSimpleMailClientSupplier = SimpleSystemMail::create( xContext ); } catch ( const uno::Exception & ) {} if ( ! xSimpleMailClientSupplier.is() ) { try { xSimpleMailClientSupplier = SimpleCommandMail::create( xContext ); } catch ( const uno::Exception & ) {} } if ( xSimpleMailClientSupplier.is() ) { css::uno::Reference< XSimpleMailClient > xSimpleMailClient = xSimpleMailClientSupplier->querySimpleMailClient(); if ( !xSimpleMailClient.is() ) { // no mail client support => message box! return SEND_MAIL_ERROR; } // we have a simple mail client css::uno::Reference< XSimpleMailMessage > xSimpleMailMessage = xSimpleMailClient->createSimpleMailMessage(); if ( xSimpleMailMessage.is() ) { sal_Int32 nSendFlags = SimpleMailClientFlags::DEFAULTS; if ( maFromAddress.isEmpty() ) { // from address not set, try figure out users e-mail address CreateFromAddress_Impl( maFromAddress ); } xSimpleMailMessage->setOriginator( maFromAddress ); size_t nToCount = mpToList ? mpToList->size() : 0; // set recipient (only one) for this simple mail server!! if ( nToCount >= 1 ) { xSimpleMailMessage->setRecipient( mpToList->at( 0 ) ); nSendFlags = SimpleMailClientFlags::NO_USER_INTERFACE; } // all other recipient must be handled with CC recipients! if ( nToCount > 1 ) { Sequence< OUString > aCcRecipientSeq( nToCount - 1 ); std::copy_n(std::next(mpToList->begin()), aCcRecipientSeq.getLength(), aCcRecipientSeq.getArray()); xSimpleMailMessage->setCcRecipient( aCcRecipientSeq ); } Sequence< OUString > aAttachmentSeq(maAttachedDocuments.data(),maAttachedDocuments.size()); if ( xSimpleMailMessage->getSubject().isEmpty() ) { INetURLObject url( maAttachedDocuments[0], INetURLObject::EncodeMechanism::WasEncoded); OUString subject( url.getBase( INetURLObject::LAST_SEGMENT, false, INetURLObject::DecodeMechanism::WithCharset)); if (subject.isEmpty()) { subject = maAttachedDocuments[0]; } if ( maAttachedDocuments.size() > 1 ) subject += ", ..."; xSimpleMailMessage->setSubject( subject ); } xSimpleMailMessage->setAttachement( aAttachmentSeq ); bool bSend( false ); try { xSimpleMailClient->sendSimpleMailMessage( xSimpleMailMessage, nSendFlags ); bSend = true; } catch ( IllegalArgumentException& ) { } catch ( Exception& ) { } if ( !bSend ) { css::uno::Reference< css::awt::XWindow > xParentWindow = xFrame->getContainerWindow(); SolarMutexGuard aGuard; std::unique_ptr xBuilder(Application::CreateBuilder(Application::GetFrameWeld(xParentWindow), u"sfx/ui/errorfindemaildialog.ui"_ustr)); std::unique_ptr xBox(xBuilder->weld_message_dialog(u"ErrorFindEmailDialog"_ustr)); xBox->run(); eResult = SEND_MAIL_CANCELLED; } else eResult = SEND_MAIL_OK; } } } else eResult = SEND_MAIL_CANCELLED; return eResult; } SfxMailModel::SendMailResult SfxMailModel::SaveAndSend( const css::uno::Reference< css::frame::XFrame >& xFrame, const OUString& rTypeName ) { SaveResult eSaveResult; SendMailResult eResult = SEND_MAIL_ERROR; OUString aFileName; eSaveResult = SaveDocumentAsFormat( OUString(), xFrame, rTypeName, aFileName ); if ( eSaveResult == SAVE_SUCCESSFUL ) { maAttachedDocuments.push_back( aFileName ); return Send( xFrame ); } else if ( eSaveResult == SAVE_CANCELLED ) eResult = SEND_MAIL_CANCELLED; return eResult; } // functions ------------------------------------------------------------- bool CreateFromAddress_Impl( OUString& rFrom ) /* [Description] This function tries to create a From-address with the help of IniManagers. For this the fields 'first name', 'Name' and 'Email' are read from the application-ini-data. If these fields are not set, FALSE is returned. [Return value] sal_True: Address could be created. sal_False: Address could not be created. */ { SvtUserOptions aUserCFG; OUString aName = aUserCFG.GetLastName (); OUString aFirstName = aUserCFG.GetFirstName (); if ( !aFirstName.isEmpty() || !aName.isEmpty() ) { if ( !aFirstName.isEmpty() ) { rFrom = comphelper::string::strip(aFirstName, ' '); if ( !aName.isEmpty() ) rFrom += " "; } rFrom += comphelper::string::strip(aName, ' '); // remove illegal characters rFrom = rFrom.replaceAll("<", "").replaceAll(">", "").replaceAll("@", ""); } OUString aEmailName = aUserCFG.GetEmail(); // remove illegal characters aEmailName = aEmailName.replaceAll("<", "").replaceAll(">", ""); if ( !aEmailName.isEmpty() ) { if ( !rFrom.isEmpty() ) rFrom += " "; rFrom += "<" + comphelper::string::strip(aEmailName, ' ') + ">"; } else rFrom.clear(); return !rFrom.isEmpty(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */