diff --git a/core/tabularbulkexport.class.inc.php b/core/tabularbulkexport.class.inc.php index a755e85a33..4446d204df 100644 --- a/core/tabularbulkexport.class.inc.php +++ b/core/tabularbulkexport.class.inc.php @@ -325,7 +325,7 @@ EOF $sQueryId = utils::ReadParam('query', null, true); $sFields = utils::ReadParam('fields', null, true, 'raw_data'); if ((($sFields === null) || ($sFields === '')) && ($sQueryId === null)) { - throw new BulkExportMissingParameterException('fields'); + $sFields = $this->GetFieldsFromQuery(); } else { if (($sQueryId !== null) && ($sQueryId !== null)) { $oSearch = DBObjectSearch::FromOQL('SELECT QueryOQL WHERE id = :query_id', ['query_id' => $sQueryId]); @@ -336,11 +336,11 @@ EOF // No 'fields' parameter supplied, take the fields from the query phrasebook definition $sFields = trim($oQuery->Get('fields')); if ($sFields === '') { - throw new BulkExportMissingParameterException('fields'); + $sFields = $this->GetFieldsFromQuery(); } } } else { - throw BulkExportException('Invalid value for the parameter: query. There is no Query Phrasebook with id = '.$sQueryId, Dict::Format('Core:BulkExport:InvalidParameter_Query', $sQueryId)); + throw new BulkExportException('Invalid value for the parameter: query. There is no Query Phrasebook with id = '.$sQueryId, Dict::Format('Core:BulkExport:InvalidParameter_Query', $sQueryId)); } } } @@ -348,6 +348,39 @@ EOF $this->SetFields($sFields); } + public function GetFieldsFromQuery(): string + { + if (is_null($this->oSearch)) { + throw new BulkExportMissingParameterException("fields"); + } + + $aSelectedClasses = $this->oSearch->GetSelectedClasses(); + $aAuthorizedClasses = []; + $aFields = []; + foreach ($aSelectedClasses as $sAlias => $sClassName) { + if (UserRights::IsActionAllowed($sClassName, UR_ACTION_BULK_READ) == UR_ALLOWED_YES) { + $aAuthorizedClasses[$sAlias] = $sClassName; + } + } + foreach ($aAuthorizedClasses as $sAlias => $sClassName) { + foreach (MetaModel::GetZListItems($sClassName, 'details') as $sAttCode) { + //$oAttDef = Metamodel::GetAttributeDef($sClassName, $sAttCode); + if (utils::IsNullOrEmptyString($sAlias)) { + $aFields[] = "$sAttCode"; + } else { + $aFields[] = "$sAlias.$sAttCode"; + } + } + } + + if (count($aFields) === 0) { + IssueLog::Error("User is not allowed to see any field for exported OQL"); + throw new BulkExportMissingParameterException("fields"); + } + + return implode(',', $aFields); + } + public function SetFields($sFields) { // Interpret (and check) the list of fields diff --git a/tests/php-unit-tests/unitary-tests/webservices/ExportTest.php b/tests/php-unit-tests/unitary-tests/webservices/ExportTest.php new file mode 100644 index 0000000000..4e07a9cc73 --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/webservices/ExportTest.php @@ -0,0 +1,184 @@ +sLogin = "import-".$sUid; + $this->CreateContactlessUser($this->sLogin, self::$aURP_Profiles['Administrator'], $this->sPassword); + $this->sPortalLogin = "import-portaluser-".$sUid; + $this->CreateContactlessUser($this->sPortalLogin, self::$aURP_Profiles['Portal user'], $this->sPassword); + } + + public function testExportWithExpressionAndFields() + { + $aParams = [ + 'expression' => 'SELECT User', + 'fields' => 'login', + 'format' => 'csv', + 'filename' => 'toto.csv', + 'charset' => 'UTF-8', + ]; + $sOutput = $this->performExportTesting($aParams, $this->sLogin); + + $sExpectedHeader = <<
assertStringContainsString($sExpectedHeader, $sOutput, "Header ($sExpectedHeader)\n should be in export-v2 answer: \n$sOutput"); + $this->assertStringContainsString($this->sLogin, $sOutput, "Login ({$this->sLogin})\n should be in export-v2 answer: \n$sOutput"); + } + + public function testExportWithExpressionAndWithoutFields() + { + $aParams = [ + 'expression' => 'SELECT User', + 'format' => 'csv', + 'filename' => 'toto.csv', + 'charset' => 'UTF-8', + ]; + $sOutput = $this->performExportTesting($aParams, $this->sLogin); + + $sExpectedHeader = <<
assertStringContainsString($sExpectedHeader, $sOutput, "Header ($sExpectedHeader)\n should be in export-v2 answer: \n$sOutput"); + $this->assertTrue(false !== strpos($sOutput, $this->sLogin), "Login ({$this->sLogin})\n should be in export-v2 answer: \n$sOutput"); + } + + public function testExportWithOQLRequestAndWithoutFields() + { + $oOuery = $this->createObject( + QueryOQL::class, + [ + 'name' => "TestExport-".uniqid(), + 'description' => "blabla", + "oql" => "SELECT User", + ] + ); + + $aParams = [ + 'query' => $oOuery->GetKey(), + 'format' => 'spreadsheet', + 'filename' => 'toto.csv', + 'charset' => 'UTF-8', + ]; + $sOutput = $this->performExportTesting($aParams, $this->sLogin); + + $sExpectedHeader = <<
Person +Organization +Email +Login +Language +Status +Profiles +Allowed Organizations +log +HEADER; + $this->assertStringContainsString("$sExpectedHeader", $sOutput, "Header ($sExpectedHeader)\n should be in export-v2 answer: \n$sOutput"); + + $this->assertStringContainsString($this->sLogin, $sOutput, "Login ({$this->sLogin})\n should be in export-v2 answer: \n$sOutput"); + } + + public function testExportWithOqlRequestAndWithoutFieldsButNoReadRights() + { + $oOuery = $this->createObject( + QueryOQL::class, + [ + 'name' => "TestExport-".uniqid(), + 'description' => "blabla", + "oql" => "SELECT User", + ] + ); + + $aParams = [ + 'query' => $oOuery->GetKey(), + 'format' => 'csv', + 'filename' => 'toto.csv', + 'charset' => 'UTF-8', + ]; + + try { + $this->performExportTesting($aParams, $this->sPortalLogin); + } catch (\Exception $e) { + $this->assertStringContainsString("PHP Error (parsing, or runtime) ", $e->getMessage()); + $this->assertStringContainsString("ERROR: Missing parameter: fields", $e->getMessage()); + } + } + + public function testExportWithOQLRequestAndFields() + { + $oOuery = $this->createObject( + QueryOQL::class, + [ + 'name' => "TestExport-".uniqid(), + 'description' => "blabla", + "fields" => "login, status", + "oql" => "SELECT User", + ] + ); + + $aParams = [ + 'query' => $oOuery->GetKey(), + 'fields' => 'login', + 'format' => 'spreadsheet', + 'filename' => 'toto.csv', + 'charset' => 'UTF-8', + ]; + $sOutput = $this->performExportTesting($aParams, $this->sLogin); + + $sExpectedHeader = <<
Login +HEADER; + $this->assertStringContainsString("$sExpectedHeader", $sOutput, "Header ($sExpectedHeader)\n should be in export-v2 answer: \n$sOutput"); + + $this->assertStringContainsString($this->sLogin, $sOutput, "Login ({$this->sLogin})\n should be in export-v2 answer: \n$sOutput"); + } + + public function testExportWithExpressionAndWithoutFieldsButNoReadRights() + { + $aParams = [ + 'expression' => 'SELECT User', + 'format' => 'csv', + 'filename' => 'toto.csv', + 'charset' => 'UTF-8', + ]; + + try { + $this->performExportTesting($aParams, $this->sPortalLogin); + } catch (\Exception $e) { + $this->assertStringContainsString("PHP Error (parsing, or runtime) ", $e->getMessage()); + $this->assertStringContainsString("ERROR: Missing parameter: fields", $e->getMessage()); + } + } + + private function performExportTesting(array $aParams, $sLogin, $iExpectedExitCode = 0) + { + $aRes = \utils::ExecITopScript('webservices/export-v2.php', $aParams, $sLogin, $this->sPassword); + $aOutput = $aRes[1]; + $sOutput = implode("\n", $aOutput); + $iRes = $aRes[0]; + $this->assertEquals($iExpectedExitCode, $iRes, "exit code: $iRes | $sOutput"); + return $sOutput; + } + +}