Repository: qgis/QGIS
Pull request #61515: [WFS Provider] Add a UI and URI parameter featureMode=default/SimpleFeatures/ComplexFeatures
Author: login: rouault
Labels: name: Needs Documentation
description: When merging a labeled PR, an issue will be created in the Doc repo.
name: ChangelogHarvested
description: This PR description has been harvested in the Changelog already.
Created: 2025-04-16T18:12:16Z | Merged: 2025-04-17T10:54:24Z

Fixes #61476 / #61493

The analysis of DescribeFeatureType response with the GMLAS driver makes appear fields that are not supported by the
addFeatures()/changeAttributeValues() method. So if transaction support is detected and featureMode=default, use QGIS built-in analyser to get SimpleFeatures only. Otherwise try the GMLAS method.



Diff:
diff --git a/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in
index f7ef67cc269a..952f59fb462a 100644
--- a/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in
+++ b/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in
@@ -147,6 +147,7 @@ following parameters (note that parameter keys are case sensitive):
 - IgnoreAxisOrientation=1: to ignore EPSG axis order for WFS 1.1 or 2.0
 - InvertAxisOrientation=1: to invert axis order
 - hideDownloadProgressDialog=1: to hide the download progress dialog
+- featureMode=default/simpleFeatures/complexFeatures (QGIS >= 3.44)
 
 The ‘filter’ key value can either be a QGIS expression or an OGC XML
 filter. If the value is set to a QGIS expression the driver will turn it
diff --git a/python/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/core/auto_generated/vector/qgsvectorlayer.sip.in
index f7ef67cc269a..952f59fb462a 100644
--- a/python/core/auto_generated/vector/qgsvectorlayer.sip.in
+++ b/python/core/auto_generated/vector/qgsvectorlayer.sip.in
@@ -147,6 +147,7 @@ following parameters (note that parameter keys are case sensitive):
 - IgnoreAxisOrientation=1: to ignore EPSG axis order for WFS 1.1 or 2.0
 - InvertAxisOrientation=1: to invert axis order
 - hideDownloadProgressDialog=1: to hide the download progress dialog
+- featureMode=default/simpleFeatures/complexFeatures (QGIS >= 3.44)
 
 The ‘filter’ key value can either be a QGIS expression or an OGC XML
 filter. If the value is set to a QGIS expression the driver will turn it
diff --git a/src/core/qgsowsconnection.cpp b/src/core/qgsowsconnection.cpp
index aca81c12dade..6c59408d6d7c 100644
--- a/src/core/qgsowsconnection.cpp
+++ b/src/core/qgsowsconnection.cpp
@@ -69,6 +69,7 @@ const QgsSettingsEntryEnumFlag<Qgis::TilePixelRatio> *QgsOwsConnection::settings
 const QgsSettingsEntryString *QgsOwsConnection::settingsMaxNumFeatures = new QgsSettingsEntryString( QStringLiteral( "max-num-features" ), sTreeOwsConnections ) ;
 const QgsSettingsEntryString *QgsOwsConnection::settingsPagesize = new QgsSettingsEntryString( QStringLiteral( "page-size" ), sTreeOwsConnections ) ;
 const QgsSettingsEntryString *QgsOwsConnection::settingsPagingEnabled = new QgsSettingsEntryString( QStringLiteral( "paging-enabled" ), sTreeOwsConnections, QString( "default" ) ) ;
+const QgsSettingsEntryString *QgsOwsConnection::settingsWfsFeatureMode = new QgsSettingsEntryString( QStringLiteral( "feature-mode" ), sTreeOwsConnections, QString( "default" ) ) ;
 const QgsSettingsEntryBool *QgsOwsConnection::settingsPreferCoordinatesForWfsT11 = new QgsSettingsEntryBool( QStringLiteral( "prefer-coordinates-for-wfs-T11" ), sTreeOwsConnections, false ) ;
 const QgsSettingsEntryBool *QgsOwsConnection::settingsIgnoreAxisOrientation = new QgsSettingsEntryBool( QStringLiteral( "ignore-axis-orientation" ), sTreeOwsConnections, false ) ;
 const QgsSettingsEntryBool *QgsOwsConnection::settingsInvertAxisOrientation = new QgsSettingsEntryBool( QStringLiteral( "invert-axis-orientation" ), sTreeOwsConnections, false ) ;
diff --git a/src/core/qgsowsconnection.h b/src/core/qgsowsconnection.h
index 706ddd77a6bd..70c10256980e 100644
--- a/src/core/qgsowsconnection.h
+++ b/src/core/qgsowsconnection.h
@@ -109,6 +109,7 @@ class CORE_EXPORT QgsOwsConnection : public QObject
     static const QgsSettingsEntryString *settingsMaxNumFeatures;
     static const QgsSettingsEntryString *settingsPagesize;
     static const QgsSettingsEntryString *settingsPagingEnabled;
+    static const QgsSettingsEntryString *settingsWfsFeatureMode;
     static const QgsSettingsEntryBool *settingsPreferCoordinatesForWfsT11;
     static const QgsSettingsEntryBool *settingsIgnoreAxisOrientation;
     static const QgsSettingsEntryBool *settingsInvertAxisOrientation;
diff --git a/src/core/vector/qgsvectorlayer.h b/src/core/vector/qgsvectorlayer.h
index 03b399ddfdec..d2e48bff6998 100644
--- a/src/core/vector/qgsvectorlayer.h
+++ b/src/core/vector/qgsvectorlayer.h
@@ -203,6 +203,7 @@ typedef QSet<int> QgsAttributeIds;
  * - IgnoreAxisOrientation=1: to ignore EPSG axis order for WFS 1.1 or 2.0
  * - InvertAxisOrientation=1: to invert axis order
  * - hideDownloadProgressDialog=1: to hide the download progress dialog
+ * - featureMode=default/simpleFeatures/complexFeatures (QGIS >= 3.44)
  *
  * The ‘filter’ key value can either be a QGIS expression
  * or an OGC XML filter. If the value is set to a QGIS expression the driver will
diff --git a/src/gui/qgsmanageconnectionsdialog.cpp b/src/gui/qgsmanageconnectionsdialog.cpp
index fed35e69f49c..c90d5a097069 100644
--- a/src/gui/qgsmanageconnectionsdialog.cpp
+++ b/src/gui/qgsmanageconnectionsdialog.cpp
@@ -528,6 +528,7 @@ QDomDocument QgsManageConnectionsDialog::saveWfsConnections( const QStringList &
     el.setAttribute( QStringLiteral( "username" ), QgsOwsConnection::settingsUsername->value( { QStringLiteral( "wfs" ), connections[i] } ) );
     el.setAttribute( QStringLiteral( "password" ), QgsOwsConnection::settingsPassword->value( { QStringLiteral( "wfs" ), connections[i] } ) );
     el.setAttribute( QStringLiteral( "httpMethod" ), QgsOwsConnection::settingsPreferredHttpMethod->value( { QStringLiteral( "wfs" ), connections[i] } ) == Qgis::HttpMethod::Post ? QStringLiteral( "post" ) : QStringLiteral( "get" ) );
+    el.setAttribute( QStringLiteral( "featureMode" ), QgsOwsConnection::settingsWfsFeatureMode->value( { QStringLiteral( "wfs" ), connections[i] } ) );
     root.appendChild( el );
   }
 
@@ -1082,6 +1083,7 @@ void QgsManageConnectionsDialog::loadWfsConnections( const QDomDocument &doc, co
     QgsOwsConnection::settingsIgnoreAxisOrientation->setValue( child.attribute( QStringLiteral( "ignoreAxisOrientation" ) ).toInt(), { QStringLiteral( "wfs" ), connectionName } );
     QgsOwsConnection::settingsInvertAxisOrientation->setValue( child.attribute( QStringLiteral( "invertAxisOrientation" ) ).toInt(), { QStringLiteral( "wfs" ), connectionName } );
     QgsOwsConnection::settingsPreferredHttpMethod->setValue( child.attribute( QStringLiteral( "httpMethod" ) ).compare( QLatin1String( "post" ), Qt::CaseInsensitive ) == 0 ? Qgis::HttpMethod::Post : Qgis::HttpMethod::Get, { QStringLiteral( "wfs" ), connectionName } );
+    QgsOwsConnection::settingsWfsFeatureMode->setValue( child.attribute( QStringLiteral( "featureMode" ) ), { QStringLiteral( "wfs" ), connectionName } );
 
     if ( !child.attribute( QStringLiteral( "username" ) ).isEmpty() )
     {
diff --git a/src/gui/qgsnewhttpconnection.cpp b/src/gui/qgsnewhttpconnection.cpp
index 3d67c435b301..e705e518801e 100644
--- a/src/gui/qgsnewhttpconnection.cpp
+++ b/src/gui/qgsnewhttpconnection.cpp
@@ -89,6 +89,11 @@ QgsNewHttpConnection::QgsNewHttpConnection( QWidget *parent, ConnectionTypes typ
   cmbVersion->addItem( tr( "OGC API - Features" ) );
   connect( cmbVersion, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsNewHttpConnection::wfsVersionCurrentIndexChanged );
 
+  mComboWfsFeatureMode->clear();
+  mComboWfsFeatureMode->addItem( tr( "Default" ), QStringLiteral( "default" ) );
+  mComboWfsFeatureMode->addItem( tr( "Simple Features" ), QStringLiteral( "simpleFeatures" ) );
+  mComboWfsFeatureMode->addItem( tr( "Complex Features" ), QStringLiteral( "complexFeatures" ) );
+
   mComboHttpMethod->addItem( QStringLiteral( "GET" ), QVariant::fromValue( Qgis::HttpMethod::Get ) );
   mComboHttpMethod->addItem( QStringLiteral( "POST" ), QVariant::fromValue( Qgis::HttpMethod::Post ) );
   mComboHttpMethod->setCurrentIndex( mComboHttpMethod->findData( QVariant::fromValue( Qgis::HttpMethod::Get ) ) );
@@ -356,6 +361,9 @@ void QgsNewHttpConnection::updateServiceSpecificSettings()
   else
     cmbFeaturePaging->setCurrentIndex( static_cast<int>( QgsNewHttpConnection::WfsFeaturePagingIndex::DEFAULT ) );
 
+  const QString wfsFeatureMode = QgsOwsConnection::settingsWfsFeatureMode->value( detailsParameters );
+  mComboWfsFeatureMode->setCurrentIndex( std::max( mComboWfsFeatureMode->findData( wfsFeatureMode ), 0 ) );
+
   mComboHttpMethod->setCurrentIndex( mComboHttpMethod->findData( QVariant::fromValue( QgsOwsConnection::settingsPreferredHttpMethod->value( detailsParameters ) ) ) );
   txtPageSize->setText( QgsOwsConnection::settingsPagesize->value( detailsParameters ) );
 }
@@ -475,6 +483,9 @@ void QgsNewHttpConnection::accept()
         break;
     }
     QgsOwsConnection::settingsPagingEnabled->setValue( pagingEnabled, detailsParameters );
+
+    const QString featureMode = mComboWfsFeatureMode->currentData().toString();
+    QgsOwsConnection::settingsWfsFeatureMode->setValue( featureMode, detailsParameters );
   }
 
   QStringList credentialsParameters = { mServiceName.toLower(), newConnectionName };
diff --git a/src/providers/wfs/qgswfsconnection.cpp b/src/providers/wfs/qgswfsconnection.cpp
index 5e8f3b1dd883..ee27793f5921 100644
--- a/src/providers/wfs/qgswfsconnection.cpp
+++ b/src/providers/wfs/qgswfsconnection.cpp
@@ -81,6 +81,16 @@ QgsWfsConnection::QgsWfsConnection( const QString &connName )
     }
   }
 
+  if ( settingsWfsFeatureMode->exists( detailsParameters ) )
+  {
+    mUri.removeParam( QgsWFSConstants::URI_PARAM_FEATURE_MODE ); // setParam allow for duplicates!
+    const QString featureMode = settingsWfsFeatureMode->value( detailsParameters );
+    if ( featureMode != QLatin1String( "default" ) )
+    {
+      mUri.setParam( QgsWFSConstants::URI_PARAM_FEATURE_MODE, featureMode );
+    }
+  }
+
   QgsDebugMsgLevel( QStringLiteral( "WFS full uri: '%1'." ).arg( QString( mUri.uri() ) ), 4 );
 }
 
diff --git a/src/providers/wfs/qgswfsconstants.cpp b/src/providers/wfs/qgswfsconstants.cpp
index 94d1bd2e47b5..0d520c65c5be 100644
--- a/src/providers/wfs/qgswfsconstants.cpp
+++ b/src/providers/wfs/qgswfsconstants.cpp
@@ -45,5 +45,6 @@ const QString QgsWFSConstants::URI_PARAM_SKIP_INITIAL_GET_FEATURE( QStringLitera
 const QString QgsWFSConstants::URI_PARAM_GEOMETRY_TYPE_FILTER( QStringLiteral( "geometryTypeFilter" ) );
 const QString QgsWFSConstants::URI_PARAM_SQL( QStringLiteral( "sql" ) );
 const QString QgsWFSConstants::URI_PARAM_HTTPMETHOD( QStringLiteral( "httpMethod" ) );
+const QString QgsWFSConstants::URI_PARAM_FEATURE_MODE( QStringLiteral( "featureMode" ) );
 
 const QString QgsWFSConstants::VERSION_AUTO( QStringLiteral( "auto" ) );
diff --git a/src/providers/wfs/qgswfsconstants.h b/src/providers/wfs/qgswfsconstants.h
index 48eae12a561a..45e63b375cf5 100644
--- a/src/providers/wfs/qgswfsconstants.h
+++ b/src/providers/wfs/qgswfsconstants.h
@@ -53,6 +53,7 @@ struct QgsWFSConstants
     static const QString URI_PARAM_GEOMETRY_TYPE_FILTER;
     static const QString URI_PARAM_SQL;
     static const QString URI_PARAM_HTTPMETHOD;
+    static const QString URI_PARAM_FEATURE_MODE;
 
     //
     static const QString VERSION_AUTO;
diff --git a/src/providers/wfs/qgswfsdataitemguiprovider.cpp b/src/providers/wfs/qgswfsdataitemguiprovider.cpp
index 2c6d54434ff7..2c792a17252f 100644
--- a/src/providers/wfs/qgswfsdataitemguiprovider.cpp
+++ b/src/providers/wfs/qgswfsdataitemguiprovider.cpp
@@ -126,6 +126,8 @@ void QgsWfsDataItemGuiProvider::duplicateConnection( QgsDataItem *item )
 
   QgsOwsConnection::settingsHeaders->setValue( QgsOwsConnection::settingsHeaders->value( detailsParameters ), newDetailsParameters );
 
+  QgsOwsConnection::settingsWfsFeatureMode->setValue( QgsOwsConnection::settingsWfsFeatureMode->value( detailsParameters ), newDetailsParameters );
+
   item->parent()->refreshConnections();
 }
 
diff --git a/src/providers/wfs/qgswfsdatasourceuri.cpp b/src/providers/wfs/qgswfsdatasourceuri.cpp
index 9914ffbb7ac4..183af46db006 100644
--- a/src/providers/wfs/qgswfsdatasourceuri.cpp
+++ b/src/providers/wfs/qgswfsdatasourceuri.cpp
@@ -15,6 +15,7 @@
 
 #include "QtGlobal"
 
+#include "qgsmessagelog.h"
 #include "qgswfsconstants.h"
 #include "qgswfsdatasourceuri.h"
 
@@ -192,7 +193,8 @@ QSet<QString> QgsWFSDataSourceURI::unknownParamKeys() const
     QgsWFSConstants::URI_PARAM_SKIP_INITIAL_GET_FEATURE,
     QgsWFSConstants::URI_PARAM_GEOMETRY_TYPE_FILTER,
     QgsWFSConstants::URI_PARAM_SQL,
-    QgsWFSConstants::URI_PARAM_HTTPMETHOD
+    QgsWFSConstants::URI_PARAM_HTTPMETHOD,
+    QgsWFSConstants::URI_PARAM_FEATURE_MODE,
   };
 
   QSet<QString> l_unknownParamKeys;
@@ -497,6 +499,24 @@ bool QgsWFSDataSourceURI::skipInitialGetFeature() const
   return mURI.param( QgsWFSConstants::URI_PARAM_SKIP_INITIAL_GET_FEATURE ).toUpper() == QLatin1String( "TRUE" );
 }
 
+QgsWFSDataSourceURI::FeatureMode QgsWFSDataSourceURI::featureMode() const
+{
+  if ( !mURI.hasParam( QgsWFSConstants::URI_PARAM_FEATURE_MODE ) )
+    return FeatureMode::Default;
+  const QString val = mURI.param( QgsWFSConstants::URI_PARAM_FEATURE_MODE );
+  if ( val == QLatin1String( "default" ) )
+    return FeatureMode::Default;
+  else if ( val == QLatin1String( "simpleFeatures" ) )
+    return FeatureMode::SimpleFeatures;
+  else if ( val == QLatin1String( "complexFeatures" ) )
+    return FeatureMode::ComplexFeatures;
+  else
+  {
+    QgsMessageLog::logMessage( QObject::tr( "Unknown value for featureMode URI parameter '%1'" ).arg( val ), QObject::tr( "WFS" ) );
+    return FeatureMode::Default;
+  }
+}
+
 QString QgsWFSDataSourceURI::build( const QString &baseUri, const QString &typeName, const QString &crsString, const QString &sql, const QString &filter, bool restrictToCurrentViewExtent )
 {
   QgsWFSDataSourceURI uri( baseUri );
diff --git a/src/providers/wfs/qgswfsdatasourceuri.h b/src/providers/wfs/qgswfsdatasourceuri.h
index db00313ed365..3a7e505d7ce3 100644
--- a/src/providers/wfs/qgswfsdatasourceuri.h
+++ b/src/providers/wfs/qgswfsdatasourceuri.h
@@ -135,6 +135,17 @@ class QgsWFSDataSourceURI
     //! Returns authorization parameters
     const QgsAuthorizationSettings &auth() const { return mAuth; }
 
+    //! How to analyze DescribeFeatureType response
+    enum class FeatureMode
+    {
+      Default,         //! If the server supports transaction, same as SIMPLE_FEATURE. Otherwise COMPLEX_FEATURE
+      SimpleFeatures,  //! Analyze DescribeFeatureType response with QGIS built-in Simple Feature analyzer
+      ComplexFeatures, //! Analyze DescribeFeatureType response with OGR GMLAS Complex Feature analyzer
+    };
+
+    //! Returns how to analyze DescribeFeatureType response.
+    FeatureMode featureMode() const;
+
     //! Builds a derived uri from a base uri
     static QString build( const QString &uri, const QString &typeName, const QString &crsString = QString(), const QString &sql = QString(), const QString &filter = QString(), bool restrictToCurrentViewExtent = false );
 
diff --git a/src/providers/wfs/qgswfsprovider.cpp b/src/providers/wfs/qgswfsprovider.cpp
index 338c3b0b6a60..dd8ce3e8f168 100644
--- a/src/providers/wfs/qgswfsprovider.cpp
+++ b/src/providers/wfs/qgswfsprovider.cpp
@@ -1532,7 +1532,11 @@ bool QgsWFSProvider::readAttributesFromSchema( QDomDocument &schemaDoc, const QB
   geometryMaybeMissing = false;
   bool mayTryWithGMLAS = false;
   bool ret = readAttributesFromSchemaWithoutGMLAS( schemaDoc, prefixedTypename, geometryAttribute, fields, geomType, errorMsg, mayTryWithGMLAS );
-  if ( singleLayerContext && mayTryWithGMLAS && GDALGetDriverByName( "GMLAS" ) )
+
+  // Only consider GMLAS / ComplexFeatures mode if FeatureMode=DEFAULT and there
+  // is no edition capabilities, or if explicitly requested.
+  // Cf https://github.com/qgis/QGIS/pull/61493
+  if ( ( ( mShared->mURI.featureMode() == QgsWFSDataSourceURI::FeatureMode::Default && ( mCapabilities & Qgis::VectorProviderCapability::AddFeatures ) == 0 ) || mShared->mURI.featureMode() == QgsWFSDataSourceURI::FeatureMode::ComplexFeatures ) && singleLayerContext && mayTryWithGMLAS && GDALGetDriverByName( "GMLAS" ) )
   {
     QString geometryAttributeGMLAS;
     QgsFields fieldsGMLAS;
diff --git a/src/ui/qgsnewhttpconnectionbase.ui b/src/ui/qgsnewhttpconnectionbase.ui
index 275f8fa27162..71ba7cc0230c 100644
--- a/src/ui/qgsnewhttpconnectionbase.ui
+++ b/src/ui/qgsnewhttpconnectionbase.ui
@@ -32,68 +32,71 @@
          <string>WFS Options</string>
         </property>
         <layout class="QGridLayout" name="gridLayout1">
-         <item row="0" column="0">
-          <widget class="QLabel" name="lblVersion">
+         <item row="5" column="0">
+          <widget class="QLabel" name="lblFeatureMode">
            <property name="text">
-            <string>Version</string>
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Feature mode&lt;br/&gt;(Simple vs Complex)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
            </property>
           </widget>
          </item>
-         <item row="0" column="2">
-          <widget class="QPushButton" name="mWfsVersionDetectButton">
+         <item row="0" column="1">
+          <widget class="QComboBox" name="cmbVersion"/>
+         </item>
+         <item row="7" column="0" colspan="3">
+          <widget class="QCheckBox" name="cbxWfsInvertAxisOrientation">
            <property name="text">
-            <string>Detect</string>
+            <string>Invert axis orientation</string>
            </property>
           </widget>
          </item>
-         <item row="5" column="0" colspan="3">
-          <widget class="QCheckBox" name="cbxWfsIgnoreAxisOrientation">
+         <item row="1" column="1" colspan="2">
+          <widget class="QComboBox" name="mComboHttpMethod"/>
+         </item>
+         <item row="8" column="0" colspan="2">
+          <widget class="QCheckBox" name="cbxWfsUseGml2EncodingForTransactions">
+           <property name="toolTip">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This might be necessary on some &lt;span style=&quot; font-weight:600;&quot;&gt;broken&lt;/span&gt; ESRI map servers when using WFS-T 1.1.0.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
            <property name="text">
-            <string>Ignore axis orientation (WFS 1.1/WFS 2.0)</string>
+            <string>Use GML2 encoding for transactions</string>
            </property>
           </widget>
          </item>
-         <item row="2" column="0">
-          <widget class="QLabel" name="lblMaxNumFeatures">
+         <item row="4" column="0">
+          <widget class="QLabel" name="lblPageSize">
            <property name="text">
-            <string>Max. number of features</string>
+            <string>Page size</string>
            </property>
           </widget>
          </item>
-         <item row="0" column="1">
-          <widget class="QComboBox" name="cmbVersion"/>
-         </item>
-         <item row="4" column="1" colspan="2">
-          <widget class="QLineEdit" name="txtPageSize">
-           <property name="toolTip">
-            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enter a number to limit the maximum number of features retrieved in a single GetFeature request when paging is enabled. If let to empty, server default will apply.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         <item row="2" column="0">
+          <widget class="QLabel" name="lblMaxNumFeatures">
+           <property name="text">
+            <string>Max. number of features</string>
            </property>
           </widget>
          </item>
-         <item row="6" column="0" colspan="3">
-          <widget class="QCheckBox" name="cbxWfsInvertAxisOrientation">
+         <item row="0" column="0">
+          <widget class="QLabel" name="lblVersion">
            <property name="text">
-            <string>Invert axis orientation</string>
+            <string>Version</string>
            </property>
           </widget>
          </item>
-         <item row="4" column="0">
-          <widget class="QLabel" name="lblPageSize">
+         <item row="6" column="0" colspan="3">
+          <widget class="QCheckBox" name="cbxWfsIgnoreAxisOrientation">
            <property name="text">
-            <string>Page size</string>
+            <string>Ignore axis orientation (WFS 1.1/WFS 2.0)</string>
            </property>
           </widget>
          </item>
-         <item row="3" column="0">
-          <widget class="QLabel" name="lblFeaturePaging">
+         <item row="1" column="0">
+          <widget class="QLabel" name="lblVersion_2">
            <property name="text">
-            <string>Feature paging</string>
+            <string>Preferred HTTP method</string>
            </property>
           </widget>
          </item>
-         <item row="3" column="1" colspan="2">
-          <widget class="QComboBox" name="cmbFeaturePaging"/>
-         </item>
          <item row="2" column="1" colspan="2">
           <widget class="QLineEdit" name="txtMaxNumFeatures">
            <property name="toolTip">
@@ -101,25 +104,32 @@
            </property>
           </widget>
          </item>
-         <item row="7" column="0" colspan="2">
-          <widget class="QCheckBox" name="cbxWfsUseGml2EncodingForTransactions">
-           <property name="toolTip">
-            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This might be necessary on some &lt;span style=&quot; font-weight:600;&quot;&gt;broken&lt;/span&gt; ESRI map servers when using WFS-T 1.1.0.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-           </property>
+         <item row="0" column="2">
+          <widget class="QPushButton" name="mWfsVersionDetectButton">
            <property name="text">
-            <string>Use GML2 encoding for transactions</string>
+            <string>Detect</string>
            </property>
           </widget>
          </item>
-         <item row="1" column="0">
-          <widget class="QLabel" name="lblVersion_2">
-           <property name="text">
-            <string>Preferred HTTP method</string>
+         <item row="4" column="1" colspan="2">
+          <widget class="QLineEdit" name="txtPageSize">
+           <property name="toolTip">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enter a number to limit the maximum number of features retrieved in a single GetFeature request when paging is enabled. If let to empty, server default will apply.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
            </property>
           </widget>
          </item>
-         <item row="1" column="1" colspan="2">
-          <widget class="QComboBox" name="mComboHttpMethod"/>
+         <item row="3" column="1" colspan="2">
+          <widget class="QComboBox" name="cmbFeaturePaging"/>
+         </item>
+         <item row="5" column="1" colspan="2">
+          <widget class="QComboBox" name="mComboWfsFeatureMode"/>
+         </item>
+         <item row="3" column="0">
+          <widget class="QLabel" name="lblFeaturePaging">
+           <property name="text">
+            <string>Feature paging</string>
+           </property>
+          </widget>
          </item>
         </layout>
        </widget>
@@ -394,6 +404,7 @@
   <tabstop>txtMaxNumFeatures</tabstop>
   <tabstop>cmbFeaturePaging</tabstop>
   <tabstop>txtPageSize</tabstop>
+  <tabstop>mComboWfsFeatureMode</tabstop>
   <tabstop>cbxWfsIgnoreAxisOrientation</tabstop>
   <tabstop>cbxWfsInvertAxisOrientation</tabstop>
   <tabstop>cbxWfsUseGml2EncodingForTransactions</tabstop>
diff --git a/tests/src/python/test_provider_wfs.py b/tests/src/python/test_provider_wfs.py
index 392ef1d76d63..20701f106510 100644
--- a/tests/src/python/test_provider_wfs.py
+++ b/tests/src/python/test_provider_wfs.py
@@ -8757,6 +8757,40 @@ def testWFSComplexFeatures(self):
         got_f = [f for f in vl.getFeatures()]
         self.assertEqual(len(got_f), 1)
 
+        # Test the various values of featureMode URI parameter
+        with MessageLogger("WFS") as logger:
+            vl = QgsVectorLayer(
+                "url='http://"
+                + endpoint
+                + "' typename='ps:ProtectedSite' version='2.0.0' skipInitialGetFeature='true' featureMode='simpleFeatures'",
+                "test",
+                "WFS",
+            )
+            self.assertFalse(vl.isValid())
+            self.assertEqual(len(logger.messages()), 1, logger.messages())
+            self.assertIn(
+                "It is probably a schema for Complex Features",
+                logger.messages()[0].decode("UTF-8"),
+            )
+
+        vl = QgsVectorLayer(
+            "url='http://"
+            + endpoint
+            + "' typename='ps:ProtectedSite' version='2.0.0' skipInitialGetFeature='true' featureMode='complexFeatures'",
+            "test",
+            "WFS",
+        )
+        self.assertTrue(vl.isValid())
+
+        vl = QgsVectorLayer(
+            "url='http://"
+            + endpoint
+            + "' typename='ps:ProtectedSite' version='2.0.0' skipInitialGetFeature='true' featureMode='default'",
+            "test",
+            "WFS",
+        )
+        self.assertTrue(vl.isValid())
+
 
 class TestPyQgsWFSProviderPost(QgisTestCase, ProviderTestCase):
     """


Reviews:
user: login: nyalldawson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-16T22:23:50Z
user: login: rouault
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T23:15:21Z
user: login: rouault
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T23:15:27Z
user: login: rouault
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T23:15:33Z
user: login: rouault
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T23:15:40Z
user: login: rouault
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T23:15:45Z
user: login: rouault
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T23:15:51Z
user: login: nirvn
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T04:50:09Z
user: login: nyalldawson
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-17T05:09:01Z

Review comments:
diff_hunk: @@ -89,6 +89,11 @@ QgsNewHttpConnection::QgsNewHttpConnection( QWidget *parent, ConnectionTypes typ
   cmbVersion->addItem( tr( "OGC API - Features" ) );
   connect( cmbVersion, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsNewHttpConnection::wfsVersionCurrentIndexChanged );
 
+  mComboWfsFeatureMode->clear();
+  mComboWfsFeatureMode->addItem( tr( "Default" ) );
+  mComboWfsFeatureMode->addItem( tr( "Simple Features" ) );
+  mComboWfsFeatureMode->addItem( tr( "Complex Features" ) );
path: src/gui/qgsnewhttpconnection.cpp
user: login: nyalldawson
body: ```suggestion
  mComboWfsFeatureMode->addItem( tr( "Default" ), QStringLiteral( "default" ) );
  mComboWfsFeatureMode->addItem( tr( "Simple Features" ), QStringLiteral( "simpleFeatures" ) );
  mComboWfsFeatureMode->addItem( tr( "Complex Features" ), QStringLiteral( "complexFeatures" ) );
```
created_at: 2025-04-16T22:19:13Z
author_association: COLLABORATOR
diff_hunk: @@ -356,6 +361,16 @@ void QgsNewHttpConnection::updateServiceSpecificSettings()
   else
     cmbFeaturePaging->setCurrentIndex( static_cast<int>( QgsNewHttpConnection::WfsFeaturePagingIndex::DEFAULT ) );
 
+  const QString wfsFeatureMode = QgsOwsConnection::settingsWfsFeatureMode->value( detailsParameters );
+  QgsNewHttpConnection::WfsFeatureModeIndex featureModeIdx = QgsNewHttpConnection::WfsFeatureModeIndex::DEFAULT;
+  if ( wfsFeatureMode == QLatin1String( "default" ) )
+    featureModeIdx = QgsNewHttpConnection::WfsFeatureModeIndex::DEFAULT;
+  else if ( wfsFeatureMode == QLatin1String( "simpleFeatures" ) )
+    featureModeIdx = QgsNewHttpConnection::WfsFeatureModeIndex::SIMPLE_FEATURES;
+  else if ( wfsFeatureMode == QLatin1String( "complexFeatures" ) )
+    featureModeIdx = QgsNewHttpConnection::WfsFeatureModeIndex::COMPLEX_FEATURES;
+  mComboWfsFeatureMode->setCurrentIndex( static_cast<int>( featureModeIdx ) );
path: src/gui/qgsnewhttpconnection.cpp
user: login: nyalldawson
body: ```suggestion
  const QString wfsFeatureMode = QgsOwsConnection::settingsWfsFeatureMode->value( detailsParameters );
  mComboWfsFeatureMode->setCurrentIndex( std::max( mComboWfsFeatureMode->findData( wfsFeatureMode ), 0 ) );
```
created_at: 2025-04-16T22:20:32Z
author_association: COLLABORATOR
diff_hunk: @@ -475,6 +490,21 @@ void QgsNewHttpConnection::accept()
         break;
     }
     QgsOwsConnection::settingsPagingEnabled->setValue( pagingEnabled, detailsParameters );
+
+    QString featureMode = QStringLiteral( "default" );
+    switch ( mComboWfsFeatureMode->currentIndex() )
+    {
+      case static_cast<int>( QgsNewHttpConnection::WfsFeatureModeIndex::DEFAULT ):
+        featureMode = QStringLiteral( "default" );
+        break;
+      case static_cast<int>( QgsNewHttpConnection::WfsFeatureModeIndex::SIMPLE_FEATURES ):
+        featureMode = QStringLiteral( "simpleFeatures" );
+        break;
+      case static_cast<int>( QgsNewHttpConnection::WfsFeatureModeIndex::COMPLEX_FEATURES ):
+        featureMode = QStringLiteral( "complexFeatures" );
+        break;
+    }
+    QgsOwsConnection::settingsWfsFeatureMode->setValue( featureMode, detailsParameters );
path: src/gui/qgsnewhttpconnection.cpp
user: login: nyalldawson
body: ```suggestion
    const QString featureMode = mComboWfsFeatureMode->currentData();
    QgsOwsConnection::settingsWfsFeatureMode->setValue( featureMode, detailsParameters );
```
created_at: 2025-04-16T22:20:58Z
author_association: COLLABORATOR
diff_hunk: @@ -120,6 +120,14 @@ class GUI_EXPORT QgsNewHttpConnection : public QDialog, private Ui::QgsNewHttpCo
       ENABLED = 1,
       DISABLED = 2,
     };
+
+    //! Index of mComboWfsFeatureMode
+    enum class WfsFeatureModeIndex
+    {
+      DEFAULT = 0,
+      SIMPLE_FEATURES = 1,
+      COMPLEX_FEATURES = 2,
+    };
path: src/gui/qgsnewhttpconnection.h
user: login: nyalldawson
body: ```suggestion
```
created_at: 2025-04-16T22:22:07Z
author_association: COLLABORATOR
diff_hunk: @@ -81,6 +81,12 @@ QgsWfsConnection::QgsWfsConnection( const QString &connName )
     }
   }
 
+  if ( settingsWfsFeatureMode->exists( detailsParameters ) )
+  {
+    mUri.removeParam( QgsWFSConstants::URI_PARAM_FEATURE_MODE ); // setParam allow for duplicates!
+    mUri.setParam( QgsWFSConstants::URI_PARAM_FEATURE_MODE, settingsWfsFeatureMode->value( detailsParameters ) );
path: src/providers/wfs/qgswfsconnection.cpp
user: login: nyalldawson
body: ```suggestion
    const QString featureMode = settingsWfsFeatureMode->value( detailsParameters );
    if ( featureMode != QLatin1String( "default" ) )
    {
      mUri.setParam( QgsWFSConstants::URI_PARAM_FEATURE_MODE, featureMode );
    }
```
created_at: 2025-04-16T22:23:15Z
author_association: COLLABORATOR
diff_hunk: @@ -135,6 +135,17 @@ class QgsWFSDataSourceURI
     //! Returns authorization parameters
     const QgsAuthorizationSettings &auth() const { return mAuth; }
 
+    //! How to analyze DescribeFeatureType response
+    enum class FeatureMode
+    {
+      DEFAULT,          //! If the server supports transaction, same as SIMPLE_FEATURE. Otherwise COMPLEX_FEATURE
+      SIMPLE_FEATURES,  //! Analyze DescribeFeatureType response with QGIS built-in Simple Feature analyzer
+      COMPLEX_FEATURES, //! Analyze DescribeFeatureType response with OGR GMLAS Complex Feature analyzer
path: src/providers/wfs/qgswfsdatasourceuri.h
user: login: nyalldawson
body: ```suggestion
      Default,          //! If the server supports transaction, same as SIMPLE_FEATURE. Otherwise COMPLEX_FEATURE
      SimpleFeatures,  //! Analyze DescribeFeatureType response with QGIS built-in Simple Feature analyzer
      ComplexFeatures, //! Analyze DescribeFeatureType response with OGR GMLAS Complex Feature analyzer
```
created_at: 2025-04-16T22:23:46Z
author_association: COLLABORATOR
diff_hunk: @@ -89,6 +89,11 @@ QgsNewHttpConnection::QgsNewHttpConnection( QWidget *parent, ConnectionTypes typ
   cmbVersion->addItem( tr( "OGC API - Features" ) );
   connect( cmbVersion, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsNewHttpConnection::wfsVersionCurrentIndexChanged );
 
+  mComboWfsFeatureMode->clear();
+  mComboWfsFeatureMode->addItem( tr( "Default" ) );
+  mComboWfsFeatureMode->addItem( tr( "Simple Features" ) );
+  mComboWfsFeatureMode->addItem( tr( "Complex Features" ) );
path: src/gui/qgsnewhttpconnection.cpp
user: login: rouault
body: fixed
created_at: 2025-04-16T23:15:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -356,6 +361,16 @@ void QgsNewHttpConnection::updateServiceSpecificSettings()
   else
     cmbFeaturePaging->setCurrentIndex( static_cast<int>( QgsNewHttpConnection::WfsFeaturePagingIndex::DEFAULT ) );
 
+  const QString wfsFeatureMode = QgsOwsConnection::settingsWfsFeatureMode->value( detailsParameters );
+  QgsNewHttpConnection::WfsFeatureModeIndex featureModeIdx = QgsNewHttpConnection::WfsFeatureModeIndex::DEFAULT;
+  if ( wfsFeatureMode == QLatin1String( "default" ) )
+    featureModeIdx = QgsNewHttpConnection::WfsFeatureModeIndex::DEFAULT;
+  else if ( wfsFeatureMode == QLatin1String( "simpleFeatures" ) )
+    featureModeIdx = QgsNewHttpConnection::WfsFeatureModeIndex::SIMPLE_FEATURES;
+  else if ( wfsFeatureMode == QLatin1String( "complexFeatures" ) )
+    featureModeIdx = QgsNewHttpConnection::WfsFeatureModeIndex::COMPLEX_FEATURES;
+  mComboWfsFeatureMode->setCurrentIndex( static_cast<int>( featureModeIdx ) );
path: src/gui/qgsnewhttpconnection.cpp
user: login: rouault
body: fixed
created_at: 2025-04-16T23:15:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -475,6 +490,21 @@ void QgsNewHttpConnection::accept()
         break;
     }
     QgsOwsConnection::settingsPagingEnabled->setValue( pagingEnabled, detailsParameters );
+
+    QString featureMode = QStringLiteral( "default" );
+    switch ( mComboWfsFeatureMode->currentIndex() )
+    {
+      case static_cast<int>( QgsNewHttpConnection::WfsFeatureModeIndex::DEFAULT ):
+        featureMode = QStringLiteral( "default" );
+        break;
+      case static_cast<int>( QgsNewHttpConnection::WfsFeatureModeIndex::SIMPLE_FEATURES ):
+        featureMode = QStringLiteral( "simpleFeatures" );
+        break;
+      case static_cast<int>( QgsNewHttpConnection::WfsFeatureModeIndex::COMPLEX_FEATURES ):
+        featureMode = QStringLiteral( "complexFeatures" );
+        break;
+    }
+    QgsOwsConnection::settingsWfsFeatureMode->setValue( featureMode, detailsParameters );
path: src/gui/qgsnewhttpconnection.cpp
user: login: rouault
body: fixed
created_at: 2025-04-16T23:15:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -120,6 +120,14 @@ class GUI_EXPORT QgsNewHttpConnection : public QDialog, private Ui::QgsNewHttpCo
       ENABLED = 1,
       DISABLED = 2,
     };
+
+    //! Index of mComboWfsFeatureMode
+    enum class WfsFeatureModeIndex
+    {
+      DEFAULT = 0,
+      SIMPLE_FEATURES = 1,
+      COMPLEX_FEATURES = 2,
+    };
path: src/gui/qgsnewhttpconnection.h
user: login: rouault
body: fixed
created_at: 2025-04-16T23:15:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -135,6 +135,17 @@ class QgsWFSDataSourceURI
     //! Returns authorization parameters
     const QgsAuthorizationSettings &auth() const { return mAuth; }
 
+    //! How to analyze DescribeFeatureType response
+    enum class FeatureMode
+    {
+      DEFAULT,          //! If the server supports transaction, same as SIMPLE_FEATURE. Otherwise COMPLEX_FEATURE
+      SIMPLE_FEATURES,  //! Analyze DescribeFeatureType response with QGIS built-in Simple Feature analyzer
+      COMPLEX_FEATURES, //! Analyze DescribeFeatureType response with OGR GMLAS Complex Feature analyzer
path: src/providers/wfs/qgswfsdatasourceuri.h
user: login: rouault
body: fixed
created_at: 2025-04-16T23:15:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -81,6 +81,12 @@ QgsWfsConnection::QgsWfsConnection( const QString &connName )
     }
   }
 
+  if ( settingsWfsFeatureMode->exists( detailsParameters ) )
+  {
+    mUri.removeParam( QgsWFSConstants::URI_PARAM_FEATURE_MODE ); // setParam allow for duplicates!
+    mUri.setParam( QgsWFSConstants::URI_PARAM_FEATURE_MODE, settingsWfsFeatureMode->value( detailsParameters ) );
path: src/providers/wfs/qgswfsconnection.cpp
user: login: rouault
body: fixed
created_at: 2025-04-16T23:15:51Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-16T20:17:03Z
body: <!-- !!## stickymarker ##!! -->

<!-- ====startsection mingw64-->
### 🪟 Windows builds
Download [Windows builds of this PR for testing](https://github.com/qgis/QGIS/actions/runs/14504424578/artifacts/2960353059).
Debug symbols for this build are available [here](https://github.com/qgis/QGIS/actions/runs/14504424578/artifacts/2960353343).
*(Built from commit 6b9bddf652a659f382e711ab7072b4ced9b6f358)*

<!-- ====endsection mingw64-->

<!-- ====startsection mingw64-qt6-->
### 🪟 Windows Qt6 builds
Download [Windows Qt6 builds of this PR for testing](https://github.com/qgis/QGIS/actions/runs/14504424581/artifacts/2960387037).
*(Built from commit 6b9bddf652a659f382e711ab7072b4ced9b6f358)*

<!-- ====endsection mingw64-qt6-->

author_association: CONTRIBUTOR
user: login: nirvn
created_at: 2025-04-17T03:18:06Z
body: @rouault , just tried and can confirm this fixes the regressions we discussed:
- feature addition works (both for newly-added layers from WFS server as well as pre-existing WFS layers in saved project files)
- attribute form and attribute table back to normal (also covering both newly-added WFS layers vs. saved project's WFS layers)

Thanks for looking into this, pretty good fix to have into our releases ASAP. Let's merge speedily so we can catch Friday's point releases.
author_association: CONTRIBUTOR
user: login: 3nids
created_at: 2025-04-17T04:45:21Z
body: Many thanks for the reactivity @rouault !
author_association: MEMBER
user: login: nirvn
created_at: 2025-04-17T07:26:02Z
body: If there's no opposition here, I will merge this PR within the next hour even though it hasn't been 24 hours yet. I think this regression it addresses is critical, and as such I think it's important that it does not miss the next point point release which is about to happen on April 18th. 
author_association: CONTRIBUTOR
user: login: qgis-bot
created_at: 2025-04-17T10:58:09Z
body: The backport to `release-3_40` failed:
```
The process '/usr/bin/git' failed with exit code 1
```
<details>
<summary>stderr</summary>

```
error: could not apply 46da0ab3292... [WFS Provider] Add a UI and URI parameter featureMode=default/SimpleFeatures/ComplexFeatures
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git cherry-pick --continue".
hint: You can instead skip this commit with "git cherry-pick --skip".
hint: To abort and get back to the state before "git cherry-pick",
hint: run "git cherry-pick --abort".
hint: Disable this message with "git config set advice.mergeConflict false"

```
</details>

<details>
<summary>stdout</summary>

```
Auto-merging python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in
Auto-merging python/core/auto_generated/vector/qgsvectorlayer.sip.in
Auto-merging src/core/qgsowsconnection.cpp
Auto-merging src/core/qgsowsconnection.h
Auto-merging src/core/vector/qgsvectorlayer.h
Auto-merging src/gui/qgsmanageconnectionsdialog.cpp
CONFLICT (content): Merge conflict in src/gui/qgsmanageconnectionsdialog.cpp
Auto-merging src/gui/qgsnewhttpconnection.cpp
CONFLICT (content): Merge conflict in src/gui/qgsnewhttpconnection.cpp
Auto-merging src/gui/qgsnewhttpconnection.h
Auto-merging src/providers/wfs/qgswfsconnection.cpp
CONFLICT (content): Merge conflict in src/providers/wfs/qgswfsconnection.cpp
Auto-merging src/providers/wfs/qgswfsconstants.cpp
CONFLICT (content): Merge conflict in src/providers/wfs/qgswfsconstants.cpp
Auto-merging src/providers/wfs/qgswfsconstants.h
CONFLICT (content): Merge conflict in src/providers/wfs/qgswfsconstants.h
Auto-merging src/providers/wfs/qgswfsdataitemguiprovider.cpp
Auto-merging src/providers/wfs/qgswfsdatasourceuri.cpp
CONFLICT (content): Merge conflict in src/providers/wfs/qgswfsdatasourceuri.cpp
Auto-merging src/providers/wfs/qgswfsdatasourceuri.h
Auto-merging src/providers/wfs/qgswfsprovider.cpp
Auto-merging src/ui/qgsnewhttpconnectionbase.ui
CONFLICT (content): Merge conflict in src/ui/qgsnewhttpconnectionbase.ui
Auto-merging tests/src/python/test_provider_wfs.py

```
</details>

To backport manually, run these commands in your terminal:
```bash
# Fetch latest updates from GitHub
git fetch
# Create a new working tree
git worktree add .worktrees/backport-release-3_40 release-3_40
# Navigate to the new working tree
cd .worktrees/backport-release-3_40
# Create a new branch
git switch --create backport-61515-to-release-3_40
# Cherry-pick the merged commit of this pull request and resolve the conflicts
git cherry-pick 46da0ab3292dcfc599cc92d5613e6b6fb651c1e7,6b9bddf652a659f382e711ab7072b4ced9b6f358
# Push it to GitHub
git push --set-upstream origin backport-61515-to-release-3_40
# Go back to the original working tree
cd ../..
# Delete the working tree
git worktree remove .worktrees/backport-release-3_40
```
Then, create a pull request where the `base` branch is `release-3_40` and the `compare`/`head` branch is `backport-61515-to-release-3_40`.
author_association: COLLABORATOR
user: login: qgis-bot
created_at: 2025-04-17T10:58:13Z
body: The backport to `release-3_42` failed:
```
The process '/usr/bin/git' failed with exit code 1
```
<details>
<summary>stderr</summary>

```
error: could not apply 46da0ab3292... [WFS Provider] Add a UI and URI parameter featureMode=default/SimpleFeatures/ComplexFeatures
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git cherry-pick --continue".
hint: You can instead skip this commit with "git cherry-pick --skip".
hint: To abort and get back to the state before "git cherry-pick",
hint: run "git cherry-pick --abort".
hint: Disable this message with "git config set advice.mergeConflict false"

```
</details>

<details>
<summary>stdout</summary>

```
Auto-merging python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in
Auto-merging python/core/auto_generated/vector/qgsvectorlayer.sip.in
Auto-merging src/core/qgsowsconnection.cpp
Auto-merging src/core/qgsowsconnection.h
Auto-merging src/core/vector/qgsvectorlayer.h
Auto-merging src/gui/qgsmanageconnectionsdialog.cpp
CONFLICT (content): Merge conflict in src/gui/qgsmanageconnectionsdialog.cpp
Auto-merging src/gui/qgsnewhttpconnection.cpp
CONFLICT (content): Merge conflict in src/gui/qgsnewhttpconnection.cpp
Auto-merging src/gui/qgsnewhttpconnection.h
Auto-merging src/providers/wfs/qgswfsconnection.cpp
CONFLICT (content): Merge conflict in src/providers/wfs/qgswfsconnection.cpp
Auto-merging src/providers/wfs/qgswfsconstants.cpp
CONFLICT (content): Merge conflict in src/providers/wfs/qgswfsconstants.cpp
Auto-merging src/providers/wfs/qgswfsconstants.h
CONFLICT (content): Merge conflict in src/providers/wfs/qgswfsconstants.h
Auto-merging src/providers/wfs/qgswfsdataitemguiprovider.cpp
Auto-merging src/providers/wfs/qgswfsdatasourceuri.cpp
CONFLICT (content): Merge conflict in src/providers/wfs/qgswfsdatasourceuri.cpp
Auto-merging src/providers/wfs/qgswfsdatasourceuri.h
Auto-merging src/providers/wfs/qgswfsprovider.cpp
Auto-merging src/ui/qgsnewhttpconnectionbase.ui
CONFLICT (content): Merge conflict in src/ui/qgsnewhttpconnectionbase.ui
Auto-merging tests/src/python/test_provider_wfs.py

```
</details>

To backport manually, run these commands in your terminal:
```bash
# Fetch latest updates from GitHub
git fetch
# Create a new working tree
git worktree add .worktrees/backport-release-3_42 release-3_42
# Navigate to the new working tree
cd .worktrees/backport-release-3_42
# Create a new branch
git switch --create backport-61515-to-release-3_42
# Cherry-pick the merged commit of this pull request and resolve the conflicts
git cherry-pick 46da0ab3292dcfc599cc92d5613e6b6fb651c1e7,6b9bddf652a659f382e711ab7072b4ced9b6f358
# Push it to GitHub
git push --set-upstream origin backport-61515-to-release-3_42
# Go back to the original working tree
cd ../..
# Delete the working tree
git worktree remove .worktrees/backport-release-3_42
```
Then, create a pull request where the `base` branch is `release-3_42` and the `compare`/`head` branch is `backport-61515-to-release-3_42`.
author_association: COLLABORATOR
user: login: rouault
created_at: 2025-04-17T11:35:29Z
body: Manual backports in 3.40 (#61535) and 3.42 (#61534)
author_association: CONTRIBUTOR
user: login: qgis-bot
created_at: 2025-04-17T19:53:24Z
body: @rouault
This pull request has been tagged as **requiring documentation**.

A documentation ticket will be opened at https://github.com/qgis/QGIS-Documentation **when this PR is merged**.

**Please update the description** (not the comments) with helpful description and screenshot to help the work from documentors.
Also, any commit having [needs-doc] or [Needs Documentation] in will see its message pushed to the issue, so please be as verbose as you can.

Thank you!
author_association: COLLABORATOR
user: login: qgis-bot
created_at: 2025-04-17T19:53:25Z
body: @rouault

This pull request has been tagged for the [changelog](https://www.qgis.org/en/site/forusers/visualchangelogs.html).

* The description will be harvested so please provide a "nearly-ready" text for the final changelog
* If possible, add a nice illustration of the feature. Only the **first** one in the description will be harvested (GIF accepted as well)
* If you can, it's better to give credits to your sponsor, see below for different formats.

You can edit the description.

<details>
<summary>Format available for credits</summary>
<br />

- `Funded by NAME`
- `Funded by URL`
- `Funded by NAME URL`
- `Sponsored by NAME`
- `Sponsored by URL`
- `Sponsored by NAME URL`

</details>

Thank you!
author_association: COLLABORATOR
user: login: qgis-bot
created_at: 2025-04-17T19:53:29Z
body: @rouault
A documentation ticket has been opened at https://github.com/qgis/QGIS-Documentation/issues/9811
It is **your** responsibility to visit this ticket and add as much detail as possible for the documentation team to correctly document this change.
Thank you!
author_association: COLLABORATOR