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 = [];
80 * @param Db $zdb Database instance
81 * @param int|ArrayObject<string,int|string>|null $args Arguments
83 public function __construct(Db
$zdb, int|ArrayObject
$args = null)
86 $this->can_public
= true;
88 $this->init($this->store_path
);
92 } elseif ($args instanceof ArrayObject
) {
93 $this->loadFromRs($args);
98 * Load a document from its identifier
100 * @param integer $id Identifier
104 private function load(int $id): void
107 $select = $this->zdb
->select(self
::TABLE
);
108 $select->limit(1)->where([self
::PK
=> $id]);
110 $results = $this->zdb
->execute($select);
111 /** @var ArrayObject<string, int|string> $res */
112 $res = $results->current();
113 $this->loadFromRs($res);
114 } catch (Throwable
$e) {
116 'An error occurred loading document #' . $id . "Message:\n" .
126 * @param string|null $type Type to retrieve
128 * @return array<int,Document>
132 public static function getList(string $type = null): array
137 $select = $zdb->select(self
::TABLE
);
139 if ($type !== null) {
140 $select->where(['type' => $type]);
143 $select->order(self
::PK
);
145 $results = $zdb->execute($select);
147 $access_level = $login->getAccessLevel();
149 foreach ($results as $r) {
150 // skip entries according to access control
152 $r->visible
== FieldsConfig
::NOBODY ||
153 ($r->visible
== FieldsConfig
::ADMIN
&&
154 $access_level < Authentication
::ACCESS_ADMIN
) ||
155 ($r->visible
== FieldsConfig
::STAFF
&&
156 $access_level < Authentication
::ACCESS_STAFF
) ||
157 ($r->visible
== FieldsConfig
::MANAGER
&&
158 $access_level < Authentication
::ACCESS_MANAGER
) ||
159 (($r->visible
== FieldsConfig
::USER_READ ||
$r->visible
== FieldsConfig
::USER_WRITE
) &&
160 $access_level < Authentication
::ACCESS_USER
)
165 $documents[$r->{self
::PK
}] = new Document($zdb, $r);
168 } catch (Throwable
$e) {
170 "An error occurred loading documents. Message:\n" .
181 * @return array<string, array<int, Document>>
185 public function getTypedList(): array
187 $list = $this->getList();
188 $sys_types = $this->getSystemTypes(false);
190 $typed_list = array_fill_keys($sys_types, []);
191 foreach ($list as $doc_id => $document) {
192 $typed_list[$document->getType()][] = $document;
195 //cleanup: some system types may have no entries
196 foreach ($sys_types as $type) {
197 if (count($typed_list[$type]) == 0) {
198 unset($typed_list[$type]);
206 * Check if a document can be shown
208 * @param Login $login Login
212 public function canShow(Login
$login): bool
214 $access_level = $login->getAccessLevel();
216 switch ($this->getPermission()) {
217 case FieldsConfig
::ALL
:
219 case FieldsConfig
::NOBODY
:
221 case FieldsConfig
::ADMIN
:
222 return $access_level >= Authentication
::ACCESS_ADMIN
;
223 case FieldsConfig
::STAFF
:
224 return $access_level >= Authentication
::ACCESS_STAFF
;
225 case FieldsConfig
::MANAGER
:
226 return $access_level >= Authentication
::ACCESS_MANAGER
;
227 case FieldsConfig
::USER_WRITE
:
228 case FieldsConfig
::USER_READ
:
229 return $access_level >= Authentication
::ACCESS_USER
;
236 * Load document from a db ResultSet
238 * @param ArrayObject<string, int|string> $rs ResultSet
242 private function loadFromRs(ArrayObject
$rs): void
244 $this->id
= $rs->{self
::PK
};
245 $this->type
= $rs->type
;
246 $this->permission
= $rs->visible
;
247 $this->filename
= $rs->filename
;
248 $this->comment
= $rs->comment
;
249 $this->creation_date
= new DateTime($rs->creation_date
);
253 * Store document in database
255 * @param array<string,mixed> $post POST data
256 * @param array<string,mixed> $files Files
260 public function store(array $post, array $files): bool
262 $this->setType($post['document_type']);
263 $this->setComment($post['comment']);
264 $this->permission
= $post['visible'];
266 $handled = $this->handleFiles($files);
267 if ($handled !== true) {
268 $this->errors
= $handled;
274 'type' => $this->type
,
275 'filename' => $this->filename
,
276 'visible' => $this->getPermission(),
277 'comment' => $this->comment
,
279 if (isset($this->id
) && $this->id
> 0) {
280 $update = $this->zdb
->update(self
::TABLE
);
281 $update->set($values)->where([self
::PK
=> $this->id
]);
282 $this->zdb
->execute($update);
284 $values['creation_date'] = date('Y-m-d H:i:s');
285 $insert = $this->zdb
->insert(self
::TABLE
);
286 $insert->values($values);
287 $add = $this->zdb
->execute($insert);
288 if (!$add->count() > 0) {
289 Analog
::log('Not stored!', Analog
::ERROR
);
293 $this->id
= $this->zdb
->getLastGeneratedValue($this);
294 if (!in_array($this->type
, $this->getSystemTypes(false))) {
295 $this->addTranslation($this->type
);
299 } catch (Throwable
$e) {
302 'An error occurred storing document: ' . $e->getMessage(),
312 * @param array<int>|null $ids IDs to remove, default to current id
316 public function remove(array $ids = null): bool
323 $this->zdb
->connection
->beginTransaction();
324 $delete = $this->zdb
->delete(self
::TABLE
);
325 $delete->where([self
::PK
=> $ids]);
326 $this->zdb
->execute($delete);
327 if (!$this->removeFile()) {
328 throw new \
RuntimeException('cannot remove file document from disk');
331 'Document #' . implode(', #', $ids) . ' deleted successfully.',
335 $this->zdb
->connection
->commit();
337 } catch (Throwable
$e) {
338 $this->zdb
->connection
->rollBack();
340 'Unable to delete document #' . implode(', #', $ids) . ' | ' . $e->getMessage(),
348 * Remove document file
352 protected function removeFile(): bool
354 $file = $this->getDestDir() . $this->getDocumentFilename();
355 if (file_exists($file)) {
356 return unlink($file);
359 Analog
::log('File ' . $file . ' does not exist', Analog
::WARNING
);
368 public function getURL(): string
370 return $this->getDestDir() . $this->getDocumentFileName();
378 public function getId(): ?
int
380 return $this->id ??
null;
384 * Get document file name
388 public function getDocumentFilename(): string
390 return $this->filename ??
'';
395 * @param ?string $comment Comment to set
399 public function setComment(?
string $comment): self
401 $this->comment
= $comment;
410 public function getComment(): ?
string
412 return $this->comment
;
418 * @param string $type Type
422 public function setType(string $type): self
433 public function getType(): string
435 return $this->type ??
'';
441 * @param boolean $formatted Return formatted date (default) or not
443 * @return string|DateTime
445 public function getCreationDate(bool $formatted = true): string|DateTime
448 return $this->creation_date
->format(_T('Y-m-d H:i:s'));
450 return $this->creation_date
;
454 * Get system social types
456 * @param boolean $translated Return translated types (default) or not
458 * @return array<string,string>
460 public function getSystemTypes(bool $translated = true): array
464 self
::STATUS
=> _T('Association status'),
465 self
::RULES
=> _T('Rules of procedure'),
466 self
::ADHESION
=> _T('Adhesion form'),
467 self
::MINUTES
=> _T('Meeting minutes'),
468 self
::VOTES
=> _T('Votes results')
472 self
::STATUS
=> 'Association status',
473 self
::RULES
=> 'Rules of procedure',
474 self
::ADHESION
=> 'Adhesion form',
475 self
::MINUTES
=> 'Meeting minutes',
476 self
::VOTES
=> 'Votes results'
483 * Get system documents types
485 * @param string $type Document type
486 * @param boolean $translated Return translated types (default) or not
490 public function getSystemType(string $type, bool $translated = true): string
492 return $this->getSystemTypes($translated)[$type] ??
_T($type);
498 * @param array<string,mixed> $files Files sent
500 * @return array<string>|true
502 public function handleFiles(array $files): array|
bool
506 if (isset($files['document_file'])) {
507 if ($files['document_file']['error'] === UPLOAD_ERR_OK
) {
508 if ($files['document_file']['tmp_name'] != '') {
509 if (is_uploaded_file($files['document_file']['tmp_name'])) {
510 $res = $this->trait_store($files['document_file']);
512 $this->errors
[] = $this->getErrorMessage($res);
514 $this->filename
= sprintf(
522 } elseif (!isset($this->id
)) {
524 $this->getPhpErrorMessage($files['document_file']['error']),
527 $this->errors
[] = $this->getPhpErrorMessage($files['document_file']['error']);
531 if (count($this->errors
) > 0) {
533 'Some errors has been thew attempting to edit/store a document file' . "\n" .
534 print_r($this->errors
, true),
537 return $this->errors
;
548 public function getErrors(): array
550 return $this->errors
;
556 * @param string $tmpfile Temporary file
557 * @param bool $ajax If the file comes from an ajax call (dnd)
561 public function writeOnDisk(string $tmpfile, bool $ajax): bool|
int
563 //remove existing file when updating
564 if (isset($this->id
) && $this->id
> 0) {
567 return $this->trait_writeOnDisk($tmpfile, $ajax);