From 90f12ab916dea2a999e5d19cd3112c707592a640 Mon Sep 17 00:00:00 2001 From: Cerbu Andrei Mihnea <100320074+andrei-mihnea-cerbu@users.noreply.github.com> Date: Wed, 10 May 2023 00:47:48 +0300 Subject: [PATCH] facut pana la video 7 --- Lab 1/index.html | 41 -- knowyourfood/.env.example | 3 + knowyourfood/.gitignore | 4 + knowyourfood/.htaccess | 14 + knowyourfood/composer.json | 17 + knowyourfood/composer.lock | 488 ++++++++++++++++++ knowyourfood/controllers/AuthController.php | 71 +++ knowyourfood/controllers/siteController.php | 32 ++ knowyourfood/core/Application.php | 91 ++++ knowyourfood/core/Controller.php | 26 + knowyourfood/core/Database.php | 78 +++ knowyourfood/core/DbModel.php | 51 ++ knowyourfood/core/Model.php | 104 ++++ knowyourfood/core/Request.php | 54 ++ knowyourfood/core/Response.php | 13 + knowyourfood/core/Router.php | 100 ++++ knowyourfood/core/Session.php | 50 ++ .../core/exceptions/ForbiddenException.php | 8 + .../core/exceptions/NotFoundException.php | 8 + knowyourfood/core/form/Button.php | 12 + knowyourfood/core/form/Field.php | 43 ++ knowyourfood/core/form/Form.php | 23 + .../core/middlewares/AuthMiddleware.php | 21 + .../core/middlewares/BaseMiddleware.php | 7 + knowyourfood/dbconfig.php | 10 + knowyourfood/index.php | 39 ++ knowyourfood/migrations.php | 10 + knowyourfood/migrations/m01_initial.php | 25 + .../migrations/m02_add_password_column.php | 17 + knowyourfood/models/LoginForm.php | 40 ++ knowyourfood/models/User.php | 53 ++ knowyourfood/runtime/.gitignore | 1 + knowyourfood/views/_error.php | 1 + knowyourfood/views/contact.php | 17 + knowyourfood/views/home.php | 2 + knowyourfood/views/layouts/auth.php | 27 + knowyourfood/views/layouts/main.php | 50 ++ knowyourfood/views/login.php | 16 + knowyourfood/views/profile.php | 1 + knowyourfood/views/register.php | 17 + 40 files changed, 1644 insertions(+), 41 deletions(-) delete mode 100644 Lab 1/index.html create mode 100644 knowyourfood/.env.example create mode 100644 knowyourfood/.gitignore create mode 100644 knowyourfood/.htaccess create mode 100644 knowyourfood/composer.json create mode 100644 knowyourfood/composer.lock create mode 100644 knowyourfood/controllers/AuthController.php create mode 100644 knowyourfood/controllers/siteController.php create mode 100644 knowyourfood/core/Application.php create mode 100644 knowyourfood/core/Controller.php create mode 100644 knowyourfood/core/Database.php create mode 100644 knowyourfood/core/DbModel.php create mode 100644 knowyourfood/core/Model.php create mode 100644 knowyourfood/core/Request.php create mode 100644 knowyourfood/core/Response.php create mode 100644 knowyourfood/core/Router.php create mode 100644 knowyourfood/core/Session.php create mode 100644 knowyourfood/core/exceptions/ForbiddenException.php create mode 100644 knowyourfood/core/exceptions/NotFoundException.php create mode 100644 knowyourfood/core/form/Button.php create mode 100644 knowyourfood/core/form/Field.php create mode 100644 knowyourfood/core/form/Form.php create mode 100644 knowyourfood/core/middlewares/AuthMiddleware.php create mode 100644 knowyourfood/core/middlewares/BaseMiddleware.php create mode 100644 knowyourfood/dbconfig.php create mode 100644 knowyourfood/index.php create mode 100644 knowyourfood/migrations.php create mode 100644 knowyourfood/migrations/m01_initial.php create mode 100644 knowyourfood/migrations/m02_add_password_column.php create mode 100644 knowyourfood/models/LoginForm.php create mode 100644 knowyourfood/models/User.php create mode 100644 knowyourfood/runtime/.gitignore create mode 100644 knowyourfood/views/_error.php create mode 100644 knowyourfood/views/contact.php create mode 100644 knowyourfood/views/home.php create mode 100644 knowyourfood/views/layouts/auth.php create mode 100644 knowyourfood/views/layouts/main.php create mode 100644 knowyourfood/views/login.php create mode 100644 knowyourfood/views/profile.php create mode 100644 knowyourfood/views/register.php diff --git a/Lab 1/index.html b/Lab 1/index.html deleted file mode 100644 index 2f6af9b..0000000 --- a/Lab 1/index.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - My test page - - -

Titlul paginii

-

de Andrei Cerbu

-

Capitolul 0

-

- Acesta este un paragraf f interesant despre aceasta pagina foarte interesanta -

- -

-
-
-
-
-
-
- I'm creating a link to - the Mozilla homepage. -

-
-
Chris Mills, Manchester, The Grim North, UK
-
Acesta          este              
-						un
-		test
-		
- - - - - diff --git a/knowyourfood/.env.example b/knowyourfood/.env.example new file mode 100644 index 0000000..e499597 --- /dev/null +++ b/knowyourfood/.env.example @@ -0,0 +1,3 @@ +DB_DSN = mysql:host=localhost;port=3306;dbname=Web +DB_USER = root +DB_PASSWORD = root \ No newline at end of file diff --git a/knowyourfood/.gitignore b/knowyourfood/.gitignore new file mode 100644 index 0000000..105e5d8 --- /dev/null +++ b/knowyourfood/.gitignore @@ -0,0 +1,4 @@ +vendor +.idea +dbconfig +!.gitignore \ No newline at end of file diff --git a/knowyourfood/.htaccess b/knowyourfood/.htaccess new file mode 100644 index 0000000..8d79af1 --- /dev/null +++ b/knowyourfood/.htaccess @@ -0,0 +1,14 @@ +ErrorDocument 403 /404.php +ErrorDocument 404 /404.php +DirectoryINdex index.php + + +RewriteEngine on +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^(.*)$ index.php?page=$1 [L,QSA] + +RewriteCond %{REQUEST_URI} !^/$ +RewriteCond %{REQUEST_URI} !^/admin +RewriteRule .*/ [L,R=301] + \ No newline at end of file diff --git a/knowyourfood/composer.json b/knowyourfood/composer.json new file mode 100644 index 0000000..ce8993e --- /dev/null +++ b/knowyourfood/composer.json @@ -0,0 +1,17 @@ +{ + "name": "andrei/knowyourfood", + "authors": [ + { + "name": "Cerbu Andrei Mihnea", + "email": "andreimihneacerbu@gmail.com" + } + ], + "autoload" : { + "psr-4" : { + "app\\": "./" + } + }, + "require": { + "vlucas/phpdotenv": "^5.5" + } +} diff --git a/knowyourfood/composer.lock b/knowyourfood/composer.lock new file mode 100644 index 0000000..5a0612e --- /dev/null +++ b/knowyourfood/composer.lock @@ -0,0 +1,488 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "c85ce2bf1fa0099ca65ec02d2a26abb1", + "packages": [ + { + "name": "graham-campbell/result-type", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2023-02-25T20:23:15+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e", + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2023-02-25T19:38:58+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.5.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.0.2", + "php": "^7.1.3 || ^8.0", + "phpoption/phpoption": "^1.8", + "symfony/polyfill-ctype": "^1.23", + "symfony/polyfill-mbstring": "^1.23.1", + "symfony/polyfill-php80": "^1.23.1" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-filter": "*", + "phpunit/phpunit": "^7.5.20 || ^8.5.30 || ^9.5.25" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "5.5-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2022-10-16T01:01:54+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/knowyourfood/controllers/AuthController.php b/knowyourfood/controllers/AuthController.php new file mode 100644 index 0000000..0277e13 --- /dev/null +++ b/knowyourfood/controllers/AuthController.php @@ -0,0 +1,71 @@ +registerMiddleware(new AuthMiddleware(['profile'])); + } + + public function login(Request $request, Response $response){ + $loginForm = new LoginForm(); + + if($request->isPost()){ + $loginForm->loadData($request->getBody()); + if($loginForm->validate() && $loginForm->login()){ + $response->redirect('home'); + exit; + } + + $this->setLayout('auth'); + return $this->render('login', [ + 'model' => $loginForm + ]); + } + + $this->setLayout('auth'); + return $this->render('login', [ + 'model' => $loginForm + ]); + } + + public function register(Request $request, $response){ + $user = new User(); + + if($request->isPost()){ + $user->loadData($request->getBody()); + + if($user->validate() && $user->save()){ + Application::$app->session->setFlash('success', 'Thanks for registering'); + $response->redirect('home'); + exit; + } + + $this->setLayout('auth'); + return $this->render('register', [ + 'model'=>$user + ]); + } + + $this->setLayout('auth'); + return $this->render('register', [ + 'model'=>$user + ]); + } + + public function logout($request, $response){ + Application::$app->logout(); + $response->redirect('/'); + } + + public function profile(){ + return $this->render('profile'); + } +} \ No newline at end of file diff --git a/knowyourfood/controllers/siteController.php b/knowyourfood/controllers/siteController.php new file mode 100644 index 0000000..39c8554 --- /dev/null +++ b/knowyourfood/controllers/siteController.php @@ -0,0 +1,32 @@ +getBody(); + return 'handling requested data'; + } + + public function contact(){ + return $this->render('contact'); + } + + public function home(){ + $params = [ + 'name' => "TheCodeholic" + ]; + + return $this->render('home', $params); + } + + public function profile(){ + return $this->render('profile'); + } + + public function _404(){ + return $this->render('_404'); + } +} \ No newline at end of file diff --git a/knowyourfood/core/Application.php b/knowyourfood/core/Application.php new file mode 100644 index 0000000..7206480 --- /dev/null +++ b/knowyourfood/core/Application.php @@ -0,0 +1,91 @@ +userClass = $config['userClass']; + self::$app = $this; + + $this->request = new Request(); + $this->response = new Response(); + $this->router = new Router($this->request, $this->response); + + $this->db = new Database($config['db']); + $this->session = new Session(); + + $primaryValue = $this->session->get('user'); + if ($primaryValue) { + $userObj = new $this->userClass(); + $primaryKey = $userObj->primaryKey(); + $this->user = $userObj->findOne([$primaryKey => $primaryValue]); + } else { + $this->user = null; + } + } + + public function setController($controller) + { + $this->controller = $controller; + } + + public function getController() + { + return $this->controller; + } + + public function login(DbModel $user) + { + $this->user = $user; + $primaryKey = $user->primaryKey(); + $primaryValue = $user->{$primaryKey}; + $this->session->set('user', $primaryValue); + return true; + } + + public function logout() + { + $this->user = null; + $this->session->remove('user'); + } + + public static function isLoggedIn() + { + return !self::$app->user; + } + + public function run() + { + try{ + echo $this->router->resolve(); + }catch(ForbiddenException $e){ + $this->response->setStatusCode(403); + echo $this->router->renderView('_error', [ + 'exception' => $e + ]); + }catch(NotFoundException $e){ + $this->response->setStatusCode(404); + echo $this->router->renderView('_error', [ + 'exception' => $e + ]); + } + } +} \ No newline at end of file diff --git a/knowyourfood/core/Controller.php b/knowyourfood/core/Controller.php new file mode 100644 index 0000000..fcbb677 --- /dev/null +++ b/knowyourfood/core/Controller.php @@ -0,0 +1,26 @@ +router->renderView($view, $params); + } + + public function setLayout($layout){ + $this->layout = $layout; + } + + public function registerMiddleware(BaseMiddleware $middleware){ + $this->middlewares[] = $middleware; + } + + public function getMiddlewares(){ + return $this->middlewares; + } +} \ No newline at end of file diff --git a/knowyourfood/core/Database.php b/knowyourfood/core/Database.php new file mode 100644 index 0000000..ba3a5bd --- /dev/null +++ b/knowyourfood/core/Database.php @@ -0,0 +1,78 @@ +pdo = new \PDO($dsn, $user, $password); + $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + } + + public function applyMigrations(){ + $this->createMigrationsTable(); + $appliedMigrations = $this->getAppliedMigrations(); + + $newMigrations = []; + $files = scandir(Application::$ROOT_DIR.'/migrations'); + $toApplyMigrations = array_diff($files, $appliedMigrations); + + foreach($toApplyMigrations as $migration){ + if($migration === '.' || $migration === '..'){ + continue; + } + + require_once Application::$ROOT_DIR.'/migrations/'.$migration; + $className = pathinfo($migration, PATHINFO_FILENAME); + + $instance = new $className(); + $this->log("Applying migration $migration" . PHP_EOL); + $instance->up(); + $this->log("Applied migration $migration" . PHP_EOL); + + $newMigrations[] = $migration; + } + + if(!empty($newMigrations)){ + $this->saveMigrations($newMigrations); + }else{ + $this->log('All migrations are applied'); + } + } + + public function createMigrationsTable(){ + $this->pdo->exec(" + CREATE TABLE IF NOT EXISTS migrations ( + id INT AUTO_INCREMENT, + migration varchar(255), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) + ) + ENGINE=INNODB;"); + } + + public function getAppliedMigrations(){ + $statement = $this->pdo->prepare("SELECT migration FROM migrations"); + $statement->execute(); + + return $statement->fetchAll(\PDO::FETCH_COLUMN); + } + + public function saveMigrations(array $migrations){ + $str = implode(",", array_map(fn($m) => "('$m')", $migrations)); + $statement = $this->pdo->prepare("INSERT INTO migrations (migration) VALUES $str"); + $statement->execute(); + } + + public function prepare($sql){ + return $this->pdo->prepare($sql); + } + + protected function log($message){ + echo '['.date('Y-m-d H:i:s').'] - '.$message.PHP_EOL; + } +} \ No newline at end of file diff --git a/knowyourfood/core/DbModel.php b/knowyourfood/core/DbModel.php new file mode 100644 index 0000000..b6928c0 --- /dev/null +++ b/knowyourfood/core/DbModel.php @@ -0,0 +1,51 @@ +tableName(); + $attributes = $this->attributes(); + $params = array_map(fn($attr) => ":$attr", $attributes); + + $statement = self::prepare("INSERT INTO $tableName (" + .implode(',', $attributes) + .") VALUES (" + .implode(',', $params) + .")" + ); + + foreach($attributes as $attribute){ + $statement->bindValue(":$attribute", $this->{$attribute}); + } + + $statement->execute(); + return true; + } + + public static function prepare($sql){ + return Application::$app->db->pdo->prepare($sql); + } + + public function findOne($where){ + $tableName = static::tableName(); + $attributes = array_keys($where); + + $sql = implode("AND ", array_map(fn($attr) => "$attr = :$attr", $attributes)); + $statement = self::prepare("SELECT * from $tableName WHERE $sql"); + + foreach($where as $key => $item){ + $statement->bindValue(":$key", $item); + } + + $statement->execute(); + + return $statement->fetchObject(static::class); + } +} \ No newline at end of file diff --git a/knowyourfood/core/Model.php b/knowyourfood/core/Model.php new file mode 100644 index 0000000..250c137 --- /dev/null +++ b/knowyourfood/core/Model.php @@ -0,0 +1,104 @@ + $value){ + if(property_exists($this, $key)){ + $this->{$key} = $value; + } + } + } + + abstract public function rules():array; + + public function labels(): array{ + return []; + } + + public array $errors = []; + + private function addErrorForRule(string $attribute, string $rule, $params =[]){ + $message = $this->errorMessages()[$rule] ?? ''; + foreach($params as $key => $value){ + $message = str_replace("{{$key}}", $value, $message); + } + + $this->errors[$attribute][] = $message; + } + + public function addError(string $attribute, string $message){ + $this->errors[$attribute][] = $message; + } + + public function errorMessages(){ + return [ + self::RULE_REQUIRED => 'This field is required', + self::RULE_EMAIL => 'This field must be valid email address', + self::RULE_MIN => 'Min length of this field must be {min}', + self::RULE_MAX => 'Max length of this field must be {max}', + self::RULE_UNIQUE => 'Email already exists' + ]; + } + + public function validate(){ + foreach($this->rules() as $attribute => $rules){ + $value = $this->{$attribute}; + foreach($rules as $rule){ + $ruleName = $rule; + + if(!is_string($ruleName)){ + $ruleName = $rule[0]; + } + + if($ruleName === self::RULE_REQUIRED && !$value){ + $this->addErrorForRule($attribute, self::RULE_REQUIRED); + } + + if($ruleName === self::RULE_EMAIL && !filter_var($value, FILTER_VALIDATE_EMAIL)){ + $this->addErrorForRule($attribute, self::RULE_EMAIL); + } + + if($ruleName === self::RULE_MIN && strlen($value) < $rule['min']){ + $this->addErrorForRule($attribute, self::RULE_MIN, $rule); + } + + if($ruleName === self::RULE_MAX && strlen($value) > $rule['max']){ + $this->addErrorForRule($attribute, self::RULE_MAX, $rule); + } + + if($ruleName === self::RULE_UNIQUE){ + $className = $rule['class']; + $uniqueAttr = $rule['attirubute'] ?? $attribute; + $tableName = $className::tableName(); + + $statement = Application::$app->db->prepare(("SELECT * FROM $tableName WHERE $uniqueAttr = :attr")); + $statement->bindValue(":attr", $value); + $statement->execute(); + $record = $statement->fetchObject(); + + if($record){ + $this->addErrorForRule($attribute, SELF::RULE_UNIQUE); + } + } + } + } + + return empty($this->errors); + } + + public function hasError($attribute){ + return $this->errors[$attribute] ?? false; + } + + public function getFirstError($attribute){ + return $this->errors[$attribute][0] ?? ''; + } +} \ No newline at end of file diff --git a/knowyourfood/core/Request.php b/knowyourfood/core/Request.php new file mode 100644 index 0000000..b59f214 --- /dev/null +++ b/knowyourfood/core/Request.php @@ -0,0 +1,54 @@ +lengthOfRoot = strlen('knowyourfood') + 1; + } + + public function getPath(){//identificam din URL pe pagina trebuie accesata + $path = $_SERVER['REQUEST_URI']; + $path = substr($path, $this->lengthOfRoot); + + $position = strpos($path, '?'); + + if(!$position){ + return $path; + } + + return substr($path, 0, $position); + } + + public function method(){ + return strtolower($_SERVER['REQUEST_METHOD']); + } + + public function isGet(){ + return $this->method() === 'get'; + } + + public function isPost(){ + return $this->method() === 'post'; + } + + //bazat pe tipul de request, accesam tuplele key/value pentru a identifica body ul requestului + public function getBody(){ + $body = []; + if($this->method() === 'get'){ + foreach($_GET as $key => $value){ + $body[$key] = filter_input(INPUT_GET, $key, FILTER_SANITIZE_SPECIAL_CHARS); + } + } + + if($this->method() === 'post'){ + foreach($_POST as $key => $value){ + $body[$key] = filter_input(INPUT_POST, $key, FILTER_SANITIZE_SPECIAL_CHARS); + } + } + + return $body; + } +} \ No newline at end of file diff --git a/knowyourfood/core/Response.php b/knowyourfood/core/Response.php new file mode 100644 index 0000000..27487ed --- /dev/null +++ b/knowyourfood/core/Response.php @@ -0,0 +1,13 @@ +request = $request; + $this->response = $response; + } + + public function get($path, $callback) + { + $this->routes['get'][$path] = $callback; + } + + public function post($path, $callback) + { + $this->routes['post'][$path] = $callback; + } + + /* + in aceasta functie se identifica path ul catre fisier, metoda (GET/POST) si se identifica un "routes" callback ul (in general controller ul) pe care trebuie sa l returneze + astfel, in call_user_function, se ofera clasa de tip constructor si request ul de la user ca dupa controller ul sa se ocupe de restul + */ + + public function resolve() + { + $path = $this->request->getPath(); + $method = $this->request->method(); + $callback = $this->routes[$method][$path] ?? false; + + if (!$callback) { + throw new NotFoundException(); + } + + if (is_string($callback)) { + return $this->renderView($callback); + } + + if (is_array($callback)) { + $controller = new $callback[0](); + Application::$app->controller = $controller; + $controller->action = $callback[1]; + $callback[0] = $controller; + + foreach($controller->getMiddlewares() as $middleware){ + $middleware->execute(); + } + } + + + return call_user_func($callback, $this->request, $this->response); + } + + //layout ne va ajuta sa identificam ce fel de layout trebuie sa aiba pagina pe care o trimitem (elemente extra in afara de continut: navbar, elemente din baza de date etc) + protected function layoutContent() + { + $layout = Application::$app->layout; + if (Application::$app->controller) { + $layout = Application::$app->controller->layout; + } + + ob_start(); + include_once Application::$ROOT_DIR . "/views/layouts/$layout.php"; + return ob_get_clean(); + } + + + public function renderView($view, $params = []) + { + $layoutContent = $this->layoutContent(); + $viewContent = $this->renderOnlyView($view, $params); + + return str_replace('{{content}}', $viewContent, $layoutContent); + } + + public function renderContent($viewContent) + { + $layoutContent = $this->layoutContent(); + return str_replace('{{content}}', $viewContent, $layoutContent); + } + + protected function renderOnlyView($view, $params) + { + foreach ($params as $key => $value) { + $$key = $value; + } + + ob_start(); + include_once Application::$ROOT_DIR . "/views/$view.php"; + return ob_get_clean(); + } +} \ No newline at end of file diff --git a/knowyourfood/core/Session.php b/knowyourfood/core/Session.php new file mode 100644 index 0000000..a4cf901 --- /dev/null +++ b/knowyourfood/core/Session.php @@ -0,0 +1,50 @@ + &$flashMessage){ + $flashMessage['remove'] = 'true'; + } + + $_SESSION[self::FLASH_KEY] = $flashMessages; + } + + public function setFlash($key, $message){ + $_SESSION[self::FLASH_KEY][$key] = [ + 'value' => $message, + 'remove' => 'false' + ]; + } + + public function getFlash($key){ + return $_SESSION[self::FLASH_KEY][$key]['value'] ?? false; + } + + public function set($key, $value){ + $_SESSION[$key] = $value; + } + + public function get($key){ + return $_SESSION[$key] ?? false; + } + + public function remove($key){ + unset($_SESSION[$key]); + } + + public function __destruct(){ + $flashMessages = $_SESSION[self::FLASH_KEY] ?? []; + foreach($flashMessages as $key => &$flashMessage){ + if($flashMessage['remove']){ + unset($flashMessages[$key]); + } + } + + $_SESSION[self::FLASH_KEY] = $flashMessages; + } +} \ No newline at end of file diff --git a/knowyourfood/core/exceptions/ForbiddenException.php b/knowyourfood/core/exceptions/ForbiddenException.php new file mode 100644 index 0000000..b6ed42a --- /dev/null +++ b/knowyourfood/core/exceptions/ForbiddenException.php @@ -0,0 +1,8 @@ +Submit + ', + ); + } +} \ No newline at end of file diff --git a/knowyourfood/core/form/Field.php b/knowyourfood/core/form/Field.php new file mode 100644 index 0000000..2d7379b --- /dev/null +++ b/knowyourfood/core/form/Field.php @@ -0,0 +1,43 @@ +type = self::TYPE_TEXT; + $this->model = $model; + $this->attribute = $attribute; + } + + public function __toString(){ + return sprintf(' +
+ + +
+ %s +
+
+ ', + $this->model->labels()[$this->attribute] ?? $this->attribute, + $this->type, + $this->attribute, + $this->model->{$this->attribute}, + $this->model->hasError($this->attribute) ? ' is invalid' : '', + $this->model->getFirstError($this->attribute) + ); + } + + public function passwordField(){ + $this->type = self::TYPE_PASSWORD; + return $this; + } +} \ No newline at end of file diff --git a/knowyourfood/core/form/Form.php b/knowyourfood/core/form/Form.php new file mode 100644 index 0000000..329064f --- /dev/null +++ b/knowyourfood/core/form/Form.php @@ -0,0 +1,23 @@ +', $action, $method); + return new Form(); + } + + public static function end(){ + echo ''; + } + + public function field(Model $model, $attribute){ + return new Field($model, $attribute); + } + + public function button(){ + return new Button(); + } +} \ No newline at end of file diff --git a/knowyourfood/core/middlewares/AuthMiddleware.php b/knowyourfood/core/middlewares/AuthMiddleware.php new file mode 100644 index 0000000..aa6a702 --- /dev/null +++ b/knowyourfood/core/middlewares/AuthMiddleware.php @@ -0,0 +1,21 @@ +actions = $actions; + } + + public function execute(){ + if(Application::isLoggedIn()){ + if(empty($this->actions) || in_array(Application::$app->controller->action, $this->actions)){ + throw new ForbiddenException; + } + } + } +} \ No newline at end of file diff --git a/knowyourfood/core/middlewares/BaseMiddleware.php b/knowyourfood/core/middlewares/BaseMiddleware.php new file mode 100644 index 0000000..4bb4ec1 --- /dev/null +++ b/knowyourfood/core/middlewares/BaseMiddleware.php @@ -0,0 +1,7 @@ + \app\models\User::class, + 'db' => [ + 'dsn' => 'mysql:host=localhost;port=3306;dbname=Web', + 'user' => 'root', + 'password' => '', + ] +]; \ No newline at end of file diff --git a/knowyourfood/index.php b/knowyourfood/index.php new file mode 100644 index 0000000..a5335eb --- /dev/null +++ b/knowyourfood/index.php @@ -0,0 +1,39 @@ +router->get('/', [SiteController::class, '/']); + +$app->router->get('/home', [SiteController::class, 'home']); + +$app->router->get('/_404', [SiteController::class, '_404']); + +$app->router->get('/contact', [SiteController::class, 'contact']); +$app->router->post('/contact', [SiteController::class, 'handleContact']); + +$app->router->get('/login', [AuthController::class, 'login']); +$app->router->post('/login', [AuthController::class, 'login']); + +$app->router->get('/register', [AuthController::class, 'register']); +$app->router->post('/register', [AuthController::class, 'register']); + +$app->router->get('/profile', [AuthController::class, 'profile']); +$app->router->post('/profile', [AuthController::class, 'profile']); + +$app->router->get('/logout', [AuthController::class, 'logout']); + +$app->run(); diff --git a/knowyourfood/migrations.php b/knowyourfood/migrations.php new file mode 100644 index 0000000..8924ef1 --- /dev/null +++ b/knowyourfood/migrations.php @@ -0,0 +1,10 @@ +db->applyMigrations(); diff --git a/knowyourfood/migrations/m01_initial.php b/knowyourfood/migrations/m01_initial.php new file mode 100644 index 0000000..3e5fef3 --- /dev/null +++ b/knowyourfood/migrations/m01_initial.php @@ -0,0 +1,25 @@ +db; + $SQL = "CREATE TABLE users( + id INT AUTO_INCREMENT, + email VARCHAR(255) NOT NULL, + firstname VARCHAR(255) NOT NULL, + lastname VARCHAR(255) NOT NULL, + status TINYINT NOT NULL, + create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) + ) Engine=INNODB; + "; + $db->pdo->exec($SQL); + } + + public function down(){ + $db = Application::$app->db; + $SQL = "DROP TABLE users"; + $db->pdo->exec($SQL); + } +} \ No newline at end of file diff --git a/knowyourfood/migrations/m02_add_password_column.php b/knowyourfood/migrations/m02_add_password_column.php new file mode 100644 index 0000000..5061466 --- /dev/null +++ b/knowyourfood/migrations/m02_add_password_column.php @@ -0,0 +1,17 @@ +db; + $SQL = "ALTER TABLE users ADD COLUMN password VARCHAR(512) NOT NULL"; + $db->pdo->exec($SQL); + } + + public function down(){ + $db = Application::$app->db; + $SQL = "ALTER TABLE users DROP COLUMN password"; + $db->pdo->exec($SQL); + } +} \ No newline at end of file diff --git a/knowyourfood/models/LoginForm.php b/knowyourfood/models/LoginForm.php new file mode 100644 index 0000000..b9e1894 --- /dev/null +++ b/knowyourfood/models/LoginForm.php @@ -0,0 +1,40 @@ + [self::RULE_REQUIRED, self::RULE_EMAIL], + 'password' => [self::RULE_REQUIRED] + ]; + } + + public function labels():array { + return [ + 'email' => 'Email', + 'password' => 'Password' + ]; + } + + public function login(){ + $user = new User(); + $user = $user->findOne(['email' => $this->email]); + if(!$user){ + $this->addError('email', 'User does not exist with this email'); + return false; + } + + if(!password_verify($this->password, $user->password)){ + $this->addError('password', 'Incorrect password'); + return false; + } + + return Application::$app->login($user); + } +} \ No newline at end of file diff --git a/knowyourfood/models/User.php b/knowyourfood/models/User.php new file mode 100644 index 0000000..281b244 --- /dev/null +++ b/knowyourfood/models/User.php @@ -0,0 +1,53 @@ +status = self::STATUS_INACTIVE; + $this->password = password_hash($this->password, PASSWORD_DEFAULT); + return parent::save(); + } + + public function tableName(): string{ + return 'users'; + } + + public function rules(): array{ + return [ + 'firstname' => [self::RULE_REQUIRED], + 'lastname' => [self::RULE_REQUIRED], + 'email' => [self::RULE_REQUIRED, self::RULE_EMAIL, [self::RULE_UNIQUE, 'class' => self::class]], + 'password' => [self::RULE_REQUIRED, [self::RULE_MIN, 'min' => 8], [self::RULE_MAX, 'max' => 15]] + ]; + } + + public function primaryKey(): string{ + return 'id'; + } + + public function labels():array { + return [ + 'firstname' => 'First name', + 'lastname' => 'Last name', + 'email' => 'Email', + 'password' => 'Password' + ]; + } + + public function attributes(): array{ + return ['firstname', 'lastname', 'email', 'password', 'status']; + } +} \ No newline at end of file diff --git a/knowyourfood/runtime/.gitignore b/knowyourfood/runtime/.gitignore new file mode 100644 index 0000000..b722e9e --- /dev/null +++ b/knowyourfood/runtime/.gitignore @@ -0,0 +1 @@ +!.gitignore \ No newline at end of file diff --git a/knowyourfood/views/_error.php b/knowyourfood/views/_error.php new file mode 100644 index 0000000..53bbef0 --- /dev/null +++ b/knowyourfood/views/_error.php @@ -0,0 +1 @@ +

getCode() ?> - getMessage();?>

\ No newline at end of file diff --git a/knowyourfood/views/contact.php b/knowyourfood/views/contact.php new file mode 100644 index 0000000..46dd3fa --- /dev/null +++ b/knowyourfood/views/contact.php @@ -0,0 +1,17 @@ +

Contact

+ +
+
+ + +
+
+ + +
+
+ + +
+ +
\ No newline at end of file diff --git a/knowyourfood/views/home.php b/knowyourfood/views/home.php new file mode 100644 index 0000000..1a1f5b0 --- /dev/null +++ b/knowyourfood/views/home.php @@ -0,0 +1,2 @@ +

Home

+

Welcome to our site!

\ No newline at end of file diff --git a/knowyourfood/views/layouts/auth.php b/knowyourfood/views/layouts/auth.php new file mode 100644 index 0000000..a8dc0b9 --- /dev/null +++ b/knowyourfood/views/layouts/auth.php @@ -0,0 +1,27 @@ + + + + + + + + + Bootstrap demo + + + + +
+ session->getFlash('success')): ?> +
+ session->getFlash('success'); + ?> +
+ + {{content}} +
+ + + + \ No newline at end of file diff --git a/knowyourfood/views/layouts/main.php b/knowyourfood/views/layouts/main.php new file mode 100644 index 0000000..ea1273d --- /dev/null +++ b/knowyourfood/views/layouts/main.php @@ -0,0 +1,50 @@ + + + + + + + Bootstrap demo + + + + + + +
+ {{content}} +
+ + + + \ No newline at end of file diff --git a/knowyourfood/views/login.php b/knowyourfood/views/login.php new file mode 100644 index 0000000..9166582 --- /dev/null +++ b/knowyourfood/views/login.php @@ -0,0 +1,16 @@ +

Login

+ +field($model, 'email'); +echo $form->field($model, 'password')->passwordField(); + +echo $form->button(); + +Form::end() +?> + diff --git a/knowyourfood/views/profile.php b/knowyourfood/views/profile.php new file mode 100644 index 0000000..e241fdb --- /dev/null +++ b/knowyourfood/views/profile.php @@ -0,0 +1 @@ +

Profile

\ No newline at end of file diff --git a/knowyourfood/views/register.php b/knowyourfood/views/register.php new file mode 100644 index 0000000..d63fcf7 --- /dev/null +++ b/knowyourfood/views/register.php @@ -0,0 +1,17 @@ +

Create An Account

+field($model, 'firstname'); +echo $form->field($model, 'lastname'); +echo $form->field($model, 'email'); +echo $form->field($model, 'password')->passwordField(); + +echo $form->button(); + +Form::end() +?> +