/* -*- 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 "DatabaseForm.hxx" #include "EventThread.hxx" #include #include #include "GroupManager.hxx" #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 #include #include #include #include #include #include #include #include using namespace ::dbtools; using namespace ::comphelper; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::sdb; using namespace ::com::sun::star::sdbc; using namespace ::com::sun::star::sdbcx; using namespace ::com::sun::star::beans; using namespace ::com::sun::star::container; using namespace ::com::sun::star::task; using namespace ::com::sun::star::frame; using namespace ::com::sun::star::form; using namespace ::com::sun::star::awt; using namespace ::com::sun::star::io; using namespace ::com::sun::star::lang; using namespace ::com::sun::star::util; namespace frm { namespace { class DocumentModifyGuard { public: explicit DocumentModifyGuard( const Reference< XInterface >& _rxFormComponent ) :m_xDocumentModify( getXModel( _rxFormComponent ), UNO_QUERY ) { impl_changeModifiableFlag_nothrow( false ); } ~DocumentModifyGuard() { impl_changeModifiableFlag_nothrow( true ); } private: void impl_changeModifiableFlag_nothrow( const bool _enable ) { try { if ( m_xDocumentModify.is() ) _enable ? m_xDocumentModify->enableSetModified() : m_xDocumentModify->disableSetModified(); } catch(const Exception&) { DBG_UNHANDLED_EXCEPTION("forms.component"); } } private: Reference< XModifiable2 > m_xDocumentModify; }; } // submitting and resetting html-forms asynchronously class OFormSubmitResetThread: public OComponentEventThread { protected: // process an event. while processing the mutex isn't locked, and pCompImpl // is made sure to remain valid virtual void processEvent( ::cppu::OComponentHelper* _pCompImpl, const EventObject* _pEvt, const Reference& _rControl, bool _bSubmit) override; public: explicit OFormSubmitResetThread(ODatabaseForm* pControl) : OComponentEventThread(pControl) { } }; void OFormSubmitResetThread::processEvent( ::cppu::OComponentHelper* pCompImpl, const EventObject *_pEvt, const Reference& _rControl, bool _bSubmit) { if (_bSubmit) static_cast(pCompImpl)->submit_impl(_rControl, *static_cast(_pEvt)); else static_cast(pCompImpl)->reset_impl(true); } //= ODatabaseForm Sequence SAL_CALL ODatabaseForm::getImplementationId() { return css::uno::Sequence(); } Sequence SAL_CALL ODatabaseForm::getTypes() { // ask the aggregate Sequence aAggregateTypes; Reference xAggregateTypes; if (query_aggregation(m_xAggregate, xAggregateTypes)) aAggregateTypes = xAggregateTypes->getTypes(); Sequence< Type > aRet = concatSequences( aAggregateTypes, ODatabaseForm_BASE1::getTypes(), OFormComponents::getTypes() ); aRet = concatSequences( aRet, ODatabaseForm_BASE2::getTypes(), ODatabaseForm_BASE3::getTypes() ); return concatSequences( aRet, OPropertySetAggregationHelper::getTypes() ); } Any SAL_CALL ODatabaseForm::queryAggregation(const Type& _rType) { Any aReturn = ODatabaseForm_BASE1::queryInterface(_rType); // our own interfaces if (!aReturn.hasValue()) { aReturn = ODatabaseForm_BASE2::queryInterface(_rType); // property set related interfaces if (!aReturn.hasValue()) { aReturn = OPropertySetAggregationHelper::queryInterface(_rType); // form component collection related interfaces if (!aReturn.hasValue()) { aReturn = OFormComponents::queryAggregation(_rType); // interfaces already present in the aggregate which we want to reroute // only available if we could create the aggregate if (!aReturn.hasValue() && m_xAggregateAsRowSet.is()) aReturn = ODatabaseForm_BASE3::queryInterface(_rType); // aggregate interfaces // (ask the aggregated object _after_ the OComponentHelper (base of OFormComponents), // so calls to the XComponent interface reach us and not the aggregation) if (!aReturn.hasValue() && m_xAggregate.is()) aReturn = m_xAggregate->queryAggregation(_rType); } } } return aReturn; } ODatabaseForm::ODatabaseForm(const Reference& _rxContext) :OFormComponents(_rxContext) ,OPropertySetAggregationHelper(OComponentHelper::rBHelper) ,OPropertyChangeListener(m_aMutex) ,m_aLoadListeners(m_aMutex) ,m_aRowSetApproveListeners(m_aMutex) ,m_aSubmitListeners(m_aMutex) ,m_aErrorListeners(m_aMutex) ,m_aResetListeners( *this, m_aMutex ) ,m_aPropertyBagHelper( *this ) ,m_aParameterManager( m_aMutex, _rxContext ) ,m_aFilterManager() ,m_nResetsPending(0) ,m_nPrivileges(0) ,m_bInsertOnly( false ) ,m_eSubmitMethod(FormSubmitMethod_GET) ,m_eSubmitEncoding(FormSubmitEncoding_URL) ,m_eNavigation(NavigationBarMode_CURRENT) ,m_bAllowInsert(true) ,m_bAllowUpdate(true) ,m_bAllowDelete(true) ,m_bLoaded(false) ,m_bSubForm(false) ,m_bForwardingConnection(false) ,m_bSharingConnection( false ) { impl_construct(); } ODatabaseForm::ODatabaseForm( const ODatabaseForm& _cloneSource ) :OFormComponents( _cloneSource ) ,OPropertySetAggregationHelper( OComponentHelper::rBHelper ) ,OPropertyChangeListener( m_aMutex ) ,ODatabaseForm_BASE1() ,ODatabaseForm_BASE2() ,ODatabaseForm_BASE3() ,IPropertyBagHelperContext() ,m_aLoadListeners( m_aMutex ) ,m_aRowSetApproveListeners( m_aMutex ) ,m_aSubmitListeners( m_aMutex ) ,m_aErrorListeners( m_aMutex ) ,m_aResetListeners( *this, m_aMutex ) ,m_aPropertyBagHelper( *this ) ,m_aParameterManager( m_aMutex, _cloneSource.m_xContext ) ,m_aFilterManager() ,m_nResetsPending( 0 ) ,m_nPrivileges( 0 ) ,m_bInsertOnly( _cloneSource.m_bInsertOnly ) ,m_aControlBorderColorFocus( _cloneSource.m_aControlBorderColorFocus ) ,m_aControlBorderColorMouse( _cloneSource.m_aControlBorderColorMouse ) ,m_aControlBorderColorInvalid( _cloneSource.m_aControlBorderColorInvalid ) ,m_aDynamicControlBorder( _cloneSource.m_aDynamicControlBorder ) ,m_sName( _cloneSource.m_sName ) ,m_aTargetURL( _cloneSource.m_aTargetURL ) ,m_aTargetFrame( _cloneSource.m_aTargetFrame ) ,m_eSubmitMethod( _cloneSource.m_eSubmitMethod ) ,m_eSubmitEncoding( _cloneSource.m_eSubmitEncoding ) ,m_eNavigation( _cloneSource.m_eNavigation ) ,m_bAllowInsert( _cloneSource.m_bAllowInsert ) ,m_bAllowUpdate( _cloneSource.m_bAllowUpdate ) ,m_bAllowDelete( _cloneSource.m_bAllowDelete ) ,m_bLoaded( false ) ,m_bSubForm( false ) ,m_bForwardingConnection( false ) ,m_bSharingConnection( false ) { impl_construct(); osl_atomic_increment( &m_refCount ); { // our aggregated rowset itself is not cloneable, so simply copy the properties ::comphelper::copyProperties( _cloneSource.m_xAggregateSet, m_xAggregateSet ); // also care for the dynamic properties: If the clone source has properties which we do not have, // then add them try { Reference< XPropertySet > xSourceProps( const_cast< ODatabaseForm& >( _cloneSource ).queryAggregation( cppu::UnoType::get() ), UNO_QUERY_THROW ); Reference< XPropertySetInfo > xSourcePSI( xSourceProps->getPropertySetInfo(), UNO_SET_THROW ); Reference< XPropertyState > xSourcePropState( xSourceProps, UNO_QUERY ); Reference< XPropertySetInfo > xDestPSI( getPropertySetInfo(), UNO_SET_THROW ); const Sequence< Property > aSourceProperties( xSourcePSI->getProperties() ); for ( auto const & sourceProperty : aSourceProperties ) { if ( xDestPSI->hasPropertyByName( sourceProperty.Name ) ) continue; // the initial value passed to XPropertyContainer is also used as default, usually. So, try // to retrieve the default of the source property Any aInitialValue; if ( xSourcePropState.is() ) { aInitialValue = xSourcePropState->getPropertyDefault( sourceProperty.Name ); } else { aInitialValue = xSourceProps->getPropertyValue( sourceProperty.Name ); } addProperty( sourceProperty.Name, sourceProperty.Attributes, aInitialValue ); setPropertyValue( sourceProperty.Name, xSourceProps->getPropertyValue( sourceProperty.Name ) ); } } catch(const RuntimeException&) { throw; } catch(const Exception&) { css::uno::Any a(cppu::getCaughtException()); throw WrappedTargetRuntimeException( "Could not clone the given database form.", *const_cast< ODatabaseForm* >( &_cloneSource ), a ); } } osl_atomic_decrement( &m_refCount ); } void ODatabaseForm::impl_construct() { // aggregate a row set osl_atomic_increment(&m_refCount); { m_xAggregate.set( m_xContext->getServiceManager()->createInstanceWithContext(SRV_SDB_ROWSET, m_xContext), UNO_QUERY_THROW ); m_xAggregateAsRowSet.set( m_xAggregate, UNO_QUERY_THROW ); setAggregation( m_xAggregate ); } // listen for the properties, important for Parameters if ( m_xAggregateSet.is() ) { m_xAggregatePropertyMultiplexer = new OPropertyChangeMultiplexer(this, m_xAggregateSet, false); m_xAggregatePropertyMultiplexer->addProperty(PROPERTY_COMMAND); m_xAggregatePropertyMultiplexer->addProperty(PROPERTY_ACTIVE_CONNECTION); } { Reference< XWarningsSupplier > xRowSetWarnings( m_xAggregate, UNO_QUERY ); m_aWarnings.setExternalWarnings( xRowSetWarnings ); } if ( m_xAggregate.is() ) { m_xAggregate->setDelegator( static_cast< XWeak* >( this ) ); } { m_aFilterManager.initialize( m_xAggregateSet ); m_aParameterManager.initialize( this, m_xAggregate ); declareForwardedProperty( PROPERTY_ID_ACTIVE_CONNECTION ); } osl_atomic_decrement( &m_refCount ); m_pGroupManager = new OGroupManager( this ); } ODatabaseForm::~ODatabaseForm() { m_pGroupManager.clear(); if (m_xAggregate.is()) m_xAggregate->setDelegator( nullptr ); m_aWarnings.setExternalWarnings( nullptr ); if (m_xAggregatePropertyMultiplexer.is()) { m_xAggregatePropertyMultiplexer->dispose(); m_xAggregatePropertyMultiplexer.clear(); } } // html tools OUString ODatabaseForm::GetDataEncoded(bool _bURLEncoded,const Reference& SubmitButton, const css::awt::MouseEvent& MouseEvt) { // Fill List of successful Controls HtmlSuccessfulObjList aSuccObjList; FillSuccessfulList( aSuccObjList, SubmitButton, MouseEvt ); // Aggregate the list to OUString OUStringBuffer aResult; OUString aName; OUString aValue; for ( HtmlSuccessfulObjList::iterator pSuccObj = aSuccObjList.begin(); pSuccObj < aSuccObjList.end(); ++pSuccObj ) { aName = pSuccObj->aName; aValue = pSuccObj->aValue; if( pSuccObj->nRepresentation == SUCCESSFUL_REPRESENT_FILE && !aValue.isEmpty() ) { // For File URLs we transfer the file name and not a URL, because Netscape does it like that INetURLObject aURL; aURL.SetSmartProtocol(INetProtocol::File); aURL.SetSmartURL(aValue); if( INetProtocol::File == aURL.GetProtocol() ) aValue = INetURLObject::decode(aURL.PathToFileName(), INetURLObject::DecodeMechanism::Unambiguous); } Encode( aName ); Encode( aValue ); aResult.append(aName); aResult.append('='); aResult.append(aValue); if (pSuccObj < aSuccObjList.end() - 1) { if ( _bURLEncoded ) aResult.append('&'); else aResult.append("\r\n"); } } aSuccObjList.clear(); return aResult.makeStringAndClear(); } // html tools Sequence ODatabaseForm::GetDataMultiPartEncoded(const Reference& SubmitButton, const css::awt::MouseEvent& MouseEvt, OUString& rContentType) { // Create Parent INetMIMEMessage aParent; aParent.EnableAttachMultipartFormDataChild(); // Fill List of successful Controls HtmlSuccessfulObjList aSuccObjList; FillSuccessfulList( aSuccObjList, SubmitButton, MouseEvt ); // Aggregate List to OUString for (auto const& succObj : aSuccObjList) { if( succObj.nRepresentation == SUCCESSFUL_REPRESENT_TEXT ) InsertTextPart( aParent, succObj.aName, succObj.aValue ); else if( succObj.nRepresentation == SUCCESSFUL_REPRESENT_FILE ) InsertFilePart( aParent, succObj.aName, succObj.aValue ); } // Delete List aSuccObjList.clear(); // Create MessageStream for parent INetMIMEMessageStream aMessStream(&aParent, true); // Copy MessageStream to SvStream SvMemoryStream aMemStream; std::unique_ptr pBuf(new char[1025]); int nRead; while( (nRead = aMessStream.Read(pBuf.get(), 1024)) > 0 ) { aMemStream.WriteBytes(pBuf.get(), nRead); } pBuf.reset(); aMemStream.Flush(); aMemStream.Seek( 0 ); void const * pData = aMemStream.GetData(); sal_Int32 nLen = aMemStream.TellEnd(); rContentType = aParent.GetContentType(); return Sequence(static_cast(pData), nLen); } namespace { void appendDigits( sal_Int32 _nNumber, sal_Int8 nDigits, OUStringBuffer& _rOut ) { sal_Int32 nCurLen = _rOut.getLength(); _rOut.append( _nNumber ); while ( _rOut.getLength() - nCurLen < nDigits ) _rOut.insert( nCurLen, '0' ); } } void ODatabaseForm::AppendComponent(HtmlSuccessfulObjList& rList, const Reference& xComponentSet, const OUString& rNamePrefix, const Reference& rxSubmitButton, const css::awt::MouseEvent& MouseEvt) { if (!xComponentSet.is()) return; // TODO: Catch nested Forms; or would we need to submit them? if (!hasProperty(PROPERTY_CLASSID, xComponentSet)) return; // Get names if (!hasProperty(PROPERTY_NAME, xComponentSet)) return; sal_Int16 nClassId = 0; xComponentSet->getPropertyValue(PROPERTY_CLASSID) >>= nClassId; OUString aName; xComponentSet->getPropertyValue( PROPERTY_NAME ) >>= aName; if( aName.isEmpty() && nClassId != FormComponentType::IMAGEBUTTON) return; else // Extend name with the prefix aName = rNamePrefix + aName; switch( nClassId ) { // Buttons case FormComponentType::COMMANDBUTTON: { // We only evaluate the pressed Submit button // If one is passed at all if( rxSubmitButton.is() ) { Reference xSubmitButtonComponent(rxSubmitButton->getModel(), UNO_QUERY); if (xSubmitButtonComponent == xComponentSet && hasProperty(PROPERTY_LABEL, xComponentSet)) { // =