From d4c5da21731396ab70866600b2c6e9b9496cf7ce Mon Sep 17 00:00:00 2001 From: Bernd Bestel Date: Mon, 31 Aug 2020 20:40:31 +0200 Subject: [PATCH] Applied PHP formatting rules --- app.php | 23 +- controllers/BaseApiController.php | 27 +- controllers/BaseController.php | 234 ++-- controllers/BatteriesApiController.php | 41 +- controllers/BatteriesController.php | 82 +- controllers/CalendarApiController.php | 20 +- controllers/CalendarController.php | 10 +- controllers/ChoresApiController.php | 113 +- controllers/ChoresController.php | 104 +- controllers/EquipmentController.php | 32 +- controllers/ExceptionController.php | 7 +- controllers/FilesApiController.php | 110 +- controllers/GenericEntityApiController.php | 194 +-- controllers/GenericEntityController.php | 78 +- controllers/LoginController.php | 83 +- controllers/OpenApiController.php | 44 +- controllers/RecipesApiController.php | 19 +- controllers/RecipesController.php | 116 +- controllers/StockApiController.php | 961 +++++++------- controllers/StockController.php | 655 ++++----- controllers/SystemApiController.php | 36 +- controllers/SystemController.php | 84 +- controllers/TasksApiController.php | 14 +- controllers/TasksController.php | 61 +- controllers/Users/User.php | 138 +- controllers/UsersApiController.php | 170 ++- controllers/UsersController.php | 48 +- helpers/BaseBarcodeLookupPlugin.php | 35 +- helpers/PrerequisiteChecker.php | 42 +- helpers/UrlManager.php | 29 +- helpers/extensions.php | 71 +- middleware/ApiKeyAuthMiddleware.php | 17 +- middleware/AuthMiddleware.php | 16 +- middleware/BaseMiddleware.php | 9 +- middleware/CorsMiddleware.php | 20 +- middleware/DefaultAuthMiddleware.php | 2 + middleware/JsonMiddleware.php | 4 +- middleware/LocaleMiddleware.php | 44 +- middleware/ReverseProxyAuthMiddleware.php | 6 +- middleware/SessionAuthMiddleware.php | 11 +- public/index.php | 7 +- services/ApiKeyService.php | 126 +- services/ApplicationService.php | 83 +- services/BaseService.php | 40 +- services/BatteriesService.php | 50 +- services/CalendarService.php | 96 +- services/ChoresService.php | 237 ++-- services/DatabaseMigrationService.php | 59 +- services/DatabaseService.php | 139 +- services/DemoDataGeneratorService.php | 49 +- services/FilesService.php | 62 +- services/LocalizationService.php | 171 +-- services/RecipesService.php | 122 +- services/SessionService.php | 85 +- services/StockService.php | 1401 ++++++++++---------- services/TasksService.php | 9 +- services/UserfieldsService.php | 128 +- services/UsersService.php | 75 +- 58 files changed, 3667 insertions(+), 3082 deletions(-) diff --git a/app.php b/app.php index 42d304cd..bbc23e56 100644 --- a/app.php +++ b/app.php @@ -1,20 +1,19 @@ getContainer(); -$container->set('view', function(Container $container) +$container->set('view', function (Container $container) { return new Slim\Views\Blade(__DIR__ . '/views', GROCY_DATAPATH . '/viewcache'); }); -$container->set('LoginControllerInstance', function(Container $container) +$container->set('LoginControllerInstance', function (Container $container) { return new LoginController($container, 'grocy_session'); }); -$container->set('UrlManager', function(Container $container) +$container->set('UrlManager', function (Container $container) { return new UrlManager(GROCY_BASE_URL); }); -$container->set('ApiKeyHeaderName', function(Container $container) +$container->set('ApiKeyHeaderName', function (Container $container) { return 'GROCY-API-KEY'; }); @@ -68,7 +68,8 @@ if (GROCY_MODE === 'production' || GROCY_MODE === 'dev') { $app->add(new \Grocy\Middleware\LocaleMiddleware($container)); } -else { +else +{ define('GROCY_LOCALE', GROCY_DEFAULT_LOCALE); } diff --git a/controllers/BaseApiController.php b/controllers/BaseApiController.php index f6809d96..f6755d24 100644 --- a/controllers/BaseApiController.php +++ b/controllers/BaseApiController.php @@ -4,23 +4,13 @@ namespace Grocy\Controllers; class BaseApiController extends BaseController { + protected $OpenApiSpec = null; public function __construct(\DI\Container $container) { parent::__construct($container); } - protected $OpenApiSpec = null; - - protected function getOpenApispec() - { - if($this->OpenApiSpec == null) - { - $this->OpenApiSpec = json_decode(file_get_contents(__DIR__ . '/../grocy.openapi.json')); - } - return $this->OpenApiSpec; - } - protected function ApiResponse(\Psr\Http\Message\ResponseInterface $response, $data) { $response->getBody()->write(json_encode($data)); @@ -34,8 +24,19 @@ class BaseApiController extends BaseController protected function GenericErrorResponse(\Psr\Http\Message\ResponseInterface $response, $errorMessage, $status = 400) { - return $response->withStatus($status)->withJson(array( + return $response->withStatus($status)->withJson([ 'error_message' => $errorMessage - )); + ]); } + + protected function getOpenApispec() + { + if ($this->OpenApiSpec == null) + { + $this->OpenApiSpec = json_decode(file_get_contents(__DIR__ . '/../grocy.openapi.json')); + } + + return $this->OpenApiSpec; + } + } diff --git a/controllers/BaseController.php b/controllers/BaseController.php index a24b9363..b929c96c 100644 --- a/controllers/BaseController.php +++ b/controllers/BaseController.php @@ -3,114 +3,34 @@ namespace Grocy\Controllers; use Grocy\Controllers\Users\User; -use \Grocy\Services\DatabaseService; -use \Grocy\Services\ApplicationService; -use \Grocy\Services\LocalizationService; -use \Grocy\Services\StockService; -use \Grocy\Services\UsersService; -use \Grocy\Services\UserfieldsService; -use \Grocy\Services\BatteriesService; -use \Grocy\Services\CalendarService; -use \Grocy\Services\SessionService; -use \Grocy\Services\RecipesService; -use \Grocy\Services\TasksService; -use \Grocy\Services\FilesService; -use \Grocy\Services\ChoresService; -use \Grocy\Services\ApiKeyService; +use Grocy\Services\ApiKeyService; +use Grocy\Services\ApplicationService; +use Grocy\Services\BatteriesService; +use Grocy\Services\CalendarService; +use Grocy\Services\ChoresService; +use Grocy\Services\DatabaseService; +use Grocy\Services\FilesService; +use Grocy\Services\LocalizationService; +use Grocy\Services\RecipesService; +use Grocy\Services\SessionService; +use Grocy\Services\StockService; +use Grocy\Services\TasksService; +use Grocy\Services\UserfieldsService; +use Grocy\Services\UsersService; class BaseController { - public function __construct(\DI\Container $container) { + protected $AppContainer; + public function __construct(\DI\Container $container) + { $this->AppContainer = $container; $this->View = $container->get('view'); } - protected function render($response, $page, $data = []) + protected function getApiKeyService() { - $container = $this->AppContainer; - - $versionInfo = $this->getApplicationService()->GetInstalledVersion(); - $this->View->set('version', $versionInfo->Version); - $this->View->set('releaseDate', $versionInfo->ReleaseDate); - - $localizationService = $this->getLocalizationService(); - $this->View->set('__t', function(string $text, ...$placeholderValues) use($localizationService) - { - return $localizationService->__t($text, $placeholderValues); - }); - $this->View->set('__n', function($number, $singularForm, $pluralForm) use($localizationService) - { - return $localizationService->__n($number, $singularForm, $pluralForm); - }); - $this->View->set('GettextPo', $localizationService->GetPoAsJsonString()); - - $this->View->set('U', function($relativePath, $isResource = false) use($container) - { - return $container->get('UrlManager')->ConstructUrl($relativePath, $isResource); - }); - - $embedded = false; - if (isset($_GET['embedded'])) - { - $embedded = true; - } - $this->View->set('embedded', $embedded); - - $constants = get_defined_constants(); - foreach ($constants as $constant => $value) - { - if (substr($constant, 0, 19) !== 'GROCY_FEATURE_FLAG_') - { - unset($constants[$constant]); - } - } - $this->View->set('featureFlags', $constants); - - if (GROCY_AUTHENTICATED) - { - $this->View->set('permissions', User::PermissionList()); - } - - return $this->View->render($response, $page, $data); - } - - protected function renderPage($response, $page, $data = []) - { - $this->View->set('userentitiesForSidebar', $this->getDatabase()->userentities()->where('show_in_sidebar_menu = 1')->orderBy('name')); - try - { - $usersService = $this->getUsersService(); - if (defined('GROCY_USER_ID')) - { - $this->View->set('userSettings', $usersService->GetUserSettings(GROCY_USER_ID)); - } - else - { - $this->View->set('userSettings', null); - } - } - catch (\Exception $ex) - { - // Happens when database is not initialised or migrated... - } - - return $this->render($response, $page, $data); - } - - protected function getDatabaseService() - { - return DatabaseService::getInstance(); - } - - protected function getDatabase() - { - return $this->getDatabaseService()->GetDbConnection(); - } - - protected function getLocalizationService() - { - return LocalizationService::getInstance(GROCY_LOCALE); + return ApiKeyService::getInstance(); } protected function getApplicationservice() @@ -128,9 +48,29 @@ class BaseController return CalendarService::getInstance(); } - protected function getSessionService() + protected function getChoresService() { - return SessionService::getInstance(); + return ChoresService::getInstance(); + } + + protected function getDatabase() + { + return $this->getDatabaseService()->GetDbConnection(); + } + + protected function getDatabaseService() + { + return DatabaseService::getInstance(); + } + + protected function getFilesService() + { + return FilesService::getInstance(); + } + + protected function getLocalizationService() + { + return LocalizationService::getInstance(GROCY_LOCALE); } protected function getRecipesService() @@ -138,6 +78,11 @@ class BaseController return RecipesService::getInstance(); } + protected function getSessionService() + { + return SessionService::getInstance(); + } + protected function getStockService() { return StockService::getInstance(); @@ -148,30 +93,93 @@ class BaseController return TasksService::getInstance(); } - protected function getUsersService() - { - return UsersService::getInstance(); - } - protected function getUserfieldsService() { return UserfieldsService::getInstance(); } - protected function getApiKeyService() + protected function getUsersService() { - return ApiKeyService::getInstance(); + return UsersService::getInstance(); } - protected function getChoresService() + protected function render($response, $page, $data = []) { - return ChoresService::getInstance(); + $container = $this->AppContainer; + + $versionInfo = $this->getApplicationService()->GetInstalledVersion(); + $this->View->set('version', $versionInfo->Version); + $this->View->set('releaseDate', $versionInfo->ReleaseDate); + + $localizationService = $this->getLocalizationService(); + $this->View->set('__t', function (string $text, ...$placeholderValues) use ($localizationService) + { + return $localizationService->__t($text, $placeholderValues); + }); + $this->View->set('__n', function ($number, $singularForm, $pluralForm) use ($localizationService) + { + return $localizationService->__n($number, $singularForm, $pluralForm); + }); + $this->View->set('GettextPo', $localizationService->GetPoAsJsonString()); + + $this->View->set('U', function ($relativePath, $isResource = false) use ($container) + { + return $container->get('UrlManager')->ConstructUrl($relativePath, $isResource); + }); + + $embedded = false; + + if (isset($_GET['embedded'])) + { + $embedded = true; + } + + $this->View->set('embedded', $embedded); + + $constants = get_defined_constants(); + + foreach ($constants as $constant => $value) + { + if (substr($constant, 0, 19) !== 'GROCY_FEATURE_FLAG_') + { + unset($constants[$constant]); + } + + } + + $this->View->set('featureFlags', $constants); + + if (GROCY_AUTHENTICATED) + { + $this->View->set('permissions', User::PermissionList()); + } + + return $this->View->render($response, $page, $data); } - protected function getFilesService() + protected function renderPage($response, $page, $data = []) { - return FilesService::getInstance(); + $this->View->set('userentitiesForSidebar', $this->getDatabase()->userentities()->where('show_in_sidebar_menu = 1')->orderBy('name')); + try + { + $usersService = $this->getUsersService(); + + if (defined('GROCY_USER_ID')) + { + $this->View->set('userSettings', $usersService->GetUserSettings(GROCY_USER_ID)); + } + else + { + $this->View->set('userSettings', null); + } + + } + catch (\Exception $ex) + { + // Happens when database is not initialised or migrated... + } + + return $this->render($response, $page, $data); } - protected $AppContainer; } diff --git a/controllers/BatteriesApiController.php b/controllers/BatteriesApiController.php index d98abfcb..651c9100 100644 --- a/controllers/BatteriesApiController.php +++ b/controllers/BatteriesApiController.php @@ -6,9 +6,22 @@ use Grocy\Controllers\Users\User; class BatteriesApiController extends BaseApiController { - public function __construct(\DI\Container $container) + public function BatteryDetails(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - parent::__construct($container); + try + { + return $this->ApiResponse($response, $this->getBatteriesService()->GetBatteryDetails($args['batteryId'])); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + + public function Current(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->ApiResponse($response, $this->getBatteriesService()->GetCurrent()); } public function TrackChargeCycle(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -20,6 +33,7 @@ class BatteriesApiController extends BaseApiController try { $trackedTime = date('Y-m-d H:i:s'); + if (array_key_exists('tracked_time', $requestBody) && IsIsoDateTime($requestBody['tracked_time'])) { $trackedTime = $requestBody['tracked_time']; @@ -32,23 +46,7 @@ class BatteriesApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } - } - public function BatteryDetails(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - try - { - return $this->ApiResponse($response, $this->getBatteriesService()->GetBatteryDetails($args['batteryId'])); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } - } - - public function Current(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->ApiResponse($response, $this->getBatteriesService()->GetCurrent()); } public function UndoChargeCycle(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -64,5 +62,12 @@ class BatteriesApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } + + public function __construct(\DI\Container $container) + { + parent::__construct($container); + } + } diff --git a/controllers/BatteriesController.php b/controllers/BatteriesController.php index f0daa4fa..4f2702bf 100644 --- a/controllers/BatteriesController.php +++ b/controllers/BatteriesController.php @@ -4,9 +4,46 @@ namespace Grocy\Controllers; class BatteriesController extends BaseController { - public function __construct(\DI\Container $container) + public function BatteriesList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - parent::__construct($container); + return $this->renderPage($response, 'batteries', [ + 'batteries' => $this->getDatabase()->batteries()->orderBy('name'), + 'userfields' => $this->getUserfieldsService()->GetFields('batteries'), + 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('batteries') + ]); + } + + public function BatteriesSettings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->renderPage($response, 'batteriessettings'); + } + + public function BatteryEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + if ($args['batteryId'] == 'new') + { + return $this->renderPage($response, 'batteryform', [ + 'mode' => 'create', + 'userfields' => $this->getUserfieldsService()->GetFields('batteries') + ]); + } + else + { + return $this->renderPage($response, 'batteryform', [ + 'battery' => $this->getDatabase()->batteries($args['batteryId']), + 'mode' => 'edit', + 'userfields' => $this->getUserfieldsService()->GetFields('batteries') + ]); + } + + } + + public function Journal(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->renderPage($response, 'batteriesjournal', [ + 'chargeCycles' => $this->getDatabase()->battery_charge_cycles()->orderBy('tracked_time', 'DESC'), + 'batteries' => $this->getDatabase()->batteries()->orderBy('name') + ]); } public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -26,48 +63,13 @@ class BatteriesController extends BaseController public function TrackChargeCycle(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'batterytracking', [ - 'batteries' => $this->getDatabase()->batteries()->orderBy('name') - ]); - } - - public function BatteriesList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->renderPage($response, 'batteries', [ - 'batteries' => $this->getDatabase()->batteries()->orderBy('name'), - 'userfields' => $this->getUserfieldsService()->GetFields('batteries'), - 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('batteries') - ]); - } - - public function BatteryEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - if ($args['batteryId'] == 'new') - { - return $this->renderPage($response, 'batteryform', [ - 'mode' => 'create', - 'userfields' => $this->getUserfieldsService()->GetFields('batteries') - ]); - } - else - { - return $this->renderPage($response, 'batteryform', [ - 'battery' => $this->getDatabase()->batteries($args['batteryId']), - 'mode' => 'edit', - 'userfields' => $this->getUserfieldsService()->GetFields('batteries') - ]); - } - } - - public function Journal(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->renderPage($response, 'batteriesjournal', [ - 'chargeCycles' => $this->getDatabase()->battery_charge_cycles()->orderBy('tracked_time', 'DESC'), 'batteries' => $this->getDatabase()->batteries()->orderBy('name') ]); } - public function BatteriesSettings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + public function __construct(\DI\Container $container) { - return $this->renderPage($response, 'batteriessettings'); + parent::__construct($container); } + } diff --git a/controllers/CalendarApiController.php b/controllers/CalendarApiController.php index d0b79e5c..f60023d4 100644 --- a/controllers/CalendarApiController.php +++ b/controllers/CalendarApiController.php @@ -4,11 +4,6 @@ namespace Grocy\Controllers; class CalendarApiController extends BaseApiController { - public function __construct(\DI\Container $container) - { - parent::__construct($container); - } - public function Ical(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { try @@ -16,7 +11,8 @@ class CalendarApiController extends BaseApiController $vCalendar = new \Eluceo\iCal\Component\Calendar('grocy'); $events = $this->getCalendarService()->GetEvents(); - foreach($events as $event) + + foreach ($events as $event) { $date = new \DateTime($event['start']); $date->setTimezone(new \DateTimeZone(date_default_timezone_get())); @@ -45,19 +41,27 @@ class CalendarApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } public function IcalSharingLink(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { try { - return $this->ApiResponse($response, array( + return $this->ApiResponse($response, [ 'url' => $this->AppContainer->get('UrlManager')->ConstructUrl('/api/calendar/ical?secret=' . $this->getApiKeyService()->GetOrCreateApiKey(\Grocy\Services\ApiKeyService::API_KEY_TYPE_SPECIAL_PURPOSE_CALENDAR_ICAL)) - )); + ]); } catch (\Exception $ex) { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } + + public function __construct(\DI\Container $container) + { + parent::__construct($container); + } + } diff --git a/controllers/CalendarController.php b/controllers/CalendarController.php index 48592404..1eb88282 100644 --- a/controllers/CalendarController.php +++ b/controllers/CalendarController.php @@ -4,15 +4,15 @@ namespace Grocy\Controllers; class CalendarController extends BaseController { - public function __construct(\DI\Container $container) - { - parent::__construct($container); - } - public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'calendar', [ 'fullcalendarEventSources' => $this->getCalendarService()->GetEvents() ]); } + + public function __construct(\DI\Container $container) + { + parent::__construct($container); + } } diff --git a/controllers/ChoresApiController.php b/controllers/ChoresApiController.php index 236376fb..71345367 100644 --- a/controllers/ChoresApiController.php +++ b/controllers/ChoresApiController.php @@ -6,40 +6,41 @@ use Grocy\Controllers\Users\User; class ChoresApiController extends BaseApiController { - public function __construct(\DI\Container $container) + public function CalculateNextExecutionAssignments(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - parent::__construct($container); - } - - public function TrackChoreExecution(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - $requestBody = $request->getParsedBody(); - try { - User::checkPermission($request, User::PERMISSION_CHORE_TRACK_EXECUTION); + $requestBody = $request->getParsedBody(); - $trackedTime = date('Y-m-d H:i:s'); - if (array_key_exists('tracked_time', $requestBody) && (IsIsoDateTime($requestBody['tracked_time']) || IsIsoDate($requestBody['tracked_time']))) + $choreId = null; + + if (array_key_exists('chore_id', $requestBody) && !empty($requestBody['chore_id']) && is_numeric($requestBody['chore_id'])) { - $trackedTime = $requestBody['tracked_time']; + $choreId = intval($requestBody['chore_id']); } - $doneBy = GROCY_USER_ID; - if (array_key_exists('done_by', $requestBody) && !empty($requestBody['done_by'])) + if ($choreId === null) { - $doneBy = $requestBody['done_by']; - } - if($doneBy != GROCY_USER_ID) - User::checkPermission($request, User::PERMISSION_CHORE_TRACK_EXECUTION_EXECUTION); + $chores = $this->getDatabase()->chores(); - $choreExecutionId = $this->getChoresService()->TrackChore($args['choreId'], $trackedTime, $doneBy); - return $this->ApiResponse($response, $this->getDatabase()->chores_log($choreExecutionId)); + foreach ($chores as $chore) + { + $this->getChoresService()->CalculateNextExecutionAssignment($chore->id); + } + + } + else + { + $this->getChoresService()->CalculateNextExecutionAssignment($choreId); + } + + return $this->EmptyApiResponse($response); } catch (\Exception $ex) { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } public function ChoreDetails(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -52,6 +53,7 @@ class ChoresApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } public function Current(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -59,6 +61,43 @@ class ChoresApiController extends BaseApiController return $this->ApiResponse($response, $this->getChoresService()->GetCurrent()); } + public function TrackChoreExecution(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + $requestBody = $request->getParsedBody(); + + try + { + User::checkPermission($request, User::PERMISSION_CHORE_TRACK_EXECUTION); + + $trackedTime = date('Y-m-d H:i:s'); + + if (array_key_exists('tracked_time', $requestBody) && (IsIsoDateTime($requestBody['tracked_time']) || IsIsoDate($requestBody['tracked_time']))) + { + $trackedTime = $requestBody['tracked_time']; + } + + $doneBy = GROCY_USER_ID; + + if (array_key_exists('done_by', $requestBody) && !empty($requestBody['done_by'])) + { + $doneBy = $requestBody['done_by']; + } + + if ($doneBy != GROCY_USER_ID) + { + User::checkPermission($request, User::PERMISSION_CHORE_TRACK_EXECUTION_EXECUTION); + } + + $choreExecutionId = $this->getChoresService()->TrackChore($args['choreId'], $trackedTime, $doneBy); + return $this->ApiResponse($response, $this->getDatabase()->chores_log($choreExecutionId)); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + public function UndoChoreExecution(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { try @@ -72,38 +111,12 @@ class ChoresApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } - public function CalculateNextExecutionAssignments(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + public function __construct(\DI\Container $container) { - try - { - $requestBody = $request->getParsedBody(); - - $choreId = null; - if (array_key_exists('chore_id', $requestBody) && !empty($requestBody['chore_id']) && is_numeric($requestBody['chore_id'])) - { - $choreId = intval($requestBody['chore_id']); - } - - if ($choreId === null) - { - $chores = $this->getDatabase()->chores(); - foreach ($chores as $chore) - { - $this->getChoresService()->CalculateNextExecutionAssignment($chore->id); - } - } - else - { - $this->getChoresService()->CalculateNextExecutionAssignment($choreId); - } - - return $this->EmptyApiResponse($response); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } + parent::__construct($container); } + } diff --git a/controllers/ChoresController.php b/controllers/ChoresController.php index e07499d9..9fbf8860 100644 --- a/controllers/ChoresController.php +++ b/controllers/ChoresController.php @@ -4,9 +4,58 @@ namespace Grocy\Controllers; class ChoresController extends BaseController { - public function __construct(\DI\Container $container) + public function ChoreEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - parent::__construct($container); + $usersService = $this->getUsersService(); + $users = $usersService->GetUsersAsDto(); + + if ($args['choreId'] == 'new') + { + return $this->renderPage($response, 'choreform', [ + 'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_PERIOD_TYPE_'), + 'mode' => 'create', + 'userfields' => $this->getUserfieldsService()->GetFields('chores'), + 'assignmentTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_ASSIGNMENT_TYPE_'), + 'users' => $users, + 'products' => $this->getDatabase()->products()->orderBy('name') + ]); + } + else + { + return $this->renderPage($response, 'choreform', [ + 'chore' => $this->getDatabase()->chores($args['choreId']), + 'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_PERIOD_TYPE_'), + 'mode' => 'edit', + 'userfields' => $this->getUserfieldsService()->GetFields('chores'), + 'assignmentTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_ASSIGNMENT_TYPE_'), + 'users' => $users, + 'products' => $this->getDatabase()->products()->orderBy('name') + ]); + } + + } + + public function ChoresList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->renderPage($response, 'chores', [ + 'chores' => $this->getDatabase()->chores()->orderBy('name'), + 'userfields' => $this->getUserfieldsService()->GetFields('chores'), + 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('chores') + ]); + } + + public function ChoresSettings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->renderPage($response, 'choressettings'); + } + + public function Journal(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->renderPage($response, 'choresjournal', [ + 'choresLog' => $this->getDatabase()->chores_log()->orderBy('tracked_time', 'DESC'), + 'chores' => $this->getDatabase()->chores()->orderBy('name'), + 'users' => $this->getDatabase()->users()->orderBy('username') + ]); } public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -32,56 +81,9 @@ class ChoresController extends BaseController ]); } - public function ChoresList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + public function __construct(\DI\Container $container) { - return $this->renderPage($response, 'chores', [ - 'chores' => $this->getDatabase()->chores()->orderBy('name'), - 'userfields' => $this->getUserfieldsService()->GetFields('chores'), - 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('chores') - ]); + parent::__construct($container); } - public function Journal(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->renderPage($response, 'choresjournal', [ - 'choresLog' => $this->getDatabase()->chores_log()->orderBy('tracked_time', 'DESC'), - 'chores' => $this->getDatabase()->chores()->orderBy('name'), - 'users' => $this->getDatabase()->users()->orderBy('username') - ]); - } - - public function ChoreEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - $usersService = $this->getUsersService(); - $users = $usersService->GetUsersAsDto(); - - if ($args['choreId'] == 'new') - { - return $this->renderPage($response, 'choreform', [ - 'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_PERIOD_TYPE_'), - 'mode' => 'create', - 'userfields' => $this->getUserfieldsService()->GetFields('chores'), - 'assignmentTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_ASSIGNMENT_TYPE_'), - 'users' => $users, - 'products' => $this->getDatabase()->products()->orderBy('name') - ]); - } - else - { - return $this->renderPage($response, 'choreform', [ - 'chore' => $this->getDatabase()->chores($args['choreId']), - 'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_PERIOD_TYPE_'), - 'mode' => 'edit', - 'userfields' => $this->getUserfieldsService()->GetFields('chores'), - 'assignmentTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_ASSIGNMENT_TYPE_'), - 'users' => $users, - 'products' => $this->getDatabase()->products()->orderBy('name') - ]); - } - } - - public function ChoresSettings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->renderPage($response, 'choressettings'); - } } diff --git a/controllers/EquipmentController.php b/controllers/EquipmentController.php index 80dc7a19..9cbe8da7 100644 --- a/controllers/EquipmentController.php +++ b/controllers/EquipmentController.php @@ -4,22 +4,8 @@ namespace Grocy\Controllers; class EquipmentController extends BaseController { - public function __construct(\DI\Container $container) - { - parent::__construct($container); - } - protected $UserfieldsService; - public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->renderPage($response, 'equipment', [ - 'equipment' => $this->getDatabase()->equipment()->orderBy('name'), - 'userfields' => $this->getUserfieldsService()->GetFields('equipment'), - 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('equipment') - ]); - } - public function EditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { if ($args['equipmentId'] == 'new') @@ -32,10 +18,26 @@ class EquipmentController extends BaseController else { return $this->renderPage($response, 'equipmentform', [ - 'equipment' => $this->getDatabase()->equipment($args['equipmentId']), + 'equipment' => $this->getDatabase()->equipment($args['equipmentId']), 'mode' => 'edit', 'userfields' => $this->getUserfieldsService()->GetFields('equipment') ]); } + } + + public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->renderPage($response, 'equipment', [ + 'equipment' => $this->getDatabase()->equipment()->orderBy('name'), + 'userfields' => $this->getUserfieldsService()->GetFields('equipment'), + 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('equipment') + ]); + } + + public function __construct(\DI\Container $container) + { + parent::__construct($container); + } + } diff --git a/controllers/ExceptionController.php b/controllers/ExceptionController.php index 0553c564..e646c2b1 100644 --- a/controllers/ExceptionController.php +++ b/controllers/ExceptionController.php @@ -27,16 +27,18 @@ class ExceptionController extends BaseApiController $response = $this->app->getResponseFactory()->createResponse(); $isApiRoute = string_starts_with($request->getUri()->getPath(), '/api/'); + if ($isApiRoute) { $status = 500; + if ($exception instanceof HttpException) { $status = $exception->getCode(); } $data = [ - 'error_message' => $exception->getMessage(), + 'error_message' => $exception->getMessage() ]; if ($displayErrorDetails) @@ -44,7 +46,7 @@ class ExceptionController extends BaseApiController $data['error_details'] = [ 'stack_trace' => $exception->getTraceAsString(), 'file' => $exception->getFile(), - 'line' => $exception->getLine(), + 'line' => $exception->getLine() ]; } @@ -71,4 +73,5 @@ class ExceptionController extends BaseApiController 'exception' => $exception ]); } + } diff --git a/controllers/FilesApiController.php b/controllers/FilesApiController.php index f0620a61..6a298ed2 100644 --- a/controllers/FilesApiController.php +++ b/controllers/FilesApiController.php @@ -2,24 +2,30 @@ namespace Grocy\Controllers; -use \Grocy\Services\FilesService; +use Grocy\Services\FilesService; use Slim\Exception\HttpNotFoundException; class FilesApiController extends BaseApiController { - public function __construct(\DI\Container $container) - { - parent::__construct($container); - } - - public function UploadFile(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + public function DeleteFile(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { try { - $fileName = $this->checkFileName($args['fileName']); + if (IsValidFileName(base64_decode($args['fileName']))) + { + $fileName = base64_decode($args['fileName']); + } + else + { + throw new \Exception('Invalid filename'); + } - $data = $request->getBody()->getContents(); - file_put_contents($this->getFilesService()->GetFilePath($args['group'], $fileName), $data); + $filePath = $this->getFilesService()->GetFilePath($args['group'], $fileName); + + if (file_exists($filePath)) + { + unlink($filePath); + } return $this->EmptyApiResponse($response); } @@ -27,6 +33,7 @@ class FilesApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } public function ServeFile(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -46,13 +53,15 @@ class FilesApiController extends BaseApiController } else { - throw new HttpNotFoundException($request, 'File not found'); + throw new HttpNotFoundException($request, 'File not found'); } + } catch (\Exception $ex) { throw new HttpNotFoundException($request, $ex->getMessage(), $ex); } + } public function ShowFile(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -75,31 +84,23 @@ class FilesApiController extends BaseApiController { throw new HttpNotFoundException($request, 'File not found'); } + } catch (\Exception $ex) { throw new HttpNotFoundException($request, $ex->getMessage(), $ex); } + } - public function DeleteFile(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + public function UploadFile(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { try { - if (IsValidFileName(base64_decode($args['fileName']))) - { - $fileName = base64_decode($args['fileName']); - } - else - { - throw new \Exception('Invalid filename'); - } + $fileName = $this->checkFileName($args['fileName']); - $filePath = $this->getFilesService()->GetFilePath($args['group'], $fileName); - if (file_exists($filePath)) - { - unlink($filePath); - } + $data = $request->getBody()->getContents(); + file_put_contents($this->getFilesService()->GetFilePath($args['group'], $fileName), $data); return $this->EmptyApiResponse($response); } @@ -107,6 +108,31 @@ class FilesApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + + } + + public function __construct(\DI\Container $container) + { + parent::__construct($container); + } + + /** + * @param string $fileName base64-encoded file-name + * @return false|string the decoded file-name + * @throws \Exception if the file-name is invalid. + */ + protected function checkFileName(string $fileName) + { + if (IsValidFileName(base64_decode($fileName))) + { + $fileName = base64_decode($fileName); + } + else + { + throw new \Exception('Invalid filename'); + } + + return $fileName; } /** @@ -118,40 +144,36 @@ class FilesApiController extends BaseApiController protected function getFilePath(string $group, string $fileName, array $queryParams = []) { $forceServeAs = null; - if (isset($queryParams['force_serve_as']) && !empty($queryParams['force_serve_as'])) { + + if (isset($queryParams['force_serve_as']) && !empty($queryParams['force_serve_as'])) + { $forceServeAs = $queryParams['force_serve_as']; } - if ($forceServeAs == FilesService::FILE_SERVE_TYPE_PICTURE) { + if ($forceServeAs == FilesService::FILE_SERVE_TYPE_PICTURE) + { $bestFitHeight = null; - if (isset($queryParams['best_fit_height']) && !empty($queryParams['best_fit_height']) && is_numeric($queryParams['best_fit_height'])) { + + if (isset($queryParams['best_fit_height']) && !empty($queryParams['best_fit_height']) && is_numeric($queryParams['best_fit_height'])) + { $bestFitHeight = $queryParams['best_fit_height']; } $bestFitWidth = null; - if (isset($queryParams['best_fit_width']) && !empty($queryParams['best_fit_width']) && is_numeric($queryParams['best_fit_width'])) { + + if (isset($queryParams['best_fit_width']) && !empty($queryParams['best_fit_width']) && is_numeric($queryParams['best_fit_width'])) + { $bestFitWidth = $queryParams['best_fit_width']; } $filePath = $this->getFilesService()->DownscaleImage($group, $fileName, $bestFitHeight, $bestFitWidth); - } else { + } + else + { $filePath = $this->getFilesService()->GetFilePath($group, $fileName); } + return $filePath; } - /** - * @param string $fileName base64-encoded file-name - * @return false|string the decoded file-name - * @throws \Exception if the file-name is invalid. - */ - protected function checkFileName(string $fileName) - { - if (IsValidFileName(base64_decode($fileName))) { - $fileName = base64_decode($fileName); - } else { - throw new \Exception('Invalid filename'); - } - return $fileName; - } } diff --git a/controllers/GenericEntityApiController.php b/controllers/GenericEntityApiController.php index cd1917b9..97be51fc 100644 --- a/controllers/GenericEntityApiController.php +++ b/controllers/GenericEntityApiController.php @@ -6,66 +6,6 @@ use Grocy\Controllers\Users\User; class GenericEntityApiController extends BaseApiController { - public function __construct(\DI\Container $container) - { - parent::__construct($container); - } - - public function GetObjects(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - $objects = $this->getDatabase()->{$args['entity']}(); - $allUserfields = $this->getUserfieldsService()->GetAllValues($args['entity']); - - foreach ($objects as $object) - { - $userfields = FindAllObjectsInArrayByPropertyValue($allUserfields, 'object_id', $object->id); - $userfieldKeyValuePairs = null; - if (count($userfields) > 0) - { - foreach ($userfields as $userfield) - { - $userfieldKeyValuePairs[$userfield->name] = $userfield->value; - } - } - - $object->userfields = $userfieldKeyValuePairs; - } - - if ($this->IsValidEntity($args['entity']) && !$this->IsEntityWithPreventedListing($args['entity'])) - { - return $this->ApiResponse($response, $objects); - } - else - { - return $this->GenericErrorResponse($response, 'Entity does not exist or is not exposed'); - } - } - - public function GetObject(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - if ($this->IsValidEntity($args['entity']) && !$this->IsEntityWithPreventedListing($args['entity'])) - { - $userfields = $this->getUserfieldsService()->GetValues($args['entity'], $args['objectId']); - if (count($userfields) === 0) - { - $userfields = null; - } - - $object = $this->getDatabase()->{$args['entity']}($args['objectId']); - if ($object == null) { - return $this->GenericErrorResponse($response, 'Object not found', 404); - } - - $object['userfields'] = $userfields; - - return $this->ApiResponse($response, $object); - } - else - { - return $this->GenericErrorResponse($response, 'Entity does not exist or is not exposed'); - } - } - public function AddObject(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { User::checkPermission($request, User::PERMISSION_MASTER_DATA_EDIT); @@ -81,22 +21,44 @@ class GenericEntityApiController extends BaseApiController throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)'); } - $newRow = $this->getDatabase()->{$args['entity']}()->createRow($requestBody); + $newRow = $this->getDatabase()->{$args['entity']} + ()->createRow($requestBody); $newRow->save(); $success = $newRow->isClean(); - return $this->ApiResponse($response, array( + return $this->ApiResponse($response, [ 'created_object_id' => $this->getDatabase()->lastInsertId() - )); + ]); } catch (\Exception $ex) { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } else { return $this->GenericErrorResponse($response, 'Entity does not exist or is not exposed'); } + + } + + public function DeleteObject(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + User::checkPermission($request, User::PERMISSION_MASTER_DATA_EDIT); + + if ($this->IsValidEntity($args['entity'])) + { + $row = $this->getDatabase()->{$args['entity']} + ($args['objectId']); + $row->delete(); + $success = $row->isClean(); + return $this->EmptyApiResponse($response); + } + else + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + } public function EditObject(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -114,7 +76,8 @@ class GenericEntityApiController extends BaseApiController throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)'); } - $row = $this->getDatabase()->{$args['entity']}($args['objectId']); + $row = $this->getDatabase()->{$args['entity']} + ($args['objectId']); $row->update($requestBody); $success = $row->isClean(); return $this->EmptyApiResponse($response); @@ -123,48 +86,77 @@ class GenericEntityApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } else { return $this->GenericErrorResponse($response, 'Entity does not exist or is not exposed'); } + } - public function DeleteObject(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + public function GetObject(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - User::checkPermission($request, User::PERMISSION_MASTER_DATA_EDIT); - - if ($this->IsValidEntity($args['entity'])) + if ($this->IsValidEntity($args['entity']) && !$this->IsEntityWithPreventedListing($args['entity'])) { - $row = $this->getDatabase()->{$args['entity']}($args['objectId']); - $row->delete(); - $success = $row->isClean(); - return $this->EmptyApiResponse($response); + $userfields = $this->getUserfieldsService()->GetValues($args['entity'], $args['objectId']); + + if (count($userfields) === 0) + { + $userfields = null; + } + + $object = $this->getDatabase()->{$args['entity']} + ($args['objectId']); + + if ($object == null) + { + return $this->GenericErrorResponse($response, 'Object not found', 404); + } + + $object['userfields'] = $userfields; + + return $this->ApiResponse($response, $object); } else { - return $this->GenericErrorResponse($response, $ex->getMessage()); + return $this->GenericErrorResponse($response, 'Entity does not exist or is not exposed'); } + } - public function SearchObjects(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + public function GetObjects(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { + $objects = $this->getDatabase()->{$args['entity']} + (); + $allUserfields = $this->getUserfieldsService()->GetAllValues($args['entity']); + + foreach ($objects as $object) + { + $userfields = FindAllObjectsInArrayByPropertyValue($allUserfields, 'object_id', $object->id); + $userfieldKeyValuePairs = null; + + if (count($userfields) > 0) + { + foreach ($userfields as $userfield) + { + $userfieldKeyValuePairs[$userfield->name] = $userfield->value; + } + + } + + $object->userfields = $userfieldKeyValuePairs; + } if ($this->IsValidEntity($args['entity']) && !$this->IsEntityWithPreventedListing($args['entity'])) { - try - { - return $this->ApiResponse($response, $this->getDatabase()->{$args['entity']}()->where('name LIKE ?', '%' . $args['searchString'] . '%')); - } - catch (\PDOException $ex) - { - return $this->GenericErrorResponse($response, 'The given entity has no field "name"'); - } + return $this->ApiResponse($response, $objects); } else { return $this->GenericErrorResponse($response, 'Entity does not exist or is not exposed'); } + } public function GetUserfields(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -177,6 +169,29 @@ class GenericEntityApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + + } + + public function SearchObjects(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + if ($this->IsValidEntity($args['entity']) && !$this->IsEntityWithPreventedListing($args['entity'])) + { + try + { + return $this->ApiResponse($response, $this->getDatabase()->{$args['entity']} + ()->where('name LIKE ?', '%' . $args['searchString'] . '%')); + } + catch (\PDOException $ex) + { + return $this->GenericErrorResponse($response, 'The given entity has no field "name"'); + } + + } + else + { + return $this->GenericErrorResponse($response, 'Entity does not exist or is not exposed'); + } + } public function SetUserfields(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -199,6 +214,17 @@ class GenericEntityApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + + } + + public function __construct(\DI\Container $container) + { + parent::__construct($container); + } + + private function IsEntityWithPreventedListing($entity) + { + return !in_array($entity, $this->getOpenApiSpec()->components->internalSchemas->ExposedEntityButNoListing->enum); } private function IsValidEntity($entity) @@ -206,8 +232,4 @@ class GenericEntityApiController extends BaseApiController return in_array($entity, $this->getOpenApiSpec()->components->internalSchemas->ExposedEntity->enum); } - private function IsEntityWithPreventedListing($entity) - { - return !in_array($entity, $this->getOpenApiSpec()->components->internalSchemas->ExposedEntityButNoListing->enum); - } } diff --git a/controllers/GenericEntityController.php b/controllers/GenericEntityController.php index e11edf5f..7543aac8 100644 --- a/controllers/GenericEntityController.php +++ b/controllers/GenericEntityController.php @@ -4,19 +4,6 @@ namespace Grocy\Controllers; class GenericEntityController extends BaseController { - public function __construct(\DI\Container $container) - { - parent::__construct($container); - } - - public function UserfieldsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->renderPage($response, 'userfields', [ - 'userfields' => $this->getUserfieldsService()->GetAllFields(), - 'entities' => $this->getUserfieldsService()->GetEntities() - ]); - } - public function UserentitiesList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'userentities', [ @@ -24,16 +11,22 @@ class GenericEntityController extends BaseController ]); } - public function UserobjectsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + public function UserentityEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - $userentity = $this->getDatabase()->userentities()->where('name = :1', $args['userentityName'])->fetch(); + if ($args['userentityId'] == 'new') + { + return $this->renderPage($response, 'userentityform', [ + 'mode' => 'create' + ]); + } + else + { + return $this->renderPage($response, 'userentityform', [ + 'mode' => 'edit', + 'userentity' => $this->getDatabase()->userentities($args['userentityId']) + ]); + } - return $this->renderPage($response, 'userobjects', [ - 'userentity' => $userentity, - 'userobjects' => $this->getDatabase()->userobjects()->where('userentity_id = :1', $userentity->id), - 'userfields' => $this->getUserfieldsService()->GetFields('userentity-' . $args['userentityName']), - 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('userentity-' . $args['userentityName']) - ]); } public function UserfieldEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -50,28 +43,20 @@ class GenericEntityController extends BaseController { return $this->renderPage($response, 'userfieldform', [ 'mode' => 'edit', - 'userfield' => $this->getUserfieldsService()->GetField($args['userfieldId']), + 'userfield' => $this->getUserfieldsService()->GetField($args['userfieldId']), 'userfieldTypes' => $this->getUserfieldsService()->GetFieldTypes(), 'entities' => $this->getUserfieldsService()->GetEntities() ]); } + } - public function UserentityEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + public function UserfieldsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - if ($args['userentityId'] == 'new') - { - return $this->renderPage($response, 'userentityform', [ - 'mode' => 'create' - ]); - } - else - { - return $this->renderPage($response, 'userentityform', [ - 'mode' => 'edit', - 'userentity' => $this->getDatabase()->userentities($args['userentityId']) - ]); - } + return $this->renderPage($response, 'userfields', [ + 'userfields' => $this->getUserfieldsService()->GetAllFields(), + 'entities' => $this->getUserfieldsService()->GetEntities() + ]); } public function UserobjectEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -91,9 +76,28 @@ class GenericEntityController extends BaseController return $this->renderPage($response, 'userobjectform', [ 'userentity' => $userentity, 'mode' => 'edit', - 'userobject' => $this->getDatabase()->userobjects($args['userobjectId']), + 'userobject' => $this->getDatabase()->userobjects($args['userobjectId']), 'userfields' => $this->getUserfieldsService()->GetFields('userentity-' . $args['userentityName']) ]); } + } + + public function UserobjectsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + $userentity = $this->getDatabase()->userentities()->where('name = :1', $args['userentityName'])->fetch(); + + return $this->renderPage($response, 'userobjects', [ + 'userentity' => $userentity, + 'userobjects' => $this->getDatabase()->userobjects()->where('userentity_id = :1', $userentity->id), + 'userfields' => $this->getUserfieldsService()->GetFields('userentity-' . $args['userentityName']), + 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('userentity-' . $args['userentityName']) + ]); + } + + public function __construct(\DI\Container $container) + { + parent::__construct($container); + } + } diff --git a/controllers/LoginController.php b/controllers/LoginController.php index 21dbd956..0a0d3698 100644 --- a/controllers/LoginController.php +++ b/controllers/LoginController.php @@ -4,45 +4,11 @@ namespace Grocy\Controllers; class LoginController extends BaseController { - public function __construct(\DI\Container $container, string $sessionCookieName) - { - parent::__construct($container); - $this->SessionCookieName = $sessionCookieName; - } protected $SessionCookieName; - public function ProcessLogin(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + public function GetSessionCookieName() { - $postParams = $request->getParsedBody(); - if (isset($postParams['username']) && isset($postParams['password'])) - { - $user = $this->getDatabase()->users()->where('username', $postParams['username'])->fetch(); - $inputPassword = $postParams['password']; - $stayLoggedInPermanently = $postParams['stay_logged_in'] == 'on'; - - if ($user !== null && password_verify($inputPassword, $user->password)) - { - $sessionKey = $this->getSessionService()->CreateSession($user->id, $stayLoggedInPermanently); - setcookie($this->SessionCookieName, $sessionKey, PHP_INT_SIZE == 4 ? PHP_INT_MAX : PHP_INT_MAX>>32); // Cookie expires never, but session validity is up to SessionService - - if (password_needs_rehash($user->password, PASSWORD_DEFAULT)) - { - $user->update(array( - 'password' => password_hash($inputPassword, PASSWORD_DEFAULT) - )); - } - - return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/')); - } - else - { - return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/login?invalid=true')); - } - } - else - { - return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/login?invalid=true')); - } + return $this->SessionCookieName; } public function LoginPage(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -56,8 +22,49 @@ class LoginController extends BaseController return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/')); } - public function GetSessionCookieName() + public function ProcessLogin(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - return $this->SessionCookieName; + $postParams = $request->getParsedBody(); + + if (isset($postParams['username']) && isset($postParams['password'])) + { + $user = $this->getDatabase()->users()->where('username', $postParams['username'])->fetch(); + $inputPassword = $postParams['password']; + $stayLoggedInPermanently = $postParams['stay_logged_in'] == 'on'; + + if ($user !== null && password_verify($inputPassword, $user->password)) + { + $sessionKey = $this->getSessionService()->CreateSession($user->id, $stayLoggedInPermanently); + setcookie($this->SessionCookieName, $sessionKey, PHP_INT_SIZE == 4 ? PHP_INT_MAX : PHP_INT_MAX >> 32); + +// Cookie expires never, but session validity is up to SessionService + + if (password_needs_rehash($user->password, PASSWORD_DEFAULT)) + { + $user->update([ + 'password' => password_hash($inputPassword, PASSWORD_DEFAULT) + ]); + } + + return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/')); + } + else + { + return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/login?invalid=true')); + } + + } + else + { + return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/login?invalid=true')); + } + } + + public function __construct(\DI\Container $container, string $sessionCookieName) + { + parent::__construct($container); + $this->SessionCookieName = $sessionCookieName; + } + } diff --git a/controllers/OpenApiController.php b/controllers/OpenApiController.php index a312a48f..6b9e6177 100644 --- a/controllers/OpenApiController.php +++ b/controllers/OpenApiController.php @@ -4,28 +4,6 @@ namespace Grocy\Controllers; class OpenApiController extends BaseApiController { - public function __construct(\DI\Container $container) - { - parent::__construct($container); - } - - public function DocumentationUi(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->render($response, 'openapiui'); - } - - public function DocumentationSpec(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - $applicationService = $this->getApplicationService(); - - $versionInfo = $applicationService->GetInstalledVersion(); - $this->getOpenApiSpec()->info->version = $versionInfo->Version; - $this->getOpenApiSpec()->info->description = str_replace('PlaceHolderManageApiKeysUrl', $this->AppContainer->get('UrlManager')->ConstructUrl('/manageapikeys'), $this->getOpenApiSpec()->info->description); - $this->getOpenApiSpec()->servers[0]->url = $this->AppContainer->get('UrlManager')->ConstructUrl('/api'); - - return $this->ApiResponse($response, $this->getOpenApiSpec()); - } - public function ApiKeysList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'manageapikeys', [ @@ -40,4 +18,26 @@ class OpenApiController extends BaseApiController $newApiKeyId = $this->getApiKeyService()->GetApiKeyId($newApiKey); return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl("/manageapikeys?CreatedApiKeyId=$newApiKeyId")); } + + public function DocumentationSpec(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + $applicationService = $this->getApplicationService(); + + $versionInfo = $applicationService->GetInstalledVersion(); + $this->getOpenApiSpec()->info->version = $versionInfo->Version; + $this->getOpenApiSpec()->info->description = str_replace('PlaceHolderManageApiKeysUrl', $this->AppContainer->get('UrlManager')->ConstructUrl('/manageapikeys'), $this->getOpenApiSpec()->info->description); + $this->getOpenApiSpec()->servers[0]->url = $this->AppContainer->get('UrlManager')->ConstructUrl('/api'); + + return $this->ApiResponse($response, $this->getOpenApiSpec()); + } + + public function DocumentationUi(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->render($response, 'openapiui'); + } + + public function __construct(\DI\Container $container) + { + parent::__construct($container); + } } diff --git a/controllers/RecipesApiController.php b/controllers/RecipesApiController.php index b44e0de8..1825f3a6 100644 --- a/controllers/RecipesApiController.php +++ b/controllers/RecipesApiController.php @@ -6,11 +6,6 @@ use Grocy\Controllers\Users\User; class RecipesApiController extends BaseApiController { - public function __construct(\DI\Container $container) - { - parent::__construct($container); - } - public function AddNotFulfilledProductsToShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_ADD); @@ -40,19 +35,21 @@ class RecipesApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } public function GetRecipeFulfillment(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { try { - if(!isset($args['recipeId'])) + if (!isset($args['recipeId'])) { return $this->ApiResponse($response, $this->getRecipesService()->GetRecipesResolved()); } $recipeResolved = FindObjectInArrayByPropertyValue($this->getRecipesService()->GetRecipesResolved(), 'recipe_id', $args['recipeId']); - if(!$recipeResolved) + + if (!$recipeResolved) { throw new \Exception('Recipe does not exist'); } @@ -60,10 +57,18 @@ class RecipesApiController extends BaseApiController { return $this->ApiResponse($response, $recipeResolved); } + } catch (\Exception $ex) { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } + + public function __construct(\DI\Container $container) + { + parent::__construct($container); + } + } diff --git a/controllers/RecipesController.php b/controllers/RecipesController.php index ffacbf82..7cd88ab8 100644 --- a/controllers/RecipesController.php +++ b/controllers/RecipesController.php @@ -2,13 +2,54 @@ namespace Grocy\Controllers; -use \Grocy\Services\RecipesService; +use Grocy\Services\RecipesService; class RecipesController extends BaseController { - public function __construct(\DI\Container $container) + public function MealPlan(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - parent::__construct($container); + $recipes = $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll(); + + $events = []; + + foreach ($this->getDatabase()->meal_plan() as $mealPlanEntry) + { + $recipe = FindObjectInArrayByPropertyValue($recipes, 'id', $mealPlanEntry['recipe_id']); + $title = ''; + + if ($recipe !== null) + { + $title = $recipe->name; + } + + $productDetails = null; + + if ($mealPlanEntry['product_id'] !== null) + { + $productDetails = $this->getStockService()->GetProductDetails($mealPlanEntry['product_id']); + } + + $events[] = [ + 'id' => $mealPlanEntry['id'], + 'title' => $title, + 'start' => $mealPlanEntry['day'], + 'date_format' => 'date', + 'recipe' => json_encode($recipe), + 'mealPlanEntry' => json_encode($mealPlanEntry), + 'type' => $mealPlanEntry['type'], + 'productDetails' => json_encode($productDetails) + ]; + } + + return $this->renderPage($response, 'mealplan', [ + 'fullcalendarEventSources' => $events, + 'recipes' => $recipes, + 'internalRecipes' => $this->getDatabase()->recipes()->whereNot('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll(), + 'recipesResolved' => $this->getRecipesService()->GetRecipesResolved(), + 'products' => $this->getDatabase()->products()->orderBy('name'), + 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() + ]); } public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -27,8 +68,9 @@ class RecipesController extends BaseController foreach ($recipes as $recipe) { $selectedRecipe = $recipe; - break; + break; } + } $selectedRecipePositionsResolved = $this->getDatabase()->recipes_pos_resolved()->where('recipe_id = :1 AND is_nested_recipe_pos = 0', $selectedRecipe->id)->orderBy('ingredient_group', 'ASC', 'product_group', 'ASC'); @@ -52,15 +94,17 @@ class RecipesController extends BaseController { $selectedRecipeSubRecipes = $this->getDatabase()->recipes()->where('id IN (SELECT includes_recipe_id FROM recipes_nestings_resolved WHERE recipe_id = :1 AND includes_recipe_id != :1)', $selectedRecipe->id)->orderBy('name')->fetchAll(); - $includedRecipeIdsAbsolute = array(); + $includedRecipeIdsAbsolute = []; $includedRecipeIdsAbsolute[] = $selectedRecipe->id; - foreach($selectedRecipeSubRecipes as $subRecipe) + + foreach ($selectedRecipeSubRecipes as $subRecipe) { $includedRecipeIdsAbsolute[] = $subRecipe->id; } - $allRecipePositions = array(); - foreach($includedRecipeIdsAbsolute as $id) + $allRecipePositions = []; + + foreach ($includedRecipeIdsAbsolute as $id) { $allRecipePositions[$id] = $this->getDatabase()->recipes_pos_resolved()->where('recipe_id = :1 AND is_nested_recipe_pos = 0', $id)->orderBy('ingredient_group', 'ASC', 'product_group', 'ASC'); } @@ -78,15 +122,15 @@ class RecipesController extends BaseController $recipeId = $args['recipeId']; return $this->renderPage($response, 'recipeform', [ - 'recipe' => $this->getDatabase()->recipes($recipeId), - 'recipePositions' => $this->getDatabase()->recipes_pos()->where('recipe_id', $recipeId), - 'mode' => $recipeId == 'new' ? "create" : "edit", + 'recipe' => $this->getDatabase()->recipes($recipeId), + 'recipePositions' => $this->getDatabase()->recipes_pos()->where('recipe_id', $recipeId), + 'mode' => $recipeId == 'new' ? 'create' : 'edit', 'products' => $this->getDatabase()->products()->orderBy('name'), 'quantityunits' => $this->getDatabase()->quantity_units(), 'recipePositionsResolved' => $this->getRecipesService()->GetRecipesPosResolved(), 'recipesResolved' => $this->getRecipesService()->GetRecipesResolved(), - 'recipes' => $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name'), - 'recipeNestings' => $this->getDatabase()->recipes_nestings()->where('recipe_id', $recipeId), + 'recipes' => $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name'), + 'recipeNestings' => $this->getDatabase()->recipes_nestings()->where('recipe_id', $recipeId), 'userfields' => $this->getUserfieldsService()->GetFields('recipes'), 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() ]); @@ -109,13 +153,14 @@ class RecipesController extends BaseController { return $this->renderPage($response, 'recipeposform', [ 'mode' => 'edit', - 'recipe' => $this->getDatabase()->recipes($args['recipeId']), + 'recipe' => $this->getDatabase()->recipes($args['recipeId']), 'recipePos' => $this->getDatabase()->recipes_pos($args['recipePosId']), 'products' => $this->getDatabase()->products()->orderBy('name'), 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() ]); } + } public function RecipesSettings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -123,46 +168,9 @@ class RecipesController extends BaseController return $this->renderPage($response, 'recipessettings'); } - public function MealPlan(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + public function __construct(\DI\Container $container) { - $recipes = $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll(); - - $events = array(); - foreach($this->getDatabase()->meal_plan() as $mealPlanEntry) - { - $recipe = FindObjectInArrayByPropertyValue($recipes, 'id', $mealPlanEntry['recipe_id']); - $title = ''; - if ($recipe !== null) - { - $title = $recipe->name; - } - - $productDetails = null; - if ($mealPlanEntry['product_id'] !== null) - { - $productDetails = $this->getStockService()->GetProductDetails($mealPlanEntry['product_id']); - } - - $events[] = array( - 'id' => $mealPlanEntry['id'], - 'title' => $title, - 'start' => $mealPlanEntry['day'], - 'date_format' => 'date', - 'recipe' => json_encode($recipe), - 'mealPlanEntry' => json_encode($mealPlanEntry), - 'type' => $mealPlanEntry['type'], - 'productDetails' => json_encode($productDetails) - ); - } - - return $this->renderPage($response, 'mealplan', [ - 'fullcalendarEventSources' => $events, - 'recipes' => $recipes, - 'internalRecipes' => $this->getDatabase()->recipes()->whereNot('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll(), - 'recipesResolved' => $this->getRecipesService()->GetRecipesResolved(), - 'products' => $this->getDatabase()->products()->orderBy('name'), - 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved() - ]); + parent::__construct($container); } + } diff --git a/controllers/StockApiController.php b/controllers/StockApiController.php index 92e34929..b693de69 100644 --- a/controllers/StockApiController.php +++ b/controllers/StockApiController.php @@ -3,62 +3,33 @@ namespace Grocy\Controllers; use Grocy\Controllers\Users\User; -use \Grocy\Services\StockService; +use Grocy\Services\StockService; class StockApiController extends BaseApiController { - public function __construct(\DI\Container $container) + public function AddMissingProductsToShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - parent::__construct($container); - } + User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_ADD); - public function ProductDetails(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { try { - return $this->ApiResponse($response, $this->getStockService()->GetProductDetails($args['productId'])); + $requestBody = $request->getParsedBody(); + + $listId = 1; + + if (array_key_exists('list_id', $requestBody) && !empty($requestBody['list_id']) && is_numeric($requestBody['list_id'])) + { + $listId = intval($requestBody['list_id']); + } + + $this->getStockService()->AddMissingProductsToShoppingList($listId); + return $this->EmptyApiResponse($response); } catch (\Exception $ex) { return $this->GenericErrorResponse($response, $ex->getMessage()); } - } - public function ProductBarcodeDetails(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - try - { - return $this->ApiResponse($response, $this->getDatabase()->product_barcodes()->where('barcode = :1', $args['barcode'])->fetch()); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } - } - - public function ProductDetailsByBarcode(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - try - { - $productId = $this->getStockService()->GetProductIdFromBarcode($args['barcode']); - return $this->ApiResponse($response, $this->getStockService()->GetProductDetails($productId)); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } - } - - public function ProductPriceHistory(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - try - { - return $this->ApiResponse($response, $this->getStockService()->GetProductPriceHistory($args['productId'])); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } } public function AddProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -80,37 +51,43 @@ class StockApiController extends BaseApiController } $bestBeforeDate = null; + if (array_key_exists('best_before_date', $requestBody) && IsIsoDate($requestBody['best_before_date'])) { $bestBeforeDate = $requestBody['best_before_date']; } $price = null; + if (array_key_exists('price', $requestBody) && is_numeric($requestBody['price'])) { $price = $requestBody['price']; } $locationId = null; + if (array_key_exists('location_id', $requestBody) && is_numeric($requestBody['location_id'])) { $locationId = $requestBody['location_id']; } $shoppingLocationId = null; + if (array_key_exists('shopping_location_id', $requestBody) && is_numeric($requestBody['shopping_location_id'])) { $shoppingLocationId = $requestBody['shopping_location_id']; } $quFactorPurchaseToStock = null; + if (array_key_exists('qu_factor_purchase_to_stock', $requestBody) && is_numeric($requestBody['qu_factor_purchase_to_stock'])) { $quFactorPurchaseToStock = $requestBody['qu_factor_purchase_to_stock']; } $transactionType = StockService::TRANSACTION_TYPE_PURCHASE; - if (array_key_exists('transaction_type', $requestBody) && !empty($requestBody['transactiontype'])) + + if (array_key_exists('transaction_type', $requestBody) && !empty($requestBody['transactiontype'])) { $transactionType = $requestBody['transactiontype']; } @@ -122,6 +99,7 @@ class StockApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } public function AddProductByBarcode(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -135,6 +113,184 @@ class StockApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + + } + + public function AddProductToShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_ADD); + + try + { + $requestBody = $request->getParsedBody(); + + $listId = 1; + $amount = 1; + $productId = null; + $note = null; + + if (array_key_exists('list_id', $requestBody) && !empty($requestBody['list_id']) && is_numeric($requestBody['list_id'])) + { + $listId = intval($requestBody['list_id']); + } + + if (array_key_exists('product_amount', $requestBody) && !empty($requestBody['product_amount']) && is_numeric($requestBody['product_amount'])) + { + $amount = intval($requestBody['product_amount']); + } + + if (array_key_exists('product_id', $requestBody) && !empty($requestBody['product_id']) && is_numeric($requestBody['product_id'])) + { + $productId = intval($requestBody['product_id']); + } + + if (array_key_exists('note', $requestBody) && !empty($requestBody['note'])) + { + $note = $requestBody['note']; + } + + if ($productId == null) + { + throw new \Exception('No product id was supplied'); + } + + $this->getStockService()->AddProductToShoppingList($productId, $amount, $note, $listId); + return $this->EmptyApiResponse($response); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + + public function ClearShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_DELETE); + + try + { + $requestBody = $request->getParsedBody(); + + $listId = 1; + + if (array_key_exists('list_id', $requestBody) && !empty($requestBody['list_id']) && is_numeric($requestBody['list_id'])) + { + $listId = intval($requestBody['list_id']); + } + + $this->getStockService()->ClearShoppingList($listId); + return $this->EmptyApiResponse($response); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + + public function ConsumeProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + User::checkPermission($request, User::PERMISSION_STOCK_CONSUME); + + $requestBody = $request->getParsedBody(); + + $result = null; + + try + { + if ($requestBody === null) + { + throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)'); + } + + if (!array_key_exists('amount', $requestBody)) + { + throw new \Exception('An amount is required'); + } + + $spoiled = false; + + if (array_key_exists('spoiled', $requestBody)) + { + $spoiled = $requestBody['spoiled']; + } + + $transactionType = StockService::TRANSACTION_TYPE_CONSUME; + + if (array_key_exists('transaction_type', $requestBody) && !empty($requestBody['transactiontype'])) + { + $transactionType = $requestBody['transactiontype']; + } + + $specificStockEntryId = 'default'; + + if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id'])) + { + $specificStockEntryId = $requestBody['stock_entry_id']; + } + + $locationId = null; + + if (array_key_exists('location_id', $requestBody) && !empty($requestBody['location_id']) && is_numeric($requestBody['location_id'])) + { + $locationId = $requestBody['location_id']; + } + + $recipeId = null; + + if (array_key_exists('recipe_id', $requestBody) && is_numeric($requestBody['recipe_id'])) + { + $recipeId = $requestBody['recipe_id']; + } + + $bookingId = $this->getStockService()->ConsumeProduct($args['productId'], $requestBody['amount'], $spoiled, $transactionType, $specificStockEntryId, $recipeId, $locationId); + return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId)); + } + catch (\Exception $ex) + { + $result = $this->GenericErrorResponse($response, $ex->getMessage()); + } + + return $result; + } + + public function ConsumeProductByBarcode(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try + { + $args['productId'] = $this->getStockService()->GetProductIdFromBarcode($args['barcode']); + return $this->ConsumeProduct($request, $response, $args); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + + public function CurrentStock(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->ApiResponse($response, $this->getStockService()->GetCurrentStock()); + } + + public function CurrentVolatileStock(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + $nextXDays = 5; + + if (isset($request->getQueryParams()['expiring_days']) && !empty($request->getQueryParams()['expiring_days']) && is_numeric($request->getQueryParams()['expiring_days'])) + { + $nextXDays = $request->getQueryParams()['expiring_days']; + } + + $expiringProducts = $this->getStockService()->GetExpiringProducts($nextXDays, true); + $expiredProducts = $this->getStockService()->GetExpiringProducts(-1); + $missingProducts = $this->getStockService()->GetMissingProducts(); + return $this->ApiResponse($response, [ + 'expiring_products' => $expiringProducts, + 'expired_products' => $expiredProducts, + 'missing_products' => $missingProducts + ]); } public function EditStockEntry(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -156,24 +312,28 @@ class StockApiController extends BaseApiController } $bestBeforeDate = null; + if (array_key_exists('best_before_date', $requestBody) && IsIsoDate($requestBody['best_before_date'])) { $bestBeforeDate = $requestBody['best_before_date']; } $price = null; + if (array_key_exists('price', $requestBody) && is_numeric($requestBody['price'])) { $price = $requestBody['price']; } $locationId = null; + if (array_key_exists('location_id', $requestBody) && is_numeric($requestBody['location_id'])) { $locationId = $requestBody['location_id']; } $shoppingLocationId = null; + if (array_key_exists('shopping_location_id', $requestBody) && is_numeric($requestBody['shopping_location_id'])) { $shoppingLocationId = $requestBody['shopping_location_id']; @@ -186,6 +346,305 @@ class StockApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + + } + + public function ExternalBarcodeLookup(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + User::checkPermission($request, User::PERMISSION_MASTER_DATA_EDIT); + + try + { + $addFoundProduct = false; + + if (isset($request->getQueryParams()['add']) && ($request->getQueryParams()['add'] === 'true' || $request->getQueryParams()['add'] === 1)) + { + $addFoundProduct = true; + } + + return $this->ApiResponse($response, $this->getStockService()->ExternalBarcodeLookup($args['barcode'], $addFoundProduct)); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + + public function InventoryProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + User::checkPermission($request, User::PERMISSION_STOCK_INVENTORY); + + $requestBody = $request->getParsedBody(); + + try + { + if ($requestBody === null) + { + throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)'); + } + + if (!array_key_exists('new_amount', $requestBody)) + { + throw new \Exception('An new amount is required'); + } + + $bestBeforeDate = null; + + if (array_key_exists('best_before_date', $requestBody) && IsIsoDate($requestBody['best_before_date'])) + { + $bestBeforeDate = $requestBody['best_before_date']; + } + + $locationId = null; + + if (array_key_exists('location_id', $requestBody) && is_numeric($requestBody['location_id'])) + { + $locationId = $requestBody['location_id']; + } + + $price = null; + + if (array_key_exists('price', $requestBody) && is_numeric($requestBody['price'])) + { + $price = $requestBody['price']; + } + + $shoppingLocationId = null; + + if (array_key_exists('shopping_location_id', $requestBody) && is_numeric($requestBody['shopping_location_id'])) + { + $shoppingLocationId = $requestBody['shopping_location_id']; + } + + $bookingId = $this->getStockService()->InventoryProduct($args['productId'], $requestBody['new_amount'], $bestBeforeDate, $locationId, $price, $shoppingLocationId); + return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId)); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + + public function InventoryProductByBarcode(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try + { + $args['productId'] = $this->getStockService()->GetProductIdFromBarcode($args['barcode']); + return $this->InventoryProduct($request, $response, $args); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + + public function OpenProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + User::checkPermission($request, User::PERMISSION_STOCK_OPEN); + + $requestBody = $request->getParsedBody(); + + try + { + if ($requestBody === null) + { + throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)'); + } + + if (!array_key_exists('amount', $requestBody)) + { + throw new \Exception('An amount is required'); + } + + $specificStockEntryId = 'default'; + + if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id'])) + { + $specificStockEntryId = $requestBody['stock_entry_id']; + } + + $bookingId = $this->getStockService()->OpenProduct($args['productId'], $requestBody['amount'], $specificStockEntryId); + return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId)); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + + public function OpenProductByBarcode(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try + { + $args['productId'] = $this->getStockService()->GetProductIdFromBarcode($args['barcode']); + return $this->OpenProduct($request, $response, $args); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + + public function ProductBarcodeDetails(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try + { + return $this->ApiResponse($response, $this->getDatabase()->product_barcodes()->where('barcode = :1', $args['barcode'])->fetch()); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + + public function ProductDetails(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try + { + return $this->ApiResponse($response, $this->getStockService()->GetProductDetails($args['productId'])); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + + public function ProductDetailsByBarcode(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try + { + $productId = $this->getStockService()->GetProductIdFromBarcode($args['barcode']); + return $this->ApiResponse($response, $this->getStockService()->GetProductDetails($productId)); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + + public function ProductPriceHistory(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try + { + return $this->ApiResponse($response, $this->getStockService()->GetProductPriceHistory($args['productId'])); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + + public function ProductStockEntries(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + $allowSubproductSubstitution = false; + + if (isset($request->getQueryParams()['include_sub_products']) && filter_var($request->getQueryParams()['include_sub_products'], FILTER_VALIDATE_BOOLEAN)) + { + $allowSubproductSubstitution = true; + } + + return $this->ApiResponse($response, $this->getStockService()->GetProductStockEntries($args['productId'], false, $allowSubproductSubstitution)); + } + + public function ProductStockLocations(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->ApiResponse($response, $this->getStockService()->GetProductStockLocations($args['productId'])); + } + + public function RemoveProductFromShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_DELETE); + + try + { + $requestBody = $request->getParsedBody(); + + $listId = 1; + $amount = 1; + $productId = null; + + if (array_key_exists('list_id', $requestBody) && !empty($requestBody['list_id']) && is_numeric($requestBody['list_id'])) + { + $listId = intval($requestBody['list_id']); + } + + if (array_key_exists('product_amount', $requestBody) && !empty($requestBody['product_amount']) && is_numeric($requestBody['product_amount'])) + { + $amount = intval($requestBody['product_amount']); + } + + if (array_key_exists('product_id', $requestBody) && !empty($requestBody['product_id']) && is_numeric($requestBody['product_id'])) + { + $productId = intval($requestBody['product_id']); + } + + if ($productId == null) + { + throw new \Exception('No product id was supplied'); + } + + $this->getStockService()->RemoveProductFromShoppingList($productId, $amount, $listId); + return $this->EmptyApiResponse($response); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + + public function StockBooking(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try + { + $stockLogRow = $this->getDatabase()->stock_log($args['bookingId']); + + if ($stockLogRow === null) + { + throw new \Exception('Stock booking does not exist'); + } + + return $this->ApiResponse($response, $stockLogRow); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + + public function StockEntry(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->ApiResponse($response, $this->getStockService()->GetStockEntry($args['entryId'])); + } + + public function StockTransactions(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try + { + $transactionRows = $this->getDatabase()->stock_log()->where('transaction_id = :1', $args['transactionId'])->fetchAll(); + + if (count($transactionRows) === 0) + { + throw new \Exception('No transaction was found by the given transaction id'); + } + + return $this->ApiResponse($response, $transactionRows); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + } public function TransferProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -217,6 +676,7 @@ class StockApiController extends BaseApiController } $specificStockEntryId = 'default'; + if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id'])) { $specificStockEntryId = $requestBody['stock_entry_id']; @@ -229,6 +689,7 @@ class StockApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } public function TransferProductByBarcode(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -242,360 +703,7 @@ class StockApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } - } - public function ConsumeProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - User::checkPermission($request, User::PERMISSION_STOCK_CONSUME); - - $requestBody = $request->getParsedBody(); - - $result = null; - - try - { - if ($requestBody === null) - { - throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)'); - } - - if (!array_key_exists('amount', $requestBody)) - { - throw new \Exception('An amount is required'); - } - - $spoiled = false; - if (array_key_exists('spoiled', $requestBody)) - { - $spoiled = $requestBody['spoiled']; - } - - $transactionType = StockService::TRANSACTION_TYPE_CONSUME; - if (array_key_exists('transaction_type', $requestBody) && !empty($requestBody['transactiontype'])) - { - $transactionType = $requestBody['transactiontype']; - } - - $specificStockEntryId = 'default'; - if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id'])) - { - $specificStockEntryId = $requestBody['stock_entry_id']; - } - - $locationId = null; - if (array_key_exists('location_id', $requestBody) && !empty($requestBody['location_id']) && is_numeric($requestBody['location_id'])) - { - $locationId = $requestBody['location_id']; - } - - $recipeId = null; - if (array_key_exists('recipe_id', $requestBody) && is_numeric($requestBody['recipe_id'])) - { - $recipeId = $requestBody['recipe_id']; - } - - $bookingId = $this->getStockService()->ConsumeProduct($args['productId'], $requestBody['amount'], $spoiled, $transactionType, $specificStockEntryId, $recipeId, $locationId); - return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId)); - } - catch (\Exception $ex) - { - $result = $this->GenericErrorResponse($response, $ex->getMessage()); - } - return $result; - } - - public function ConsumeProductByBarcode(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - try - { - $args['productId'] = $this->getStockService()->GetProductIdFromBarcode($args['barcode']); - return $this->ConsumeProduct($request, $response, $args); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } - } - - public function InventoryProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - User::checkPermission($request, User::PERMISSION_STOCK_INVENTORY); - - $requestBody = $request->getParsedBody(); - - try - { - if ($requestBody === null) - { - throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)'); - } - - if (!array_key_exists('new_amount', $requestBody)) - { - throw new \Exception('An new amount is required'); - } - - $bestBeforeDate = null; - if (array_key_exists('best_before_date', $requestBody) && IsIsoDate($requestBody['best_before_date'])) - { - $bestBeforeDate = $requestBody['best_before_date']; - } - - $locationId = null; - if (array_key_exists('location_id', $requestBody) && is_numeric($requestBody['location_id'])) - { - $locationId = $requestBody['location_id']; - } - - $price = null; - if (array_key_exists('price', $requestBody) && is_numeric($requestBody['price'])) - { - $price = $requestBody['price']; - } - - $shoppingLocationId = null; - if (array_key_exists('shopping_location_id', $requestBody) && is_numeric($requestBody['shopping_location_id'])) - { - $shoppingLocationId = $requestBody['shopping_location_id']; - } - - $bookingId = $this->getStockService()->InventoryProduct($args['productId'], $requestBody['new_amount'], $bestBeforeDate, $locationId, $price, $shoppingLocationId); - return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId)); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } - } - - public function InventoryProductByBarcode(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - try - { - $args['productId'] = $this->getStockService()->GetProductIdFromBarcode($args['barcode']); - return $this->InventoryProduct($request, $response, $args); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } - } - - public function OpenProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - User::checkPermission($request, User::PERMISSION_STOCK_OPEN); - - $requestBody = $request->getParsedBody(); - - try - { - if ($requestBody === null) - { - throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)'); - } - - if (!array_key_exists('amount', $requestBody)) - { - throw new \Exception('An amount is required'); - } - - $specificStockEntryId = 'default'; - if (array_key_exists('stock_entry_id', $requestBody) && !empty($requestBody['stock_entry_id'])) - { - $specificStockEntryId = $requestBody['stock_entry_id']; - } - - $bookingId = $this->getStockService()->OpenProduct($args['productId'], $requestBody['amount'], $specificStockEntryId); - return $this->ApiResponse($response, $this->getDatabase()->stock_log($bookingId)); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } - } - - public function OpenProductByBarcode(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - try - { - $args['productId'] = $this->getStockService()->GetProductIdFromBarcode($args['barcode']); - return $this->OpenProduct($request, $response, $args); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } - } - - public function CurrentStock(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->ApiResponse($response, $this->getStockService()->GetCurrentStock()); - } - - public function CurrentVolatileStock(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - $nextXDays = 5; - if (isset($request->getQueryParams()['expiring_days']) && !empty($request->getQueryParams()['expiring_days']) && is_numeric($request->getQueryParams()['expiring_days'])) - { - $nextXDays = $request->getQueryParams()['expiring_days']; - } - - $expiringProducts = $this->getStockService()->GetExpiringProducts($nextXDays, true); - $expiredProducts = $this->getStockService()->GetExpiringProducts(-1); - $missingProducts = $this->getStockService()->GetMissingProducts(); - return $this->ApiResponse($response, array( - 'expiring_products' => $expiringProducts, - 'expired_products' => $expiredProducts, - 'missing_products' => $missingProducts - )); - } - - public function AddMissingProductsToShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_ADD); - - try - { - $requestBody = $request->getParsedBody(); - - $listId = 1; - if (array_key_exists('list_id', $requestBody) && !empty($requestBody['list_id']) && is_numeric($requestBody['list_id'])) - { - $listId = intval($requestBody['list_id']); - } - - $this->getStockService()->AddMissingProductsToShoppingList($listId); - return $this->EmptyApiResponse($response); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } - } - - public function ClearShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_DELETE); - - try - { - $requestBody = $request->getParsedBody(); - - $listId = 1; - if (array_key_exists('list_id', $requestBody) && !empty($requestBody['list_id']) && is_numeric($requestBody['list_id'])) - { - $listId = intval($requestBody['list_id']); - } - - $this->getStockService()->ClearShoppingList($listId); - return $this->EmptyApiResponse($response); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } - } - - - public function AddProductToShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_ADD); - - try - { - $requestBody = $request->getParsedBody(); - - $listId = 1; - $amount = 1; - $productId = null; - $note = null; - if (array_key_exists('list_id', $requestBody) && !empty($requestBody['list_id']) && is_numeric($requestBody['list_id'])) - { - $listId = intval($requestBody['list_id']); - } - if (array_key_exists('product_amount', $requestBody) && !empty($requestBody['product_amount']) && is_numeric($requestBody['product_amount'])) - { - $amount = intval($requestBody['product_amount']); - } - if (array_key_exists('product_id', $requestBody) && !empty($requestBody['product_id']) && is_numeric($requestBody['product_id'])) - { - $productId = intval($requestBody['product_id']); - } - if (array_key_exists('note', $requestBody) && !empty($requestBody['note'])) - { - $note = $requestBody['note']; - } - - if ($productId == null) - { - throw new \Exception("No product id was supplied"); - } - - $this->getStockService()->AddProductToShoppingList($productId, $amount, $note, $listId); - return $this->EmptyApiResponse($response); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } - } - - public function RemoveProductFromShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - User::checkPermission($request, User::PERMISSION_SHOPPINGLIST_ITEMS_DELETE); - - try - { - $requestBody = $request->getParsedBody(); - - $listId = 1; - $amount = 1; - $productId = null; - if (array_key_exists('list_id', $requestBody) && !empty($requestBody['list_id']) && is_numeric($requestBody['list_id'])) - { - $listId = intval($requestBody['list_id']); - } - if (array_key_exists('product_amount', $requestBody) && !empty($requestBody['product_amount']) && is_numeric($requestBody['product_amount'])) - { - $amount = intval($requestBody['product_amount']); - } - if (array_key_exists('product_id', $requestBody) && !empty($requestBody['product_id']) && is_numeric($requestBody['product_id'])) - { - $productId = intval($requestBody['product_id']); - } - - if ($productId == null) - { - throw new \Exception("No product id was supplied"); - } - - $this->getStockService()->RemoveProductFromShoppingList($productId, $amount, $listId); - return $this->EmptyApiResponse($response); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } - } - - public function ExternalBarcodeLookup(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - User::checkPermission($request, User::PERMISSION_MASTER_DATA_EDIT); - - try - { - $addFoundProduct = false; - if (isset($request->getQueryParams()['add']) && ($request->getQueryParams()['add'] === 'true' || $request->getQueryParams()['add'] === 1)) - { - $addFoundProduct = true; - } - - return $this->ApiResponse($response, $this->getStockService()->ExternalBarcodeLookup($args['barcode'], $addFoundProduct)); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } } public function UndoBooking(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -611,6 +719,7 @@ class StockApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } public function UndoTransaction(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -626,64 +735,12 @@ class StockApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } - public function ProductStockEntries(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + public function __construct(\DI\Container $container) { - $allowSubproductSubstitution = false; - if (isset($request->getQueryParams()['include_sub_products']) && filter_var($request->getQueryParams()['include_sub_products'], FILTER_VALIDATE_BOOLEAN)) - { - $allowSubproductSubstitution = true; - } - - return $this->ApiResponse($response, $this->getStockService()->GetProductStockEntries($args['productId'], false, $allowSubproductSubstitution)); + parent::__construct($container); } - public function ProductStockLocations(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->ApiResponse($response, $this->getStockService()->GetProductStockLocations($args['productId'])); - } - - public function StockEntry(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->ApiResponse($response, $this->getStockService()->GetStockEntry($args['entryId'])); - } - - public function StockBooking(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - try - { - $stockLogRow = $this->getDatabase()->stock_log($args['bookingId']); - - if ($stockLogRow === null) - { - throw new \Exception('Stock booking does not exist'); - } - - return $this->ApiResponse($response, $stockLogRow); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } - } - - public function StockTransactions(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - try - { - $transactionRows = $this->getDatabase()->stock_log()->where('transaction_id = :1', $args['transactionId'])->fetchAll(); - - if (count($transactionRows) === 0) - { - throw new \Exception('No transaction was found by the given transaction id'); - } - - return $this->ApiResponse($response, $transactionRows); - } - catch (\Exception $ex) - { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } - } } diff --git a/controllers/StockController.php b/controllers/StockController.php index a6327c62..32765a05 100644 --- a/controllers/StockController.php +++ b/controllers/StockController.php @@ -4,59 +4,6 @@ namespace Grocy\Controllers; class StockController extends BaseController { - - public function __construct(\DI\Container $container) - { - parent::__construct($container); - } - - public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - $usersService = $this->getUsersService(); - $nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['stock_expring_soon_days']; - - return $this->renderPage($response, 'stockoverview', [ - 'currentStock' => $this->getStockService()->GetCurrentStockOverview(), - 'locations' => $this->getDatabase()->locations()->orderBy('name'), - 'currentStockLocations' => $this->getStockService()->GetCurrentStockLocations(), - 'nextXDays' => $nextXDays, - 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'), - 'userfields' => $this->getUserfieldsService()->GetFields('products'), - 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('products'), - ]); - } - - public function Stockentries(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - $usersService = $this->getUsersService(); - $nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['stock_expring_soon_days']; - - return $this->renderPage($response, 'stockentries', [ - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'locations' => $this->getDatabase()->locations()->orderBy('name'), - 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), - 'stockEntries' => $this->getDatabase()->stock()->orderBy('product_id'), - 'currentStockLocations' => $this->getStockService()->GetCurrentStockLocations(), - 'nextXDays' => $nextXDays, - 'userfields' => $this->getUserfieldsService()->GetFields('products'), - 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('products') - ]); - } - - public function Purchase(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - $sql = 'select group_concat(barcode) barcodes, product_id from product_barcodes group by product_id'; - $productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); - - return $this->renderPage($response, 'purchase', [ - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), - 'barcodes' => $productBarcodes, - 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), - 'locations' => $this->getDatabase()->locations()->orderBy('name') - ]); - } - public function Consume(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { $sql = 'select group_concat(barcode) barcodes, product_id from product_barcodes group by product_id'; @@ -64,20 +11,7 @@ class StockController extends BaseController return $this->renderPage($response, 'consume', [ 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), - 'barcodes' => $productBarcodes, - 'recipes' => $this->getDatabase()->recipes()->orderBy('name'), - 'locations' => $this->getDatabase()->locations()->orderBy('name') - ]); - } - - public function Transfer(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - $sql = 'select group_concat(barcode) barcodes, product_id from product_barcodes group by product_id'; - $productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); - - return $this->renderPage($response, 'transfer', [ - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), - 'barcodes' => $productBarcodes, + 'barcodes' => $productBarcodes, 'recipes' => $this->getDatabase()->recipes()->orderBy('name'), 'locations' => $this->getDatabase()->locations()->orderBy('name') ]); @@ -90,264 +24,12 @@ class StockController extends BaseController return $this->renderPage($response, 'inventory', [ 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), - 'barcodes' => $productBarcodes, + 'barcodes' => $productBarcodes, 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), 'locations' => $this->getDatabase()->locations()->orderBy('name') ]); } - public function StockEntryEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->renderPage($response, 'stockentryform', [ - 'stockEntry' => $this->getDatabase()->stock()->where('id', $args['entryId'])->fetch(), - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), - 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), - 'locations' => $this->getDatabase()->locations()->orderBy('name') - ]); - } - - public function ShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - $listId = 1; - if (isset($request->getQueryParams()['list'])) - { - $listId = $request->getQueryParams()['list']; - } - - return $this->renderPage($response, 'shoppinglist', [ - 'listItems' => $this->getDatabase()->shopping_list()->where('shopping_list_id = :1', $listId), - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'missingProducts' => $this->getStockService()->GetMissingProducts(), - 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'), - 'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name'), - 'selectedShoppingListId' => $listId, - 'userfields' => $this->getUserfieldsService()->GetFields('products'), - 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('products') - ]); - } - - public function ProductsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->renderPage($response, 'products', [ - 'products' => $this->getDatabase()->products()->orderBy('name'), - 'locations' => $this->getDatabase()->locations()->orderBy('name'), - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'), - 'userfields' => $this->getUserfieldsService()->GetFields('products'), - 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('products') - ]); - } - - public function StockSettings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->renderPage($response, 'stocksettings', [ - 'locations' => $this->getDatabase()->locations()->orderBy('name'), - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name') - ]); - } - - public function LocationsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->renderPage($response, 'locations', [ - 'locations' => $this->getDatabase()->locations()->orderBy('name'), - 'userfields' => $this->getUserfieldsService()->GetFields('locations'), - 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('locations') - ]); - } - - public function ShoppingLocationsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->renderPage($response, 'shoppinglocations', [ - 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), - 'userfields' => $this->getUserfieldsService()->GetFields('shopping_locations'), - 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('shopping_locations') - ]); - } - - public function ProductGroupsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->renderPage($response, 'productgroups', [ - 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'), - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), - 'userfields' => $this->getUserfieldsService()->GetFields('product_groups'), - 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('product_groups') - ]); - } - - public function QuantityUnitsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->renderPage($response, 'quantityunits', [ - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'userfields' => $this->getUserfieldsService()->GetFields('quantity_units'), - 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('quantity_units') - ]); - } - - public function ProductEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - if ($args['productId'] == 'new') - { - return $this->renderPage($response, 'productform', [ - 'locations' => $this->getDatabase()->locations()->orderBy('name'), - 'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'), - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), - 'productgroups' => $this->getDatabase()->product_groups()->orderBy('name'), - 'userfields' => $this->getUserfieldsService()->GetFields('products'), - 'products' => $this->getDatabase()->products()->where('parent_product_id IS NULL and active = 1')->orderBy('name'), - 'isSubProductOfOthers' => false, - 'mode' => 'create' - ]); - } - else - { - $product = $this->getDatabase()->products($args['productId']); - - return $this->renderPage($response, 'productform', [ - 'product' => $product, - 'locations' => $this->getDatabase()->locations()->orderBy('name'), - 'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'), - 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), - 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), - 'productgroups' => $this->getDatabase()->product_groups()->orderBy('name'), - 'userfields' => $this->getUserfieldsService()->GetFields('products'), - 'products' => $this->getDatabase()->products()->where('id != :1 AND parent_product_id IS NULL and active = 1', $product->id)->orderBy('name'), - 'isSubProductOfOthers' => $this->getDatabase()->products()->where('parent_product_id = :1', $product->id)->count() !== 0, - 'mode' => 'edit', - 'quConversions' => $this->getDatabase()->quantity_unit_conversions() - ]); - } - } - - public function LocationEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - if ($args['locationId'] == 'new') - { - return $this->renderPage($response, 'locationform', [ - 'mode' => 'create', - 'userfields' => $this->getUserfieldsService()->GetFields('locations') - ]); - } - else - { - return $this->renderPage($response, 'locationform', [ - 'location' => $this->getDatabase()->locations($args['locationId']), - 'mode' => 'edit', - 'userfields' => $this->getUserfieldsService()->GetFields('locations') - ]); - } - } - - public function ShoppingLocationEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - if ($args['shoppingLocationId'] == 'new') - { - return $this->renderPage($response, 'shoppinglocationform', [ - 'mode' => 'create', - 'userfields' => $this->getUserfieldsService()->GetFields('shopping_locations') - ]); - } - else - { - return $this->renderPage($response, 'shoppinglocationform', [ - 'shoppinglocation' => $this->getDatabase()->shopping_locations($args['shoppingLocationId']), - 'mode' => 'edit', - 'userfields' => $this->getUserfieldsService()->GetFields('shopping_locations') - ]); - } - } - - public function ProductGroupEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - if ($args['productGroupId'] == 'new') - { - return $this->renderPage($response, 'productgroupform', [ - 'mode' => 'create', - 'userfields' => $this->getUserfieldsService()->GetFields('product_groups') - ]); - } - else - { - return $this->renderPage($response, 'productgroupform', [ - 'group' => $this->getDatabase()->product_groups($args['productGroupId']), - 'mode' => 'edit', - 'userfields' => $this->getUserfieldsService()->GetFields('product_groups') - ]); - } - } - - public function QuantityUnitEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - if ($args['quantityunitId'] == 'new') - { - return $this->renderPage($response, 'quantityunitform', [ - 'mode' => 'create', - 'userfields' => $this->getUserfieldsService()->GetFields('quantity_units'), - 'pluralCount' => $this->getLocalizationService()->GetPluralCount(), - 'pluralRule' => $this->getLocalizationService()->GetPluralDefinition() - ]); - } - else - { - $quantityUnit = $this->getDatabase()->quantity_units($args['quantityunitId']); - - return $this->renderPage($response, 'quantityunitform', [ - 'quantityUnit' => $quantityUnit, - 'mode' => 'edit', - 'userfields' => $this->getUserfieldsService()->GetFields('quantity_units'), - 'pluralCount' => $this->getLocalizationService()->GetPluralCount(), - 'pluralRule' => $this->getLocalizationService()->GetPluralDefinition(), - 'defaultQuConversions' => $this->getDatabase()->quantity_unit_conversions()->where('from_qu_id = :1 AND product_id IS NULL', $quantityUnit->id), - 'quantityUnits' => $this->getDatabase()->quantity_units() - ]); - } - } - - public function ShoppingListItemEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - if ($args['itemId'] == 'new') - { - return $this->renderPage($response, 'shoppinglistitemform', [ - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), - 'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name'), - 'mode' => 'create' - ]); - } - else - { - return $this->renderPage($response, 'shoppinglistitemform', [ - 'listItem' => $this->getDatabase()->shopping_list($args['itemId']), - 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), - 'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name'), - 'mode' => 'edit' - ]); - } - } - - public function ShoppingListEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - if ($args['listId'] == 'new') - { - return $this->renderPage($response, 'shoppinglistform', [ - 'mode' => 'create' - ]); - } - else - { - return $this->renderPage($response, 'shoppinglistform', [ - 'shoppingList' => $this->getDatabase()->shopping_lists($args['listId']), - 'mode' => 'edit' - ]); - } - } - - public function ShoppingListSettings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->renderPage($response, 'shoppinglistsettings'); - } - public function Journal(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'stockjournal', [ @@ -368,9 +50,55 @@ class StockController extends BaseController ]); } + public function LocationEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + if ($args['locationId'] == 'new') + { + return $this->renderPage($response, 'locationform', [ + 'mode' => 'create', + 'userfields' => $this->getUserfieldsService()->GetFields('locations') + ]); + } + else + { + return $this->renderPage($response, 'locationform', [ + 'location' => $this->getDatabase()->locations($args['locationId']), + 'mode' => 'edit', + 'userfields' => $this->getUserfieldsService()->GetFields('locations') + ]); + } + + } + + public function LocationsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->renderPage($response, 'locations', [ + 'locations' => $this->getDatabase()->locations()->orderBy('name'), + 'userfields' => $this->getUserfieldsService()->GetFields('locations'), + 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('locations') + ]); + } + + public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + $usersService = $this->getUsersService(); + $nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['stock_expring_soon_days']; + + return $this->renderPage($response, 'stockoverview', [ + 'currentStock' => $this->getStockService()->GetCurrentStockOverview(), + 'locations' => $this->getDatabase()->locations()->orderBy('name'), + 'currentStockLocations' => $this->getStockService()->GetCurrentStockLocations(), + 'nextXDays' => $nextXDays, + 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'), + 'userfields' => $this->getUserfieldsService()->GetFields('products'), + 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('products') + ]); + } + public function ProductBarcodesEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { $product = null; + if (isset($request->getQueryParams()['product'])) { $product = $this->getDatabase()->products($request->getQueryParams()['product']); @@ -394,17 +122,112 @@ class StockController extends BaseController 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name') ]); } + + } + + public function ProductEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + if ($args['productId'] == 'new') + { + return $this->renderPage($response, 'productform', [ + 'locations' => $this->getDatabase()->locations()->orderBy('name'), + 'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'), + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), + 'productgroups' => $this->getDatabase()->product_groups()->orderBy('name'), + 'userfields' => $this->getUserfieldsService()->GetFields('products'), + 'products' => $this->getDatabase()->products()->where('parent_product_id IS NULL and active = 1')->orderBy('name'), + 'isSubProductOfOthers' => false, + 'mode' => 'create' + ]); + } + else + { + $product = $this->getDatabase()->products($args['productId']); + + return $this->renderPage($response, 'productform', [ + 'product' => $product, + 'locations' => $this->getDatabase()->locations()->orderBy('name'), + 'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'), + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), + 'productgroups' => $this->getDatabase()->product_groups()->orderBy('name'), + 'userfields' => $this->getUserfieldsService()->GetFields('products'), + 'products' => $this->getDatabase()->products()->where('id != :1 AND parent_product_id IS NULL and active = 1', $product->id)->orderBy('name'), + 'isSubProductOfOthers' => $this->getDatabase()->products()->where('parent_product_id = :1', $product->id)->count() !== 0, + 'mode' => 'edit', + 'quConversions' => $this->getDatabase()->quantity_unit_conversions() + ]); + } + + } + + public function ProductGroupEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + if ($args['productGroupId'] == 'new') + { + return $this->renderPage($response, 'productgroupform', [ + 'mode' => 'create', + 'userfields' => $this->getUserfieldsService()->GetFields('product_groups') + ]); + } + else + { + return $this->renderPage($response, 'productgroupform', [ + 'group' => $this->getDatabase()->product_groups($args['productGroupId']), + 'mode' => 'edit', + 'userfields' => $this->getUserfieldsService()->GetFields('product_groups') + ]); + } + + } + + public function ProductGroupsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->renderPage($response, 'productgroups', [ + 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'), + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), + 'userfields' => $this->getUserfieldsService()->GetFields('product_groups'), + 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('product_groups') + ]); + } + + public function ProductsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->renderPage($response, 'products', [ + 'products' => $this->getDatabase()->products()->orderBy('name'), + 'locations' => $this->getDatabase()->locations()->orderBy('name'), + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'), + 'userfields' => $this->getUserfieldsService()->GetFields('products'), + 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('products') + ]); + } + + public function Purchase(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + $sql = 'select group_concat(barcode) barcodes, product_id from product_barcodes group by product_id'; + $productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); + + return $this->renderPage($response, 'purchase', [ + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), + 'barcodes' => $productBarcodes, + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), + 'locations' => $this->getDatabase()->locations()->orderBy('name') + ]); } public function QuantityUnitConversionEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { $product = null; + if (isset($request->getQueryParams()['product'])) { $product = $this->getDatabase()->products($request->getQueryParams()['product']); } $defaultQuUnit = null; + if (isset($request->getQueryParams()['qu-unit'])) { $defaultQuUnit = $this->getDatabase()->quantity_units($request->getQueryParams()['qu-unit']); @@ -423,7 +246,7 @@ class StockController extends BaseController else { return $this->renderPage($response, 'quantityunitconversionform', [ - 'quConversion' => $this->getDatabase()->quantity_unit_conversions($args['quConversionId']), + 'quConversion' => $this->getDatabase()->quantity_unit_conversions($args['quConversionId']), 'mode' => 'edit', 'userfields' => $this->getUserfieldsService()->GetFields('quantity_unit_conversions'), 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), @@ -431,6 +254,35 @@ class StockController extends BaseController 'defaultQuUnit' => $defaultQuUnit ]); } + + } + + public function QuantityUnitEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + if ($args['quantityunitId'] == 'new') + { + return $this->renderPage($response, 'quantityunitform', [ + 'mode' => 'create', + 'userfields' => $this->getUserfieldsService()->GetFields('quantity_units'), + 'pluralCount' => $this->getLocalizationService()->GetPluralCount(), + 'pluralRule' => $this->getLocalizationService()->GetPluralDefinition() + ]); + } + else + { + $quantityUnit = $this->getDatabase()->quantity_units($args['quantityunitId']); + + return $this->renderPage($response, 'quantityunitform', [ + 'quantityUnit' => $quantityUnit, + 'mode' => 'edit', + 'userfields' => $this->getUserfieldsService()->GetFields('quantity_units'), + 'pluralCount' => $this->getLocalizationService()->GetPluralCount(), + 'pluralRule' => $this->getLocalizationService()->GetPluralDefinition(), + 'defaultQuConversions' => $this->getDatabase()->quantity_unit_conversions()->where('from_qu_id = :1 AND product_id IS NULL', $quantityUnit->id), + 'quantityUnits' => $this->getDatabase()->quantity_units() + ]); + } + } public function QuantityUnitPluralFormTesting(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -439,4 +291,165 @@ class StockController extends BaseController 'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name') ]); } + + public function QuantityUnitsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->renderPage($response, 'quantityunits', [ + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'userfields' => $this->getUserfieldsService()->GetFields('quantity_units'), + 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('quantity_units') + ]); + } + + public function ShoppingList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + $listId = 1; + + if (isset($request->getQueryParams()['list'])) + { + $listId = $request->getQueryParams()['list']; + } + + return $this->renderPage($response, 'shoppinglist', [ + 'listItems' => $this->getDatabase()->shopping_list()->where('shopping_list_id = :1', $listId), + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'missingProducts' => $this->getStockService()->GetMissingProducts(), + 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name'), + 'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name'), + 'selectedShoppingListId' => $listId, + 'userfields' => $this->getUserfieldsService()->GetFields('products'), + 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('products') + ]); + } + + public function ShoppingListEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + if ($args['listId'] == 'new') + { + return $this->renderPage($response, 'shoppinglistform', [ + 'mode' => 'create' + ]); + } + else + { + return $this->renderPage($response, 'shoppinglistform', [ + 'shoppingList' => $this->getDatabase()->shopping_lists($args['listId']), + 'mode' => 'edit' + ]); + } + + } + + public function ShoppingListItemEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + if ($args['itemId'] == 'new') + { + return $this->renderPage($response, 'shoppinglistitemform', [ + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), + 'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name'), + 'mode' => 'create' + ]); + } + else + { + return $this->renderPage($response, 'shoppinglistitemform', [ + 'listItem' => $this->getDatabase()->shopping_list($args['itemId']), + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), + 'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name'), + 'mode' => 'edit' + ]); + } + + } + + public function ShoppingListSettings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->renderPage($response, 'shoppinglistsettings'); + } + + public function ShoppingLocationEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + if ($args['shoppingLocationId'] == 'new') + { + return $this->renderPage($response, 'shoppinglocationform', [ + 'mode' => 'create', + 'userfields' => $this->getUserfieldsService()->GetFields('shopping_locations') + ]); + } + else + { + return $this->renderPage($response, 'shoppinglocationform', [ + 'shoppinglocation' => $this->getDatabase()->shopping_locations($args['shoppingLocationId']), + 'mode' => 'edit', + 'userfields' => $this->getUserfieldsService()->GetFields('shopping_locations') + ]); + } + + } + + public function ShoppingLocationsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->renderPage($response, 'shoppinglocations', [ + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), + 'userfields' => $this->getUserfieldsService()->GetFields('shopping_locations'), + 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('shopping_locations') + ]); + } + + public function StockEntryEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->renderPage($response, 'stockentryform', [ + 'stockEntry' => $this->getDatabase()->stock()->where('id', $args['entryId'])->fetch(), + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), + 'locations' => $this->getDatabase()->locations()->orderBy('name') + ]); + } + + public function StockSettings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->renderPage($response, 'stocksettings', [ + 'locations' => $this->getDatabase()->locations()->orderBy('name'), + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'productGroups' => $this->getDatabase()->product_groups()->orderBy('name') + ]); + } + + public function Stockentries(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + $usersService = $this->getUsersService(); + $nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['stock_expring_soon_days']; + + return $this->renderPage($response, 'stockentries', [ + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), + 'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'), + 'locations' => $this->getDatabase()->locations()->orderBy('name'), + 'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'), + 'stockEntries' => $this->getDatabase()->stock()->orderBy('product_id'), + 'currentStockLocations' => $this->getStockService()->GetCurrentStockLocations(), + 'nextXDays' => $nextXDays, + 'userfields' => $this->getUserfieldsService()->GetFields('products'), + 'userfieldValues' => $this->getUserfieldsService()->GetAllValues('products') + ]); + } + + public function Transfer(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + $sql = 'select group_concat(barcode) barcodes, product_id from product_barcodes group by product_id'; + $productBarcodes = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); + + return $this->renderPage($response, 'transfer', [ + 'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name'), + 'barcodes' => $productBarcodes, + 'recipes' => $this->getDatabase()->recipes()->orderBy('name'), + 'locations' => $this->getDatabase()->locations()->orderBy('name') + ]); + } + + public function __construct(\DI\Container $container) + { + parent::__construct($container); + } + } diff --git a/controllers/SystemApiController.php b/controllers/SystemApiController.php index 50cc2160..2131b8e3 100644 --- a/controllers/SystemApiController.php +++ b/controllers/SystemApiController.php @@ -4,18 +4,6 @@ namespace Grocy\Controllers; class SystemApiController extends BaseApiController { - public function __construct(\DI\Container $container) - { - parent::__construct($container); - } - - public function GetDbChangedTime(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->ApiResponse($response, array( - 'changed_time' => $this->getDatabaseService()->GetDbChangedTime() - )); - } - public function GetConfig(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { try @@ -28,13 +16,15 @@ class SystemApiController extends BaseApiController unset($constants['GROCY_IS_EMBEDDED_INSTALL']); unset($constants['GROCY_USER_ID']); - $returnArray = array(); + $returnArray = []; + foreach ($constants as $constant => $value) { if (substr($constant, 0, 6) === 'GROCY_') { $returnArray[substr($constant, 6)] = $value; } + } return $this->ApiResponse($response, $returnArray); @@ -43,6 +33,19 @@ class SystemApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + + } + + public function GetDbChangedTime(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->ApiResponse($response, [ + 'changed_time' => $this->getDatabaseService()->GetDbChangedTime() + ]); + } + + public function GetSystemInfo(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->ApiResponse($response, $this->getApplicationService()->GetSystemInfo()); } public function LogMissingLocalization(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -60,11 +63,14 @@ class SystemApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } + } - public function GetSystemInfo(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + public function __construct(\DI\Container $container) { - return $this->ApiResponse($response, $this->getApplicationService()->GetSystemInfo()); + parent::__construct($container); } + } diff --git a/controllers/SystemController.php b/controllers/SystemController.php index 0b5a5a22..a186a7de 100644 --- a/controllers/SystemController.php +++ b/controllers/SystemController.php @@ -2,15 +2,22 @@ namespace Grocy\Controllers; -use \Grocy\Services\DatabaseMigrationService; -use \Grocy\Services\DemoDataGeneratorService; +use Grocy\Services\DatabaseMigrationService; +use Grocy\Services\DemoDataGeneratorService; class SystemController extends BaseController { - - public function __construct(\DI\Container $container) + public function About(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - parent::__construct($container); + return $this->renderPage($response, 'about', [ + 'system_info' => $this->getApplicationService()->GetSystemInfo(), + 'changelog' => $this->getApplicationService()->GetChangelog() + ]); + } + + public function BarcodeScannerTesting(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + return $this->renderPage($response, 'barcodescannertesting'); } public function Root(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -28,6 +35,11 @@ class SystemController extends BaseController return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl($this->GetEntryPageRelative())); } + public function __construct(\DI\Container $container) + { + parent::__construct($container); + } + /** * Get the entry page of the application based on the value of the entry page setting. * @@ -38,69 +50,69 @@ class SystemController extends BaseController */ private function GetEntryPageRelative() { - if (defined('GROCY_ENTRY_PAGE')) { + if (defined('GROCY_ENTRY_PAGE')) + { $entryPage = constant('GROCY_ENTRY_PAGE'); - } else { + } + else + { $entryPage = 'stock'; } - // Stock - if ($entryPage === 'stock' && constant('GROCY_FEATURE_FLAG_STOCK')) { +// Stock + if ($entryPage === 'stock' && constant('GROCY_FEATURE_FLAG_STOCK')) + { return '/stockoverview'; } - // Shoppinglist - if ($entryPage === 'shoppinglist' && constant('GROCY_FEATURE_FLAG_SHOPPINGLIST')) { +// Shoppinglist + if ($entryPage === 'shoppinglist' && constant('GROCY_FEATURE_FLAG_SHOPPINGLIST')) + { return '/shoppinglist'; } - // Recipes - if ($entryPage === 'recipes' && constant('GROCY_FEATURE_FLAG_RECIPES')) { +// Recipes + if ($entryPage === 'recipes' && constant('GROCY_FEATURE_FLAG_RECIPES')) + { return '/recipes'; } - // Chores - if ($entryPage === 'chores' && constant('GROCY_FEATURE_FLAG_CHORES')) { +// Chores + if ($entryPage === 'chores' && constant('GROCY_FEATURE_FLAG_CHORES')) + { return '/choresoverview'; } - // Tasks - if ($entryPage === 'tasks' && constant('GROCY_FEATURE_FLAG_TASKS')) { +// Tasks + if ($entryPage === 'tasks' && constant('GROCY_FEATURE_FLAG_TASKS')) + { return '/tasks'; } - // Batteries - if ($entryPage === 'batteries' && constant('GROCY_FEATURE_FLAG_BATTERIES')) { +// Batteries + if ($entryPage === 'batteries' && constant('GROCY_FEATURE_FLAG_BATTERIES')) + { return '/batteriesoverview'; } - if ($entryPage === 'equipment' && constant('GROCY_FEATURE_FLAG_EQUIPMENT')) { + if ($entryPage === 'equipment' && constant('GROCY_FEATURE_FLAG_EQUIPMENT')) + { return '/equipment'; } - // Calendar - if ($entryPage === 'calendar' && constant('GROCY_FEATURE_FLAG_CALENDAR')) { +// Calendar + if ($entryPage === 'calendar' && constant('GROCY_FEATURE_FLAG_CALENDAR')) + { return '/calendar'; } - // Meal Plan - if ($entryPage === 'mealplan' && constant('GROCY_FEATURE_FLAG_RECIPES')) { +// Meal Plan + if ($entryPage === 'mealplan' && constant('GROCY_FEATURE_FLAG_RECIPES')) + { return '/mealplan'; } return '/about'; } - public function About(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->renderPage($response, 'about', [ - 'system_info' => $this->getApplicationService()->GetSystemInfo(), - 'changelog' => $this->getApplicationService()->GetChangelog() - ]); - } - - public function BarcodeScannerTesting(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - return $this->renderPage($response, 'barcodescannertesting'); - } } diff --git a/controllers/TasksApiController.php b/controllers/TasksApiController.php index b1c0f4b8..6d0d6f39 100644 --- a/controllers/TasksApiController.php +++ b/controllers/TasksApiController.php @@ -6,11 +6,6 @@ use Grocy\Controllers\Users\User; class TasksApiController extends BaseApiController { - public function __construct(\DI\Container $container) - { - parent::__construct($container); - } - public function Current(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->ApiResponse($response, $this->getTasksService()->GetCurrent()); @@ -25,6 +20,7 @@ class TasksApiController extends BaseApiController try { $doneTime = date('Y-m-d H:i:s'); + if (array_key_exists('done_time', $requestBody) && IsIsoDateTime($requestBody['done_time'])) { $doneTime = $requestBody['done_time']; @@ -37,6 +33,7 @@ class TasksApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } public function UndoTask(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -52,5 +49,12 @@ class TasksApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } + + public function __construct(\DI\Container $container) + { + parent::__construct($container); + } + } diff --git a/controllers/TasksController.php b/controllers/TasksController.php index ab57c547..c66200a2 100644 --- a/controllers/TasksController.php +++ b/controllers/TasksController.php @@ -4,11 +4,6 @@ namespace Grocy\Controllers; class TasksController extends BaseController { - public function __construct(\DI\Container $container) - { - parent::__construct($container); - } - public function Overview(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { if (isset($request->getQueryParams()['include_done'])) @@ -33,29 +28,6 @@ class TasksController extends BaseController ]); } - public function TaskEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - if ($args['taskId'] == 'new') - { - return $this->renderPage($response, 'taskform', [ - 'mode' => 'create', - 'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name'), - 'users' => $this->getDatabase()->users()->orderBy('username'), - 'userfields' => $this->getUserfieldsService()->GetFields('tasks') - ]); - } - else - { - return $this->renderPage($response, 'taskform', [ - 'task' => $this->getDatabase()->tasks($args['taskId']), - 'mode' => 'edit', - 'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name'), - 'users' => $this->getDatabase()->users()->orderBy('username'), - 'userfields' => $this->getUserfieldsService()->GetFields('tasks') - ]); - } - } - public function TaskCategoriesList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'taskcategories', [ @@ -77,15 +49,46 @@ class TasksController extends BaseController else { return $this->renderPage($response, 'taskcategoryform', [ - 'category' => $this->getDatabase()->task_categories($args['categoryId']), + 'category' => $this->getDatabase()->task_categories($args['categoryId']), 'mode' => 'edit', 'userfields' => $this->getUserfieldsService()->GetFields('task_categories') ]); } + + } + + public function TaskEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + if ($args['taskId'] == 'new') + { + return $this->renderPage($response, 'taskform', [ + 'mode' => 'create', + 'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name'), + 'users' => $this->getDatabase()->users()->orderBy('username'), + 'userfields' => $this->getUserfieldsService()->GetFields('tasks') + ]); + } + else + { + return $this->renderPage($response, 'taskform', [ + 'task' => $this->getDatabase()->tasks($args['taskId']), + 'mode' => 'edit', + 'taskCategories' => $this->getDatabase()->task_categories()->orderBy('name'), + 'users' => $this->getDatabase()->users()->orderBy('username'), + 'userfields' => $this->getUserfieldsService()->GetFields('tasks') + ]); + } + } public function TasksSettings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'taskssettings'); } + + public function __construct(\DI\Container $container) + { + parent::__construct($container); + } + } diff --git a/controllers/Users/User.php b/controllers/Users/User.php index a3ea8488..722b267e 100644 --- a/controllers/Users/User.php +++ b/controllers/Users/User.php @@ -9,76 +9,92 @@ class User { const PERMISSION_ADMIN = 'ADMIN'; - const PERMISSION_USERS = 'USERS'; - const PERMISSION_USERS_CREATE = 'USERS_CREATE'; - const PERMISSION_USERS_EDIT = 'USERS_EDIT'; - const PERMISSION_USERS_READ = 'USERS_READ'; - const PERMISSION_USERS_EDIT_SELF = 'USERS_EDIT_SELF'; - - const PERMISSION_STOCK = 'STOCK'; - const PERMISSION_STOCK_PURCHASE = 'STOCK_PURCHASE'; - const PERMISSION_STOCK_CONSUME = 'STOCK_CONSUME'; - const PERMISSION_STOCK_INVENTORY = 'STOCK_INVENTORY'; - const PERMISSION_STOCK_TRANSFER = 'STOCK_TRANSFER'; - const PERMISSION_STOCK_OPEN = 'STOCK_OPEN'; - const PERMISSION_STOCK_EDIT = 'STOCK_EDIT'; - - const PERMISSION_RECIPES = 'RECIPES'; - const PERMISSION_RECIPES_MEALPLAN = 'RECIPES_MEALPLAN'; - - const PERMISSION_SHOPPINGLIST = 'SHOPPINGLIST'; - const PERMISSION_SHOPPINGLIST_ITEMS_ADD = 'SHOPPINGLIST_ITEMS_ADD'; - const PERMISSION_SHOPPINGLIST_ITEMS_DELETE = 'SHOPPINGLIST_ITEMS_DELETE'; - - const PERMISSION_CHORES = 'CHORES'; - const PERMISSION_CHORE_TRACK_EXECUTION = 'CHORE_TRACK_EXECUTION'; - const PERMISSION_CHORE_UNDO_EXECUTION = 'CHORE_UNDO_EXECUTION'; - const PERMISSION_BATTERIES = 'BATTERIES'; + const PERMISSION_BATTERIES_TRACK_CHARGE_CYCLE = 'BATTERIES_TRACK_CHARGE_CYCLE'; + const PERMISSION_BATTERIES_UNDO_CHARGE_CYCLE = 'BATTERIES_UNDO_CHARGE_CYCLE'; - const PERMISSION_TASKS = 'TASKS'; - const PERMISSION_TASKS_UNDO_EXECUTION = 'TASKS_UNDO_EXECUTION'; - const PERMISSION_TASKS_MARK_COMPLETED = 'TASKS_MARK_COMPLETED'; - - const PERMISSION_EQUIPMENT = 'EQUIPMENT'; - const PERMISSION_CALENDAR = 'CALENDAR'; + const PERMISSION_CHORES = 'CHORES'; + + const PERMISSION_CHORE_TRACK_EXECUTION = 'CHORE_TRACK_EXECUTION'; + + const PERMISSION_CHORE_UNDO_EXECUTION = 'CHORE_UNDO_EXECUTION'; + + const PERMISSION_EQUIPMENT = 'EQUIPMENT'; + const PERMISSION_MASTER_DATA_EDIT = 'MASTER_DATA_EDIT'; + const PERMISSION_RECIPES = 'RECIPES'; + + const PERMISSION_RECIPES_MEALPLAN = 'RECIPES_MEALPLAN'; + + const PERMISSION_SHOPPINGLIST = 'SHOPPINGLIST'; + + const PERMISSION_SHOPPINGLIST_ITEMS_ADD = 'SHOPPINGLIST_ITEMS_ADD'; + + const PERMISSION_SHOPPINGLIST_ITEMS_DELETE = 'SHOPPINGLIST_ITEMS_DELETE'; + + const PERMISSION_STOCK = 'STOCK'; + + const PERMISSION_STOCK_CONSUME = 'STOCK_CONSUME'; + + const PERMISSION_STOCK_EDIT = 'STOCK_EDIT'; + + const PERMISSION_STOCK_INVENTORY = 'STOCK_INVENTORY'; + + const PERMISSION_STOCK_OPEN = 'STOCK_OPEN'; + + const PERMISSION_STOCK_PURCHASE = 'STOCK_PURCHASE'; + + const PERMISSION_STOCK_TRANSFER = 'STOCK_TRANSFER'; + + const PERMISSION_TASKS = 'TASKS'; + + const PERMISSION_TASKS_MARK_COMPLETED = 'TASKS_MARK_COMPLETED'; + + const PERMISSION_TASKS_UNDO_EXECUTION = 'TASKS_UNDO_EXECUTION'; + + const PERMISSION_USERS = 'USERS'; + + const PERMISSION_USERS_CREATE = 'USERS_CREATE'; + + const PERMISSION_USERS_EDIT = 'USERS_EDIT'; + + const PERMISSION_USERS_EDIT_SELF = 'USERS_EDIT_SELF'; + + const PERMISSION_USERS_READ = 'USERS_READ'; + /** * @var \LessQL\Database|null */ protected $db; + public static function PermissionList() + { + $user = new self(); + return $user->getPermissionList(); + } + public function __construct() { $this->db = DatabaseService::getInstance()->GetDbConnection(); } - protected function getPermissions(): Result + public static function checkPermission($request, string...$permissions): void { - return $this->db->user_permissions_resolved()->where('user_id', GROCY_USER_ID); - } + $user = new self(); - public function hasPermission(string $permission): bool - { - // global $PERMISSION_CACHE; - // if(isset($PERMISSION_CACHE[$permission])) - // return $PERMISSION_CACHE[$permission]; - return $this->getPermissions()->where('permission_name', $permission)->fetch() !== null; - } - - public static function checkPermission($request, string ...$permissions): void - { - $user = new User(); - foreach ($permissions as $permission) { - if (!$user->hasPermission($permission)) { + foreach ($permissions as $permission) + { + if (!$user->hasPermission($permission)) + { throw new PermissionMissingException($request, $permission); } + } } @@ -88,20 +104,34 @@ class User return $this->db->uihelper_user_permissions()->where('user_id', GROCY_USER_ID); } - public static function hasPermissions(string ...$permissions) + public function hasPermission(string $permission): bool { - $user = new User(); - foreach ($permissions as $permission) { - if (!$user->hasPermission($permission)) { +// global $PERMISSION_CACHE; + +// if(isset($PERMISSION_CACHE[$permission])) + // return $PERMISSION_CACHE[$permission]; + return $this->getPermissions()->where('permission_name', $permission)->fetch() !== null; + } + + public static function hasPermissions(string...$permissions) + { + $user = new self(); + + foreach ($permissions as $permission) + { + if (!$user->hasPermission($permission)) + { return false; } + } + return true; } - public static function PermissionList() + protected function getPermissions(): Result { - $user = new User(); - return $user->getPermissionList(); + return $this->db->user_permissions_resolved()->where('user_id', GROCY_USER_ID); } + } diff --git a/controllers/UsersApiController.php b/controllers/UsersApiController.php index fb284a33..c3837261 100644 --- a/controllers/UsersApiController.php +++ b/controllers/UsersApiController.php @@ -6,22 +6,27 @@ use Grocy\Controllers\Users\User; class UsersApiController extends BaseApiController { - public function __construct(\DI\Container $container) + public function AddPermission(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - parent::__construct($container); - } + try { + User::checkPermission($request, User::PERMISSION_ADMIN); + $requestBody = $request->getParsedBody(); - public function GetUsers(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - User::checkPermission($request, User::PERMISSION_USERS_READ); - try + $this->getDatabase()->user_permissions()->createRow([ + 'user_id' => $args['userId'], + 'permission_id' => $requestBody['permission_id'] + ])->save(); + return $this->EmptyApiResponse($response); + } + catch (\Slim\Exception\HttpSpecializedException $ex) { - return $this->ApiResponse($response, $this->getUsersService()->GetUsersAsDto()); + return $this->GenericErrorResponse($response, $ex->getMessage(), $ex->getCode()); } catch (\Exception $ex) { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } public function CreateUser(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -43,6 +48,7 @@ class UsersApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } public function DeleteUser(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -57,15 +63,20 @@ class UsersApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } public function EditUser(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - if ($args['userId'] == GROCY_USER_ID) { + if ($args['userId'] == GROCY_USER_ID) + { User::checkPermission($request, User::PERMISSION_USERS_EDIT_SELF); - } else { + } + else + { User::checkPermission($request, User::PERMISSION_USERS_EDIT); } + $requestBody = $request->getParsedBody(); try @@ -77,6 +88,21 @@ class UsersApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + + } + + public function GetUserSetting(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try + { + $value = $this->getUsersService()->GetUserSetting(GROCY_USER_ID, $args['settingKey']); + return $this->ApiResponse($response, ['value' => $value]); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + } public function GetUserSettings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -89,19 +115,76 @@ class UsersApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } - public function GetUserSetting(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + public function GetUsers(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { + User::checkPermission($request, User::PERMISSION_USERS_READ); try { - $value = $this->getUsersService()->GetUserSetting(GROCY_USER_ID, $args['settingKey']); - return $this->ApiResponse($response, array('value' => $value)); + return $this->ApiResponse($response, $this->getUsersService()->GetUsersAsDto()); } catch (\Exception $ex) { return $this->GenericErrorResponse($response, $ex->getMessage()); } + + } + + public function ListPermissions(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try { + User::checkPermission($request, User::PERMISSION_ADMIN); + + return $this->ApiResponse($response, + $this->getDatabase()->user_permissions()->where($args['userId']) + ); + } + catch (\Slim\Exception\HttpSpecializedException $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage(), $ex->getCode()); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + + } + + public function SetPermissions(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + try { + User::checkPermission($request, User::PERMISSION_ADMIN); + $requestBody = $request->getParsedBody(); + $db = $this->getDatabase(); + $db->user_permissions() + ->where('user_id', $args['userId']) + ->delete(); + + $perms = []; + + foreach ($requestBody['permissions'] as $perm_id) + { + $perms[] = [ + 'user_id' => $args['userId'], + 'permission_id' => $perm_id + ]; + } + + $db->insert('user_permissions', $perms, 'batch'); + + return $this->EmptyApiResponse($response); + } + catch (\Slim\Exception\HttpSpecializedException $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage(), $ex->getCode()); + } + catch (\Exception $ex) + { + return $this->GenericErrorResponse($response, $ex->getMessage()); + } + } public function SetUserSetting(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -117,67 +200,12 @@ class UsersApiController extends BaseApiController { return $this->GenericErrorResponse($response, $ex->getMessage()); } + } - public function AddPermission(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + public function __construct(\DI\Container $container) { - try { - User::checkPermission($request, User::PERMISSION_ADMIN); - $requestBody = $request->getParsedBody(); - - $this->getDatabase()->user_permissions()->createRow(array( - 'user_id' => $args['userId'], - 'permission_id' => $requestBody['permission_id'], - ))->save(); - return $this->EmptyApiResponse($response); - } catch (\Slim\Exception\HttpSpecializedException $ex) { - return $this->GenericErrorResponse($response, $ex->getMessage(), $ex->getCode()); - } catch (\Exception $ex) { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } + parent::__construct($container); } - public function ListPermissions(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - try { - User::checkPermission($request, User::PERMISSION_ADMIN); - - return $this->ApiResponse($response, - $this->getDatabase()->user_permissions()->where($args['userId']) - ); - } catch (\Slim\Exception\HttpSpecializedException $ex) { - return $this->GenericErrorResponse($response, $ex->getMessage(), $ex->getCode()); - } catch (\Exception $ex) { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } - } - - public function SetPermissions(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - try { - User::checkPermission($request, User::PERMISSION_ADMIN); - $requestBody = $request->getParsedBody(); - $db = $this->getDatabase(); - $db->user_permissions() - ->where('user_id', $args['userId']) - ->delete(); - - $perms = []; - - foreach ($requestBody['permissions'] as $perm_id) { - $perms[] = array( - 'user_id' => $args['userId'], - 'permission_id' => $perm_id - ); - } - - $db->insert('user_permissions', $perms, 'batch'); - - return $this->EmptyApiResponse($response); - } catch (\Slim\Exception\HttpSpecializedException $ex) { - return $this->GenericErrorResponse($response, $ex->getMessage(), $ex->getCode()); - } catch (\Exception $ex) { - return $this->GenericErrorResponse($response, $ex->getMessage()); - } - } } diff --git a/controllers/UsersController.php b/controllers/UsersController.php index de03bbd0..024a0f14 100644 --- a/controllers/UsersController.php +++ b/controllers/UsersController.php @@ -6,11 +6,13 @@ use Grocy\Controllers\Users\User; class UsersController extends BaseController { - public function UsersList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + public function PermissionList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { User::checkPermission($request, User::PERMISSION_USERS_READ); - return $this->renderPage($response, 'users', [ - 'users' => $this->getDatabase()->users()->orderBy('username') + return $this->renderPage($response, 'userpermissions', [ + 'user' => $this->getDatabase()->users($args['userId']), + 'permissions' => $this->getDatabase()->uihelper_user_permissions() + ->where('parent IS NULL')->where('user_id', $args['userId']) ]); } @@ -25,35 +27,45 @@ class UsersController extends BaseController } else { - if($args['userId'] == GROCY_USER_ID) + if ($args['userId'] == GROCY_USER_ID) + { User::checkPermission($request, User::PERMISSION_USERS_EDIT_SELF); - else User::checkPermission($request, User::PERMISSION_USERS_EDIT); + } + else + { + User::checkPermission($request, User::PERMISSION_USERS_EDIT); + } + return $this->renderPage($response, 'userform', [ - 'user' => $this->getDatabase()->users($args['userId']), + 'user' => $this->getDatabase()->users($args['userId']), 'mode' => 'edit' ]); } - } - public function PermissionList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) - { - User::checkPermission($request, User::PERMISSION_USERS_READ); - return $this->renderPage($response, 'userpermissions', [ - 'user' => $this->getDatabase()->users($args['userId']), - 'permissions' => $this->getDatabase()->uihelper_user_permissions() - ->where('parent IS NULL')->where('user_id', $args['userId']), - ]); } public function UserSettings(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { return $this->renderPage($response, 'usersettings', [ - 'languages' => array_filter(scandir(__DIR__.'/../localization'), function ($item){ - if($item == "." || $item == "..") + 'languages' => array_filter(scandir(__DIR__ . '/../localization'), function ($item) + { + if ($item == '.' || $item == '..') + { return false; - return is_dir(__DIR__.'/../localization/'.$item); + } + + return is_dir(__DIR__ . '/../localization/' . $item); }) ]); } + + public function UsersList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) + { + User::checkPermission($request, User::PERMISSION_USERS_READ); + return $this->renderPage($response, 'users', [ + 'users' => $this->getDatabase()->users()->orderBy('username') + ]); + } + } diff --git a/helpers/BaseBarcodeLookupPlugin.php b/helpers/BaseBarcodeLookupPlugin.php index a8a4101b..ef6c4394 100644 --- a/helpers/BaseBarcodeLookupPlugin.php +++ b/helpers/BaseBarcodeLookupPlugin.php @@ -4,16 +4,9 @@ namespace Grocy\Helpers; abstract class BaseBarcodeLookupPlugin { - final public function __construct($locations, $quantityUnits) - { - $this->Locations = $locations; - $this->QuantityUnits = $quantityUnits; - } - protected $Locations; - protected $QuantityUnits; - abstract protected function ExecuteLookup($barcode); + protected $QuantityUnits; final public function Lookup($barcode) { @@ -24,52 +17,62 @@ abstract class BaseBarcodeLookupPlugin return $pluginOutput; } - // Plugin must return an associative array +// Plugin must return an associative array if (!is_array($pluginOutput)) { throw new \Exception('Plugin output must be an associative array'); } + if (!IsAssociativeArray($pluginOutput)) // $pluginOutput is at least an indexed array here { throw new \Exception('Plugin output must be an associative array'); } // Check for minimum needed properties - $minimunNeededProperties = array( + $minimunNeededProperties = [ 'name', 'location_id', 'qu_id_purchase', 'qu_id_stock', 'qu_factor_purchase_to_stock', 'barcode' - ); + ]; + foreach ($minimunNeededProperties as $prop) { if (!array_key_exists($prop, $pluginOutput)) { throw new \Exception("Plugin output does not provide needed property $prop"); } + } - // $pluginOutput contains all needed properties here +// $pluginOutput contains all needed properties here // Check referenced entity ids are valid $locationId = $pluginOutput['location_id']; + if (FindObjectInArrayByPropertyValue($this->Locations, 'id', $locationId) === null) { throw new \Exception("Location $locationId is not a valid location id"); } + $quIdPurchase = $pluginOutput['qu_id_purchase']; + if (FindObjectInArrayByPropertyValue($this->QuantityUnits, 'id', $quIdPurchase) === null) { throw new \Exception("Location $quIdPurchase is not a valid quantity unit id"); } + $quIdStock = $pluginOutput['qu_id_stock']; + if (FindObjectInArrayByPropertyValue($this->QuantityUnits, 'id', $quIdStock) === null) { throw new \Exception("Location $quIdStock is not a valid quantity unit id"); } + $quFactor = $pluginOutput['qu_factor_purchase_to_stock']; + if (empty($quFactor) || !is_numeric($quFactor)) { throw new \Exception('Quantity unit factor is empty or not a number'); @@ -77,4 +80,12 @@ abstract class BaseBarcodeLookupPlugin return $pluginOutput; } + + final public function __construct($locations, $quantityUnits) + { + $this->Locations = $locations; + $this->QuantityUnits = $quantityUnits; + } + + abstract protected function ExecuteLookup($barcode); } diff --git a/helpers/PrerequisiteChecker.php b/helpers/PrerequisiteChecker.php index 16c01e24..513368e8 100644 --- a/helpers/PrerequisiteChecker.php +++ b/helpers/PrerequisiteChecker.php @@ -1,9 +1,11 @@ query('select sqlite_version()')->fetch()[0]; - } + private function getSqlVersionAsString() + { + $dbh = new PDO('sqlite::memory:'); + return $dbh->query('select sqlite_version()')->fetch()[0]; + } } diff --git a/helpers/UrlManager.php b/helpers/UrlManager.php index f3ef6d88..cb5c4d08 100644 --- a/helpers/UrlManager.php +++ b/helpers/UrlManager.php @@ -4,18 +4,6 @@ namespace Grocy\Helpers; class UrlManager { - public function __construct(string $basePath) - { - if ($basePath === '/') - { - $this->BasePath = $this->GetBaseUrl(); - } - else - { - $this->BasePath = $basePath; - } - } - protected $BasePath; public function ConstructUrl($relativePath, $isResource = false) @@ -28,6 +16,20 @@ class UrlManager { return rtrim($this->BasePath, '/') . '/index.php' . $relativePath; } + + } + + public function __construct(string $basePath) + { + if ($basePath === '/') + { + $this->BasePath = $this->GetBaseUrl(); + } + else + { + $this->BasePath = $basePath; + } + } private function GetBaseUrl() @@ -37,6 +39,7 @@ class UrlManager $_SERVER['HTTPS'] = 'on'; } - return (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]"; + return (isset($_SERVER['HTTPS']) ? 'https' : 'http') . "://$_SERVER[HTTP_HOST]"; } + } diff --git a/helpers/extensions.php b/helpers/extensions.php index 6cdcb203..9a280bc5 100644 --- a/helpers/extensions.php +++ b/helpers/extensions.php @@ -2,12 +2,14 @@ function FindObjectInArrayByPropertyValue($array, $propertyName, $propertyValue) { - foreach($array as $object) + foreach ($array as $object) { - if($object->{$propertyName} == $propertyValue) + if ($object->{$propertyName} + == $propertyValue) { return $object; } + } return null; @@ -15,31 +17,41 @@ function FindObjectInArrayByPropertyValue($array, $propertyName, $propertyValue) function FindAllObjectsInArrayByPropertyValue($array, $propertyName, $propertyValue, $operator = '==') { - $returnArray = array(); + $returnArray = []; - foreach($array as $object) + foreach ($array as $object) { - switch($operator) + switch ($operator) { case '==': - if($object->{$propertyName} == $propertyValue) + + if ($object-> {$propertyName} + == $propertyValue) { $returnArray[] = $object; } + break; case '>': - if($object->{$propertyName} > $propertyValue) + + if ($object-> {$propertyName} + > $propertyValue) { $returnArray[] = $object; } + break; case '<': - if($object->{$propertyName} < $propertyValue) + + if ($object-> {$propertyName} + < $propertyValue) { $returnArray[] = $object; } + break; } + } return $returnArray; @@ -47,31 +59,38 @@ function FindAllObjectsInArrayByPropertyValue($array, $propertyName, $propertyVa function FindAllItemsInArrayByValue($array, $value, $operator = '==') { - $returnArray = array(); + $returnArray = []; - foreach($array as $item) + foreach ($array as $item) { - switch($operator) + switch ($operator) { case '==': - if($item == $value) + + if ($item == $value) { $returnArray[] = $item; } + break; case '>': - if($item > $value) + + if ($item > $value) { $returnArray[] = $item; } + break; case '<': - if($item < $value) + + if ($item < $value) { $returnArray[] = $item; } + break; } + } return $returnArray; @@ -80,7 +99,8 @@ function FindAllItemsInArrayByValue($array, $value, $operator = '==') function SumArrayValue($array, $propertyName) { $sum = 0; - foreach($array as $object) + + foreach ($array as $object) { $sum += floatval($object->{$propertyName}); } @@ -102,11 +122,13 @@ function GetClassConstants($className, $prefix = null) $matchingKeys = preg_grep('!^' . $prefix . '!', array_keys($constants)); return array_intersect_key($constants, array_flip($matchingKeys)); } + } function RandomString($length, $allowedChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') { $randomString = ''; + for ($i = 0; $i < $length; $i++) { $randomString .= $allowedChars[rand(0, strlen($allowedChars) - 1)]; @@ -142,13 +164,16 @@ function ExternalSettingValue(string $value) { $tvalue = rtrim($value, "\r\n"); $lvalue = strtolower($tvalue); - if ($lvalue === "true"){ + + if ($lvalue === 'true') + { return true; } - elseif ($lvalue === "false") + elseif ($lvalue === 'false') { return false; } + return $tvalue; } @@ -158,30 +183,35 @@ function Setting(string $name, $value) { // The content of a $name.txt file in /data/settingoverrides can overwrite the given setting (for embedded mode) $settingOverrideFile = GROCY_DATAPATH . '/settingoverrides/' . $name . '.txt'; + if (file_exists($settingOverrideFile)) { define('GROCY_' . $name, ExternalSettingValue(file_get_contents($settingOverrideFile))); } elseif (getenv('GROCY_' . $name) !== false) // An environment variable with the same name and prefix GROCY_ overwrites the given setting { - define('GROCY_' . $name, ExternalSettingValue(getenv('GROCY_'. $name))); + define('GROCY_' . $name, ExternalSettingValue(getenv('GROCY_' . $name))); } else { define('GROCY_' . $name, $value); } + } + } global $GROCY_DEFAULT_USER_SETTINGS; -$GROCY_DEFAULT_USER_SETTINGS = array(); +$GROCY_DEFAULT_USER_SETTINGS = []; function DefaultUserSetting(string $name, $value) { global $GROCY_DEFAULT_USER_SETTINGS; + if (!array_key_exists($name, $GROCY_DEFAULT_USER_SETTINGS)) { $GROCY_DEFAULT_USER_SETTINGS[$name] = $value; } + } function GetUserDisplayName($user) @@ -210,7 +240,7 @@ function GetUserDisplayName($user) function IsValidFileName($fileName) { - if(preg_match('=^[^/?*;:{}\\\\]+\.[^/?*;:{}\\\\]+$=', $fileName)) + if (preg_match('=^[^/?*;:{}\\\\]+\.[^/?*;:{}\\\\]+$=', $fileName)) { return true; } @@ -232,6 +262,7 @@ function string_starts_with($haystack, $needle) function string_ends_with($haystack, $needle) { $length = strlen($needle); + if ($length == 0) { return true; diff --git a/middleware/ApiKeyAuthMiddleware.php b/middleware/ApiKeyAuthMiddleware.php index 42c29079..f5ce9398 100644 --- a/middleware/ApiKeyAuthMiddleware.php +++ b/middleware/ApiKeyAuthMiddleware.php @@ -2,22 +2,21 @@ namespace Grocy\Middleware; +use Grocy\Services\ApiKeyService; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ServerRequestInterface as Request; use Slim\Routing\RouteContext; -use Grocy\Services\ApiKeyService; - class ApiKeyAuthMiddleware extends AuthMiddleware { + protected $ApiKeyHeaderName; + public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory) { parent::__construct($container, $responseFactory); $this->ApiKeyHeaderName = $this->AppContainer->get('ApiKeyHeaderName'); } - protected $ApiKeyHeaderName; - function authenticate(Request $request) { if (!defined('GROCY_SHOW_AUTH_VIEWS')) @@ -34,7 +33,7 @@ class ApiKeyAuthMiddleware extends AuthMiddleware $apiKeyService = new ApiKeyService(); - // First check of the API key in the configured header +// First check of the API key in the configured header if (!$request->hasHeader($this->ApiKeyHeaderName) || !$apiKeyService->IsValidApiKey($request->getHeaderLine($this->ApiKeyHeaderName))) { $validApiKey = false; @@ -44,14 +43,14 @@ class ApiKeyAuthMiddleware extends AuthMiddleware $usedApiKey = $request->getHeaderLine($this->ApiKeyHeaderName); } - // Not recommended, but it's also possible to provide the API key via a query parameter (same name as the configured header) +// Not recommended, but it's also possible to provide the API key via a query parameter (same name as the configured header) if (!$validApiKey && !empty($request->getQueryParam($this->ApiKeyHeaderName)) && $apiKeyService->IsValidApiKey($request->getQueryParam($this->ApiKeyHeaderName))) { $validApiKey = true; $usedApiKey = $request->getQueryParam($this->ApiKeyHeaderName); } - // Handling of special purpose API keys +// Handling of special purpose API keys if (!$validApiKey) { if ($routeName === 'calendar-ical') @@ -60,7 +59,9 @@ class ApiKeyAuthMiddleware extends AuthMiddleware { $validApiKey = true; } + } + } if ($validApiKey) @@ -72,5 +73,7 @@ class ApiKeyAuthMiddleware extends AuthMiddleware { return null; } + } + } diff --git a/middleware/AuthMiddleware.php b/middleware/AuthMiddleware.php index 6d99dbbd..17f8e86d 100644 --- a/middleware/AuthMiddleware.php +++ b/middleware/AuthMiddleware.php @@ -2,24 +2,23 @@ namespace Grocy\Middleware; +use Grocy\Services\SessionService; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\RequestHandlerInterface as RequestHandler; use Slim\Routing\RouteContext; -use Grocy\Services\SessionService; - abstract class AuthMiddleware extends BaseMiddleware { + protected $ResponseFactory; + public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory) { parent::__construct($container); $this->ResponseFactory = $responseFactory; } - protected $ResponseFactory; - public function __invoke(Request $request, RequestHandler $handler): Response { $routeContext = RouteContext::fromRequest($request); @@ -31,11 +30,14 @@ abstract class AuthMiddleware extends BaseMiddleware { return $handler->handle($request); } - else if ($routeName === 'login') + else + + if ($routeName === 'login') { define('GROCY_AUTHENTICATED', false); return $handler->handle($request); } + if (GROCY_MODE === 'dev' || GROCY_MODE === 'demo' || GROCY_MODE === 'prerelease' || GROCY_IS_EMBEDDED_INSTALL || GROCY_DISABLE_AUTH) { $sessionService = SessionService::getInstance(); @@ -55,6 +57,7 @@ abstract class AuthMiddleware extends BaseMiddleware define('GROCY_AUTHENTICATED', false); $response = $this->ResponseFactory->createResponse(); + if ($isApiRoute) { return $response->withStatus(401); @@ -63,6 +66,7 @@ abstract class AuthMiddleware extends BaseMiddleware { return $response->withHeader('Location', $this->AppContainer->get('UrlManager')->ConstructUrl('/login')); } + } else { @@ -72,7 +76,9 @@ abstract class AuthMiddleware extends BaseMiddleware return $response = $handler->handle($request); } + } + } /** diff --git a/middleware/BaseMiddleware.php b/middleware/BaseMiddleware.php index 3b38a957..6c7bb58c 100644 --- a/middleware/BaseMiddleware.php +++ b/middleware/BaseMiddleware.php @@ -2,16 +2,17 @@ namespace Grocy\Middleware; -use \Grocy\Services\ApplicationService; +use Grocy\Services\ApplicationService; class BaseMiddleware { + protected $AppContainer; + + protected $ApplicationService; + public function __construct(\DI\Container $container) { $this->AppContainer = $container; $this->ApplicationService = ApplicationService::getInstance(); } - - protected $AppContainer; - protected $ApplicationService; } diff --git a/middleware/CorsMiddleware.php b/middleware/CorsMiddleware.php index f6d98690..dfa43205 100644 --- a/middleware/CorsMiddleware.php +++ b/middleware/CorsMiddleware.php @@ -3,10 +3,9 @@ namespace Grocy\Middleware; use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\RequestHandlerInterface as RequestHandler; -use Psr\Http\Message\ResponseInterface as Response; -use Slim\Routing\RouteContext; class CorsMiddleware { @@ -22,15 +21,21 @@ class CorsMiddleware public function __invoke(Request $request, RequestHandler $handler): Response { - if ($request->getMethod() == "OPTIONS") + if ($request->getMethod() == 'OPTIONS') + { $response = $this->responseFactory->createResponse(200); - else { + } + else + { $response = $handler->handle($request); } - //$routeContext = RouteContext::fromRequest($request); - //$routingResults = $routeContext->getRoutingResults(); - //$methods = $routingResults->getAllowedMethods(); + +//$routeContext = RouteContext::fromRequest($request); + +//$routingResults = $routeContext->getRoutingResults(); + +//$methods = $routingResults->getAllowedMethods(); //$requestHeaders = $request->getHeaderLine('Access-Control-Request-Headers'); $response = $response->withHeader('Access-Control-Allow-Origin', '*'); @@ -39,4 +44,5 @@ class CorsMiddleware return $response; } + } diff --git a/middleware/DefaultAuthMiddleware.php b/middleware/DefaultAuthMiddleware.php index d16b0d21..1115e77f 100644 --- a/middleware/DefaultAuthMiddleware.php +++ b/middleware/DefaultAuthMiddleware.php @@ -11,6 +11,7 @@ class DefaultAuthMiddleware extends AuthMiddleware // First try to authenticate by API key $auth = new ApiKeyAuthMiddleware($this->AppContainer, $this->ResponseFactory); $user = $auth->authenticate($request); + if ($user !== null) { return $user; @@ -21,4 +22,5 @@ class DefaultAuthMiddleware extends AuthMiddleware $user = $auth->authenticate($request); return $user; } + } diff --git a/middleware/JsonMiddleware.php b/middleware/JsonMiddleware.php index 92ac0e38..3814804b 100644 --- a/middleware/JsonMiddleware.php +++ b/middleware/JsonMiddleware.php @@ -2,9 +2,9 @@ namespace Grocy\Middleware; +use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Server\RequestHandlerInterface as RequestHandler; -use Psr\Http\Message\ResponseInterface as Response; class JsonMiddleware extends BaseMiddleware { @@ -25,5 +25,7 @@ class JsonMiddleware extends BaseMiddleware return $response; } + } + } diff --git a/middleware/LocaleMiddleware.php b/middleware/LocaleMiddleware.php index e431ce7c..1d706cd4 100644 --- a/middleware/LocaleMiddleware.php +++ b/middleware/LocaleMiddleware.php @@ -1,9 +1,7 @@ getLocale($request); @@ -21,22 +18,28 @@ class LocaleMiddleware extends BaseMiddleware protected function getLocale(Request $request) { - if(GROCY_AUTHENTICATED) + if (GROCY_AUTHENTICATED) { $locale = UsersService::getInstance()->GetUserSetting(GROCY_USER_ID, 'locale'); - if (isset($locale) && !empty($locale)) { - if (in_array($locale, scandir(__DIR__ . '/../localization'))) { + + if (isset($locale) && !empty($locale)) + { + if (in_array($locale, scandir(__DIR__ . '/../localization'))) + { return $locale; } + } + } - $langs = join(',', $request->getHeader('Accept-Language')); + $langs = implode(',', $request->getHeader('Accept-Language')); // src: https://gist.github.com/spolischook/0cde9c6286415cddc088 $prefLocales = array_reduce( explode(',', $langs), - function ($res, $el) { + function ($res, $el) + { list($l, $q) = array_merge(explode(';q=', $el), [1]); $res[$l] = (float) $q; return $res; @@ -44,24 +47,33 @@ class LocaleMiddleware extends BaseMiddleware arsort($prefLocales); $availableLocales = scandir(__DIR__ . '/../localization'); - foreach ($prefLocales as $locale => $q) { - if(in_array($locale, $availableLocales)) + + foreach ($prefLocales as $locale => $q) + { + if (in_array($locale, $availableLocales)) { return $locale; } - // e.g. en_GB - if(in_array(substr($locale, 0, 5), $availableLocales)) + +// e.g. en_GB + if (in_array(substr($locale, 0, 5), $availableLocales)) { return substr($locale, 0, 5); } - // e.g: cs - // or en - // or de - if(in_array(substr($locale, 0, 2), $availableLocales)) + +// e.g: cs + +// or en + +// or de + if (in_array(substr($locale, 0, 2), $availableLocales)) { return substr($locale, 0, 2); } + } + return GROCY_DEFAULT_LOCALE; } + } diff --git a/middleware/ReverseProxyAuthMiddleware.php b/middleware/ReverseProxyAuthMiddleware.php index 2e6e5d76..714559d4 100644 --- a/middleware/ReverseProxyAuthMiddleware.php +++ b/middleware/ReverseProxyAuthMiddleware.php @@ -2,10 +2,9 @@ namespace Grocy\Middleware; -use Psr\Http\Message\ServerRequestInterface as Request; - use Grocy\Services\DatabaseService; use Grocy\Services\UsersService; +use Psr\Http\Message\ServerRequestInterface as Request; class ReverseProxyAuthMiddleware extends AuthMiddleware { @@ -23,7 +22,7 @@ class ReverseProxyAuthMiddleware extends AuthMiddleware if (count($username) !== 1) { // Invalid configuration of Proxy - throw new \Exception("ReverseProxyAuthMiddleware: Invalid username from proxy: " . var_dump($username)); + throw new \Exception('ReverseProxyAuthMiddleware: Invalid username from proxy: ' . var_dump($username)); } $username = $username[0]; @@ -37,4 +36,5 @@ class ReverseProxyAuthMiddleware extends AuthMiddleware return $user; } + } diff --git a/middleware/SessionAuthMiddleware.php b/middleware/SessionAuthMiddleware.php index 2a716720..d2e5dafa 100644 --- a/middleware/SessionAuthMiddleware.php +++ b/middleware/SessionAuthMiddleware.php @@ -1,23 +1,21 @@ SessionCookieName = $this->AppContainer->get('LoginControllerInstance')->GetSessionCookieName(); } - protected $SessionCookieName; - function authenticate(Request $request) { if (!defined('GROCY_SHOW_AUTH_VIEWS')) @@ -26,6 +24,7 @@ class SessionAuthMiddleware extends AuthMiddleware } $sessionService = SessionService::getInstance(); + if (!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) { return null; @@ -34,5 +33,7 @@ class SessionAuthMiddleware extends AuthMiddleware { return $sessionService->GetUserBySessionKey($_COOKIE[$this->SessionCookieName]); } + } + } diff --git a/public/index.php b/public/index.php index b022a1e3..12546063 100644 --- a/public/index.php +++ b/public/index.php @@ -1,6 +1,7 @@ checkRequirements(); + (new PrerequisiteChecker())->checkRequirements(); } catch (ERequirementNotMet $ex) { - die('Unable to run grocy: ' . $ex->getMessage()); + exit('Unable to run grocy: ' . $ex->getMessage()); } require_once __DIR__ . '/../app.php'; diff --git a/services/ApiKeyService.php b/services/ApiKeyService.php index 2e8f76cd..46fcc98f 100644 --- a/services/ApiKeyService.php +++ b/services/ApiKeyService.php @@ -5,8 +5,70 @@ namespace Grocy\Services; class ApiKeyService extends BaseService { const API_KEY_TYPE_DEFAULT = 'default'; + const API_KEY_TYPE_SPECIAL_PURPOSE_CALENDAR_ICAL = 'special-purpose-calendar-ical'; + /** + * @return string + */ + public function CreateApiKey($keyType = self::API_KEY_TYPE_DEFAULT) + { + $newApiKey = $this->GenerateApiKey(); + + $apiKeyRow = $this->getDatabase()->api_keys()->createRow([ + 'api_key' => $newApiKey, + 'user_id' => GROCY_USER_ID, + 'expires' => '2999-12-31 23:59:59', // Default is that API keys expire never + 'key_type' => $keyType + ]); + $apiKeyRow->save(); + + return $newApiKey; + } + + public function GetApiKeyId($apiKey) + { + $apiKey = $this->getDatabase()->api_keys()->where('api_key', $apiKey)->fetch(); + return $apiKey->id; + } + +// Returns any valid key for $keyType, + // not allowed for key type "default" + public function GetOrCreateApiKey($keyType) + { + if ($keyType === self::API_KEY_TYPE_DEFAULT) + { + return null; + } + else + { + $apiKeyRow = $this->getDatabase()->api_keys()->where('key_type = :1 AND expires > :2', $keyType, date('Y-m-d H:i:s', time()))->fetch(); + + if ($apiKeyRow !== null) + { + return $apiKeyRow->api_key; + } + else + { + return $this->CreateApiKey($keyType); + } + + } + + } + + public function GetUserByApiKey($apiKey) + { + $apiKeyRow = $this->getDatabase()->api_keys()->where('api_key', $apiKey)->fetch(); + + if ($apiKeyRow !== null) + { + return $this->getDatabase()->users($apiKeyRow->user_id); + } + + return null; + } + /** * @return boolean */ @@ -19,14 +81,15 @@ class ApiKeyService extends BaseService else { $apiKeyRow = $this->getDatabase()->api_keys()->where('api_key = :1 AND expires > :2 AND key_type = :3', $apiKey, date('Y-m-d H:i:s', time()), $keyType)->fetch(); + if ($apiKeyRow !== null) { - // This should not change the database file modification time as this is used +// This should not change the database file modification time as this is used // to determine if REALLY something has changed $dbModTime = $this->getDatabaseService()->GetDbChangedTime(); - $apiKeyRow->update(array( + $apiKeyRow->update([ 'last_used' => date('Y-m-d H:i:s', time()) - )); + ]); $this->getDatabaseService()->SetDbChangedTime($dbModTime); return true; @@ -35,25 +98,9 @@ class ApiKeyService extends BaseService { return false; } + } - } - /** - * @return string - */ - public function CreateApiKey($keyType = self::API_KEY_TYPE_DEFAULT) - { - $newApiKey = $this->GenerateApiKey(); - - $apiKeyRow = $this->getDatabase()->api_keys()->createRow(array( - 'api_key' => $newApiKey, - 'user_id' => GROCY_USER_ID, - 'expires' => '2999-12-31 23:59:59', // Default is that API keys expire never - 'key_type' => $keyType - )); - $apiKeyRow->save(); - - return $newApiKey; } public function RemoveApiKey($apiKey) @@ -61,46 +108,9 @@ class ApiKeyService extends BaseService $this->getDatabase()->api_keys()->where('api_key', $apiKey)->delete(); } - public function GetApiKeyId($apiKey) - { - $apiKey = $this->getDatabase()->api_keys()->where('api_key', $apiKey)->fetch(); - return $apiKey->id; - } - - public function GetUserByApiKey($apiKey) - { - $apiKeyRow = $this->getDatabase()->api_keys()->where('api_key', $apiKey)->fetch(); - if ($apiKeyRow !== null) - { - return $this->getDatabase()->users($apiKeyRow->user_id); - } - return null; - } - - // Returns any valid key for $keyType, - // not allowed for key type "default" - public function GetOrCreateApiKey($keyType) - { - if ($keyType === self::API_KEY_TYPE_DEFAULT) - { - return null; - } - else - { - $apiKeyRow = $this->getDatabase()->api_keys()->where('key_type = :1 AND expires > :2', $keyType, date('Y-m-d H:i:s', time()))->fetch(); - if ($apiKeyRow !== null) - { - return $apiKeyRow->api_key; - } - else - { - return $this->CreateApiKey($keyType); - } - } - } - private function GenerateApiKey() { return RandomString(50); } + } diff --git a/services/ApplicationService.php b/services/ApplicationService.php index 13137dd0..c3675430 100644 --- a/services/ApplicationService.php +++ b/services/ApplicationService.php @@ -6,6 +6,45 @@ class ApplicationService extends BaseService { private $InstalledVersion; + public function GetChangelog() + { + $changelogItems = []; + + foreach (glob(__DIR__ . '/../changelog/*.md') as $file) + { + $fileName = basename($file); + $fileNameParts = explode('_', $fileName); + + $fileContent = file_get_contents($file); + $version = $fileNameParts[1]; + $releaseDate = explode('.', $fileNameParts[2])[0]; + $releaseNumber = intval($fileNameParts[0]); + + $changelogItems[] = [ + 'version' => $version, + 'release_date' => $releaseDate, + 'body' => $fileContent, + 'release_number' => $releaseNumber + ]; + } + + // Sort changelog items to have the changelog descending by newest version + usort($changelogItems, function ($a, $b) + { + if ($a['release_number'] == $b['release_number']) + { + return 0; + } + + return ($a['release_number'] < $b['release_number']) ? 1 : -1; + }); + + return [ + 'changelog_items' => $changelogItems, + 'newest_release_number' => $changelogItems[0]['release_number'] + ]; + } + public function GetInstalledVersion() { if ($this->InstalledVersion == null) @@ -20,6 +59,7 @@ class ApplicationService extends BaseService $this->InstalledVersion->Version = "pre-release-$commitHash"; $this->InstalledVersion->ReleaseDate = substr($commitDate, 0, 19); } + } return $this->InstalledVersion; @@ -31,48 +71,11 @@ class ApplicationService extends BaseService $sqliteVersion = $pdo->query('SELECT sqlite_version()')->fetch()[0]; $pdo = null; - return array( + return [ 'grocy_version' => $this->GetInstalledVersion(), 'php_version' => phpversion(), - 'sqlite_version' => $sqliteVersion - ); + 'sqlite_version' => $sqliteVersion + ]; } - public function GetChangelog() - { - $changelogItems = array(); - foreach(glob(__DIR__ . '/../changelog/*.md') as $file) - { - $fileName = basename($file); - $fileNameParts = explode('_', $fileName); - - $fileContent = file_get_contents($file); - $version = $fileNameParts[1]; - $releaseDate = explode('.', $fileNameParts[2])[0]; - $releaseNumber = intval($fileNameParts[0]); - - $changelogItems[] = array( - 'version' => $version, - 'release_date' => $releaseDate, - 'body' => $fileContent, - 'release_number' => $releaseNumber - ); - } - - // Sort changelog items to have the changelog descending by newest version - usort($changelogItems, function($a, $b) - { - if ($a['release_number'] == $b['release_number']) - { - return 0; - } - - return ($a['release_number'] < $b['release_number']) ? 1 : -1; - }); - - return array( - 'changelog_items' => $changelogItems, - 'newest_release_number' => $changelogItems[0]['release_number'] - ); - } } diff --git a/services/BaseService.php b/services/BaseService.php index b5c98df0..9d95d877 100644 --- a/services/BaseService.php +++ b/services/BaseService.php @@ -2,20 +2,19 @@ namespace Grocy\Services; -#use \Grocy\Services\DatabaseService; -#use \Grocy\Services\LocalizationService; - class BaseService { - public function __construct() { - } + private static $instances = []; - private static $instances = array(); + public function __construct() + { + } public static function getInstance() { $className = get_called_class(); - if(!isset(self::$instances[$className])) + + if (!isset(self::$instances[$className])) { self::$instances[$className] = new $className(); } @@ -23,9 +22,14 @@ class BaseService return self::$instances[$className]; } - protected function getDatabaseService() + protected function getBatteriesService() { - return DatabaseService::getInstance(); + return BatteriesService::getInstance(); + } + + protected function getChoresService() + { + return ChoresService::getInstance(); } protected function getDatabase() @@ -33,7 +37,12 @@ class BaseService return $this->getDatabaseService()->GetDbConnection(); } - protected function getLocalizationService() + protected function getDatabaseService() + { + return DatabaseService::getInstance(); + } + + protected function getLocalizationService() { return LocalizationService::getInstance(GROCY_LOCALE); } @@ -48,18 +57,9 @@ class BaseService return TasksService::getInstance(); } - protected function getChoresService() - { - return ChoresService::getInstance(); - } - - protected function getBatteriesService() - { - return BatteriesService::getInstance(); - } - protected function getUsersService() { return UsersService::getInstance(); } + } diff --git a/services/BatteriesService.php b/services/BatteriesService.php index 1e3da503..7fc4276e 100644 --- a/services/BatteriesService.php +++ b/services/BatteriesService.php @@ -4,12 +4,6 @@ namespace Grocy\Services; class BatteriesService extends BaseService { - public function GetCurrent() - { - $sql = 'SELECT * from batteries_current'; - return $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); - } - public function GetBatteryDetails(int $batteryId) { if (!$this->BatteryExists($batteryId)) @@ -22,12 +16,18 @@ class BatteriesService extends BaseService $batteryLastChargedTime = $this->getDatabase()->battery_charge_cycles()->where('battery_id = :1 AND undone = 0', $batteryId)->max('tracked_time'); $nextChargeTime = $this->getDatabase()->batteries_current()->where('battery_id', $batteryId)->min('next_estimated_charge_time'); - return array( + return [ 'battery' => $battery, 'last_charged' => $batteryLastChargedTime, 'charge_cycles_count' => $batteryChargeCyclesCount, 'next_estimated_charge_time' => $nextChargeTime - ); + ]; + } + + public function GetCurrent() + { + $sql = 'SELECT * from batteries_current'; + return $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); } public function TrackChargeCycle(int $batteryId, string $trackedTime) @@ -37,33 +37,35 @@ class BatteriesService extends BaseService throw new \Exception('Battery does not exist'); } - $logRow = $this->getDatabase()->battery_charge_cycles()->createRow(array( + $logRow = $this->getDatabase()->battery_charge_cycles()->createRow([ 'battery_id' => $batteryId, 'tracked_time' => $trackedTime - )); + ]); $logRow->save(); return $this->getDatabase()->lastInsertId(); } + public function UndoChargeCycle($chargeCycleId) + { + $logRow = $this->getDatabase()->battery_charge_cycles()->where('id = :1 AND undone = 0', $chargeCycleId)->fetch(); + + if ($logRow == null) + { + throw new \Exception('Charge cycle does not exist or was already undone'); + } + + // Update log entry + $logRow->update([ + 'undone' => 1, + 'undone_timestamp' => date('Y-m-d H:i:s') + ]); + } + private function BatteryExists($batteryId) { $batteryRow = $this->getDatabase()->batteries()->where('id = :1', $batteryId)->fetch(); return $batteryRow !== null; } - public function UndoChargeCycle($chargeCycleId) - { - $logRow = $this->getDatabase()->battery_charge_cycles()->where('id = :1 AND undone = 0', $chargeCycleId)->fetch(); - if ($logRow == null) - { - throw new \Exception('Charge cycle does not exist or was already undone'); - } - - // Update log entry - $logRow->update(array( - 'undone' => 1, - 'undone_timestamp' => date('Y-m-d H:i:s') - )); - } } diff --git a/services/CalendarService.php b/services/CalendarService.php index b160f17e..3ecac12e 100644 --- a/services/CalendarService.php +++ b/services/CalendarService.php @@ -3,147 +3,171 @@ namespace Grocy\Services; #use \Grocy\Services\StockService; + #use \Grocy\Services\TasksService; + #use \Grocy\Services\ChoresService; + #use \Grocy\Services\BatteriesService; #use \Grocy\Services\UsersService; -use \Grocy\Helpers\UrlManager; +use Grocy\Helpers\UrlManager; class CalendarService extends BaseService { - public function __construct() - { - parent::__construct(); - $this->UrlManager = new UrlManager(GROCY_BASE_URL); - } - public function GetEvents() { - $stockEvents = array(); + $stockEvents = []; + if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) { $products = $this->getDatabase()->products(); $titlePrefix = $this->getLocalizationService()->__t('Product expires') . ': '; - foreach($this->getStockService()->GetCurrentStock() as $currentStockEntry) + + foreach ($this->getStockService()->GetCurrentStock() as $currentStockEntry) { if ($currentStockEntry->amount > 0) { - $stockEvents[] = array( + $stockEvents[] = [ 'title' => $titlePrefix . FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name, 'start' => $currentStockEntry->best_before_date, 'date_format' => 'date', 'link' => $this->UrlManager->ConstructUrl('/stockoverview') - ); + ]; } + } + } - $taskEvents = array(); + $taskEvents = []; + if (GROCY_FEATURE_FLAG_TASKS) { $titlePrefix = $this->getLocalizationService()->__t('Task due') . ': '; - foreach($this->getTasksService()->GetCurrent() as $currentTaskEntry) + + foreach ($this->getTasksService()->GetCurrent() as $currentTaskEntry) { - $taskEvents[] = array( + $taskEvents[] = [ 'title' => $titlePrefix . $currentTaskEntry->name, 'start' => $currentTaskEntry->due_date, 'date_format' => 'date', 'link' => $this->UrlManager->ConstructUrl('/tasks') - ); + ]; } + } - $choreEvents = array(); + $choreEvents = []; + if (GROCY_FEATURE_FLAG_CHORES) { $users = $this->getUsersService()->GetUsersAsDto(); $chores = $this->getDatabase()->chores(); $titlePrefix = $this->getLocalizationService()->__t('Chore due') . ': '; - foreach($this->getChoresService()->GetCurrent() as $currentChoreEntry) + + foreach ($this->getChoresService()->GetCurrent() as $currentChoreEntry) { $chore = FindObjectInArrayByPropertyValue($chores, 'id', $currentChoreEntry->chore_id); $assignedToText = ''; + if (!empty($currentChoreEntry->next_execution_assigned_to_user_id)) { $assignedToText = ' (' . $this->getLocalizationService()->__t('assigned to %s', FindObjectInArrayByPropertyValue($users, 'id', $currentChoreEntry->next_execution_assigned_to_user_id)->display_name) . ')'; } - $choreEvents[] = array( + $choreEvents[] = [ 'title' => $titlePrefix . $chore->name . $assignedToText, 'start' => $currentChoreEntry->next_estimated_execution_time, 'date_format' => 'datetime', 'link' => $this->UrlManager->ConstructUrl('/choresoverview'), 'allDay' => $chore->track_date_only == 1 - ); + ]; } + } - $batteryEvents = array(); + $batteryEvents = []; + if (GROCY_FEATURE_FLAG_BATTERIES) { $batteries = $this->getDatabase()->batteries(); $titlePrefix = $this->getLocalizationService()->__t('Battery charge cycle due') . ': '; - foreach($this->getBatteriesService()->GetCurrent() as $currentBatteryEntry) + + foreach ($this->getBatteriesService()->GetCurrent() as $currentBatteryEntry) { - $batteryEvents[] = array( + $batteryEvents[] = [ 'title' => $titlePrefix . FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->name, 'start' => $currentBatteryEntry->next_estimated_charge_time, 'date_format' => 'datetime', 'link' => $this->UrlManager->ConstructUrl('/batteriesoverview') - ); + ]; } + } - $mealPlanRecipeEvents = array(); + $mealPlanRecipeEvents = []; + if (GROCY_FEATURE_FLAG_RECIPES) { $recipes = $this->getDatabase()->recipes(); $mealPlanDayRecipes = $this->getDatabase()->recipes()->where('type', 'mealplan-day'); $titlePrefix = $this->getLocalizationService()->__t('Meal plan recipe') . ': '; - foreach($mealPlanDayRecipes as $mealPlanDayRecipe) + foreach ($mealPlanDayRecipes as $mealPlanDayRecipe) { $recipesOfCurrentDay = $this->getDatabase()->recipes_nestings_resolved()->where('recipe_id = :1 AND includes_recipe_id != :1', $mealPlanDayRecipe->id); + foreach ($recipesOfCurrentDay as $recipeOfCurrentDay) { - $mealPlanRecipeEvents[] = array( + $mealPlanRecipeEvents[] = [ 'title' => $titlePrefix . FindObjectInArrayByPropertyValue($recipes, 'id', $recipeOfCurrentDay->includes_recipe_id)->name, 'start' => FindObjectInArrayByPropertyValue($recipes, 'id', $recipeOfCurrentDay->recipe_id)->name, 'date_format' => 'date', 'description' => $this->UrlManager->ConstructUrl('/mealplan' . '?week=' . FindObjectInArrayByPropertyValue($recipes, 'id', $recipeOfCurrentDay->recipe_id)->name), - 'link' => $this->UrlManager->ConstructUrl('/recipes' . '?recipe=' . $recipeOfCurrentDay->includes_recipe_id . "#fullscreen") - ); + 'link' => $this->UrlManager->ConstructUrl('/recipes' . '?recipe=' . $recipeOfCurrentDay->includes_recipe_id . '#fullscreen') + ]; } + } $mealPlanDayNotes = $this->getDatabase()->meal_plan()->where('type', 'note'); $titlePrefix = $this->getLocalizationService()->__t('Meal plan note') . ': '; - $mealPlanNotesEvents = array(); - foreach($mealPlanDayNotes as $mealPlanDayNote) + $mealPlanNotesEvents = []; + + foreach ($mealPlanDayNotes as $mealPlanDayNote) { - $mealPlanNotesEvents[] = array( + $mealPlanNotesEvents[] = [ 'title' => $titlePrefix . $mealPlanDayNote->note, 'start' => $mealPlanDayNote->day, 'date_format' => 'date' - ); + ]; } $products = $this->getDatabase()->products(); $mealPlanDayProducts = $this->getDatabase()->meal_plan()->where('type', 'product'); $titlePrefix = $this->getLocalizationService()->__t('Meal plan product') . ': '; - $mealPlanProductEvents = array(); - foreach($mealPlanDayProducts as $mealPlanDayProduct) + $mealPlanProductEvents = []; + + foreach ($mealPlanDayProducts as $mealPlanDayProduct) { - $mealPlanProductEvents[] = array( + $mealPlanProductEvents[] = [ 'title' => $titlePrefix . FindObjectInArrayByPropertyValue($products, 'id', $mealPlanDayProduct->product_id)->name, 'start' => $mealPlanDayProduct->day, 'date_format' => 'date' - ); + ]; } + } return array_merge($stockEvents, $taskEvents, $choreEvents, $batteryEvents, $mealPlanRecipeEvents, $mealPlanNotesEvents, $mealPlanProductEvents); } + + public function __construct() + { + parent::__construct(); + $this->UrlManager = new UrlManager(GROCY_BASE_URL); + } + } diff --git a/services/ChoresService.php b/services/ChoresService.php index a22200cf..6f161293 100644 --- a/services/ChoresService.php +++ b/services/ChoresService.php @@ -2,31 +2,115 @@ namespace Grocy\Services; -#use \Grocy\Services\StockService; - class ChoresService extends BaseService { - const CHORE_PERIOD_TYPE_MANUALLY = 'manually'; - const CHORE_PERIOD_TYPE_DYNAMIC_REGULAR = 'dynamic-regular'; - const CHORE_PERIOD_TYPE_DAILY = 'daily'; - const CHORE_PERIOD_TYPE_WEEKLY = 'weekly'; - const CHORE_PERIOD_TYPE_MONTHLY = 'monthly'; - const CHORE_PERIOD_TYPE_YEARLY = 'yearly'; - - const CHORE_ASSIGNMENT_TYPE_NO_ASSIGNMENT = 'no-assignment'; - const CHORE_ASSIGNMENT_TYPE_WHO_LEAST_DID_FIRST = 'who-least-did-first'; - const CHORE_ASSIGNMENT_TYPE_RANDOM = 'random'; const CHORE_ASSIGNMENT_TYPE_IN_ALPHABETICAL_ORDER = 'in-alphabetical-order'; - public function __construct() - { - parent::__construct(); - } + const CHORE_ASSIGNMENT_TYPE_NO_ASSIGNMENT = 'no-assignment'; - public function GetCurrent() + const CHORE_ASSIGNMENT_TYPE_RANDOM = 'random'; + + const CHORE_ASSIGNMENT_TYPE_WHO_LEAST_DID_FIRST = 'who-least-did-first'; + + const CHORE_PERIOD_TYPE_DAILY = 'daily'; + + const CHORE_PERIOD_TYPE_DYNAMIC_REGULAR = 'dynamic-regular'; + + const CHORE_PERIOD_TYPE_MANUALLY = 'manually'; + + const CHORE_PERIOD_TYPE_MONTHLY = 'monthly'; + + const CHORE_PERIOD_TYPE_WEEKLY = 'weekly'; + + const CHORE_PERIOD_TYPE_YEARLY = 'yearly'; + + public function CalculateNextExecutionAssignment($choreId) { - $sql = 'SELECT chores_current.*, chores.name AS chore_name from chores_current join chores on chores_current.chore_id = chores.id'; - return $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); + if (!$this->ChoreExists($choreId)) + { + throw new \Exception('Chore does not exist'); + } + + $chore = $this->getDatabase()->chores($choreId); + $choreLastTrackedTime = $this->getDatabase()->chores_log()->where('chore_id = :1 AND undone = 0', $choreId)->max('tracked_time'); + $lastChoreLogRow = $this->getDatabase()->chores_log()->where('chore_id = :1 AND tracked_time = :2 AND undone = 0', $choreId, $choreLastTrackedTime)->orderBy('row_created_timestamp', 'DESC')->fetch(); + $lastDoneByUserId = $lastChoreLogRow->done_by_user_id; + + $users = $this->getUsersService()->GetUsersAsDto(); + $assignedUsers = []; + + foreach ($users as $user) + { + if (in_array($user->id, explode(',', $chore->assignment_config))) + { + $assignedUsers[] = $user; + } + + } + + $nextExecutionUserId = null; + + if ($chore->assignment_type == self::CHORE_ASSIGNMENT_TYPE_RANDOM) + { +// Random assignment and only 1 user in the group? Well, ok - will be hard to guess the next one... + if (count($assignedUsers) == 1) + { + $nextExecutionUserId = array_shift($assignedUsers)->id; + } + else + { +// Randomness in small groups will likely often result in the same user, so try it as long as this is the case + while ($nextExecutionUserId == null || $nextExecutionUserId == $lastDoneByUserId) + { + $nextExecutionUserId = $assignedUsers[array_rand($assignedUsers)]->id; + } + + } + + } + else if ($chore->assignment_type == self::CHORE_ASSIGNMENT_TYPE_IN_ALPHABETICAL_ORDER) + { + usort($assignedUsers, function ($a, $b) + { + return strcmp($a->display_name, $b->display_name); + }); + + $nextRoundMatches = false; + foreach ($assignedUsers as $user) + { + if ($nextRoundMatches) + { + $nextExecutionUserId = $user->id; + break; + } + + if ($user->id == $lastDoneByUserId) + { + $nextRoundMatches = true; + } + + } + +// If nothing has matched, probably it was the last user in the sorted list -> the first one is the next one + if ($nextExecutionUserId == null) + { + $nextExecutionUserId = array_shift($assignedUsers)->id; + } + + } + else if ($chore->assignment_type == self::CHORE_ASSIGNMENT_TYPE_WHO_LEAST_DID_FIRST) + { + $row = $this->getDatabase()->chores_execution_users_statistics()->where('chore_id = :1', $choreId)->orderBy('execution_count')->limit(1)->fetch(); + if ($row != null) + { + $nextExecutionUserId = $row->user_id; + } + + } + + $chore->update([ + 'next_execution_assigned_to_user_id' => $nextExecutionUserId + ]); } public function GetChoreDetails(int $choreId) @@ -36,14 +120,14 @@ class ChoresService extends BaseService throw new \Exception('Chore does not exist'); } - $users = $this->getUsersService()->GetUsersAsDto(); + $users = $this->getUsersService()->GetUsersAsDto(); $chore = $this->getDatabase()->chores($choreId); $choreTrackedCount = $this->getDatabase()->chores_log()->where('chore_id = :1 AND undone = 0', $choreId)->count(); $choreLastTrackedTime = $this->getDatabase()->chores_log()->where('chore_id = :1 AND undone = 0', $choreId)->max('tracked_time'); $nextExecutionTime = $this->getDatabase()->chores_current()->where('chore_id', $choreId)->min('next_estimated_execution_time'); - $lastChoreLogRow = $this->getDatabase()->chores_log()->where('chore_id = :1 AND tracked_time = :2 AND undone = 0', $choreId, $choreLastTrackedTime)->fetch(); + $lastChoreLogRow = $this->getDatabase()->chores_log()->where('chore_id = :1 AND tracked_time = :2 AND undone = 0', $choreId, $choreLastTrackedTime)->fetch(); $lastDoneByUser = null; if ($lastChoreLogRow !== null && !empty($lastChoreLogRow)) { @@ -56,14 +140,20 @@ class ChoresService extends BaseService $nextExecutionAssignedUser = FindObjectInArrayByPropertyValue($users, 'id', $chore->next_execution_assigned_to_user_id); } - return array( + return [ 'chore' => $chore, 'last_tracked' => $choreLastTrackedTime, 'tracked_count' => $choreTrackedCount, 'last_done_by' => $lastDoneByUser, 'next_estimated_execution_time' => $nextExecutionTime, 'next_execution_assigned_user' => $nextExecutionAssignedUser - ); + ]; + } + + public function GetCurrent() + { + $sql = 'SELECT chores_current.*, chores.name AS chore_name from chores_current join chores on chores_current.chore_id = chores.id'; + return $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); } public function TrackChore(int $choreId, string $trackedTime, $doneBy = GROCY_USER_ID) @@ -85,11 +175,11 @@ class ChoresService extends BaseService $trackedTime = substr($trackedTime, 0, 10) . ' 00:00:00'; } - $logRow = $this->getDatabase()->chores_log()->createRow(array( + $logRow = $this->getDatabase()->chores_log()->createRow([ 'chore_id' => $choreId, 'tracked_time' => $trackedTime, 'done_by_user_id' => $doneBy - )); + ]); $logRow->save(); $lastInsertId = $this->getDatabase()->lastInsertId(); @@ -103,12 +193,6 @@ class ChoresService extends BaseService return $lastInsertId; } - private function ChoreExists($choreId) - { - $choreRow = $this->getDatabase()->chores()->where('id = :1', $choreId)->fetch(); - return $choreRow !== null; - } - public function UndoChoreExecution($executionId) { $logRow = $this->getDatabase()->chores_log()->where('id = :1 AND undone = 0', $executionId)->fetch(); @@ -118,90 +202,21 @@ class ChoresService extends BaseService } // Update log entry - $logRow->update(array( + $logRow->update([ 'undone' => 1, 'undone_timestamp' => date('Y-m-d H:i:s') - )); + ]); } - public function CalculateNextExecutionAssignment($choreId) + public function __construct() { - if (!$this->ChoreExists($choreId)) - { - throw new \Exception('Chore does not exist'); - } - - $chore = $this->getDatabase()->chores($choreId); - $choreLastTrackedTime = $this->getDatabase()->chores_log()->where('chore_id = :1 AND undone = 0', $choreId)->max('tracked_time'); - $lastChoreLogRow = $this->getDatabase()->chores_log()->where('chore_id = :1 AND tracked_time = :2 AND undone = 0', $choreId, $choreLastTrackedTime)->orderBy('row_created_timestamp', 'DESC')->fetch(); - $lastDoneByUserId = $lastChoreLogRow->done_by_user_id; - - $users = $this->getUsersService()->GetUsersAsDto(); - $assignedUsers = array(); - foreach ($users as $user) - { - if (in_array($user->id, explode(',', $chore->assignment_config))) - { - $assignedUsers[] = $user; - } - } - - $nextExecutionUserId = null; - if ($chore->assignment_type == self::CHORE_ASSIGNMENT_TYPE_RANDOM) - { - // Random assignment and only 1 user in the group? Well, ok - will be hard to guess the next one... - if (count($assignedUsers) == 1) - { - $nextExecutionUserId = array_shift($assignedUsers)->id; - } - else - { - // Randomness in small groups will likely often result in the same user, so try it as long as this is the case - while ($nextExecutionUserId == null || $nextExecutionUserId == $lastDoneByUserId) - { - $nextExecutionUserId = $assignedUsers[array_rand($assignedUsers)]->id; - } - } - } - else if ($chore->assignment_type == self::CHORE_ASSIGNMENT_TYPE_IN_ALPHABETICAL_ORDER) - { - usort($assignedUsers, function($a, $b) - { - return strcmp($a->display_name, $b->display_name); - }); - - $nextRoundMatches = false; - foreach ($assignedUsers as $user) - { - if ($nextRoundMatches) - { - $nextExecutionUserId = $user->id; - break; - } - - if ($user->id == $lastDoneByUserId) - { - $nextRoundMatches = true; - } - } - - // If nothing has matched, probably it was the last user in the sorted list -> the first one is the next one - if ($nextExecutionUserId == null) - { - $nextExecutionUserId = array_shift($assignedUsers)->id; - } - } - else if ($chore->assignment_type == self::CHORE_ASSIGNMENT_TYPE_WHO_LEAST_DID_FIRST) - { - $row = $this->getDatabase()->chores_execution_users_statistics()->where('chore_id = :1', $choreId)->orderBy('execution_count')->limit(1)->fetch(); - if ($row != null) - { - $nextExecutionUserId = $row->user_id; - } - } - - $chore->update(array( - 'next_execution_assigned_to_user_id' => $nextExecutionUserId - )); + parent::__construct(); } + + private function ChoreExists($choreId) + { + $choreRow = $this->getDatabase()->chores()->where('id = :1', $choreId)->fetch(); + return $choreRow !== null; + } + } diff --git a/services/DatabaseMigrationService.php b/services/DatabaseMigrationService.php index c84da137..378964ce 100644 --- a/services/DatabaseMigrationService.php +++ b/services/DatabaseMigrationService.php @@ -6,47 +6,58 @@ class DatabaseMigrationService extends BaseService { public function MigrateDatabase() { - $this->getDatabaseService()->ExecuteDbStatement("CREATE TABLE IF NOT EXISTS migrations (migration INTEGER NOT NULL PRIMARY KEY UNIQUE, execution_time_timestamp DATETIME DEFAULT (datetime('now', 'localtime')))"); + $this->getDatabaseService()->ExecuteDbStatement("CREATE TABLE IF NOT EXISTS migrations (migration INTEGER NOT NULL PRIMARY KEY UNIQUE, execution_time_timestamp DATETIME DEFAULT (datetime('now', 'localtime')))"); + + $migrationFiles = []; - $migrationFiles = array(); foreach (new \FilesystemIterator(__DIR__ . '/../migrations') as $file) { - $migrationFiles[$file->getBasename()] = $file; + $migrationFiles[$file->getBasename()] = $file; } + ksort($migrationFiles); - foreach($migrationFiles as $migrationKey => $migrationFile) - { - if($migrationFile->getExtension() === 'php') - { - $migrationNumber = ltrim($migrationFile->getBasename('.php'), '0'); - $this->ExecutePhpMigrationWhenNeeded($migrationNumber, $migrationFile->getPathname()); - } - else if($migrationFile->getExtension() === 'sql') - { - $migrationNumber = ltrim($migrationFile->getBasename('.sql'), '0'); - $this->ExecuteSqlMigrationWhenNeeded($migrationNumber, file_get_contents($migrationFile->getPathname())); - } - } - } - - private function ExecuteSqlMigrationWhenNeeded(int $migrationId, string $sql) - { - $rowCount = $this->getDatabaseService()->ExecuteDbQuery('SELECT COUNT(*) FROM migrations WHERE migration = ' . $migrationId)->fetchColumn(); - if (intval($rowCount) === 0) + foreach ($migrationFiles as $migrationKey => $migrationFile) { - $this->getDatabaseService()->ExecuteDbStatement($sql); - $this->getDatabaseService()->ExecuteDbStatement('INSERT INTO migrations (migration) VALUES (' . $migrationId . ')'); + if ($migrationFile->getExtension() === 'php') + { + $migrationNumber = ltrim($migrationFile->getBasename('.php'), '0'); + $this->ExecutePhpMigrationWhenNeeded($migrationNumber, $migrationFile->getPathname()); + } + else + + if ($migrationFile->getExtension() === 'sql') + { + $migrationNumber = ltrim($migrationFile->getBasename('.sql'), '0'); + $this->ExecuteSqlMigrationWhenNeeded($migrationNumber, file_get_contents($migrationFile->getPathname())); + } + } + } private function ExecutePhpMigrationWhenNeeded(int $migrationId, string $phpFile) { $rowCount = $this->getDatabaseService()->ExecuteDbQuery('SELECT COUNT(*) FROM migrations WHERE migration = ' . $migrationId)->fetchColumn(); + if (intval($rowCount) === 0) { include $phpFile; $this->getDatabaseService()->ExecuteDbStatement('INSERT INTO migrations (migration) VALUES (' . $migrationId . ')'); } + } + + private function ExecuteSqlMigrationWhenNeeded(int $migrationId, string $sql) + { + $rowCount = $this->getDatabaseService()->ExecuteDbQuery('SELECT COUNT(*) FROM migrations WHERE migration = ' . $migrationId)->fetchColumn(); + + if (intval($rowCount) === 0) + { + $this->getDatabaseService()->ExecuteDbStatement($sql); + $this->getDatabaseService()->ExecuteDbStatement('INSERT INTO migrations (migration) VALUES (' . $migrationId . ')'); + } + + } + } diff --git a/services/DatabaseService.php b/services/DatabaseService.php index 81866bd6..f3045cd6 100644 --- a/services/DatabaseService.php +++ b/services/DatabaseService.php @@ -6,8 +6,80 @@ namespace Grocy\Services; class DatabaseService { + private static $DbConnection = null; + + private static $DbConnectionRaw = null; + private static $instance = null; + /** + * @return boolean|\PDOStatement + */ + public function ExecuteDbQuery(string $sql) + { + $pdo = $this->GetDbConnectionRaw(); + + if ($this->ExecuteDbStatement($sql) === true) + { + return $pdo->query($sql); + } + + return false; + } + + /** + * @return boolean + */ + public function ExecuteDbStatement(string $sql) + { + $pdo = $this->GetDbConnectionRaw(); + + if ($pdo->exec($sql) === false) + { + throw new Exception($pdo->errorInfo()); + } + + return true; + } + + public function GetDbChangedTime() + { + return date('Y-m-d H:i:s', filemtime($this->GetDbFilePath())); + } + + /** + * @return \LessQL\Database + */ + public function GetDbConnection() + { + if (self::$DbConnection == null) + { + self::$DbConnection = new \LessQL\Database($this->GetDbConnectionRaw()); + } + + return self::$DbConnection; + } + + /** + * @return \PDO + */ + public function GetDbConnectionRaw() + { + if (self::$DbConnectionRaw == null) + { + $pdo = new \PDO('sqlite:' . $this->GetDbFilePath()); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + self::$DbConnectionRaw = $pdo; + } + + return self::$DbConnectionRaw; + } + + public function SetDbChangedTime($dateTime) + { + touch($this->GetDbFilePath(), strtotime($dateTime)); + } + public static function getInstance() { if (self::$instance == null) @@ -28,71 +100,4 @@ class DatabaseService return GROCY_DATAPATH . '/grocy.db'; } - private static $DbConnectionRaw = null; - /** - * @return \PDO - */ - public function GetDbConnectionRaw() - { - if (self::$DbConnectionRaw == null) - { - $pdo = new \PDO('sqlite:' . $this->GetDbFilePath()); - $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); - self::$DbConnectionRaw = $pdo; - } - - return self::$DbConnectionRaw; - } - - private static $DbConnection = null; - /** - * @return \LessQL\Database - */ - public function GetDbConnection() - { - if (self::$DbConnection == null) - { - self::$DbConnection = new \LessQL\Database($this->GetDbConnectionRaw()); - } - - return self::$DbConnection; - } - - /** - * @return boolean - */ - public function ExecuteDbStatement(string $sql) - { - $pdo = $this->GetDbConnectionRaw(); - if ($pdo->exec($sql) === false) - { - throw new Exception($pdo->errorInfo()); - } - - return true; - } - - /** - * @return boolean|\PDOStatement - */ - public function ExecuteDbQuery(string $sql) - { - $pdo = $this->GetDbConnectionRaw(); - if ($this->ExecuteDbStatement($sql) === true) - { - return $pdo->query($sql); - } - - return false; - } - - public function GetDbChangedTime() - { - return date('Y-m-d H:i:s', filemtime($this->GetDbFilePath())); - } - - public function SetDbChangedTime($dateTime) - { - touch($this->GetDbFilePath(), strtotime($dateTime)); - } } diff --git a/services/DemoDataGeneratorService.php b/services/DemoDataGeneratorService.php index e74edb75..8c3bc0bb 100644 --- a/services/DemoDataGeneratorService.php +++ b/services/DemoDataGeneratorService.php @@ -6,17 +6,14 @@ namespace Grocy\Services; class DemoDataGeneratorService extends BaseService { - public function __construct() - { - parent::__construct(); - $this->LocalizationService = new LocalizationService(GROCY_DEFAULT_LOCALE); - } - protected $LocalizationService; + private $LastSupermarketId = 1; + public function PopulateDemoData() { $rowCount = $this->getDatabaseService()->ExecuteDbQuery('SELECT COUNT(*) FROM migrations WHERE migration = -1')->fetchColumn(); + if (intval($rowCount) === 0) { $loremIpsum = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.'; @@ -314,14 +311,29 @@ class DemoDataGeneratorService extends BaseService $this->DownloadFileIfNotAlreadyExists('https://releases.grocy.info/demoresources/chocolate_sauce.jpg', "$recipePicturesFolder/chocolate_sauce.jpg"); $this->DownloadFileIfNotAlreadyExists('https://releases.grocy.info/demoresources/pancakes_chocolate_sauce.jpg', "$recipePicturesFolder/pancakes_chocolate_sauce.jpg"); } + } - private function RandomPrice() + public function __construct() { - return mt_rand(2 * 100, 25 * 100) / 100; + parent::__construct(); + $this->LocalizationService = new LocalizationService(GROCY_DEFAULT_LOCALE); + } + + private function DownloadFileIfNotAlreadyExists($sourceUrl, $destinationPath) + { + if (!file_exists($destinationPath)) + { + file_put_contents($destinationPath, file_get_contents($sourceUrl, false, stream_context_create([ + 'ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false + ] + ]))); + } + } - private $LastSupermarketId = 1; private function NextSupermarketId() { $returnValue = $this->LastSupermarketId; @@ -338,10 +350,9 @@ class DemoDataGeneratorService extends BaseService return $returnValue; } - private function __t_sql(string $text) + private function RandomPrice() { - $localizedText = $this->getLocalizationService()->__t($text, null); - return str_replace("'", "''", $localizedText); + return mt_rand(2 * 100, 25 * 100) / 100; } private function __n_sql($number, string $singularForm, string $pluralForm) @@ -350,16 +361,10 @@ class DemoDataGeneratorService extends BaseService return str_replace("'", "''", $localizedText); } - private function DownloadFileIfNotAlreadyExists($sourceUrl, $destinationPath) + private function __t_sql(string $text) { - if (!file_exists($destinationPath)) - { - file_put_contents($destinationPath, file_get_contents($sourceUrl, false, stream_context_create(array( - 'ssl' => array( - 'verify_peer' => false, - 'verify_peer_name' => false, - ), - )))); - } + $localizedText = $this->getLocalizationService()->__t($text, null); + return str_replace("'", "''", $localizedText); } + } diff --git a/services/FilesService.php b/services/FilesService.php index cecdae3c..ac5c8310 100644 --- a/services/FilesService.php +++ b/services/FilesService.php @@ -2,37 +2,14 @@ namespace Grocy\Services; -use \Gumlet\ImageResize; +use Gumlet\ImageResize; class FilesService extends BaseService { const FILE_SERVE_TYPE_PICTURE = 'picture'; - public function __construct() - { - parent::__construct(); - - $this->StoragePath = GROCY_DATAPATH . '/storage'; - - if (!file_exists($this->StoragePath)) - { - mkdir($this->StoragePath); - } - } - private $StoragePath; - public function GetFilePath($group, $fileName) - { - $groupFolderPath = $this->StoragePath . '/' . $group; - if (!file_exists($groupFolderPath)) - { - mkdir($groupFolderPath); - } - - return $groupFolderPath . '/' . $fileName; - } - public function DownscaleImage($group, $fileName, $bestFitHeight = null, $bestFitWidth = null) { $filePath = $this->GetFilePath($group, $fileName); @@ -52,20 +29,27 @@ class FilesService extends BaseService if (!file_exists($filePathDownscaled)) { $image = new ImageResize($filePath); + if ($bestFitHeight !== null && $bestFitHeight !== null) { $image->resizeToBestFit($bestFitWidth, $bestFitHeight); } - else if ($bestFitHeight !== null) + else + + if ($bestFitHeight !== null) { $image->resizeToHeight($bestFitHeight); } - else if ($bestFitWidth !== null) + else + + if ($bestFitWidth !== null) { $image->resizeToWidth($bestFitWidth); } + $image->save($filePathDownscaled); } + } catch (ImageResizeException $ex) { @@ -74,4 +58,30 @@ class FilesService extends BaseService return $filePathDownscaled; } + + public function GetFilePath($group, $fileName) + { + $groupFolderPath = $this->StoragePath . '/' . $group; + + if (!file_exists($groupFolderPath)) + { + mkdir($groupFolderPath); + } + + return $groupFolderPath . '/' . $fileName; + } + + public function __construct() + { + parent::__construct(); + + $this->StoragePath = GROCY_DATAPATH . '/storage'; + + if (!file_exists($this->StoragePath)) + { + mkdir($this->StoragePath); + } + + } + } diff --git a/services/LocalizationService.php b/services/LocalizationService.php index 0c409d36..d23620ec 100644 --- a/services/LocalizationService.php +++ b/services/LocalizationService.php @@ -3,14 +3,69 @@ namespace Grocy\Services; #use \Grocy\Services\DatabaseService; -use \Gettext\Translation; -use \Gettext\Translations; -use \Gettext\Translator; +use Gettext\Translation; +use Gettext\Translations; +use Gettext\Translator; class LocalizationService { + protected $Po; - private static $instanceMap = array(); + protected $PoUserStrings; + + protected $Pot; + + protected $PotMain; + + protected $Translator; + + private static $instanceMap = []; + + public function CheckAndAddMissingTranslationToPot($text) + { + if (GROCY_MODE === 'dev') + { + if ($this->Pot->find('', $text) === false && $this->PoUserStrings->find('', $text) === false && empty($text) === false) + { + $translation = new Translation('', $text); + $this->PotMain[] = $translation; + $this->PotMain->toPoFile(__DIR__ . '/../localization/strings.pot'); + } + + } + + } + + public function GetPluralCount() + { + if ($this->Po->getHeader(Translations::HEADER_PLURAL) !== null) + { + return $this->Po->getPluralForms()[0]; + } + else + { + return 2; + } + + } + + public function GetPluralDefinition() + { + if ($this->Po->getHeader(Translations::HEADER_PLURAL) !== null) + { + return $this->Po->getPluralForms()[1]; + } + else + { + return '(n != 1)'; + } + + } + + public function GetPoAsJsonString() + { + return $this->Po->toJsonString(); + } public function __construct(string $culture) { @@ -19,15 +74,26 @@ class LocalizationService $this->LoadLocalizations($culture); } - - protected function getDatabaseService() + public function __n($number, $singularForm, $pluralForm) { - return DatabaseService::getInstance(); + $this->CheckAndAddMissingTranslationToPot($singularForm); + + return sprintf($this->Translator->ngettext($singularForm, $pluralForm, $number), $number); } - protected function getdatabase() + public function __t($text, ...$placeholderValues) { - return $this->getDatabaseService()->GetDbConnection(); + $this->CheckAndAddMissingTranslationToPot($text); + + if (func_num_args() === 1) + { + return $this->Translator->gettext($text); + } + else + { + return vsprintf($this->Translator->gettext($text), ...$placeholderValues); + } + } public static function getInstance(string $culture) @@ -40,11 +106,15 @@ class LocalizationService return self::$instanceMap[$culture]; } - protected $Pot; - protected $PotMain; - protected $Po; - protected $PoUserStrings; - protected $Translator; + protected function getDatabaseService() + { + return DatabaseService::getInstance(); + } + + protected function getdatabase() + { + return $this->getDatabaseService()->GetDbConnection(); + } private function LoadLocalizations() { @@ -63,45 +133,53 @@ class LocalizationService $this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/permissions.pot')); $this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/locales.pot')); - if (GROCY_MODE !== 'production') { $this->Pot = $this->Pot->mergeWith(Translations::fromPoFile(__DIR__ . '/../localization/demo_data.pot')); } + } $this->PoUserStrings = new Translations(); $this->PoUserStrings->setDomain('grocy/userstrings'); $this->Po = Translations::fromPoFile(__DIR__ . "/../localization/$culture/strings.po"); + if (file_exists(__DIR__ . "/../localization/$culture/chore_assignment_types.po")) { $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/chore_assignment_types.po")); } + if (file_exists(__DIR__ . "/../localization/$culture/component_translations.po")) { $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/component_translations.po")); } + if (file_exists(__DIR__ . "/../localization/$culture/stock_transaction_types.po")) { $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/stock_transaction_types.po")); } + if (file_exists(__DIR__ . "/../localization/$culture/chore_period_types.po")) { $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/chore_period_types.po")); } + if (file_exists(__DIR__ . "/../localization/$culture/userfield_types.po")) { $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/userfield_types.po")); } + if (file_exists(__DIR__ . "/../localization/$culture/permissions.po")) { $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/permissions.po")); } + if (file_exists(__DIR__ . "/../localization/$culture/locales.po")) { $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/locales.po")); } + if (GROCY_MODE !== 'production' && file_exists(__DIR__ . "/../localization/$culture/demo_data.po")) { $this->Po = $this->Po->mergeWith(Translations::fromPoFile(__DIR__ . "/../localization/$culture/demo_data.po")); @@ -128,6 +206,7 @@ class LocalizationService $this->PoUserStrings[] = $translation; } + $this->Po = $this->Po->mergeWith($this->PoUserStrings); } @@ -135,66 +214,4 @@ class LocalizationService $this->Translator->loadTranslations($this->Po); } - public function GetPoAsJsonString() - { - return $this->Po->toJsonString(); - } - - public function GetPluralCount() - { - if ($this->Po->getHeader(Translations::HEADER_PLURAL) !== null) - { - return $this->Po->getPluralForms()[0]; - } - else - { - return 2; - } - } - - public function GetPluralDefinition() - { - if ($this->Po->getHeader(Translations::HEADER_PLURAL) !== null) - { - return $this->Po->getPluralForms()[1]; - } - else - { - return '(n != 1)'; - } - } - - public function __t($text, ...$placeholderValues) - { - $this->CheckAndAddMissingTranslationToPot($text); - - if (func_num_args() === 1) - { - return $this->Translator->gettext($text); - } - else - { - return vsprintf($this->Translator->gettext($text), ...$placeholderValues); - } - } - - public function __n($number, $singularForm, $pluralForm) - { - $this->CheckAndAddMissingTranslationToPot($singularForm); - - return sprintf($this->Translator->ngettext($singularForm, $pluralForm, $number), $number); - } - - public function CheckAndAddMissingTranslationToPot($text) - { - if (GROCY_MODE === 'dev') - { - if ($this->Pot->find('', $text) === false && $this->PoUserStrings->find('', $text) === false && empty($text) === false) - { - $translation = new Translation('', $text); - $this->PotMain[] = $translation; - $this->PotMain->toPoFile(__DIR__ . '/../localization/strings.pot'); - } - } - } } diff --git a/services/RecipesService.php b/services/RecipesService.php index 18dfeaed..12f74cbf 100644 --- a/services/RecipesService.php +++ b/services/RecipesService.php @@ -6,13 +6,74 @@ namespace Grocy\Services; class RecipesService extends BaseService { - const RECIPE_TYPE_NORMAL = 'normal'; const RECIPE_TYPE_MEALPLAN_DAY = 'mealplan-day'; + const RECIPE_TYPE_MEALPLAN_WEEK = 'mealplan-week'; - public function __construct() + const RECIPE_TYPE_NORMAL = 'normal'; + + public function AddNotFulfilledProductsToShoppingList($recipeId, $excludedProductIds = null) { - parent::__construct(); + $recipe = $this->getDataBase()->recipes($recipeId); + + $recipePositions = $this->GetRecipesPosResolved(); + + foreach ($recipePositions as $recipePosition) + { + if ($recipePosition->recipe_id == $recipeId && !in_array($recipePosition->product_id, $excludedProductIds)) + { + $product = $this->getDataBase()->products($recipePosition->product_id); + + $toOrderAmount = round(($recipePosition->missing_amount - $recipePosition->amount_on_shopping_list) / $product->qu_factor_purchase_to_stock, 2); + + if ($recipe->not_check_shoppinglist == 1) + { + $toOrderAmount = round($recipePosition->missing_amount / $product->qu_factor_purchase_to_stock, 2); + } + + if ($toOrderAmount > 0) + { + $shoppinglistRow = $this->getDataBase()->shopping_list()->createRow([ + 'product_id' => $recipePosition->product_id, + 'amount' => $toOrderAmount, + 'note' => $this->getLocalizationService()->__t('Added for recipe %s', $recipe->name) + ]); + $shoppinglistRow->save(); + } + + } + + } + + } + + public function ConsumeRecipe($recipeId) + { + if (!$this->RecipeExists($recipeId)) + { + throw new \Exception('Recipe does not exist'); + } + + $transactionId = uniqid(); + $recipePositions = $this->getDatabase()->recipes_pos_resolved()->where('recipe_id', $recipeId)->fetchAll(); + + foreach ($recipePositions as $recipePosition) + { + if ($recipePosition->only_check_single_unit_in_stock == 0) + { + $this->getStockService()->ConsumeProduct($recipePosition->product_id, $recipePosition->recipe_amount, false, StockService::TRANSACTION_TYPE_CONSUME, 'default', $recipeId, null, $transactionId, true); + } + + } + + $recipeRow = $this->getDatabase()->recipes()->where('id = :1', $recipeId)->fetch(); + + if (!empty($recipeRow->product_id)) + { + $recipeResolvedRow = $this->getDatabase()->recipes_resolved()->where('recipe_id = :1', $recipeId)->fetch(); + $this->getStockService()->AddProduct($recipeRow->product_id, floatval($recipeRow->desired_servings), null, StockService::TRANSACTION_TYPE_SELF_PRODUCTION, date('Y-m-d'), floatval($recipeResolvedRow->costs)); + } + } public function GetRecipesPosResolved() @@ -27,59 +88,9 @@ class RecipesService extends BaseService return $this->getDataBaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); } - public function AddNotFulfilledProductsToShoppingList($recipeId, $excludedProductIds = null) + public function __construct() { - $recipe = $this->getDataBase()->recipes($recipeId); - - $recipePositions = $this->GetRecipesPosResolved(); - foreach ($recipePositions as $recipePosition) - { - if($recipePosition->recipe_id == $recipeId && !in_array($recipePosition->product_id, $excludedProductIds)) - { - $product = $this->getDataBase()->products($recipePosition->product_id); - - $toOrderAmount = round(($recipePosition->missing_amount - $recipePosition->amount_on_shopping_list) / $product->qu_factor_purchase_to_stock, 2); - if ($recipe->not_check_shoppinglist == 1) - { - $toOrderAmount = round($recipePosition->missing_amount / $product->qu_factor_purchase_to_stock, 2); - } - - if($toOrderAmount > 0) - { - $shoppinglistRow = $this->getDataBase()->shopping_list()->createRow(array( - 'product_id' => $recipePosition->product_id, - 'amount' => $toOrderAmount, - 'note' => $this->getLocalizationService()->__t('Added for recipe %s', $recipe->name) - )); - $shoppinglistRow->save(); - } - } - } - } - - public function ConsumeRecipe($recipeId) - { - if (!$this->RecipeExists($recipeId)) - { - throw new \Exception('Recipe does not exist'); - } - - $transactionId = uniqid(); - $recipePositions = $this->getDatabase()->recipes_pos_resolved()->where('recipe_id', $recipeId)->fetchAll(); - foreach ($recipePositions as $recipePosition) - { - if ($recipePosition->only_check_single_unit_in_stock == 0) - { - $this->getStockService()->ConsumeProduct($recipePosition->product_id, $recipePosition->recipe_amount, false, StockService::TRANSACTION_TYPE_CONSUME, 'default', $recipeId, null, $transactionId, true); - } - } - - $recipeRow = $this->getDatabase()->recipes()->where('id = :1', $recipeId)->fetch(); - if (!empty($recipeRow->product_id)) - { - $recipeResolvedRow = $this->getDatabase()->recipes_resolved()->where('recipe_id = :1', $recipeId)->fetch(); - $this->getStockService()->AddProduct($recipeRow->product_id, floatval($recipeRow->desired_servings), null, StockService::TRANSACTION_TYPE_SELF_PRODUCTION, date('Y-m-d'), floatval($recipeResolvedRow->costs)); - } + parent::__construct(); } private function RecipeExists($recipeId) @@ -87,4 +98,5 @@ class RecipesService extends BaseService $recipeRow = $this->getDataBase()->recipes()->where('id = :1', $recipeId)->fetch(); return $recipeRow !== null; } + } diff --git a/services/SessionService.php b/services/SessionService.php index a1b547aa..4e9b672b 100644 --- a/services/SessionService.php +++ b/services/SessionService.php @@ -4,6 +4,47 @@ namespace Grocy\Services; class SessionService extends BaseService { + /** + * @return string + */ + public function CreateSession($userId, $stayLoggedInPermanently = false) + { + $newSessionKey = $this->GenerateSessionKey(); + + $expires = date('Y-m-d H:i:s', intval(time() + 2592000)); + +// Default is that sessions expire in 30 days + if ($stayLoggedInPermanently === true) + { + $expires = date('Y-m-d H:i:s', PHP_INT_SIZE == 4 ? PHP_INT_MAX : PHP_INT_MAX >> 32); // Never + } + + $sessionRow = $this->getDatabase()->sessions()->createRow([ + 'user_id' => $userId, + 'session_key' => $newSessionKey, + 'expires' => $expires + ]); + $sessionRow->save(); + + return $newSessionKey; + } + + public function GetDefaultUser() + { + return $this->getDatabase()->users(1); + } + + public function GetUserBySessionKey($sessionKey) + { + $sessionRow = $this->getDatabase()->sessions()->where('session_key', $sessionKey)->fetch(); + + if ($sessionRow !== null) + { + return $this->getDatabase()->users($sessionRow->user_id); + } + + return null; + } /** * @return boolean @@ -17,14 +58,15 @@ class SessionService extends BaseService else { $sessionRow = $this->getDatabase()->sessions()->where('session_key = :1 AND expires > :2', $sessionKey, date('Y-m-d H:i:s', time()))->fetch(); + if ($sessionRow !== null) { - // This should not change the database file modification time as this is used +// This should not change the database file modification time as this is used // to determine if REALLY something has changed $dbModTime = $this->getDatabaseService()->GetDbChangedTime(); - $sessionRow->update(array( + $sessionRow->update([ 'last_used' => date('Y-m-d H:i:s', time()) - )); + ]); $this->getDatabaseService()->SetDbChangedTime($dbModTime); return true; @@ -33,30 +75,9 @@ class SessionService extends BaseService { return false; } - } - } - /** - * @return string - */ - public function CreateSession($userId, $stayLoggedInPermanently = false) - { - $newSessionKey = $this->GenerateSessionKey(); - - $expires = date('Y-m-d H:i:s', intval(time() + 2592000)); // Default is that sessions expire in 30 days - if ($stayLoggedInPermanently === true) - { - $expires = date('Y-m-d H:i:s', PHP_INT_SIZE == 4 ? PHP_INT_MAX : PHP_INT_MAX>>32); // Never } - $sessionRow = $this->getDatabase()->sessions()->createRow(array( - 'user_id' => $userId, - 'session_key' => $newSessionKey, - 'expires' => $expires - )); - $sessionRow->save(); - - return $newSessionKey; } public function RemoveSession($sessionKey) @@ -64,23 +85,9 @@ class SessionService extends BaseService $this->getDatabase()->sessions()->where('session_key', $sessionKey)->delete(); } - public function GetUserBySessionKey($sessionKey) - { - $sessionRow = $this->getDatabase()->sessions()->where('session_key', $sessionKey)->fetch(); - if ($sessionRow !== null) - { - return $this->getDatabase()->users($sessionRow->user_id); - } - return null; - } - - public function GetDefaultUser() - { - return $this->getDatabase()->users(1); - } - private function GenerateSessionKey() { return RandomString(50); } + } diff --git a/services/StockService.php b/services/StockService.php index f76b8ed3..6596638a 100644 --- a/services/StockService.php +++ b/services/StockService.php @@ -4,31 +4,425 @@ namespace Grocy\Services; class StockService extends BaseService { - const TRANSACTION_TYPE_PURCHASE = 'purchase'; const TRANSACTION_TYPE_CONSUME = 'consume'; - const TRANSACTION_TYPE_TRANSFER_FROM = 'transfer_from'; - const TRANSACTION_TYPE_TRANSFER_TO = 'transfer_to'; + const TRANSACTION_TYPE_INVENTORY_CORRECTION = 'inventory-correction'; - const TRANSACTION_TYPE_STOCK_EDIT_NEW = 'stock-edit-new'; - const TRANSACTION_TYPE_STOCK_EDIT_OLD = 'stock-edit-old'; + const TRANSACTION_TYPE_PRODUCT_OPENED = 'product-opened'; + + const TRANSACTION_TYPE_PURCHASE = 'purchase'; + const TRANSACTION_TYPE_SELF_PRODUCTION = 'self-production'; - public function GetCurrentStockOverview() + const TRANSACTION_TYPE_STOCK_EDIT_NEW = 'stock-edit-new'; + + const TRANSACTION_TYPE_STOCK_EDIT_OLD = 'stock-edit-old'; + + const TRANSACTION_TYPE_TRANSFER_FROM = 'transfer_from'; + + const TRANSACTION_TYPE_TRANSFER_TO = 'transfer_to'; + + public function AddMissingProductsToShoppingList($listId = 1) { - if (!GROCY_FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT) { - return $this->getDatabase()->uihelper_stock_current_overview(); - } else { - return $this->getDatabase()->uihelper_stock_current_overview_including_opened(); + if (!$this->ShoppingListExists($listId)) + { + throw new \Exception('Shopping list does not exist'); } + + $missingProducts = $this->GetMissingProducts(); + + foreach ($missingProducts as $missingProduct) + { + $product = $this->getDatabase()->products()->where('id', $missingProduct->id)->fetch(); + $amountToAdd = round($missingProduct->amount_missing / $product->qu_factor_purchase_to_stock, 2); + + $alreadyExistingEntry = $this->getDatabase()->shopping_list()->where('product_id', $missingProduct->id)->fetch(); + + if ($alreadyExistingEntry) // Update + { + if ($alreadyExistingEntry->amount < $amountToAdd) + { + $alreadyExistingEntry->update([ + 'amount' => $amountToAdd, + 'shopping_list_id' => $listId + ]); + } + + } + else // Insert + { + $shoppinglistRow = $this->getDatabase()->shopping_list()->createRow([ + 'product_id' => $missingProduct->id, + 'amount' => $amountToAdd, + 'shopping_list_id' => $listId + ]); + $shoppinglistRow->save(); + } + + } + + } + + public function AddProduct(int $productId, float $amount, $bestBeforeDate, $transactionType, $purchasedDate, $price, $quFactorPurchaseToStock, $locationId = null, $shoppingLocationId = null, &$transactionId = null) + { + if (!$this->ProductExists($productId)) + { + throw new \Exception('Product does not exist or is inactive'); + } + +// Tare weight handling + +// The given amount is the new total amount including the container weight (gross) + // The amount to be posted needs to be the given amount - stock amount - tare weight + $productDetails = (object) $this->GetProductDetails($productId); + + if ($productDetails->product->enable_tare_weight_handling == 1) + { + if ($amount <= floatval($productDetails->product->tare_weight) + floatval($productDetails->stock_amount)) + { + throw new \Exception('The amount cannot be lower or equal than the defined tare weight + current stock amount'); + } + + $amount = $amount - floatval($productDetails->stock_amount) - floatval($productDetails->product->tare_weight); + } + +//Sets the default best before date, if none is supplied + if ($bestBeforeDate == null) + { + if (intval($productDetails->product->default_best_before_days) == -1) + { + $bestBeforeDate = date('2999-12-31'); + } + else if (intval($productDetails->product->default_best_before_days) > 0) + { + $bestBeforeDate = date('Y-m-d', strtotime(date('Y-m-d') . ' + ' . $productDetails->product->default_best_before_days . ' days')); + } + else + { + $bestBeforeDate = date('Y-m-d'); + } + + } + + if ($transactionType === self::TRANSACTION_TYPE_PURCHASE || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION || $transactionType == self::TRANSACTION_TYPE_SELF_PRODUCTION) + { + if ($transactionId === null) + { + $transactionId = uniqid(); + } + + $stockId = uniqid(); + + $logRow = $this->getDatabase()->stock_log()->createRow([ + 'product_id' => $productId, + 'amount' => $amount, + 'best_before_date' => $bestBeforeDate, + 'purchased_date' => $purchasedDate, + 'stock_id' => $stockId, + 'transaction_type' => $transactionType, + 'price' => $price, + 'location_id' => $locationId, + 'transaction_id' => $transactionId, + 'shopping_location_id' => $shoppingLocationId, + 'qu_factor_purchase_to_stock' => $quFactorPurchaseToStock + ]); + $logRow->save(); + + $returnValue = $this->getDatabase()->lastInsertId(); + + $stockRow = $this->getDatabase()->stock()->createRow([ + 'product_id' => $productId, + 'amount' => $amount, + 'best_before_date' => $bestBeforeDate, + 'purchased_date' => $purchasedDate, + 'stock_id' => $stockId, + 'price' => $price, + 'location_id' => $locationId, + 'shopping_location_id' => $shoppingLocationId, + 'qu_factor_purchase_to_stock' => $quFactorPurchaseToStock + ]); + $stockRow->save(); + + return $returnValue; + } + else + { + throw new \Exception("Transaction type $transactionType is not valid (StockService.AddProduct)"); + } + + } + + public function AddProductToShoppingList($productId, $amount = 1, $note = null, $listId = 1) + { + if (!$this->ShoppingListExists($listId)) + { + throw new \Exception('Shopping list does not exist'); + } + + if (!$this->ProductExists($productId)) + { + throw new \Exception('Product does not exist or is inactive'); + } + + $alreadyExistingEntry = $this->getDatabase()->shopping_list()->where('product_id = :1 AND shopping_list_id = :2', $productId, $listId)->fetch(); + if ($alreadyExistingEntry) // Update + { + $alreadyExistingEntry->update([ + 'amount' => ($alreadyExistingEntry->amount + $amount), + 'shopping_list_id' => $listId, + 'note' => $note + ]); + } + else // Insert + { + $shoppinglistRow = $this->getDatabase()->shopping_list()->createRow([ + 'product_id' => $productId, + 'amount' => $amount, + 'shopping_list_id' => $listId, + 'note' => $note + ]); + $shoppinglistRow->save(); + } + + } + + public function ClearShoppingList($listId = 1) + { + if (!$this->ShoppingListExists($listId)) + { + throw new \Exception('Shopping list does not exist'); + } + + $this->getDatabase()->shopping_list()->where('shopping_list_id = :1', $listId)->delete(); + } + + public function ConsumeProduct(int $productId, float $amount, bool $spoiled, $transactionType, $specificStockEntryId = 'default', $recipeId = null, $locationId = null, &$transactionId = null, $allowSubproductSubstitution = false) + { + if (!$this->ProductExists($productId)) + { + throw new \Exception('Product does not exist or is inactive'); + } + + if ($locationId !== null && !$this->LocationExists($locationId)) + { + throw new \Exception('Location does not exist'); + } + +// Tare weight handling + +// The given amount is the new total amount including the container weight (gross) + // The amount to be posted needs to be the absolute value of the given amount - stock amount - tare weight + $productDetails = (object) $this->GetProductDetails($productId); + + if ($productDetails->product->enable_tare_weight_handling == 1) + { + if ($amount < floatval($productDetails->product->tare_weight)) + { + throw new \Exception('The amount cannot be lower than the defined tare weight'); + } + + $amount = abs($amount - floatval($productDetails->stock_amount) - floatval($productDetails->product->tare_weight)); + } + + if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION) + { + if ($locationId === null) // Consume from any location + { + $potentialStockEntries = $this->GetProductStockEntries($productId, false, $allowSubproductSubstitution); + } + else // Consume only from the supplied location + { + $potentialStockEntries = $this->GetProductStockEntriesForLocation($productId, $locationId, false, $allowSubproductSubstitution); + } + + $productStockAmount = SumArrayValue($potentialStockEntries, 'amount'); + + if ($amount > $productStockAmount) + { + throw new \Exception('Amount to be consumed cannot be > current stock amount (if supplied, at the desired location)'); + } + + if ($specificStockEntryId !== 'default') + { + $potentialStockEntries = FindAllObjectsInArrayByPropertyValue($potentialStockEntries, 'stock_id', $specificStockEntryId); + } + + if ($transactionId === null) + { + $transactionId = uniqid(); + } + + foreach ($potentialStockEntries as $stockEntry) + { + if ($amount == 0) + { + break; + } + + if ($amount >= $stockEntry->amount) // Take the whole stock entry + { + $logRow = $this->getDatabase()->stock_log()->createRow([ + 'product_id' => $stockEntry->product_id, + 'amount' => $stockEntry->amount * -1, + 'best_before_date' => $stockEntry->best_before_date, + 'purchased_date' => $stockEntry->purchased_date, + 'used_date' => date('Y-m-d'), + 'spoiled' => $spoiled, + 'stock_id' => $stockEntry->stock_id, + 'transaction_type' => $transactionType, + 'price' => $stockEntry->price, + 'opened_date' => $stockEntry->opened_date, + 'recipe_id' => $recipeId, + 'transaction_id' => $transactionId + ]); + $logRow->save(); + + $stockEntry->delete(); + + $amount -= $stockEntry->amount; + } + else // Stock entry amount is > than needed amount -> split the stock entry resp. update the amount + { + $restStockAmount = $stockEntry->amount - $amount; + + $logRow = $this->getDatabase()->stock_log()->createRow([ + 'product_id' => $stockEntry->product_id, + 'amount' => $amount * -1, + 'best_before_date' => $stockEntry->best_before_date, + 'purchased_date' => $stockEntry->purchased_date, + 'used_date' => date('Y-m-d'), + 'spoiled' => $spoiled, + 'stock_id' => $stockEntry->stock_id, + 'transaction_type' => $transactionType, + 'price' => $stockEntry->price, + 'opened_date' => $stockEntry->opened_date, + 'recipe_id' => $recipeId, + 'transaction_id' => $transactionId + ]); + $logRow->save(); + + $stockEntry->update([ + 'amount' => $restStockAmount + ]); + + $amount = 0; + } + + } + + return $this->getDatabase()->lastInsertId(); + } + else + { + throw new Exception("Transaction type $transactionType is not valid (StockService.ConsumeProduct)"); + } + + } + + public function EditStockEntry(int $stockRowId, float $amount, $bestBeforeDate, $locationId, $shoppingLocationId, $price, $open, $purchasedDate, $quFactorPurchaseToStock) + { + $stockRow = $this->getDatabase()->stock()->where('id = :1', $stockRowId)->fetch(); + + if ($stockRow === null) + { + throw new \Exception('Stock does not exist'); + } + + $correlationId = uniqid(); + $transactionId = uniqid(); + $logOldRowForStockUpdate = $this->getDatabase()->stock_log()->createRow([ + 'product_id' => $stockRow->product_id, + 'amount' => $stockRow->amount, + 'best_before_date' => $stockRow->best_before_date, + 'purchased_date' => $stockRow->purchased_date, + 'stock_id' => $stockRow->stock_id, + 'transaction_type' => self::TRANSACTION_TYPE_STOCK_EDIT_OLD, + 'price' => $stockRow->price, + 'opened_date' => $stockRow->opened_date, + 'location_id' => $stockRow->location_id, + 'shopping_location_id' => $stockRow->shopping_location_id, + 'qu_factor_purchase_to_stock' => $stockRow->qu_factor_purchase_to_stock, + 'correlation_id' => $correlationId, + 'transaction_id' => $transactionId, + 'stock_row_id' => $stockRow->id + ]); + $logOldRowForStockUpdate->save(); + + $openedDate = $stockRow->opened_date; + + if ($open && $openedDate == null) + { + $openedDate = date('Y-m-d'); + } + else + + if (!$open) + { + $openedDate = null; + } + + $stockRow->update([ + 'amount' => $amount, + 'price' => $price, + 'best_before_date' => $bestBeforeDate, + 'location_id' => $locationId, + 'shopping_location_id' => $shoppingLocationId, + 'opened_date' => $openedDate, + 'open' => $open, + 'qu_factor_purchase_to_stock' => $quFactorPurchaseToStock, + 'purchased_date' => $purchasedDate + ]); + + $logNewRowForStockUpdate = $this->getDatabase()->stock_log()->createRow([ + 'product_id' => $stockRow->product_id, + 'amount' => $amount, + 'best_before_date' => $bestBeforeDate, + 'purchased_date' => $stockRow->purchased_date, + 'stock_id' => $stockRow->stock_id, + 'transaction_type' => self::TRANSACTION_TYPE_STOCK_EDIT_NEW, + 'price' => $price, + 'opened_date' => $stockRow->opened_date, + 'location_id' => $locationId, + 'shopping_location_id' => $shoppingLocationId, + 'qu_factor_purchase_to_stock' => $stockRow->qu_factor_purchase_to_stock, + 'correlation_id' => $correlationId, + 'transaction_id' => $transactionId, + 'stock_row_id' => $stockRow->id + ]); + $logNewRowForStockUpdate->save(); + + return $this->getDatabase()->lastInsertId(); + } + + public function ExternalBarcodeLookup($barcode, $addFoundProduct) + { + $plugin = $this->LoadBarcodeLookupPlugin(); + $pluginOutput = $plugin->Lookup($barcode); + + if ($pluginOutput !== null) // Lookup was successful + { + if ($addFoundProduct === true) + { + // Add product to database and include new product id in output + $newRow = $this->getDatabase()->products()->createRow($pluginOutput); + $newRow->save(); + + $pluginOutput['id'] = $newRow->id; + } + + } + + return $pluginOutput; } public function GetCurrentStock($includeNotInStockButMissingProducts = false) { $sql = 'SELECT * FROM stock_current'; + if ($includeNotInStockButMissingProducts) { $missingProductsView = 'stock_missing_products_including_opened'; + if (!GROCY_FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT) { $missingProductsView = 'stock_missing_products'; @@ -36,9 +430,11 @@ class StockService extends BaseService $sql = 'SELECT * FROM stock_current WHERE best_before_date IS NOT NULL UNION SELECT id, 0, 0, 0, 0, null, 0, 0, 0 FROM ' . $missingProductsView . ' WHERE id NOT IN (SELECT product_id FROM stock_current)'; } - $currentStockMapped = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_GROUP|\PDO::FETCH_OBJ); + + $currentStockMapped = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_GROUP | \PDO::FETCH_OBJ); $relevantProducts = $this->getDatabase()->products()->where('id IN (SELECT product_id FROM (' . $sql . ') x)'); + foreach ($relevantProducts as $product) { $currentStockMapped[$product->id][0]->product_id = $product->id; @@ -60,32 +456,17 @@ class StockService extends BaseService return $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); } - public function GetMissingProducts() + public function GetCurrentStockOverview() { - $sql = 'SELECT * FROM stock_missing_products_including_opened'; if (!GROCY_FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT) { - $sql = 'SELECT * FROM stock_missing_products'; + return $this->getDatabase()->uihelper_stock_current_overview(); } - - return $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); - } - - public function GetProductStockLocations($productId) - { - return $this->getDatabase()->stock_current_locations()->where('product_id', $productId)->fetchAll(); - } - - public function GetProductIdFromBarcode(string $barcode) - { - $potentialProduct = $this->getDatabase()->product_barcodes()->where("barcode = :1", $barcode)->fetch(); - - if ($potentialProduct === null) + else { - throw new \Exception("No product with barcode $barcode found"); + return $this->getDatabase()->uihelper_stock_current_overview_including_opened(); } - return intval($potentialProduct->id); } public function GetExpiringProducts(int $days = 5, bool $excludeExpired = false) @@ -101,6 +482,18 @@ class StockService extends BaseService return $currentStock; } + public function GetMissingProducts() + { + $sql = 'SELECT * FROM stock_missing_products_including_opened'; + + if (!GROCY_FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT) + { + $sql = 'SELECT * FROM stock_missing_products'; + } + + return $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ); + } + public function GetProductDetails(int $productId) { if (!$this->ProductExists($productId)) @@ -140,13 +533,15 @@ class StockService extends BaseService $consumeCount = $this->getDatabase()->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->where('undone = 0 AND spoiled = 0')->sum('amount') * -1; $consumeCountSpoiled = $this->getDatabase()->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_CONSUME)->where('undone = 0 AND spoiled = 1')->sum('amount') * -1; + if ($consumeCount == 0) { $consumeCount = 1; } + $spoilRate = ($consumeCountSpoiled * 100) / $consumeCount; - return array( + return [ 'product' => $product, 'product_barcodes' => $productBarcodes, 'last_purchased' => $productLastPurchased->purchased_date, @@ -170,7 +565,19 @@ class StockService extends BaseService 'average_shelf_life_days' => $averageShelfLifeDays, 'spoil_rate_percent' => $spoilRate, 'is_aggregated_amount' => $stockCurrentRow->is_aggregated_amount - ); + ]; + } + + public function GetProductIdFromBarcode(string $barcode) + { + $potentialProduct = $this->getDatabase()->product_barcodes()->where('barcode = :1', $barcode)->fetch(); + + if ($potentialProduct === null) + { + throw new \Exception("No product with barcode $barcode found"); + } + + return intval($potentialProduct->id); } public function GetProductPriceHistory(int $productId) @@ -180,37 +587,36 @@ class StockService extends BaseService throw new \Exception('Product does not exist or is inactive'); } - $returnData = array(); + $returnData = []; $shoppingLocations = $this->getDatabase()->shopping_locations(); $rows = $this->getDatabase()->product_price_history()->where('product_id = :1', $productId)->orderBy('purchased_date', 'DESC'); + foreach ($rows as $row) { - $returnData[] = array( + $returnData[] = [ 'date' => $row->purchased_date, 'price' => $row->price, - 'shopping_location' => FindObjectInArrayByPropertyValue($shoppingLocations, 'id', $row->shopping_location_id), - ); + 'shopping_location' => FindObjectInArrayByPropertyValue($shoppingLocations, 'id', $row->shopping_location_id) + ]; } - return $returnData; - } - public function GetStockEntry($entryId) - { - return $this->getDatabase()->stock()->where('id', $entryId)->fetch(); + return $returnData; } public function GetProductStockEntries($productId, $excludeOpened = false, $allowSubproductSubstitution = false) { - // In order of next use: +// In order of next use: // First expiring first, then first in first out $sqlWhereProductId = 'product_id = :1'; + if ($allowSubproductSubstitution) { $sqlWhereProductId = '(product_id IN (SELECT sub_product_id FROM products_resolved WHERE parent_product_id = :1) OR product_id = :1)'; } $sqlWhereAndOpen = 'AND open IN (0, 1)'; + if ($excludeOpened) { $sqlWhereAndOpen = 'AND open = 0'; @@ -225,465 +631,14 @@ class StockService extends BaseService return FindAllObjectsInArrayByPropertyValue($stockEntries, 'location_id', $locationId); } - public function AddProduct(int $productId, float $amount, $bestBeforeDate, $transactionType, $purchasedDate, $price, $quFactorPurchaseToStock, $locationId = null, $shoppingLocationId = null, &$transactionId = null) + public function GetProductStockLocations($productId) { - if (!$this->ProductExists($productId)) - { - throw new \Exception('Product does not exist or is inactive'); - } - - // Tare weight handling - // The given amount is the new total amount including the container weight (gross) - // The amount to be posted needs to be the given amount - stock amount - tare weight - $productDetails = (object)$this->GetProductDetails($productId); - if ($productDetails->product->enable_tare_weight_handling == 1) - { - if ($amount <= floatval($productDetails->product->tare_weight) + floatval($productDetails->stock_amount)) - { - throw new \Exception('The amount cannot be lower or equal than the defined tare weight + current stock amount'); - } - - $amount = $amount - floatval($productDetails->stock_amount) - floatval($productDetails->product->tare_weight); - } - - //Sets the default best before date, if none is supplied - if ($bestBeforeDate == null) - { - if (intval($productDetails->product->default_best_before_days) == -1) - { - $bestBeforeDate = date('2999-12-31'); - } - else if (intval($productDetails->product->default_best_before_days) > 0) - { - $bestBeforeDate = date('Y-m-d', strtotime(date('Y-m-d') . ' + '.$productDetails->product->default_best_before_days.' days')); - } - else - { - $bestBeforeDate = date('Y-m-d'); - } - } - - if ($transactionType === self::TRANSACTION_TYPE_PURCHASE || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION || $transactionType == self::TRANSACTION_TYPE_SELF_PRODUCTION) - { - if ($transactionId === null) - { - $transactionId = uniqid(); - } - - $stockId = uniqid(); - - $logRow = $this->getDatabase()->stock_log()->createRow(array( - 'product_id' => $productId, - 'amount' => $amount, - 'best_before_date' => $bestBeforeDate, - 'purchased_date' => $purchasedDate, - 'stock_id' => $stockId, - 'transaction_type' => $transactionType, - 'price' => $price, - 'location_id' => $locationId, - 'transaction_id' => $transactionId, - 'shopping_location_id' => $shoppingLocationId, - 'qu_factor_purchase_to_stock' => $quFactorPurchaseToStock, - )); - $logRow->save(); - - $returnValue = $this->getDatabase()->lastInsertId(); - - $stockRow = $this->getDatabase()->stock()->createRow(array( - 'product_id' => $productId, - 'amount' => $amount, - 'best_before_date' => $bestBeforeDate, - 'purchased_date' => $purchasedDate, - 'stock_id' => $stockId, - 'price' => $price, - 'location_id' => $locationId, - 'shopping_location_id' => $shoppingLocationId, - 'qu_factor_purchase_to_stock' => $quFactorPurchaseToStock, - )); - $stockRow->save(); - - return $returnValue; - } - else - { - throw new \Exception("Transaction type $transactionType is not valid (StockService.AddProduct)"); - } + return $this->getDatabase()->stock_current_locations()->where('product_id', $productId)->fetchAll(); } - public function ConsumeProduct(int $productId, float $amount, bool $spoiled, $transactionType, $specificStockEntryId = 'default', $recipeId = null, $locationId = null, &$transactionId = null, $allowSubproductSubstitution = false) + public function GetStockEntry($entryId) { - if (!$this->ProductExists($productId)) - { - throw new \Exception('Product does not exist or is inactive'); - } - - if ($locationId !== null && !$this->LocationExists($locationId)) - { - throw new \Exception('Location does not exist'); - } - - // Tare weight handling - // The given amount is the new total amount including the container weight (gross) - // The amount to be posted needs to be the absolute value of the given amount - stock amount - tare weight - $productDetails = (object)$this->GetProductDetails($productId); - if ($productDetails->product->enable_tare_weight_handling == 1) - { - if ($amount < floatval($productDetails->product->tare_weight)) - { - throw new \Exception('The amount cannot be lower than the defined tare weight'); - } - - $amount = abs($amount - floatval($productDetails->stock_amount) - floatval($productDetails->product->tare_weight)); - } - - if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION) - { - - if ($locationId === null) // Consume from any location - { - $potentialStockEntries = $this->GetProductStockEntries($productId, false, $allowSubproductSubstitution); - } - else // Consume only from the supplied location - { - $potentialStockEntries = $this->GetProductStockEntriesForLocation($productId, $locationId, false, $allowSubproductSubstitution); - } - - $productStockAmount = SumArrayValue($potentialStockEntries, 'amount'); - if ($amount > $productStockAmount) - { - throw new \Exception('Amount to be consumed cannot be > current stock amount (if supplied, at the desired location)'); - } - - if ($specificStockEntryId !== 'default') - { - $potentialStockEntries = FindAllObjectsInArrayByPropertyValue($potentialStockEntries, 'stock_id', $specificStockEntryId); - } - - if ($transactionId === null) - { - $transactionId = uniqid(); - } - - foreach ($potentialStockEntries as $stockEntry) - { - if ($amount == 0) - { - break; - } - - if ($amount >= $stockEntry->amount) // Take the whole stock entry - { - $logRow = $this->getDatabase()->stock_log()->createRow(array( - 'product_id' => $stockEntry->product_id, - 'amount' => $stockEntry->amount * -1, - 'best_before_date' => $stockEntry->best_before_date, - 'purchased_date' => $stockEntry->purchased_date, - 'used_date' => date('Y-m-d'), - 'spoiled' => $spoiled, - 'stock_id' => $stockEntry->stock_id, - 'transaction_type' => $transactionType, - 'price' => $stockEntry->price, - 'opened_date' => $stockEntry->opened_date, - 'recipe_id' => $recipeId, - 'transaction_id' => $transactionId - )); - $logRow->save(); - - $stockEntry->delete(); - - $amount -= $stockEntry->amount; - } - else // Stock entry amount is > than needed amount -> split the stock entry resp. update the amount - { - $restStockAmount = $stockEntry->amount - $amount; - - $logRow = $this->getDatabase()->stock_log()->createRow(array( - 'product_id' => $stockEntry->product_id, - 'amount' => $amount * -1, - 'best_before_date' => $stockEntry->best_before_date, - 'purchased_date' => $stockEntry->purchased_date, - 'used_date' => date('Y-m-d'), - 'spoiled' => $spoiled, - 'stock_id' => $stockEntry->stock_id, - 'transaction_type' => $transactionType, - 'price' => $stockEntry->price, - 'opened_date' => $stockEntry->opened_date, - 'recipe_id' => $recipeId, - 'transaction_id' => $transactionId - )); - $logRow->save(); - - $stockEntry->update(array( - 'amount' => $restStockAmount - )); - - $amount = 0; - } - } - - return $this->getDatabase()->lastInsertId(); - } - else - { - throw new Exception("Transaction type $transactionType is not valid (StockService.ConsumeProduct)"); - } - } - - public function TransferProduct(int $productId, float $amount, int $locationIdFrom, int $locationIdTo, $specificStockEntryId = 'default', &$transactionId = null) - { - if (!$this->ProductExists($productId)) - { - throw new \Exception('Product does not exist or is inactive'); - } - - if (!$this->LocationExists($locationIdFrom)) - { - throw new \Exception('Source location does not exist'); - } - - if (!$this->LocationExists($locationIdTo)) - { - throw new \Exception('Destination location does not exist'); - } - - // Tare weight handling - // The given amount is the new total amount including the container weight (gross) - // The amount to be posted needs to be the absolute value of the given amount - stock amount - tare weight - $productDetails = (object)$this->GetProductDetails($productId); - if ($productDetails->product->enable_tare_weight_handling == 1) - { - // Hard fail for now, as we not yet support transfering tare weight enabled products - throw new \Exception('Transfering tare weight enabled products is not yet possible'); - - if ($amount < floatval($productDetails->product->tare_weight)) - { - throw new \Exception('The amount cannot be lower than the defined tare weight'); - } - - $amount = abs($amount - floatval($productDetails->stock_amount) - floatval($productDetails->product->tare_weight)); - } - - $productStockAmountAtFromLocation = $this->getDatabase()->stock()->where('product_id = :1 AND location_id = :2', $productId, $locationIdFrom)->sum('amount'); - $potentialStockEntriesAtFromLocation = $this->GetProductStockEntriesForLocation($productId, $locationIdFrom); - - if ($amount > $productStockAmountAtFromLocation) - { - throw new \Exception('Amount to be transfered cannot be > current stock amount at the source location'); - } - - if ($specificStockEntryId !== 'default') - { - $potentialStockEntriesAtFromLocation = FindAllObjectsInArrayByPropertyValue($potentialStockEntriesAtFromLocation, 'stock_id', $specificStockEntryId); - } - - if ($transactionId === null) - { - $transactionId = uniqid(); - } - - foreach ($potentialStockEntriesAtFromLocation as $stockEntry) - { - if ($amount == 0) - { - break; - } - - $newBestBeforeDate = $stockEntry->best_before_date; - - if (GROCY_FEATURE_FLAG_STOCK_PRODUCT_FREEZING) - { - $locationFrom = $this->getDatabase()->locations()->where('id', $locationIdFrom)->fetch(); - $locationTo = $this->getDatabase()->locations()->where('id', $locationIdTo)->fetch(); - - // Product was moved from a non-freezer to freezer location -> freeze - if (intval($locationFrom->is_freezer) === 0 && intval($locationTo->is_freezer) === 1 && $productDetails->product->default_best_before_days_after_freezing > 0) - { - $newBestBeforeDate = date("Y-m-d", strtotime('+' . $productDetails->product->default_best_before_days_after_freezing . ' days')); - } - - // Product was moved from a freezer to non-freezer location -> thaw - if (intval($locationFrom->is_freezer) === 1 && intval($locationTo->is_freezer) === 0 && $productDetails->product->default_best_before_days_after_thawing > 0) - { - $newBestBeforeDate = date("Y-m-d", strtotime('+' . $productDetails->product->default_best_before_days_after_thawing . ' days')); - } - } - - $correlationId = uniqid(); - if ($amount >= $stockEntry->amount) // Take the whole stock entry - { - $logRowForLocationFrom = $this->getDatabase()->stock_log()->createRow(array( - 'product_id' => $stockEntry->product_id, - 'amount' => $stockEntry->amount * -1, - 'best_before_date' => $stockEntry->best_before_date, - 'purchased_date' => $stockEntry->purchased_date, - 'stock_id' => $stockEntry->stock_id, - 'transaction_type' => self::TRANSACTION_TYPE_TRANSFER_FROM, - 'price' => $stockEntry->price, - 'qu_factor_purchase_to_stock' => $stockEntry->qu_factor_purchase_to_stock, - 'opened_date' => $stockEntry->opened_date, - 'location_id' => $stockEntry->location_id, - 'correlation_id' => $correlationId, - 'transaction_Id' => $transactionId - )); - $logRowForLocationFrom->save(); - - $logRowForLocationTo = $this->getDatabase()->stock_log()->createRow(array( - 'product_id' => $stockEntry->product_id, - 'amount' => $stockEntry->amount, - 'best_before_date' => $newBestBeforeDate, - 'purchased_date' => $stockEntry->purchased_date, - 'stock_id' => $stockEntry->stock_id, - 'transaction_type' => self::TRANSACTION_TYPE_TRANSFER_TO, - 'price' => $stockEntry->price, - 'qu_factor_purchase_to_stock' => $stockEntry->qu_factor_purchase_to_stock, - 'opened_date' => $stockEntry->opened_date, - 'location_id' => $locationIdTo, - 'correlation_id' => $correlationId, - 'transaction_Id' => $transactionId - )); - $logRowForLocationTo->save(); - - $stockEntry->update(array( - 'location_id' => $locationIdTo, - 'best_before_date' => $newBestBeforeDate - )); - - $amount -= $stockEntry->amount; - } - else // Stock entry amount is > than needed amount -> split the stock entry resp. update the amount - { - $restStockAmount = $stockEntry->amount - $amount; - - $logRowForLocationFrom = $this->getDatabase()->stock_log()->createRow(array( - 'product_id' => $stockEntry->product_id, - 'amount' => $amount * -1, - 'best_before_date' => $stockEntry->best_before_date, - 'purchased_date' => $stockEntry->purchased_date, - 'stock_id' => $stockEntry->stock_id, - 'transaction_type' => self::TRANSACTION_TYPE_TRANSFER_FROM, - 'price' => $stockEntry->price, - 'qu_factor_purchase_to_stock' => $stockEntry->qu_factor_purchase_to_stock, - 'opened_date' => $stockEntry->opened_date, - 'location_id' => $stockEntry->location_id, - 'correlation_id' => $correlationId, - 'transaction_Id' => $transactionId - )); - $logRowForLocationFrom->save(); - - $logRowForLocationTo = $this->getDatabase()->stock_log()->createRow(array( - 'product_id' => $stockEntry->product_id, - 'amount' => $amount, - 'best_before_date' => $newBestBeforeDate, - 'purchased_date' => $stockEntry->purchased_date, - 'stock_id' => $stockEntry->stock_id, - 'transaction_type' => self::TRANSACTION_TYPE_TRANSFER_TO, - 'price' => $stockEntry->price, - 'qu_factor_purchase_to_stock' => $stockEntry->qu_factor_purchase_to_stock, - 'opened_date' => $stockEntry->opened_date, - 'location_id' => $locationIdTo, - 'correlation_id' => $correlationId, - 'transaction_Id' => $transactionId - )); - $logRowForLocationTo->save(); - - // This is the existing stock entry -> remains at the source location with the rest amount - $stockEntry->update(array( - 'amount' => $restStockAmount - )); - - // The transfered amount gets into a new stock entry - $stockEntryNew = $this->getDatabase()->stock()->createRow(array( - 'product_id' => $stockEntry->product_id, - 'amount' => $amount, - 'best_before_date' => $newBestBeforeDate, - 'purchased_date' => $stockEntry->purchased_date, - 'stock_id' => $stockEntry->stock_id, - 'qu_factor_purchase_to_stock' => $stockEntry->qu_factor_purchase_to_stock, - 'price' => $stockEntry->price, - 'location_id' => $locationIdTo, - 'open' => $stockEntry->open, - 'opened_date' => $stockEntry->opened_date - )); - $stockEntryNew->save(); - - $amount = 0; - } - } - - return $this->getDatabase()->lastInsertId(); - } - - public function EditStockEntry(int $stockRowId, float $amount, $bestBeforeDate, $locationId, $shoppingLocationId, $price, $open, $purchasedDate, $quFactorPurchaseToStock) - { - - $stockRow = $this->getDatabase()->stock()->where('id = :1', $stockRowId)->fetch(); - - if ($stockRow === null) - { - throw new \Exception('Stock does not exist'); - } - - $correlationId = uniqid(); - $transactionId = uniqid(); - $logOldRowForStockUpdate = $this->getDatabase()->stock_log()->createRow(array( - 'product_id' => $stockRow->product_id, - 'amount' => $stockRow->amount, - 'best_before_date' => $stockRow->best_before_date, - 'purchased_date' => $stockRow->purchased_date, - 'stock_id' => $stockRow->stock_id, - 'transaction_type' => self::TRANSACTION_TYPE_STOCK_EDIT_OLD, - 'price' => $stockRow->price, - 'opened_date' => $stockRow->opened_date, - 'location_id' => $stockRow->location_id, - 'shopping_location_id' => $stockRow->shopping_location_id, - 'qu_factor_purchase_to_stock' => $stockRow->qu_factor_purchase_to_stock, - 'correlation_id' => $correlationId, - 'transaction_id' => $transactionId, - 'stock_row_id' => $stockRow->id - )); - $logOldRowForStockUpdate->save(); - - $openedDate = $stockRow->opened_date; - if ($open && $openedDate == null) - { - $openedDate = date('Y-m-d'); - } - else if (!$open) - { - $openedDate = null; - } - - $stockRow->update(array( - 'amount' => $amount, - 'price' => $price, - 'best_before_date' => $bestBeforeDate, - 'location_id' => $locationId, - 'shopping_location_id' => $shoppingLocationId, - 'opened_date' => $openedDate, - 'open' => $open, - 'qu_factor_purchase_to_stock' => $quFactorPurchaseToStock, - 'purchased_date' => $purchasedDate - )); - - $logNewRowForStockUpdate = $this->getDatabase()->stock_log()->createRow(array( - 'product_id' => $stockRow->product_id, - 'amount' => $amount, - 'best_before_date' => $bestBeforeDate, - 'purchased_date' => $stockRow->purchased_date, - 'stock_id' => $stockRow->stock_id, - 'transaction_type' => self::TRANSACTION_TYPE_STOCK_EDIT_NEW, - 'price' => $price, - 'opened_date' => $stockRow->opened_date, - 'location_id' => $locationId, - 'shopping_location_id' => $shoppingLocationId, - 'qu_factor_purchase_to_stock' => $stockRow->qu_factor_purchase_to_stock, - 'correlation_id' => $correlationId, - 'transaction_id' => $transactionId, - 'stock_row_id' => $stockRow->id - )); - $logNewRowForStockUpdate->save(); - - return $this->getDatabase()->lastInsertId(); + return $this->getDatabase()->stock()->where('id', $entryId)->fetch(); } public function InventoryProduct(int $productId, float $newAmount, $bestBeforeDate, $locationId = null, $price = null, $shoppingLocationId = null) @@ -693,7 +648,7 @@ class StockService extends BaseService throw new \Exception('Product does not exist or is inactive'); } - $productDetails = (object)$this->GetProductDetails($productId); + $productDetails = (object) $this->GetProductDetails($productId); if ($price === null) { @@ -705,10 +660,12 @@ class StockService extends BaseService $shoppingLocationId = $productDetails->last_shopping_location_id; } - // Tare weight handling - // The given amount is the new total amount including the container weight (gross) +// Tare weight handling + +// The given amount is the new total amount including the container weight (gross) // So assume that the amount in stock is the amount also including the container weight $containerWeight = 0; + if ($productDetails->product->enable_tare_weight_handling == 1) { $containerWeight = floatval($productDetails->product->tare_weight); @@ -718,9 +675,12 @@ class StockService extends BaseService { throw new \Exception('The new amount cannot equal the current stock amount'); } - else if ($newAmount > floatval($productDetails->stock_amount) + $containerWeight) + else + + if ($newAmount > floatval($productDetails->stock_amount) + $containerWeight) { $bookingAmount = $newAmount - floatval($productDetails->stock_amount); + if ($productDetails->product->enable_tare_weight_handling == 1) { $bookingAmount = $newAmount; @@ -728,9 +688,12 @@ class StockService extends BaseService return $this->AddProduct($productId, $bookingAmount, $bestBeforeDate, self::TRANSACTION_TYPE_INVENTORY_CORRECTION, date('Y-m-d'), $price, $locationId, $shoppingLocationId); } - else if ($newAmount < $productDetails->stock_amount + $containerWeight) + else + + if ($newAmount < $productDetails->stock_amount + $containerWeight) { $bookingAmount = $productDetails->stock_amount - $newAmount; + if ($productDetails->product->enable_tare_weight_handling == 1) { $bookingAmount = $newAmount; @@ -776,14 +739,15 @@ class StockService extends BaseService } $newBestBeforeDate = $stockEntry->best_before_date; + if ($product->default_best_before_days_after_open > 0) { - $newBestBeforeDate = date("Y-m-d", strtotime('+' . $product->default_best_before_days_after_open . ' days')); + $newBestBeforeDate = date('Y-m-d', strtotime('+' . $product->default_best_before_days_after_open . ' days')); } if ($amount >= $stockEntry->amount) // Mark the whole stock entry as opened { - $logRow = $this->getDatabase()->stock_log()->createRow(array( + $logRow = $this->getDatabase()->stock_log()->createRow([ 'product_id' => $stockEntry->product_id, 'amount' => $stockEntry->amount, 'best_before_date' => $stockEntry->best_before_date, @@ -795,14 +759,14 @@ class StockService extends BaseService 'price' => $stockEntry->price, 'opened_date' => date('Y-m-d'), 'transaction_id' => $transactionId - )); + ]); $logRow->save(); - $stockEntry->update(array( + $stockEntry->update([ 'open' => 1, 'opened_date' => date('Y-m-d'), 'best_before_date' => $newBestBeforeDate - )); + ]); $amount -= $stockEntry->amount; } @@ -810,7 +774,7 @@ class StockService extends BaseService { $restStockAmount = $stockEntry->amount - $amount; - $newStockRow = $this->getDatabase()->stock()->createRow(array( + $newStockRow = $this->getDatabase()->stock()->createRow([ 'product_id' => $stockEntry->product_id, 'amount' => $restStockAmount, 'best_before_date' => $stockEntry->best_before_date, @@ -819,10 +783,10 @@ class StockService extends BaseService 'shopping_location_id' => $stockEntry->shopping_location_id, 'stock_id' => $stockEntry->stock_id, 'price' => $stockEntry->price - )); + ]); $newStockRow->save(); - $logRow = $this->getDatabase()->stock_log()->createRow(array( + $logRow = $this->getDatabase()->stock_log()->createRow([ 'product_id' => $stockEntry->product_id, 'amount' => $amount, 'best_before_date' => $stockEntry->best_before_date, @@ -834,70 +798,24 @@ class StockService extends BaseService 'price' => $stockEntry->price, 'opened_date' => date('Y-m-d'), 'transaction_id' => $transactionId - )); + ]); $logRow->save(); - $stockEntry->update(array( + $stockEntry->update([ 'amount' => $amount, 'open' => 1, 'opened_date' => date('Y-m-d'), 'best_before_date' => $newBestBeforeDate - )); + ]); $amount = 0; } + } return $this->getDatabase()->lastInsertId(); } - public function AddMissingProductsToShoppingList($listId = 1) - { - if (!$this->ShoppingListExists($listId)) - { - throw new \Exception('Shopping list does not exist'); - } - - $missingProducts = $this->GetMissingProducts(); - foreach ($missingProducts as $missingProduct) - { - $product = $this->getDatabase()->products()->where('id', $missingProduct->id)->fetch(); - $amountToAdd = round($missingProduct->amount_missing / $product->qu_factor_purchase_to_stock, 2); - - $alreadyExistingEntry = $this->getDatabase()->shopping_list()->where('product_id', $missingProduct->id)->fetch(); - if ($alreadyExistingEntry) // Update - { - if ($alreadyExistingEntry->amount < $amountToAdd) - { - $alreadyExistingEntry->update(array( - 'amount' => $amountToAdd, - 'shopping_list_id' => $listId - )); - } - } - else // Insert - { - $shoppinglistRow = $this->getDatabase()->shopping_list()->createRow(array( - 'product_id' => $missingProduct->id, - 'amount' => $amountToAdd, - 'shopping_list_id' => $listId - )); - $shoppinglistRow->save(); - } - } - } - - public function ClearShoppingList($listId = 1) - { - if (!$this->ShoppingListExists($listId)) - { - throw new \Exception('Shopping list does not exist'); - } - - $this->getDatabase()->shopping_list()->where('shopping_list_id = :1', $listId)->delete(); - } - - public function RemoveProductFromShoppingList($productId, $amount = 1, $listId = 1) { if (!$this->ShoppingListExists($listId)) @@ -907,7 +825,7 @@ class StockService extends BaseService $productRow = $this->getDatabase()->shopping_list()->where('product_id = :1', $productId)->fetch(); - //If no entry was found with for this product, we return gracefully +//If no entry was found with for this product, we return gracefully if ($productRow != null && !empty($productRow)) { $newAmount = $productRow->amount - $amount; @@ -917,112 +835,211 @@ class StockService extends BaseService } else { - $productRow->update(array('amount' => $newAmount)); + $productRow->update(['amount' => $newAmount]); } } + } - public function AddProductToShoppingList($productId, $amount = 1, $note = null, $listId = 1) + public function TransferProduct(int $productId, float $amount, int $locationIdFrom, int $locationIdTo, $specificStockEntryId = 'default', &$transactionId = null) { - if (!$this->ShoppingListExists($listId)) - { - throw new \Exception('Shopping list does not exist'); - } - if (!$this->ProductExists($productId)) { throw new \Exception('Product does not exist or is inactive'); } - $alreadyExistingEntry = $this->getDatabase()->shopping_list()->where('product_id = :1 AND shopping_list_id = :2', $productId, $listId)->fetch(); - if ($alreadyExistingEntry) // Update + if (!$this->LocationExists($locationIdFrom)) { - $alreadyExistingEntry->update(array( - 'amount' => ($alreadyExistingEntry->amount + $amount), - 'shopping_list_id' => $listId, - 'note' => $note - )); - } - else // Insert - { - $shoppinglistRow = $this->getDatabase()->shopping_list()->createRow(array( - 'product_id' => $productId, - 'amount' => $amount, - 'shopping_list_id' => $listId, - 'note' => $note - )); - $shoppinglistRow->save(); - } - } - - private function ProductExists($productId) - { - $productRow = $this->getDatabase()->products()->where('id = :1 and active = 1', $productId)->fetch(); - return $productRow !== null; - } - - private function LocationExists($locationId) - { - $locationRow = $this->getDatabase()->locations()->where('id = :1', $locationId)->fetch(); - return $locationRow !== null; - } - - private function ShoppingListExists($listId) - { - $shoppingListRow = $this->getDatabase()->shopping_lists()->where('id = :1', $listId)->fetch(); - return $shoppingListRow !== null; - } - - private function LoadBarcodeLookupPlugin() - { - $pluginName = defined('GROCY_STOCK_BARCODE_LOOKUP_PLUGIN') ? GROCY_STOCK_BARCODE_LOOKUP_PLUGIN : ''; - if (empty($pluginName)) - { - throw new \Exception('No barcode lookup plugin defined'); + throw new \Exception('Source location does not exist'); } - $path = GROCY_DATAPATH . "/plugins/$pluginName.php"; - if (file_exists($path)) + if (!$this->LocationExists($locationIdTo)) { - require_once $path; - return new $pluginName($this->getDatabase()->locations()->fetchAll(), $this->getDatabase()->quantity_units()->fetchAll()); + throw new \Exception('Destination location does not exist'); } - else - { - throw new \Exception("Plugin $pluginName was not found"); - } - } - public function ExternalBarcodeLookup($barcode, $addFoundProduct) - { - $plugin = $this->LoadBarcodeLookupPlugin(); - $pluginOutput = $plugin->Lookup($barcode); +// Tare weight handling - if ($pluginOutput !== null) // Lookup was successful +// The given amount is the new total amount including the container weight (gross) + // The amount to be posted needs to be the absolute value of the given amount - stock amount - tare weight + $productDetails = (object) $this->GetProductDetails($productId); + + if ($productDetails->product->enable_tare_weight_handling == 1) { - if ($addFoundProduct === true) + // Hard fail for now, as we not yet support transfering tare weight enabled products + throw new \Exception('Transfering tare weight enabled products is not yet possible'); + + if ($amount < floatval($productDetails->product->tare_weight)) { - // Add product to database and include new product id in output - $newRow = $this->getDatabase()->products()->createRow($pluginOutput); - $newRow->save(); - - $pluginOutput['id'] = $newRow->id; + throw new \Exception('The amount cannot be lower than the defined tare weight'); } + + $amount = abs($amount - floatval($productDetails->stock_amount) - floatval($productDetails->product->tare_weight)); } - return $pluginOutput; + $productStockAmountAtFromLocation = $this->getDatabase()->stock()->where('product_id = :1 AND location_id = :2', $productId, $locationIdFrom)->sum('amount'); + $potentialStockEntriesAtFromLocation = $this->GetProductStockEntriesForLocation($productId, $locationIdFrom); + + if ($amount > $productStockAmountAtFromLocation) + { + throw new \Exception('Amount to be transfered cannot be > current stock amount at the source location'); + } + + if ($specificStockEntryId !== 'default') + { + $potentialStockEntriesAtFromLocation = FindAllObjectsInArrayByPropertyValue($potentialStockEntriesAtFromLocation, 'stock_id', $specificStockEntryId); + } + + if ($transactionId === null) + { + $transactionId = uniqid(); + } + + foreach ($potentialStockEntriesAtFromLocation as $stockEntry) + { + if ($amount == 0) + { + break; + } + + $newBestBeforeDate = $stockEntry->best_before_date; + + if (GROCY_FEATURE_FLAG_STOCK_PRODUCT_FREEZING) + { + $locationFrom = $this->getDatabase()->locations()->where('id', $locationIdFrom)->fetch(); + $locationTo = $this->getDatabase()->locations()->where('id', $locationIdTo)->fetch(); + +// Product was moved from a non-freezer to freezer location -> freeze + if (intval($locationFrom->is_freezer) === 0 && intval($locationTo->is_freezer) === 1 && $productDetails->product->default_best_before_days_after_freezing > 0) + { + $newBestBeforeDate = date('Y-m-d', strtotime('+' . $productDetails->product->default_best_before_days_after_freezing . ' days')); + } + +// Product was moved from a freezer to non-freezer location -> thaw + if (intval($locationFrom->is_freezer) === 1 && intval($locationTo->is_freezer) === 0 && $productDetails->product->default_best_before_days_after_thawing > 0) + { + $newBestBeforeDate = date('Y-m-d', strtotime('+' . $productDetails->product->default_best_before_days_after_thawing . ' days')); + } + + } + + $correlationId = uniqid(); + if ($amount >= $stockEntry->amount) // Take the whole stock entry + { + $logRowForLocationFrom = $this->getDatabase()->stock_log()->createRow([ + 'product_id' => $stockEntry->product_id, + 'amount' => $stockEntry->amount * -1, + 'best_before_date' => $stockEntry->best_before_date, + 'purchased_date' => $stockEntry->purchased_date, + 'stock_id' => $stockEntry->stock_id, + 'transaction_type' => self::TRANSACTION_TYPE_TRANSFER_FROM, + 'price' => $stockEntry->price, + 'qu_factor_purchase_to_stock' => $stockEntry->qu_factor_purchase_to_stock, + 'opened_date' => $stockEntry->opened_date, + 'location_id' => $stockEntry->location_id, + 'correlation_id' => $correlationId, + 'transaction_Id' => $transactionId + ]); + $logRowForLocationFrom->save(); + + $logRowForLocationTo = $this->getDatabase()->stock_log()->createRow([ + 'product_id' => $stockEntry->product_id, + 'amount' => $stockEntry->amount, + 'best_before_date' => $newBestBeforeDate, + 'purchased_date' => $stockEntry->purchased_date, + 'stock_id' => $stockEntry->stock_id, + 'transaction_type' => self::TRANSACTION_TYPE_TRANSFER_TO, + 'price' => $stockEntry->price, + 'qu_factor_purchase_to_stock' => $stockEntry->qu_factor_purchase_to_stock, + 'opened_date' => $stockEntry->opened_date, + 'location_id' => $locationIdTo, + 'correlation_id' => $correlationId, + 'transaction_Id' => $transactionId + ]); + $logRowForLocationTo->save(); + + $stockEntry->update([ + 'location_id' => $locationIdTo, + 'best_before_date' => $newBestBeforeDate + ]); + + $amount -= $stockEntry->amount; + } + else // Stock entry amount is > than needed amount -> split the stock entry resp. update the amount + { + $restStockAmount = $stockEntry->amount - $amount; + + $logRowForLocationFrom = $this->getDatabase()->stock_log()->createRow([ + 'product_id' => $stockEntry->product_id, + 'amount' => $amount * -1, + 'best_before_date' => $stockEntry->best_before_date, + 'purchased_date' => $stockEntry->purchased_date, + 'stock_id' => $stockEntry->stock_id, + 'transaction_type' => self::TRANSACTION_TYPE_TRANSFER_FROM, + 'price' => $stockEntry->price, + 'qu_factor_purchase_to_stock' => $stockEntry->qu_factor_purchase_to_stock, + 'opened_date' => $stockEntry->opened_date, + 'location_id' => $stockEntry->location_id, + 'correlation_id' => $correlationId, + 'transaction_Id' => $transactionId + ]); + $logRowForLocationFrom->save(); + + $logRowForLocationTo = $this->getDatabase()->stock_log()->createRow([ + 'product_id' => $stockEntry->product_id, + 'amount' => $amount, + 'best_before_date' => $newBestBeforeDate, + 'purchased_date' => $stockEntry->purchased_date, + 'stock_id' => $stockEntry->stock_id, + 'transaction_type' => self::TRANSACTION_TYPE_TRANSFER_TO, + 'price' => $stockEntry->price, + 'qu_factor_purchase_to_stock' => $stockEntry->qu_factor_purchase_to_stock, + 'opened_date' => $stockEntry->opened_date, + 'location_id' => $locationIdTo, + 'correlation_id' => $correlationId, + 'transaction_Id' => $transactionId + ]); + $logRowForLocationTo->save(); + + // This is the existing stock entry -> remains at the source location with the rest amount + $stockEntry->update([ + 'amount' => $restStockAmount + ]); + + // The transfered amount gets into a new stock entry + $stockEntryNew = $this->getDatabase()->stock()->createRow([ + 'product_id' => $stockEntry->product_id, + 'amount' => $amount, + 'best_before_date' => $newBestBeforeDate, + 'purchased_date' => $stockEntry->purchased_date, + 'stock_id' => $stockEntry->stock_id, + 'qu_factor_purchase_to_stock' => $stockEntry->qu_factor_purchase_to_stock, + 'price' => $stockEntry->price, + 'location_id' => $locationIdTo, + 'open' => $stockEntry->open, + 'opened_date' => $stockEntry->opened_date + ]); + $stockEntryNew->save(); + + $amount = 0; + } + + } + + return $this->getDatabase()->lastInsertId(); } public function UndoBooking($bookingId, $skipCorrelatedBookings = false) { $logRow = $this->getDatabase()->stock_log()->where('id = :1 AND undone = 0', $bookingId)->fetch(); + if ($logRow == null) { throw new \Exception('Booking does not exist or was already undone'); } - // Undo all correlated bookings first, in order from newest first to the oldest +// Undo all correlated bookings first, in order from newest first to the oldest if (!$skipCorrelatedBookings && !empty($logRow->correlation_id)) { $correlatedBookings = $this->getDatabase()->stock_log()->where('undone = 0 AND correlation_id = :1', $logRow->correlation_id)->orderBy('id', 'DESC')->fetchAll(); @@ -1030,6 +1047,7 @@ class StockService extends BaseService { $this->UndoBooking($correlatedBooking->id, true); } + return; } @@ -1046,15 +1064,15 @@ class StockService extends BaseService $stockRows->delete(); // Update log entry - $logRow->update(array( + $logRow->update([ 'undone' => 1, 'undone_timestamp' => date('Y-m-d H:i:s') - )); + ]); } elseif ($logRow->transaction_type === self::TRANSACTION_TYPE_CONSUME || ($logRow->transaction_type === self::TRANSACTION_TYPE_INVENTORY_CORRECTION && $logRow->amount < 0)) { // Add corresponding amount back to stock - $stockRow = $this->getDatabase()->stock()->createRow(array( + $stockRow = $this->getDatabase()->stock()->createRow([ 'product_id' => $logRow->product_id, 'amount' => $logRow->amount * -1, 'best_before_date' => $logRow->best_before_date, @@ -1062,48 +1080,53 @@ class StockService extends BaseService 'stock_id' => $logRow->stock_id, 'price' => $logRow->price, 'opened_date' => $logRow->opened_date - )); + ]); $stockRow->save(); // Update log entry - $logRow->update(array( + $logRow->update([ 'undone' => 1, 'undone_timestamp' => date('Y-m-d H:i:s') - )); + ]); } elseif ($logRow->transaction_type === self::TRANSACTION_TYPE_TRANSFER_TO) { $stockRow = $this->getDatabase()->stock()->where('stock_id = :1 AND location_id = :2', $logRow->stock_id, $logRow->location_id)->fetch(); + if ($stockRow === null) { throw new \Exception('Booking does not exist or was already undone'); } + $newAmount = $stockRow->amount - $logRow->amount; if ($newAmount == 0) { $stockRow->delete(); - } else { - // Remove corresponding amount back to stock - $stockRow->update(array( + } + else + { + // Remove corresponding amount back to stock + $stockRow->update([ 'amount' => $newAmount - )); + ]); } // Update log entry - $logRow->update(array( + $logRow->update([ 'undone' => 1, 'undone_timestamp' => date('Y-m-d H:i:s') - )); + ]); } elseif ($logRow->transaction_type === self::TRANSACTION_TYPE_TRANSFER_FROM) { - // Add corresponding amount back to stock or +// Add corresponding amount back to stock or // create a row if missing $stockRow = $this->getDatabase()->stock()->where('stock_id = :1 AND location_id = :2', $logRow->stock_id, $logRow->location_id)->fetch(); + if ($stockRow === null) { - $stockRow = $this->getDatabase()->stock()->createRow(array( + $stockRow = $this->getDatabase()->stock()->createRow([ 'product_id' => $logRow->product_id, 'amount' => $logRow->amount * -1, 'best_before_date' => $logRow->best_before_date, @@ -1111,47 +1134,50 @@ class StockService extends BaseService 'stock_id' => $logRow->stock_id, 'price' => $logRow->price, 'opened_date' => $logRow->opened_date - )); + ]); $stockRow->save(); - } else { - $stockRow->update(array( - 'amount' => $stockRow->amount - $logRow->amount - )); + } + else + { + $stockRow->update([ + 'amount' => $stockRow->amount - $logRow->amount + ]); } // Update log entry - $logRow->update(array( + $logRow->update([ 'undone' => 1, 'undone_timestamp' => date('Y-m-d H:i:s') - )); + ]); } elseif ($logRow->transaction_type === self::TRANSACTION_TYPE_PRODUCT_OPENED) { // Remove opened flag from corresponding log entry $stockRows = $this->getDatabase()->stock()->where('stock_id = :1 AND amount = :2 AND purchased_date = :3', $logRow->stock_id, $logRow->amount, $logRow->purchased_date)->limit(1); - $stockRows->update(array( + $stockRows->update([ 'open' => 0, 'opened_date' => null - )); + ]); // Update log entry - $logRow->update(array( + $logRow->update([ 'undone' => 1, 'undone_timestamp' => date('Y-m-d H:i:s') - )); + ]); } elseif ($logRow->transaction_type === self::TRANSACTION_TYPE_STOCK_EDIT_NEW) { // Update log entry, no action needed - $logRow->update(array( + $logRow->update([ 'undone' => 1, 'undone_timestamp' => date('Y-m-d H:i:s') - )); + ]); } elseif ($logRow->transaction_type === self::TRANSACTION_TYPE_STOCK_EDIT_OLD) { // Make sure there is a stock row still $stockRow = $this->getDatabase()->stock()->where('id = :1', $logRow->stock_row_id)->fetch(); + if ($stockRow == null) { throw new \Exception('Booking does not exist or was already undone'); @@ -1159,12 +1185,13 @@ class StockService extends BaseService $openedDate = $logRow->opened_date; $open = true; + if ($openedDate == null) { $open = false; } - $stockRow->update(array( + $stockRow->update([ 'amount' => $logRow->amount, 'best_before_date' => $logRow->best_before_date, 'purchased_date' => $logRow->purchased_date, @@ -1173,18 +1200,19 @@ class StockService extends BaseService 'location_id' => $logRow->location_id, 'open' => $open, 'opened_date' => $openedDate - )); + ]); // Update log entry - $logRow->update(array( + $logRow->update([ 'undone' => 1, 'undone_timestamp' => date('Y-m-d H:i:s') - )); + ]); } else { throw new \Exception('This booking cannot be undone'); } + } public function UndoTransaction($transactionId) @@ -1200,5 +1228,48 @@ class StockService extends BaseService { $this->UndoBooking($transactionBooking->id, true); } + } + + private function LoadBarcodeLookupPlugin() + { + $pluginName = defined('GROCY_STOCK_BARCODE_LOOKUP_PLUGIN') ? GROCY_STOCK_BARCODE_LOOKUP_PLUGIN : ''; + + if (empty($pluginName)) + { + throw new \Exception('No barcode lookup plugin defined'); + } + + $path = GROCY_DATAPATH . "/plugins/$pluginName.php"; + + if (file_exists($path)) + { + require_once $path; + return new $pluginName($this->getDatabase()->locations()->fetchAll(), $this->getDatabase()->quantity_units()->fetchAll()); + } + else + { + throw new \Exception("Plugin $pluginName was not found"); + } + + } + + private function LocationExists($locationId) + { + $locationRow = $this->getDatabase()->locations()->where('id = :1', $locationId)->fetch(); + return $locationRow !== null; + } + + private function ProductExists($productId) + { + $productRow = $this->getDatabase()->products()->where('id = :1 and active = 1', $productId)->fetch(); + return $productRow !== null; + } + + private function ShoppingListExists($listId) + { + $shoppingListRow = $this->getDatabase()->shopping_lists()->where('id = :1', $listId)->fetch(); + return $shoppingListRow !== null; + } + } diff --git a/services/TasksService.php b/services/TasksService.php index 61244de0..2a4f870f 100644 --- a/services/TasksService.php +++ b/services/TasksService.php @@ -18,10 +18,10 @@ class TasksService extends BaseService } $taskRow = $this->getDatabase()->tasks()->where('id = :1', $taskId)->fetch(); - $taskRow->update(array( + $taskRow->update([ 'done' => 1, 'done_timestamp' => $doneTime - )); + ]); return true; } @@ -34,10 +34,10 @@ class TasksService extends BaseService } $taskRow = $this->getDatabase()->tasks()->where('id = :1', $taskId)->fetch(); - $taskRow->update(array( + $taskRow->update([ 'done' => 0, 'done_timestamp' => null - )); + ]); return true; } @@ -47,4 +47,5 @@ class TasksService extends BaseService $taskRow = $this->getDatabase()->tasks()->where('id = :1', $taskId)->fetch(); return $taskRow !== null; } + } diff --git a/services/UserfieldsService.php b/services/UserfieldsService.php index 34b55824..46ff3b28 100644 --- a/services/UserfieldsService.php +++ b/services/UserfieldsService.php @@ -4,33 +4,69 @@ namespace Grocy\Services; class UserfieldsService extends BaseService { - const USERFIELD_TYPE_SINGLE_LINE_TEXT = 'text-single-line'; - const USERFIELD_TYPE_SINGLE_MULTILINE_TEXT = 'text-multi-line'; - const USERFIELD_TYPE_INTEGRAL_NUMBER = 'number-integral'; - const USERFIELD_TYPE_DECIMAL_NUMBER = 'number-decimal'; - const USERFIELD_TYPE_DATE = 'date'; - const USERFIELD_TYPE_DATETIME = 'datetime'; const USERFIELD_TYPE_CHECKBOX = 'checkbox'; - const USERFIELD_TYPE_PRESET_LIST = 'preset-list'; - const USERFIELD_TYPE_PRESET_CHECKLIST = 'preset-checklist'; - const USERFIELD_TYPE_LINK = 'link'; - const USERFIELD_TYPE_FILE = 'file'; - const USERFIELD_TYPE_IMAGE = 'image'; - public function __construct() - { - parent::__construct(); - } + const USERFIELD_TYPE_DATE = 'date'; + + const USERFIELD_TYPE_DATETIME = 'datetime'; + + const USERFIELD_TYPE_DECIMAL_NUMBER = 'number-decimal'; + + const USERFIELD_TYPE_FILE = 'file'; + + const USERFIELD_TYPE_IMAGE = 'image'; + + const USERFIELD_TYPE_INTEGRAL_NUMBER = 'number-integral'; + + const USERFIELD_TYPE_LINK = 'link'; + + const USERFIELD_TYPE_PRESET_CHECKLIST = 'preset-checklist'; + + const USERFIELD_TYPE_PRESET_LIST = 'preset-list'; + + const USERFIELD_TYPE_SINGLE_LINE_TEXT = 'text-single-line'; + + const USERFIELD_TYPE_SINGLE_MULTILINE_TEXT = 'text-multi-line'; protected $OpenApiSpec = null; - protected function getOpenApispec() + public function GetAllFields() { - if($this->OpenApiSpec == null) + return $this->getDatabase()->userfields()->orderBy('name')->fetchAll(); + } + + public function GetAllValues($entity) + { + if (!$this->IsValidEntity($entity)) { - $this->OpenApiSpec = json_decode(file_get_contents(__DIR__ . '/../grocy.openapi.json')); + throw new \Exception('Entity does not exist or is not exposed'); } - return $this->OpenApiSpec; + + return $this->getDatabase()->userfield_values_resolved()->where('entity', $entity)->orderBy('name')->fetchAll(); + } + + public function GetEntities() + { + $exposedDefaultEntities = $this->getOpenApiSpec()->components->internalSchemas->ExposedEntity->enum; + + $userentities = []; + + foreach ($this->getDatabase()->userentities()->orderBy('name') as $userentity) + { + $userentities[] = 'userentity-' . $userentity->name; + } + + return array_merge($exposedDefaultEntities, $userentities); + } + + public function GetField($fieldId) + { + return $this->getDatabase()->userfields($fieldId); + } + + public function GetFieldTypes() + { + return GetClassConstants('\Grocy\Services\UserfieldsService'); } public function GetFields($entity) @@ -43,16 +79,6 @@ class UserfieldsService extends BaseService return $this->getDatabase()->userfields()->where('entity', $entity)->orderBy('name')->fetchAll(); } - public function GetField($fieldId) - { - return $this->getDatabase()->userfields($fieldId); - } - - public function GetAllFields() - { - return $this->getDatabase()->userfields()->orderBy('name')->fetchAll(); - } - public function GetValues($entity, $objectId) { if (!$this->IsValidEntity($entity)) @@ -61,7 +87,8 @@ class UserfieldsService extends BaseService } $userfields = $this->getDatabase()->userfield_values_resolved()->where('entity = :1 AND object_id = :2', $entity, $objectId)->orderBy('name')->fetchAll(); - $userfieldKeyValuePairs = array(); + $userfieldKeyValuePairs = []; + foreach ($userfields as $userfield) { $userfieldKeyValuePairs[$userfield->name] = $userfield->value; @@ -70,16 +97,6 @@ class UserfieldsService extends BaseService return $userfieldKeyValuePairs; } - public function GetAllValues($entity) - { - if (!$this->IsValidEntity($entity)) - { - throw new \Exception('Entity does not exist or is not exposed'); - } - - return $this->getDatabase()->userfield_values_resolved()->where('entity', $entity)->orderBy('name')->fetchAll(); - } - public function SetValues($entity, $objectId, $userfields) { if (!$this->IsValidEntity($entity)) @@ -99,44 +116,45 @@ class UserfieldsService extends BaseService $fieldId = $fieldRow->id; $alreadyExistingEntry = $this->getDatabase()->userfield_values()->where('field_id = :1 AND object_id = :2', $fieldId, $objectId)->fetch(); + if ($alreadyExistingEntry) // Update { - $alreadyExistingEntry->update(array( + $alreadyExistingEntry->update([ 'value' => $value - )); + ]); } else // Insert { - $newRow = $this->getDatabase()->userfield_values()->createRow(array( + $newRow = $this->getDatabase()->userfield_values()->createRow([ 'field_id' => $fieldId, 'object_id' => $objectId, 'value' => $value - )); + ]); $newRow->save(); } + } + } - public function GetEntities() + public function __construct() { - $exposedDefaultEntities = $this->getOpenApiSpec()->components->internalSchemas->ExposedEntity->enum; + parent::__construct(); + } - $userentities = array(); - foreach ($this->getDatabase()->userentities()->orderBy('name') as $userentity) + protected function getOpenApispec() + { + if ($this->OpenApiSpec == null) { - $userentities[] = 'userentity-' . $userentity->name; + $this->OpenApiSpec = json_decode(file_get_contents(__DIR__ . '/../grocy.openapi.json')); } - return array_merge($exposedDefaultEntities, $userentities); - } - - public function GetFieldTypes() - { - return GetClassConstants('\Grocy\Services\UserfieldsService'); + return $this->OpenApiSpec; } private function IsValidEntity($entity) { return in_array($entity, $this->GetEntities()); } + } diff --git a/services/UsersService.php b/services/UsersService.php index 688e6999..407088d5 100644 --- a/services/UsersService.php +++ b/services/UsersService.php @@ -6,25 +6,34 @@ class UsersService extends BaseService { public function CreateUser(string $username, string $firstName, string $lastName, string $password) { - $newUserRow = $this->getDatabase()->users()->createRow(array( + $newUserRow = $this->getDatabase()->users()->createRow([ 'username' => $username, 'first_name' => $firstName, 'last_name' => $lastName, 'password' => password_hash($password, PASSWORD_DEFAULT) - )); + ]); $newUserRow = $newUserRow->save(); - $permList = array(); - foreach ($this->getDatabase()->permission_hierarchy()->where('name', GROCY_DEFAULT_PERMISSIONS)->fetchAll() as $perm) { - $permList[] = array( + $permList = []; + + foreach ($this->getDatabase()->permission_hierarchy()->where('name', GROCY_DEFAULT_PERMISSIONS)->fetchAll() as $perm) + { + $permList[] = [ 'user_id' => $newUserRow->id, 'permission_id' => $perm->id - ); + ]; } + $this->getDatabase()->user_permissions()->insert($permList); return $newUserRow; } + public function DeleteUser($userId) + { + $row = $this->getDatabase()->users($userId); + $row->delete(); + } + public function EditUser(int $userId, string $username, string $firstName, string $lastName, string $password) { if (!$this->UserExists($userId)) @@ -33,36 +42,18 @@ class UsersService extends BaseService } $user = $this->getDatabase()->users($userId); - $user->update(array( + $user->update([ 'username' => $username, 'first_name' => $firstName, 'last_name' => $lastName, 'password' => password_hash($password, PASSWORD_DEFAULT) - )); - } - - public function DeleteUser($userId) - { - $row = $this->getDatabase()->users($userId); - $row->delete(); - } - - public function GetUsersAsDto() - { - $users = $this->getDatabase()->users(); - $returnUsers = array(); - foreach ($users as $user) - { - unset($user->password); - $user->display_name = GetUserDisplayName($user); - $returnUsers[] = $user; - } - return $returnUsers; + ]); } public function GetUserSetting($userId, $settingKey) { $settingRow = $this->getDatabase()->user_settings()->where('user_id = :1 AND key = :2', $userId, $settingKey)->fetch(); + if ($settingRow !== null) { return $settingRow->value; @@ -71,13 +62,15 @@ class UsersService extends BaseService { return null; } + } public function GetUserSettings($userId) { - $settings = array(); + $settings = []; $settingRows = $this->getDatabase()->user_settings()->where('user_id = :1', $userId)->fetchAll(); + foreach ($settingRows as $settingRow) { $settings[$settingRow->key] = $settingRow->value; @@ -88,25 +81,42 @@ class UsersService extends BaseService return array_merge($GROCY_DEFAULT_USER_SETTINGS, $settings); } + public function GetUsersAsDto() + { + $users = $this->getDatabase()->users(); + $returnUsers = []; + + foreach ($users as $user) + { + unset($user->password); + $user->display_name = GetUserDisplayName($user); + $returnUsers[] = $user; + } + + return $returnUsers; + } + public function SetUserSetting($userId, $settingKey, $settingValue) { $settingRow = $this->getDatabase()->user_settings()->where('user_id = :1 AND key = :2', $userId, $settingKey)->fetch(); + if ($settingRow !== null) { - $settingRow->update(array( + $settingRow->update([ 'value' => $settingValue, 'row_updated_timestamp' => date('Y-m-d H:i:s') - )); + ]); } else { - $settingRow = $this->getDatabase()->user_settings()->createRow(array( + $settingRow = $this->getDatabase()->user_settings()->createRow([ 'user_id' => $userId, 'key' => $settingKey, 'value' => $settingValue - )); + ]); $settingRow->save(); } + } private function UserExists($userId) @@ -114,4 +124,5 @@ class UsersService extends BaseService $userRow = $this->getDatabase()->users()->where('id = :1', $userId)->fetch(); return $userRow !== null; } + }