From 38ec9efe5ebcd0680c06bc805f197ede204b269a Mon Sep 17 00:00:00 2001
From: Andrea Giudiceandrea <andreaerdna@libero.it>
Date: Sat, 11 Jan 2025 17:23:28 +0100
Subject: [PATCH] [WFS] Fix filtering by QGIS expression in order to support
 "@geometry" (Fix #60094) (#60105)

Fix #60094
---
 .../vector/qgsvectorlayer.sip.in              |   4 +-
 .../vector/qgsvectorlayer.sip.in              |   4 +-
 src/core/qgsogcutils.cpp                      |   4 +-
 src/core/vector/qgsvectorlayer.h              |   4 +-
 tests/src/core/testqgsogcutils.cpp            | 135 ++++++++++++++----
 5 files changed, 119 insertions(+), 32 deletions(-)

diff --git a/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in
index 2cf57cdf6761..c9924db14036 100644
--- a/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in
+++ b/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in
@@ -145,8 +145,8 @@ Also note:
 
 - You can use various functions available in the QGIS Expression list,
   however the function must exist server side and have the same name and arguments to work.
-- Use the special $geometry parameter to provide the layer geometry column as input
-  into the spatial binary operators e.g intersects($geometry, geomFromWKT('POINT (5 6)'))
+- Use the special ``@geometry`` parameter to provide the layer geometry column as input
+  into the spatial binary operators e.g ``intersects(@geometry, geomFromWKT('POINT (5 6)'))``
 
 OGC API Features data provider (oapif)
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/python/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/core/auto_generated/vector/qgsvectorlayer.sip.in
index 2cf57cdf6761..c9924db14036 100644
--- a/python/core/auto_generated/vector/qgsvectorlayer.sip.in
+++ b/python/core/auto_generated/vector/qgsvectorlayer.sip.in
@@ -145,8 +145,8 @@ Also note:
 
 - You can use various functions available in the QGIS Expression list,
   however the function must exist server side and have the same name and arguments to work.
-- Use the special $geometry parameter to provide the layer geometry column as input
-  into the spatial binary operators e.g intersects($geometry, geomFromWKT('POINT (5 6)'))
+- Use the special ``@geometry`` parameter to provide the layer geometry column as input
+  into the spatial binary operators e.g ``intersects(@geometry, geomFromWKT('POINT (5 6)'))``
 
 OGC API Features data provider (oapif)
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/src/core/qgsogcutils.cpp b/src/core/qgsogcutils.cpp
index a68a730f9ba2..16a3d25f3e1f 100644
--- a/src/core/qgsogcutils.cpp
+++ b/src/core/qgsogcutils.cpp
@@ -2318,7 +2318,7 @@ static bool isGeometryColumn( const QgsExpressionNode *node )
 
   const QgsExpressionNodeFunction *fn = static_cast<const QgsExpressionNodeFunction *>( node );
   QgsExpressionFunction *fd = QgsExpression::Functions()[fn->fnIndex()];
-  return fd->name() == QLatin1String( "$geometry" );
+  return fd->name() == QLatin1String( "$geometry" ) || ( fd->name() == QLatin1String( "var" ) && fn->referencedVariables().contains( QLatin1String( "geometry" ) ) );
 }
 
 static QgsGeometry geometryFromConstExpr( const QgsExpressionNode *node )
@@ -2382,7 +2382,7 @@ QDomElement QgsOgcUtilsExprToFilter::expressionFunctionToOgcFilter( const QgsExp
     }
     else
     {
-      mErrorMessage = QObject::tr( "<BBOX> is currently supported only in form: bbox($geometry, geomFromWKT('…'))" );
+      mErrorMessage = QObject::tr( "<BBOX> is currently supported only in form: bbox(@geometry, geomFromWKT('…'))" );
       return QDomElement();
     }
   }
diff --git a/src/core/vector/qgsvectorlayer.h b/src/core/vector/qgsvectorlayer.h
index 4e3f5ba6145d..725e53fc6e19 100644
--- a/src/core/vector/qgsvectorlayer.h
+++ b/src/core/vector/qgsvectorlayer.h
@@ -216,8 +216,8 @@ typedef QSet<int> QgsAttributeIds;
  *
  * - You can use various functions available in the QGIS Expression list,
  *   however the function must exist server side and have the same name and arguments to work.
- * - Use the special $geometry parameter to provide the layer geometry column as input
- *   into the spatial binary operators e.g intersects($geometry, geomFromWKT('POINT (5 6)'))
+ * - Use the special ``@geometry`` parameter to provide the layer geometry column as input
+ *   into the spatial binary operators e.g ``intersects(@geometry, geomFromWKT('POINT (5 6)'))``
  *
  * \subsection oapif OGC API Features data provider (oapif)
  *
diff --git a/tests/src/core/testqgsogcutils.cpp b/tests/src/core/testqgsogcutils.cpp
index 6237a903b7a4..17b67212dbb4 100644
--- a/tests/src/core/testqgsogcutils.cpp
+++ b/tests/src/core/testqgsogcutils.cpp
@@ -733,26 +733,47 @@ void TestQgsOgcUtils::testExpressionToOgcFilter_data()
                                                                              "</ogc:Not>"
                                                                              "</ogc:Filter>" );
 
-  QTest::newRow( "intersects_bbox" ) << QStringLiteral( "intersects_bbox($geometry, geomFromWKT('POINT (5 6)'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">"
-                                                                                                                               "<ogc:BBOX>"
-                                                                                                                               "<ogc:PropertyName>geometry</ogc:PropertyName>"
-                                                                                                                               "<gml:Box><gml:coordinates ts=\" \" cs=\",\">5,6 5,6</gml:coordinates></gml:Box>"
-                                                                                                                               "</ogc:BBOX>"
-                                                                                                                               "</ogc:Filter>" );
-
-  QTest::newRow( "intersects + wkt" ) << QStringLiteral( "intersects($geometry, geomFromWKT('POINT (5 6)'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">"
-                                                                                                                           "<ogc:Intersects>"
-                                                                                                                           "<ogc:PropertyName>geometry</ogc:PropertyName>"
-                                                                                                                           "<gml:Point><gml:coordinates ts=\" \" cs=\",\">5,6</gml:coordinates></gml:Point>"
-                                                                                                                           "</ogc:Intersects>"
-                                                                                                                           "</ogc:Filter>" );
-
-  QTest::newRow( "contains + gml" ) << QStringLiteral( "contains($geometry, geomFromGML('<Point><coordinates cs=\",\" ts=\" \">5,6</coordinates></Point>'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">"
-                                                                                                                                                                           "<ogc:Contains>"
-                                                                                                                                                                           "<ogc:PropertyName>geometry</ogc:PropertyName>"
-                                                                                                                                                                           "<Point><coordinates ts=\" \" cs=\",\">5,6</coordinates></Point>"
-                                                                                                                                                                           "</ogc:Contains>"
-                                                                                                                                                                           "</ogc:Filter>" );
+  QTest::newRow( "intersects_bbox $geometry" ) << QStringLiteral( "intersects_bbox($geometry, geomFromWKT('POINT (5 6)'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">"
+                                                                                                                                         "<ogc:BBOX>"
+                                                                                                                                         "<ogc:PropertyName>geometry</ogc:PropertyName>"
+                                                                                                                                         "<gml:Box><gml:coordinates ts=\" \" cs=\",\">5,6 5,6</gml:coordinates></gml:Box>"
+                                                                                                                                         "</ogc:BBOX>"
+                                                                                                                                         "</ogc:Filter>" );
+
+  QTest::newRow( "intersects + wkt $geometry" ) << QStringLiteral( "intersects($geometry, geomFromWKT('POINT (5 6)'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">"
+                                                                                                                                     "<ogc:Intersects>"
+                                                                                                                                     "<ogc:PropertyName>geometry</ogc:PropertyName>"
+                                                                                                                                     "<gml:Point><gml:coordinates ts=\" \" cs=\",\">5,6</gml:coordinates></gml:Point>"
+                                                                                                                                     "</ogc:Intersects>"
+                                                                                                                                     "</ogc:Filter>" );
+
+  QTest::newRow( "contains + gml $geometry" ) << QStringLiteral( "contains($geometry, geomFromGML('<Point><coordinates cs=\",\" ts=\" \">5,6</coordinates></Point>'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">"
+                                                                                                                                                                                     "<ogc:Contains>"
+                                                                                                                                                                                     "<ogc:PropertyName>geometry</ogc:PropertyName>"
+                                                                                                                                                                                     "<Point><coordinates ts=\" \" cs=\",\">5,6</coordinates></Point>"
+                                                                                                                                                                                     "</ogc:Contains>"
+                                                                                                                                                                                     "</ogc:Filter>" );
+
+  QTest::newRow( "intersects_bbox @geometry" ) << QStringLiteral( "intersects_bbox(@geometry, geomFromWKT('POINT (5 6)'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">"
+                                                                                                                                         "<ogc:BBOX>"
+                                                                                                                                         "<ogc:PropertyName>geometry</ogc:PropertyName>"
+                                                                                                                                         "<gml:Box><gml:coordinates ts=\" \" cs=\",\">5,6 5,6</gml:coordinates></gml:Box>"
+                                                                                                                                         "</ogc:BBOX>"
+                                                                                                                                         "</ogc:Filter>" );
+
+  QTest::newRow( "intersects + wkt @geometry" ) << QStringLiteral( "intersects(@geometry, geomFromWKT('POINT (5 6)'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">"
+                                                                                                                                     "<ogc:Intersects>"
+                                                                                                                                     "<ogc:PropertyName>geometry</ogc:PropertyName>"
+                                                                                                                                     "<gml:Point><gml:coordinates ts=\" \" cs=\",\">5,6</gml:coordinates></gml:Point>"
+                                                                                                                                     "</ogc:Intersects>"
+                                                                                                                                     "</ogc:Filter>" );
+
+  QTest::newRow( "contains + gml @geometry" ) << QStringLiteral( "contains(@geometry, geomFromGML('<Point><coordinates cs=\",\" ts=\" \">5,6</coordinates></Point>'))" ) << QString( "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">"
+                                                                                                                                                                                     "<ogc:Contains>"
+                                                                                                                                                                                     "<ogc:PropertyName>geometry</ogc:PropertyName>"
+                                                                                                                                                                                     "<Point><coordinates ts=\" \" cs=\",\">5,6</coordinates></Point>"
+                                                                                                                                                                                     "</ogc:Contains>"
+                                                                                                                                                                                     "</ogc:Filter>" );
 }
 
 void TestQgsOgcUtils::testExpressionToOgcFilterWFS11()
@@ -791,7 +812,7 @@ void TestQgsOgcUtils::testExpressionToOgcFilterWFS11_data()
   QTest::addColumn<QString>( "srsName" );
   QTest::addColumn<QString>( "xmlText" );
 
-  QTest::newRow( "bbox" )
+  QTest::newRow( "bbox $geometry" )
     << QStringLiteral( "intersects_bbox($geometry, geomFromWKT('POLYGON((2 49,2 50,3 50,3 49,2 49))'))" )
     << QStringLiteral( "urn:ogc:def:crs:EPSG::4326" )
     << QString(
@@ -805,6 +826,21 @@ void TestQgsOgcUtils::testExpressionToOgcFilterWFS11_data()
          "</ogc:BBOX>"
          "</ogc:Filter>"
        );
+
+  QTest::newRow( "bbox @geometry" )
+    << QStringLiteral( "intersects_bbox(@geometry, geomFromWKT('POLYGON((2 49,2 50,3 50,3 49,2 49))'))" )
+    << QStringLiteral( "urn:ogc:def:crs:EPSG::4326" )
+    << QString(
+         "<ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\">"
+         "<ogc:BBOX>"
+         "<ogc:PropertyName>my_geometry_name</ogc:PropertyName>"
+         "<gml:Envelope srsName=\"urn:ogc:def:crs:EPSG::4326\">"
+         "<gml:lowerCorner>49 2</gml:lowerCorner>"
+         "<gml:upperCorner>50 3</gml:upperCorner>"
+         "</gml:Envelope>"
+         "</ogc:BBOX>"
+         "</ogc:Filter>"
+       );
 }
 
 void TestQgsOgcUtils::testExpressionToOgcFilterWFS20()
@@ -860,7 +896,7 @@ void TestQgsOgcUtils::testExpressionToOgcFilterWFS20_data()
                                                                                                         "</fes:PropertyIsEqualTo></fes:Filter>" )
                                       << QStringLiteral( "myns" ) << QStringLiteral( "http://example.com/myns" );
 
-  QTest::newRow( "bbox" )
+  QTest::newRow( "bbox $geometry" )
     << QStringLiteral( "intersects_bbox($geometry, geomFromWKT('POLYGON((2 49,2 50,3 50,3 49,2 49))'))" )
     << QStringLiteral( "urn:ogc:def:crs:EPSG::4326" )
     << QString(
@@ -876,7 +912,7 @@ void TestQgsOgcUtils::testExpressionToOgcFilterWFS20_data()
        )
     << QString() << QString();
 
-  QTest::newRow( "bbox with namespace" )
+  QTest::newRow( "bbox with namespace $geometry" )
     << QStringLiteral( "intersects_bbox($geometry, geomFromWKT('POLYGON((2 49,2 50,3 50,3 49,2 49))'))" )
     << QStringLiteral( "urn:ogc:def:crs:EPSG::4326" )
     << QString(
@@ -892,7 +928,7 @@ void TestQgsOgcUtils::testExpressionToOgcFilterWFS20_data()
        )
     << QStringLiteral( "myns" ) << QStringLiteral( "http://example.com/myns" );
 
-  QTest::newRow( "intersects" )
+  QTest::newRow( "intersects $geometry" )
     << QStringLiteral( "intersects($geometry, geomFromWKT('POLYGON((2 49,2 50,3 50,3 49,2 49))'))" )
     << QStringLiteral( "urn:ogc:def:crs:EPSG::4326" )
     << QString(
@@ -910,6 +946,57 @@ void TestQgsOgcUtils::testExpressionToOgcFilterWFS20_data()
          "</fes:Filter>"
        )
     << QString() << QString();
+
+  QTest::newRow( "bbox @geometry" )
+    << QStringLiteral( "intersects_bbox(@geometry, geomFromWKT('POLYGON((2 49,2 50,3 50,3 49,2 49))'))" )
+    << QStringLiteral( "urn:ogc:def:crs:EPSG::4326" )
+    << QString(
+         "<fes:Filter xmlns:fes=\"http://www.opengis.net/fes/2.0\" xmlns:gml=\"http://www.opengis.net/gml/3.2\">"
+         "<fes:BBOX>"
+         "<fes:ValueReference>my_geometry_name</fes:ValueReference>"
+         "<gml:Envelope srsName=\"urn:ogc:def:crs:EPSG::4326\">"
+         "<gml:lowerCorner>49 2</gml:lowerCorner>"
+         "<gml:upperCorner>50 3</gml:upperCorner>"
+         "</gml:Envelope>"
+         "</fes:BBOX>"
+         "</fes:Filter>"
+       )
+    << QString() << QString();
+
+  QTest::newRow( "bbox with namespace @geometry" )
+    << QStringLiteral( "intersects_bbox(@geometry, geomFromWKT('POLYGON((2 49,2 50,3 50,3 49,2 49))'))" )
+    << QStringLiteral( "urn:ogc:def:crs:EPSG::4326" )
+    << QString(
+         "<fes:Filter xmlns:fes=\"http://www.opengis.net/fes/2.0\" xmlns:gml=\"http://www.opengis.net/gml/3.2\" xmlns:myns=\"http://example.com/myns\">"
+         "<fes:BBOX>"
+         "<fes:ValueReference>myns:my_geometry_name</fes:ValueReference>"
+         "<gml:Envelope srsName=\"urn:ogc:def:crs:EPSG::4326\">"
+         "<gml:lowerCorner>49 2</gml:lowerCorner>"
+         "<gml:upperCorner>50 3</gml:upperCorner>"
+         "</gml:Envelope>"
+         "</fes:BBOX>"
+         "</fes:Filter>"
+       )
+    << QStringLiteral( "myns" ) << QStringLiteral( "http://example.com/myns" );
+
+  QTest::newRow( "intersects @geometry" )
+    << QStringLiteral( "intersects(@geometry, geomFromWKT('POLYGON((2 49,2 50,3 50,3 49,2 49))'))" )
+    << QStringLiteral( "urn:ogc:def:crs:EPSG::4326" )
+    << QString(
+         "<fes:Filter xmlns:fes=\"http://www.opengis.net/fes/2.0\" xmlns:gml=\"http://www.opengis.net/gml/3.2\">"
+         "<fes:Intersects>"
+         "<fes:ValueReference>my_geometry_name</fes:ValueReference>"
+         "<gml:Polygon gml:id=\"qgis_id_geom_1\" srsName=\"urn:ogc:def:crs:EPSG::4326\">"
+         "<gml:exterior>"
+         "<gml:LinearRing>"
+         "<gml:posList srsDimension=\"2\">49 2 50 2 50 3 49 3 49 2</gml:posList>"
+         "</gml:LinearRing>"
+         "</gml:exterior>"
+         "</gml:Polygon>"
+         "</fes:Intersects>"
+         "</fes:Filter>"
+       )
+    << QString() << QString();
 }
 
 Q_DECLARE_METATYPE( QgsOgcUtils::GMLVersion )