]> git.agnieray.net Git - galette.git/commitdiff
Add association's documents management
authorJohan Cwiklinski <johan@x-tnd.be>
Sun, 3 Mar 2024 18:47:25 +0000 (19:47 +0100)
committerJohan Cwiklinski <johan@x-tnd.be>
Wed, 20 Mar 2024 17:47:01 +0000 (18:47 +0100)
closes #1216

31 files changed:
.composer-require-checker.config.json
.gitignore
bin/twig-cache
galette/composer.json
galette/composer.lock
galette/config/paths.inc.php
galette/data/documents/readme.txt [new file with mode: 0644]
galette/includes/core_acls.php
galette/includes/dependencies.php
galette/includes/routes/management.routes.php
galette/includes/routes/public_pages.routes.php
galette/install/scripts/mysql.sql
galette/install/scripts/pgsql.sql
galette/install/scripts/sql/upgrade-to-1.10-mysql.sql
galette/install/scripts/sql/upgrade-to-1.10-pgsql.sql
galette/lang/galette.pot
galette/lang/galette_en_US.po
galette/lib/Galette/Controllers/Crud/DocumentsController.php [new file with mode: 0644]
galette/lib/Galette/Core/Galette.php
galette/lib/Galette/Entity/Document.php [new file with mode: 0644]
galette/lib/Galette/Entity/FieldsConfig.php
galette/lib/Galette/Features/Permissions.php
galette/lib/Galette/Features/Socials.php
galette/lib/Galette/Filters/DocumentsList.php [new file with mode: 0644]
galette/templates/default/pages/document_form.html.twig [new file with mode: 0644]
galette/templates/default/pages/documents_list.html.twig [new file with mode: 0644]
galette/templates/default/pages/documents_public_list.html.twig [new file with mode: 0644]
tests/Galette/Core/tests/units/Db.php
tests/Galette/Core/tests/units/Galette.php
tests/Galette/Entity/tests/units/Document.php [new file with mode: 0644]
ui/semantic/galette/collections/menu.overrides

index 5356adaddadb9b35a0414884217d3ce7ccd3cebf..482010e7bd8deccc917f4ddc08ba6690cae64647 100644 (file)
@@ -42,6 +42,7 @@
     "GALETTE_DEBUG",
     "GALETTE_NIGHTLY",
     "GALETTE_PHOTOS_PATH",
+    "GALETTE_DOCUMENTS_PATH",
     "GALETTE_PLUGINS_PATH",
     "GALETTE_ROOT",
     "GALETTE_TELEMETRY_URI",
index f41b200507a37bbc7b80111e95d3a1abfb4f5f44..73aeb92c04eafe770be5c3823e05adcb7804673a 100644 (file)
@@ -70,6 +70,11 @@ quick_gen_project_*
 !/galette/data/files/readme.txt
 !/galette/data/files/.htaccess
 
+# /galette/data/documents/
+/galette/data/documents/*
+!/galette/data/documents/readme.txt
+!/galette/data/documents/.htaccess
+
 # Test stuff
 tests/coverage/
 tests/mageekguy.atoum.phar
index 75bf6f742ea9d5614ff5092ae8c052896b60098e..df431063bbfc4100a8d1533b314b3b6baad21678 100755 (executable)
@@ -6,6 +6,7 @@ use Twig\Cache\FilesystemCache;
 use Twig\Environment;
 use Twig\Loader\FilesystemLoader;
 use Twig\TwigFunction;
+use Twig\Extra\String\StringExtension;
 
 if ( !class_exists('\Twig\Loader\FilesystemLoader')) {
     require_once __DIR__ . '/../galette/vendor/autoload.php';
@@ -81,6 +82,8 @@ $twig = new Environment(
     ]
 );
 
+$twig->addExtension(new StringExtension());
+
 $twig_functions = [
     '__',
     '_T',
index 798efb71c9b533e4a70cafbd627f846d81afa87b..dcfe52e8d1e1e267374b95cc14f0b9d3a5fd1139 100644 (file)
@@ -52,7 +52,8 @@
         "slim/twig-view": "^3.3",
         "slim/psr7": "^1.6",
         "symfony/yaml": "^6.2",
-        "guzzlehttp/guzzle": "^7.8"
+        "guzzlehttp/guzzle": "^7.8",
+        "twig/string-extra": "^3.8"
     },
     "require-dev": {
         "squizlabs/php_codesniffer": "^3.7",
index e1c212aed3c4a6a809629aaa95ad9a07c1f507ae..e58d3df3b6bf20d51298b32e45b597d2c3edaf38 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "dbbf63121881c53f2c16c57ee40027ed",
+    "content-hash": "49aee4bfa35737b80d4e7c19726e83c8",
     "packages": [
         {
             "name": "akrabat/rka-slim-session-middleware",
             ],
             "time": "2023-01-26T09:26:14+00:00"
         },
+        {
+            "name": "symfony/polyfill-intl-grapheme",
+            "version": "v1.28.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
+                "reference": "875e90aeea2777b6f135677f618529449334a612"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612",
+                "reference": "875e90aeea2777b6f135677f618529449334a612",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.28-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for intl's grapheme_* functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "grapheme",
+                "intl",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-01-26T09:26:14+00:00"
+        },
+        {
+            "name": "symfony/polyfill-intl-normalizer",
+            "version": "v1.28.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+                "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
+                "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "suggest": {
+                "ext-intl": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.28-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for intl's Normalizer class and related functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "intl",
+                "normalizer",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-01-26T09:26:14+00:00"
+        },
         {
             "name": "symfony/polyfill-mbstring",
             "version": "v1.28.0",
             ],
             "time": "2023-01-26T09:26:14+00:00"
         },
+        {
+            "name": "symfony/string",
+            "version": "v6.3.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/string.git",
+                "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/string/zipball/13d76d0fb049051ed12a04bef4f9de8715bea339",
+                "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1",
+                "symfony/polyfill-ctype": "~1.8",
+                "symfony/polyfill-intl-grapheme": "~1.0",
+                "symfony/polyfill-intl-normalizer": "~1.0",
+                "symfony/polyfill-mbstring": "~1.0"
+            },
+            "conflict": {
+                "symfony/translation-contracts": "<2.5"
+            },
+            "require-dev": {
+                "symfony/error-handler": "^5.4|^6.0",
+                "symfony/http-client": "^5.4|^6.0",
+                "symfony/intl": "^6.2",
+                "symfony/translation-contracts": "^2.5|^3.0",
+                "symfony/var-exporter": "^5.4|^6.0"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "Resources/functions.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Component\\String\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "grapheme",
+                "i18n",
+                "string",
+                "unicode",
+                "utf-8",
+                "utf8"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/string/tree/v6.3.5"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-09-18T10:38:32+00:00"
+        },
+        {
+            "name": "symfony/translation-contracts",
+            "version": "v3.4.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/translation-contracts.git",
+                "reference": "06450585bf65e978026bda220cdebca3f867fde7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/06450585bf65e978026bda220cdebca3f867fde7",
+                "reference": "06450585bf65e978026bda220cdebca3f867fde7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "3.4-dev"
+                },
+                "thanks": {
+                    "name": "symfony/contracts",
+                    "url": "https://github.com/symfony/contracts"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Translation\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Test/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to translation",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/translation-contracts/tree/v3.4.1"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2023-12-26T14:02:43+00:00"
+        },
         {
             "name": "symfony/yaml",
             "version": "v6.3.7",
             },
             "funding": [
                 {
-                    "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&currency_code=GBP&business=paypal@tecnick.com&item_name=donation%20for%20tcpdf%20project",
-                    "type": "custom"
+                    "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&currency_code=GBP&business=paypal@tecnick.com&item_name=donation%20for%20tcpdf%20project",
+                    "type": "custom"
+                }
+            ],
+            "time": "2023-09-06T15:09:26+00:00"
+        },
+        {
+            "name": "twig/string-extra",
+            "version": "v3.8.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/twigphp/string-extra.git",
+                "reference": "b0c9037d96baff79abe368dc092a59b726517548"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/twigphp/string-extra/zipball/b0c9037d96baff79abe368dc092a59b726517548",
+                "reference": "b0c9037d96baff79abe368dc092a59b726517548",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/string": "^5.4|^6.0|^7.0",
+                "symfony/translation-contracts": "^1.1|^2|^3",
+                "twig/twig": "^3.0"
+            },
+            "require-dev": {
+                "symfony/phpunit-bridge": "^6.4|^7.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Twig\\Extra\\String\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com",
+                    "homepage": "http://fabien.potencier.org",
+                    "role": "Lead Developer"
+                }
+            ],
+            "description": "A Twig extension for Symfony String",
+            "homepage": "https://twig.symfony.com",
+            "keywords": [
+                "html",
+                "string",
+                "twig",
+                "unicode"
+            ],
+            "support": {
+                "source": "https://github.com/twigphp/string-extra/tree/v3.8.0"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/twig/twig",
+                    "type": "tidelift"
                 }
             ],
-            "time": "2023-09-06T15:09:26+00:00"
+            "time": "2023-11-21T14:02:01+00:00"
         },
         {
             "name": "twig/twig",
             ],
             "time": "2023-09-26T12:56:25+00:00"
         },
-        {
-            "name": "symfony/polyfill-intl-grapheme",
-            "version": "v1.28.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
-                "reference": "875e90aeea2777b6f135677f618529449334a612"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612",
-                "reference": "875e90aeea2777b6f135677f618529449334a612",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "suggest": {
-                "ext-intl": "For best performance"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.28-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Intl\\Grapheme\\": ""
-                }
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill for intl's grapheme_* functions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "grapheme",
-                "intl",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2023-01-26T09:26:14+00:00"
-        },
-        {
-            "name": "symfony/polyfill-intl-normalizer",
-            "version": "v1.28.0",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
-                "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
-                "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=7.1"
-            },
-            "suggest": {
-                "ext-intl": "For best performance"
-            },
-            "type": "library",
-            "extra": {
-                "branch-alias": {
-                    "dev-main": "1.28-dev"
-                },
-                "thanks": {
-                    "name": "symfony/polyfill",
-                    "url": "https://github.com/symfony/polyfill"
-                }
-            },
-            "autoload": {
-                "files": [
-                    "bootstrap.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
-                },
-                "classmap": [
-                    "Resources/stubs"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Symfony polyfill for intl's Normalizer class and related functions",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "compatibility",
-                "intl",
-                "normalizer",
-                "polyfill",
-                "portable",
-                "shim"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2023-01-26T09:26:14+00:00"
-        },
         {
             "name": "symfony/service-contracts",
             "version": "v2.5.2",
             ],
             "time": "2022-05-30T19:17:29+00:00"
         },
-        {
-            "name": "symfony/string",
-            "version": "v6.3.5",
-            "source": {
-                "type": "git",
-                "url": "https://github.com/symfony/string.git",
-                "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339"
-            },
-            "dist": {
-                "type": "zip",
-                "url": "https://api.github.com/repos/symfony/string/zipball/13d76d0fb049051ed12a04bef4f9de8715bea339",
-                "reference": "13d76d0fb049051ed12a04bef4f9de8715bea339",
-                "shasum": ""
-            },
-            "require": {
-                "php": ">=8.1",
-                "symfony/polyfill-ctype": "~1.8",
-                "symfony/polyfill-intl-grapheme": "~1.0",
-                "symfony/polyfill-intl-normalizer": "~1.0",
-                "symfony/polyfill-mbstring": "~1.0"
-            },
-            "conflict": {
-                "symfony/translation-contracts": "<2.5"
-            },
-            "require-dev": {
-                "symfony/error-handler": "^5.4|^6.0",
-                "symfony/http-client": "^5.4|^6.0",
-                "symfony/intl": "^6.2",
-                "symfony/translation-contracts": "^2.5|^3.0",
-                "symfony/var-exporter": "^5.4|^6.0"
-            },
-            "type": "library",
-            "autoload": {
-                "files": [
-                    "Resources/functions.php"
-                ],
-                "psr-4": {
-                    "Symfony\\Component\\String\\": ""
-                },
-                "exclude-from-classmap": [
-                    "/Tests/"
-                ]
-            },
-            "notification-url": "https://packagist.org/downloads/",
-            "license": [
-                "MIT"
-            ],
-            "authors": [
-                {
-                    "name": "Nicolas Grekas",
-                    "email": "p@tchwork.com"
-                },
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                }
-            ],
-            "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
-            "homepage": "https://symfony.com",
-            "keywords": [
-                "grapheme",
-                "i18n",
-                "string",
-                "unicode",
-                "utf-8",
-                "utf8"
-            ],
-            "support": {
-                "source": "https://github.com/symfony/string/tree/v6.3.5"
-            },
-            "funding": [
-                {
-                    "url": "https://symfony.com/sponsor",
-                    "type": "custom"
-                },
-                {
-                    "url": "https://github.com/fabpot",
-                    "type": "github"
-                },
-                {
-                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
-                    "type": "tidelift"
-                }
-            ],
-            "time": "2023-09-18T10:38:32+00:00"
-        },
         {
             "name": "theseer/tokenizer",
             "version": "1.2.1",
index b99e0a854f5b70152bb8164e594e2139ee1a5ce2..b2ea234974ef5066fca7f4a013ec4a1b2f655052 100644 (file)
@@ -98,6 +98,9 @@ if (!defined('GALETTE_IMPORTS_PATH')) {
 if (!defined('GALETTE_PHOTOS_PATH')) {
     define('GALETTE_PHOTOS_PATH', GALETTE_DATA_PATH . 'photos/');
 }
+if (!defined('GALETTE_DOCUMENTS_PATH')) {
+    define('GALETTE_DOCUMENTS_PATH', GALETTE_DATA_PATH . 'documents/');
+}
 if (!defined('GALETTE_ATTACHMENTS_PATH')) {
     define('GALETTE_ATTACHMENTS_PATH', GALETTE_DATA_PATH . 'attachments/');
 }
diff --git a/galette/data/documents/readme.txt b/galette/data/documents/readme.txt
new file mode 100644 (file)
index 0000000..a341c0b
--- /dev/null
@@ -0,0 +1,5 @@
+
+This file is here because most ftp-clients don't upload empty directory.
+
+You can delete this file after your upload.
+
index 3238244fe5e4baa4f6a7ceab964e97acdc394050..4a2b3d3dab11524c6a92f8047538b90b70868bc3 100644 (file)
@@ -86,4 +86,5 @@ $core_acls = [
     'pdfModels'                         => 'staff',
     'attendance_sheet_details'          => 'groupmanager',
     'attendance_sheet'                  => 'groupmanager',
+    '/(.+)?document(.+)?/i'             => 'staff'
 ];
index f61f6ae73fe22e44c47f7fb482287a57fd7f20ff..f7c97f0312e778f405d1866e088d25c54dbe06ab 100644 (file)
@@ -25,6 +25,7 @@ use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
 use Slim\Routing\RouteContext;
 use Slim\Routing\RouteParser;
 use Slim\Views\Twig;
+use Twig\Extra\String\StringExtension;
 
 $container = $app->getContainer();
 
@@ -65,6 +66,7 @@ $container->set('Slim\Views\Twig', function (ContainerInterface $c) {
 
     //Twig extensions
     $view->addExtension(new \Galette\Twig\CsrfExtension($c->get('csrf')));
+    $view->addExtension(new StringExtension());
     if (\Galette\Core\Galette::isDebugEnabled()) {
         $view->addExtension(new \Twig\Extension\DebugExtension());
     }
index 5ff552ec3d82f653d9e9d465b20515fc452e6b79..97e703fc511596904e54d88de8f03faffd96fb82 100644 (file)
@@ -437,3 +437,48 @@ $app->get(
     '/{form_name:adh|contrib|trans}/{id:\d+}/file/{fid:\d+}/{pos:\d+}/{name}',
     [Crud\DynamicFieldsController::class, 'getDynamicFile']
 )->setName('getDynamicFile')->add($authenticate);
+
+$app->get(
+    '/documents[/{option:page|order}/{value}]',
+    [Crud\DocumentsController::class, 'list']
+)->setName('documentsList')->add($authenticate);
+
+$app->post(
+    '/documents/filter',
+    [Crud\DocumentsController::class, 'filter']
+)->setName('documentsFilter')->add($authenticate);
+
+$app->get(
+    '/document/remove/{id:\d+}',
+    [Crud\DocumentsController::class, 'confirmDelete']
+)->setName('removeDocument')->add($authenticate);
+
+$app->post(
+    '/document/remove/{id:\d+}',
+    [Crud\DocumentsController::class, 'delete']
+)->setName('doRemoveDocument')->add($authenticate);
+
+$app->get(
+    '/document/add',
+    [Crud\DocumentsController::class, 'add']
+)->setName('addDocument')->add($authenticate);
+
+$app->get(
+    '/document/edit/{id:\d+}',
+    [Crud\DocumentsController::class, 'edit']
+)->setName('editDocument')->add($authenticate);
+
+$app->post(
+    '/document/add',
+    [Crud\DocumentsController::class, 'doAdd']
+)->setName('doAddDocument')->add($authenticate);
+
+$app->post(
+    '/document/edit/{id:\d+}',
+    [Crud\DocumentsController::class, 'doEdit']
+)->setName('doEditDocument')->add($authenticate);
+
+$app->get(
+    '/document/get/{id:\d+}',
+    [Crud\DocumentsController::class, 'getDocument']
+)->setName('getDocumentFile');
index 5ac0c582ff9b65339610d64bd7f3e25ab824594b..446268e4dae7350c344e4d58cc69bb6ecc7a535c 100644 (file)
@@ -58,4 +58,9 @@ $app->group('/public', function (RouteCollectorProxy $app) use ($routeparser) {
                 ->withHeader('Location', $routeparser->urlFor('publicList', $args));
         }
     );
+
+    $app->get(
+        '/documents[/{option:page|order}/{value:\d+|\w+}]',
+        [Crud\DocumentsController::class, 'publicList']
+    )->setName('documentsPublicList');
 })->add(\Galette\Middleware\PublicPages::class);
index 9896af3e88d475988e8f19c19256be7e3802ac80..9ee85c3e8e1a5c97febb2573c5b1c518b51fcd05 100644 (file)
@@ -354,6 +354,19 @@ CREATE TABLE galette_socials (
   FOREIGN KEY (id_adh) REFERENCES  galette_adherents (id_adh) ON DELETE CASCADE ON UPDATE CASCADE
 ) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
 
+-- table for documents
+DROP TABLE IF EXISTS galette_documents;
+CREATE TABLE galette_documents (
+  id_document int(10) unsigned NOT NULL auto_increment,
+  type varchar(250) NOT NULL,
+  visible tinyint(1) NOT NULL,
+  filename varchar(255) DEFAULT NULL,
+  comment text,
+  creation_date datetime NOT NULL,
+  PRIMARY KEY (id_document),
+  KEY (type)
+) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
+
 -- table for database version
 DROP TABLE IF EXISTS galette_database;
 CREATE TABLE galette_database (
index 16572bbcb5dc6696ab1be8581db10668c535315b..92bbe0099cfcb06486b17043cec4ae4d0df668f1 100644 (file)
@@ -165,6 +165,15 @@ CREATE SEQUENCE galette_socials_id_seq
     MINVALUE 1
     CACHE 1;
 
+-- sequence for documents
+DROP SEQUENCE IF EXISTS galette_documents_id_seq;
+CREATE SEQUENCE galette_documents_id_seq
+    START 1
+    INCREMENT 1
+    MAXVALUE 2147483647
+    MINVALUE 1
+    CACHE 1;
+
 -- Schema
 -- REMINDER: Create order IS important, dependencies first !!
 DROP TABLE IF EXISTS galette_paymenttypes CASCADE;
@@ -499,6 +508,20 @@ CREATE TABLE galette_socials (
 -- add index on table to look for type
 CREATE INDEX galette_socials_idx ON galette_socials (type);
 
+-- table for documents
+DROP TABLE IF EXISTS galette_documents CASCADE;
+CREATE TABLE galette_documents (
+  id_document integer DEFAULT nextval('galette_documents_id_seq'::text) NOT NULL,
+  type character varying(250) NOT NULL,
+  visible integer NOT NULL,
+  filename character varying(255) DEFAULT NULL,
+  comment text,
+  creation_date timestamp NOT NULL,
+  PRIMARY KEY (id_document)
+);
+-- add index on table to look for type
+CREATE INDEX galette_documents_idx ON galette_documents (type);
+
 -- table for database version
 DROP TABLE IF EXISTS galette_database CASCADE;
 CREATE TABLE galette_database (
index 22819b82e79de6681b4a440324a6a29d357e0e54..1469fbc81ca0d9ac91a5690996723ac3699a705a 100644 (file)
@@ -47,3 +47,16 @@ ALTER TABLE galette_types_cotisation CONVERT TO CHARACTER SET utf8mb4 COLLATE ut
 
 -- change dynamic fields permissions
 ALTER TABLE galette_field_types CHANGE field_perm field_perm INT(10) NOT NULL DEFAULT 1;
+
+-- table for documents
+DROP TABLE IF EXISTS galette_documents;
+CREATE TABLE galette_documents (
+  id_document int(10) unsigned NOT NULL auto_increment,
+  type varchar(250) NOT NULL,
+  visible tinyint(1) NOT NULL,
+  filename varchar(255) DEFAULT NULL,
+  comment text,
+  creation_date datetime NOT NULL,
+  PRIMARY KEY (id_document),
+  KEY (type)
+) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci;
index dba2f0e2cae0ea55092cb71829796b5ff27d122d..08a6499807543c41bf2bc933f7b694f6d3137993 100644 (file)
@@ -16,3 +16,26 @@ ALTER TABLE galette_field_types ADD field_information_above boolean DEFAULT FALS
 
 -- change dynamic fields permissions
 ALTER TABLE galette_field_types ALTER COLUMN field_perm SET DEFAULT 1;
+
+-- sequence for documents
+DROP SEQUENCE IF EXISTS galette_documents_id_seq;
+CREATE SEQUENCE galette_documents_id_seq
+    START 1
+    INCREMENT 1
+    MAXVALUE 2147483647
+    MINVALUE 1
+    CACHE 1;
+
+-- table for documents
+DROP TABLE IF EXISTS galette_documents CASCADE;
+CREATE TABLE galette_documents (
+  id_document integer DEFAULT nextval('galette_documents_id_seq'::text) NOT NULL,
+  type character varying(250) NOT NULL,
+  visible integer NOT NULL,
+  filename character varying(255) DEFAULT NULL,
+  comment text,
+  creation_date timestamp NOT NULL,
+  PRIMARY KEY (id_document)
+);
+-- add index on table to look for type
+CREATE INDEX galette_documents_idx ON galette_documents (type);
index e9f97eaf615864536910a9d311b13f202cfcb8be..54da4563b31380f8d76cef5141e234c01c5478cc 100644 (file)
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-03-17 19:30+0100\n"
+"POT-Creation-Date: 2024-03-20 18:45+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -101,7 +101,7 @@ msgid "Group name is missing!"
 msgstr ""
 
 #: ../lib/Galette/Controllers/Crud/GroupsController.php:184
-#: ../lib/Galette/Core/Galette.php:548
+#: ../lib/Galette/Core/Galette.php:570
 msgid "Groups"
 msgstr ""
 
@@ -155,7 +155,7 @@ msgid "Mailing has been successfully saved."
 msgstr ""
 
 #: ../lib/Galette/Controllers/Crud/MailingsController.php:477
-#: ../lib/Galette/Core/Galette.php:564
+#: ../lib/Galette/Core/Galette.php:586
 msgid "Mailings"
 msgstr ""
 
@@ -171,7 +171,7 @@ msgid "Mailing preview"
 msgstr ""
 
 #: ../lib/Galette/Controllers/Crud/ContributionsTypesController.php:89
-#: ../lib/Galette/Core/Install.php:1166 ../lib/Galette/Core/Galette.php:380
+#: ../lib/Galette/Core/Install.php:1166 ../lib/Galette/Core/Galette.php:387
 msgid "Contributions types"
 msgstr ""
 
@@ -224,6 +224,7 @@ msgstr ""
 #: ../lib/Galette/Controllers/Crud/DynamicFieldsController.php:308
 #: ../lib/Galette/Controllers/Crud/MembersController.php:238
 #: ../lib/Galette/Controllers/Crud/MembersController.php:935
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:276
 #: ../lib/Galette/Controllers/PdfController.php:97
 #: ../lib/Galette/Controllers/PdfController.php:264
 #: ../lib/Galette/Middleware/Authenticate.php:169
@@ -231,6 +232,7 @@ msgid "You do not have permission for requested URL."
 msgstr ""
 
 #: ../lib/Galette/Controllers/Crud/DynamicFieldsController.php:364
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:315
 msgid "The file does not exists or cannot be read :("
 msgstr ""
 
@@ -279,12 +281,12 @@ msgid "Member Profile"
 msgstr ""
 
 #: ../lib/Galette/Controllers/Crud/MembersController.php:353
-#: ../lib/Galette/Core/Galette.php:486
+#: ../lib/Galette/Core/Galette.php:493
 msgid "Members list"
 msgstr ""
 
 #: ../lib/Galette/Controllers/Crud/MembersController.php:353
-#: ../lib/Galette/Core/Galette.php:494
+#: ../lib/Galette/Core/Galette.php:501
 msgid "Trombinoscope"
 msgstr ""
 
@@ -531,6 +533,35 @@ msgstr ""
 msgid "transaction"
 msgstr ""
 
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:64
+msgid "Add document"
+msgstr ""
+
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:120
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:406
+msgid "An error occurred adding document :("
+msgstr ""
+
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:134
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:420
+msgid "Document has been successfully stored!"
+msgstr ""
+
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:195
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:230
+msgid "Documents list"
+msgstr ""
+
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:350
+#: ../../tempcache/pages/documents_list.html.twig:203
+msgid "Edit document"
+msgstr ""
+
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:492
+#: ../../tempcache/pages/documents_list.html.twig:216
+msgid "Delete document"
+msgstr ""
+
 #: ../lib/Galette/Controllers/Crud/SavedSearchesController.php:102
 msgid "This search is already saved."
 msgstr ""
@@ -574,8 +605,8 @@ msgid "Dashboard"
 msgstr ""
 
 #: ../lib/Galette/Controllers/GaletteController.php:222
-#: ../lib/Galette/Core/Install.php:1162 ../lib/Galette/Core/Galette.php:328
-#: ../lib/Galette/Core/Galette.php:606
+#: ../lib/Galette/Core/Install.php:1162 ../lib/Galette/Core/Galette.php:335
+#: ../lib/Galette/Core/Galette.php:647
 msgid "Settings"
 msgstr ""
 
@@ -666,7 +697,7 @@ msgid "An error occurred while storing list configuration :("
 msgstr ""
 
 #: ../lib/Galette/Controllers/GaletteController.php:664
-#: ../lib/Galette/Core/Galette.php:256 ../lib/Galette/Core/Galette.php:590
+#: ../lib/Galette/Core/Galette.php:256 ../lib/Galette/Core/Galette.php:612
 msgid "Reminders"
 msgstr ""
 
@@ -747,7 +778,7 @@ msgid "No member selected to generate attendance sheet"
 msgstr ""
 
 #: ../lib/Galette/Controllers/PdfController.php:384
-#: ../lib/Galette/Core/Galette.php:898
+#: ../lib/Galette/Core/Galette.php:938
 #: ../lib/Galette/IO/PdfAttendanceSheet.php:120
 #: ../../tempcache/modals/members_attendance_sheet.html.twig:104
 msgid "Attendance sheet"
@@ -758,7 +789,7 @@ msgid "Unable to get groups list."
 msgstr ""
 
 #: ../lib/Galette/Controllers/PdfController.php:525
-#: ../lib/Galette/Core/Install.php:1186 ../lib/Galette/Core/Galette.php:402
+#: ../lib/Galette/Core/Install.php:1186 ../lib/Galette/Core/Galette.php:409
 msgid "PDF models"
 msgstr ""
 
@@ -984,7 +1015,7 @@ msgid "Flush the logs"
 msgstr ""
 
 #: ../lib/Galette/Controllers/PluginsController.php:61
-#: ../lib/Galette/Core/Galette.php:335 ../lib/Galette/Core/Galette.php:614
+#: ../lib/Galette/Core/Galette.php:342 ../lib/Galette/Core/Galette.php:655
 msgid "Plugins"
 msgstr ""
 
@@ -997,7 +1028,7 @@ msgid "Plugin %name has been disabled"
 msgstr ""
 
 #: ../lib/Galette/Controllers/DynamicTranslationsController.php:54
-#: ../lib/Galette/Core/Galette.php:365
+#: ../lib/Galette/Core/Galette.php:372
 #: ../../tempcache/pages/configuration_payment_types.html.twig:293
 #: ../../tempcache/pages/configuration_dynamic_fields.html.twig:250
 msgid "Translate labels"
@@ -1206,7 +1237,7 @@ msgstr ""
 msgid "Mails texts"
 msgstr ""
 
-#: ../lib/Galette/Core/Install.php:1182 ../lib/Galette/Core/Galette.php:394
+#: ../lib/Galette/Core/Install.php:1182 ../lib/Galette/Core/Galette.php:401
 msgid "Titles"
 msgstr ""
 
@@ -1297,27 +1328,27 @@ msgstr ""
 msgid "My Account"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:125 ../lib/Galette/Core/Galette.php:639
+#: ../lib/Galette/Core/Galette.php:125 ../lib/Galette/Core/Galette.php:680
 msgid "My contributions"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:126 ../lib/Galette/Core/Galette.php:640
+#: ../lib/Galette/Core/Galette.php:126 ../lib/Galette/Core/Galette.php:681
 msgid "View and filter all my contributions"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:133 ../lib/Galette/Core/Galette.php:648
+#: ../lib/Galette/Core/Galette.php:133 ../lib/Galette/Core/Galette.php:689
 msgid "My transactions"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:134 ../lib/Galette/Core/Galette.php:649
+#: ../lib/Galette/Core/Galette.php:134 ../lib/Galette/Core/Galette.php:690
 msgid "View and filter all my transactions"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:141 ../lib/Galette/Core/Galette.php:631
+#: ../lib/Galette/Core/Galette.php:141 ../lib/Galette/Core/Galette.php:672
 msgid "My information"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:142 ../lib/Galette/Core/Galette.php:632
+#: ../lib/Galette/Core/Galette.php:142 ../lib/Galette/Core/Galette.php:673
 msgid "View my member card"
 msgstr ""
 
@@ -1329,7 +1360,7 @@ msgstr ""
 msgid "Add new child member in database"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:164 ../lib/Galette/Core/Galette.php:540
+#: ../lib/Galette/Core/Galette.php:164 ../lib/Galette/Core/Galette.php:562
 #: ../lib/Galette/DynamicFields/DynamicField.php:533
 #: ../../tempcache/elements/group.html.twig:95
 msgid "Members"
@@ -1339,7 +1370,7 @@ msgstr ""
 msgid "List of members"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:173 ../lib/Galette/Core/Galette.php:541
+#: ../lib/Galette/Core/Galette.php:173 ../lib/Galette/Core/Galette.php:563
 msgid "View, search into and filter member's list"
 msgstr ""
 
@@ -1356,7 +1387,7 @@ msgstr ""
 msgid "Add new member in database"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:211 ../lib/Galette/Core/Galette.php:572
+#: ../lib/Galette/Core/Galette.php:211 ../lib/Galette/Core/Galette.php:594
 #: ../lib/Galette/DynamicFields/DynamicField.php:534
 #: ../../tempcache/pages/members_list.html.twig:561
 msgid "Contributions"
@@ -1366,7 +1397,7 @@ msgstr ""
 msgid "List of contributions"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:216 ../lib/Galette/Core/Galette.php:573
+#: ../lib/Galette/Core/Galette.php:216 ../lib/Galette/Core/Galette.php:595
 msgid "View and filter contributions"
 msgstr ""
 
@@ -1374,7 +1405,7 @@ msgstr ""
 msgid "List of transactions"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:225 ../lib/Galette/Core/Galette.php:582
+#: ../lib/Galette/Core/Galette.php:225 ../lib/Galette/Core/Galette.php:604
 msgid "View and filter transactions"
 msgstr ""
 
@@ -1410,7 +1441,7 @@ msgstr ""
 msgid "Add new transaction in database"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:257 ../lib/Galette/Core/Galette.php:591
+#: ../lib/Galette/Core/Galette.php:257 ../lib/Galette/Core/Galette.php:613
 msgid "Send reminders to late members"
 msgstr ""
 
@@ -1422,7 +1453,7 @@ msgstr ""
 msgid "Manage groups"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:273 ../lib/Galette/Core/Galette.php:549
+#: ../lib/Galette/Core/Galette.php:273 ../lib/Galette/Core/Galette.php:571
 msgid "View and manage groups"
 msgstr ""
 
@@ -1434,7 +1465,7 @@ msgstr ""
 msgid "Manage mailings"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:292 ../lib/Galette/Core/Galette.php:565
+#: ../lib/Galette/Core/Galette.php:292 ../lib/Galette/Core/Galette.php:587
 msgid "Manage mailings that has been sent"
 msgstr ""
 
@@ -1458,152 +1489,167 @@ msgstr ""
 msgid "Various charts"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:324 ../install/steps/check.php:53
+#: ../lib/Galette/Core/Galette.php:319 ../lib/Galette/Core/Galette.php:516
+#: ../lib/Galette/Core/Galette.php:631
+msgid "Documents"
+msgstr ""
+
+#: ../lib/Galette/Core/Galette.php:320
+msgid ""
+"Add documents to share related to your association (status, rules of "
+"procedure, ...)"
+msgstr ""
+
+#: ../lib/Galette/Core/Galette.php:331 ../install/steps/check.php:53
 msgid "Configuration"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:329 ../lib/Galette/Core/Galette.php:607
+#: ../lib/Galette/Core/Galette.php:336 ../lib/Galette/Core/Galette.php:648
 msgid ""
 "Set applications preferences (address, website, member's cards "
 "configuration, ...)"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:336 ../lib/Galette/Core/Galette.php:615
+#: ../lib/Galette/Core/Galette.php:343 ../lib/Galette/Core/Galette.php:656
 msgid "Information about available plugins"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:342
+#: ../lib/Galette/Core/Galette.php:349
 msgid "Core lists"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:343
+#: ../lib/Galette/Core/Galette.php:350
 msgid "Customize lists fields and order"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:350
+#: ../lib/Galette/Core/Galette.php:357
 msgid "Core fields"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:351
+#: ../lib/Galette/Core/Galette.php:358
 msgid ""
 "Customize fields order, set which are required, and for who they're visibles"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:357
+#: ../lib/Galette/Core/Galette.php:364
 msgid "Dynamic fields"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:358
+#: ../lib/Galette/Core/Galette.php:365
 msgid "Manage additional fields for various forms"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:366
+#: ../lib/Galette/Core/Galette.php:373
 msgid "Translate additional fields labels"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:372
+#: ../lib/Galette/Core/Galette.php:379
 msgid "Manage statuses"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:381
+#: ../lib/Galette/Core/Galette.php:388
 msgid "Manage contributions types"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:387
+#: ../lib/Galette/Core/Galette.php:394
 msgid "Emails content"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:388
+#: ../lib/Galette/Core/Galette.php:395
 msgid "Manage emails texts and subjects"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:395
+#: ../lib/Galette/Core/Galette.php:402
 msgid "Manage titles"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:403
+#: ../lib/Galette/Core/Galette.php:410
 msgid "Manage PDF models"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:409
+#: ../lib/Galette/Core/Galette.php:416
 msgid "Payment types"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:410
+#: ../lib/Galette/Core/Galette.php:417
 msgid "Manage payment types"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:417
+#: ../lib/Galette/Core/Galette.php:424
 msgid "Empty adhesion form"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:418
+#: ../lib/Galette/Core/Galette.php:425
 msgid "Download empty adhesion form"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:428
+#: ../lib/Galette/Core/Galette.php:435
 msgid "Admin tools"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:429
+#: ../lib/Galette/Core/Galette.php:436
 msgid "Various administrative tools"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:482
+#: ../lib/Galette/Core/Galette.php:489
 msgid "Public pages"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:581
+#: ../lib/Galette/Core/Galette.php:517 ../lib/Galette/Core/Galette.php:632
+msgid "View documents related to your association"
+msgstr ""
+
+#: ../lib/Galette/Core/Galette.php:603
 #: ../lib/Galette/DynamicFields/DynamicField.php:535
 msgid "Transactions"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:699 ../lib/Galette/Core/Galette.php:704
+#: ../lib/Galette/Core/Galette.php:739 ../lib/Galette/Core/Galette.php:744
 msgid "%membername: edit information"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:720 ../lib/Galette/Core/Galette.php:725
+#: ../lib/Galette/Core/Galette.php:760 ../lib/Galette/Core/Galette.php:765
 msgid "%membername: contributions"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:741 ../lib/Galette/Core/Galette.php:746
+#: ../lib/Galette/Core/Galette.php:781 ../lib/Galette/Core/Galette.php:786
 msgid "%membername: remove from database"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:765 ../lib/Galette/Core/Galette.php:770
+#: ../lib/Galette/Core/Galette.php:805 ../lib/Galette/Core/Galette.php:810
 #: ../../tempcache/elements/group_persons.html.twig:182
 msgid "Log in in as %membername"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:856
+#: ../lib/Galette/Core/Galette.php:896
 msgid "Mass change"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:861
+#: ../lib/Galette/Core/Galette.php:901
 msgid "Mass add contributions"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:866
+#: ../lib/Galette/Core/Galette.php:906
 #: ../../tempcache/elements/group.html.twig:374
 #: ../../tempcache/pages/contributions_list.html.twig:56
 msgid "Delete"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:882
+#: ../lib/Galette/Core/Galette.php:922
 #: ../../tempcache/pages/members_list.html.twig:740
 msgid "Mail"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:903
+#: ../lib/Galette/Core/Galette.php:943
 #: ../../tempcache/elements/mailing_recipients.html.twig:106
 msgid "Generate labels"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:908
+#: ../lib/Galette/Core/Galette.php:948
 msgid "Generate Member Cards"
 msgstr ""
 
-#: ../lib/Galette/Core/Galette.php:913
+#: ../lib/Galette/Core/Galette.php:953
 #: ../../tempcache/pages/contributions_list.html.twig:56
 msgid "Export as CSV"
 msgstr ""
@@ -1705,7 +1751,7 @@ msgstr ""
 
 #: ../lib/Galette/Core/Preferences.php:1136
 #: ../lib/Galette/Core/Preferences.php:1152 ../lib/Galette/IO/Csv.php:97
-#: ../lib/Galette/Entity/Texts.php:193
+#: ../lib/Galette/Entity/Document.php:448 ../lib/Galette/Entity/Texts.php:193
 #: ../../tempcache/pages/history.html.twig:323
 #: ../../tempcache/pages/mailings_list.html.twig:296
 msgid "Y-m-d H:i:s"
@@ -2372,19 +2418,24 @@ msgstr ""
 msgid "invoice"
 msgstr ""
 
-#: ../lib/Galette/Features/Permissions.php:59
+#: ../lib/Galette/Features/Permissions.php:60
 msgid "Inaccessible"
 msgstr ""
 
-#: ../lib/Galette/Features/Permissions.php:60
+#: ../lib/Galette/Features/Permissions.php:64
+#: ../lib/Galette/Entity/ListsConfig.php:202
+msgid "Public"
+msgstr ""
+
+#: ../lib/Galette/Features/Permissions.php:68
 msgid "User, read only"
 msgstr ""
 
-#: ../lib/Galette/Features/Permissions.php:61
+#: ../lib/Galette/Features/Permissions.php:69
 msgid "User, read/write"
 msgstr ""
 
-#: ../lib/Galette/Features/Permissions.php:62
+#: ../lib/Galette/Features/Permissions.php:70
 #: ../../tempcache/elements/ajax_members.html.twig:152
 #: ../../tempcache/elements/group_persons.html.twig:148
 #: ../../tempcache/pages/members_list.html.twig:626
@@ -2395,7 +2446,7 @@ msgstr ""
 msgid "Group manager"
 msgstr ""
 
-#: ../lib/Galette/Features/Permissions.php:63
+#: ../lib/Galette/Features/Permissions.php:71
 #: ../../tempcache/elements/ajax_members.html.twig:141
 #: ../../tempcache/elements/group_persons.html.twig:137
 #: ../../tempcache/pages/members_list.html.twig:619
@@ -2405,17 +2456,12 @@ msgstr ""
 msgid "Staff member"
 msgstr ""
 
-#: ../lib/Galette/Features/Permissions.php:64
+#: ../lib/Galette/Features/Permissions.php:72
 #: ../../tempcache/elements/ajax_members.html.twig:130
 #: ../../tempcache/elements/group_persons.html.twig:126
 msgid "Administrator"
 msgstr ""
 
-#: ../lib/Galette/Features/Permissions.php:68
-#: ../lib/Galette/Entity/ListsConfig.php:202
-msgid "Public"
-msgstr ""
-
 #: ../lib/Galette/Features/I18n.php:115
 #, php-format
 msgid "Unable to add dynamic translation for %field :("
@@ -2614,6 +2660,7 @@ msgid "Contribution year"
 msgstr ""
 
 #: ../lib/Galette/Features/Replacements.php:402
+#: ../../tempcache/pages/document_form.html.twig:176
 msgid "Comment"
 msgstr ""
 
@@ -3007,6 +3054,28 @@ msgstr ""
 msgid "Membership"
 msgstr ""
 
+#: ../lib/Galette/Entity/Document.php:464
+msgid "Association status"
+msgstr ""
+
+#: ../lib/Galette/Entity/Document.php:465
+msgid "Rules of procedure"
+msgstr ""
+
+#: ../lib/Galette/Entity/Document.php:466
+#: ../../tempcache/elements/edit_pdf_models.html.twig:189
+#: ../../tempcache/pages/member_show.html.twig:152
+msgid "Adhesion form"
+msgstr ""
+
+#: ../lib/Galette/Entity/Document.php:467
+msgid "Meeting minutes"
+msgstr ""
+
+#: ../lib/Galette/Entity/Document.php:468
+msgid "Votes results"
+msgstr ""
+
 #: ../lib/Galette/Entity/Title.php:169
 msgid "You cannot delete Mr. or Mrs. titles!"
 msgstr ""
@@ -3677,11 +3746,11 @@ msgid "Association"
 msgstr ""
 
 #. TRANS: see https://fomantic-ui.com/modules/calendar.html#custom-format - must be the same as Y-m-d for PHP https://www.php.net/manual/datetime.format.php
-#: ../includes/dependencies.php:172
+#: ../includes/dependencies.php:174
 msgid "YYYY-MM-DD"
 msgstr ""
 
-#: ../includes/dependencies.php:443
+#: ../includes/dependencies.php:445
 msgid "Failed CSRF check!"
 msgstr ""
 
@@ -4230,6 +4299,7 @@ msgstr ""
 #: ../../tempcache/pages/configuration_title_form.html.twig:94
 #: ../../tempcache/pages/preferences.html.twig:2662
 #: ../../tempcache/pages/configuration_payment_type_form.html.twig:94
+#: ../../tempcache/pages/document_form.html.twig:201
 msgid "Cancel"
 msgstr ""
 
@@ -4876,6 +4946,7 @@ msgstr ""
 #: ../../tempcache/pages/configuration_core_lists.html.twig:74
 #: ../../tempcache/pages/configuration_core_lists.html.twig:186
 #: ../../tempcache/pages/configuration_core_lists.html.twig:305
+#: ../../tempcache/pages/document_form.html.twig:147
 msgid "Permissions"
 msgstr ""
 
@@ -4886,6 +4957,8 @@ msgstr ""
 #: ../../tempcache/pages/transaction_form.html.twig:285
 #: ../../tempcache/pages/contributions_list.html.twig:408
 #: ../../tempcache/pages/contributions_list.html.twig:662
+#: ../../tempcache/pages/documents_list.html.twig:97
+#: ../../tempcache/pages/documents_list.html.twig:154
 #: ../../tempcache/pages/advanced_search.html.twig:819
 #: ../../tempcache/pages/advanced_search.html.twig:824
 msgid "Type"
@@ -5227,6 +5300,7 @@ msgstr ""
 #: ../../tempcache/pages/member_form.html.twig:468
 #: ../../tempcache/pages/configuration_payment_type_form.html.twig:89
 #: ../../tempcache/pages/configuration_dynamic_translations.html.twig:185
+#: ../../tempcache/pages/document_form.html.twig:196
 msgid "Save"
 msgstr ""
 
@@ -5370,11 +5444,6 @@ msgstr ""
 msgid "Receipt"
 msgstr ""
 
-#: ../../tempcache/elements/edit_pdf_models.html.twig:189
-#: ../../tempcache/pages/member_show.html.twig:152
-msgid "Adhesion form"
-msgstr ""
-
 #: ../../tempcache/elements/display_dynamic_fields.html.twig:45
 #: ../../tempcache/components/dynamic_fields.html.twig:60
 msgid "Additionnal fields:"
@@ -5539,6 +5608,7 @@ msgid "Add new social network"
 msgstr ""
 
 #: ../../tempcache/elements/edit_socials.html.twig:151
+#: ../../tempcache/pages/document_form.html.twig:121
 msgid "Choose or enter your own..."
 msgstr ""
 
@@ -5730,9 +5800,19 @@ msgid "Field name"
 msgstr ""
 
 #: ../../tempcache/pages/configuration_dynamic_field_form.html.twig:423
+#: ../../tempcache/pages/documents_list.html.twig:97
+#: ../../tempcache/pages/documents_list.html.twig:160
 msgid "Visibility"
 msgstr ""
 
+#: ../../tempcache/pages/documents_public_list.html.twig:86
+#: ../../tempcache/pages/documents_list.html.twig:57
+#, php-format
+msgid "%count document"
+msgid_plural "%count documents"
+msgstr[0] ""
+msgstr[1] ""
+
 #: ../../tempcache/pages/transaction_form.html.twig:63
 msgid "Transaction details"
 msgstr ""
@@ -6548,6 +6628,28 @@ msgstr ""
 msgid "Click on a row to select a member"
 msgstr ""
 
+#: ../../tempcache/pages/documents_list.html.twig:84
+msgid "Add a document"
+msgstr ""
+
+#: ../../tempcache/pages/documents_list.html.twig:97
+#: ../../tempcache/pages/documents_list.html.twig:166
+msgid "Filename"
+msgstr ""
+
+#: ../../tempcache/pages/documents_list.html.twig:97
+#: ../../tempcache/pages/documents_list.html.twig:180
+#: ../../tempcache/pages/saved_searches_list.html.twig:69
+#: ../../tempcache/pages/saved_searches_list.html.twig:128
+#: ../../tempcache/pages/advanced_search.html.twig:308
+#: ../../tempcache/pages/advanced_search.html.twig:661
+msgid "Creation date"
+msgstr ""
+
+#: ../../tempcache/pages/documents_list.html.twig:247
+msgid "No document"
+msgstr ""
+
 #: ../../tempcache/pages/index.html.twig:122
 msgid "Lost your password?"
 msgstr ""
@@ -6606,6 +6708,7 @@ msgstr ""
 #: ../../tempcache/pages/import.html.twig:259
 #: ../../tempcache/pages/preferences.html.twig:245
 #: ../../tempcache/pages/preferences.html.twig:2017
+#: ../../tempcache/pages/document_form.html.twig:101
 #: ../../tempcache/components/forms/picture.html.twig:75
 #: ../../tempcache/components/dynamic_fields.html.twig:758
 msgid "Choose a file"
@@ -6943,6 +7046,7 @@ msgstr ""
 
 #: ../../tempcache/pages/preferences.html.twig:243
 #: ../../tempcache/pages/preferences.html.twig:2015
+#: ../../tempcache/pages/document_form.html.twig:99
 #: ../../tempcache/components/forms/picture.html.twig:73
 #: ../../tempcache/components/dynamic_fields.html.twig:756
 msgid "Choose another file"
@@ -7693,13 +7797,6 @@ msgid_plural "%count searches"
 msgstr[0] ""
 msgstr[1] ""
 
-#: ../../tempcache/pages/saved_searches_list.html.twig:69
-#: ../../tempcache/pages/saved_searches_list.html.twig:128
-#: ../../tempcache/pages/advanced_search.html.twig:308
-#: ../../tempcache/pages/advanced_search.html.twig:661
-msgid "Creation date"
-msgstr ""
-
 #: ../../tempcache/pages/saved_searches_list.html.twig:140
 msgid "Search parameters"
 msgstr ""
@@ -8124,6 +8221,7 @@ msgid "Code:"
 msgstr ""
 
 #: ../../tempcache/pages/500.html.twig:121
+#: ../../tempcache/pages/document_form.html.twig:68
 msgid "File:"
 msgstr ""
 
@@ -8135,6 +8233,14 @@ msgstr ""
 msgid "Trace"
 msgstr ""
 
+#: ../../tempcache/pages/document_form.html.twig:111
+msgid "Document type"
+msgstr ""
+
+#: ../../tempcache/pages/document_form.html.twig:184
+msgid "Extra information displayed along with document."
+msgstr ""
+
 #: ../../tempcache/pages/export.html.twig:56
 msgid ""
 "Each selected export will be stored into a separate file in the exports "
index 7d2b553ff5f59f3e10431b51ddbb1eeab42a2967..8f45c4f697fc3ec24c55aa67f7d328a93fc132d8 100644 (file)
@@ -7,8 +7,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-03-17 19:30+0100\n"
-"PO-Revision-Date: 2024-03-17 19:30+0100\n"
+"POT-Creation-Date: 2024-03-20 18:45+0100\n"
+"PO-Revision-Date: 2024-03-20 18:45+0100\n"
 "Last-Translator: Automatically generated\n"
 "Language-Team: none\n"
 "Language: en_US\n"
@@ -100,7 +100,7 @@ msgid "Group name is missing!"
 msgstr "Group name is missing!"
 
 #: ../lib/Galette/Controllers/Crud/GroupsController.php:184
-#: ../lib/Galette/Core/Galette.php:548
+#: ../lib/Galette/Core/Galette.php:570
 msgid "Groups"
 msgstr "Groups"
 
@@ -154,7 +154,7 @@ msgid "Mailing has been successfully saved."
 msgstr "Mailing has been successfully saved."
 
 #: ../lib/Galette/Controllers/Crud/MailingsController.php:477
-#: ../lib/Galette/Core/Galette.php:564
+#: ../lib/Galette/Core/Galette.php:586
 msgid "Mailings"
 msgstr "Mailings"
 
@@ -170,7 +170,7 @@ msgid "Mailing preview"
 msgstr "Mailing preview"
 
 #: ../lib/Galette/Controllers/Crud/ContributionsTypesController.php:89
-#: ../lib/Galette/Core/Install.php:1166 ../lib/Galette/Core/Galette.php:380
+#: ../lib/Galette/Core/Install.php:1166 ../lib/Galette/Core/Galette.php:387
 msgid "Contributions types"
 msgstr "Contributions types"
 
@@ -223,6 +223,7 @@ msgstr "Dynamic fields configuration"
 #: ../lib/Galette/Controllers/Crud/DynamicFieldsController.php:308
 #: ../lib/Galette/Controllers/Crud/MembersController.php:238
 #: ../lib/Galette/Controllers/Crud/MembersController.php:935
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:276
 #: ../lib/Galette/Controllers/PdfController.php:97
 #: ../lib/Galette/Controllers/PdfController.php:264
 #: ../lib/Galette/Middleware/Authenticate.php:169
@@ -230,6 +231,7 @@ msgid "You do not have permission for requested URL."
 msgstr "You do not have permission for requested URL."
 
 #: ../lib/Galette/Controllers/Crud/DynamicFieldsController.php:364
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:315
 msgid "The file does not exists or cannot be read :("
 msgstr "The file does not exists or cannot be read :("
 
@@ -278,12 +280,12 @@ msgid "Member Profile"
 msgstr "Member Profile"
 
 #: ../lib/Galette/Controllers/Crud/MembersController.php:353
-#: ../lib/Galette/Core/Galette.php:486
+#: ../lib/Galette/Core/Galette.php:493
 msgid "Members list"
 msgstr "Members list"
 
 #: ../lib/Galette/Controllers/Crud/MembersController.php:353
-#: ../lib/Galette/Core/Galette.php:494
+#: ../lib/Galette/Core/Galette.php:501
 msgid "Trombinoscope"
 msgstr "Trombinoscope"
 
@@ -530,6 +532,35 @@ msgstr "contribution"
 msgid "transaction"
 msgstr "transaction"
 
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:64
+msgid "Add document"
+msgstr "Add document"
+
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:120
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:406
+msgid "An error occurred adding document :("
+msgstr "An error occurred adding document :("
+
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:134
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:420
+msgid "Document has been successfully stored!"
+msgstr "Document has been successfully stored!"
+
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:195
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:230
+msgid "Documents list"
+msgstr "Documents list"
+
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:350
+#: ../../tempcache/pages/documents_list.html.twig:203
+msgid "Edit document"
+msgstr "Edit document"
+
+#: ../lib/Galette/Controllers/Crud/DocumentsController.php:492
+#: ../../tempcache/pages/documents_list.html.twig:216
+msgid "Delete document"
+msgstr "Delete document"
+
 #: ../lib/Galette/Controllers/Crud/SavedSearchesController.php:102
 msgid "This search is already saved."
 msgstr "This search is already saved."
@@ -573,8 +604,8 @@ msgid "Dashboard"
 msgstr "Dashboard"
 
 #: ../lib/Galette/Controllers/GaletteController.php:222
-#: ../lib/Galette/Core/Install.php:1162 ../lib/Galette/Core/Galette.php:328
-#: ../lib/Galette/Core/Galette.php:606
+#: ../lib/Galette/Core/Install.php:1162 ../lib/Galette/Core/Galette.php:335
+#: ../lib/Galette/Core/Galette.php:647
 msgid "Settings"
 msgstr "Settings"
 
@@ -669,7 +700,7 @@ msgid "An error occurred while storing list configuration :("
 msgstr "An error occurred while storing list configuration :("
 
 #: ../lib/Galette/Controllers/GaletteController.php:664
-#: ../lib/Galette/Core/Galette.php:256 ../lib/Galette/Core/Galette.php:590
+#: ../lib/Galette/Core/Galette.php:256 ../lib/Galette/Core/Galette.php:612
 msgid "Reminders"
 msgstr "Reminders"
 
@@ -750,7 +781,7 @@ msgid "No member selected to generate attendance sheet"
 msgstr "No member selected to generate attendance sheet"
 
 #: ../lib/Galette/Controllers/PdfController.php:384
-#: ../lib/Galette/Core/Galette.php:898
+#: ../lib/Galette/Core/Galette.php:938
 #: ../lib/Galette/IO/PdfAttendanceSheet.php:120
 #: ../../tempcache/modals/members_attendance_sheet.html.twig:104
 msgid "Attendance sheet"
@@ -761,7 +792,7 @@ msgid "Unable to get groups list."
 msgstr "Unable to get groups list."
 
 #: ../lib/Galette/Controllers/PdfController.php:525
-#: ../lib/Galette/Core/Install.php:1186 ../lib/Galette/Core/Galette.php:402
+#: ../lib/Galette/Core/Install.php:1186 ../lib/Galette/Core/Galette.php:409
 msgid "PDF models"
 msgstr "PDF models"
 
@@ -993,7 +1024,7 @@ msgid "Flush the logs"
 msgstr "Flush the logs"
 
 #: ../lib/Galette/Controllers/PluginsController.php:61
-#: ../lib/Galette/Core/Galette.php:335 ../lib/Galette/Core/Galette.php:614
+#: ../lib/Galette/Core/Galette.php:342 ../lib/Galette/Core/Galette.php:655
 msgid "Plugins"
 msgstr "Plugins"
 
@@ -1006,7 +1037,7 @@ msgid "Plugin %name has been disabled"
 msgstr "Plugin %name has been disabled"
 
 #: ../lib/Galette/Controllers/DynamicTranslationsController.php:54
-#: ../lib/Galette/Core/Galette.php:365
+#: ../lib/Galette/Core/Galette.php:372
 #: ../../tempcache/pages/configuration_payment_types.html.twig:293
 #: ../../tempcache/pages/configuration_dynamic_fields.html.twig:250
 msgid "Translate labels"
@@ -1219,7 +1250,7 @@ msgstr "Fields config and categories"
 msgid "Mails texts"
 msgstr "Mails texts"
 
-#: ../lib/Galette/Core/Install.php:1182 ../lib/Galette/Core/Galette.php:394
+#: ../lib/Galette/Core/Install.php:1182 ../lib/Galette/Core/Galette.php:401
 msgid "Titles"
 msgstr "Titles"
 
@@ -1313,27 +1344,27 @@ msgstr "Delete mailing entries"
 msgid "My Account"
 msgstr "My Account"
 
-#: ../lib/Galette/Core/Galette.php:125 ../lib/Galette/Core/Galette.php:639
+#: ../lib/Galette/Core/Galette.php:125 ../lib/Galette/Core/Galette.php:680
 msgid "My contributions"
 msgstr "My contributions"
 
-#: ../lib/Galette/Core/Galette.php:126 ../lib/Galette/Core/Galette.php:640
+#: ../lib/Galette/Core/Galette.php:126 ../lib/Galette/Core/Galette.php:681
 msgid "View and filter all my contributions"
 msgstr "View and filter all my contributions"
 
-#: ../lib/Galette/Core/Galette.php:133 ../lib/Galette/Core/Galette.php:648
+#: ../lib/Galette/Core/Galette.php:133 ../lib/Galette/Core/Galette.php:689
 msgid "My transactions"
 msgstr "My transactions"
 
-#: ../lib/Galette/Core/Galette.php:134 ../lib/Galette/Core/Galette.php:649
+#: ../lib/Galette/Core/Galette.php:134 ../lib/Galette/Core/Galette.php:690
 msgid "View and filter all my transactions"
 msgstr "View and filter all my transactions"
 
-#: ../lib/Galette/Core/Galette.php:141 ../lib/Galette/Core/Galette.php:631
+#: ../lib/Galette/Core/Galette.php:141 ../lib/Galette/Core/Galette.php:672
 msgid "My information"
 msgstr "My information"
 
-#: ../lib/Galette/Core/Galette.php:142 ../lib/Galette/Core/Galette.php:632
+#: ../lib/Galette/Core/Galette.php:142 ../lib/Galette/Core/Galette.php:673
 msgid "View my member card"
 msgstr "View my member card"
 
@@ -1345,7 +1376,7 @@ msgstr "Add a child member"
 msgid "Add new child member in database"
 msgstr "Add new child member in database"
 
-#: ../lib/Galette/Core/Galette.php:164 ../lib/Galette/Core/Galette.php:540
+#: ../lib/Galette/Core/Galette.php:164 ../lib/Galette/Core/Galette.php:562
 #: ../lib/Galette/DynamicFields/DynamicField.php:533
 #: ../../tempcache/elements/group.html.twig:95
 msgid "Members"
@@ -1355,7 +1386,7 @@ msgstr "Members"
 msgid "List of members"
 msgstr "List of members"
 
-#: ../lib/Galette/Core/Galette.php:173 ../lib/Galette/Core/Galette.php:541
+#: ../lib/Galette/Core/Galette.php:173 ../lib/Galette/Core/Galette.php:563
 msgid "View, search into and filter member's list"
 msgstr "View, search into and filter member's list"
 
@@ -1372,7 +1403,7 @@ msgstr "Add a member"
 msgid "Add new member in database"
 msgstr "Add new member in database"
 
-#: ../lib/Galette/Core/Galette.php:211 ../lib/Galette/Core/Galette.php:572
+#: ../lib/Galette/Core/Galette.php:211 ../lib/Galette/Core/Galette.php:594
 #: ../lib/Galette/DynamicFields/DynamicField.php:534
 #: ../../tempcache/pages/members_list.html.twig:561
 msgid "Contributions"
@@ -1382,7 +1413,7 @@ msgstr "Contributions"
 msgid "List of contributions"
 msgstr "List of contributions"
 
-#: ../lib/Galette/Core/Galette.php:216 ../lib/Galette/Core/Galette.php:573
+#: ../lib/Galette/Core/Galette.php:216 ../lib/Galette/Core/Galette.php:595
 msgid "View and filter contributions"
 msgstr "View and filter contributions"
 
@@ -1390,7 +1421,7 @@ msgstr "View and filter contributions"
 msgid "List of transactions"
 msgstr "List of transactions"
 
-#: ../lib/Galette/Core/Galette.php:225 ../lib/Galette/Core/Galette.php:582
+#: ../lib/Galette/Core/Galette.php:225 ../lib/Galette/Core/Galette.php:604
 msgid "View and filter transactions"
 msgstr "View and filter transactions"
 
@@ -1426,7 +1457,7 @@ msgstr "Add a transaction"
 msgid "Add new transaction in database"
 msgstr "Add new transaction in database"
 
-#: ../lib/Galette/Core/Galette.php:257 ../lib/Galette/Core/Galette.php:591
+#: ../lib/Galette/Core/Galette.php:257 ../lib/Galette/Core/Galette.php:613
 msgid "Send reminders to late members"
 msgstr "Send reminders to late members"
 
@@ -1438,7 +1469,7 @@ msgstr "Management"
 msgid "Manage groups"
 msgstr "Manage groups"
 
-#: ../lib/Galette/Core/Galette.php:273 ../lib/Galette/Core/Galette.php:549
+#: ../lib/Galette/Core/Galette.php:273 ../lib/Galette/Core/Galette.php:571
 msgid "View and manage groups"
 msgstr "View and manage groups"
 
@@ -1450,7 +1481,7 @@ msgstr "View application's logs"
 msgid "Manage mailings"
 msgstr "Manage mailings"
 
-#: ../lib/Galette/Core/Galette.php:292 ../lib/Galette/Core/Galette.php:565
+#: ../lib/Galette/Core/Galette.php:292 ../lib/Galette/Core/Galette.php:587
 msgid "Manage mailings that has been sent"
 msgstr "Manage mailings that has been sent"
 
@@ -1474,11 +1505,24 @@ msgstr "Import members from CSV files"
 msgid "Various charts"
 msgstr "Various charts"
 
-#: ../lib/Galette/Core/Galette.php:324 ../install/steps/check.php:53
+#: ../lib/Galette/Core/Galette.php:319 ../lib/Galette/Core/Galette.php:516
+#: ../lib/Galette/Core/Galette.php:631
+msgid "Documents"
+msgstr "Documents"
+
+#: ../lib/Galette/Core/Galette.php:320
+msgid ""
+"Add documents to share related to your association (status, rules of "
+"procedure, ...)"
+msgstr ""
+"Add documents to share related to your association (status, rules of "
+"procedure, ...)"
+
+#: ../lib/Galette/Core/Galette.php:331 ../install/steps/check.php:53
 msgid "Configuration"
 msgstr "Configuration"
 
-#: ../lib/Galette/Core/Galette.php:329 ../lib/Galette/Core/Galette.php:607
+#: ../lib/Galette/Core/Galette.php:336 ../lib/Galette/Core/Galette.php:648
 msgid ""
 "Set applications preferences (address, website, member's cards "
 "configuration, ...)"
@@ -1486,143 +1530,147 @@ msgstr ""
 "Set applications preferences (address, website, member's cards "
 "configuration, ...)"
 
-#: ../lib/Galette/Core/Galette.php:336 ../lib/Galette/Core/Galette.php:615
+#: ../lib/Galette/Core/Galette.php:343 ../lib/Galette/Core/Galette.php:656
 msgid "Information about available plugins"
 msgstr "Information about available plugins"
 
-#: ../lib/Galette/Core/Galette.php:342
+#: ../lib/Galette/Core/Galette.php:349
 msgid "Core lists"
 msgstr "Core lists"
 
-#: ../lib/Galette/Core/Galette.php:343
+#: ../lib/Galette/Core/Galette.php:350
 msgid "Customize lists fields and order"
 msgstr "Customize lists fields and order"
 
-#: ../lib/Galette/Core/Galette.php:350
+#: ../lib/Galette/Core/Galette.php:357
 msgid "Core fields"
 msgstr "Core fields"
 
-#: ../lib/Galette/Core/Galette.php:351
+#: ../lib/Galette/Core/Galette.php:358
 msgid ""
 "Customize fields order, set which are required, and for who they're visibles"
 msgstr ""
 "Customize fields order, set which are required, and for who they're visibles"
 
-#: ../lib/Galette/Core/Galette.php:357
+#: ../lib/Galette/Core/Galette.php:364
 msgid "Dynamic fields"
 msgstr "Dynamic fields"
 
-#: ../lib/Galette/Core/Galette.php:358
+#: ../lib/Galette/Core/Galette.php:365
 msgid "Manage additional fields for various forms"
 msgstr "Manage additional fields for various forms"
 
-#: ../lib/Galette/Core/Galette.php:366
+#: ../lib/Galette/Core/Galette.php:373
 msgid "Translate additional fields labels"
 msgstr "Translate additional fields labels"
 
-#: ../lib/Galette/Core/Galette.php:372
+#: ../lib/Galette/Core/Galette.php:379
 msgid "Manage statuses"
 msgstr "Manage statuses"
 
-#: ../lib/Galette/Core/Galette.php:381
+#: ../lib/Galette/Core/Galette.php:388
 msgid "Manage contributions types"
 msgstr "Manage contributions types"
 
-#: ../lib/Galette/Core/Galette.php:387
+#: ../lib/Galette/Core/Galette.php:394
 msgid "Emails content"
 msgstr "Emails content"
 
-#: ../lib/Galette/Core/Galette.php:388
+#: ../lib/Galette/Core/Galette.php:395
 msgid "Manage emails texts and subjects"
 msgstr "Manage emails texts and subjects"
 
-#: ../lib/Galette/Core/Galette.php:395
+#: ../lib/Galette/Core/Galette.php:402
 msgid "Manage titles"
 msgstr "Manage titles"
 
-#: ../lib/Galette/Core/Galette.php:403
+#: ../lib/Galette/Core/Galette.php:410
 msgid "Manage PDF models"
 msgstr "Manage PDF models"
 
-#: ../lib/Galette/Core/Galette.php:409
+#: ../lib/Galette/Core/Galette.php:416
 msgid "Payment types"
 msgstr "Payment types"
 
-#: ../lib/Galette/Core/Galette.php:410
+#: ../lib/Galette/Core/Galette.php:417
 msgid "Manage payment types"
 msgstr "Manage payment types"
 
-#: ../lib/Galette/Core/Galette.php:417
+#: ../lib/Galette/Core/Galette.php:424
 msgid "Empty adhesion form"
 msgstr "Empty adhesion form"
 
-#: ../lib/Galette/Core/Galette.php:418
+#: ../lib/Galette/Core/Galette.php:425
 msgid "Download empty adhesion form"
 msgstr "Download empty adhesion form"
 
-#: ../lib/Galette/Core/Galette.php:428
+#: ../lib/Galette/Core/Galette.php:435
 msgid "Admin tools"
 msgstr "Admin tools"
 
-#: ../lib/Galette/Core/Galette.php:429
+#: ../lib/Galette/Core/Galette.php:436
 msgid "Various administrative tools"
 msgstr "Various administrative tools"
 
-#: ../lib/Galette/Core/Galette.php:482
+#: ../lib/Galette/Core/Galette.php:489
 msgid "Public pages"
 msgstr "Public pages"
 
-#: ../lib/Galette/Core/Galette.php:581
+#: ../lib/Galette/Core/Galette.php:517 ../lib/Galette/Core/Galette.php:632
+msgid "View documents related to your association"
+msgstr "View documents related to your association"
+
+#: ../lib/Galette/Core/Galette.php:603
 #: ../lib/Galette/DynamicFields/DynamicField.php:535
 msgid "Transactions"
 msgstr "Transactions"
 
-#: ../lib/Galette/Core/Galette.php:699 ../lib/Galette/Core/Galette.php:704
+#: ../lib/Galette/Core/Galette.php:739 ../lib/Galette/Core/Galette.php:744
 msgid "%membername: edit information"
 msgstr "%membername: edit information"
 
-#: ../lib/Galette/Core/Galette.php:720 ../lib/Galette/Core/Galette.php:725
+#: ../lib/Galette/Core/Galette.php:760 ../lib/Galette/Core/Galette.php:765
 msgid "%membername: contributions"
 msgstr "%membername: contributions"
 
-#: ../lib/Galette/Core/Galette.php:741 ../lib/Galette/Core/Galette.php:746
+#: ../lib/Galette/Core/Galette.php:781 ../lib/Galette/Core/Galette.php:786
 msgid "%membername: remove from database"
 msgstr "%membername: remove from database"
 
-#: ../lib/Galette/Core/Galette.php:765 ../lib/Galette/Core/Galette.php:770
+#: ../lib/Galette/Core/Galette.php:805 ../lib/Galette/Core/Galette.php:810
 #: ../../tempcache/elements/group_persons.html.twig:182
 msgid "Log in in as %membername"
 msgstr "Log in in as %membername"
 
-#: ../lib/Galette/Core/Galette.php:856
+#: ../lib/Galette/Core/Galette.php:896
 msgid "Mass change"
 msgstr "Mass change"
 
-#: ../lib/Galette/Core/Galette.php:861
+#: ../lib/Galette/Core/Galette.php:901
 msgid "Mass add contributions"
 msgstr "Mass add contributions"
 
-#: ../lib/Galette/Core/Galette.php:866
+#: ../lib/Galette/Core/Galette.php:906
 #: ../../tempcache/elements/group.html.twig:374
 #: ../../tempcache/pages/contributions_list.html.twig:56
 msgid "Delete"
 msgstr "Delete"
 
-#: ../lib/Galette/Core/Galette.php:882
+#: ../lib/Galette/Core/Galette.php:922
 #: ../../tempcache/pages/members_list.html.twig:740
 msgid "Mail"
 msgstr "Mail"
 
-#: ../lib/Galette/Core/Galette.php:903
+#: ../lib/Galette/Core/Galette.php:943
 #: ../../tempcache/elements/mailing_recipients.html.twig:106
 msgid "Generate labels"
 msgstr "Generate labels"
 
-#: ../lib/Galette/Core/Galette.php:908
+#: ../lib/Galette/Core/Galette.php:948
 msgid "Generate Member Cards"
 msgstr "Generate Member Cards"
 
-#: ../lib/Galette/Core/Galette.php:913
+#: ../lib/Galette/Core/Galette.php:953
 #: ../../tempcache/pages/contributions_list.html.twig:56
 msgid "Export as CSV"
 msgstr "Export as CSV"
@@ -1727,7 +1775,7 @@ msgstr "Please define constant \"GALETTE_URI\" with the path to your instance."
 
 #: ../lib/Galette/Core/Preferences.php:1136
 #: ../lib/Galette/Core/Preferences.php:1152 ../lib/Galette/IO/Csv.php:97
-#: ../lib/Galette/Entity/Texts.php:193
+#: ../lib/Galette/Entity/Document.php:448 ../lib/Galette/Entity/Texts.php:193
 #: ../../tempcache/pages/history.html.twig:323
 #: ../../tempcache/pages/mailings_list.html.twig:296
 msgid "Y-m-d H:i:s"
@@ -2399,19 +2447,24 @@ msgstr "receipt"
 msgid "invoice"
 msgstr "invoice"
 
-#: ../lib/Galette/Features/Permissions.php:59
+#: ../lib/Galette/Features/Permissions.php:60
 msgid "Inaccessible"
 msgstr "Inaccessible"
 
-#: ../lib/Galette/Features/Permissions.php:60
+#: ../lib/Galette/Features/Permissions.php:64
+#: ../lib/Galette/Entity/ListsConfig.php:202
+msgid "Public"
+msgstr "Public"
+
+#: ../lib/Galette/Features/Permissions.php:68
 msgid "User, read only"
 msgstr "User, read only"
 
-#: ../lib/Galette/Features/Permissions.php:61
+#: ../lib/Galette/Features/Permissions.php:69
 msgid "User, read/write"
 msgstr "User, read/write"
 
-#: ../lib/Galette/Features/Permissions.php:62
+#: ../lib/Galette/Features/Permissions.php:70
 #: ../../tempcache/elements/ajax_members.html.twig:152
 #: ../../tempcache/elements/group_persons.html.twig:148
 #: ../../tempcache/pages/members_list.html.twig:626
@@ -2422,7 +2475,7 @@ msgstr "User, read/write"
 msgid "Group manager"
 msgstr "Group manager"
 
-#: ../lib/Galette/Features/Permissions.php:63
+#: ../lib/Galette/Features/Permissions.php:71
 #: ../../tempcache/elements/ajax_members.html.twig:141
 #: ../../tempcache/elements/group_persons.html.twig:137
 #: ../../tempcache/pages/members_list.html.twig:619
@@ -2432,17 +2485,12 @@ msgstr "Group manager"
 msgid "Staff member"
 msgstr "Staff member"
 
-#: ../lib/Galette/Features/Permissions.php:64
+#: ../lib/Galette/Features/Permissions.php:72
 #: ../../tempcache/elements/ajax_members.html.twig:130
 #: ../../tempcache/elements/group_persons.html.twig:126
 msgid "Administrator"
 msgstr "Administrator"
 
-#: ../lib/Galette/Features/Permissions.php:68
-#: ../lib/Galette/Entity/ListsConfig.php:202
-msgid "Public"
-msgstr "Public"
-
 #: ../lib/Galette/Features/I18n.php:115
 #, php-format
 msgid "Unable to add dynamic translation for %field :("
@@ -2641,6 +2689,7 @@ msgid "Contribution year"
 msgstr "Contribution year"
 
 #: ../lib/Galette/Features/Replacements.php:402
+#: ../../tempcache/pages/document_form.html.twig:176
 msgid "Comment"
 msgstr "Comment"
 
@@ -3034,6 +3083,28 @@ msgstr "Script output"
 msgid "Membership"
 msgstr "Membership"
 
+#: ../lib/Galette/Entity/Document.php:464
+msgid "Association status"
+msgstr "Association status"
+
+#: ../lib/Galette/Entity/Document.php:465
+msgid "Rules of procedure"
+msgstr "Rules of procedure"
+
+#: ../lib/Galette/Entity/Document.php:466
+#: ../../tempcache/elements/edit_pdf_models.html.twig:189
+#: ../../tempcache/pages/member_show.html.twig:152
+msgid "Adhesion form"
+msgstr "Adhesion form"
+
+#: ../lib/Galette/Entity/Document.php:467
+msgid "Meeting minutes"
+msgstr "Meeting minutes"
+
+#: ../lib/Galette/Entity/Document.php:468
+msgid "Votes results"
+msgstr "Votes results"
+
 #: ../lib/Galette/Entity/Title.php:169
 msgid "You cannot delete Mr. or Mrs. titles!"
 msgstr "You cannot delete Mr. or Mrs. titles!"
@@ -3717,11 +3788,11 @@ msgid "Association"
 msgstr "Association"
 
 #. TRANS: see https://fomantic-ui.com/modules/calendar.html#custom-format - must be the same as Y-m-d for PHP https://www.php.net/manual/datetime.format.php
-#: ../includes/dependencies.php:172
+#: ../includes/dependencies.php:174
 msgid "YYYY-MM-DD"
 msgstr "YYYY-MM-DD"
 
-#: ../includes/dependencies.php:443
+#: ../includes/dependencies.php:445
 msgid "Failed CSRF check!"
 msgstr "Failed CSRF check!"
 
@@ -4321,6 +4392,7 @@ msgstr "and"
 #: ../../tempcache/pages/configuration_title_form.html.twig:94
 #: ../../tempcache/pages/preferences.html.twig:2662
 #: ../../tempcache/pages/configuration_payment_type_form.html.twig:94
+#: ../../tempcache/pages/document_form.html.twig:201
 msgid "Cancel"
 msgstr "Cancel"
 
@@ -4995,6 +5067,7 @@ msgstr "Add"
 #: ../../tempcache/pages/configuration_core_lists.html.twig:74
 #: ../../tempcache/pages/configuration_core_lists.html.twig:186
 #: ../../tempcache/pages/configuration_core_lists.html.twig:305
+#: ../../tempcache/pages/document_form.html.twig:147
 msgid "Permissions"
 msgstr "Permissions"
 
@@ -5005,6 +5078,8 @@ msgstr "Permissions"
 #: ../../tempcache/pages/transaction_form.html.twig:285
 #: ../../tempcache/pages/contributions_list.html.twig:408
 #: ../../tempcache/pages/contributions_list.html.twig:662
+#: ../../tempcache/pages/documents_list.html.twig:97
+#: ../../tempcache/pages/documents_list.html.twig:154
 #: ../../tempcache/pages/advanced_search.html.twig:819
 #: ../../tempcache/pages/advanced_search.html.twig:824
 msgid "Type"
@@ -5346,6 +5421,7 @@ msgstr "Loading..."
 #: ../../tempcache/pages/member_form.html.twig:468
 #: ../../tempcache/pages/configuration_payment_type_form.html.twig:89
 #: ../../tempcache/pages/configuration_dynamic_translations.html.twig:185
+#: ../../tempcache/pages/document_form.html.twig:196
 msgid "Save"
 msgstr "Save"
 
@@ -5491,11 +5567,6 @@ msgstr "Invoice"
 msgid "Receipt"
 msgstr "Receipt"
 
-#: ../../tempcache/elements/edit_pdf_models.html.twig:189
-#: ../../tempcache/pages/member_show.html.twig:152
-msgid "Adhesion form"
-msgstr "Adhesion form"
-
 #: ../../tempcache/elements/display_dynamic_fields.html.twig:45
 #: ../../tempcache/components/dynamic_fields.html.twig:60
 msgid "Additionnal fields:"
@@ -5665,6 +5736,7 @@ msgid "Add new social network"
 msgstr "Add new social network"
 
 #: ../../tempcache/elements/edit_socials.html.twig:151
+#: ../../tempcache/pages/document_form.html.twig:121
 msgid "Choose or enter your own..."
 msgstr "Choose or enter your own..."
 
@@ -5858,9 +5930,19 @@ msgid "Field name"
 msgstr "Field name"
 
 #: ../../tempcache/pages/configuration_dynamic_field_form.html.twig:423
+#: ../../tempcache/pages/documents_list.html.twig:97
+#: ../../tempcache/pages/documents_list.html.twig:160
 msgid "Visibility"
 msgstr "Visibility"
 
+#: ../../tempcache/pages/documents_public_list.html.twig:86
+#: ../../tempcache/pages/documents_list.html.twig:57
+#, php-format
+msgid "%count document"
+msgid_plural "%count documents"
+msgstr[0] "%count document"
+msgstr[1] "%count documents"
+
 #: ../../tempcache/pages/transaction_form.html.twig:63
 msgid "Transaction details"
 msgstr "Transaction details"
@@ -6691,6 +6773,28 @@ msgstr ""
 msgid "Click on a row to select a member"
 msgstr "Click on a row to select a member"
 
+#: ../../tempcache/pages/documents_list.html.twig:84
+msgid "Add a document"
+msgstr "Add a document"
+
+#: ../../tempcache/pages/documents_list.html.twig:97
+#: ../../tempcache/pages/documents_list.html.twig:166
+msgid "Filename"
+msgstr "Filename"
+
+#: ../../tempcache/pages/documents_list.html.twig:97
+#: ../../tempcache/pages/documents_list.html.twig:180
+#: ../../tempcache/pages/saved_searches_list.html.twig:69
+#: ../../tempcache/pages/saved_searches_list.html.twig:128
+#: ../../tempcache/pages/advanced_search.html.twig:308
+#: ../../tempcache/pages/advanced_search.html.twig:661
+msgid "Creation date"
+msgstr "Creation date"
+
+#: ../../tempcache/pages/documents_list.html.twig:247
+msgid "No document"
+msgstr "No document"
+
 #: ../../tempcache/pages/index.html.twig:122
 msgid "Lost your password?"
 msgstr "Lost your password?"
@@ -6751,6 +6855,7 @@ msgstr "Upload new file"
 #: ../../tempcache/pages/import.html.twig:259
 #: ../../tempcache/pages/preferences.html.twig:245
 #: ../../tempcache/pages/preferences.html.twig:2017
+#: ../../tempcache/pages/document_form.html.twig:101
 #: ../../tempcache/components/forms/picture.html.twig:75
 #: ../../tempcache/components/dynamic_fields.html.twig:758
 msgid "Choose a file"
@@ -7094,6 +7199,7 @@ msgstr "Current logo"
 
 #: ../../tempcache/pages/preferences.html.twig:243
 #: ../../tempcache/pages/preferences.html.twig:2015
+#: ../../tempcache/pages/document_form.html.twig:99
 #: ../../tempcache/components/forms/picture.html.twig:73
 #: ../../tempcache/components/dynamic_fields.html.twig:756
 msgid "Choose another file"
@@ -7895,13 +8001,6 @@ msgid_plural "%count searches"
 msgstr[0] "%count search"
 msgstr[1] "%count searches"
 
-#: ../../tempcache/pages/saved_searches_list.html.twig:69
-#: ../../tempcache/pages/saved_searches_list.html.twig:128
-#: ../../tempcache/pages/advanced_search.html.twig:308
-#: ../../tempcache/pages/advanced_search.html.twig:661
-msgid "Creation date"
-msgstr "Creation date"
-
 #: ../../tempcache/pages/saved_searches_list.html.twig:140
 msgid "Search parameters"
 msgstr "Search parameters"
@@ -8333,6 +8432,7 @@ msgid "Code:"
 msgstr "Code:"
 
 #: ../../tempcache/pages/500.html.twig:121
+#: ../../tempcache/pages/document_form.html.twig:68
 msgid "File:"
 msgstr "File:"
 
@@ -8344,6 +8444,14 @@ msgstr "Line:"
 msgid "Trace"
 msgstr "Trace"
 
+#: ../../tempcache/pages/document_form.html.twig:111
+msgid "Document type"
+msgstr "Document type"
+
+#: ../../tempcache/pages/document_form.html.twig:184
+msgid "Extra information displayed along with document."
+msgstr "Extra information displayed along with document."
+
 #: ../../tempcache/pages/export.html.twig:56
 msgid ""
 "Each selected export will be stored into a separate file in the exports "
diff --git a/galette/lib/Galette/Controllers/Crud/DocumentsController.php b/galette/lib/Galette/Controllers/Crud/DocumentsController.php
new file mode 100644 (file)
index 0000000..7d52c1e
--- /dev/null
@@ -0,0 +1,511 @@
+<?php
+
+/**
+ * Copyright © 2003-2024 The Galette Team
+ *
+ * This file is part of Galette (https://galette.eu).
+ *
+ * Galette is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Galette 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Galette. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace Galette\Controllers\Crud;
+
+use Galette\Core\Galette;
+use Galette\Entity\Document;
+use Galette\Filters\DocumentsList;
+use Galette\IO\File;
+use Galette\Repository\DynamicFieldsSet;
+use Throwable;
+use Galette\Controllers\CrudController;
+use Slim\Psr7\Request;
+use Slim\Psr7\Response;
+use Galette\DynamicFields\DynamicField;
+use Analog\Analog;
+
+/**
+ * Galette documents controller
+ *
+ * @author Johan Cwiklinski <johan@x-tnd.be>
+ */
+
+class DocumentsController extends CrudController
+{
+    // CRUD - Create
+
+    /**
+     * Add page
+     *
+     * @param Request  $request   PSR Request
+     * @param Response $response  PSR Response
+     * @param ?string  $form_name Form name
+     *
+     * @return Response
+     */
+    public function add(Request $request, Response $response, string $form_name = null): Response
+    {
+        if (isset($this->session->document)) {
+            $document = $this->session->document;
+            unset($this->session->document);
+        } else {
+            $document = new Document($this->zdb);
+        }
+        $params = [
+            'page_title'        => _T("Add document"),
+            'action'            => 'add',
+            'mode'              => (($request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest') ? 'ajax' : ''),
+            'document'          => $document,
+            'types'             => $document->getSystemTypes(),
+            'perm_names'        => $document::getPermissionsList(true),
+            'html_editor'   => true,
+        ];
+
+        // display page
+        $this->view->render(
+            $response,
+            'pages/document_form.html.twig',
+            $params
+        );
+        return $response;
+    }
+
+    /**
+     * Add action
+     *
+     * @param Request  $request   PSR Request
+     * @param Response $response  PSR Response
+     * @param ?string  $form_name Form name
+     *
+     * @return Response
+     */
+    public function doAdd(Request $request, Response $response, string $form_name = null): Response
+    {
+        $post = $request->getParsedBody();
+
+        $error_detected = [];
+        $warning_detected = [];
+
+        if (isset($post['cancel'])) {
+            return $response
+                ->withStatus(301)
+                ->withHeader('Location', $this->cancelUri($this->getArgs($request)));
+        }
+
+        $document = new Document($this->zdb);
+
+        try {
+            $document->store($post, $_FILES);
+            $error_detected = $document->getErrors();
+            $warning_detected = $document->getWarnings();
+        } catch (Throwable $e) {
+            $msg = 'An error occurred adding new document.';
+            Analog::log(
+                $msg . ' | ' .
+                $e->getMessage(),
+                Analog::ERROR
+            );
+            if (Galette::isDebugEnabled()) {
+                throw $e;
+            }
+            $error_detected[] = _T('An error occurred adding document :(');
+        }
+
+        //flash messages
+        if (count($error_detected) > 0) {
+            foreach ($error_detected as $error) {
+                $this->flash->addMessage(
+                    'error_detected',
+                    $error
+                );
+            }
+        } else {
+            $this->flash->addMessage(
+                'success_detected',
+                _T('Document has been successfully stored!')
+            );
+        }
+
+        if (count($warning_detected) > 0) {
+            foreach ($warning_detected as $warning) {
+                $this->flash->addMessage(
+                    'warning_detected',
+                    $warning
+                );
+            }
+        }
+
+        //handle redirections
+        if (count($error_detected) > 0) {
+            //something went wrong :'(
+            $this->session->document = $document;
+            return $response
+                ->withStatus(301)
+                ->withHeader(
+                    'Location',
+                    $this->routeparser->urlFor('addDocument')
+                );
+        } else {
+            return $response
+                ->withStatus(301)
+                ->withHeader(
+                    'Location',
+                    $this->routeparser->urlFor('documentsList')
+                );
+        }
+    }
+
+    // /CRUD - Create
+    // CRUD - Read
+
+    /**
+     * List page
+     *
+     * @param Request             $request  PSR Request
+     * @param Response            $response PSR Response
+     * @param string|null         $option   One of 'page' or 'order'
+     * @param integer|string|null $value    Value of the option
+     *
+     * @return Response
+     */
+    public function list(
+        Request $request,
+        Response $response,
+        string $option = null,
+        int|string $value = null,
+    ): Response {
+        $filters = new DocumentsList();
+
+        $document = new Document($this->zdb);
+        $documents = $document->getList();
+
+        //assign pagination variables to the template and add pagination links
+        $filters->setViewPagination($this->routeparser, $this->view);
+
+        $params = [
+            'page_title' => _T("Documents list"),
+            'nb' => count($documents),
+            'documents' => $documents,
+            'filters' => $filters
+        ];
+
+        // display page
+        $this->view->render(
+            $response,
+            'pages/documents_list.html.twig',
+            $params
+        );
+        return $response;
+    }
+
+    /**
+     * List page
+     *
+     * @param Request             $request  PSR Request
+     * @param Response            $response PSR Response
+     * @param string|null         $option   One of 'page' or 'order'
+     * @param integer|string|null $value    Value of the option
+     *
+     * @return Response
+     */
+    public function publicList(
+        Request $request,
+        Response $response,
+        string $option = null,
+        int|string $value = null,
+    ): Response {
+        $document = new Document($this->zdb);
+        $documents = $document->getTypedList();
+
+        $params = [
+            'page_title' => _T("Documents list"),
+            'typed_documents' => $documents
+        ];
+
+        // display page
+        $this->view->render(
+            $response,
+            'pages/documents_public_list.html.twig',
+            $params
+        );
+        return $response;
+    }
+
+    /**
+     * Filtering
+     *
+     * @param Request  $request  PSR Request
+     * @param Response $response PSR Response
+     *
+     * @return Response
+     */
+    public function filter(Request $request, Response $response): Response
+    {
+        //no filtering
+        return $response;
+    }
+
+    /**
+     * Get a document
+     *
+     * @param Request  $request  PSR Request
+     * @param Response $response PSR Response
+     * @param integer  $id       Document ID
+     *
+     * @return Response
+     */
+    public function getDocument(
+        Request $request,
+        Response $response,
+        int $id
+    ): Response {
+        $document = new Document($this->zdb, $id);
+
+        if (!$document->canShow($this->login)) {
+            $this->flash->addMessage(
+                'error_detected',
+                _T("You do not have permission for requested URL.")
+            );
+
+            return $response
+                ->withStatus(301)
+                ->withHeader(
+                    'Location',
+                    $this->routeparser->urlFor(
+                        'slash'
+                    )
+                );
+        }
+
+        if (file_exists($document->getDestDir() . $document->getDocumentFilename())) {
+            $type = File::getMimeType($document->getDestDir() . $document->getDocumentFilename());
+
+            $response = $response->withHeader('Content-Description', 'File Transfer')
+                ->withHeader('Content-Type', $type)
+                ->withHeader('Content-Disposition', 'attachment;filename="' . $document->getDocumentFilename() . '"')
+                ->withHeader('Pragma', 'no-cache')
+                ->withHeader('Content-Transfer-Encoding', 'binary')
+                ->withHeader('Expires', '0')
+                ->withHeader('Cache-Control', 'must-revalidate')
+                ->withHeader('Pragma', 'public');
+
+            $stream = fopen('php://memory', 'r+');
+            fwrite($stream, file_get_contents($document->getDestDir() . $document->getDocumentFilename()));
+            rewind($stream);
+
+            return $response->withBody(new \Slim\Psr7\Stream($stream));
+        } else {
+            Analog::log(
+                'A request has been made to get a document file named `' .
+                $document->getDocumentFilename() . '` that does not exists.',
+                Analog::WARNING
+            );
+
+            $this->flash->addMessage(
+                'error_detected',
+                _T("The file does not exists or cannot be read :(")
+            );
+
+            return $response
+                ->withStatus(301)
+                ->withHeader(
+                    'Location',
+                    $this->routeparser->urlFor(
+                        'slash'
+                    )
+                );
+        }
+    }
+
+    // /CRUD - Read
+    // CRUD - Update
+
+    /**
+     * Edit page
+     *
+     * @param Request  $request  PSR Request
+     * @param Response $response PSR Response
+     * @param integer  $id       Document id
+     *
+     * @return Response
+     */
+    public function edit(Request $request, Response $response, int $id): Response
+    {
+        if (isset($this->session->document)) {
+            $document = $this->session->document;
+            unset($this->session->document);
+        } else {
+            $document = new Document($this->zdb, $id);
+        }
+        $params = [
+            'page_title'        => _T("Edit document"),
+            'action'            => 'edit',
+            'mode'              => (($request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest') ? 'ajax' : ''),
+            'document'          => $document,
+            'types'             => $document->getSystemTypes(),
+            'perm_names'        => $document::getPermissionsList(true),
+            'html_editor'       => true,
+        ];
+
+        // display page
+        $this->view->render(
+            $response,
+            'pages/document_form.html.twig',
+            $params
+        );
+        return $response;
+    }
+
+    /**
+     * Edit action
+     *
+     * @param Request  $request  PSR Request
+     * @param Response $response PSR Response
+     * @param integer  $id       Document id
+     *
+     * @return Response
+     */
+    public function doEdit(Request $request, Response $response, int $id): Response
+    {
+        $post = $request->getParsedBody();
+
+        $error_detected = [];
+        $warning_detected = [];
+
+        if (isset($post['cancel'])) {
+            return $response
+                ->withStatus(301)
+                ->withHeader('Location', $this->cancelUri($this->getArgs($request)));
+        }
+
+        $document = new Document($this->zdb, $id);
+
+        try {
+            $document->store($post, $_FILES);
+            $error_detected = $document->getErrors();
+            $warning_detected = $document->getWarnings();
+        } catch (Throwable $e) {
+            $msg = 'An error occurred adding new document.';
+            Analog::log(
+                $msg . ' | ' .
+                $e->getMessage(),
+                Analog::ERROR
+            );
+            if (Galette::isDebugEnabled()) {
+                throw $e;
+            }
+            $error_detected[] = _T('An error occurred adding document :(');
+        }
+
+        //flash messages
+        if (count($error_detected) > 0) {
+            foreach ($error_detected as $error) {
+                $this->flash->addMessage(
+                    'error_detected',
+                    $error
+                );
+            }
+        } else {
+            $this->flash->addMessage(
+                'success_detected',
+                _T('Document has been successfully stored!')
+            );
+        }
+
+        if (count($warning_detected) > 0) {
+            foreach ($warning_detected as $warning) {
+                $this->flash->addMessage(
+                    'warning_detected',
+                    $warning
+                );
+            }
+        }
+
+        //handle redirections
+        if (count($error_detected) > 0) {
+            //something went wrong :'(
+            $this->session->document = $document;
+            return $response
+                ->withStatus(301)
+                ->withHeader(
+                    'Location',
+                    $this->routeparser->urlFor('addDocument')
+                );
+        } else {
+            return $response
+                ->withStatus(301)
+                ->withHeader(
+                    'Location',
+                    $this->routeparser->urlFor('documentsList')
+                );
+        }
+    }
+
+    // /CRUD - Update
+    // CRUD - Delete
+
+    /**
+     * Get redirection URI
+     *
+     * @param array<string,mixed> $args Route arguments
+     *
+     * @return string
+     */
+    public function redirectUri(array $args): string
+    {
+        return $this->routeparser->urlFor('documentsList');
+    }
+
+    /**
+     * Get form URI
+     *
+     * @param array<string,mixed> $args Route arguments
+     *
+     * @return string
+     */
+    public function formUri(array $args): string
+    {
+        return $this->routeparser->urlFor(
+            'doRemoveDocument',
+            ['id' => $args['id']]
+        );
+    }
+
+    /**
+     * Get confirmation removal page title
+     *
+     * @param array<string,mixed> $args Route arguments
+     *
+     * @return string
+     */
+    public function confirmRemoveTitle(array $args): string
+    {
+        return _T('Delete document');
+    }
+
+    /**
+     * Remove object
+     *
+     * @param array<string,mixed> $args Route arguments
+     * @param array<string,mixed> $post POST values
+     *
+     * @return boolean
+     */
+    protected function doDelete(array $args, array $post): bool
+    {
+        $document = new Document($this->zdb, (int)$post['id']);
+        return $document->remove();
+    }
+
+    // /CRUD - Delete
+    // /CRUD
+}
index 2d6f5151c8ace37845e2f0bdb90c659df8f6aa00..da0b157206300477ed3332e5bc31393b87a39950 100644 (file)
@@ -315,6 +315,13 @@ class Galette
                             'route' => [
                                 'name' => 'charts'
                             ]
+                        ], [
+                            'label' => _T("Documents"),
+                            'title' => _T("Add documents to share related to your association (status, rules of procedure, ...)"),
+                            'route' => [
+                                'name' => 'documentsList',
+                                'aliases' => ['editDocument', 'addDocument']
+                            ]
                         ]
                     ]);
                 }//admin or staff
@@ -474,7 +481,7 @@ class Galette
          * @var Login $login
          * @var Plugins $plugins
          */
-        global $preferences, $login, $plugins;
+        global $preferences, $login, $plugins, $zdb;
 
         $menus = [];
         if ($preferences->showPublicPages($login)) {
@@ -501,6 +508,20 @@ class Galette
                 ]
             ];
 
+            //display documents menu if at least one document is present with current ACLs
+            $document = new \Galette\Entity\Document($zdb);
+            $documents = $document->getList();
+            if ($login->isSuperAdmin() || count($documents)) {
+                $menus['public']['items'][] = [
+                    'label' => _T("Documents"),
+                    'title' => _T("View documents related to your association"),
+                    'route' => [
+                        'name' => 'documentsPublicList'
+                    ],
+                    'icon' => 'file alternate'
+                ];
+            }
+
             foreach (array_keys($plugins->getModules()) as $module_id) {
                 //get plugins public menus entries
                 $plugin_class = $plugins->getClassName($module_id, true);
@@ -527,8 +548,9 @@ class Galette
         /**
          * @var Login $login
          * @var Plugins $plugins
+         * @var Db $zdb
          */
-        global $login, $plugins;
+        global $login, $plugins, $zdb;
 
         $dashboards = [];
 
@@ -598,6 +620,25 @@ class Galette
             );
         }
 
+        //display documents menu if at least one document is present with current ACLs
+        $document = new \Galette\Entity\Document($zdb);
+        $documents = $document->getList();
+        if ($login->isSuperAdmin() || count($documents)) {
+            $dashboards = array_merge(
+                $dashboards,
+                [
+                    [
+                        'label' => _T("Documents"),
+                        'title' => _T("View documents related to your association"),
+                        'route' => [
+                            'name' => 'documentsPublicList'
+                        ],
+                        'icon' => 'dividers'
+                    ]
+                ]
+            );
+        }
+
         if ($login->isAdmin()) {
             $dashboards = array_merge(
                 $dashboards,
@@ -652,8 +693,7 @@ class Galette
                             'args' => ['type' => 'transactions']
                         ],
                         'icon' => 'book'
-                    ],
-
+                    ]
                 ]
             );
         }
diff --git a/galette/lib/Galette/Entity/Document.php b/galette/lib/Galette/Entity/Document.php
new file mode 100644 (file)
index 0000000..f62ff11
--- /dev/null
@@ -0,0 +1,569 @@
+<?php
+
+/**
+ * Copyright © 2003-2024 The Galette Team
+ *
+ * This file is part of Galette (https://galette.eu).
+ *
+ * Galette is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Galette 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Galette. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace Galette\Entity;
+
+use ArrayObject;
+use DateTime;
+use Galette\Core\Authentication;
+use Galette\Core\Login;
+use Galette\Features\I18n;
+use Galette\Features\Permissions;
+use Galette\IO\FileInterface;
+use Galette\IO\FileTrait;
+use Throwable;
+use Galette\Core\Db;
+use Analog\Analog;
+
+/**
+ * Documents
+ *
+ * @author Johan Cwiklinski <johan@x-tnd.be>
+ */
+
+class Document implements FileInterface
+{
+    use I18n;
+    use Permissions;
+    use FileTrait {
+        store as protected trait_store;
+        writeOnDisk as protected trait_writeOnDisk;
+    }
+
+    public const TABLE = 'documents';
+    public const PK = 'id_document';
+
+    public const STATUS = 'status';
+    public const RULES = 'rules';
+    public const ADHESION = 'adhesion';
+    public const MINUTES = 'minutes';
+    public const VOTES = 'votes';
+
+    /** @var Db */
+    private Db $zdb;
+    /** @var int */
+    private int $id;
+    /** @var string */
+    private string $type;
+    /** @var string */
+    private string $filename;
+    /** @var DateTime */
+    private DateTime $creation_date;
+    /** @var string */
+    protected string $store_path = GALETTE_DOCUMENTS_PATH;
+    /** @var ?string */
+    private ?string $comment = null;
+    /** @var array<string> */
+    private array $errors = [];
+
+    /**
+     * Main constructor
+     *
+     * @param Db                                      $zdb  Database instance
+     * @param int|ArrayObject<string,int|string>|null $args Arguments
+     */
+    public function __construct(Db $zdb, int|ArrayObject $args = null)
+    {
+        $this->zdb = $zdb;
+        $this->can_public = true;
+
+        $this->init($this->store_path);
+
+        if (is_int($args)) {
+            $this->load($args);
+        } elseif ($args instanceof ArrayObject) {
+            $this->loadFromRs($args);
+        }
+    }
+
+    /**
+     * Load a document from its identifier
+     *
+     * @param integer $id Identifier
+     *
+     * @return void
+     */
+    private function load(int $id): void
+    {
+        try {
+            $select = $this->zdb->select(self::TABLE);
+            $select->limit(1)->where([self::PK => $id]);
+
+            $results = $this->zdb->execute($select);
+            /** @var ArrayObject<string, int|string> $res */
+            $res = $results->current();
+            $this->loadFromRs($res);
+        } catch (Throwable $e) {
+            Analog::log(
+                'An error occurred loading document #' . $id . "Message:\n" .
+                $e->getMessage(),
+                Analog::ERROR
+            );
+        }
+    }
+
+    /**
+     * Get documents
+     *
+     * @param string|null $type Type to retrieve
+     *
+     * @return array<int,Document>
+     *
+     * @throws Throwable
+     */
+    public static function getList(string $type = null): array
+    {
+        global $zdb, $login;
+
+        try {
+            $select = $zdb->select(self::TABLE);
+
+            if ($type !== null) {
+                $select->where(['type' => $type]);
+            }
+
+            $select->order(self::PK);
+
+            $results = $zdb->execute($select);
+            $documents = [];
+            $access_level = $login->getAccessLevel();
+
+            foreach ($results as $r) {
+                // skip entries according to access control
+                if (
+                    $r->visible == FieldsConfig::NOBODY ||
+                    ($r->visible == FieldsConfig::ADMIN &&
+                        $access_level < Authentication::ACCESS_ADMIN) ||
+                    ($r->visible == FieldsConfig::STAFF &&
+                        $access_level < Authentication::ACCESS_STAFF) ||
+                    ($r->visible == FieldsConfig::MANAGER &&
+                        $access_level < Authentication::ACCESS_MANAGER) ||
+                    (($r->visible == FieldsConfig::USER_READ || $r->visible == FieldsConfig::USER_WRITE) &&
+                        $access_level < Authentication::ACCESS_USER)
+                ) {
+                    continue;
+                }
+
+                $documents[$r->{self::PK}] = new Document($zdb, $r);
+            }
+            return $documents;
+        } catch (Throwable $e) {
+            Analog::log(
+                "An error occurred loading documents. Message:\n" .
+                $e->getMessage(),
+                Analog::ERROR
+            );
+            throw $e;
+        }
+    }
+
+    /**
+     * Get list by type
+     *
+     * @return array<string, array<int, Document>>
+     *
+     * @throws Throwable
+     */
+    public function getTypedList(): array
+    {
+        $list = $this->getList();
+        $sys_types = $this->getSystemTypes(false);
+
+        $typed_list = array_fill_keys($sys_types, []);
+        foreach ($list as $doc_id => $document) {
+            $typed_list[$document->getType()][] = $document;
+        }
+
+        //cleanup: some system types may have no entries
+        foreach ($sys_types as $type) {
+            if (count($typed_list[$type]) == 0) {
+                unset($typed_list[$type]);
+            }
+        }
+
+        return $typed_list;
+    }
+
+    /**
+     * Check if a document can be shown
+     *
+     * @param Login $login Login
+     *
+     * @return boolean
+     */
+    public function canShow(Login $login): bool
+    {
+        $access_level = $login->getAccessLevel();
+
+        switch ($this->getPermission()) {
+            case FieldsConfig::ALL:
+                return true;
+            case FieldsConfig::NOBODY:
+                return false;
+            case FieldsConfig::ADMIN:
+                return $access_level >= Authentication::ACCESS_ADMIN;
+            case FieldsConfig::STAFF:
+                return $access_level >= Authentication::ACCESS_STAFF;
+            case FieldsConfig::MANAGER:
+                return $access_level >= Authentication::ACCESS_MANAGER;
+            case FieldsConfig::USER_WRITE:
+            case FieldsConfig::USER_READ:
+                return $access_level >= Authentication::ACCESS_USER;
+        }
+
+        return false;
+    }
+
+    /**
+     * Load document from a db ResultSet
+     *
+     * @param ArrayObject<string, int|string> $rs ResultSet
+     *
+     * @return void
+     */
+    private function loadFromRs(ArrayObject $rs): void
+    {
+        $this->id = $rs->{self::PK};
+        $this->type = $rs->type;
+        $this->permission = $rs->visible;
+        $this->filename = $rs->filename;
+        $this->comment = $rs->comment;
+        $this->creation_date = new DateTime($rs->creation_date);
+    }
+
+    /**
+     * Store document in database
+     *
+     * @param array<string,mixed> $post  POST data
+     * @param array<string,mixed> $files Files
+     *
+     * @return boolean
+     */
+    public function store(array $post, array $files): bool
+    {
+        $this->setType($post['document_type']);
+        $this->setComment($post['comment']);
+        $this->permission = $post['visible'];
+
+        $handled = $this->handleFiles($files);
+        if ($handled !== true) {
+            $this->errors = $handled;
+            return false;
+        }
+
+        try {
+            $values = [
+                'type' => $this->type,
+                'filename' => $this->filename,
+                'visible' => $this->getPermission(),
+                'comment' => $this->comment,
+            ];
+            if (isset($this->id) && $this->id > 0) {
+                $update = $this->zdb->update(self::TABLE);
+                $update->set($values)->where([self::PK => $this->id]);
+                $this->zdb->execute($update);
+            } else {
+                $values['creation_date'] = date('Y-m-d H:i:s');
+                $insert = $this->zdb->insert(self::TABLE);
+                $insert->values($values);
+                $add = $this->zdb->execute($insert);
+                if (!$add->count() > 0) {
+                    Analog::log('Not stored!', Analog::ERROR);
+                    return false;
+                }
+
+                $this->id = $this->zdb->getLastGeneratedValue($this);
+                if (!in_array($this->type, $this->getSystemTypes(false))) {
+                    $this->addTranslation($this->type);
+                }
+            }
+            return true;
+        } catch (Throwable $e) {
+            $this->removeFile();
+            Analog::log(
+                'An error occurred storing document: ' . $e->getMessage(),
+                Analog::ERROR
+            );
+            throw $e;
+        }
+    }
+
+    /**
+     * Remove document
+     *
+     * @param array<int>|null $ids IDs to remove, default to current id
+     *
+     * @return boolean
+     */
+    public function remove(array $ids = null): bool
+    {
+        if ($ids == null) {
+            $ids[] = $this->id;
+        }
+
+        try {
+            $this->zdb->connection->beginTransaction();
+            $delete = $this->zdb->delete(self::TABLE);
+            $delete->where([self::PK => $ids]);
+            $this->zdb->execute($delete);
+            if (!$this->removeFile()) {
+                throw new \RuntimeException('cannot remove file document from disk');
+            }
+            Analog::log(
+                'Document #' . implode(', #', $ids) . ' deleted successfully.',
+                Analog::INFO
+            );
+
+            $this->zdb->connection->commit();
+            return true;
+        } catch (Throwable $e) {
+            $this->zdb->connection->rollBack();
+            Analog::log(
+                'Unable to delete document #' . implode(', #', $ids) . ' | ' . $e->getMessage(),
+                Analog::ERROR
+            );
+            throw $e;
+        }
+    }
+
+    /**
+     * Remove document file
+     *
+     * @return bool
+     */
+    protected function removeFile(): bool
+    {
+        $file = $this->getDestDir() . $this->getDocumentFilename();
+        if (file_exists($file)) {
+            return unlink($file);
+        }
+
+        Analog::log('File ' . $file . ' does not exist', Analog::WARNING);
+        return false;
+    }
+
+    /**
+     * Get file URL
+     *
+     * @return string
+     */
+    public function getURL(): string
+    {
+        return $this->getDestDir() . $this->getDocumentFileName();
+    }
+
+    /**
+     * Get document ID
+     *
+     * @return ?int
+     */
+    public function getId(): ?int
+    {
+        return $this->id ?? null;
+    }
+
+    /**
+     * Get document file name
+     *
+     * @return string
+     */
+    public function getDocumentFilename(): string
+    {
+        return $this->filename ?? '';
+    }
+
+    /**
+     * Set comment
+     * @param ?string $comment Comment to set
+     *
+     * @return self
+     */
+    public function setComment(?string $comment): self
+    {
+        $this->comment = $comment;
+        return $this;
+    }
+
+    /**
+     * Get comment
+     *
+     * @return ?string
+     */
+    public function getComment(): ?string
+    {
+        return $this->comment;
+    }
+
+    /**
+     * Set type
+     *
+     * @param string $type Type
+     *
+     * @return self
+     */
+    public function setType(string $type): self
+    {
+        $this->type = $type;
+        return $this;
+    }
+
+    /**
+     * Get type
+     *
+     * @return string
+     */
+    public function getType(): string
+    {
+        return $this->type ?? '';
+    }
+
+    /**
+     * Get creation date
+     *
+     * @param boolean $formatted Return formatted date (default) or not
+     *
+     * @return string|DateTime
+     */
+    public function getCreationDate(bool $formatted = true): string|DateTime
+    {
+        if ($formatted) {
+            return $this->creation_date->format(_T('Y-m-d H:i:s'));
+        }
+        return $this->creation_date;
+    }
+
+    /**
+     * Get system social types
+     *
+     * @param boolean $translated Return translated types (default) or not
+     *
+     * @return array<string,string>
+     */
+    public function getSystemTypes(bool $translated = true): array
+    {
+        if ($translated) {
+            $systypes = [
+                self::STATUS => _T('Association status'),
+                self::RULES => _T('Rules of procedure'),
+                self::ADHESION => _T('Adhesion form'),
+                self::MINUTES => _T('Meeting minutes'),
+                self::VOTES => _T('Votes results')
+            ];
+        } else {
+            $systypes = [
+                self::STATUS => 'Association status',
+                self::RULES => 'Rules of procedure',
+                self::ADHESION => 'Adhesion form',
+                self::MINUTES => 'Meeting minutes',
+                self::VOTES => 'Votes results'
+            ];
+        }
+        return $systypes;
+    }
+
+    /**
+     * Get system documents types
+     *
+     * @param string  $type       Document type
+     * @param boolean $translated Return translated types (default) or not
+     *
+     * @return string
+     */
+    public function getSystemType(string $type, bool $translated = true): string
+    {
+        return $this->getSystemTypes($translated)[$type] ?? _T($type);
+    }
+
+    /**
+     * Handle files
+     *
+     * @param array<string,mixed> $files Files sent
+     *
+     * @return array<string>|true
+     */
+    public function handleFiles(array $files): array|bool
+    {
+        $this->errors = [];
+        // document upload
+        if (isset($files['document_file'])) {
+            if ($files['document_file']['error'] === UPLOAD_ERR_OK) {
+                if ($files['document_file']['tmp_name'] != '') {
+                    if (is_uploaded_file($files['document_file']['tmp_name'])) {
+                        $res = $this->trait_store($files['document_file']);
+                        if ($res < 0) {
+                            $this->errors[] = $this->getErrorMessage($res);
+                        } else {
+                            $this->filename = sprintf(
+                                '%s.%s',
+                                $this->name_wo_ext,
+                                $this->extension
+                            );
+                        }
+                    }
+                }
+            } elseif (!isset($this->id)) {
+                Analog::log(
+                    $this->getPhpErrorMessage($files['document_file']['error']),
+                    Analog::WARNING
+                );
+                $this->errors[] = $this->getPhpErrorMessage($files['document_file']['error']);
+            }
+        }
+
+        if (count($this->errors) > 0) {
+            Analog::log(
+                'Some errors has been thew attempting to edit/store a document file' . "\n" .
+                print_r($this->errors, true),
+                Analog::ERROR
+            );
+            return $this->errors;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Get errors
+     *
+     * @return string[]
+     */
+    public function getErrors(): array
+    {
+        return $this->errors;
+    }
+
+    /**
+     * Write file on disk
+     *
+     * @param string $tmpfile Temporary file
+     * @param bool   $ajax    If the file comes from an ajax call (dnd)
+     *
+     * @return bool|int
+     */
+    public function writeOnDisk(string $tmpfile, bool $ajax): bool|int
+    {
+        //remove existing file when updating
+        if (isset($this->id) && $this->id > 0) {
+            $this->removeFile();
+        }
+        return $this->trait_writeOnDisk($tmpfile, $ajax);
+    }
+}
index 14fc5c7fab111a26be05c50c728700c7208d28d4..473f51af201bd4c3bf561476c455b4d11b6f69f9 100644 (file)
@@ -808,7 +808,7 @@ class FieldsConfig
 
     /**
      * Migrate old required fields configuration
-     * Only needeed for 0.7.4 upgrade
+     * Only needed for 0.7.4 upgrade
      * (should have been 0.7.3 - but I missed that.)
      *
      * @return boolean
index 706c8fe35a5b4a44437773aab063c8437ed8d2cd..a164b0ba28405dc742b69491c825de04786b5621 100644 (file)
@@ -36,6 +36,7 @@ use Laminas\Db\Sql\Expression;
 trait Permissions
 {
     protected ?int $permission = null;
+    protected bool $can_public = false;
 
     /* FIXME/ requires PHP 8.2
     public const NOBODY = 0;
@@ -57,6 +58,13 @@ trait Permissions
     {
         $list = [
             FieldsConfig::NOBODY => _T("Inaccessible"),
+        ];
+
+        if ($can_public) {
+            $list += [FieldsConfig::ALL => _T("Public")];
+        }
+
+        $list += [
             FieldsConfig::USER_READ => _T("User, read only"),
             FieldsConfig::USER_WRITE => _T("User, read/write"),
             FieldsConfig::MANAGER => _T("Group manager"),
@@ -64,11 +72,6 @@ trait Permissions
             FieldsConfig::ADMIN => _T("Administrator"),
         ];
 
-        if ($can_public) {
-            $all = [FieldsConfig::ALL => _T("Public")];
-            $list = array_unshift($all);
-        }
-
         return $list;
     }
 
@@ -79,7 +82,7 @@ trait Permissions
      */
     public function getPermissionName(): string
     {
-        $perms = self::getPermissionsList();
+        $perms = self::getPermissionsList($this->can_public);
         return $perms[$this->getPermission()];
     }
 
index 5e027e906d7f6ce5526e3b1bf9e544dd88c17164..48d95a28d60d00ceb55748817d017edb99737854 100644 (file)
@@ -25,7 +25,7 @@ use Galette\Entity\Adherent;
 use Galette\Entity\Social;
 
 /**
- * Replacements feature
+ * Socials feature
  *
  * @author Johan Cwiklinski <johan@x-tnd.be>
  */
diff --git a/galette/lib/Galette/Filters/DocumentsList.php b/galette/lib/Galette/Filters/DocumentsList.php
new file mode 100644 (file)
index 0000000..4aa07ef
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * Copyright © 2003-2024 The Galette Team
+ *
+ * This file is part of Galette (https://galette.eu).
+ *
+ * Galette is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Galette 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Galette. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace Galette\Filters;
+
+use Analog\Analog;
+use Galette\Core\Pagination;
+use Galette\Entity\Group;
+use Galette\Repository\Members;
+
+/**
+ * Documents list filters and paginator
+ *
+ * @author Johan Cwiklinski <johan@x-tnd.be>
+ */
+
+class DocumentsList extends Pagination
+{
+    public const ORDERBY_DATE = 0;
+    public const ORDERBY_TYPE = 1;
+    public const ORDERBY_NAME = 2;
+    public const ORDERBY_ID = 3;
+
+    /**
+     * Returns the field we want to default set order to
+     *
+     * @return int|string
+     */
+    protected function getDefaultOrder(): int|string
+    {
+        return 'creation_date';
+    }
+
+    /**
+     * Return the default direction for ordering
+     *
+     * @return string ASC or DESC
+     */
+    protected function getDefaultDirection(): string
+    {
+        return self::ORDER_DESC;
+    }
+}
diff --git a/galette/templates/default/pages/document_form.html.twig b/galette/templates/default/pages/document_form.html.twig
new file mode 100644 (file)
index 0000000..47c4fb0
--- /dev/null
@@ -0,0 +1,100 @@
+{#
+/**
+ * Copyright © 2003-2024 The Galette Team
+ *
+ * This file is part of Galette (https://galette.eu).
+ *
+ * Galette is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Galette 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Galette. If not, see <http://www.gnu.org/licenses/>.
+ */
+#}
+{% extends (mode == 'ajax') ? "ajax.html.twig" : "page.html.twig" %}
+
+{% block content %}
+    <form action="{% if document.getID() is null %}{{ url_for('doAddDocument') }}{% else %}{{ url_for('editDocument', {'id': document.getID()}) }}{% endif %}" method="post" class="ui form" enctype="multipart/form-data">
+    {% if mode != 'ajax' %}
+        <div class="ui segment" id="general">
+    {% endif %}
+            <div class="field ui items">
+                <label>{{ _T("File:") }}</label>
+    {% if document.getID() is not null %}
+                <a href="{{ url_for('getDocumentFile', {'id': document.getID()}) }}">
+                    {{ document.getDocumentFilename() }}
+                    <i class="external alternate icon" aria-hidden="true"></i>
+                </a>
+    {% endif %}
+                <div class="extra ui basic fitted segment">
+                    <div class="ui file action input">
+                        <input id="document_new" type="file" name="document_file"/>
+                        <label for="document_new" class="ui button{% if callstatic('\\Galette\\Core\\Galette', 'isDemo') %} disabled{% endif %}">
+                            <i class="blue upload icon" aria-hidden="true"></i>
+                            {% if document.getId() != null %}{{ _T("Choose another file") }}{% else %}{{ _T("Choose a file") }}{% endif %}
+                        </label>
+                    </div>
+                </div>
+            </div>
+            <div class="required field">
+                <label>{{ _T("Document type") }}</label>
+                <div id="document_type" class="jsonly search-dropdown documents-dropdown ui input">
+                    <input id="document_type_input" type="hidden" name="document_type" value="{{ document.getType() }}">
+                    <i class="jsonly displaynone dropdown icon" aria-hidden="true"></i>
+                    <div class="jsonly displaynone default text">{{ _T("Choose or enter your own...") }}</div>
+                    <div class="jsonly displaynone menu">
+                        {% for doc_type in document.getSystemTypes(false) %}
+                            <div class="item" data-value="{{ doc_type }}">{{ document.getSystemType(doc_type) }}</div>
+                        {% endfor %}
+                    </div>
+                </div>
+            </div>
+            <div class="required field">
+                <label>{{ _T("Permissions") }}</label>
+                    <select name="visible" id="visible" class="ui dropdown">
+                        {% for key, value in perm_names %}
+                            <option value="{{ key }}"{% if key == document.getPermission() %} selected="selected"{% endif %}>{{ value }}</option>
+                        {% endfor %}
+                    </select>
+            </div>
+            <div class="field">
+                <label for="field_information">{{ _T("Comment") }}</label>
+                <textarea name="comment" id="comment" cols="20" rows="6">{{ document.getComment() }}</textarea>
+                <span class="tip">{{ _T("Extra information displayed along with document.") }}</span>
+            </div>
+        </div>
+    {% if mode != 'ajax' %}
+        <div class="ui basic center aligned segment">
+            <button type="submit" class="ui labeled icon primary button action">
+                <i class="save icon" aria-hidden="true"></i> {{ _T("Save") }}
+            </button>
+            <input type="submit" name="cancel" value="{{ _T("Cancel") }}" class="ui button" />
+    {% endif %}
+            <input type="hidden" name="id" id="id" value="{{ document.id }}"/>
+            {% include 'components/forms/csrf.html.twig' %}
+    {% if mode != 'ajax' %}
+        </div>
+    {% endif %}
+     </form>
+{% endblock %}
+
+{% block javascripts %}
+    <script>
+        $(function() {
+            activateHtmlEditor($('#comment'), true);
+            $('.documents-dropdown').dropdown({
+                allowAdditions: true,
+                onNoResults: function(searchValue) {
+                    $(this).dropdown('set value', searchValue);
+                }
+            });
+        });
+    </script>
+{% endblock %}
diff --git a/galette/templates/default/pages/documents_list.html.twig b/galette/templates/default/pages/documents_list.html.twig
new file mode 100644 (file)
index 0000000..7b3c0a5
--- /dev/null
@@ -0,0 +1,102 @@
+{#
+/**
+ * Copyright © 2003-2024 The Galette Team
+ *
+ * This file is part of Galette (https://galette.eu).
+ *
+ * Galette is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Galette 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Galette. If not, see <http://www.gnu.org/licenses/>.
+ */
+#}
+{% extends 'elements/list.html.twig' %}
+
+{% set form = {
+    'order': {
+        'name': "documentsList",
+    }
+} %}
+
+{% block infoline %}
+    {% set infoline = {
+        'label': _Tn("%count document", "%count documents", nb)|replace({"%count": nb})
+    } %}
+    {{ parent() }}
+{% endblock %}
+
+{% block infoline_actions %}
+    {% if not is_public and (login.isAdmin() or login.isStaff()) %}
+        <a
+            class="ui tiny labeled icon button"
+            href="{{ url_for("addDocument") }}"
+        >
+            <i class="ui user check green icon" aria-hidden="true"></i>
+            {{ _T("Add a document") }}
+        </a>
+    {% endif %}
+{% endblock %}
+
+{% block header %}
+    {% set columns = [
+        {'label': '#'},
+        {'label': _T('Type')},
+        {'label': _T('Visibility')},
+        {'label': _T("Filename")},
+        {'label': _T("Creation date")}
+    ] %}
+
+    {{ parent() }}
+{% endblock %}
+
+{% block body %}
+    {% for document in documents %}
+        <tr class="" id="row_{{ document.id }}">
+            <td data-scope="row">
+                {% if preferences.pref_show_id %}
+                    {{ document.id }}
+                {% else %}
+                    {{ loop.index }}
+                {% endif %}
+            </td>
+            <td data-col-label="{{ _T("Type") }}">{{ document.getType() }}</td>
+            <td data-col-label="{{ _T("Visibility") }}">{{ document.getPermissionName() }}</td>
+            <td data-col-label="{{ _T("Filename") }}">
+                <a href="{{ url_for('getDocumentFile', {'id': document.getID()}) }}">
+                    {{ document.getDocumentFilename() }}
+                </a>
+            </td>
+            <td data-col-label="{{ _T("Creation date") }}">{{ document.getCreationDate() }}</td>
+            {% if mode != 'ajax' %}
+                <td class="actions_row center">
+                    {% if (login.isAdmin() or login.isStaff()) %}
+                        <a
+                                href="{{ url_for("editDocument", {"id": document.getID()}) }}"
+                                class="action"
+                        >
+                            <i class="ui edit icon tooltip" aria-hidden="true"></i>
+                            <span class="ui special popup">{{ _T("Edit document") }}</span>
+                        </a>
+                        <a
+                                href="{{ url_for("removeDocument", {"id": document.getID()}) }}"
+                                class="delete"
+                        >
+                            <i class="ui trash red icon tooltip" aria-hidden="true"></i>
+                            <span class="ui special popup">{{ _T("Delete document") }}</span>
+                        </a>
+                    {% endif %}
+                </td>
+            {% endif %}
+        </tr>
+    {% else %}
+        <tr><td colspan="{% if login.isAdmin() or login.isStaff() %}6{% else %}5{% endif %}" class="emptylist">{{ _T("No document") }}</td></tr>
+    {% endfor %}
+{% endblock %}
diff --git a/galette/templates/default/pages/documents_public_list.html.twig b/galette/templates/default/pages/documents_public_list.html.twig
new file mode 100644 (file)
index 0000000..07a33c6
--- /dev/null
@@ -0,0 +1,72 @@
+{#
+/**
+ * Copyright © 2003-2024 The Galette Team
+ *
+ * This file is part of Galette (https://galette.eu).
+ *
+ * Galette is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Galette 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Galette. If not, see <http://www.gnu.org/licenses/>.
+ */
+#}
+{% extends 'public_page.html.twig' %}
+
+{% macro getSlug(value) -%}
+    {{ value|lower|spaceless|slug|e('html_attr') }}
+{%- endmacro %}
+
+{% block content %}
+    <div class="ui stackable grid">
+        <div class="four wide left attached column">
+            <div class="ui stackable vertical fluid tabular menu tabbed">
+    {% for type, documents in typed_documents %}
+                <a class="{% if loop.index == 1 %}active {% endif %}item" data-tab="{{ _self.getSlug(type) }}">
+                    {{ _T(type) }}
+                    <p class="ui small disabled text">({{ _Tn("%count document", "%count documents", documents|length)|replace({"%count": documents|length}) }})</p>
+                </a>
+    {% endfor %}
+            </div>
+        </div>
+        <div class="twelve wide stretched right attached column">
+    {% for type, documents in typed_documents %}
+            <div class="ui seamless right attached tab segment{% if loop.index == 1 %} active{% endif %}" data-tab="{{ _self.getSlug(type) }}">
+        {% for document in documents %}
+                <div class="ui relaxed divided list">
+                    <div class="item">
+                        <div class="ui tiny image">
+                            <i class="large file middle aligned icon"></i>
+                        </div>
+                        <div class="content">
+                            <a class="ui small header" href="{{ url_for('getDocumentFile', {'id': document.getID()}) }}">
+                                {{ document.getDocumentFilename() }}
+                            </a>
+                            <div class="description">
+                                {% if document.getComment %}{{ document.getComment()|raw }}{% endif %}
+                            </div>
+                            <div class="extra">
+                                <div class="ui right floated meta">
+                                    <span class="date ui small text">
+                                        <i class="clock outline icon"></i>
+                                        {{ document.getCreationDate() }}
+                                    </span>
+                                    <div class="ui label">{{ document.getPermissionName() }}</div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+        {% endfor %}
+            </div>
+    {% endfor %}
+        </div>
+    </div>
+{% endblock %}
index 6f7eadca0b5815f16e4b40f61b6de3300a0206be..6b70b677e9f79d0e8d17ee4077f4198e34a2300a 100644 (file)
@@ -570,7 +570,8 @@ class Db extends TestCase
             'galette_pdfmodels',
             'galette_preferences',
             'galette_searches',
-            'galette_tmplinks'
+            'galette_tmplinks',
+            'galette_documents'
         );
 
         $tables = $this->db->getTables();
index f5202f9957b70a1b06b856c23dd33f765ee405d4..8ab0729357d09c6e208b43038416438919444878 100644 (file)
@@ -271,7 +271,7 @@ class Galette extends GaletteTestCase
         $login->method('isSuperAdmin')->willReturn(true);
 
         $dashboards = \Galette\Core\Galette::getDashboards();
-        $this->assertCount(8, $dashboards);
+        $this->assertCount(9, $dashboards);
 
         $login = $this->getMockBuilder(\Galette\Core\Login::class)
             ->setConstructorArgs(array($db, new \Galette\Core\I18n()))
diff --git a/tests/Galette/Entity/tests/units/Document.php b/tests/Galette/Entity/tests/units/Document.php
new file mode 100644 (file)
index 0000000..b0ae7cb
--- /dev/null
@@ -0,0 +1,201 @@
+<?php
+
+/**
+ * Copyright © 2003-2024 The Galette Team
+ *
+ * This file is part of Galette (https://galette.eu).
+ *
+ * Galette is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Galette 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Galette. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace Galette\Entity\test\units;
+
+use Galette\GaletteTestCase;
+
+/**
+ * Status tests
+ *
+ * @author Johan Cwiklinski <johan@x-tnd.be>
+ */
+class Document extends GaletteTestCase
+{
+    protected int $seed = 20240312213127;
+
+    /**
+     * Tear down tests
+     *
+     * @return void
+     */
+    public function tearDown(): void
+    {
+        parent::tearDown();
+
+        $this->deleteDocuments();
+
+        //drop dynamic translations
+        $delete = $this->zdb->delete(\Galette\Core\L10n::TABLE);
+        $this->zdb->execute($delete);
+    }
+
+    /**
+     * Delete documents
+     *
+     * @return void
+     */
+    private function deleteDocuments(): void
+    {
+        $delete = $this->zdb->delete(\Galette\Entity\Document::TABLE);
+        $this->zdb->execute($delete);
+    }
+
+    /**
+     * Test document object
+     *
+     * @return void
+     */
+    public function testObject(): void
+    {
+        $document = new \Galette\Entity\Document($this->zdb);
+
+        //getters only
+        $this->assertSame('', $document->getDocumentFilename());
+        $this->assertSame($document->getDestDir(), $document->getURL());
+        $this->assertNull($document->getID());
+
+        //setters and getters
+        $this->assertSame('', $document->getType());
+        $this->assertInstanceOf(\Galette\Entity\Document::class, $document->setType('mytype'));
+        $this->assertSame('mytype', $document->getType());
+
+        $this->assertNull($document->getComment());
+        $this->assertInstanceOf(\Galette\Entity\Document::class, $document->setComment('any comment'));
+        $this->assertSame('any comment', $document->getComment());
+    }
+
+    /**
+     * Test document "system" types
+     *
+     * @return void
+     */
+    public function testGetSystemTypes(): void
+    {
+        $document = new \Galette\Entity\Document($this->zdb);
+        $this->assertCount(5, $document->getSystemTypes());
+    }
+
+    //FIXME: not possible to test real document, since all relies on a file upload...
+
+    /**
+     * Get mocked document instance
+     *
+     * @return \Galette\Entity\Document
+     */
+    private function getDocumentInstance(): \Galette\Entity\Document
+    {
+        $document = $this->getMockBuilder(\Galette\Entity\Document::class)
+            ->setConstructorArgs(array($this->zdb))
+            ->onlyMethods(array('handleFiles'))
+            ->getMock();
+
+        $document->method('handleFiles')
+            ->willReturnCallback(
+                function (array $files) use ($document) {
+                    $reflection = new \ReflectionClass(\Galette\Entity\Document::class);
+                    $reflection_property = $reflection->getProperty('filename');
+                    $reflection_property->setAccessible(true);
+                    $reflection_property->setValue($document, $files['document_file']['name']);
+
+                    return true;
+                }
+            );
+        return $document;
+    }
+
+    /**
+     * Test getList
+     *
+     * @return void
+     */
+    public function testGetList(): void
+    {
+        $document = $this->getDocumentInstance();
+
+        // no document yet, list is empty
+        $this->assertSame([], $document->getList());
+
+        $_FILES['document_file'] = [
+            'error' => UPLOAD_ERR_OK,
+            'name'      => 'status.pdf',
+            'tmp_name'  => '/tmp/status.pdf',
+            'size'      => 2048
+        ];
+        $post = [
+            'document_type' => \Galette\Entity\Document::STATUS,
+            'comment' => 'Status of the association',
+            'visible' => \Galette\Entity\FieldsConfig::ALL
+        ];
+
+        $this->assertTrue($document->store($post, $_FILES));
+
+        //test list
+        $list = $document->getList();
+        $this->assertCount(1, $list);
+
+        $entry = array_pop($list);
+        $this->assertSame('status.pdf', $entry->getDocumentFilename());
+        $this->assertSame(\Galette\Entity\Document::STATUS, $entry->getType());
+        $this->assertSame('Status of the association', $entry->getComment());
+        $this->assertSame(\Galette\Entity\FieldsConfig::ALL, $entry->getPermission());
+        $this->assertSame('Public', $entry->getPermissionName());
+
+        //test list by type (for public pages)
+        $tlist = $document->getTypedList();
+        $this->assertCount(1, $tlist);
+        $this->assertArrayHasKey(\Galette\Entity\Document::STATUS, $tlist);
+        $this->assertCount(1, $tlist[\Galette\Entity\Document::STATUS]);
+
+        //"upload" another document
+        $document = $this->getDocumentInstance();
+        $_FILES['document_file'] = [
+            'error' => UPLOAD_ERR_OK,
+            'name'      => 'afile.pdf',
+            'tmp_name'  => '/tmp/afile.pdf',
+            'size'      => 4096
+        ];
+        $post = [
+            'document_type' => 'An other document type',
+            'comment' => '',
+            'visible' => \Galette\Entity\FieldsConfig::ADMIN
+        ];
+
+        $this->assertTrue($document->store($post, $_FILES));
+
+        //test list - not authenticated
+        $list = $document->getList();
+        $this->assertCount(1, $list);
+
+        //test list - authenticated
+        $this->logSuperAdmin();
+        $list = $document->getList();
+        $this->assertCount(2, $list);
+
+        //test list by type (for public pages)
+        $tlist = $document->getTypedList();
+        $this->assertCount(2, $tlist);
+        $this->assertArrayHasKey(\Galette\Entity\Document::STATUS, $tlist);
+        $this->assertArrayHasKey('An other document type', $tlist);
+        $this->assertCount(1, $tlist[\Galette\Entity\Document::STATUS]);
+        $this->assertCount(1, $tlist['An other document type']);
+    }
+}
index 65fe446f3ca6dc19e4a44f6a515490258217593e..5a4894e23df26bc4ab9df16241e6070d6ce07b41 100644 (file)
       Vertical
   ---------------*/
 
-  .ui.vertical.menu {
+  .ui.vertical.menu:not(.tabular) {
     display: block;
     flex-direction: column;
     background: @verticalBackground;
     box-shadow: @verticalBoxShadow;
   }
-  .ui.vertical.menu {
+  .ui.vertical.menu:not(.tabular) {
     border: @border;
   }
-  .ui.vertical.menu div.item .ui.button {
+  .ui.vertical.menu:not(.tabular) div.item .ui.button {
     width: 100%;
     border: @border;
     margin: 0 0 .4rem 0;
     box-shadow: none;
   }
-  .ui.vertical.menu div.item .item.button::before {
+  .ui.vertical.menu:not(.tabular) div.item .item.button::before {
     content: none;
   }
   .ui.vertical.accordion.menu {