4 * Copyright © 2003-2024 The Galette Team
6 * This file is part of Galette (https://galette.eu).
8 * Galette is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * Galette is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
22 namespace Galette\Entity
;
26 use Galette\Core\Authentication
;
27 use Galette\Core\Login
;
28 use Galette\Features\I18n
;
29 use Galette\Features\Permissions
;
30 use Galette\IO\FileInterface
;
31 use Galette\IO\FileTrait
;
39 * @author Johan Cwiklinski <johan@x-tnd.be>
42 class Document
implements FileInterface
47 store
as protected trait_store
;
48 writeOnDisk
as protected trait_writeOnDisk
;
51 public const TABLE
= 'documents';
52 public const PK
= 'id_document';
54 public const STATUS
= 'status';
55 public const RULES
= 'rules';
56 public const ADHESION
= 'adhesion';
57 public const MINUTES
= 'minutes';
58 public const VOTES
= 'votes';
67 private string $filename;
69 private DateTime
$creation_date;
71 protected string $store_path = GALETTE_DOCUMENTS_PATH
;
73 private ?
string $comment = null;
74 /** @var array<string> */
75 private array $errors = [];
76 private bool $public_list = false;
81 * @param Db $zdb Database instance
82 * @param int|ArrayObject<string,int|string>|null $args Arguments
84 public function __construct(Db
$zdb, int|ArrayObject
$args = null)
87 $this->can_public
= true;
89 $this->init($this->store_path
);
93 } elseif ($args instanceof ArrayObject
) {
94 $this->loadFromRs($args);
99 * Load a document from its identifier
101 * @param integer $id Identifier
105 private function load(int $id): void
108 $select = $this->zdb
->select(self
::TABLE
);
109 $select->limit(1)->where([self
::PK
=> $id]);
111 $results = $this->zdb
->execute($select);
112 /** @var ArrayObject<string, int|string> $res */
113 $res = $results->current();
114 $this->loadFromRs($res);
115 } catch (Throwable
$e) {
117 'An error occurred loading document #' . $id . "Message:\n" .
127 * @param string|null $type Type to retrieve
129 * @return array<int,Document>
133 public function getList(string $type = null): array
138 $select = $this->zdb
->select(self
::TABLE
);
140 if ($type !== null) {
141 $select->where(['type' => $type]);
144 $select->order(self
::PK
);
146 $results = $this->zdb
->execute($select);
148 $access_level = $login->getAccessLevel();
150 foreach ($results as $r) {
151 // skip entries according to access control
153 $r->visible
== FieldsConfig
::NOBODY
&&
154 ($this->public_list
=== true ||
($this->public_list
=== false && !$login->isAdmin())) ||
155 ($r->visible
== FieldsConfig
::ADMIN
&&
156 $access_level < Authentication
::ACCESS_ADMIN
) ||
157 ($r->visible
== FieldsConfig
::STAFF
&&
158 $access_level < Authentication
::ACCESS_STAFF
) ||
159 ($r->visible
== FieldsConfig
::MANAGER
&&
160 $access_level < Authentication
::ACCESS_MANAGER
) ||
161 (($r->visible
== FieldsConfig
::USER_READ ||
$r->visible
== FieldsConfig
::USER_WRITE
) &&
162 $access_level < Authentication
::ACCESS_USER
)
167 $documents[$r->{self
::PK
}] = new Document($this->zdb
, $r);
170 } catch (Throwable
$e) {
172 "An error occurred loading documents. Message:\n" .
183 * @return array<string, array<int, Document>>
187 public function getTypedList(): array
189 $this->public_list
= true;
190 $list = $this->getList();
191 $sys_types = $this->getSystemTypes(false);
193 $typed_list = array_fill_keys($sys_types, []);
194 foreach ($list as $doc_id => $document) {
195 $typed_list[$document->getType()][] = $document;
198 //cleanup: some system types may have no entries
199 foreach ($sys_types as $type) {
200 if (count($typed_list[$type]) == 0) {
201 unset($typed_list[$type]);
209 * Check if a document can be shown
211 * @param Login $login Login
215 public function canShow(Login
$login): bool
217 $access_level = $login->getAccessLevel();
219 switch ($this->getPermission()) {
220 case FieldsConfig
::ALL
:
222 case FieldsConfig
::NOBODY
:
224 case FieldsConfig
::ADMIN
:
225 return $access_level >= Authentication
::ACCESS_ADMIN
;
226 case FieldsConfig
::STAFF
:
227 return $access_level >= Authentication
::ACCESS_STAFF
;
228 case FieldsConfig
::MANAGER
:
229 return $access_level >= Authentication
::ACCESS_MANAGER
;
230 case FieldsConfig
::USER_WRITE
:
231 case FieldsConfig
::USER_READ
:
232 return $access_level >= Authentication
::ACCESS_USER
;
239 * Load document from a db ResultSet
241 * @param ArrayObject<string, int|string> $rs ResultSet
245 private function loadFromRs(ArrayObject
$rs): void
247 $this->id
= $rs->{self
::PK
};
248 $this->type
= $rs->type
;
249 $this->permission
= $rs->visible
;
250 $this->filename
= $rs->filename
;
251 $this->comment
= $rs->comment
;
252 $this->creation_date
= new DateTime($rs->creation_date
);
256 * Store document in database
258 * @param array<string,mixed> $post POST data
259 * @param array<string,mixed> $files Files
263 public function store(array $post, array $files): bool
265 $this->setType($post['document_type']);
266 $this->setComment($post['comment']);
267 $this->permission
= $post['visible'];
269 $handled = $this->handleFiles($files);
270 if ($handled !== true) {
271 $this->errors
= $handled;
277 'type' => $this->type
,
278 'filename' => $this->filename
,
279 'visible' => $this->getPermission(),
280 'comment' => $this->comment
,
282 if (isset($this->id
) && $this->id
> 0) {
283 $update = $this->zdb
->update(self
::TABLE
);
284 $update->set($values)->where([self
::PK
=> $this->id
]);
285 $this->zdb
->execute($update);
287 $values['creation_date'] = date('Y-m-d H:i:s');
288 $insert = $this->zdb
->insert(self
::TABLE
);
289 $insert->values($values);
290 $add = $this->zdb
->execute($insert);
291 if (!$add->count() > 0) {
292 Analog
::log('Not stored!', Analog
::ERROR
);
296 $this->id
= $this->zdb
->getLastGeneratedValue($this);
297 if (!in_array($this->type
, $this->getSystemTypes(false))) {
298 $this->addTranslation($this->type
);
302 } catch (Throwable
$e) {
305 'An error occurred storing document: ' . $e->getMessage(),
315 * @param array<int>|null $ids IDs to remove, default to current id
319 public function remove(array $ids = null): bool
326 $this->zdb
->connection
->beginTransaction();
327 $delete = $this->zdb
->delete(self
::TABLE
);
328 $delete->where([self
::PK
=> $ids]);
329 $this->zdb
->execute($delete);
330 if (!$this->removeFile()) {
331 throw new \
RuntimeException('cannot remove file document from disk');
334 'Document #' . implode(', #', $ids) . ' deleted successfully.',
338 $this->zdb
->connection
->commit();
340 } catch (Throwable
$e) {
341 $this->zdb
->connection
->rollBack();
343 'Unable to delete document #' . implode(', #', $ids) . ' | ' . $e->getMessage(),
351 * Remove document file
355 protected function removeFile(): bool
357 $file = $this->getDestDir() . $this->getDocumentFilename();
358 if (file_exists($file)) {
359 return unlink($file);
362 Analog
::log('File ' . $file . ' does not exist', Analog
::WARNING
);
371 public function getURL(): string
373 return $this->getDestDir() . $this->getDocumentFileName();
381 public function getId(): ?
int
383 return $this->id ??
null;
387 * Get document file name
391 public function getDocumentFilename(): string
393 return $this->filename ??
'';
398 * @param ?string $comment Comment to set
402 public function setComment(?
string $comment): self
404 $this->comment
= $comment;
413 public function getComment(): ?
string
415 return $this->comment
;
421 * @param string $type Type
425 public function setType(string $type): self
436 public function getType(): string
438 return $this->type ??
'';
444 * @param boolean $formatted Return formatted date (default) or not
446 * @return string|DateTime
448 public function getCreationDate(bool $formatted = true): string|DateTime
451 return $this->creation_date
->format(_T('Y-m-d H:i:s'));
453 return $this->creation_date
;
457 * Get system social types
459 * @param boolean $translated Return translated types (default) or not
461 * @return array<string,string>
463 public function getSystemTypes(bool $translated = true): array
467 self
::STATUS
=> _T('Association status'),
468 self
::RULES
=> _T('Rules of procedure'),
469 self
::ADHESION
=> _T('Adhesion form'),
470 self
::MINUTES
=> _T('Meeting minutes'),
471 self
::VOTES
=> _T('Votes results')
475 self
::STATUS
=> 'Association status',
476 self
::RULES
=> 'Rules of procedure',
477 self
::ADHESION
=> 'Adhesion form',
478 self
::MINUTES
=> 'Meeting minutes',
479 self
::VOTES
=> 'Votes results'
486 * Get system documents types
488 * @param string $type Document type
489 * @param boolean $translated Return translated types (default) or not
493 public function getSystemType(string $type, bool $translated = true): string
495 return $this->getSystemTypes($translated)[$type] ??
_T($type);
499 * Get all known types
501 * @return array<string,string>
505 public function getTypes(): array
507 $types = $this->getSystemTypes();
509 $select = $this->zdb
->select(self
::TABLE
);
510 $select->quantifier('DISTINCT');
511 $select->where
->notIn('type', array_keys($this->getSystemTypes(false)));
512 $results = $this->zdb
->execute($select);
514 foreach ($results as $r) {
515 $types[$r->type
] = $r->type
;
524 * @param array<string,mixed> $files Files sent
526 * @return array<string>|true
528 public function handleFiles(array $files): array|
bool
532 if (isset($files['document_file'])) {
533 if ($files['document_file']['error'] === UPLOAD_ERR_OK
) {
534 if ($files['document_file']['tmp_name'] != '') {
535 if (is_uploaded_file($files['document_file']['tmp_name'])) {
536 $res = $this->trait_store($files['document_file']);
538 $this->errors
[] = $this->getErrorMessage($res);
540 $this->filename
= sprintf(
548 } elseif (!isset($this->id
)) {
550 $this->getPhpErrorMessage($files['document_file']['error']),
553 $this->errors
[] = $this->getPhpErrorMessage($files['document_file']['error']);
557 if (count($this->errors
) > 0) {
559 'Some errors has been thew attempting to edit/store a document file' . "\n" .
560 print_r($this->errors
, true),
563 return $this->errors
;
574 public function getErrors(): array
576 return $this->errors
;
582 * @param string $tmpfile Temporary file
583 * @param bool $ajax If the file comes from an ajax call (dnd)
587 public function writeOnDisk(string $tmpfile, bool $ajax): bool|
int
589 //remove existing file when updating
590 if (isset($this->id
) && $this->id
> 0) {
593 return $this->trait_writeOnDisk($tmpfile, $ajax);