From 1c97d4e573b485e55c0d7c69778d287d47a9cc25 Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski Date: Thu, 24 Sep 2026 07:16:52 +0200 Subject: [PATCH 1/9] Store preferences in core preferences The 17 lend_parameters become pref_objectslend_* entries declared to core, and the upgrade script moves stored values then drops the table. The preferences page only reads declared names, uses toggles, and now actually saves the contribution type. --- _define.php | 2 +- .../Controllers/Crud/CategoriesController.php | 6 +- .../Controllers/Crud/ObjectsController.php | 22 +- .../Controllers/ImagesController.php | 6 +- .../Controllers/MainController.php | 54 +-- .../Controllers/PdfController.php | 6 +- lib/GaletteObjectsLend/Entity/Picture.php | 19 +- lib/GaletteObjectsLend/Entity/Preferences.php | 312 ------------------ .../Filters/ObjectsList.php | 14 +- lib/GaletteObjectsLend/IO/PdfObject.php | 28 +- lib/GaletteObjectsLend/IO/PdfObjects.php | 4 +- lib/GaletteObjectsLend/LendPreferences.php | 223 +++++++++++++ lib/GaletteObjectsLend/LendService.php | 15 +- .../PluginGaletteObjectslend.php | 15 +- .../Repository/Categories.php | 4 +- lib/GaletteObjectsLend/Repository/Objects.php | 4 +- phpstan-baseline.neon | 30 -- scripts/mysql.sql | 101 ------ scripts/pgsql.sql | 111 ------- scripts/upgrade-to-1.1-mysql.sql | 24 ++ scripts/upgrade-to-1.1-pgsql.sql | 25 ++ templates/default/objects_edit.html.twig | 2 +- templates/default/objects_list.html.twig | 34 +- templates/default/preferences.html.twig | 259 +++------------ templates/default/take_object.html.twig | 12 +- .../tests/units/MainController.php | 137 ++++++++ .../tests/units/ObjectsController.php | 31 +- .../Entity/tests/units/LendObject.php | 3 +- .../Entity/tests/units/Preferences.php | 118 ------- .../Repository/tests/units/Objects.php | 16 +- .../tests/units/LendPreferences.php | 76 +++++ .../tests/units/LendService.php | 31 +- 32 files changed, 702 insertions(+), 1042 deletions(-) delete mode 100644 lib/GaletteObjectsLend/Entity/Preferences.php create mode 100644 lib/GaletteObjectsLend/LendPreferences.php create mode 100644 scripts/upgrade-to-1.1-mysql.sql create mode 100644 scripts/upgrade-to-1.1-pgsql.sql create mode 100644 tests/GaletteObjectsLend/Controllers/tests/units/MainController.php delete mode 100644 tests/GaletteObjectsLend/Entity/tests/units/Preferences.php create mode 100644 tests/GaletteObjectsLend/tests/units/LendPreferences.php diff --git a/_define.php b/_define.php index 82914db7..66e76c88 100644 --- a/_define.php +++ b/_define.php @@ -56,5 +56,5 @@ 'objectslend_object_dotake' => 'member', 'objectslend_object_doreturn' => 'member' ], - dbver: 1.00 + dbver: 1.1 ); diff --git a/lib/GaletteObjectsLend/Controllers/Crud/CategoriesController.php b/lib/GaletteObjectsLend/Controllers/Crud/CategoriesController.php index fdb274d5..c647482c 100644 --- a/lib/GaletteObjectsLend/Controllers/Crud/CategoriesController.php +++ b/lib/GaletteObjectsLend/Controllers/Crud/CategoriesController.php @@ -15,7 +15,7 @@ use GaletteObjectsLend\Entity\CategoryPicture; use GaletteObjectsLend\Entity\LendCategory; use GaletteObjectsLend\Entity\LendStatus; -use GaletteObjectsLend\Entity\Preferences; +use GaletteObjectsLend\LendPreferences; use GaletteObjectsLend\Filters\CategoriesList; use GaletteObjectsLend\Repository\Categories; use Slim\Psr7\Request; @@ -79,7 +79,7 @@ protected function getListView(Pagination $filters): array 'page_title' => _T("Categories list", "objectslend"), 'categories' => $list, 'nb_categories' => count($list), - 'olendsprefs' => new Preferences($this->zdb), + 'olendsprefs' => new LendPreferences($this->preferences), 'time' => time() ] ]; @@ -126,7 +126,7 @@ protected function getEditView(LendCategory|LendStatus $entity, string $action): : _T("New category", "objectslend"), 'category' => $entity, 'time' => time(), - 'olendsprefs' => new Preferences($this->zdb), + 'olendsprefs' => new LendPreferences($this->preferences), 'picture' => new CategoryPicture($entity->getId()) ] ]; diff --git a/lib/GaletteObjectsLend/Controllers/Crud/ObjectsController.php b/lib/GaletteObjectsLend/Controllers/Crud/ObjectsController.php index 54be7a61..60f31392 100644 --- a/lib/GaletteObjectsLend/Controllers/Crud/ObjectsController.php +++ b/lib/GaletteObjectsLend/Controllers/Crud/ObjectsController.php @@ -21,7 +21,7 @@ use GaletteObjectsLend\Repository\Status; use GaletteObjectsLend\Entity\LendObject; use GaletteObjectsLend\Entity\LendRent; -use GaletteObjectsLend\Entity\Preferences; +use GaletteObjectsLend\LendPreferences; use GaletteObjectsLend\LendException; use GaletteObjectsLend\LendService; use Galette\Controllers\Crud\AbstractPluginController; @@ -126,7 +126,7 @@ public function list(Request $request, Response $response, ?string $option = nul } } - $lendsprefs = new Preferences($this->zdb); + $lendsprefs = new LendPreferences($this->preferences); $objects = new Objects($this->zdb, $this->preferences, $this->login, $lendsprefs, $filters); $list = $objects->getObjectsList(true); @@ -156,7 +156,7 @@ public function list(Request $request, Response $response, ?string $option = nul 'objects' => $list, 'nb_objects' => count($list), 'filters' => $filters, - 'lendsprefs' => $lendsprefs->getPreferences(), + 'lendsprefs' => $lendsprefs->toArray(), 'olendsprefs' => $lendsprefs, 'time' => time(), 'module_id' => $this->getModuleId(), @@ -314,14 +314,14 @@ public function edit(Request $request, Response $response, ?int $id = null, stri $statuses = new Status($this->zdb, $this->preferences, $this->login, $sfilter); $slist = $statuses->getStatusList(true); - $lendsprefs = new Preferences($this->zdb); + $lendsprefs = new LendPreferences($this->preferences); $params = [ 'page_title' => $title, 'object' => $object, 'rents' => $object->getId() !== null ? (new Rents($this->zdb))->getForObject($object->getId()) : [], 'time' => time(), 'action' => $action, - 'lendsprefs' => $lendsprefs->getPreferences(), + 'lendsprefs' => $lendsprefs->toArray(), 'olendsprefs' => $lendsprefs, 'categories' => $categories_list, 'statuses' => $slist, @@ -592,7 +592,7 @@ public function doClone(Request $request, Response $response, int $id): Response */ public function lend(Request $request, Response $response, string $action, int $id): Response { - $lendsprefs = new Preferences($this->zdb); + $lendsprefs = new LendPreferences($this->preferences); $params = [ 'page_title' => ( @@ -604,7 +604,7 @@ public function lend(Request $request, Response $response, string $action, int $ 'statuses' => ($action == 'take' ? (new Status($this->zdb, $this->preferences, $this->login))->getActiveTakeAwayStatuses() : (new Status($this->zdb, $this->preferences, $this->login))->getActiveStockStatuses()), - 'lendsprefs' => $lendsprefs->getPreferences(), + 'lendsprefs' => $lendsprefs->toArray(), 'olendsprefs' => $lendsprefs, 'ajax' => $this->isAjax($request), 'takeorgive' => $action, @@ -826,11 +826,11 @@ private function lendResponse(Request $request, Response $response): Response /** * Get lend service * - * @param ?Preferences $lendsprefs Plugin preferences, loaded if not provided + * @param ?LendPreferences $lendsprefs Plugin preferences, loaded if not provided */ - private function getLendService(?Preferences $lendsprefs = null): LendService + private function getLendService(?LendPreferences $lendsprefs = null): LendService { - return new LendService($this->zdb, $this->preferences, $this->login, $lendsprefs ?? new Preferences($this->zdb)); + return new LendService($this->zdb, $this->preferences, $this->login, $lendsprefs ?? new LendPreferences($this->preferences)); } // /CRUD - Update @@ -915,7 +915,7 @@ public function confirmRemoveTitle(array $args): string protected function doDelete(array $args, array $post): bool { $filters = $this->getFilters(); - $lendsprefs = new Preferences($this->zdb); + $lendsprefs = new LendPreferences($this->preferences); $objects = new Objects($this->zdb, $this->preferences, $this->login, $lendsprefs, $filters); if (!is_array($post['id'])) { diff --git a/lib/GaletteObjectsLend/Controllers/ImagesController.php b/lib/GaletteObjectsLend/Controllers/ImagesController.php index e7349b0e..ff79bdc3 100644 --- a/lib/GaletteObjectsLend/Controllers/ImagesController.php +++ b/lib/GaletteObjectsLend/Controllers/ImagesController.php @@ -11,7 +11,7 @@ namespace GaletteObjectsLend\Controllers; use Galette\Controllers\ImagesController as GImagesController; -use GaletteObjectsLend\Entity\Preferences; +use GaletteObjectsLend\LendPreferences; use Slim\Psr7\Request; use Slim\Psr7\Response; @@ -23,7 +23,7 @@ class ImagesController extends GImagesController { - private Preferences $lendsprefs; + private LendPreferences $lendsprefs; /** * Objects lends category or object route @@ -40,7 +40,7 @@ public function lendPicture(Request $request, Response $response, string $type, . ($type == 'category' ? 'CategoryPicture' : 'ObjectPicture'); $picture = new $class($id); - $this->lendsprefs = new Preferences($this->zdb); + $this->lendsprefs = new LendPreferences($this->preferences); $thumb = false; if (!$this->lendsprefs->showFullsize() || $mode == 'thumbnail') { //force thumbnail display from preferences diff --git a/lib/GaletteObjectsLend/Controllers/MainController.php b/lib/GaletteObjectsLend/Controllers/MainController.php index 2c903033..bb5e042c 100644 --- a/lib/GaletteObjectsLend/Controllers/MainController.php +++ b/lib/GaletteObjectsLend/Controllers/MainController.php @@ -13,7 +13,7 @@ use DI\Attribute\Inject; use Galette\Controllers\AbstractPluginController; use Galette\Entity\ContributionsTypes; -use GaletteObjectsLend\Entity\Preferences; +use GaletteObjectsLend\LendPreferences; use Slim\Psr7\Request; use Slim\Psr7\Response; @@ -39,25 +39,12 @@ class MainController extends AbstractPluginController */ public function preferences(Request $request, Response $response): Response { - if ($this->session->objectslend_preferences !== null) { - $lendsprefs = $this->session->objectslend_preferences; - $this->session->objectslend_preferences = null; - } else { - $lendsprefs = new Preferences($this->zdb); - } - $ctypes = new ContributionsTypes($this->zdb); - $types_list = $ctypes->getList(); - $ctypes_params = [0 => _T("Choose a contribution type", "objectslend")]; - foreach ($types_list as $id => $type) { - $ctypes_params[$id] = $type['label']; - } $params = [ 'page_title' => _T('ObjectsLend preferences', 'objectslend'), 'type_cotis_options' => $ctypes->getList(), - 'ctypes' => $ctypes_params, - 'lendsprefs' => $lendsprefs->getPreferences() + 'lendsprefs' => (new LendPreferences($this->preferences))->toArray() ]; // display page @@ -70,7 +57,10 @@ public function preferences(Request $request, Response $response): Response } /** - * Objects lends preferences + * Store objects lends preferences + * + * Only declared preferences are read from the request. A yes/no one + * missing from it is an unchecked box, and is set off. * * @param Request $request PSR Request * @param Response $response PSR Response @@ -78,26 +68,38 @@ public function preferences(Request $request, Response $response): Response public function storePreferences(Request $request, Response $response): Response { $post = $request->getParsedBody(); - $lendsprefs = new Preferences($this->zdb); + $booleans = LendPreferences::getBooleans(); + + $stored = true; + $errors = []; + foreach (array_keys(LendPreferences::getSchema()) as $name) { + if (isset($booleans[$name])) { + $value = (int)isset($post[$name]); + } elseif (isset($post[$name])) { + $value = trim((string)$post[$name]); + } else { + continue; + } + + if (!$this->preferences->setValue($name, $value, $this->login)) { + $stored = false; + $errors = array_merge($errors, $this->preferences->getErrors()); + } + } - $error_detected = []; - if ($lendsprefs->store($post)) { + if ($stored) { $this->flash->addMessage( 'success_detected', _T("Preferences have been successfully stored!", "objectslend") ); } else { - $this->session->objectslend_preferences = $lendsprefs; - foreach ($error_detected as $error) { - $this->flash->addMessage( - 'error_detected', - $error - ); + foreach (array_unique($errors) as $error) { + $this->flash->addMessage('error_detected', $error); } } return $response - ->withStatus(301) + ->withStatus(302) ->withHeader( 'Location', $this->routeparser->urlFor('objectslend_preferences') diff --git a/lib/GaletteObjectsLend/Controllers/PdfController.php b/lib/GaletteObjectsLend/Controllers/PdfController.php index b5b8b7ac..d835a8a2 100644 --- a/lib/GaletteObjectsLend/Controllers/PdfController.php +++ b/lib/GaletteObjectsLend/Controllers/PdfController.php @@ -12,7 +12,7 @@ use Galette\Controllers\PdfController as GPdfController; use GaletteObjectsLend\Controllers\Crud\ObjectsController; -use GaletteObjectsLend\Entity\Preferences; +use GaletteObjectsLend\LendPreferences; use GaletteObjectsLend\Filters\ObjectsList; use GaletteObjectsLend\Repository\Objects; use GaletteObjectsLend\IO\PdfObject; @@ -37,7 +37,7 @@ class PdfController extends GPdfController */ public function printObject(Request $request, Response $response, int $id): Response { - $lendsprefs = new Preferences($this->zdb); + $lendsprefs = new LendPreferences($this->preferences); $object = (new Objects($this->zdb, $this->preferences, $this->login, $lendsprefs))->getWithCurrentRent($id); $pdf = new PdfObject( @@ -57,7 +57,7 @@ public function printObject(Request $request, Response $response, int $id): Resp */ public function printObjects(Request $request, Response $response): Response { - $lendsprefs = new Preferences($this->zdb); + $lendsprefs = new LendPreferences($this->preferences); $filters = clone ($this->session->{$this->getFilterName(ObjectsController::getDefaultFilterName())} ?? new ObjectsList()); diff --git a/lib/GaletteObjectsLend/Entity/Picture.php b/lib/GaletteObjectsLend/Entity/Picture.php index 35c1d4d1..fd926340 100644 --- a/lib/GaletteObjectsLend/Entity/Picture.php +++ b/lib/GaletteObjectsLend/Entity/Picture.php @@ -11,6 +11,7 @@ namespace GaletteObjectsLend\Entity; use Analog\Analog; +use GaletteObjectsLend\LendPreferences; use Psr\Http\Message\UploadedFileInterface; use Slim\Psr7\Response; use Slim\Psr7\Stream; @@ -82,10 +83,10 @@ protected function getDefaultPicture(): void /** * Display a thumbnail image, create it if necessary * - * @param Response $response Response - * @param Preferences $prefs Preferences instance + * @param Response $response Response + * @param LendPreferences $prefs Plugin preferences */ - public function displayThumb(Response $response, Preferences $prefs): Response + public function displayThumb(Response $response, LendPreferences $prefs): Response { $this->setThumbSizes($prefs); $response = $response->withHeader('Content-Type', $this->mime) @@ -269,9 +270,9 @@ public function getThumbPath(): string * * Should override Picture::setSize(), but this one is private :/ * - * @param Preferences $prefs Preferences instance + * @param LendPreferences $prefs Plugin preferences */ - private function setThumbSizes(Preferences $prefs): void + private function setThumbSizes(LendPreferences $prefs): void { $thumb = $this->getThumbPath(); $this->thumb_max_width = $prefs->getThumbWidth(); @@ -313,11 +314,11 @@ private function setThumbSizes(Preferences $prefs): void /** * Returns current thumbnail optimal height * - * @param Preferences $prefs Preferences instance + * @param LendPreferences $prefs Plugin preferences * * @return int optimal height */ - public function getOptimalThumbHeight(Preferences $prefs): int + public function getOptimalThumbHeight(LendPreferences $prefs): int { if (!isset($this->thumb_optimal_height)) { $this->setThumbSizes($prefs); @@ -328,11 +329,11 @@ public function getOptimalThumbHeight(Preferences $prefs): int /** * Returns current thumbnail optimal width * - * @param Preferences $prefs Preferences instance + * @param LendPreferences $prefs Plugin preferences * * @return int optimal width */ - public function getOptimalThumbWidth(Preferences $prefs): int + public function getOptimalThumbWidth(LendPreferences $prefs): int { if (!isset($this->thumb_optimal_width)) { $this->setThumbSizes($prefs); diff --git a/lib/GaletteObjectsLend/Entity/Preferences.php b/lib/GaletteObjectsLend/Entity/Preferences.php deleted file mode 100644 index 22080fd8..00000000 --- a/lib/GaletteObjectsLend/Entity/Preferences.php +++ /dev/null @@ -1,312 +0,0 @@ - - * @author Johan Cwiklinski - * - * @property bool $ENABLE_MEMBER_RENT_OBJECT - * @property bool $AUTO_GENERATE_CONTRIBUTION - * @property int $GENERATED_CONTRIBUTION_TYPE_ID - * @property string $GENERATED_CONTRIB_INFO_TEXT - * @property bool $VIEW_CATEGORY - * @property bool $VIEW_SERIAL - * @property bool $VIEW_THUMBNAIL - * @property bool $VIEW_DESCRIPTION - * @property bool $VIEW_PRICE - * @property bool $VIEW_DIMENSION - * @property bool $VIEW_WEIGHT - * @property bool $VIEW_LEND_PRICE - * @property bool $VIEW_DATE_FORECAST - * @property bool $VIEW_LIST_PRICE_SUM - * @property int $THUMB_MAX_WIDTH - * @property int $THUMB_MAX_HEIGHT - */ -class Preferences -{ - public const string TABLE = 'parameters'; - public const string PK = 'code'; - - private Db $zdb; - /** @var array */ - private array $prefs = []; - - /** - * Show categories at the top of the objects list - * Valeur : 0 = false / 1 = true - */ - public const string PARAM_VIEW_CATEGORY = 'VIEW_CATEGORY'; - - /** - * Show serial number column - * Valeur : 0 = false / 1 = true - */ - public const string PARAM_VIEW_SERIAL = 'VIEW_SERIAL'; - - /** - * Show thumbnail column - * Valeur : 0 = false / 1 = true - */ - public const string PARAM_VIEW_THUMBNAIL = 'VIEW_THUMBNAIL'; - - /** - * Show description column - * Valeur : 0 = false / 1 = true - */ - public const string PARAM_VIEW_DESCRIPTION = 'VIEW_DESCRIPTION'; - - /** - * Show price column - * Valeur : 0 = false / 1 = true - */ - public const string PARAM_VIEW_PRICE = 'VIEW_PRICE'; - - /** - * Show dimensions column - * Valeur : 0 = false / 1 = true - */ - public const string PARAM_VIEW_DIMENSION = 'VIEW_DIMENSION'; - - /** - * Show weight column - * Valeur : 0 = false / 1 = true - */ - public const string PARAM_VIEW_WEIGHT = 'VIEW_WEIGHT'; - - /** - * Show rent price column - * Valeur : 0 = false / 1 = true - */ - public const string PARAM_VIEW_LEND_PRICE = 'VIEW_LEND_PRICE'; - - /** - * Show previsional return date column - * Valeur : 0 = false / 1 = true - */ - public const string PARAM_VIEW_DATE_FORECAST = 'VIEW_DATE_FORECAST'; - - /** - * Show the sum of prices on the list of objects - * Valeur : 0 = false / 1 = true - */ - public const string PARAM_VIEW_LIST_PRICE_SUM = 'VIEW_LIST_PRICE_SUM'; - - /** - * Maximum width of a thumbnail (applied to objects/categories) - * Valeur : largeur en pixels - */ - public const string PARAM_THUMB_MAX_WIDTH = 'THUMB_MAX_WIDTH'; - - /** - * Maximum height of a thumbnail (applied to objects/categories) - * Valeur : largeur en pixels - */ - public const string PARAM_THUMB_MAX_HEIGHT = 'THUMB_MAX_HEIGHT'; - - /** - * Generate automatically a contribution when an object is rented - * Valeur : 0 = false / 1 = true - */ - public const string PARAM_AUTO_GENERATE_CONTRIBUTION = 'AUTO_GENERATE_CONTRIBUTION'; - - /** - * Contribution ID to generate when an object is rented - * Valeur : ID du type de contribution - */ - public const string PARAM_GENERATED_CONTRIBUTION_TYPE_ID = 'GENERATED_CONTRIBUTION_TYPE_ID'; - - /** - * Text for the contribution - * Valeur : texte d'info à mettre avec des placeholders à remplacer - */ - public const string PARAM_GENERATED_CONTRIB_INFO_TEXT = 'GENERATED_CONTRIB_INFO_TEXT'; - - /** - * Allow non staff members to rent objects - * Valeur : 0 = false / 1 = true - */ - public const string PARAM_ENABLE_MEMBER_RENT_OBJECT = 'ENABLE_MEMBER_RENT_OBJECT'; - - /** - * Default constructor - * - * @param Db $zdb Db instance - * @param bool $load Automatically load preferences on load - * - * @return void - */ - public function __construct(Db $zdb, bool $load = true) - { - $this->zdb = $zdb; - if ($load) { - $this->load(); - } - } - - /** - * Get preferences - * - * @return array - */ - public function getPreferences(): array - { - if (count($this->prefs) == 0) { - $this->load(); - } - return $this->prefs; - } - - /** - * Global getter method - * - * @param string $name name of the property we want to retrieve - * - * @return mixed the called property - */ - public function __get(string $name): mixed - { - if (isset($this->prefs[$name])) { - return $this->prefs[$name]; - } - - $msg = __CLASS__ . '::' . $name . ' is not set'; - Analog::log( - $msg, - Analog::INFO - ); - throw new \RuntimeException($msg); - } - - /** - * Store preferences - * - * @param array $data Posted data - */ - public function store(array $data): bool - { - foreach ($data as $key => $value) { - $this->prefs[$key] = $value; - } - - $need_transaction = !$this->zdb->inTransaction(); - try { - if ($need_transaction) { - $this->zdb->beginTransaction(); - } - $update = $this->zdb->update(LEND_PREFIX . self::TABLE); - $update->set( - [ - 'value_text' => $data['GENERATED_CONTRIB_INFO_TEXT'], - 'date_modification' => date('Y-m-d H:i:s') - ] - )->where->equalTo(self::PK, 'GENERATED_CONTRIB_INFO_TEXT'); - $this->zdb->execute($update); - - $update = $this->zdb->update(LEND_PREFIX . self::TABLE); - $update->set( - [ - 'value_numeric' => ':value_numeric', - 'date_modification' => ':date_modification' - ] - )->where->equalTo(self::PK, ':' . self::PK); - $stmt = $this->zdb->sql->prepareStatementForSqlObject($update); - - unset($data['GENERATED_CONTRIB_INFO_TEXT']); - foreach ($data as $key => $value) { - $stmt->execute( - [ - 'value_numeric' => $value, - 'date_modification' => date('Y-m-d H:i:s'), - self::PK => $key - ] - ); - } - - if ($need_transaction) { - $this->zdb->commit(); - } - return true; - } catch (\Exception $e) { - if ($need_transaction) { - $this->zdb->rollback(); - } - throw $e; - } - } - - /** - * Load current preferences from database. - */ - public function load(): bool - { - $this->prefs = []; - - try { - $result = $this->zdb->selectAll(LEND_PREFIX . self::TABLE); - foreach ($result as $pref) { - $pk_field = self::PK; - $value_field = 'value_numeric'; - if ($pref->is_text == '1') { - $value_field = 'value_text'; - } - $this->prefs[$pref->$pk_field] = $pref->$value_field; - } - return true; - } catch (\Exception $e) { - Analog::log( - 'ObjectsLend Preferences cannot be loaded. Plugin should not work without ' - . 'it. Exiting. ' . $e->getMessage(), - Analog::URGENT - ); - return false; - } - } - - /** - * Get thumbnail maximum with - */ - public function getThumbWidth(): int - { - return (int)$this->prefs['THUMB_MAX_WIDTH']; - } - - /** - * Get thumbnail maximum height - */ - public function getThumbHeight(): int - { - return (int)$this->prefs['THUMB_MAX_HEIGHT']; - } - - /** - * Whether to display images (as thumbnails) in lists - */ - public function imagesInLists(): bool - { - return (bool)$this->prefs['VIEW_THUMBNAIL']; - } - - /** - * Shall we show fullsize images? - * - * Would appear editing object, and on thumbnails click - */ - public function showFullsize(): bool - { - return (bool)$this->prefs['VIEW_FULLSIZE']; - } -} diff --git a/lib/GaletteObjectsLend/Filters/ObjectsList.php b/lib/GaletteObjectsLend/Filters/ObjectsList.php index bbc0967f..ef935ac8 100644 --- a/lib/GaletteObjectsLend/Filters/ObjectsList.php +++ b/lib/GaletteObjectsLend/Filters/ObjectsList.php @@ -12,7 +12,7 @@ use Analog\Analog; use Galette\Core\Pagination; -use GaletteObjectsLend\Entity\Preferences; +use GaletteObjectsLend\LendPreferences; use GaletteObjectsLend\Repository\Objects; use Slim\Views\Twig; @@ -163,21 +163,19 @@ private function warnType(string $name, string $expected, mixed $value): void /** * Set commons filters for templates * - * @param \GaletteObjectsLend\Entity\Preferences $prefs Preferences instance - * @param Twig $view Template reference + * @param LendPreferences $prefs Plugin preferences + * @param Twig $view Template reference */ - public function setViewCommonsFilters(Preferences $prefs, Twig $view): void + public function setViewCommonsFilters(LendPreferences $prefs, Twig $view): void { - $prefs = $prefs->getPreferences(); - $options = [ - Objects::FILTER_NAME => ($prefs['VIEW_DESCRIPTION'] + Objects::FILTER_NAME => ($prefs->isEnabled(LendPreferences::VIEW_DESCRIPTION) ? _T("Name/description", "objectslend") : _T("Name", "objectslend")), Objects::FILTER_SERIAL => _T("Serial number", "objectslend"), Objects::FILTER_ID => _T("Id", "objectslend") ]; - if ($prefs['VIEW_DIMENSION']) { + if ($prefs->isEnabled(LendPreferences::VIEW_DIMENSION)) { $options[Objects::FILTER_DIM] = _T("Dimensions", "objectslend"); } diff --git a/lib/GaletteObjectsLend/IO/PdfObject.php b/lib/GaletteObjectsLend/IO/PdfObject.php index f988ce9d..11ed93cc 100644 --- a/lib/GaletteObjectsLend/IO/PdfObject.php +++ b/lib/GaletteObjectsLend/IO/PdfObject.php @@ -13,7 +13,7 @@ use Galette\IO\Pdf; use Galette\Core\Db; use Galette\Core\Preferences; -use GaletteObjectsLend\Entity\Preferences as LPreferences; +use GaletteObjectsLend\LendPreferences; use GaletteObjectsLend\Entity\LendObject; use GaletteObjectsLend\Repository\Rents; @@ -25,16 +25,16 @@ class PdfObject extends Pdf { private Db $zdb; - private LPreferences $lprefs; + private LendPreferences $lprefs; /** * Main constructor * - * @param Db $zdb Database instance - * @param Preferences $prefs Preferences instance - * @param LPreferences $lprefs Plugin Preferences instance + * @param Db $zdb Database instance + * @param Preferences $prefs Preferences instance + * @param LendPreferences $lprefs Plugin Preferences instance */ - public function __construct(Db $zdb, Preferences $prefs, LPreferences $lprefs) + public function __construct(Db $zdb, Preferences $prefs, LendPreferences $lprefs) { parent::__construct($prefs); // Disable Auto Page breaks @@ -120,19 +120,19 @@ public function drawCard(LendObject $object): void } $this->addCell(_T("Name", "objectslend"), $object->getName(), $wpic); - if ($this->lprefs->{LPreferences::PARAM_VIEW_DESCRIPTION}) { + if ($this->lprefs->isEnabled(LendPreferences::VIEW_DESCRIPTION)) { $this->addCell(_T("Description", "objectslend"), $object->getDescription(), $wpic); } - if ($this->lprefs->{LPreferences::PARAM_VIEW_CATEGORY}) { + if ($this->lprefs->isEnabled(LendPreferences::VIEW_CATEGORY)) { $this->addCell(_T("Category", "objectslend"), $object->getCategoryName() ?? '', $wpic); } - if ($this->lprefs->{LPreferences::PARAM_VIEW_SERIAL}) { + if ($this->lprefs->isEnabled(LendPreferences::VIEW_SERIAL)) { $this->addCell(_T("Serial number", "objectslend"), $object->getSerialNumber(), $wpic); } - if ($this->lprefs->{LPreferences::PARAM_VIEW_PRICE}) { + if ($this->lprefs->isEnabled(LendPreferences::VIEW_PRICE)) { $this->addCell(_T("Price", "objectslend"), number_format($object->getPrice(), 2, ',', ' '), $wpic); } - if ($this->lprefs->{LPreferences::PARAM_VIEW_LEND_PRICE}) { + if ($this->lprefs->isEnabled(LendPreferences::VIEW_LEND_PRICE)) { $this->addCell( _T("Borrow price", "objectslend"), number_format($object->getRentPrice(), 2, ',', ' ') . ' €' @@ -140,17 +140,17 @@ public function drawCard(LendObject $object): void $wpic ); } - if ($this->lprefs->{LPreferences::PARAM_VIEW_DIMENSION}) { + if ($this->lprefs->isEnabled(LendPreferences::VIEW_DIMENSION)) { $this->addCell(_T("Dimensions", "objectslend"), $object->getDimension() . ' ' . _T('Cm', 'objectslend'), $wpic); } - if ($this->lprefs->{LPreferences::PARAM_VIEW_WEIGHT}) { + if ($this->lprefs->isEnabled(LendPreferences::VIEW_WEIGHT)) { $this->addCell(_T("Weight", "objectslend"), number_format($object->getWeight(), 3, ',', ' ') . ' ' . _T('Kg', 'objectslend'), $wpic); } $this->addCell(_T("Active", "objectslend"), $object->isObjectActive() ? 'X' : '', $wpic); $this->addCell(_T("Location", "objectslend"), $object->getStatusText(), $wpic); $this->addCell(_T("Since", "objectslend"), $object->getDateBegin(), $wpic); $this->addCell(_T("Member", "objectslend"), $object->getMemberName(), $wpic); - if ($this->lprefs->{LPreferences::PARAM_VIEW_DATE_FORECAST}) { + if ($this->lprefs->isEnabled(LendPreferences::VIEW_DATE_FORECAST)) { $this->addCell(_T("Return", "objectslend"), $object->getDateForecast(), $wpic); } diff --git a/lib/GaletteObjectsLend/IO/PdfObjects.php b/lib/GaletteObjectsLend/IO/PdfObjects.php index 3029ae53..27ddc7ca 100644 --- a/lib/GaletteObjectsLend/IO/PdfObjects.php +++ b/lib/GaletteObjectsLend/IO/PdfObjects.php @@ -17,7 +17,7 @@ use GaletteObjectsLend\Entity\LendObject; use GaletteObjectsLend\Filters\ObjectsList; use GaletteObjectsLend\Entity\LendCategory; -use GaletteObjectsLend\Entity\Preferences as LendPreferences; +use GaletteObjectsLend\LendPreferences; /** * Object labels PDF @@ -159,7 +159,7 @@ public function drawList(array $objects): void foreach ($objects as $object) { if ( - $this->lendsprefs->{LendPreferences::PARAM_VIEW_CATEGORY} + $this->lendsprefs->isEnabled(LendPreferences::VIEW_CATEGORY) && $current_category !== $object->getCategoryId() ) { $this->SetFont('', 'B'); diff --git a/lib/GaletteObjectsLend/LendPreferences.php b/lib/GaletteObjectsLend/LendPreferences.php new file mode 100644 index 00000000..79a181b6 --- /dev/null +++ b/lib/GaletteObjectsLend/LendPreferences.php @@ -0,0 +1,223 @@ + + */ +final class LendPreferences +{ + public const string PREFIX = 'pref_objectslend_'; + + /** Allow non staff members to rent objects */ + public const string ENABLE_MEMBER_RENT_OBJECT = self::PREFIX . 'enable_member_rent_object'; + /** Generate automatically a contribution when an object is rented */ + public const string AUTO_GENERATE_CONTRIBUTION = self::PREFIX . 'auto_generate_contribution'; + /** Type of the generated contribution */ + public const string GENERATED_CONTRIBUTION_TYPE_ID = self::PREFIX . 'generated_contribution_type_id'; + /** Information text of the generated contribution, with placeholders */ + public const string GENERATED_CONTRIB_INFO_TEXT = self::PREFIX . 'generated_contrib_info_text'; + /** Maximum width of a thumbnail, in pixels */ + public const string THUMB_MAX_WIDTH = self::PREFIX . 'thumb_max_width'; + /** Maximum height of a thumbnail, in pixels */ + public const string THUMB_MAX_HEIGHT = self::PREFIX . 'thumb_max_height'; + /** Show images in objects and categories lists */ + public const string VIEW_THUMBNAIL = self::PREFIX . 'view_thumbnail'; + /** Show fullsize images clicking on thumbnails */ + public const string VIEW_FULLSIZE = self::PREFIX . 'view_fullsize'; + /** Show categories on the objects list */ + public const string VIEW_CATEGORY = self::PREFIX . 'view_category'; + /** Show the forecast return date */ + public const string VIEW_DATE_FORECAST = self::PREFIX . 'view_date_forecast'; + /** Show the description */ + public const string VIEW_DESCRIPTION = self::PREFIX . 'view_description'; + /** Show dimensions */ + public const string VIEW_DIMENSION = self::PREFIX . 'view_dimension'; + /** Show the rent price */ + public const string VIEW_LEND_PRICE = self::PREFIX . 'view_lend_price'; + /** Show the sum of prices on the objects list */ + public const string VIEW_LIST_PRICE_SUM = self::PREFIX . 'view_list_price_sum'; + /** Show the buy price */ + public const string VIEW_PRICE = self::PREFIX . 'view_price'; + /** Show the serial number */ + public const string VIEW_SERIAL = self::PREFIX . 'view_serial'; + /** Show the weight */ + public const string VIEW_WEIGHT = self::PREFIX . 'view_weight'; + + /** + * Constructor + * + * @param Preferences $preferences Core preferences + */ + public function __construct(private readonly Preferences $preferences) + { + } + + /** + * Get the preferences the plugin declares + * + * Defaults are the ones the former install scripts inserted; contribution + * type 5 is the "donation in money" core installs. + * + * @return array> + */ + public static function getSchema(): array + { + $schema = [ + self::GENERATED_CONTRIBUTION_TYPE_ID => [ + 'type' => PreferencesSchema::TYPE_INT, + 'default' => 5, + 'min' => 0, + 'error' => PreferencesSchema::ERR_POSITIVE_NUMBER, + ], + self::GENERATED_CONTRIB_INFO_TEXT => [ + 'type' => PreferencesSchema::TYPE_STRING, + 'default' => 'Location de {NAME} {DESCRIPTION} {SERIAL_NUMBER}', + ], + self::THUMB_MAX_WIDTH => [ + 'type' => PreferencesSchema::TYPE_INT, + 'default' => 128, + 'min' => 1, + 'error' => PreferencesSchema::ERR_POSITIVE_NUMBER, + ], + self::THUMB_MAX_HEIGHT => [ + 'type' => PreferencesSchema::TYPE_INT, + 'default' => 128, + 'min' => 1, + 'error' => PreferencesSchema::ERR_POSITIVE_NUMBER, + ], + ]; + + foreach (self::getBooleans() as $name => $default) { + $schema[$name] = [ + 'type' => PreferencesSchema::TYPE_BOOL, + 'default' => $default, + ]; + } + + return $schema; + } + + /** + * Get yes/no preferences, with their default value + * + * A form does not post an unchecked box: this is the list to tell it + * from a value that was not part of the form at all. + * + * @return array + */ + public static function getBooleans(): array + { + return [ + self::ENABLE_MEMBER_RENT_OBJECT => true, + self::AUTO_GENERATE_CONTRIBUTION => true, + self::VIEW_THUMBNAIL => true, + self::VIEW_FULLSIZE => true, + self::VIEW_CATEGORY => false, + self::VIEW_DATE_FORECAST => true, + self::VIEW_DESCRIPTION => true, + self::VIEW_DIMENSION => false, + self::VIEW_LEND_PRICE => false, + self::VIEW_LIST_PRICE_SUM => false, + self::VIEW_PRICE => false, + self::VIEW_SERIAL => false, + self::VIEW_WEIGHT => false, + ]; + } + + /** + * Is a yes/no preference on? + * + * @param string $name Preference name, one of the class constants + */ + public function isEnabled(string $name): bool + { + return (bool)$this->preferences->getPluginValue($name); + } + + /** + * Get type of the generated contribution + */ + public function getContributionTypeId(): int + { + return (int)$this->preferences->getPluginValue(self::GENERATED_CONTRIBUTION_TYPE_ID); + } + + /** + * Get information text of the generated contribution + */ + public function getContributionText(): string + { + return (string)$this->preferences->getPluginValue(self::GENERATED_CONTRIB_INFO_TEXT); + } + + /** + * Get thumbnail maximum width + */ + public function getThumbWidth(): int + { + return (int)$this->preferences->getPluginValue(self::THUMB_MAX_WIDTH); + } + + /** + * Get thumbnail maximum height + */ + public function getThumbHeight(): int + { + return (int)$this->preferences->getPluginValue(self::THUMB_MAX_HEIGHT); + } + + /** + * Whether to display images (as thumbnails) in lists + */ + public function imagesInLists(): bool + { + return $this->isEnabled(self::VIEW_THUMBNAIL); + } + + /** + * Shall we show fullsize images? + * + * Would appear editing object, and on thumbnails click + */ + public function showFullsize(): bool + { + return $this->isEnabled(self::VIEW_FULLSIZE); + } + + /** + * Get every preference, named without its prefix, for templates + * + * @return array + */ + public function toArray(): array + { + $values = []; + foreach (self::getSchema() as $name => $entry) { + //core hands a false boolean back as an empty string + $values[substr($name, strlen(self::PREFIX))] = match ($entry['type']) { + PreferencesSchema::TYPE_BOOL => $this->isEnabled($name), + PreferencesSchema::TYPE_INT => (int)$this->preferences->getPluginValue($name), + default => (string)$this->preferences->getPluginValue($name), + }; + } + return $values; + } +} diff --git a/lib/GaletteObjectsLend/LendService.php b/lib/GaletteObjectsLend/LendService.php index 8730d29b..ac211010 100644 --- a/lib/GaletteObjectsLend/LendService.php +++ b/lib/GaletteObjectsLend/LendService.php @@ -20,7 +20,6 @@ use GaletteObjectsLend\Entity\LendObject; use GaletteObjectsLend\Entity\LendRent; use GaletteObjectsLend\Entity\LendStatus; -use GaletteObjectsLend\Entity\Preferences; use GaletteObjectsLend\Repository\Objects; use GaletteObjectsLend\Repository\Rents; use GaletteObjectsLend\Repository\Status; @@ -42,13 +41,13 @@ class LendService * @param Db $zdb Database instance * @param CorePreferences $preferences Preferences * @param Login $login Logged in instance - * @param Preferences $lendsprefs Plugin preferences + * @param LendPreferences $lendsprefs Plugin preferences */ public function __construct( private Db $zdb, private CorePreferences $preferences, private Login $login, - private Preferences $lendsprefs + private LendPreferences $lendsprefs ) { } @@ -68,7 +67,7 @@ public function getObject(int $id): LendObject public function canTake(): bool { return $this->isManager() - || $this->lendsprefs->{Preferences::PARAM_ENABLE_MEMBER_RENT_OBJECT}; + || $this->lendsprefs->isEnabled(LendPreferences::ENABLE_MEMBER_RENT_OBJECT); } /** @@ -85,7 +84,7 @@ public function canGiveBack(LendObject $object): bool return true; } - return $this->lendsprefs->{Preferences::PARAM_ENABLE_MEMBER_RENT_OBJECT} + return $this->lendsprefs->isEnabled(LendPreferences::ENABLE_MEMBER_RENT_OBJECT) && $object->getIdAdh() !== null && $this->login->id == $object->getIdAdh(); } @@ -178,7 +177,7 @@ public function take( return $this->inTransaction(function () use ($object, $status_id, $member_id, $date_forecast, $rent_price, $payment_type) { $rent = $this->openRent($object, $status_id, $member_id, '', $date_forecast); - if ($this->lendsprefs->{Preferences::PARAM_AUTO_GENERATE_CONTRIBUTION} && $rent_price > 0) { + if ($this->lendsprefs->isEnabled(LendPreferences::AUTO_GENERATE_CONTRIBUTION) && $rent_price > 0) { return $this->storeContribution($object, $rent, $rent_price, $payment_type); } return null; @@ -330,12 +329,12 @@ private function storeContribution( number_format($object->getWeight(), 3, ',', ' '), $object->getDimension() ], - $this->lendsprefs->{Preferences::PARAM_GENERATED_CONTRIB_INFO_TEXT} + $this->lendsprefs->getContributionText() ); $values = [ 'montant_cotis' => $amount, - ContributionsTypes::PK => $this->lendsprefs->{Preferences::PARAM_GENERATED_CONTRIBUTION_TYPE_ID}, + ContributionsTypes::PK => $this->lendsprefs->getContributionTypeId(), 'date_enreg' => date("Y-m-d"), 'date_debut_cotis' => date("Y-m-d"), 'type_paiement_cotis' => $payment_type, diff --git a/lib/GaletteObjectsLend/PluginGaletteObjectslend.php b/lib/GaletteObjectsLend/PluginGaletteObjectslend.php index 96027428..dff2cbb8 100644 --- a/lib/GaletteObjectsLend/PluginGaletteObjectslend.php +++ b/lib/GaletteObjectsLend/PluginGaletteObjectslend.php @@ -15,6 +15,7 @@ use Galette\Core\Login; use Galette\Core\Plugins\InstallableInterface; use Galette\Core\Plugins\MenuProviderInterface; +use Galette\Core\Plugins\PreferencesProviderInterface; use Galette\Core\GalettePlugin; use GaletteObjectsLend\Entity\CategoryPicture; use GaletteObjectsLend\Entity\LendObject; @@ -22,7 +23,6 @@ use GaletteObjectsLend\Entity\LendRent; use GaletteObjectsLend\Entity\ObjectPicture; use GaletteObjectsLend\Entity\LendStatus; -use GaletteObjectsLend\Entity\Preferences; /** * Plugin Galette Objects Lend @@ -30,7 +30,7 @@ * @author Johan Cwiklinski */ -class PluginGaletteObjectslend extends GalettePlugin implements InstallableInterface, MenuProviderInterface +class PluginGaletteObjectslend extends GalettePlugin implements InstallableInterface, MenuProviderInterface, PreferencesProviderInterface { #[Inject] private readonly Db $zdb; //@phpstan-ignore property.uninitializedReadonly, property.onlyRead (injected from DI) @@ -96,6 +96,16 @@ public function getMenus(): array return $menus; } + /** + * Get the preferences the plugin declares + * + * @return array> + */ + public function getPreferences(): array + { + return LendPreferences::getSchema(); + } + /** * Extra public menus entries * @@ -118,7 +128,6 @@ public function isInstalled(): bool && $this->zdb->tableExists(LEND_PREFIX . LendRent::TABLE) && $this->zdb->tableExists(LEND_PREFIX . LendStatus::TABLE) && $this->zdb->tableExists(LEND_PREFIX . ObjectPicture::TABLE) - && $this->zdb->tableExists(LEND_PREFIX . Preferences::TABLE) ; } } diff --git a/lib/GaletteObjectsLend/Repository/Categories.php b/lib/GaletteObjectsLend/Repository/Categories.php index acf9f6d2..9410e5aa 100644 --- a/lib/GaletteObjectsLend/Repository/Categories.php +++ b/lib/GaletteObjectsLend/Repository/Categories.php @@ -16,7 +16,7 @@ use Galette\Core\Preferences; use GaletteObjectsLend\Entity\LendCategory; use GaletteObjectsLend\Entity\LendObject; -use GaletteObjectsLend\Entity\Preferences as LendPreferences; +use GaletteObjectsLend\LendPreferences; use GaletteObjectsLend\Filters\CategoriesList; use GaletteObjectsLend\Filters\ObjectsList; use Laminas\Db\ResultSet\ResultSet; @@ -113,7 +113,7 @@ protected function buildSelect(): Select $this->zdb, $this->preferences, $this->login, - new LendPreferences($this->zdb), + new LendPreferences($this->preferences), $this->filters->objects_filters ); $objects->applyFilters($select); diff --git a/lib/GaletteObjectsLend/Repository/Objects.php b/lib/GaletteObjectsLend/Repository/Objects.php index 45b8bd75..7324e014 100644 --- a/lib/GaletteObjectsLend/Repository/Objects.php +++ b/lib/GaletteObjectsLend/Repository/Objects.php @@ -19,7 +19,7 @@ use GaletteObjectsLend\Entity\LendObject; use GaletteObjectsLend\Entity\LendRent; use GaletteObjectsLend\Entity\LendStatus; -use GaletteObjectsLend\Entity\Preferences as LendPreferences; +use GaletteObjectsLend\LendPreferences; use GaletteObjectsLend\Filters\ObjectsList; use Laminas\Db\ResultSet\ResultSet; use Laminas\Db\Sql\Select; @@ -286,7 +286,7 @@ public function applyFilters(Select $select): void switch ($this->filters->field_filter) { case self::FILTER_NAME: - if ($this->lendsprefs->{LendPreferences::PARAM_VIEW_DESCRIPTION}) { + if ($this->lendsprefs->isEnabled(LendPreferences::VIEW_DESCRIPTION)) { $select->where ->nest() ->addPredicate($this->contains('o.name', $search)) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 10f2856b..c2e8d3d2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -12,36 +12,6 @@ parameters: count: 1 path: lib/GaletteObjectsLend/Controllers/Crud/ObjectsController.php - - - message: '#^Empty array passed to foreach\.$#' - identifier: foreach.emptyArray - count: 1 - path: lib/GaletteObjectsLend/Controllers/MainController.php - - - - message: '#^Cannot access property \$code on array\|ArrayObject\.$#' - identifier: property.nonObject - count: 1 - path: lib/GaletteObjectsLend/Entity/Preferences.php - - - - message: '#^Cannot access property \$is_text on array\|ArrayObject\.$#' - identifier: property.nonObject - count: 1 - path: lib/GaletteObjectsLend/Entity/Preferences.php - - - - message: '#^Cannot access property \$value_numeric on array\|ArrayObject\.$#' - identifier: property.nonObject - count: 1 - path: lib/GaletteObjectsLend/Entity/Preferences.php - - - - message: '#^Cannot access property \$value_text on array\|ArrayObject\.$#' - identifier: property.nonObject - count: 1 - path: lib/GaletteObjectsLend/Entity/Preferences.php - - message: '#^Parameter \#5 \$ln of method TCPDF\:\:Cell\(\) expects int, false given\.$#' identifier: argument.type diff --git a/scripts/mysql.sql b/scripts/mysql.sql index 12f3a634..c267644b 100644 --- a/scripts/mysql.sql +++ b/scripts/mysql.sql @@ -78,107 +78,6 @@ INSERT INTO galette_lend_status (status_text, in_stock, is_active, rent_day_numb INSERT INTO galette_lend_status (status_text, in_stock, is_active) VALUES('Vendu (exemple)', 0, 1); INSERT INTO galette_lend_status (status_text, in_stock, is_active) VALUES('Detruit (exemple)', 0, 1); -DROP TABLE IF EXISTS galette_lend_parameters; -CREATE TABLE galette_lend_parameters ( - parameter_id int(10) unsigned NOT NULL AUTO_INCREMENT, - code varchar(30) COLLATE utf8_general_ci NOT NULL, - is_date tinyint(1) NOT NULL, - value_date date DEFAULT NULL, - is_text tinyint(1) NOT NULL, - value_text varchar(300) COLLATE utf8_general_ci DEFAULT NULL, - is_numeric tinyint(1) NOT NULL, - nb_digits int(11) DEFAULT NULL, - value_numeric double DEFAULT NULL, - date_creation datetime NOT NULL, - date_modification datetime NOT NULL, - PRIMARY KEY (parameter_id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_CATEGORY', 0, 0, 1, 0, 0, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_SERIAL', 0, 0, 1, 0, 0, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_THUMBNAIL', 0, 0, 1, 0, 1, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_FULLSIZE', 0, 0, 1, 0, 1, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_DESCRIPTION', 0, 0, 1, 0, 1, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_PRICE', 0, 0, 1, 0, 0, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_DIMENSION', 0, 0, 1, 0, 0, NOW(), NOW()); - -insert into galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_WEIGHT', 0, 0, 1, 0, 0, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_LEND_PRICE', 0, 0, 1, 0, 0, NOW(), NOW()); - -insert into galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('THUMB_MAX_WIDTH', 0, 0, 1, 0, 128, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('THUMB_MAX_HEIGHT', 0, 0, 1, 0, 128, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('AUTO_GENERATE_CONTRIBUTION', 0, 0, 1, 0, 1, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('GENERATED_CONTRIBUTION_TYPE_ID', 0, 0, 1, 0, 5, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, value_text, date_creation, date_modification) -VALUES -('GENERATED_CONTRIB_INFO_TEXT', 0, 1, 0, 'Location de {NAME} {DESCRIPTION} {SERIAL_NUMBER}', NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('ENABLE_MEMBER_RENT_OBJECT', 0, 0, 1, 0, 1, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_DATE_FORECAST', 0, 0, 1, 0, 1, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_LIST_PRICE_SUM', 0, 0, 1, 0, 0, NOW(), NOW()); - DROP TABLE IF EXISTS galette_lend_categories_pictures; CREATE TABLE IF NOT EXISTS galette_lend_categories_pictures ( category_id int(11) NOT NULL, diff --git a/scripts/pgsql.sql b/scripts/pgsql.sql index 0ba06d03..c6bd68ed 100644 --- a/scripts/pgsql.sql +++ b/scripts/pgsql.sql @@ -30,15 +30,6 @@ CREATE SEQUENCE galette_lend_status_id_seq MINVALUE 1 CACHE 1; --- sequence for lend parameters -DROP SEQUENCE IF EXISTS galette_lend_parameters_id_seq; -CREATE SEQUENCE galette_lend_parameters_id_seq - START 1 - INCREMENT 1 - MAXVALUE 2147483647 - MINVALUE 1 - CACHE 1; - -- sequence for lend cetegories DROP SEQUENCE IF EXISTS galette_lend_category_id_seq; CREATE SEQUENCE galette_lend_category_id_seq @@ -115,22 +106,6 @@ CREATE TABLE galette_lend_pictures ( ); -DROP TABLE IF EXISTS galette_lend_parameters CASCADE; -CREATE TABLE IF NOT EXISTS galette_lend_parameters ( - parameter_id integer DEFAULT nextval('galette_lend_parameters_id_seq'::text) NOT NULL, - code character varying(30) NOT NULL, - is_date boolean NOT NULL, - value_date timestamp DEFAULT NULL, - is_text boolean NOT NULL, - value_text character varying(300) DEFAULT NULL, - is_numeric boolean NOT NULL, - nb_digits integer DEFAULT NULL, - value_numeric real DEFAULT NULL, - date_creation timestamp NOT NULL, - date_modification timestamp NOT NULL, - PRIMARY KEY (parameter_id) -); - DROP TABLE IF EXISTS galette_lend_categories_pictures; CREATE TABLE galette_lend_categories_pictures ( @@ -150,89 +125,3 @@ INSERT INTO galette_lend_status (status_text, in_stock, is_active, rent_day_numb INSERT INTO galette_lend_status (status_text, in_stock, is_active, rent_day_number) VALUES('Reparation (exemple)', FALSE, TRUE, 14); INSERT INTO galette_lend_status (status_text, in_stock, is_active) VALUES('Vendu (exemple)', FALSE, TRUE); INSERT INTO galette_lend_status (status_text, in_stock, is_active) VALUES('Detruit (exemple)', FALSE, TRUE); - --- Default parameters -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_CATEGORY', FALSE, FALSE, TRUE, 0, 0, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_SERIAL', FALSE, FALSE, TRUE, 0, 0, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_THUMBNAIL', FALSE, FALSE, TRUE, 0, 1, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_FULLSIZE', FALSE, FALSE, TRUE, 0, 1, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_DESCRIPTION', FALSE, FALSE, TRUE, 0, 1, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_PRICE', FALSE, FALSE, TRUE, 0, 0, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_DIMENSION', FALSE, FALSE, TRUE, 0, 0, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_WEIGHT', FALSE, FALSE, TRUE, 0, 0, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_LEND_PRICE', FALSE, FALSE, TRUE, 0, 0, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('THUMB_MAX_WIDTH', FALSE, FALSE, TRUE, 0, 128, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('THUMB_MAX_HEIGHT', FALSE, FALSE, TRUE, 0, 128, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -values -('AUTO_GENERATE_CONTRIBUTION', FALSE, FALSE, TRUE, 0, 1, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -values -('GENERATED_CONTRIBUTION_TYPE_ID', FALSE, FALSE, TRUE, 0, 5, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, value_text, date_creation, date_modification) -VALUES -('GENERATED_CONTRIB_INFO_TEXT', FALSE, TRUE, FALSE, 'Location de {NAME} {DESCRIPTION} {SERIAL_NUMBER}', NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('ENABLE_MEMBER_RENT_OBJECT', FALSE, FALSE, TRUE, 0, 1, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_DATE_FORECAST', FALSE, FALSE, TRUE, 0, 1, NOW(), NOW()); - -INSERT INTO galette_lend_parameters -(code, is_date, is_text, is_numeric, nb_digits, value_numeric, date_creation, date_modification) -VALUES -('VIEW_LIST_PRICE_SUM', FALSE, FALSE, TRUE, 0, 0, NOW(), NOW()); diff --git a/scripts/upgrade-to-1.1-mysql.sql b/scripts/upgrade-to-1.1-mysql.sql new file mode 100644 index 00000000..80480c54 --- /dev/null +++ b/scripts/upgrade-to-1.1-mysql.sql @@ -0,0 +1,24 @@ +-- +-- This file is part of Galette Objects Lend plugin (https://galette.eu). +-- SPDX-FileCopyrightText: Copyright © 2013-2026 The Galette Team +-- SPDX-License-Identifier: GPL-3.0-or-later +-- + +-- Plugin preferences move to core preferences. Declaring them already +-- inserted their defaults: those give way to the values set so far. +DELETE FROM galette_preferences WHERE nom_pref LIKE 'pref_objectslend_%'; + +INSERT INTO galette_preferences (nom_pref, val_pref) +SELECT + CONCAT('pref_objectslend_', LOWER(code)), + CASE WHEN is_text = 1 THEN COALESCE(value_text, '') + ELSE CAST(CAST(COALESCE(value_numeric, 0) AS SIGNED) AS CHAR) END +FROM galette_lend_parameters +WHERE code IN ( + 'ENABLE_MEMBER_RENT_OBJECT', 'AUTO_GENERATE_CONTRIBUTION', 'GENERATED_CONTRIBUTION_TYPE_ID', + 'GENERATED_CONTRIB_INFO_TEXT', 'THUMB_MAX_WIDTH', 'THUMB_MAX_HEIGHT', 'VIEW_THUMBNAIL', 'VIEW_FULLSIZE', + 'VIEW_CATEGORY', 'VIEW_DATE_FORECAST', 'VIEW_DESCRIPTION', 'VIEW_DIMENSION', 'VIEW_LEND_PRICE', + 'VIEW_LIST_PRICE_SUM', 'VIEW_PRICE', 'VIEW_SERIAL', 'VIEW_WEIGHT' +); + +DROP TABLE galette_lend_parameters; diff --git a/scripts/upgrade-to-1.1-pgsql.sql b/scripts/upgrade-to-1.1-pgsql.sql new file mode 100644 index 00000000..743c66a2 --- /dev/null +++ b/scripts/upgrade-to-1.1-pgsql.sql @@ -0,0 +1,25 @@ +-- +-- This file is part of Galette Objects Lend plugin (https://galette.eu). +-- SPDX-FileCopyrightText: Copyright © 2013-2026 The Galette Team +-- SPDX-License-Identifier: GPL-3.0-or-later +-- + +-- Plugin preferences move to core preferences. Declaring them already +-- inserted their defaults: those give way to the values set so far. +DELETE FROM galette_preferences WHERE nom_pref LIKE 'pref_objectslend_%'; + +INSERT INTO galette_preferences (nom_pref, val_pref) +SELECT + 'pref_objectslend_' || LOWER(code), + CASE WHEN is_text THEN COALESCE(value_text, '') + ELSE CAST(CAST(ROUND(COALESCE(value_numeric, 0)) AS integer) AS text) END +FROM galette_lend_parameters +WHERE code IN ( + 'ENABLE_MEMBER_RENT_OBJECT', 'AUTO_GENERATE_CONTRIBUTION', 'GENERATED_CONTRIBUTION_TYPE_ID', + 'GENERATED_CONTRIB_INFO_TEXT', 'THUMB_MAX_WIDTH', 'THUMB_MAX_HEIGHT', 'VIEW_THUMBNAIL', 'VIEW_FULLSIZE', + 'VIEW_CATEGORY', 'VIEW_DATE_FORECAST', 'VIEW_DESCRIPTION', 'VIEW_DIMENSION', 'VIEW_LEND_PRICE', + 'VIEW_LIST_PRICE_SUM', 'VIEW_PRICE', 'VIEW_SERIAL', 'VIEW_WEIGHT' +); + +DROP TABLE galette_lend_parameters; +DROP SEQUENCE IF EXISTS galette_lend_parameters_id_seq; diff --git a/templates/default/objects_edit.html.twig b/templates/default/objects_edit.html.twig index 8641878b..24a6a411 100644 --- a/templates/default/objects_edit.html.twig +++ b/templates/default/objects_edit.html.twig @@ -27,7 +27,7 @@ label: _T("Description:", "objectslend") } %} - {% if lendsprefs.VIEW_CATEGORY %} + {% if lendsprefs.view_category %} {% set category_list_values = {(0): _T("--- Select a category ---", "objectslend")} %} {% for categ in categories %} {% set category_list_values = category_list_values + {(categ.getId()): categ.getName()} %} diff --git a/templates/default/objects_list.html.twig b/templates/default/objects_list.html.twig index be584811..94f9a844 100644 --- a/templates/default/objects_list.html.twig +++ b/templates/default/objects_list.html.twig @@ -65,31 +65,31 @@ {'label': _T("Name", "objectslend"), 'order': constant("GaletteObjectsLend\\Repository\\Objects::ORDERBY_NAME")}, ]) %} - {% if lendsprefs.VIEW_SERIAL %} + {% if lendsprefs.view_serial %} {% set columns = columns|merge([ {'label': _T("Serial", "objectslend"), 'order': constant("GaletteObjectsLend\\Repository\\Objects::ORDERBY_SERIAL")}, ]) %} {% endif %} - {% if lendsprefs.VIEW_PRICE %} + {% if lendsprefs.view_price %} {% set columns = columns|merge([ {'label': _T("Price", "objectslend"), 'order': constant("GaletteObjectsLend\\Repository\\Objects::ORDERBY_PRICE")}, ]) %} {% endif %} - {% if lendsprefs.VIEW_LEND_PRICE %} + {% if lendsprefs.view_lend_price %} {% set columns = columns|merge([ {'label': _T("Borrow price", "objectslend"), 'order': constant("GaletteObjectsLend\\Repository\\Objects::ORDERBY_RENTPRICE")}, ]) %} {% endif %} - {% if lendsprefs.VIEW_DIMENSION %} + {% if lendsprefs.view_dimension %} {% set columns = columns|merge([ {'label': _T("Dimensions", "objectslend")}, ]) %} {% endif %} - {% if lendsprefs.VIEW_WEIGHT %} + {% if lendsprefs.view_weight %} {% set columns = columns|merge([ {'label': _T("Weight", "objectslend"), 'order': constant("GaletteObjectsLend\\Repository\\Objects::ORDERBY_WEIGHT")}, ]) %} @@ -104,7 +104,7 @@ {'label': _T("By", "objectslend"), 'order': constant("GaletteObjectsLend\\Repository\\Objects::ORDERBY_MEMBER")}, ]) %} - {% if lendsprefs.VIEW_DATE_FORECAST %} + {% if lendsprefs.view_date_forecast %} {% set columns = columns|merge([ { 'label': _T("Return", "objectslend"), 'order': constant("GaletteObjectsLend\\Repository\\Objects::ORDERBY_FDATE"), @@ -128,7 +128,7 @@ {% block search %}
-
+
@@ -141,7 +141,7 @@ {% endfor %}
- {% if lendsprefs.VIEW_CATEGORY and categories|length > 0 %} + {% if lendsprefs.view_category and categories|length > 0 %}
{% for categ in categories %}
- {% if lendsprefs.VIEW_LIST_PRICE_SUM and lendsprefs.VIEW_PRICE and (login.isAdmin() or login.isStaff()) %} + {% if lendsprefs.view_list_price_sum and lendsprefs.view_price and (login.isAdmin() or login.isStaff()) %} {{ categ.getSum()|format_currency('EUR', {}, i18n.getWebID()) }} {% endif %} @@ -227,21 +227,21 @@ {% endif %} {{ filters.highlight(object.getName()|e, 'name')|raw }} - {% if lendsprefs.VIEW_DESCRIPTION %} + {% if lendsprefs.view_description %}
{{ filters.highlight(object.getDescriptionHtml(), 'description')|raw }} {% endif %} - {% if lendsprefs.VIEW_SERIAL %} + {% if lendsprefs.view_serial %} {{ filters.highlight(object.getSerialNumber()|e, 'serial_number')|raw }} {% endif %} - {% if lendsprefs.VIEW_PRICE %} + {% if lendsprefs.view_price %} {{ object.getPrice()|format_currency('EUR', {}, i18n.getWebID()) }} {% endif %} - {% if lendsprefs.VIEW_LEND_PRICE %} + {% if lendsprefs.view_lend_price %} {{ object.getRentPrice()|format_currency('EUR', {}, i18n.getWebID()) }}
{% if object.isPricePerDay() %} @@ -251,12 +251,12 @@ {% endif %} {% endif %} - {% if lendsprefs.VIEW_DIMENSION %} + {% if lendsprefs.view_dimension %} {{ filters.highlight(object.getDimension()|e, 'dimension')|raw }} {% endif %} - {% if lendsprefs.VIEW_WEIGHT %} + {% if lendsprefs.view_weight %} {{ object.getWeight()|format_number({fraction_digit: 3}, 'decimal', 'default', i18n.getWebID()) }} @@ -274,7 +274,7 @@ {{ memberName({'id': object.getIdAdh()}) }} {% else %}-{% endif %} - {% if lendsprefs.VIEW_DATE_FORECAST %} + {% if lendsprefs.view_date_forecast %} {{ object.getDateForecast() }} @@ -338,7 +338,7 @@ {% endif %} {% if not object.getRentId() or object.inStock() %} - {% if lendsprefs.ENABLE_MEMBER_RENT_OBJECT or login.isAdmin() or login.isStaff() %} + {% if lendsprefs.enable_member_rent_object or login.isAdmin() or login.isStaff() %} {% set actions = [ { 'label': _T("Take away", "objectslend"), diff --git a/templates/default/preferences.html.twig b/templates/default/preferences.html.twig index 972cedb1..cdc8f6df 100644 --- a/templates/default/preferences.html.twig +++ b/templates/default/preferences.html.twig @@ -6,28 +6,17 @@ {% extends 'page.html.twig' %} -{% macro yesno_checkbox(label, name, tip, yes, no) %} -
-
- -
-
- - -
-
-
-
- - -
-
-
-
+{% macro toggles(boxes, values) %} + {% for box in boxes %} + {% include "components/forms/checkbox.html.twig" with { + id: box.name, + name: 'pref_objectslend_' ~ box.name, + value: 1, + label: box.label, + checked: values[box.name], + tip_html: box.tip ?? '', + } %} + {% endfor %} {% endmacro %} {% block content %} @@ -41,56 +30,25 @@
- {% set yesno_boxes = [ + {{ _self.toggles([ { label: _T("Members can borrow:", "objectslend"), - name: 'ENABLE_MEMBER_RENT_OBJECT', + name: 'enable_member_rent_object', tip: _T("Allow a member (not staff neither admin) to borrow an object. If set to 'No', only admin and staff members can access the 'Take object' page", "objectslend"), - yes: { - name: 'yes_memberborrow', - checked: lendsprefs.ENABLE_MEMBER_RENT_OBJECT == '1' - }, - no: { - name: 'no_memberborrow', - checked: lendsprefs.ENABLE_MEMBER_RENT_OBJECT == '0' - } }, { label: _T("Generate contribution:", "objectslend"), - name: 'AUTO_GENERATE_CONTRIBUTION', + name: 'auto_generate_contribution', tip: _T("Automatically generate a contribution for the member of the amount of the rental price of the object", "objectslend"), - yes: { - name: 'yes_contrib', - checked: lendsprefs.AUTO_GENERATE_CONTRIBUTION == '1' - }, - no: { - name: 'no_contrib', - checked: lendsprefs.AUTO_GENERATE_CONTRIBUTION == '0' - } } - ] %} - - {% for yesno_box in yesno_boxes %} - {{ _self.yesno_checkbox( - yesno_box.label, - yesno_box.name, - yesno_box.tip, - yesno_box.yes, - yesno_box.no - ) }} - {% endfor %} + ], lendsprefs) }} - {% set ctype_list_values = {(0): _T("Choose a contribution type", "objectslend")} %} - {% for id, ctype in ctypes %} - {% set ctype_list_values = ctype_list_values + {(id): ctype} %} - {% endfor %} - - {# TODO: hide this one if AUTO_GENERATE_CONTRIBUTION is off #} -
+ {# TODO: hide this one if auto_generate_contribution is off #} +
@@ -180,128 +110,21 @@
- {% set yesno_boxes = [ - { - label: _T("View category:", "objectslend"), - name: 'VIEW_CATEGORY', - yes: { - name: 'yes_view_category', - checked: lendsprefs.VIEW_CATEGORY == '1' - }, - no: { - name: 'no_view_category', - checked: lendsprefs.VIEW_CATEGORY == '0' - } - }, - { - label: _T("View forecast return date:", "objectslend"), - name: 'VIEW_DATE_FORECAST', - yes: { - name: 'yes_view_dateforecast', - checked: lendsprefs.VIEW_DATE_FORECAST == '1' - }, - no: { - name: 'no_view_dateforecats', - checked: lendsprefs.VIEW_DATE_FORECAST == '0' - } - }, - { - label: _T("View description", "objectslend"), - name: 'VIEW_DESCRIPTION', - yes: { - name: 'yes_view_description', - checked: lendsprefs.VIEW_DESCRIPTION == '1' - }, - no: { - name: 'no_view_description', - checked: lendsprefs.VIEW_DESCRIPTION == '0' - } - }, - { - label: _T("View dimensions:", "objectslend"), - name: 'VIEW_DIMENSION', - yes: { - name: 'yes_view_dimension', - checked: lendsprefs.VIEW_DIMENSION == '1' - }, - no: { - name: 'no_view_dimension', - checked: lendsprefs.VIEW_DIMENSION == '0' - } - }, - { - label: _T("View borrow price:", "objectslend"), - name: 'VIEW_LEND_PRICE', - yes: { - name: 'yes_view_lendprice', - checked: lendsprefs.VIEW_LEND_PRICE == '1' - }, - no: { - name: 'no_view_lendprice', - checked: lendsprefs.VIEW_LEND_PRICE == '0' - } - }, + {{ _self.toggles([ + {label: _T("View category:", "objectslend"), name: 'view_category'}, + {label: _T("View forecast return date:", "objectslend"), name: 'view_date_forecast'}, + {label: _T("View description", "objectslend"), name: 'view_description'}, + {label: _T("View dimensions:", "objectslend"), name: 'view_dimension'}, + {label: _T("View borrow price:", "objectslend"), name: 'view_lend_price'}, { label: _T("View price sum:", "objectslend"), - name: 'VIEW_LIST_PRICE_SUM', + name: 'view_list_price_sum', tip: _T("View the objects buy price sum on the list under the category", "objectslend"), - yes: { - name: 'yes_view_pricesum', - checked: lendsprefs.VIEW_LIST_PRICE_SUM == '1' - }, - no: { - name: 'no_view_pricesum', - checked: lendsprefs.VIEW_LIST_PRICE_SUM == '0' - } - }, - { - label: _T("View buy price:", "objectslend"), - name: 'VIEW_PRICE', - yes: { - name: 'yes_view_price', - checked: lendsprefs.VIEW_PRICE == '1' - }, - no: { - name: 'no_view_price', - checked: lendsprefs.VIEW_PRICE == '0' - } }, - { - label: _T("View serial number:", "objectslend"), - name: 'VIEW_SERIAL', - yes: { - name: 'yes_view_serial', - checked: lendsprefs.VIEW_SERIAL == '1' - }, - no: { - name: 'no_view_serial', - checked: lendsprefs.VIEW_SERIAL == '0' - } - }, - { - label: _T("View weight", "objectslend"), - name: 'VIEW_WEIGHT', - yes: { - name: 'yes_view_weight', - checked: lendsprefs.VIEW_WEIGHT == '1' - }, - no: { - name: 'no_view_weight', - checked: lendsprefs.VIEW_WEIGHT == '0' - } - }, - - ] %} - - {% for yesno_box in yesno_boxes %} - {{ _self.yesno_checkbox( - yesno_box.label, - yesno_box.name, - yesno_box.tip ?? null, - yesno_box.yes, - yesno_box.no - ) }} - {% endfor %} + {label: _T("View buy price:", "objectslend"), name: 'view_price'}, + {label: _T("View serial number:", "objectslend"), name: 'view_serial'}, + {label: _T("View weight", "objectslend"), name: 'view_weight'}, + ], lendsprefs) }}
diff --git a/templates/default/take_object.html.twig b/templates/default/take_object.html.twig index c6f7164e..adde4f57 100644 --- a/templates/default/take_object.html.twig +++ b/templates/default/take_object.html.twig @@ -24,28 +24,28 @@ alt="{{ _T("Object photo", "objectslend") }}"/>
- {% if lendsprefs.VIEW_DESCRIPTION %} + {% if lendsprefs.view_description %}
{{ object.getDescriptionHtml()|raw }}
{% endif %} - {% if lendsprefs.VIEW_SERIAL %} + {% if lendsprefs.view_serial %}
{{ object.getSerialNumber() }}
{% endif %} - {% if lendsprefs.VIEW_PRICE %} + {% if lendsprefs.view_price %}
{{ object.getPrice()|format_currency('EUR', {}, i18n.getWebID()) }}
{% endif %} - {% if lendsprefs.VIEW_DIMENSION %} + {% if lendsprefs.view_dimension %}
{% if object.getDimension() != '' %} @@ -56,7 +56,7 @@
{% endif %} - {% if lendsprefs.VIEW_WEIGHT %} + {% if lendsprefs.view_weight %}
{% if object.getWeight() %} @@ -128,7 +128,7 @@ } %}
- {% if lendsprefs.AUTO_GENERATE_CONTRIBUTION %} + {% if lendsprefs.auto_generate_contribution %}
{% include "components/forms/payment_types.html.twig" with { show_inline: true, diff --git a/tests/GaletteObjectsLend/Controllers/tests/units/MainController.php b/tests/GaletteObjectsLend/Controllers/tests/units/MainController.php new file mode 100644 index 00000000..c97e329b --- /dev/null +++ b/tests/GaletteObjectsLend/Controllers/tests/units/MainController.php @@ -0,0 +1,137 @@ + + */ +class MainController extends GaletteRoutingTestCase +{ + protected int $seed = 20260924101512; + protected bool $load_plugins = true; + + /** @var array */ + private array $orig_prefs; + + /** + * Set up tests + */ + public function setUp(): void + { + parent::setUp(); + $this->orig_prefs = []; + foreach (array_keys(LendPreferences::getSchema()) as $name) { + $this->orig_prefs[$name] = $this->preferences->getPluginValue($name); + } + } + + /** + * Cleanup after each test method + */ + public function tearDown(): void + { + foreach ($this->orig_prefs as $name => $value) { + $this->preferences->setValue($name, $value, $this->login); + } + $this->login->logout(); + parent::tearDown(); + } + + /** + * Preferences page shows stored values + */ + public function testPreferences(): void + { + $this->logSuperAdmin(); + $this->assertTrue($this->preferences->setValue(LendPreferences::THUMB_MAX_WIDTH, 321, $this->login)); + + $test_response = $this->app->handle($this->createRequest(route_name: 'objectslend_preferences')); + $this->assertSame(200, $test_response->getStatusCode()); + $body = (string)$test_response->getBody(); + $this->assertStringContainsString('name="pref_objectslend_thumb_max_width"', $body); + $this->assertStringContainsString('value="321"', $body); + $this->assertStringContainsString('name="pref_objectslend_view_serial"', $body); + } + + /** + * Store preferences: unchecked boxes are off, unknown names are ignored + */ + public function testStorePreferences(): void + { + $this->logSuperAdmin(); + $request = $this->createRequest(route_name: 'store_objectlend_preferences', method: 'POST') + ->withParsedBody([ + LendPreferences::VIEW_SERIAL => '1', + LendPreferences::GENERATED_CONTRIBUTION_TYPE_ID => '3', + LendPreferences::GENERATED_CONTRIB_INFO_TEXT => ' Rent of {NAME} ', + LendPreferences::THUMB_MAX_WIDTH => '200', + 'pref_nom' => 'Hijacked', + ]); + $test_response = $this->app->handle($request); + $this->assertSame(302, $test_response->getStatusCode()); + $this->assertSame( + [$this->routeparser->urlFor('objectslend_preferences')], + $test_response->getHeader('Location') + ); + $this->expectFlashData(['success_detected' => ['Preferences have been successfully stored!']]); + + $lendsprefs = new LendPreferences($this->preferences); + $this->assertTrue($lendsprefs->isEnabled(LendPreferences::VIEW_SERIAL)); + //defaults on, but not posted + $this->assertFalse($lendsprefs->isEnabled(LendPreferences::ENABLE_MEMBER_RENT_OBJECT)); + $this->assertFalse($lendsprefs->imagesInLists()); + $this->assertSame(3, $lendsprefs->getContributionTypeId()); + $this->assertSame('Rent of {NAME}', $lendsprefs->getContributionText()); + $this->assertSame(200, $lendsprefs->getThumbWidth()); + //not posted: kept + $this->assertSame($this->orig_prefs[LendPreferences::THUMB_MAX_HEIGHT], $lendsprefs->getThumbHeight()); + $this->assertNotSame('Hijacked', $this->preferences->pref_nom); + } + + /** + * Invalid value is reported + */ + public function testStoreInvalidPreference(): void + { + $this->logSuperAdmin(); + $request = $this->createRequest(route_name: 'store_objectlend_preferences', method: 'POST') + ->withParsedBody([LendPreferences::THUMB_MAX_WIDTH => '0']); + $test_response = $this->app->handle($request); + $this->assertSame(302, $test_response->getStatusCode()); + //an invalid value is not stored + $this->preferences->load(); + $this->assertNotSame(0, (new LendPreferences($this->preferences))->getThumbWidth()); + + $flash = $this->flash_data['slimFlash'] ?? []; + $this->assertArrayNotHasKey('success_detected', $flash); + $this->assertArrayHasKey('error_detected', $flash); + $this->flash_data = []; + } + + /** + * Preferences are for admins only + */ + public function testStorePreferencesAsMember(): void + { + $mdata = $this->dataAdherentOne(); + $this->getMemberOne(); + $this->assertTrue($this->login->login($mdata['login_adh'], $mdata['mdp_adh'])); + $request = $this->createRequest(route_name: 'store_objectlend_preferences', method: 'POST') + ->withParsedBody([LendPreferences::THUMB_MAX_WIDTH => '222']); + $this->app->handle($request); + $this->assertNotSame(222, (new LendPreferences($this->preferences))->getThumbWidth()); + } +} diff --git a/tests/GaletteObjectsLend/Controllers/tests/units/ObjectsController.php b/tests/GaletteObjectsLend/Controllers/tests/units/ObjectsController.php index a4bf6220..b9695ded 100644 --- a/tests/GaletteObjectsLend/Controllers/tests/units/ObjectsController.php +++ b/tests/GaletteObjectsLend/Controllers/tests/units/ObjectsController.php @@ -15,7 +15,7 @@ use GaletteObjectsLend\Entity\LendObject; use GaletteObjectsLend\Entity\LendRent; use GaletteObjectsLend\Entity\LendStatus; -use GaletteObjectsLend\Entity\Preferences; +use GaletteObjectsLend\LendPreferences; /** * Objects controller tests @@ -40,8 +40,10 @@ public function setUp(): void { parent::setUp(); - $prefs = new Preferences($this->zdb); - $this->orig_prefs = $prefs->getPreferences(); + $this->orig_prefs = []; + foreach (array_keys(LendPreferences::getSchema()) as $name) { + $this->orig_prefs[$name] = $this->preferences->getPluginValue($name); + } $status = new LendStatus($this->zdb); $status->setText('In stock'); @@ -70,8 +72,9 @@ public function tearDown(): void { $this->login->logout(); - $prefs = new Preferences($this->zdb); - $prefs->store($this->orig_prefs); + foreach ($this->orig_prefs as $name => $value) { + $this->preferences->setValue($name, $value, $this->login); + } $update = $this->zdb->update(LEND_PREFIX . LendObject::TABLE); $update->set([LendRent::PK => null]); @@ -102,13 +105,15 @@ public function tearDown(): void */ private function setPrefs(bool $member_rent, bool $auto_contrib = false): void { - $prefs = new Preferences($this->zdb); - $values = $this->orig_prefs; - $values[Preferences::PARAM_ENABLE_MEMBER_RENT_OBJECT] = (int)$member_rent; - $values[Preferences::PARAM_AUTO_GENERATE_CONTRIBUTION] = (int)$auto_contrib; - $values[Preferences::PARAM_GENERATED_CONTRIBUTION_TYPE_ID] = 5; - $values[Preferences::PARAM_GENERATED_CONTRIB_INFO_TEXT] = 'Rent of {NAME} ({SERIAL_NUMBER})'; - $this->assertTrue($prefs->store($values)); + $values = [ + LendPreferences::ENABLE_MEMBER_RENT_OBJECT => (int)$member_rent, + LendPreferences::AUTO_GENERATE_CONTRIBUTION => (int)$auto_contrib, + LendPreferences::GENERATED_CONTRIBUTION_TYPE_ID => 5, + LendPreferences::GENERATED_CONTRIB_INFO_TEXT => 'Rent of {NAME} ({SERIAL_NUMBER})', + ]; + foreach ($values as $name => $value) { + $this->assertTrue($this->preferences->setValue($name, $value, $this->login)); + } } /** @@ -465,7 +470,7 @@ public function testFilterOnField(): void $filters = $this->session->plugin_objectslend_objects_filter; $this->assertSame(\GaletteObjectsLend\Repository\Objects::FILTER_SERIAL, $filters->field_filter); - $objects = new \GaletteObjectsLend\Repository\Objects($this->zdb, $this->preferences, $this->login, new Preferences($this->zdb), $filters); + $objects = new \GaletteObjectsLend\Repository\Objects($this->zdb, $this->preferences, $this->login, new LendPreferences($this->preferences), $filters); $list = $objects->getObjectsList(true); $this->assertCount(1, $list); } diff --git a/tests/GaletteObjectsLend/Entity/tests/units/LendObject.php b/tests/GaletteObjectsLend/Entity/tests/units/LendObject.php index 2a09ce8c..e2657171 100644 --- a/tests/GaletteObjectsLend/Entity/tests/units/LendObject.php +++ b/tests/GaletteObjectsLend/Entity/tests/units/LendObject.php @@ -20,6 +20,7 @@ class LendObject extends GaletteTestCase { protected int $seed = 20240522000325; + protected bool $load_plugins = true; private int $active_category_id; private int $inactive_category_id; @@ -93,7 +94,7 @@ public function testCrud(): void $this->zdb, $this->preferences, $this->login, - new \GaletteObjectsLend\Entity\Preferences($this->zdb) + new \GaletteObjectsLend\LendPreferences($this->preferences) ); $object = new \GaletteObjectsLend\Entity\LendObject($this->zdb); diff --git a/tests/GaletteObjectsLend/Entity/tests/units/Preferences.php b/tests/GaletteObjectsLend/Entity/tests/units/Preferences.php deleted file mode 100644 index 6b584c4f..00000000 --- a/tests/GaletteObjectsLend/Entity/tests/units/Preferences.php +++ /dev/null @@ -1,118 +0,0 @@ - - */ -class Preferences extends GaletteTestCase -{ - protected int $seed = 20240519131740; - - /** - * Test defaults - */ - public function testDefaults(): void - { - $prefs = new \GaletteObjectsLend\Entity\Preferences($this->zdb); - $this->assertSame(128, $prefs->getThumbWidth()); - $this->assertSame(128, $prefs->getThumbHeight()); - $this->assertTrue($prefs->imagesInLists()); - $this->assertTrue($prefs->showFullsize()); - - $all_prefs = $prefs->getPreferences(); - $this->assertCount(17, $all_prefs); - foreach ($all_prefs as $code => $value) { - $this->assertSame($value, $prefs->$code); - } - - $prefs = new \GaletteObjectsLend\Entity\Preferences($this->zdb, false); - $this->assertTrue($prefs->load()); - - $prefs = new \GaletteObjectsLend\Entity\Preferences($this->zdb, false); - $this->assertCount(17, $prefs->getPreferences()); - - $this->expectException(\RuntimeException::class); - $this->assertSame(null, $prefs->NON_EXISTING); // @phpstan-ignore property.notFound - } - - /** - * Test add and update - */ - public function testCrud(): void - { - $prefs = new \GaletteObjectsLend\Entity\Preferences($this->zdb); - $orig_prefs = $prefs->getPreferences(); - $this->assertCount(17, $orig_prefs); - - $all_prefs = $orig_prefs; - - $this->assertSame(128, (int)$all_prefs[\GaletteObjectsLend\Entity\Preferences::PARAM_THUMB_MAX_WIDTH]); - $this->assertSame(128, (int)$all_prefs[\GaletteObjectsLend\Entity\Preferences::PARAM_THUMB_MAX_HEIGHT]); - $all_prefs[\GaletteObjectsLend\Entity\Preferences::PARAM_THUMB_MAX_WIDTH] = 256; - $all_prefs[\GaletteObjectsLend\Entity\Preferences::PARAM_THUMB_MAX_HEIGHT] = 256; - - $this->assertTrue($prefs->store($all_prefs)); - $all_prefs = $prefs->getPreferences(); - $this->assertCount(17, $all_prefs); - $this->assertSame(256, (int)$all_prefs[\GaletteObjectsLend\Entity\Preferences::PARAM_THUMB_MAX_WIDTH]); - $this->assertSame(256, (int)$all_prefs[\GaletteObjectsLend\Entity\Preferences::PARAM_THUMB_MAX_HEIGHT]); - - $this->assertTrue($prefs->store($orig_prefs)); - } - - /** - * Stored values are read back from the database - */ - public function testReload(): void - { - $prefs = new \GaletteObjectsLend\Entity\Preferences($this->zdb); - $orig_prefs = $prefs->getPreferences(); - - $all_prefs = $orig_prefs; - $all_prefs[\GaletteObjectsLend\Entity\Preferences::PARAM_ENABLE_MEMBER_RENT_OBJECT] = 0; - $all_prefs[\GaletteObjectsLend\Entity\Preferences::PARAM_AUTO_GENERATE_CONTRIBUTION] = 1; - $all_prefs[\GaletteObjectsLend\Entity\Preferences::PARAM_GENERATED_CONTRIBUTION_TYPE_ID] = 3; - $all_prefs[\GaletteObjectsLend\Entity\Preferences::PARAM_GENERATED_CONTRIB_INFO_TEXT] = 'Rent of {NAME}'; - $all_prefs[\GaletteObjectsLend\Entity\Preferences::PARAM_THUMB_MAX_WIDTH] = 200; - $this->assertTrue($prefs->store($all_prefs)); - - try { - $prefs = new \GaletteObjectsLend\Entity\Preferences($this->zdb); - $this->assertFalse((bool)$prefs->{\GaletteObjectsLend\Entity\Preferences::PARAM_ENABLE_MEMBER_RENT_OBJECT}); - $this->assertTrue((bool)$prefs->{\GaletteObjectsLend\Entity\Preferences::PARAM_AUTO_GENERATE_CONTRIBUTION}); - $this->assertSame( - 3, - (int)$prefs->{\GaletteObjectsLend\Entity\Preferences::PARAM_GENERATED_CONTRIBUTION_TYPE_ID} - ); - $this->assertSame( - 'Rent of {NAME}', - $prefs->{\GaletteObjectsLend\Entity\Preferences::PARAM_GENERATED_CONTRIB_INFO_TEXT} - ); - $this->assertSame(200, $prefs->getThumbWidth()); - $this->assertSame(128, $prefs->getThumbHeight()); - $this->assertCount(17, $prefs->getPreferences()); - } finally { - $this->assertTrue($prefs->store($orig_prefs)); - } - - $prefs = new \GaletteObjectsLend\Entity\Preferences($this->zdb); - $this->assertSame(128, $prefs->getThumbWidth()); - $this->assertSame( - (string)$orig_prefs[\GaletteObjectsLend\Entity\Preferences::PARAM_GENERATED_CONTRIB_INFO_TEXT], - (string)$prefs->{\GaletteObjectsLend\Entity\Preferences::PARAM_GENERATED_CONTRIB_INFO_TEXT} - ); - } -} diff --git a/tests/GaletteObjectsLend/Repository/tests/units/Objects.php b/tests/GaletteObjectsLend/Repository/tests/units/Objects.php index 2931298f..5bd2fb7b 100644 --- a/tests/GaletteObjectsLend/Repository/tests/units/Objects.php +++ b/tests/GaletteObjectsLend/Repository/tests/units/Objects.php @@ -20,8 +20,9 @@ class Objects extends GaletteTestCase { protected int $seed = 20240526224135; + protected bool $load_plugins = true; - protected \GaletteObjectsLend\Entity\Preferences $lend_prefs; + protected \GaletteObjectsLend\LendPreferences $lend_prefs; /** * Set up tests @@ -29,7 +30,7 @@ class Objects extends GaletteTestCase public function setUp(): void { parent::setUp(); - $this->lend_prefs = new \GaletteObjectsLend\Entity\Preferences($this->zdb); + $this->lend_prefs = new \GaletteObjectsLend\LendPreferences($this->preferences); } /** @@ -153,17 +154,18 @@ public function testGetList(): void $this->assertSame(2, $objects->getCount()); //disable view description - $orig_prefs = $this->lend_prefs->getPreferences(); - $all_prefs = $orig_prefs; - $all_prefs[\GaletteObjectsLend\Entity\Preferences::PARAM_VIEW_DESCRIPTION] = 0; - $this->assertTrue($this->lend_prefs->store($all_prefs)); + $this->assertTrue( + $this->preferences->setValue(\GaletteObjectsLend\LendPreferences::VIEW_DESCRIPTION, 0, $this->login) + ); //only one result (first in name only) $this->assertCount(1, $objects->getObjectsList(true)); $this->assertSame(1, $objects->getCount()); //reset prefs - $this->assertTrue($this->lend_prefs->store($orig_prefs)); + $this->assertTrue( + $this->preferences->setValue(\GaletteObjectsLend\LendPreferences::VIEW_DESCRIPTION, 1, $this->login) + ); $filters->field_filter = \GaletteObjectsLend\Repository\Objects::FILTER_ID; $filters->filter_str = (string)$third_object_id; diff --git a/tests/GaletteObjectsLend/tests/units/LendPreferences.php b/tests/GaletteObjectsLend/tests/units/LendPreferences.php new file mode 100644 index 00000000..8cd81bc1 --- /dev/null +++ b/tests/GaletteObjectsLend/tests/units/LendPreferences.php @@ -0,0 +1,76 @@ + + */ +class LendPreferences extends GaletteTestCase +{ + protected int $seed = 20240519131740; + protected bool $load_plugins = true; + + /** + * Preferences are declared to core, with their defaults + */ + public function testDefaults(): void + { + $schema = \GaletteObjectsLend\LendPreferences::getSchema(); + $this->assertCount(17, $schema); + foreach (array_keys($schema) as $name) { + $this->assertStringStartsWith(\GaletteObjectsLend\LendPreferences::PREFIX, $name); + $this->assertSame('objectslend', PreferencesSchema::getOwner($name)); + } + + $prefs = new \GaletteObjectsLend\LendPreferences($this->preferences); + $this->assertSame(128, $prefs->getThumbWidth()); + $this->assertSame(128, $prefs->getThumbHeight()); + $this->assertTrue($prefs->imagesInLists()); + $this->assertTrue($prefs->showFullsize()); + $this->assertTrue($prefs->isEnabled(\GaletteObjectsLend\LendPreferences::ENABLE_MEMBER_RENT_OBJECT)); + $this->assertFalse($prefs->isEnabled(\GaletteObjectsLend\LendPreferences::VIEW_SERIAL)); + $this->assertSame(5, $prefs->getContributionTypeId()); + $this->assertSame('Location de {NAME} {DESCRIPTION} {SERIAL_NUMBER}', $prefs->getContributionText()); + + $values = $prefs->toArray(); + $this->assertCount(17, $values); + $this->assertSame(128, $values['thumb_max_width']); + $this->assertFalse($values['view_serial']); + } + + /** + * Values are read from core preferences + */ + public function testChange(): void + { + $prefs = new \GaletteObjectsLend\LendPreferences($this->preferences); + $this->assertTrue( + $this->preferences->setValue(\GaletteObjectsLend\LendPreferences::THUMB_MAX_WIDTH, 256, $this->login) + ); + $this->assertTrue( + $this->preferences->setValue(\GaletteObjectsLend\LendPreferences::VIEW_SERIAL, 1, $this->login) + ); + $this->assertSame(256, $prefs->getThumbWidth()); + $this->assertTrue($prefs->isEnabled(\GaletteObjectsLend\LendPreferences::VIEW_SERIAL)); + + $this->assertFalse( + $this->preferences->setValue(\GaletteObjectsLend\LendPreferences::THUMB_MAX_HEIGHT, -3, $this->login) + ); + //an invalid value is not stored + $this->preferences->load(); + $this->assertSame(128, $prefs->getThumbHeight()); + } +} diff --git a/tests/GaletteObjectsLend/tests/units/LendService.php b/tests/GaletteObjectsLend/tests/units/LendService.php index daf44a04..4b8beabf 100644 --- a/tests/GaletteObjectsLend/tests/units/LendService.php +++ b/tests/GaletteObjectsLend/tests/units/LendService.php @@ -15,7 +15,7 @@ use GaletteObjectsLend\Entity\LendObject; use GaletteObjectsLend\Entity\LendRent; use GaletteObjectsLend\Entity\LendStatus; -use GaletteObjectsLend\Entity\Preferences; +use GaletteObjectsLend\LendPreferences; use GaletteObjectsLend\LendException; /** @@ -29,6 +29,7 @@ class LendService extends GaletteTestCase { protected int $seed = 20260923081245; protected bool $db_transactions = false; + protected bool $load_plugins = true; private int $instock_status; private int $lent_status; @@ -43,14 +44,19 @@ public function setUp(): void { parent::setUp(); - $prefs = new Preferences($this->zdb); - $this->orig_prefs = $prefs->getPreferences(); - $values = $this->orig_prefs; - $values[Preferences::PARAM_ENABLE_MEMBER_RENT_OBJECT] = 1; - $values[Preferences::PARAM_AUTO_GENERATE_CONTRIBUTION] = 1; - $values[Preferences::PARAM_GENERATED_CONTRIBUTION_TYPE_ID] = 5; - $values[Preferences::PARAM_GENERATED_CONTRIB_INFO_TEXT] = 'Service rent of {NAME}'; - $this->assertTrue($prefs->store($values)); + $this->orig_prefs = []; + foreach (array_keys(LendPreferences::getSchema()) as $name) { + $this->orig_prefs[$name] = $this->preferences->getPluginValue($name); + } + $values = [ + LendPreferences::ENABLE_MEMBER_RENT_OBJECT => 1, + LendPreferences::AUTO_GENERATE_CONTRIBUTION => 1, + LendPreferences::GENERATED_CONTRIBUTION_TYPE_ID => 5, + LendPreferences::GENERATED_CONTRIB_INFO_TEXT => 'Service rent of {NAME}', + ]; + foreach ($values as $name => $value) { + $this->assertTrue($this->preferences->setValue($name, $value, $this->login)); + } $status = new LendStatus($this->zdb); $status->setText('In stock'); @@ -80,8 +86,9 @@ public function tearDown(): void { $this->login->logout(); - $prefs = new Preferences($this->zdb); - $prefs->store($this->orig_prefs); + foreach ($this->orig_prefs as $name => $value) { + $this->preferences->setValue($name, $value, $this->login); + } $update = $this->zdb->update(LEND_PREFIX . LendObject::TABLE) ->set([LendRent::PK => null]); @@ -109,7 +116,7 @@ public function tearDown(): void */ private function getService(): \GaletteObjectsLend\LendService { - return new \GaletteObjectsLend\LendService($this->zdb, $this->preferences, $this->login, new Preferences($this->zdb)); + return new \GaletteObjectsLend\LendService($this->zdb, $this->preferences, $this->login, new LendPreferences($this->preferences)); } /** From 0e3601889bd6727c5dfce5bfd34210584ebfc0aa Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski Date: Thu, 24 Sep 2026 07:21:48 +0200 Subject: [PATCH 2/9] Replace thumbs icons in lists with state icons --- templates/default/categories_list.html.twig | 2 +- templates/default/objects_list.html.twig | 2 +- templates/default/status_list.html.twig | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/templates/default/categories_list.html.twig b/templates/default/categories_list.html.twig index 8112c029..fdef3935 100644 --- a/templates/default/categories_list.html.twig +++ b/templates/default/categories_list.html.twig @@ -110,7 +110,7 @@ {{ categ.getName(false) }} - + {% if categ.isActive() %}{{ _T("Active", "objectslend") }}{% else %}{{ _T("Inactive", "objectslend") }}{% endif %} {% if login.isAdmin() or login.isStaff() %} diff --git a/templates/default/objects_list.html.twig b/templates/default/objects_list.html.twig index 94f9a844..3cd1b633 100644 --- a/templates/default/objects_list.html.twig +++ b/templates/default/objects_list.html.twig @@ -282,7 +282,7 @@ {% if login.isAdmin() or login.isStaff() %} {% if object.isActive() %}{{ _T("Active", "objectslend") }}{% else %}{{ _T("Inactive", "objectslend") }}{% endif %} diff --git a/templates/default/status_list.html.twig b/templates/default/status_list.html.twig index 6d43ba6d..f5570344 100644 --- a/templates/default/status_list.html.twig +++ b/templates/default/status_list.html.twig @@ -142,19 +142,19 @@ {% if status.isActive() %} - + {{ _T("Active", "objectslend") }} {% else %} - + {{ _T("Inactive", "objectslend") }} {% endif %} {% if status.isInStock() %} - + {{ _T("In stock", "objectslend") }} {% else %} - + {{ _T("Not in stock", "objectslend") }} {% endif %} From a85b753730ad7ae575f3d19dbcd75bd5dc1f0fd8 Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski Date: Thu, 24 Sep 2026 07:51:40 +0200 Subject: [PATCH 3/9] Align preferences toggles --- templates/default/preferences.html.twig | 34 +++++++++++++++++++------ 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/templates/default/preferences.html.twig b/templates/default/preferences.html.twig index cdc8f6df..8fcc953c 100644 --- a/templates/default/preferences.html.twig +++ b/templates/default/preferences.html.twig @@ -6,16 +6,33 @@ {% extends 'page.html.twig' %} +{# labels in one column, toggles in the next: they line up whatever the label length #} {% macro toggles(boxes, values) %} {% for box in boxes %} - {% include "components/forms/checkbox.html.twig" with { - id: box.name, - name: 'pref_objectslend_' ~ box.name, - value: 1, - label: box.label, - checked: values[box.name], - tip_html: box.tip ?? '', - } %} +
+
+ {% include "components/forms/tip.html.twig" with { + label: box.label, + for: box.name, + tip: box.tip ?? '', + id: box.name, + html: true, + } only %} +
+
+
+ + +
+
+
{% endfor %} {% endmacro %} @@ -135,4 +152,5 @@
+ {% endblock %} From b0f195bf686d3a0921f9eb337df505e0165dfa19 Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski Date: Thu, 24 Sep 2026 07:51:40 +0200 Subject: [PATCH 4/9] Show object form next to its photo, history and status --- templates/default/history_modal.js.twig | 36 +++ templates/default/objects_edit.html.twig | 365 +++++++++++------------ templates/default/objects_list.html.twig | 30 +- 3 files changed, 216 insertions(+), 215 deletions(-) create mode 100644 templates/default/history_modal.js.twig diff --git a/templates/default/history_modal.js.twig b/templates/default/history_modal.js.twig new file mode 100644 index 00000000..1e3f7064 --- /dev/null +++ b/templates/default/history_modal.js.twig @@ -0,0 +1,36 @@ +{# + # This file is part of Galette Objects Lend plugin (https://galette.eu). + # SPDX-FileCopyrightText: Copyright © 2013-2026 The Galette Team + # SPDX-License-Identifier: GPL-3.0-or-later + #} + +{# Open object history in a modal from any `.object_hist` link #} + $(function(){ + $('.object_hist').on('click', function(e) { + e.preventDefault(); + var _this = $(this); + + $.ajax({ + url: _this.attr('href'), + type: 'GET', + datatype: 'html', + {% include "elements/js/loader.js.twig" with { + loader: "action", + } %}, + success: function(res){ + var _content = $($.parseHTML(res)).find('div.main-content').children(); + {% include "elements/js/modal.js.twig" with { + modal_title_twig: _T("History of object", "objectslend")|e("js"), + modal_content: "_content", + modal_class: "fullscreen", + modal_content_class: "scrolling", + modal_deny_only: true, + modal_cancel_text: _T("Close")|e("js") + } %} + }, + error: function(){ + alert("{{ _T("An error occurred loading history display :(", "objectslend")|e("js") }}") + } + }); + }); + }); diff --git a/templates/default/objects_edit.html.twig b/templates/default/objects_edit.html.twig index 24a6a411..8629283b 100644 --- a/templates/default/objects_edit.html.twig +++ b/templates/default/objects_edit.html.twig @@ -7,221 +7,214 @@ {% extends 'page.html.twig' %} {% block content %} -
-
-
- - {{ _T("Object", "objectslend") }} -
-
- {% include "components/forms/text.html.twig" with { - id: 'name', - value: object.getName(), - label: _T("Name:", "objectslend"), - required: true - } %} - - {% include "components/forms/text.html.twig" with { - id: 'description', - value: object.getDescription(), - label: _T("Description:", "objectslend") - } %} - - {% if lendsprefs.view_category %} - {% set category_list_values = {(0): _T("--- Select a category ---", "objectslend")} %} - {% for categ in categories %} - {% set category_list_values = category_list_values + {(categ.getId()): categ.getName()} %} - {% endfor %} - - {% include "components/forms/select.html.twig" with { - id: 'category_id', - value: object.getCategoryId(), - values: category_list_values, - label: _T("Category:", "objectslend") +
+
+
+ +
+ {% include "components/forms/text.html.twig" with { + id: 'name', + value: object.getName(), + label: _T("Name:", "objectslend"), + required: true } %} - {% endif %} + {% include "components/forms/text.html.twig" with { + id: 'description', + value: object.getDescription(), + label: _T("Description:", "objectslend") + } %} + + {% if lendsprefs.view_category %} + {% set category_list_values = {(0): _T("--- Select a category ---", "objectslend")} %} + {% for categ in categories %} + {% set category_list_values = category_list_values + {(categ.getId()): categ.getName()} %} + {% endfor %} + + {% include "components/forms/select.html.twig" with { + id: 'category_id', + value: object.getCategoryId(), + values: category_list_values, + label: _T("Category:", "objectslend") + } %} + + {% endif %} - {% include "components/forms/text.html.twig" with { - id: 'serial', - value: object.getSerialNumber(), - label: _T("Serial number:", "objectslend") - } %} - - {% include "components/forms/text.html.twig" with { - id: 'price', - value: object.getPrice()|number_format(2, '.', ''), - label: _T("Price:", "objectslend"), - rightlabel: '€' - } %} - - {% include "components/forms/text.html.twig" with { - id: 'rent_price', - value: object.getRentPrice()|number_format(2, '.', ''), - label: _T("Borrow price:", "objectslend"), - rightlabel: '€' - } %} - - {% include "components/forms/checkbox.html.twig" with { - id: 'price_per_day', - value: 1, - label: _T("Price per rental day:", "objectslend"), - tip: _T("The price applies on each rental day if checked, only once otherwise", "objectslend"), - checked: object.isPricePerDay() - } %} - - {% include "components/forms/text.html.twig" with { - id: 'dimension', - value: object.getDimension(), - label: _T("Dimensions:", "objectslend"), - rightlabel: _T("Cm", "objectslend") - } %} - - {% include "components/forms/text.html.twig" with { - id: 'weight', - value: object.getWeight()|number_format(3, '.', ''), - label: _T("Weight:", "objectslend"), - rightlabel: _T("Kg", "objectslend") - } %} - - {% include "components/forms/checkbox.html.twig" with { - id: 'is_active', - value: 1, - label: _T("Active:", "objectslend"), - checked: object.isObjectActive() - } %} - - - {% if not object.getId() %} - {% set state_list_values = {(0): _T("--- Select a status ---", "objectslend")} %} - {% for sta in statuses %} - {% set state_list_values = state_list_values + {(sta.getId()): sta.getText()} %} - {% endfor %} - - {% include "components/forms/select.html.twig" with { - id: '1st_status', - value: '', - values: state_list_values, - label: _T("Where is the object?", "objectslend") + {% include "components/forms/text.html.twig" with { + id: 'serial', + value: object.getSerialNumber(), + label: _T("Serial number:", "objectslend") + } %} + + {% include "components/forms/text.html.twig" with { + id: 'price', + value: object.getPrice()|number_format(2, '.', ''), + label: _T("Price:", "objectslend"), + rightlabel: '€' + } %} + + {% include "components/forms/text.html.twig" with { + id: 'rent_price', + value: object.getRentPrice()|number_format(2, '.', ''), + label: _T("Borrow price:", "objectslend"), + rightlabel: '€' } %} - {% endif %} -
-
-
-
- - {{ _T("Object's photo", "objectslend") }} -
-
- {{ _T( -
- {% if picture.hasPicture() %} {% include "components/forms/checkbox.html.twig" with { - id: 'del_picture', + id: 'price_per_day', value: 1, - label: _T("Delete image"), - required: false + label: _T("Price per rental day:", "objectslend"), + tip: _T("The price applies on each rental day if checked, only once otherwise", "objectslend"), + checked: object.isPricePerDay() } %} - {% endif %} -
- - -
-
-
-
- - {% if object.getId() %} - - {{ _T("Duplicate", "objectslend") }} - - - {{ _T("Print object", "objectslend") }} - - {% endif %} - -
- + {% include "components/forms/text.html.twig" with { + id: 'dimension', + value: object.getDimension(), + label: _T("Dimensions:", "objectslend"), + rightlabel: _T("Cm", "objectslend") + } %} - {% if object.getId() %} -
-
- - {{ _T("History", "objectslend") }} -
-
- {% include "@PluginGaletteObjectslend/list_lent_object.html.twig" with {rents: rents, mode: 'ajax'} %} -
-
+ {% include "components/forms/text.html.twig" with { + id: 'weight', + value: object.getWeight()|number_format(3, '.', ''), + label: _T("Weight:", "objectslend"), + rightlabel: _T("Kg", "objectslend") + } %} - {# FIXME: should not be here, create a specific page. #} -
-
-
-
- - {{ _T("Change status", "objectslend") }} -
-
- {% set state_list_values = {(-1): _T("--- Select a status ---", "objectslend")} %} + {% include "components/forms/checkbox.html.twig" with { + id: 'is_active', + value: 1, + label: _T("Active:", "objectslend"), + checked: object.isObjectActive() + } %} + + + {% if not object.getId() %} + {% set state_list_values = {(0): _T("--- Select a status ---", "objectslend")} %} {% for sta in statuses %} - {% set text = sta.getText() %} - {% if sta.isInStock() %} - {% set text = text ~ ' (' ~ _T("In stock", "objectslend") ~ ')' %} - {% endif %} - {% set state_list_values = state_list_values + {(sta.getId()): text} %} + {% set state_list_values = state_list_values + {(sta.getId()): sta.getText()} %} {% endfor %} {% include "components/forms/select.html.twig" with { - id: 'new_status', + id: '1st_status', value: '', values: state_list_values, - label: _T("Status", "objectslend") + label: _T("Where is the object?", "objectslend") } %} - - {% include "components/forms/text.html.twig" with { - id: 'new_comment', - value: '', - label: _T("Comments", "objectslend"), - size: 100, - maxlength: 200 - } %} - - {% include 'components/forms/member_dropdown.html.twig' with { - 'id': 'new_adh', - 'label': _T("Member", "objectslend"), - 'value': null, - 'component_class': 'inline field' - } %} - -
+ {% endif %}
-
+
+ {% if object.getId() %} + + {{ _T("Duplicate", "objectslend") }} + + + {{ _T("Print object", "objectslend") }} + + {% endif %} +
-
- {% endif %} + + {# photo fields are part of the object form, out of it in the page #} +
+
+
+
{{ _T("Object's photo", "objectslend") }}
+
+ {{ _T( +
+
+ {% if picture.hasPicture() %} +
+
+ + +
+
+ {% endif %} +
+
+ + +
+
+
+
+ + {% if object.getId() %} + + +
+
{{ _T("Change status", "objectslend") }}
+ {% set state_list_values = {(-1): _T("--- Select a status ---", "objectslend")} %} + {% for sta in statuses %} + {% set text = sta.getText() %} + {% if sta.isInStock() %} + {% set text = text ~ ' (' ~ _T("In stock", "objectslend") ~ ')' %} + {% endif %} + {% set state_list_values = state_list_values + {(sta.getId()): text} %} + {% endfor %} + + {% include "components/forms/select.html.twig" with { + id: 'new_status', + value: '', + values: state_list_values, + label: _T("Status", "objectslend") + } %} + + {% include "components/forms/text.html.twig" with { + id: 'new_comment', + value: '', + label: _T("Comments", "objectslend"), + size: 100, + maxlength: 200 + } %} + + {% include 'components/forms/member_dropdown.html.twig' with { + 'id': 'new_adh', + 'label': _T("Member", "objectslend"), + 'value': null, + 'component_class': 'field' + } %} + + + +
+ {% endif %} +
+
+
+
{% endblock %} {% block javascripts %} {% endblock %} diff --git a/templates/default/objects_list.html.twig b/templates/default/objects_list.html.twig index 3cd1b633..7c7fd650 100644 --- a/templates/default/objects_list.html.twig +++ b/templates/default/objects_list.html.twig @@ -413,35 +413,7 @@ {{ parent() }} {% endblock %} From 9b13e94a4df39bd54a974c41099f2063d978641e Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski Date: Thu, 24 Sep 2026 07:51:40 +0200 Subject: [PATCH 5/9] Add sample data, loadable into an empty catalog --- _define.php | 1 + _routes.php | 5 + .../Controllers/MainController.php | 46 ++- lib/GaletteObjectsLend/SampleData.php | 295 ++++++++++++++++++ scripts/sample-data.json | 217 +++++++++++++ templates/default/preferences.html.twig | 14 + .../tests/units/MainController.php | 35 +++ .../tests/units/SampleData.php | 151 +++++++++ 8 files changed, 763 insertions(+), 1 deletion(-) create mode 100644 lib/GaletteObjectsLend/SampleData.php create mode 100644 scripts/sample-data.json create mode 100644 tests/GaletteObjectsLend/tests/units/SampleData.php diff --git a/_define.php b/_define.php index 66e76c88..d5deb590 100644 --- a/_define.php +++ b/_define.php @@ -20,6 +20,7 @@ acls: [ 'objectslend_preferences' => 'admin', 'store_objectlend_preferences' => 'admin', + 'objectslend_sample_data' => 'admin', 'objectslend_category_add' => 'staff', 'objectslend_category_edit' => 'staff', 'objectslend_category_action_add' => 'staff', diff --git a/_routes.php b/_routes.php index 21a47ffa..49c2af42 100644 --- a/_routes.php +++ b/_routes.php @@ -32,6 +32,11 @@ [MainController::class, 'storePreferences'] )->setName('store_objectlend_preferences')->add(Authenticate::class); +$app->post( + '/sample-data', + [MainController::class, 'loadSampleData'] +)->setName('objectslend_sample_data')->add(Authenticate::class); + $app->get( '/category/add', [CategoriesController::class, 'add'] diff --git a/lib/GaletteObjectsLend/Controllers/MainController.php b/lib/GaletteObjectsLend/Controllers/MainController.php index bb5e042c..658ba2d6 100644 --- a/lib/GaletteObjectsLend/Controllers/MainController.php +++ b/lib/GaletteObjectsLend/Controllers/MainController.php @@ -10,10 +10,12 @@ namespace GaletteObjectsLend\Controllers; +use Analog\Analog; use DI\Attribute\Inject; use Galette\Controllers\AbstractPluginController; use Galette\Entity\ContributionsTypes; use GaletteObjectsLend\LendPreferences; +use GaletteObjectsLend\SampleData; use Slim\Psr7\Request; use Slim\Psr7\Response; @@ -44,7 +46,8 @@ public function preferences(Request $request, Response $response): Response $params = [ 'page_title' => _T('ObjectsLend preferences', 'objectslend'), 'type_cotis_options' => $ctypes->getList(), - 'lendsprefs' => (new LendPreferences($this->preferences))->toArray() + 'lendsprefs' => (new LendPreferences($this->preferences))->toArray(), + 'sample_data' => !(new SampleData($this->zdb))->hasObjects() ]; // display page @@ -105,4 +108,45 @@ public function storePreferences(Request $request, Response $response): Response $this->routeparser->urlFor('objectslend_preferences') ); } + + /** + * Load sample data into an empty catalog + * + * @param Request $request PSR Request + * @param Response $response PSR Response + */ + public function loadSampleData(Request $request, Response $response): Response + { + $sample = new SampleData($this->zdb); + + if ($sample->hasObjects()) { + $this->flash->addMessage( + 'error_detected', + _T("Sample data can only be loaded into an empty catalog.", "objectslend") + ); + } else { + try { + $created = $sample->load($sample->getMembers()); + $this->flash->addMessage( + 'success_detected', + sprintf( + //TRANS: %1$d is the number of objects, %2$d the number of rents + _T('Sample data loaded: %1$d objects, %2$d rents.', 'objectslend'), + $created['objects'], + $created['rents'] + ) + ); + } catch (\Throwable $e) { + Analog::log('Unable to load sample data | ' . $e->getMessage(), Analog::ERROR); + $this->flash->addMessage( + 'error_detected', + _T("Sample data could not be loaded.", "objectslend") + ); + } + } + + return $response + ->withStatus(302) + ->withHeader('Location', $this->routeparser->urlFor('objectslend_objects')); + } } diff --git a/lib/GaletteObjectsLend/SampleData.php b/lib/GaletteObjectsLend/SampleData.php new file mode 100644 index 00000000..6273392f --- /dev/null +++ b/lib/GaletteObjectsLend/SampleData.php @@ -0,0 +1,295 @@ + + * @phpstan-type Rent array{status: string, member?: int, begin: int, end?: int, comment?: string} + * @phpstan-type Item array{ + * name: string, + * description: string, + * serial: string, + * price: float|int, + * rent_price: float|int, + * per_day: bool, + * dimension: string, + * weight: float|int, + * category: ?string, + * active: bool, + * rents: list + * } + */ +final class SampleData +{ + public const string FILE = __DIR__ . '/../../scripts/sample-data.json'; + + /** @var array Category key => id */ + private array $categories = []; + /** @var array Status key => id */ + private array $statuses = []; + + /** + * Constructor + * + * @param Db $zdb Database instance + * @param string $file Data file + */ + public function __construct(private readonly Db $zdb, private readonly string $file = self::FILE) + { + } + + /** + * Does the catalog already hold objects? + * + * Sample data are meant for an empty catalog; loading them twice would + * duplicate every object. + */ + public function hasObjects(): bool + { + $select = $this->zdb->select(LEND_PREFIX . LendObject::TABLE); + $select->limit(1); + return $this->zdb->execute($select)->count() > 0; + } + + /** + * Get active members ids, as sample data would pick them + * + * @param int $limit Maximum number of members + * + * @return list + */ + public function getMembers(int $limit = 10): array + { + $select = $this->zdb->select(Adherent::TABLE); + $select->columns([Adherent::PK]) + ->where(['activite_adh' => true]) + ->order(Adherent::PK) + ->limit($limit); + + $ids = []; + foreach ($this->zdb->execute($select) as $row) { + $ids[] = (int)$row->{Adherent::PK}; + } + return $ids; + } + + /** + * Load sample data, in a single transaction + * + * @param list $members Members to give rents to + * + * @return array{categories: int, statuses: int, objects: int, rents: int} What was created + */ + public function load(array $members): array + { + $data = $this->read(); + $created = ['categories' => 0, 'statuses' => 0, 'objects' => 0, 'rents' => 0]; + + $need_transaction = !$this->zdb->inTransaction(); + try { + if ($need_transaction) { + $this->zdb->beginTransaction(); + } + + foreach ($data['categories'] as $category) { + $created['categories'] += (int)$this->addCategory($category); + } + foreach ($data['statuses'] as $status) { + $created['statuses'] += (int)$this->addStatus($status); + } + foreach ($data['objects'] as $item) { + $created['rents'] += $this->addObject($item, $members); + ++$created['objects']; + } + + if ($need_transaction) { + $this->zdb->commit(); + } + } catch (Throwable $e) { + if ($need_transaction) { + $this->zdb->rollback(); + } + throw $e; + } + + return $created; + } + + /** + * Read and check data file + * + * @return array{ + * categories: list, + * statuses: list, + * objects: list + * } + */ + private function read(): array + { + $contents = file_get_contents($this->file); + if ($contents === false) { + throw new \RuntimeException(sprintf('Unable to read sample data file %s', $this->file)); + } + $data = json_decode($contents, true, flags: JSON_THROW_ON_ERROR); + if (!is_array($data) || !isset($data['categories'], $data['statuses'], $data['objects'])) { + throw new \RuntimeException(sprintf('Invalid sample data file %s', $this->file)); + } + return $data; + } + + /** + * Get category, create it if missing + * + * @param array{key: string, name: string, active: bool} $data Category data + * + * @return bool Whether it has been created + */ + private function addCategory(array $data): bool + { + $select = $this->zdb->select(LEND_PREFIX . LendCategory::TABLE) + ->where(['name' => $data['name']]) + ->limit(1); + $row = $this->zdb->execute($select)->current(); + if ($row) { + $this->categories[$data['key']] = (int)$row->{LendCategory::PK}; + return false; + } + + $category = new LendCategory($this->zdb); + $category->setName($data['name'])->setActive($data['active']); + $category->store(); + $this->categories[$data['key']] = (int)$category->getId(); + return true; + } + + /** + * Get status, create it if missing + * + * @param array{key: string, text: string, in_stock: bool, active: bool, days?: int} $data Status data + * + * @return bool Whether it has been created + */ + private function addStatus(array $data): bool + { + $select = $this->zdb->select(LEND_PREFIX . LendStatus::TABLE) + ->where(['status_text' => $data['text']]) + ->limit(1); + $row = $this->zdb->execute($select)->current(); + if ($row) { + $this->statuses[$data['key']] = (int)$row->{LendStatus::PK}; + return false; + } + + $status = new LendStatus($this->zdb); + $status + ->setText($data['text']) + ->setInStock($data['in_stock']) + ->setActive($data['active']) + ->setRentDayNumber($data['days'] ?? null); + $status->store(); + $this->statuses[$data['key']] = (int)$status->getId(); + return true; + } + + /** + * Create object, its rents, and set the last one as current + * + * @param Item $data Object data + * @param list $members Members to give rents to + * + * @return int Number of rents created + */ + private function addObject(array $data, array $members): int + { + $object = new LendObject($this->zdb); + $object + ->setName($data['name']) + ->setDescription($data['description']) + ->setSerialNumber($data['serial']) + ->setPrice((float)$data['price']) + ->setRentPrice((float)$data['rent_price']) + ->setPricePerDay($data['per_day']) + ->setDimension($data['dimension']) + ->setWeight((float)$data['weight']) + ->setActive($data['active']) + ->setCategoryId($data['category'] !== null ? $this->categories[$data['category']] : null); + $object->store(); + $object_id = (int)$object->getId(); + + $rent = null; + $last = count($data['rents']) - 1; + foreach ($data['rents'] as $i => $entry) { + $rent = new LendRent($this->zdb); + $rent + ->setObjectId($object_id) + ->setStatusId($this->statuses[$entry['status']]) + ->setAdherentId($this->getMember($entry['member'] ?? null, $members)) + ->setComments($entry['comment'] ?? '') + ->setDateBegin($this->getDate($entry['begin'])); + //the last rent is the current one + if ($i < $last && isset($entry['end'])) { + $rent->setDateEnd($this->getDate($entry['end'])); + } + $rent->store(); + } + + if ($rent !== null) { + $update = $this->zdb->update(LEND_PREFIX . LendObject::TABLE) + ->set([LendRent::PK => $rent->getId()]) + ->where([LendObject::PK => $object_id]); + $this->zdb->execute($update); + } + + return count($data['rents']); + } + + /** + * Get member for a rent + * + * @param ?int $position Member position in sample data + * @param list $members Available members + */ + private function getMember(?int $position, array $members): ?int + { + if ($position === null || count($members) === 0) { + return null; + } + return $members[$position % count($members)]; + } + + /** + * Get date from a number of days relative to today + * + * @param int $days Days, negative in the past + */ + private function getDate(int $days): string + { + return (new \DateTimeImmutable())->modify(sprintf('%+d days', $days))->format('Y-m-d H:i:s'); + } +} diff --git a/scripts/sample-data.json b/scripts/sample-data.json new file mode 100644 index 00000000..9eb2b363 --- /dev/null +++ b/scripts/sample-data.json @@ -0,0 +1,217 @@ +{ + "_comment": "Sample data for Galette Objects Lend. Dates are days relative to the import; the last rent of each object is its current one. Members are picked among existing ones, by position.", + "categories": [ + {"key": "tools", "name": "Outillage", "active": true}, + {"key": "garden", "name": "Jardin", "active": true}, + {"key": "party", "name": "Fêtes et événements", "active": true}, + {"key": "books", "name": "Ludothèque", "active": true}, + {"key": "old", "name": "Ancien matériel", "active": false} + ], + "statuses": [ + {"key": "shelf", "text": "Local associatif", "in_stock": true, "active": true}, + {"key": "cellar", "text": "Cave de la mairie", "in_stock": true, "active": true}, + {"key": "loan", "text": "Prêt courte durée", "in_stock": false, "active": true, "days": 7}, + {"key": "long", "text": "Prêt longue durée", "in_stock": false, "active": true, "days": 30}, + {"key": "repair", "text": "En réparation", "in_stock": false, "active": true, "days": 21}, + {"key": "lost", "text": "Perdu", "in_stock": false, "active": false} + ], + "objects": [ + { + "name": "Perceuse-visseuse sans fil", + "description": "18 V, deux batteries et chargeur", + "serial": "OUT-001", + "price": 129.90, "rent_price": 2.00, "per_day": true, + "dimension": "30x25x10", "weight": 1.8, + "category": "tools", "active": true, + "rents": [ + {"status": "loan", "member": 0, "begin": -95, "end": -90, "comment": "Rendue avec les deux batteries"}, + {"status": "shelf", "begin": -90, "end": -40}, + {"status": "loan", "member": 1, "begin": -40, "end": -33}, + {"status": "shelf", "begin": -33, "end": -3}, + {"status": "loan", "member": 2, "begin": -3} + ] + }, + { + "name": "Scie sauteuse", + "description": "Avec lot de lames bois et métal", + "serial": "OUT-002", + "price": 89.00, "rent_price": 1.50, "per_day": true, + "dimension": "25x20x8", "weight": 2.1, + "category": "tools", "active": true, + "rents": [ + {"status": "loan", "member": 3, "begin": -60, "end": -52, "comment": "Une lame cassée"}, + {"status": "repair", "begin": -52, "end": -30, "comment": "Changement du guide"}, + {"status": "shelf", "begin": -30} + ] + }, + { + "name": "Escabeau 5 marches", + "description": "Aluminium", + "serial": "OUT-003", + "price": 65.00, "rent_price": 0, "per_day": false, + "dimension": "150x45x10", "weight": 5.5, + "category": "tools", "active": true, + "rents": [ + {"status": "cellar", "begin": -200} + ] + }, + { + "name": "Ponceuse orbitale", + "description": "", + "serial": "OUT-004", + "price": 75.00, "rent_price": 1.00, "per_day": true, + "dimension": "20x15x15", "weight": 1.6, + "category": "tools", "active": true, + "rents": [ + {"status": "long", "member": 4, "begin": -45, "comment": "Rénovation d'un parquet"} + ] + }, + { + "name": "Tondeuse thermique", + "description": "Coupe 46 cm, bac de ramassage", + "serial": "JAR-001", + "price": 349.00, "rent_price": 5.00, "per_day": false, + "dimension": "150x50x100", "weight": 28, + "category": "garden", "active": true, + "rents": [ + {"status": "loan", "member": 1, "begin": -150, "end": -145}, + {"status": "shelf", "begin": -145, "end": -20}, + {"status": "repair", "begin": -20, "comment": "Démarreur à changer"} + ] + }, + { + "name": "Taille-haie électrique", + "description": "", + "serial": "JAR-002", + "price": 99.00, "rent_price": 2.00, "per_day": false, + "dimension": "100x20x20", "weight": 3.9, + "category": "garden", "active": true, + "rents": [ + {"status": "loan", "member": 0, "begin": -12, "end": -10}, + {"status": "shelf", "begin": -10} + ] + }, + { + "name": "Brouette", + "description": "100 litres", + "serial": "JAR-003", + "price": 55.00, "rent_price": 0, "per_day": false, + "dimension": "140x60x60", "weight": 12, + "category": "garden", "active": true, + "rents": [ + {"status": "shelf", "begin": -300} + ] + }, + { + "name": "Barnum 3x6 m", + "description": "Avec parois latérales et lestes", + "serial": "FET-001", + "price": 420.00, "rent_price": 15.00, "per_day": false, + "dimension": "120x40x40", "weight": 35, + "category": "party", "active": true, + "rents": [ + {"status": "loan", "member": 2, "begin": -80, "end": -78, "comment": "Fête de quartier"}, + {"status": "cellar", "begin": -78, "end": -8}, + {"status": "loan", "member": 5, "begin": -8, "end": -6, "comment": "Mariage"}, + {"status": "cellar", "begin": -6} + ] + }, + { + "name": "Sonorisation portable", + "description": "Enceinte 300 W, deux micros sans fil", + "serial": "FET-002", + "price": 499.00, "rent_price": 10.00, "per_day": false, + "dimension": "60x40x35", "weight": 14, + "category": "party", "active": true, + "rents": [ + {"status": "loan", "member": 3, "begin": -30, "end": -29}, + {"status": "shelf", "begin": -29, "end": -2}, + {"status": "loan", "member": 0, "begin": -2} + ] + }, + { + "name": "Lot de 10 tables pliantes", + "description": "180x75 cm", + "serial": "FET-003", + "price": 600.00, "rent_price": 8.00, "per_day": false, + "dimension": "180x75x5", "weight": 120, + "category": "party", "active": true, + "rents": [ + {"status": "cellar", "begin": -365} + ] + }, + { + "name": "Machine à barbe à papa", + "description": "", + "serial": "FET-004", + "price": 110.00, "rent_price": 4.00, "per_day": false, + "dimension": "50x50x40", "weight": 8, + "category": "party", "active": true, + "rents": [ + {"status": "loan", "member": 6, "begin": -15, "end": -14}, + {"status": "shelf", "begin": -14} + ] + }, + { + "name": "Jeu de société « Les Aventuriers du Rail »", + "description": "Boîte complète", + "serial": "LUD-001", + "price": 45.00, "rent_price": 0, "per_day": false, + "dimension": "30x30x8", "weight": 1.5, + "category": "books", "active": true, + "rents": [ + {"status": "loan", "member": 4, "begin": -50, "end": -43}, + {"status": "shelf", "begin": -43, "end": -21}, + {"status": "loan", "member": 1, "begin": -21, "end": -14}, + {"status": "shelf", "begin": -14, "end": -5}, + {"status": "loan", "member": 7, "begin": -5} + ] + }, + { + "name": "Mölkky", + "description": "Quilles finlandaises en bois", + "serial": "LUD-002", + "price": 35.00, "rent_price": 0, "per_day": false, + "dimension": "40x20x20", "weight": 3, + "category": "books", "active": true, + "rents": [ + {"status": "loan", "member": 2, "begin": -70, "end": -60, "comment": "Une quille manquante au retour"}, + {"status": "lost", "begin": -60, "comment": "Quille numéro 7 jamais retrouvée"} + ] + }, + { + "name": "Rétroprojecteur", + "description": "Remplacé par le vidéoprojecteur", + "serial": "ANC-001", + "price": 150.00, "rent_price": 0, "per_day": false, + "dimension": "40x40x50", "weight": 9, + "category": "old", "active": false, + "rents": [ + {"status": "cellar", "begin": -1000} + ] + }, + { + "name": "Vidéoprojecteur", + "description": "Full HD, câble HDMI de 10 m", + "serial": "FET-005", + "price": 550.00, "rent_price": 6.00, "per_day": true, + "dimension": "30x25x10", "weight": 2.8, + "category": "party", "active": true, + "rents": [ + {"status": "long", "member": 5, "begin": -35, "end": -5, "comment": "Cycle de projections"}, + {"status": "shelf", "begin": -5} + ] + }, + { + "name": "Nettoyeur haute pression", + "description": "", + "serial": "", + "price": 0, "rent_price": 3.00, "per_day": false, + "dimension": "", "weight": 0, + "category": null, "active": true, + "rents": [ + {"status": "shelf", "begin": -7} + ] + } + ] +} diff --git a/templates/default/preferences.html.twig b/templates/default/preferences.html.twig index 8fcc953c..0e23a51e 100644 --- a/templates/default/preferences.html.twig +++ b/templates/default/preferences.html.twig @@ -153,4 +153,18 @@
+{% if sample_data %} +
+
+ +
+
{{ _T("Sample data", "objectslend") }}
+

{{ _T("The catalog is empty. You can fill it with sample categories, statuses and objects, along with a rents history given to existing members.", "objectslend") }}

+ +
+
+
+{% endif %} {% endblock %} diff --git a/tests/GaletteObjectsLend/Controllers/tests/units/MainController.php b/tests/GaletteObjectsLend/Controllers/tests/units/MainController.php index c97e329b..a9d26c15 100644 --- a/tests/GaletteObjectsLend/Controllers/tests/units/MainController.php +++ b/tests/GaletteObjectsLend/Controllers/tests/units/MainController.php @@ -11,6 +11,8 @@ namespace GaletteObjectsLend\Controllers\tests\units; use Galette\Tests\GaletteRoutingTestCase; +use GaletteObjectsLend\Entity\LendObject; +use GaletteObjectsLend\Entity\LendRent; use GaletteObjectsLend\LendPreferences; /** @@ -134,4 +136,37 @@ public function testStorePreferencesAsMember(): void $this->app->handle($request); $this->assertNotSame(222, (new LendPreferences($this->preferences))->getThumbWidth()); } + + /** + * Sample data are offered and loaded on an empty catalog only + */ + public function testLoadSampleData(): void + { + $this->zdb->execute($this->zdb->update(LEND_PREFIX . LendObject::TABLE)->set([LendRent::PK => null])); + $this->zdb->execute($this->zdb->delete(LEND_PREFIX . LendRent::TABLE)); + $this->zdb->execute($this->zdb->delete(LEND_PREFIX . LendObject::TABLE)); + + $this->logSuperAdmin(); + $body = (string)$this->app->handle($this->createRequest(route_name: 'objectslend_preferences'))->getBody(); + $this->assertStringContainsString($this->routeparser->urlFor('objectslend_sample_data'), $body); + + $request = $this->createRequest(route_name: 'objectslend_sample_data', method: 'POST'); + $test_response = $this->app->handle($request); + $this->assertSame(302, $test_response->getStatusCode()); + $this->assertSame( + [$this->routeparser->urlFor('objectslend_objects')], + $test_response->getHeader('Location') + ); + $flash = $this->flash_data['slimFlash'] ?? []; + $this->flash_data = []; + $this->assertArrayHasKey('success_detected', $flash); + $this->assertStringStartsWith('Sample data loaded: 16 objects', $flash['success_detected'][0]); + + //no longer offered, and refused + $body = (string)$this->app->handle($this->createRequest(route_name: 'objectslend_preferences'))->getBody(); + $this->assertStringNotContainsString($this->routeparser->urlFor('objectslend_sample_data'), $body); + + $this->app->handle($this->createRequest(route_name: 'objectslend_sample_data', method: 'POST')); + $this->expectFlashData(['error_detected' => ['Sample data can only be loaded into an empty catalog.']]); + } } diff --git a/tests/GaletteObjectsLend/tests/units/SampleData.php b/tests/GaletteObjectsLend/tests/units/SampleData.php new file mode 100644 index 00000000..393c186a --- /dev/null +++ b/tests/GaletteObjectsLend/tests/units/SampleData.php @@ -0,0 +1,151 @@ + + */ +class SampleData extends GaletteTestCase +{ + protected int $seed = 20260924143015; + protected bool $load_plugins = true; + + /** + * Set up tests: sample data need an empty catalog + */ + public function setUp(): void + { + parent::setUp(); + $this->emptyCatalog(); + } + + /** + * Cleanup after each test method + */ + public function tearDown(): void + { + $this->emptyCatalog(); + parent::tearDown(); + } + + /** + * Remove objects, rents, categories and statuses + */ + private function emptyCatalog(): void + { + $this->zdb->execute($this->zdb->update(LEND_PREFIX . LendObject::TABLE)->set([LendRent::PK => null])); + foreach ([LendRent::TABLE, LendObject::TABLE, LendCategory::TABLE, LendStatus::TABLE] as $table) { + $this->zdb->execute($this->zdb->delete(LEND_PREFIX . $table)); + } + } + + /** + * Count rows of a plugin table + * + * @param string $table Table name, without prefixes + */ + private function countRows(string $table): int + { + return $this->zdb->execute($this->zdb->select(LEND_PREFIX . $table))->count(); + } + + /** + * Load sample data + */ + public function testLoad(): void + { + $data = json_decode( + (string)file_get_contents(\GaletteObjectsLend\SampleData::FILE), + true, + flags: JSON_THROW_ON_ERROR + ); + $this->assertIsArray($data); + + //an existing category is reused + $category = new LendCategory($this->zdb); + $category->setName($data['categories'][0]['name'])->setActive(true); + $category->store(); + + $member = $this->getMemberOne(); + $sample = new \GaletteObjectsLend\SampleData($this->zdb); + $this->assertFalse($sample->hasObjects()); + + $created = $sample->load([(int)$member->id]); + $this->assertTrue($sample->hasObjects()); + + $nb_rents = array_sum(array_map(fn($o) => count($o['rents']), $data['objects'])); + $this->assertSame( + [ + 'categories' => count($data['categories']) - 1, + 'statuses' => count($data['statuses']), + 'objects' => count($data['objects']), + 'rents' => $nb_rents + ], + $created + ); + $this->assertSame(count($data['categories']), $this->countRows(LendCategory::TABLE)); + $this->assertSame(count($data['objects']), $this->countRows(LendObject::TABLE)); + $this->assertSame($nb_rents, $this->countRows(LendRent::TABLE)); + + //every object has a current rent, the last one, still open + $rents_repo = new Rents($this->zdb); + foreach ($this->zdb->execute($this->zdb->select(LEND_PREFIX . LendObject::TABLE)) as $row) { + $this->assertNotNull($row->rent_id, $row->name); + $rents = $rents_repo->getForObject((int)$row->object_id); + $open = array_filter($rents, fn(LendRent $rent) => $rent->getDateEnd() === ''); + $this->assertCount(1, $open, $row->name); + $this->assertSame((int)$row->rent_id, array_values($open)[0]->getId()); + } + + //rents with a member are given to the only one available + $select = $this->zdb->select(LEND_PREFIX . LendRent::TABLE); + $select->where->isNotNull('adherent_id'); + foreach ($this->zdb->execute($select) as $row) { + $this->assertSame((int)$member->id, (int)$row->adherent_id); + } + } + + /** + * Rents are anonymous without members + */ + public function testLoadWithoutMembers(): void + { + (new \GaletteObjectsLend\SampleData($this->zdb))->load([]); + $select = $this->zdb->select(LEND_PREFIX . LendRent::TABLE); + $select->where->isNotNull('adherent_id'); + $this->assertSame(0, $this->zdb->execute($select)->count()); + $this->assertGreaterThan(0, $this->countRows(LendRent::TABLE)); + } + + /** + * Nothing is kept when loading fails + */ + public function testInvalidFile(): void + { + $file = tempnam(sys_get_temp_dir(), 'lend'); + file_put_contents($file, '{"categories": []}'); + try { + $this->expectException(\RuntimeException::class); + (new \GaletteObjectsLend\SampleData($this->zdb, $file))->load([]); + } finally { + unlink($file); + } + } +} From 0d380e80dbc20f73759938cb33d84ab646f69d9d Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski Date: Thu, 24 Sep 2026 08:53:55 +0200 Subject: [PATCH 6/9] Always allow to view fullsize images --- .../Controllers/ImagesController.php | 8 +------- lib/GaletteObjectsLend/LendPreferences.php | 13 ------------- scripts/upgrade-to-1.1-mysql.sql | 2 +- scripts/upgrade-to-1.1-pgsql.sql | 2 +- templates/default/headers.html.twig | 2 +- templates/default/preferences.html.twig | 5 ----- templates/default/take_object.html.twig | 2 -- .../tests/units/LendPreferences.php | 5 ++--- 8 files changed, 6 insertions(+), 33 deletions(-) diff --git a/lib/GaletteObjectsLend/Controllers/ImagesController.php b/lib/GaletteObjectsLend/Controllers/ImagesController.php index ff79bdc3..e0ede265 100644 --- a/lib/GaletteObjectsLend/Controllers/ImagesController.php +++ b/lib/GaletteObjectsLend/Controllers/ImagesController.php @@ -41,13 +41,7 @@ public function lendPicture(Request $request, Response $response, string $type, $picture = new $class($id); $this->lendsprefs = new LendPreferences($this->preferences); - $thumb = false; - if (!$this->lendsprefs->showFullsize() || $mode == 'thumbnail') { - //force thumbnail display from preferences - $thumb = true; - } - - if ($thumb) { + if ($mode == 'thumbnail') { return $picture->displayThumb($response, $this->lendsprefs); } else { return $picture->display($response); diff --git a/lib/GaletteObjectsLend/LendPreferences.php b/lib/GaletteObjectsLend/LendPreferences.php index 79a181b6..db281a4b 100644 --- a/lib/GaletteObjectsLend/LendPreferences.php +++ b/lib/GaletteObjectsLend/LendPreferences.php @@ -40,8 +40,6 @@ final class LendPreferences public const string THUMB_MAX_HEIGHT = self::PREFIX . 'thumb_max_height'; /** Show images in objects and categories lists */ public const string VIEW_THUMBNAIL = self::PREFIX . 'view_thumbnail'; - /** Show fullsize images clicking on thumbnails */ - public const string VIEW_FULLSIZE = self::PREFIX . 'view_fullsize'; /** Show categories on the objects list */ public const string VIEW_CATEGORY = self::PREFIX . 'view_category'; /** Show the forecast return date */ @@ -129,7 +127,6 @@ public static function getBooleans(): array self::ENABLE_MEMBER_RENT_OBJECT => true, self::AUTO_GENERATE_CONTRIBUTION => true, self::VIEW_THUMBNAIL => true, - self::VIEW_FULLSIZE => true, self::VIEW_CATEGORY => false, self::VIEW_DATE_FORECAST => true, self::VIEW_DESCRIPTION => true, @@ -192,16 +189,6 @@ public function imagesInLists(): bool return $this->isEnabled(self::VIEW_THUMBNAIL); } - /** - * Shall we show fullsize images? - * - * Would appear editing object, and on thumbnails click - */ - public function showFullsize(): bool - { - return $this->isEnabled(self::VIEW_FULLSIZE); - } - /** * Get every preference, named without its prefix, for templates * diff --git a/scripts/upgrade-to-1.1-mysql.sql b/scripts/upgrade-to-1.1-mysql.sql index 80480c54..a368848d 100644 --- a/scripts/upgrade-to-1.1-mysql.sql +++ b/scripts/upgrade-to-1.1-mysql.sql @@ -16,7 +16,7 @@ SELECT FROM galette_lend_parameters WHERE code IN ( 'ENABLE_MEMBER_RENT_OBJECT', 'AUTO_GENERATE_CONTRIBUTION', 'GENERATED_CONTRIBUTION_TYPE_ID', - 'GENERATED_CONTRIB_INFO_TEXT', 'THUMB_MAX_WIDTH', 'THUMB_MAX_HEIGHT', 'VIEW_THUMBNAIL', 'VIEW_FULLSIZE', + 'GENERATED_CONTRIB_INFO_TEXT', 'THUMB_MAX_WIDTH', 'THUMB_MAX_HEIGHT', 'VIEW_THUMBNAIL', 'VIEW_CATEGORY', 'VIEW_DATE_FORECAST', 'VIEW_DESCRIPTION', 'VIEW_DIMENSION', 'VIEW_LEND_PRICE', 'VIEW_LIST_PRICE_SUM', 'VIEW_PRICE', 'VIEW_SERIAL', 'VIEW_WEIGHT' ); diff --git a/scripts/upgrade-to-1.1-pgsql.sql b/scripts/upgrade-to-1.1-pgsql.sql index 743c66a2..62f2ba2e 100644 --- a/scripts/upgrade-to-1.1-pgsql.sql +++ b/scripts/upgrade-to-1.1-pgsql.sql @@ -16,7 +16,7 @@ SELECT FROM galette_lend_parameters WHERE code IN ( 'ENABLE_MEMBER_RENT_OBJECT', 'AUTO_GENERATE_CONTRIBUTION', 'GENERATED_CONTRIBUTION_TYPE_ID', - 'GENERATED_CONTRIB_INFO_TEXT', 'THUMB_MAX_WIDTH', 'THUMB_MAX_HEIGHT', 'VIEW_THUMBNAIL', 'VIEW_FULLSIZE', + 'GENERATED_CONTRIB_INFO_TEXT', 'THUMB_MAX_WIDTH', 'THUMB_MAX_HEIGHT', 'VIEW_THUMBNAIL', 'VIEW_CATEGORY', 'VIEW_DATE_FORECAST', 'VIEW_DESCRIPTION', 'VIEW_DIMENSION', 'VIEW_LEND_PRICE', 'VIEW_LIST_PRICE_SUM', 'VIEW_PRICE', 'VIEW_SERIAL', 'VIEW_WEIGHT' ); diff --git a/templates/default/headers.html.twig b/templates/default/headers.html.twig index ffc45d24..51ee64a2 100644 --- a/templates/default/headers.html.twig +++ b/templates/default/headers.html.twig @@ -4,7 +4,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later #} -{% if olendsprefs is defined and olendsprefs.showFullsize() %} +{% if olendsprefs is defined %} +{% endblock %} From bbf442c16c14f753b96114dd59eedbc735be3862 Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski Date: Thu, 24 Sep 2026 08:53:59 +0200 Subject: [PATCH 8/9] Show maximum upload size on picture fields --- templates/default/category_edit.html.twig | 9 +++++++-- templates/default/objects_edit.html.twig | 13 +++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/templates/default/category_edit.html.twig b/templates/default/category_edit.html.twig index fc06753a..19db932e 100644 --- a/templates/default/category_edit.html.twig +++ b/templates/default/category_edit.html.twig @@ -29,7 +29,12 @@ } %}
- + {% include "components/forms/max_size.html.twig" with { + label: _T("Picture:", "objectslend"), + for: "picture_file", + max_size: preferences.pref_upload_size_images, + input_id: "picture_file" + } %} +
diff --git a/templates/default/objects_edit.html.twig b/templates/default/objects_edit.html.twig index 8629283b..450988f8 100644 --- a/templates/default/objects_edit.html.twig +++ b/templates/default/objects_edit.html.twig @@ -126,7 +126,16 @@
-
{{ _T("Object's photo", "objectslend") }}
+
+ {{ _T("Object's photo", "objectslend") }} + {# out of the header's direct children, which Fomantic draws as a big icon #} + + {% include "components/forms/max_size.html.twig" with { + max_size: preferences.pref_upload_size_images, + input_id: "picture_file" + } %} + +
- +