Skip to content

Commit

Permalink
Merge pull request #2029 from nextcloud/enh/shared_forms_mapping
Browse files Browse the repository at this point in the history
Get only forms shared with user from database
  • Loading branch information
Chartman123 committed Apr 13, 2024
2 parents c3f848e + 26cebe1 commit e019f14
Show file tree
Hide file tree
Showing 11 changed files with 364 additions and 351 deletions.
18 changes: 18 additions & 0 deletions lib/Constants.php
Expand Up @@ -60,6 +60,24 @@ class Constants {
public const FORM_STATE_CLOSED = 1;
public const FORM_STATE_ARCHIVED = 2;

/**
* Access flags of a form
*/
public const FORM_ACCESS_NOPUBLICSHARE = 0;
public const FORM_ACCESS_PERMITALLUSERS = 1;
public const FORM_ACCESS_SHOWTOALLUSERS = 2;
public const FORM_ACCESS_LEGACYLINK = 3;
public const FORM_ACCESS_LEGACYLINK_PERMITALLUSERS = 4;
public const FORM_ACCESS_LEGACYLINK_SHOWTOALLUSERS = 5;
public const FORM_ACCESS_ARRAY_PERMIT = [
self::FORM_ACCESS_PERMITALLUSERS,
self::FORM_ACCESS_LEGACYLINK_PERMITALLUSERS
];
public const FORM_ACCESS_ARRAY_SHOWN = [
self::FORM_ACCESS_SHOWTOALLUSERS,
self::FORM_ACCESS_LEGACYLINK_SHOWTOALLUSERS
];

/**
* !! Keep in sync with src/models/AnswerTypes.js !!
*/
Expand Down
13 changes: 2 additions & 11 deletions lib/Controller/ApiController.php
Expand Up @@ -114,16 +114,8 @@ public function getForms(): DataResponse {
* @return DataResponse
*/
public function getSharedForms(): DataResponse {
$forms = $this->formMapper->findAll();

$result = [];
foreach ($forms as $form) {
// Check if the form should be shown on sidebar
if (!$this->formsService->isSharedFormShown($form)) {
continue;
}
$result[] = $this->formsService->getPartialFormArray($form);
}
$forms = $this->formsService->getSharedForms($this->currentUser);
$result = array_map(fn (Form $form): array => $this->formsService->getPartialFormArray($form), $forms);

return new DataResponse($result);
}
Expand Down Expand Up @@ -1120,7 +1112,6 @@ public function insertSubmission(int $formId, array $answers, string $shareHash

// Insert new submission
$this->submissionMapper->insert($submission);
$submissionId = $submission->getId();

// Ensure the form is unique if needed.
// If we can not submit anymore then the submission must be unique
Expand Down
44 changes: 41 additions & 3 deletions lib/Db/Form.php
Expand Up @@ -28,6 +28,7 @@

namespace OCA\Forms\Db;

use OCA\Forms\Constants;
use OCP\AppFramework\Db\Entity;

/**
Expand Down Expand Up @@ -69,7 +70,7 @@ class Form extends Entity {
protected $ownerId;
protected $fileId;
protected $fileFormat;
protected $accessJson;
protected $accessEnum;
protected $created;
protected $expires;
protected $isAnonymous;
Expand All @@ -94,12 +95,49 @@ public function __construct() {

// JSON-Decoding of access-column.
public function getAccess(): array {
return json_decode($this->getAccessJson(), true); // assoc=true, => Convert to associative Array
$accessEnum = $this->getAccessEnum();
$access = [];

if ($accessEnum >= Constants::FORM_ACCESS_LEGACYLINK) {
$access['legacyLink'] = true;
}
switch ($accessEnum % Constants::FORM_ACCESS_LEGACYLINK) {
case Constants::FORM_ACCESS_NOPUBLICSHARE:
$access['permitAllUsers'] = false;
$access['showToAllUsers'] = false;
break;
case Constants::FORM_ACCESS_PERMITALLUSERS:
$access['permitAllUsers'] = true;
$access['showToAllUsers'] = false;
break;
case Constants::FORM_ACCESS_SHOWTOALLUSERS:
$access['permitAllUsers'] = true;
$access['showToAllUsers'] = true;
break;
}

return $access;
}

// JSON-Encoding of access-column.
public function setAccess(array $access) {
$this->setAccessJson(json_encode($access));
// No further permissions -> 0
// Permit all users, but don't show in navigation -> 1
// Permit all users and show in navigation -> 2
if (!$access['permitAllUsers'] && !$access['showToAllUsers']) {
$value = Constants::FORM_ACCESS_NOPUBLICSHARE;
} elseif ($access['permitAllUsers'] && !$access['showToAllUsers']) {
$value = Constants::FORM_ACCESS_PERMITALLUSERS;
} else {
$value = Constants::FORM_ACCESS_SHOWTOALLUSERS;
}

// If legacyLink add 3
if (isset($access['legacyLink'])) {
$value += Constants::FORM_ACCESS_LEGACYLINK;
}

$this->setAccessEnum($value);
}

// Read full form
Expand Down
94 changes: 73 additions & 21 deletions lib/Db/FormMapper.php
Expand Up @@ -26,36 +26,30 @@

namespace OCA\Forms\Db;

use OCA\Forms\Constants;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\Share\IShare;
use Psr\Log\LoggerInterface;

/**
* @extends QBMapper<Form>
*/
class FormMapper extends QBMapper {
/** @var QuestionMapper */
private $questionMapper;

/** @var ShareMapper */
private $shareMapper;

/** @var SubmissionMapper */
private $submissionMapper;

/**
* FormMapper constructor.
*
* @param IDBConnection $db
*/
public function __construct(QuestionMapper $questionMapper,
ShareMapper $shareMapper,
SubmissionMapper $submissionMapper,
IDBConnection $db) {
public function __construct(
private QuestionMapper $questionMapper,
private ShareMapper $shareMapper,
private SubmissionMapper $submissionMapper,
private LoggerInterface $logger,
IDBConnection $db,
) {
parent::__construct($db, 'forms_v2_forms', Form::class);
$this->questionMapper = $questionMapper;
$this->shareMapper = $shareMapper;
$this->submissionMapper = $submissionMapper;
}

/**
Expand Down Expand Up @@ -95,18 +89,76 @@ public function findByHash(string $hash): Form {
}

/**
* Get forms shared with the user
* @param string $userId The user ID
* @param string[] $groups IDs of groups the user is memeber of
* @param string[] $teams IDs of teams the user is memeber of
* @param bool $filterShown Set to false to also include forms shared but not visible on sidebar
* @return Form[]
*/
public function findAll(): array {
$qb = $this->db->getQueryBuilder();

$qb->select('*')
public function findSharedForms(string $userId, array $groups = [], array $teams = [], bool $filterShown = true): array {
$qbForms = $this->db->getQueryBuilder();
$qbShares = $this->db->getQueryBuilder();

$memberships = $qbShares->expr()->orX();
// share type user and share with current user
$memberships->add(
$qbShares->expr()->andX(
$qbShares->expr()->eq('share_type', $qbShares->createNamedParameter(IShare::TYPE_USER)),
$qbShares->expr()->eq('share_with', $qbShares->createNamedParameter($userId, IQueryBuilder::PARAM_STR)),
),
);
// share type group and one of the user groups
if (!empty($groups)) {
$memberships->add(
$qbShares->expr()->andX(
$qbShares->expr()->eq('share_type', $qbShares->createNamedParameter(IShare::TYPE_GROUP)),
$qbShares->expr()->in('share_with', $qbShares->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY)),
),
);
}
// share type team and one of the user teams
if (!empty($teams)) {
$memberships->add(
$qbShares->expr()->andX(
$qbShares->expr()->eq('share_type', $qbShares->createNamedParameter(IShare::TYPE_CIRCLE)),
$qbShares->expr()->in('share_with', $qbShares->createNamedParameter($teams, IQueryBuilder::PARAM_STR_ARRAY)),
),
);
}

// get form_id's that are shared to user
$qbShares->selectDistinct('form_id')
->from('forms_v2_shares')
->where($memberships);
$sharedFormIdsResult = $qbShares->executeQuery();
$sharedFormIds = [];
for ($i = 0; $i < $sharedFormIdsResult->rowCount(); $i++) {
$sharedFormIds[] = $sharedFormIdsResult->fetchOne();
}

// build expression for publicy shared forms (default only directly shown)
if ($filterShown) {
$access = $qbForms->expr()->in('access_enum', $qbForms->createNamedParameter(Constants::FORM_ACCESS_ARRAY_SHOWN, IQueryBuilder::PARAM_INT_ARRAY));
} else {
$access = $qbForms->expr()->in('access_enum', $qbForms->createNamedParameter(Constants::FORM_ACCESS_ARRAY_PERMIT, IQueryBuilder::PARAM_INT_ARRAY));
}

$whereTerm = $qbForms->expr()->orX();
$whereTerm->add($qbForms->expr()->in('id', $qbForms->createNamedParameter($sharedFormIds, IQueryBuilder::PARAM_INT_ARRAY)));
$whereTerm->add($access);

$qbForms->select('*')
->from($this->getTableName())
// user is member of or form is publicly shared
->where($whereTerm)
// ensure not to include owned forms
->andWhere($qbForms->expr()->neq('owner_id', $qbForms->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
//Last updated forms first, then newest forms first
->addOrderBy('last_updated', 'DESC')
->addOrderBy('created', 'DESC');

return $this->findEntities($qb);
return $this->findEntities($qbForms);
}

/**
Expand Down
122 changes: 122 additions & 0 deletions lib/Migration/Version040200Date20240402200139.php
@@ -0,0 +1,122 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2024 Christian Hartmann <chris-hartmann@gmx.de>
*
* @author Christian Hartmann <chris-hartmann@gmx.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\Forms\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\Types;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

/**
* Add new column access_enum and update with data from access_json
*/
class Version040200Date20240402200139 extends SimpleMigrationStep {

public function __construct(
protected IDBConnection $db,
) {
}

/**
* @param IOutput $output
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$table = $schema->getTable('forms_v2_forms');

// Abort if already existing.
if ($table->hasColumn('access_enum')) {
return null;
}

// Create new column
$table->addColumn('access_enum', Types::SMALLINT, [
'notnull' => false,
'default' => null,
'unsigned' => true,
]);

return $schema;
}

/**
* @param IOutput $output
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
*/
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
$qbFetch = $this->db->getQueryBuilder();
$qbUpdate = $this->db->getQueryBuilder();

// Prepare Queries
$qbFetch->select('id', 'access_json')
->from('forms_v2_forms');

$qbUpdate->update('forms_v2_forms')
->set('access_enum', $qbUpdate->createParameter('access_enum'))
->where($qbUpdate->expr()->eq('id', $qbUpdate->createParameter('id')));

// Fetch Forms...
$cursor = $qbFetch->executeQuery();

// ... then handle each existing form and translate its sharing settings.
while ($row = $cursor->fetch()) {
// Decode access to array (param assoc=true)
$access = json_decode($row['access_json'], true);

$value = 0;

// No further permissions -> 0
// Permit all users, but don't show in navigation -> 1
// Permit all users and show in navigation -> 2
if (!$access['permitAllUsers'] && !$access['showToAllUsers']) {
$value = 0;
} elseif ($access['permitAllUsers'] && !$access['showToAllUsers']) {
$value = 1;
} else {
$value = 2;
}

// If legacyLink add 3
if ($access['legacyLink']) {
$value += 3;
}

$qbUpdate->setParameter('id', $row['id'], IQueryBuilder::PARAM_INT)
->setParameter('access_enum', $value, IQueryBuilder::PARAM_INT)
->executeStatement();
}
$cursor->closeCursor();
}
}

0 comments on commit e019f14

Please sign in to comment.