Skip to content

Commit

Permalink
Attribute filter - activate multiple values capability only for Postg…
Browse files Browse the repository at this point in the history
…reSQL layers & if allow_multiple_acl_values is true
  • Loading branch information
mdouchin committed Aug 23, 2024
1 parent b4ecaad commit 29ba3d0
Show file tree
Hide file tree
Showing 5 changed files with 411 additions and 25 deletions.
46 changes: 36 additions & 10 deletions lizmap/modules/lizmap/lib/Project/Project.php
Original file line number Diff line number Diff line change
Expand Up @@ -1271,8 +1271,27 @@ public function getLoginFilters($layers, $edition = false)
$cnx = $this->appContext->getDbConnection();
$quotedField = $cnx->encloseName($attribute);

// Get QGIS vector layer provider
/** @var null|\qgisVectorLayer $qgisLayer The QGIS vector layer instance */
$qgisLayer = $this->qgis->getLayer($layerByTypeName->id, $this);
$provider = 'unknown';
if ($qgisLayer) {
$provider = $qgisLayer->getProvider();
}

// default no user connected
$filter = "{$quotedField} = 'all' OR {$quotedField} LIKE 'all,%' OR {$quotedField} LIKE '%,all' OR {$quotedField} LIKE '%,all,%'";
$filter = "{$quotedField} = 'all'";

// For PostgreSQL layers, allow multiple values in the filter field
// E.g. "groupe_a,other_group"
if ($provider == 'postgres'
&& property_exists($loginFilteredConfig, 'allow_multiple_acl_values')
&& $this->optionToBoolean($loginFilteredConfig->allow_multiple_acl_values)
) {
$filter .= " OR {$quotedField} LIKE 'all,%'";
$filter .= " OR {$quotedField} LIKE '%,all'";
$filter .= " OR {$quotedField} LIKE '%,all,%'";
}

// A user is connected
if ($this->appContext->userIsConnected()) {
Expand Down Expand Up @@ -1307,17 +1326,24 @@ public function getLoginFilters($layers, $edition = false)
// equality
$valueFilters[] = "{$quotedField} = {$quotedValue}";

// begins with value & comma
$quotedLikeValue = $cnx->quote("{$value},%");
$valueFilters[] = "{$quotedField} LIKE {$quotedLikeValue}";
// For PostgreSQL layers, allow multiple values in the filter field
// E.g. "groupe_a,other_group"
if ($provider == 'postgres'
&& property_exists($loginFilteredConfig, 'allow_multiple_acl_values')
&& $this->optionToBoolean($loginFilteredConfig->allow_multiple_acl_values)
) {
// begins with value & comma
$quotedLikeValue = $cnx->quote("{$value},%");
$valueFilters[] = "{$quotedField} LIKE {$quotedLikeValue}";

// ends with comma & value
$quotedLikeValue = $cnx->quote("%,{$value}");
$valueFilters[] = "{$quotedField} LIKE {$quotedLikeValue}";
// ends with comma & value
$quotedLikeValue = $cnx->quote("%,{$value}");
$valueFilters[] = "{$quotedField} LIKE {$quotedLikeValue}";

// value between two commas
$quotedLikeValue = $cnx->quote("%,{$value},%");
$valueFilters[] = "{$quotedField} LIKE {$quotedLikeValue}";
// value between two commas
$quotedLikeValue = $cnx->quote("%,{$value},%");
$valueFilters[] = "{$quotedField} LIKE {$quotedLikeValue}";
}

// Build the filter for this value
$allValuesFilters[] = implode(' OR ', $valueFilters);
Expand Down
6 changes: 3 additions & 3 deletions tests/end2end/playwright/filter-layer-by-user.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ test.describe('Filter layer data by user - not connected', () => {
test('WMS GetFeatureInfo JSON', async ({ page }) => {

const getFeatureInfo = await page.evaluate(async () => {
return await fetch("/index.php/lizmap/service?repository=testsrepository&project=filter_layer_by_user&SERVICE=WMS&REQUEST=GetFeatureInfo&VERSION=1.3.0&CRS=EPSG%3A2154&INFO_FORMAT=application%2Fjson&QUERY_LAYERS=green_filter_layer_by_user_edition_only%2Cblue_filter_layer_by_user%2Cred_layer_with_no_filter&LAYERS=green_filter_layer_by_user_edition_only%2Cblue_filter_layer_by_user%2Cred_layer_with_no_filter&STYLE=default%2Cdefault%2Cdefault&BBOX=-310600.3821764754%2C726545.2026738503%2C364617.6349262256%2C1244071.2377259205&FEATURE_COUNT=10&FILTER=green_filter_layer_by_user_edition_only:\"gid\" > 0")
return await fetch("/index.php/lizmap/service?repository=testsrepository&project=filter_layer_by_user&SERVICE=WMS&REQUEST=GetFeatureInfo&VERSION=1.3.0&CRS=EPSG%3A2154&INFO_FORMAT=application%2Fjson&QUERY_LAYERS=green_filter_layer_by_user_edition_only%2Cblue_filter_layer_by_user%2Cred_layer_with_no_filter&LAYERS=green_filter_layer_by_user_edition_only%2Cblue_filter_layer_by_user%2Cred_layer_with_no_filter&STYLE=default%2Cdefault%2Cdefault&FEATURE_COUNT=10&FILTER=green_filter_layer_by_user_edition_only:\"gid\" > 0")
.then(r => r.ok ? r.json() : Promise.reject(r))
})

Expand Down Expand Up @@ -165,7 +165,7 @@ test.describe('Filter layer data by user - user in group a', () => {
test('WMS GetFeatureInfo JSON', async ({ page }) => {

const getFeatureInfo = await page.evaluate(async () => {
return await fetch("/index.php/lizmap/service?repository=testsrepository&project=filter_layer_by_user&SERVICE=WMS&REQUEST=GetFeatureInfo&VERSION=1.3.0&CRS=EPSG%3A2154&INFO_FORMAT=application%2Fjson&QUERY_LAYERS=green_filter_layer_by_user_edition_only%2Cblue_filter_layer_by_user%2Cred_layer_with_no_filter&LAYERS=green_filter_layer_by_user_edition_only%2Cblue_filter_layer_by_user%2Cred_layer_with_no_filter&STYLE=default%2Cdefault%2Cdefault&BBOX=-310600.3821764754%2C726545.2026738503%2C364617.6349262256%2C1244071.2377259205&FEATURE_COUNT=10&FILTER=green_filter_layer_by_user_edition_only:\"gid\" > 0")
return await fetch("/index.php/lizmap/service?repository=testsrepository&project=filter_layer_by_user&SERVICE=WMS&REQUEST=GetFeatureInfo&VERSION=1.3.0&CRS=EPSG%3A2154&INFO_FORMAT=application%2Fjson&QUERY_LAYERS=green_filter_layer_by_user_edition_only%2Cblue_filter_layer_by_user%2Cred_layer_with_no_filter&LAYERS=green_filter_layer_by_user_edition_only%2Cblue_filter_layer_by_user%2Cred_layer_with_no_filter&STYLE=default%2Cdefault%2Cdefault&FEATURE_COUNT=10&FILTER=green_filter_layer_by_user_edition_only:\"gid\" > 0")
.then(r => r.ok ? r.json() : Promise.reject(r))
})

Expand Down Expand Up @@ -307,7 +307,7 @@ test.describe('Filter layer data by user - admin', () => {
test('WMS GetFeatureInfo JSON', async ({ page }) => {

const getFeatureInfo = await page.evaluate(async () => {
return await fetch("/index.php/lizmap/service?repository=testsrepository&project=filter_layer_by_user&SERVICE=WMS&REQUEST=GetFeatureInfo&VERSION=1.3.0&CRS=EPSG%3A2154&INFO_FORMAT=application%2Fjson&QUERY_LAYERS=green_filter_layer_by_user_edition_only%2Cblue_filter_layer_by_user%2Cred_layer_with_no_filter&LAYERS=green_filter_layer_by_user_edition_only%2Cblue_filter_layer_by_user%2Cred_layer_with_no_filter&STYLE=default%2Cdefault%2Cdefault&BBOX=-310600.3821764754%2C726545.2026738503%2C364617.6349262256%2C1244071.2377259205&FEATURE_COUNT=10&FILTER=green_filter_layer_by_user_edition_only:\"gid\" > 0")
return await fetch("/index.php/lizmap/service?repository=testsrepository&project=filter_layer_by_user&SERVICE=WMS&REQUEST=GetFeatureInfo&VERSION=1.3.0&CRS=EPSG%3A2154&INFO_FORMAT=application%2Fjson&QUERY_LAYERS=green_filter_layer_by_user_edition_only%2Cblue_filter_layer_by_user%2Cred_layer_with_no_filter&LAYERS=green_filter_layer_by_user_edition_only%2Cblue_filter_layer_by_user%2Cred_layer_with_no_filter&STYLE=default%2Cdefault%2Cdefault&FEATURE_COUNT=10&FILTER=green_filter_layer_by_user_edition_only:\"gid\" > 0")
.then(r => r.ok ? r.json() : Promise.reject(r))
})

Expand Down
2 changes: 2 additions & 0 deletions tests/qgis-projects/tests/filter_layer_by_user.qgs.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -236,13 +236,15 @@
"filterAttribute": "user",
"filterPrivate": "True",
"edition_only": "False",
"allow_multiple_acl_values": "True",
"order": 0
},
"green_filter_layer_by_user_edition_only": {
"layerId": "filter_layer_by_user_edition_only_7bc0e81c_2860_4d6b_8b20_ad6c7b76e42f",
"filterAttribute": "user",
"filterPrivate": "True",
"edition_only": "True",
"allow_multiple_acl_values": "True",
"order": 1
}
},
Expand Down
97 changes: 85 additions & 12 deletions tests/units/classes/Project/ProjectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -270,17 +270,17 @@ public static function getFiltersData()
$aclData2 = array(
'userIsConnected' => false,
);
//$filter1 = '"Group" IN ( \'admin\' , \'groups\' , \'lizmap\' , \'all\' )';
$filter1 = '"Group" = \'admin\' OR "Group" LIKE \'admin,%\' OR "Group" LIKE \'%,admin\' OR "Group" LIKE \'%,admin,%\'';
//$filter1 = '"descr" IN ( \'admin\' , \'groups\' , \'lizmap\' , \'all\' )';
$filter1 = '"descr" = \'admin\' OR "descr" LIKE \'admin,%\' OR "descr" LIKE \'%,admin\' OR "descr" LIKE \'%,admin,%\'';
$filter1 .= ' OR ';
$filter1 .= '"Group" = \'groups\' OR "Group" LIKE \'groups,%\' OR "Group" LIKE \'%,groups\' OR "Group" LIKE \'%,groups,%\'';
$filter1 .= '"descr" = \'groups\' OR "descr" LIKE \'groups,%\' OR "descr" LIKE \'%,groups\' OR "descr" LIKE \'%,groups,%\'';
$filter1 .= ' OR ';
$filter1 .= '"Group" = \'lizmap\' OR "Group" LIKE \'lizmap,%\' OR "Group" LIKE \'%,lizmap\' OR "Group" LIKE \'%,lizmap,%\'';
$filter1 .= '"descr" = \'lizmap\' OR "descr" LIKE \'lizmap,%\' OR "descr" LIKE \'%,lizmap\' OR "descr" LIKE \'%,lizmap,%\'';
$filter1 .= ' OR ';
$filter1 .= '"Group" = \'all\' OR "Group" LIKE \'all,%\' OR "Group" LIKE \'%,all\' OR "Group" LIKE \'%,all,%\'';
$filter1 .= '"descr" = \'all\' OR "descr" LIKE \'all,%\' OR "descr" LIKE \'%,all\' OR "descr" LIKE \'%,all,%\'';

//$filter2 = '"Group" = \'all\'';
$filter2 = '"Group" = \'all\' OR "Group" LIKE \'all,%\' OR "Group" LIKE \'%,all\' OR "Group" LIKE \'%,all,%\'';
$filter2 = '"descr" = \'all\' OR "descr" LIKE \'all,%\' OR "descr" LIKE \'%,all\' OR "descr" LIKE \'%,all,%\'';

return array(
array($aclData1, $filter1),
Expand All @@ -296,20 +296,93 @@ public static function getFiltersData()
*/
public function testGetLoginFilters($aclData, $expectedFilters)
{
$file = __DIR__.'/Ressources/montpellier_filtered.qgs.cfg';
$json = json_decode(file_get_contents($file));
$data = array(
'WMSInformation' => array(),
'layers' => array(),
);
$file = __DIR__.'/Ressources/edition_embed_parent.qgs';
$context = new ContextForTests();
$context->setResult($aclData);
$proj = new ProjectForTests($context);
$testQgis = new QgisProjectForTests($data);
$rep = new Project\Repository('key', array(), null, null, null);
$testQgis->setPath($file);
$testQgis->readXMLProjectTest($file);

$cfg = json_decode(file_get_contents( __DIR__.'/Ressources/edition_embed_parent_filtered.qgs.cfg'));
$config = new Project\ProjectConfig($cfg);
$proj->setCfg($config);
$proj->setQgis($testQgis);
$proj->setRepo($rep);
$proj->setKey('test');

// Test
$expectedFilters = array(
'edition_line' => array_merge((array)$json->loginFilteredLayers->edition_line,
array('layername' => 'edition_line',
'edition_layer_embed_point' => array_merge((array)$cfg->loginFilteredLayers->edition_layer_embed_point,
array('layername' => 'edition_layer_embed_point',
'filter' => $expectedFilters)),
);
$config = new Project\ProjectConfig($json);
$filters = $proj->getLoginFilters(array('edition_layer_embed_point'));
$this->assertEquals($expectedFilters, $filters);

}

public static function getFiltersDataNotMultiple()
{
$aclData1 = array(
'userIsConnected' => true,
'userSession' => (object) array('login' => 'admin'),
'groups' => array('admin', 'groups', 'lizmap'),
);
$aclData2 = array(
'userIsConnected' => false,
);
$filter1 = '"descr" = \'admin\' OR "descr" = \'groups\' OR "descr" = \'lizmap\' OR "descr" = \'all\'';
$filter2 = '"descr" = \'all\'';

return array(
array($aclData1, $filter1),
array($aclData2, $filter2),
);
}

/**
* @dataProvider getFiltersDataNotMultiple
*
* @param mixed $aclData
* @param mixed $expectedFilters
*/
public function testGetLoginFiltersNotMultiple($aclData, $expectedFilters)
{
$data = array(
'WMSInformation' => array(),
'layers' => array(),
);
$file = __DIR__.'/Ressources/edition_embed_parent.qgs';
$context = new ContextForTests();
$context->setResult($aclData);
$proj = new ProjectForTests($context);
$testQgis = new QgisProjectForTests($data);
$rep = new Project\Repository('key', array(), null, null, null);
$testQgis->setPath($file);
$testQgis->readXMLProjectTest($file);

$cfg = json_decode(file_get_contents( __DIR__.'/Ressources/edition_embed_parent_filtered.qgs.cfg'));
$config = new Project\ProjectConfig($cfg);
$proj->setCfg($config);
$filters = $proj->getLoginFilters(array('edition_line'));
$proj->setQgis($testQgis);
$proj->setRepo($rep);
$proj->setKey('test');

// test
$expectedFilters = array(
'edition_layer_embed_line' => array_merge((array)$cfg->loginFilteredLayers->edition_layer_embed_line,
array('layername' => 'edition_layer_embed_line',
'filter' => $expectedFilters)),
);
$filters = $proj->getLoginFilters(array('edition_layer_embed_line'));
$this->assertEquals($expectedFilters, $filters);

}

public static function getGoogleData()
Expand Down
Loading

0 comments on commit 29ba3d0

Please sign in to comment.