├── .env-example ├── .env.panther ├── .env.test ├── .eslintrc.js ├── .gitignore ├── .php-cs-fixer.dist.php ├── LICENSE.md ├── Makefile ├── README.md ├── assets ├── css │ └── section │ │ ├── admin │ │ ├── libs.scss │ │ ├── main.scss │ │ ├── sb-admin-2.css │ │ └── styles.css │ │ └── front │ │ ├── _alert.css │ │ ├── _banner.css │ │ ├── _base.css │ │ ├── _button.css │ │ ├── _cart.css │ │ ├── _cart_dropdown.css │ │ ├── _footer.css │ │ ├── _form.css │ │ ├── _header.css │ │ ├── _header_menu.css │ │ ├── _pagination.css │ │ ├── _product.css │ │ ├── _product_list.css │ │ ├── _spinner.css │ │ ├── _typography.css │ │ ├── email.scss │ │ ├── libs.scss │ │ └── main.scss ├── images │ ├── banner │ │ └── collection │ │ │ ├── banner_1.jpg │ │ │ ├── banner_2.jpg │ │ │ ├── banner_3.jpg │ │ │ ├── banner_4.jpg │ │ │ └── banner_5.jpg │ ├── favicon │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── mstile-150x150.png │ │ ├── safari-pinned-tab.svg │ │ └── site.webmanifest │ └── icons │ │ ├── favicon.ico │ │ ├── logo_366x79.png │ │ ├── logo_366x79.svg │ │ └── short-logo_115x115.svg ├── js │ ├── section │ │ ├── admin │ │ │ ├── admin-order │ │ │ │ ├── App.vue │ │ │ │ ├── app.js │ │ │ │ ├── components │ │ │ │ │ ├── OrderProductAdd.vue │ │ │ │ │ ├── OrderProductItem.vue │ │ │ │ │ └── TotalPriceBlock.vue │ │ │ │ └── store │ │ │ │ │ ├── index.js │ │ │ │ │ └── modules │ │ │ │ │ └── products.js │ │ │ └── theme │ │ │ │ ├── filters-feature.js │ │ │ │ └── sb-admin-2.js │ │ └── front │ │ │ ├── front-cart-show │ │ │ ├── App.vue │ │ │ ├── app.js │ │ │ ├── components │ │ │ │ ├── Alert.vue │ │ │ │ ├── CartProductItem.vue │ │ │ │ ├── CartProductList.vue │ │ │ │ └── CartTotalPrice.vue │ │ │ └── store │ │ │ │ ├── index.js │ │ │ │ └── modules │ │ │ │ └── cart.js │ │ │ ├── menu-cart │ │ │ ├── App.vue │ │ │ ├── app.js │ │ │ ├── components │ │ │ │ ├── CartActions.vue │ │ │ │ ├── CartProductItem.vue │ │ │ │ ├── CartProductList.vue │ │ │ │ └── CartTotalPrice.vue │ │ │ └── store │ │ │ │ ├── index.js │ │ │ │ └── modules │ │ │ │ └── cart.js │ │ │ └── utils │ │ │ └── menu.js │ └── utils │ │ ├── changed-locale.js │ │ ├── cookie-manager.js │ │ ├── settings.js │ │ ├── title-formatter.js │ │ └── url-generator.js ├── section-admin.js └── section-front.js ├── bin ├── console ├── phpunit ├── run-tests.sh ├── selenium-server-4.22.0.jar └── selenium-server-standalone-3.141.59.jar ├── composer.json ├── composer.lock ├── config ├── bundles.php ├── packages │ ├── api_platform.yaml │ ├── assets.yaml │ ├── cache.yaml │ ├── dev │ │ ├── debug.yaml │ │ ├── hautelook_alice.yaml │ │ ├── monolog.yaml │ │ ├── nelmio_alice.yaml │ │ ├── php_translation.yaml │ │ └── web_profiler.yaml │ ├── doctrine.yaml │ ├── doctrine_migrations.yaml │ ├── framework.yaml │ ├── knp_paginator.yaml │ ├── knpu_oauth2_client.yaml │ ├── mailer.yaml │ ├── messenger.yaml │ ├── nelmio_cors.yaml │ ├── notifier.yaml │ ├── php_translation.yaml │ ├── prod │ │ ├── deprecations.yaml │ │ ├── doctrine.yaml │ │ ├── monolog.yaml │ │ └── webpack_encore.yaml │ ├── reset_password.yaml │ ├── routing.yaml │ ├── security.yaml │ ├── sensio_framework_extra.yaml │ ├── stof_doctrine_extensions.yaml │ ├── test │ │ ├── dama_doctrine_test_bundle.yaml │ │ ├── doctrine.yaml │ │ ├── hautelook_alice.yaml │ │ ├── monolog.yaml │ │ ├── nelmio_alice.yaml │ │ ├── validator.yaml │ │ ├── web_profiler.yaml │ │ └── webpack_encore.yaml │ ├── translation.yaml │ ├── twig.yaml │ ├── validator.yaml │ └── webpack_encore.yaml ├── preload.php ├── routes.yaml ├── routes │ ├── annotations.yaml │ ├── api_platform.yaml │ ├── dev │ │ ├── php_translation.yaml │ │ └── web_profiler.yaml │ ├── framework.yaml │ └── php_translation.yaml └── services.yaml ├── deploy-example.php ├── deployer7 ├── docs ├── conf │ ├── nginx │ │ └── s-shop.conf │ └── supervisor │ │ └── messenger-worker.conf ├── img │ ├── chromedriver-not-found.png │ ├── deployer7-deploy.png │ ├── install-openjdk-21-jdk.png │ └── selenium-server-not-work.png └── langs │ ├── README_de.md │ ├── README_en.md │ ├── README_es.md │ ├── README_fr.md │ └── README_zh.md ├── drivers ├── chromedriver └── geckodriver ├── fixtures ├── category.yaml ├── order.yaml ├── order_product.yaml ├── product.yaml └── user.yaml ├── migrations ├── Version20210923235603.php ├── Version20210924222154.php ├── Version20210924223952.php ├── Version20210927185157.php ├── Version20210927185503.php ├── Version20210927190540.php ├── Version20211001104011.php ├── Version20211002150834.php ├── Version20211002222439.php ├── Version20211002223343.php ├── Version20211003091352.php ├── Version20211003203107.php ├── Version20211004063505.php ├── Version20211004205602.php ├── Version20211004210150.php ├── Version20211006193743.php ├── Version20211110214414.php ├── Version20211118200708.php ├── Version20211119072129.php ├── Version20211125173610.php ├── Version20220325174952.php ├── Version20220326090356.php ├── Version20220326141147.php ├── Version20220326151023.php └── Version20240714084855.php ├── package-lock.json ├── package.json ├── phpunit.xml.dist ├── phpunit.xml.dist.bak ├── public └── index.php ├── src ├── Command │ ├── AddUserCommand.php │ └── UpdateSlugProduct.php ├── Controller │ ├── Admin │ │ ├── BaseAdminController.php │ │ ├── CategoryController.php │ │ ├── DashboardController.php │ │ ├── OrderController.php │ │ ├── ProductController.php │ │ ├── ProductImageController.php │ │ ├── SecurityController.php │ │ └── UserController.php │ └── Front │ │ ├── AuthGithubEnController.php │ │ ├── AuthGithubRusController.php │ │ ├── AuthGoogleController.php │ │ ├── AuthVkontakteController.php │ │ ├── AuthYandexController.php │ │ ├── CartApiController.php │ │ ├── CartController.php │ │ ├── CategoryController.php │ │ ├── DefaultController.php │ │ ├── EmbedController.php │ │ ├── ProductController.php │ │ ├── ProfileController.php │ │ ├── RegistrationController.php │ │ ├── ResetPasswordController.php │ │ ├── RobotsTxtController.php │ │ ├── SecurityController.php │ │ └── SitemapController.php ├── Entity │ ├── Cart.php │ ├── CartProduct.php │ ├── Category.php │ ├── Order.php │ ├── OrderProduct.php │ ├── Product.php │ ├── ProductImage.php │ ├── ResetPasswordRequest.php │ ├── StaticStorage │ │ ├── OrderStaticStorage.php │ │ └── UserStaticStorage.php │ └── User.php ├── Event │ ├── OrderCreatedFromCartEvent.php │ └── UserLoggedInViaSocialNetworkEvent.php ├── EventSubscriber │ ├── LocaleSubscriber.php │ ├── OrderCreatedFromCartSendNotificationSubscriber.php │ └── UserLoggedInViaSocialNetworkSendNotificationSubscriber.php ├── Exception │ └── Security │ │ └── EmptyUserPlainPasswordException.php ├── Form │ ├── Admin │ │ ├── EditCategoryFormType.php │ │ ├── EditOrderFormType.php │ │ ├── EditProductFormType.php │ │ ├── EditUserFormType.php │ │ └── FilterType │ │ │ ├── OrderFilterFormType.php │ │ │ └── ProductFilterFormType.php │ ├── DTO │ │ ├── EditCategoryModel.php │ │ ├── EditOrderModel.php │ │ ├── EditProductModel.php │ │ └── EditUserModel.php │ ├── Front │ │ ├── ChangePasswordFormType.php │ │ ├── ProfileEditFormType.php │ │ ├── RegistrationFormType.php │ │ └── ResetPasswordRequestFormType.php │ ├── Handler │ │ ├── CategoryFormHandler.php │ │ ├── OrderFormHandler.php │ │ ├── ProductFormHandler.php │ │ └── UserFormHandler.php │ └── Validator │ │ ├── GreaterThanOrEqualPrice.php │ │ └── GreaterThanOrEqualPriceValidator.php ├── Kernel.php ├── Messenger │ ├── Message │ │ ├── Command │ │ │ └── ResetUserPasswordCommand.php │ │ └── Event │ │ │ └── EventUserRegisteredEvent.php │ └── MessageHandler │ │ ├── Command │ │ └── ResetUserPasswordHandler.php │ │ └── Event │ │ └── UserRegisteredHandler.php ├── Repository │ ├── CartProductRepository.php │ ├── CartRepository.php │ ├── CategoryRepository.php │ ├── OrderProductRepository.php │ ├── OrderRepository.php │ ├── ProductImageRepository.php │ ├── ProductRepository.php │ ├── ResetPasswordRequestRepository.php │ └── UserRepository.php ├── Security │ ├── Authenticator │ │ ├── Admin │ │ │ └── LoginFormAuthenticator.php │ │ └── Front │ │ │ ├── GithubEnAuthenticator.php │ │ │ ├── GithubRusAuthenticator.php │ │ │ ├── GoogleAuthenticator.php │ │ │ ├── LoginFormAuthenticator.php │ │ │ ├── VkontakteAuthenticator.php │ │ │ └── YandexAuthenticator.php │ ├── EntryPoint │ │ ├── AuthenticationAdminEntryPoint.php │ │ └── AuthenticationFrontEntryPoint.php │ ├── Handler │ │ ├── AccessAdminDeniedHandler.php │ │ └── AccessFrontDeniedHandler.php │ ├── Verifier │ │ └── EmailVerifier.php │ └── Voters │ │ ├── AdminOrderEditVoter.php │ │ ├── CartProductVoter.php │ │ └── CartVoter.php └── Utils │ ├── ApiPlatform │ ├── Event │ │ └── Subscriber │ │ │ ├── MakeOrderFromCartSubscriber.php │ │ │ └── SetCartTokenSubscriber.php │ └── Extension │ │ ├── FilterCartQueryExtension.php │ │ └── FilterProductQueryExtension.php │ ├── Authenticator │ └── CheckingUserSocialNetworkBeforeAuthorization.php │ ├── Event │ └── Subscriber │ │ └── MigrationEventSubscriber.php │ ├── Factory │ └── UserFactory.php │ ├── File │ ├── FileSaver.php │ └── ImageResizer.php │ ├── FileSystem │ └── FilesystemWorker.php │ ├── Generator │ ├── PasswordGenerator.php │ └── TokenGenerator.php │ ├── Mailer │ ├── DTO │ │ └── MailerOptionModel.php │ ├── MailerSender.php │ └── Sender │ │ ├── BaseSender.php │ │ ├── OrderCreatedFromCartEmailSender.php │ │ ├── ResetUserPasswordEmailSender.php │ │ ├── UserLoggedInViaSocialNetworkEmailSender.php │ │ └── UserRegisteredEmailSender.php │ ├── Manager │ ├── AbstractBaseManager.php │ ├── CartManager.php │ ├── CategoryManager.php │ ├── OrderManager.php │ ├── ProductImageManager.php │ ├── ProductManager.php │ └── UserManager.php │ └── Oauth2 │ └── Vk │ ├── Vk.php │ └── VkUser.php ├── symfony.lock ├── templates ├── admin │ ├── _embed │ │ ├── _card │ │ │ ├── _breadcrumbs.html.twig │ │ │ ├── _form_buttons.html.twig │ │ │ └── _header_action.html.twig │ │ ├── _filters │ │ │ └── _header.html.twig │ │ ├── _main │ │ │ ├── _sidebar.html.twig │ │ │ └── _topbar.html.twig │ │ └── _utils │ │ │ ├── _error_authenticator.html.twig │ │ │ ├── _flash_message.html.twig │ │ │ └── _modal.html.twig │ ├── base.html.twig │ ├── category │ │ ├── edit.html.twig │ │ └── list.html.twig │ ├── layout │ │ ├── layout_empty_page.html.twig │ │ └── layout_main.html.twig │ ├── order │ │ ├── _embed │ │ │ ├── _table_actions_btn.html.twig │ │ │ └── _table_filtration.html.twig │ │ ├── edit.html.twig │ │ └── list.html.twig │ ├── pages │ │ └── dashboard.html.twig │ ├── product │ │ ├── _embed │ │ │ ├── _table_actions_btn.html.twig │ │ │ └── _table_filtration.html.twig │ │ ├── edit.html.twig │ │ └── list.html.twig │ ├── security │ │ └── login.html.twig │ └── user │ │ ├── edit.html.twig │ │ └── list.html.twig ├── bundles │ └── TwigBundle │ │ └── Exception │ │ └── error.html.twig └── front │ ├── _embed │ ├── _footer.html.twig │ ├── _header.html.twig │ ├── _menu │ │ ├── _desktop_menu.html.twig │ │ ├── _login_via_social_network.html.twig │ │ ├── _menu_cart.html.twig │ │ ├── _menu_nav_item.twig │ │ └── _mobile_menu.html.twig │ ├── _similar_products.html.twig │ └── _utils │ │ ├── _error_authenticator.html.twig │ │ ├── _flash_message.html.twig │ │ └── _social_network_link_unlink_btn.html.twig │ ├── base.html.twig │ ├── cart │ └── show.html.twig │ ├── category │ └── show.html.twig │ ├── default │ └── index.html.twig │ ├── email │ ├── base.html.twig │ ├── client │ │ ├── created_order_from_cart.html.twig │ │ └── user_logged_in_via_social_network.html.twig │ ├── manager │ │ └── created_order_from_cart.html.twig │ └── security │ │ ├── confirmation_email.html.twig │ │ └── reset_password.html.twig │ ├── product │ └── show.html.twig │ ├── profile │ ├── edit.html.twig │ └── index.html.twig │ ├── reset_password │ ├── check_email.html.twig │ ├── request.html.twig │ └── reset.html.twig │ ├── robots.txt.twig │ ├── security │ ├── login.html.twig │ └── registration.html.twig │ └── sitemap.xml.twig ├── tests ├── Functional │ ├── ApiPlatform │ │ ├── ProductResourceTest.php │ │ └── ResourceTestUtils.php │ └── Controller │ │ └── Front │ │ ├── AuthLoginControllerTest.php │ │ ├── DefaultControllerTest.php │ │ └── RegistrationControllerTest.php ├── Integration │ └── Security │ │ └── Verifier │ │ └── EmailVerifierTest.php ├── SymfonyPanther │ └── BasePantherTestCase.php ├── TestUtils │ └── Fixtures │ │ └── UserFixtures.php ├── Unit │ └── Utils │ │ └── Generator │ │ └── PasswordGeneratorTest.php └── bootstrap.php ├── translations ├── LexikFormFilterBundle.en.xlf ├── LexikFormFilterBundle.en.yml ├── LexikFormFilterBundle.ru.xlf ├── LexikFormFilterBundle.ru.yml ├── messages.en.xlf ├── messages.en.yml ├── messages.ru.xlf ├── messages.ru.yml ├── security.ru.xlf ├── security.ru.yml ├── validators.en.xlf ├── validators.en.yml ├── validators.ru.xlf └── validators.ru.yml └── webpack.config.js /.env.panther: -------------------------------------------------------------------------------- 1 | # define your env variables for the test env here 2 | APP_ENV=test 3 | KERNEL_CLASS='App\Kernel' 4 | APP_SECRET='$ecretf0rt3st' 5 | SYMFONY_DEPRECATIONS_HELPER=999999 6 | PANTHER_APP_ENV=panther 7 | PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots 8 | 9 | MAILER_DSN=null://null 10 | MESSENGER_TRANSPORT_DSN=doctrine://default 11 | ADMIN_EMAIL=test@test.test 12 | DATABASE_URL="sqlite:///%kernel.project_dir%/var/db_for_test.db" 13 | 14 | SITE_BASE_SCHEME=https 15 | SITE_BASE_HOST=127.0.0.1:8000 16 | 17 | CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' 18 | 19 | APP_TIMEZONE=Europe/Moscow -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | # define your env variables for the test env here 2 | APP_ENV=test 3 | KERNEL_CLASS='App\Kernel' 4 | APP_SECRET='$ecretf0rt3st' 5 | SYMFONY_DEPRECATIONS_HELPER=999999 6 | PANTHER_APP_ENV=panther 7 | PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots 8 | 9 | MAILER_DSN=null://null 10 | MESSENGER_TRANSPORT_DSN=doctrine://default 11 | ADMIN_EMAIL=test@test.test 12 | DATABASE_URL="sqlite:///%kernel.project_dir%/var/db_for_test.db" 13 | 14 | SITE_BASE_SCHEME=https 15 | SITE_BASE_HOST=127.0.0.1:8000 16 | 17 | CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' 18 | 19 | APP_TIMEZONE=Europe/Moscow -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:vue/recommended', 5 | 'plugin:prettier/recommended', 6 | ], 7 | env: { 8 | node: true, 9 | "jquery": true 10 | }, 11 | }; -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in(__DIR__) 5 | ->exclude('var') 6 | ->exclude('vendor'); 7 | 8 | return (new PhpCsFixer\Config()) 9 | ->setRules([ 10 | '@Symfony' => true, 11 | 'no_superfluous_phpdoc_tags' => true, 12 | 'global_namespace_import' => false, 13 | 'phpdoc_separation' => false, 14 | ]) 15 | ->setFinder($finder); 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Александр Юрченко 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NODE_MODULES = ./node_modules 2 | VENDOR = ./vendor 3 | 4 | ## 5 | ## UTILS 6 | ## ---------- 7 | del-log: 8 | rm -rf ./var/log 9 | 10 | del-cache: 11 | rm -rf ./var/cache 12 | 13 | watch: 14 | npm run watch 15 | 16 | deploy: 17 | php deployer7 deploy 18 | 19 | ## 20 | ## REFACTORING 21 | ## ----------- 22 | 23 | check: 24 | make refactoring --keep-going 25 | 26 | refactoring: eslint php-cs-fixer 27 | 28 | eslint: 29 | ${NODE_MODULES}/.bin/eslint assets/js/ --ext .js,.vue --fix 30 | 31 | php-cs-fixer: 32 | ${VENDOR}/bin/php-cs-fixer fix src/ --verbose 33 | 34 | phpstan: 35 | ${VENDOR}/bin/phpstan analyse src --level 4 36 | 37 | ## 38 | ## TESTING 39 | ## ----------- 40 | 41 | run-test: 42 | sh ./bin/run-tests.sh -------------------------------------------------------------------------------- /assets/css/section/admin/libs.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Nunito:200,300,400,600,700,800,900'); 2 | 3 | @import "~@fortawesome/fontawesome-free/css/all"; 4 | -------------------------------------------------------------------------------- /assets/css/section/admin/main.scss: -------------------------------------------------------------------------------- 1 | .select-language { 2 | width: auto; 3 | &:focus { 4 | outline: none; 5 | text-decoration: none; 6 | box-shadow: none; 7 | border: 1px solid #ced4da; 8 | } 9 | } -------------------------------------------------------------------------------- /assets/css/section/front/_alert.css: -------------------------------------------------------------------------------- 1 | .alert { 2 | font-weight: 300; 3 | letter-spacing: -.01em; 4 | border-radius: 0; 5 | font-size: 1.4rem; 6 | line-height: 1.43; 7 | margin: 0; 8 | padding: 1rem 1.5rem; 9 | border: none; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /assets/css/section/front/_button.css: -------------------------------------------------------------------------------- 1 | .btn { 2 | cursor: pointer; 3 | } 4 | 5 | .btn-link, .btn-success, .btn-cancel { 6 | font-size: 1.2rem; 7 | text-transform: uppercase; 8 | padding: 10px !important; 9 | border: none; 10 | color: white; 11 | } 12 | 13 | .btn-link:hover, .btn-success:hover { 14 | color: white !important; 15 | text-decoration: none; 16 | } 17 | 18 | .btn-link { 19 | border-bottom: 4px solid #ff59bd !important; 20 | background-color: transparent; 21 | } 22 | 23 | .btn-link:hover { 24 | background: linear-gradient(to right, #da2893 2%,#93002F 82%); 25 | } 26 | 27 | .btn-success { 28 | border-bottom: 4px solid #799647 !important; 29 | background-color: #a6c76c; 30 | } 31 | 32 | .btn-success:hover { 33 | background: linear-gradient(to right, #a6c76c 2%, #799647 82%); 34 | } 35 | 36 | .btn-cancel { 37 | border-bottom: 4px solid #909090 !important; 38 | background-color: #f7f7f7; 39 | } 40 | 41 | .btn-cancel:hover { 42 | color: #333 !important; 43 | text-decoration: none; 44 | background: linear-gradient(to right, #f7f7f7 2%, #dedede 82%); 45 | } 46 | 47 | .btn-dark { 48 | color: #333; 49 | } 50 | 51 | -------------------------------------------------------------------------------- /assets/css/section/front/_cart.css: -------------------------------------------------------------------------------- 1 | .table-cart .product-title { 2 | font-size: 1.4rem; 3 | text-decoration: none; 4 | } 5 | 6 | .table-cart .product-title a { 7 | color: #212529; 8 | } 9 | 10 | .table-cart .total-col { 11 | width: 80px; 12 | color: #dc143c; 13 | font-size: 1.6rem; 14 | } 15 | 16 | .table-cart .price-col { 17 | width: 120px; 18 | } 19 | 20 | .table-cart .quantity-col { 21 | width: 135px; 22 | } 23 | 24 | .table-cart .quantity-col .form-control { 25 | height: 40px; 26 | text-align: center; 27 | } 28 | 29 | .table-cart .product-col figure { 30 | #margin: 15px 40px; 31 | } 32 | 33 | .table-cart figure img { 34 | margin-left: auto; 35 | margin-right: auto; 36 | } 37 | 38 | .table-cart td, .table-cart th { 39 | vertical-align: middle; 40 | } 41 | -------------------------------------------------------------------------------- /assets/css/section/front/_footer.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | width: 100%; 3 | padding: 4rem 0 1rem; 4 | border-top: .1rem solid #ebebeb; 5 | font-size: 1.4rem; 6 | color: #777; 7 | text-align: center; 8 | background: #333; 9 | background: -webkit-linear-gradient(left, #565656 2%,#333 82%); 10 | background: -moz-linear-gradient(left, #565656 2%, #333 82%); 11 | background: linear-gradient(to right, #565656 2%,#333 82%); 12 | } 13 | 14 | .footer a { 15 | color: inherit; 16 | } 17 | 18 | .footer .widget-title { 19 | margin-top: 0; 20 | margin-bottom: 1rem; 21 | font-weight: 400; 22 | letter-spacing: .1em; 23 | color: #fff; 24 | text-transform: uppercase; 25 | font-size: 1.2rem; 26 | } 27 | 28 | .footer .widget-copyright { 29 | font-size: 1rem; 30 | } 31 | 32 | .widget-list { 33 | margin-bottom: 0; 34 | padding: 0; 35 | } 36 | 37 | .widget-list li:not(:last-child) { 38 | margin-bottom: .4rem; 39 | } 40 | 41 | .widget-list li { 42 | display: inline; 43 | margin-right: 5px; 44 | font-size: 1rem; 45 | } 46 | 47 | .widget-list li:after { 48 | content: " \00b7"; 49 | font-weight: 700; 50 | vertical-align: middle; 51 | padding: 0 10px; 52 | } 53 | 54 | .widget-list li:last-child:after { 55 | content: none; 56 | } 57 | -------------------------------------------------------------------------------- /assets/css/section/front/_header.css: -------------------------------------------------------------------------------- 1 | .header { 2 | width: 100%; 3 | border-bottom: none; 4 | height: auto; 5 | top: 0; 6 | z-index: 99; 7 | background: #ffffff; 8 | -webkit-box-shadow: 0 10px 10px -10px rgba(0, 0, 0, 0.35); 9 | box-shadow: 0 10px 10px -10px rgba(0, 0, 0, 0.35); 10 | } 11 | 12 | .header .container-fluid { 13 | display: flex; 14 | align-items: center; 15 | justify-content: center; 16 | } 17 | 18 | .logotype { 19 | display: block; 20 | min-height: 30px; 21 | } 22 | 23 | .logotype img { 24 | display: block; 25 | width: 100%; 26 | min-height: 30px; 27 | } 28 | 29 | .mobile-menu-toggler { 30 | border: none; 31 | background: transparent; 32 | color: #666666; 33 | padding: .2rem .25rem; 34 | font-size: 2.8rem; 35 | line-height: 1; 36 | display: flex; 37 | align-items: center; 38 | justify-content: center; 39 | cursor: pointer; 40 | margin-left: 1rem; 41 | margin-right: 1rem; 42 | } 43 | 44 | @media screen and (min-width: 1100px) { 45 | .header { 46 | height: 110px; 47 | } 48 | .mobile-menu-toggler { 49 | display: none; 50 | } 51 | .logotype img { 52 | display: block; 53 | max-width: 100%; 54 | height: auto; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /assets/css/section/front/_pagination.css: -------------------------------------------------------------------------------- 1 | .page-item { 2 | margin: 0 .2rem; 3 | } 4 | 5 | .page-item.active .btn-nav { 6 | color: #f13c76; 7 | border-color: #e8e8e8; 8 | } 9 | 10 | .btn-nav { 11 | display: inline-flex; 12 | align-items: center; 13 | justify-content: center; 14 | min-width: 3rem; 15 | height: 3rem; 16 | font-weight: 400; 17 | font-size: 1rem; 18 | color: #706f6c; 19 | border-radius: .3rem; 20 | background-color: transparent; 21 | border: .1rem solid transparent; 22 | } 23 | 24 | .btn-nav.btn-nav-prev i { 25 | margin-right: 1rem; 26 | } 27 | 28 | .btn-nav.btn-nav-next i { 29 | margin-left: 1rem; 30 | } 31 | 32 | .btn-nav.btn-nav-prev, .btn-nav.btn-nav-next { 33 | padding: 0 1rem; 34 | min-width: 60px; 35 | } 36 | 37 | .btn-nav:hover, .btn-nav:focus { 38 | box-shadow: none; 39 | color: #f13c76; 40 | border-color: #e3e3e3; 41 | text-decoration: none; 42 | outline: none !important; 43 | } 44 | 45 | .page-item-total { 46 | color: #cccccc; 47 | font-size: 1.4rem; 48 | font-weight: 400; 49 | margin: 0 .2rem; 50 | } 51 | 52 | -------------------------------------------------------------------------------- /assets/css/section/front/_spinner.css: -------------------------------------------------------------------------------- 1 | .spinner-dual-ring { 2 | position: absolute; 3 | display: inline-block; 4 | width: 80px; 5 | height: 80px; 6 | padding-left: 50%; 7 | padding-top: 50px; 8 | transform: translateY(-50%); 9 | } 10 | .spinner-dual-ring:after { 11 | content: " "; 12 | display: block; 13 | width: 64px; 14 | height: 64px; 15 | margin: 8px; 16 | border-radius: 50%; 17 | border: 6px solid #fff; 18 | border-color: #ff59bd transparent #ff59bd transparent; 19 | animation: spinner-dual-ring 1.2s linear infinite; 20 | } 21 | @keyframes spinner-dual-ring { 22 | 0% { 23 | transform: rotate(0deg); 24 | } 25 | 100% { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /assets/css/section/front/email.scss: -------------------------------------------------------------------------------- 1 | @import "~foundation-emails"; 2 | @import url('https://fonts.googleapis.com/css?family=Roboto:300,400,500,700'); 3 | 4 | body { 5 | margin: 0; 6 | padding: 0; 7 | font-family: 'Roboto', sans-serif; 8 | background-color: #f8f9fa; 9 | } 10 | .header { 11 | text-align: center; 12 | background-color: #f8f9fa; 13 | } 14 | .footer { 15 | background-color: #f8f9fa; 16 | color: #ababab; 17 | } 18 | .footer p { 19 | padding: 20px; 20 | } 21 | .data-table { 22 | width: 100%; 23 | } 24 | .data-table thead { 25 | border-bottom: 1px solid #ececec; 26 | } 27 | .data-table td { 28 | padding: 20px; 29 | } 30 | -------------------------------------------------------------------------------- /assets/css/section/front/libs.scss: -------------------------------------------------------------------------------- 1 | @import "~bootstrap/scss/bootstrap"; 2 | @import "~@fortawesome/fontawesome-free/css/all"; 3 | @import "main"; 4 | -------------------------------------------------------------------------------- /assets/css/section/front/main.scss: -------------------------------------------------------------------------------- 1 | #select-language { 2 | width: auto; 3 | 4 | &:focus { 5 | outline: none; 6 | text-decoration: none; 7 | box-shadow: none; 8 | border: 1px solid #ced4da; 9 | } 10 | } 11 | 12 | .social-group { 13 | display: inline-block; 14 | font-weight: 400; 15 | color: #212529; 16 | text-align: center; 17 | vertical-align: middle; 18 | user-select: none; 19 | background-color: transparent; 20 | border: 1px solid transparent; 21 | padding: 0.375rem 0.75rem; 22 | font-size: 1rem; 23 | line-height: 1.5; 24 | border-radius: 0.25rem; 25 | transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; 26 | 27 | &--unlink { 28 | color: #dc3545; 29 | background-color: #f4f4f4; 30 | border-color: #dc3545; 31 | } 32 | 33 | &--link { 34 | color: #fff; 35 | border-color: #28a745; 36 | background-color: #28a745; 37 | 38 | &:hover { 39 | color: #fff; 40 | cursor: pointer; 41 | text-decoration: none; 42 | } 43 | 44 | &:focus { 45 | color: #fff; 46 | text-decoration: none; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /assets/images/banner/collection/banner_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaleksandr89/symfony-shop/c18698d9fc881cac80270695ddf88a6888c4a9a4/assets/images/banner/collection/banner_1.jpg -------------------------------------------------------------------------------- /assets/images/banner/collection/banner_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaleksandr89/symfony-shop/c18698d9fc881cac80270695ddf88a6888c4a9a4/assets/images/banner/collection/banner_2.jpg -------------------------------------------------------------------------------- /assets/images/banner/collection/banner_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaleksandr89/symfony-shop/c18698d9fc881cac80270695ddf88a6888c4a9a4/assets/images/banner/collection/banner_3.jpg -------------------------------------------------------------------------------- /assets/images/banner/collection/banner_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaleksandr89/symfony-shop/c18698d9fc881cac80270695ddf88a6888c4a9a4/assets/images/banner/collection/banner_4.jpg -------------------------------------------------------------------------------- /assets/images/banner/collection/banner_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaleksandr89/symfony-shop/c18698d9fc881cac80270695ddf88a6888c4a9a4/assets/images/banner/collection/banner_5.jpg -------------------------------------------------------------------------------- /assets/images/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaleksandr89/symfony-shop/c18698d9fc881cac80270695ddf88a6888c4a9a4/assets/images/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /assets/images/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaleksandr89/symfony-shop/c18698d9fc881cac80270695ddf88a6888c4a9a4/assets/images/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /assets/images/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaleksandr89/symfony-shop/c18698d9fc881cac80270695ddf88a6888c4a9a4/assets/images/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /assets/images/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /assets/images/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaleksandr89/symfony-shop/c18698d9fc881cac80270695ddf88a6888c4a9a4/assets/images/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /assets/images/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaleksandr89/symfony-shop/c18698d9fc881cac80270695ddf88a6888c4a9a4/assets/images/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /assets/images/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaleksandr89/symfony-shop/c18698d9fc881cac80270695ddf88a6888c4a9a4/assets/images/favicon/favicon.ico -------------------------------------------------------------------------------- /assets/images/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaleksandr89/symfony-shop/c18698d9fc881cac80270695ddf88a6888c4a9a4/assets/images/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /assets/images/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /assets/images/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaleksandr89/symfony-shop/c18698d9fc881cac80270695ddf88a6888c4a9a4/assets/images/icons/favicon.ico -------------------------------------------------------------------------------- /assets/images/icons/logo_366x79.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaleksandr89/symfony-shop/c18698d9fc881cac80270695ddf88a6888c4a9a4/assets/images/icons/logo_366x79.png -------------------------------------------------------------------------------- /assets/images/icons/short-logo_115x115.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /assets/js/section/admin/admin-order/App.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 36 | -------------------------------------------------------------------------------- /assets/js/section/admin/admin-order/app.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App"; 3 | import store from "./store"; 4 | 5 | if (document.getElementById("app")) { 6 | new Vue({ 7 | el: "#app", 8 | store, 9 | render: (h) => h(App), 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /assets/js/section/admin/admin-order/components/TotalPriceBlock.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 30 | -------------------------------------------------------------------------------- /assets/js/section/admin/admin-order/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import products from "./modules/products"; 4 | 5 | Vue.use(Vuex); 6 | 7 | const debug = process.env.NODE_ENV !== "production"; 8 | 9 | export default new Vuex.Store({ 10 | modules: { 11 | products, 12 | }, 13 | strict: debug, 14 | }); 15 | -------------------------------------------------------------------------------- /assets/js/section/admin/theme/filters-feature.js: -------------------------------------------------------------------------------- 1 | import { getCookie, setCookie } from "../../../utils/cookie-manager"; 2 | 3 | window.toggleFiltersVisibility = function toggleFiltersVisibility(section) { 4 | const filtersKey = "filtersVisible_" + section; 5 | const filtersSaveValue = getCookie(filtersKey); 6 | 7 | const visibleValue = filtersSaveValue === "false"; 8 | 9 | setCookie(filtersKey, visibleValue, { 10 | secure: true, 11 | "max-age": 3600, 12 | }); 13 | }; 14 | 15 | window.changeFiltersBlockView = function changeFiltersBlockView( 16 | filterSection, 17 | element 18 | ) { 19 | const filtersKey = "filtersVisible_" + filterSection; 20 | const filtersSaveValue = getCookie(filtersKey); 21 | 22 | element.style.display = filtersSaveValue === "false" ? "block" : "none"; 23 | }; 24 | -------------------------------------------------------------------------------- /assets/js/section/front/front-cart-show/app.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App"; 3 | import store from "./store"; 4 | 5 | if (document.getElementById("app")) { 6 | new Vue({ 7 | el: "#app", 8 | store, 9 | render: (h) => h(App), 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /assets/js/section/front/front-cart-show/components/Alert.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | -------------------------------------------------------------------------------- /assets/js/section/front/front-cart-show/components/CartProductList.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 34 | -------------------------------------------------------------------------------- /assets/js/section/front/front-cart-show/components/CartTotalPrice.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /assets/js/section/front/front-cart-show/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import cart from "./modules/cart"; 4 | 5 | Vue.use(Vuex); 6 | 7 | const debug = process.env.NODE_ENV !== "production"; 8 | 9 | export default new Vuex.Store({ 10 | modules: { 11 | cart, 12 | }, 13 | strict: debug, 14 | }); 15 | -------------------------------------------------------------------------------- /assets/js/section/front/menu-cart/App.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 45 | -------------------------------------------------------------------------------- /assets/js/section/front/menu-cart/app.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App"; 3 | import store from "./store"; 4 | 5 | if (document.getElementById("appFrontMenuCart")) { 6 | const vueMenuCartInstance = new Vue({ 7 | el: "#appFrontMenuCart", 8 | store, 9 | render: (h) => h(App), 10 | }); 11 | 12 | window.vueMenuCartInstance = {}; 13 | window.vueMenuCartInstance.addCartProduct = (productData) => 14 | vueMenuCartInstance.$store.dispatch("cart/addCartProduct", productData); 15 | window.vueMenuCartInstance.setCart = () => 16 | vueMenuCartInstance.$store.commit("cart/setCart", {}); 17 | } 18 | -------------------------------------------------------------------------------- /assets/js/section/front/menu-cart/components/CartActions.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 25 | -------------------------------------------------------------------------------- /assets/js/section/front/menu-cart/components/CartProductList.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 23 | -------------------------------------------------------------------------------- /assets/js/section/front/menu-cart/components/CartTotalPrice.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /assets/js/section/front/menu-cart/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import cart from "./modules/cart"; 4 | 5 | Vue.use(Vuex); 6 | 7 | const debug = process.env.NODE_ENV !== "production"; 8 | 9 | export default new Vuex.Store({ 10 | modules: { 11 | cart, 12 | }, 13 | strict: debug, 14 | }); 15 | -------------------------------------------------------------------------------- /assets/js/section/front/utils/menu.js: -------------------------------------------------------------------------------- 1 | document.getElementById("mobile_menu_toggler").addEventListener("click", () => { 2 | document.getElementById("mobile_menu_container").classList.add("show"); 3 | }); 4 | document 5 | .getElementById("mobile_menu_close_btn") 6 | .addEventListener("click", () => { 7 | document.getElementById("mobile_menu_container").classList.remove("show"); 8 | }); 9 | -------------------------------------------------------------------------------- /assets/js/utils/changed-locale.js: -------------------------------------------------------------------------------- 1 | let $desktopSelect = $("#desktop-select-language"); 2 | let $mobileSelect = $("#mobile-select-language"); 3 | changedLanguageSite($desktopSelect, $desktopSelect.find("option")); 4 | changedLanguageSite($mobileSelect, $mobileSelect.find("option")); 5 | 6 | function changedLanguageSite(selector, childEl) { 7 | let locale = location.pathname; 8 | 9 | if ("undefined" !== typeof locale || "" !== locale) { 10 | childEl.each(function () { 11 | let $this = $(this); 12 | if (locale.includes($this.attr("value"))) { 13 | this.selected = true; 14 | } 15 | }); 16 | } 17 | 18 | selector.on("change", function (event) { 19 | event.preventDefault(); 20 | let selectedOptionsIndex = event.target.options.selectedIndex; 21 | 22 | childEl.each(function (key, value) { 23 | if (key.toString() === selectedOptionsIndex.toString()) { 24 | let currentLocale = $(value).attr("value").replace(/\//g, ""); 25 | let preparedUrl = $(value).attr("data-url").split("/"); 26 | preparedUrl[1] = currentLocale; 27 | 28 | location.href = preparedUrl.join("/"); 29 | } 30 | }); 31 | return false; 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /assets/js/utils/cookie-manager.js: -------------------------------------------------------------------------------- 1 | /** @see https://learn.javascript.ru/cookie */ 2 | export function setCookie(name, value, options = {}) { 3 | options = { 4 | path: "/", 5 | ...options, 6 | }; 7 | 8 | if (options.expires instanceof Date) { 9 | options.expires = options.expires.toUTCString(); 10 | } 11 | 12 | let updatedCookie = 13 | encodeURIComponent(name) + "=" + encodeURIComponent(value); 14 | 15 | for (let optionKey in options) { 16 | updatedCookie += "; " + optionKey; 17 | let optionValue = options[optionKey]; 18 | if (optionValue !== true) { 19 | updatedCookie += "=" + optionValue; 20 | } 21 | } 22 | 23 | document.cookie = updatedCookie; 24 | } 25 | 26 | export function getCookie(name) { 27 | let matches = document.cookie.match( 28 | /* eslint-disable */ 29 | new RegExp( 30 | "(?:^|; )" + 31 | name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, "\\$1") + 32 | "=([^;]*)" 33 | ) 34 | ); 35 | return matches ? decodeURIComponent(matches[1]) : undefined; 36 | } 37 | -------------------------------------------------------------------------------- /assets/js/utils/settings.js: -------------------------------------------------------------------------------- 1 | export const apiConfig = { 2 | headers: { 3 | accept: "application/ld+json", 4 | "Content-Type": "application/json", 5 | }, 6 | }; 7 | 8 | export const apiConfigPatch = { 9 | headers: { 10 | accept: "application/ld+json", 11 | "Content-Type": "application/merge-patch+json", 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /assets/js/utils/title-formatter.js: -------------------------------------------------------------------------------- 1 | export function getProductInformativeTitle(product) { 2 | return ( 3 | "#" + 4 | product.id + 5 | " " + 6 | product.title + 7 | " / P: $" + 8 | product.price + 9 | " / Q: " + 10 | product.quantity 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /assets/js/utils/url-generator.js: -------------------------------------------------------------------------------- 1 | export function getUrlViewProduct(viewUrl, productId) { 2 | return ( 3 | window.location.protocol + 4 | "//" + 5 | window.location.host + 6 | viewUrl + 7 | "/" + 8 | productId 9 | ); 10 | } 11 | 12 | export function concatUrlByParams(...params) { 13 | return params.join("/"); 14 | } 15 | 16 | export function getUrlProductsByCategory( 17 | defaultUrl, 18 | categoryId, 19 | page, 20 | itemsPerPage 21 | ) { 22 | return ( 23 | defaultUrl + 24 | "?category=/api/categories/" + 25 | categoryId + 26 | "&isPublished=true" + 27 | "&page=" + 28 | page + 29 | "&itemsPerPage=" + 30 | itemsPerPage 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /assets/section-admin.js: -------------------------------------------------------------------------------- 1 | const $ = require('jquery'); 2 | require('bootstrap'); 3 | 4 | global.$ = global.jQuery = $; 5 | 6 | require('jquery.easing'); 7 | require('chart.js'); 8 | 9 | require('./js/section/admin/theme/sb-admin-2'); 10 | require('./js/utils/changed-locale'); 11 | require('./js/section/admin/theme/filters-feature'); 12 | 13 | import './css/section/admin/libs.scss'; 14 | import './css/section/admin/sb-admin-2.css'; 15 | import './css/section/admin/styles.css'; 16 | import './css/section/admin/main.scss'; 17 | -------------------------------------------------------------------------------- /assets/section-front.js: -------------------------------------------------------------------------------- 1 | const $ = require('jquery'); 2 | require('bootstrap'); 3 | 4 | global.$ = global.jQuery = $; 5 | 6 | import './css/section/front/libs.scss'; 7 | 8 | import './css/section/front/_alert.css'; 9 | import './css/section/front/_banner.css'; 10 | import './css/section/front/_base.css'; 11 | import './css/section/front/_button.css'; 12 | import './css/section/front/_cart.css'; 13 | import './css/section/front/_cart_dropdown.css'; 14 | import './css/section/front/_footer.css'; 15 | import './css/section/front/_form.css'; 16 | import './css/section/front/_header.css'; 17 | import './css/section/front/_header_menu.css'; 18 | import './css/section/front/_pagination.css'; 19 | import './css/section/front/_product.css'; 20 | import './css/section/front/_product_list.css'; 21 | import './css/section/front/_spinner.css'; 22 | import './css/section/front/_typography.css'; 23 | 24 | require('./js/section/front/utils/menu'); 25 | require('./js/utils/changed-locale'); -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | ' 10 | dsn: '%env(MAILER_DSN)%' 11 | -------------------------------------------------------------------------------- /config/packages/messenger.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | messenger: 3 | # Uncomment this (and the failed transport below) to send failed messages to this transport for later handling. 4 | # failure_transport: failed 5 | default_bus: command.bus 6 | buses: 7 | command.bus: ~ 8 | event.bus: ~ 9 | 10 | transports: 11 | # https://symfony.com/doc/current/messenger.html#transport-configuration 12 | async: 13 | dsn: '%env(MESSENGER_TRANSPORT_DSN)%' 14 | retry_strategy: 15 | delay: 500 16 | # failed: 'doctrine://default?queue_name=failed' 17 | # sync: 'sync://' 18 | 19 | routing: 20 | # Route your messages to the transports 21 | 'App\Messenger\Message\Event\EventUserRegisteredEvent': async 22 | 'App\Messenger\Message\Command\ResetUserPasswordCommand': async 23 | -------------------------------------------------------------------------------- /config/packages/nelmio_cors.yaml: -------------------------------------------------------------------------------- 1 | nelmio_cors: 2 | defaults: 3 | origin_regex: true 4 | allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] 5 | allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] 6 | allow_headers: ['Content-Type', 'Authorization'] 7 | expose_headers: ['Link'] 8 | max_age: 3600 9 | paths: 10 | '^/': null 11 | -------------------------------------------------------------------------------- /config/packages/notifier.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | notifier: 3 | #chatter_transports: 4 | # slack: '%env(SLACK_DSN)%' 5 | # telegram: '%env(TELEGRAM_DSN)%' 6 | #texter_transports: 7 | # twilio: '%env(TWILIO_DSN)%' 8 | # nexmo: '%env(NEXMO_DSN)%' 9 | channel_policy: 10 | # use chat/slack, chat/telegram, sms/twilio or sms/nexmo 11 | urgent: [ 'email' ] 12 | high: [ 'email' ] 13 | medium: [ 'email' ] 14 | low: [ 'email' ] 15 | admin_recipients: 16 | - { email: admin@example.com } 17 | -------------------------------------------------------------------------------- /config/packages/php_translation.yaml: -------------------------------------------------------------------------------- 1 | translation: 2 | locales: ['ru', 'en'] 3 | edit_in_place: 4 | enabled: false 5 | config_name: app 6 | configs: 7 | app: 8 | dirs: ["%kernel.project_dir%/templates", "%kernel.project_dir%/src"] 9 | output_dir: "%kernel.project_dir%/translations" 10 | excluded_names: ["*TestCase.php", "*Test.php"] 11 | excluded_dirs: [cache, data, logs] 12 | -------------------------------------------------------------------------------- /config/packages/prod/deprecations.yaml: -------------------------------------------------------------------------------- 1 | # As of Symfony 5.1, deprecations are logged in the dedicated "deprecation" channel when it exists 2 | #monolog: 3 | # channels: [deprecation] 4 | # handlers: 5 | # deprecation: 6 | # type: stream 7 | # channels: [deprecation] 8 | # path: php://stderr 9 | -------------------------------------------------------------------------------- /config/packages/prod/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | orm: 3 | auto_generate_proxy_classes: false 4 | query_cache_driver: 5 | type: pool 6 | pool: doctrine.system_cache_pool 7 | result_cache_driver: 8 | type: pool 9 | pool: doctrine.result_cache_pool 10 | 11 | framework: 12 | cache: 13 | pools: 14 | doctrine.result_cache_pool: 15 | adapter: cache.app 16 | doctrine.system_cache_pool: 17 | adapter: cache.system 18 | -------------------------------------------------------------------------------- /config/packages/prod/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: nested 7 | excluded_http_codes: [ 404, 405 ] 8 | buffer_size: 50 # How many messages should be saved? Prevent memory leaks 9 | nested: 10 | type: rotating_file 11 | path: "%kernel.logs_dir%/%kernel.environment%.log" 12 | level: debug 13 | max_files: 14 14 | formatter: monolog.formatter.json 15 | console: 16 | type: console 17 | process_psr_3_messages: false 18 | channels: [ "!event", "!doctrine" ] 19 | -------------------------------------------------------------------------------- /config/packages/prod/webpack_encore.yaml: -------------------------------------------------------------------------------- 1 | #webpack_encore: 2 | # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes) 3 | # Available in version 1.2 4 | #cache: true 5 | -------------------------------------------------------------------------------- /config/packages/reset_password.yaml: -------------------------------------------------------------------------------- 1 | symfonycasts_reset_password: 2 | request_password_repository: App\Repository\ResetPasswordRequestRepository 3 | -------------------------------------------------------------------------------- /config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | utf8: true 4 | 5 | # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. 6 | # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands 7 | #default_uri: http://localhost 8 | 9 | when@prod: 10 | framework: 11 | router: 12 | strict_requirements: null 13 | -------------------------------------------------------------------------------- /config/packages/sensio_framework_extra.yaml: -------------------------------------------------------------------------------- 1 | sensio_framework_extra: 2 | router: 3 | annotations: false 4 | -------------------------------------------------------------------------------- /config/packages/stof_doctrine_extensions.yaml: -------------------------------------------------------------------------------- 1 | # Read the documentation: https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html 2 | # See the official DoctrineExtensions documentation for more details: https://github.com/Atlantic18/DoctrineExtensions/tree/master/doc/ 3 | stof_doctrine_extensions: 4 | default_locale: en_US 5 | orm: 6 | default: 7 | sluggable: true 8 | -------------------------------------------------------------------------------- /config/packages/test/dama_doctrine_test_bundle.yaml: -------------------------------------------------------------------------------- 1 | dama_doctrine_test: 2 | enable_static_connection: true 3 | enable_static_meta_data_cache: true 4 | enable_static_query_cache: true 5 | -------------------------------------------------------------------------------- /config/packages/test/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | dbal: 3 | # "TEST_TOKEN" is typically set by ParaTest 4 | dbname_suffix: '_test%env(default::TEST_TOKEN)%' 5 | url: "sqlite:///%kernel.project_dir%/var/db_for_test.db" -------------------------------------------------------------------------------- /config/packages/test/hautelook_alice.yaml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: ../dev/hautelook_alice.yaml } 3 | -------------------------------------------------------------------------------- /config/packages/test/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: nested 7 | excluded_http_codes: [ 404, 405 ] 8 | channels: [ "!event" ] 9 | nested: 10 | type: stream 11 | path: "%kernel.logs_dir%/%kernel.environment%.log" 12 | level: debug 13 | -------------------------------------------------------------------------------- /config/packages/test/nelmio_alice.yaml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: ../dev/nelmio_alice.yaml } 3 | -------------------------------------------------------------------------------- /config/packages/test/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | not_compromised_password: false 4 | -------------------------------------------------------------------------------- /config/packages/test/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: false 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { collect: false } 7 | -------------------------------------------------------------------------------- /config/packages/test/webpack_encore.yaml: -------------------------------------------------------------------------------- 1 | #webpack_encore: 2 | # strict_mode: false 3 | -------------------------------------------------------------------------------- /config/packages/translation.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | default_locale: ru 3 | translator: 4 | default_path: '%kernel.project_dir%/translations' 5 | fallbacks: 6 | - ru 7 | # providers: 8 | # crowdin: 9 | # dsn: '%env(CROWDIN_DSN)%' 10 | # loco: 11 | # dsn: '%env(LOCO_DSN)%' 12 | # lokalise: 13 | # dsn: '%env(LOKALISE_DSN)%' 14 | -------------------------------------------------------------------------------- /config/packages/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | paths: 3 | 'assets/images': images 4 | 'public': public 5 | default_path: '%kernel.project_dir%/templates' 6 | 7 | when@test: 8 | twig: 9 | strict_variables: true 10 | -------------------------------------------------------------------------------- /config/packages/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | email_validation_mode: html5 4 | 5 | # Enables validator auto-mapping support. 6 | # For instance, basic validation constraints will be inferred from Doctrine's metadata. 7 | #auto_mapping: 8 | # App\Entity\: [] 9 | -------------------------------------------------------------------------------- /config/packages/webpack_encore.yaml: -------------------------------------------------------------------------------- 1 | webpack_encore: 2 | # The path where Encore is building the assets - i.e. Encore.setOutputPath() 3 | output_path: '%kernel.project_dir%/public/build' 4 | # If multiple builds are defined (as shown below), you can disable the default build: 5 | # output_path: false 6 | 7 | # Set attributes that will be rendered on all script and link tags 8 | script_attributes: 9 | defer: true 10 | # Uncomment (also under link_attributes) if using Turbo Drive 11 | # https://turbo.hotwired.dev/handbook/drive#reloading-when-assets-change 12 | # 'data-turbo-track': reload 13 | # link_attributes: 14 | # Uncomment if using Turbo Drive 15 | # 'data-turbo-track': reload 16 | 17 | # If using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials') 18 | # crossorigin: 'anonymous' 19 | 20 | # Preload all rendered script and link tags automatically via the HTTP/2 Link header 21 | # preload: true 22 | 23 | # Throw an exception if the entrypoints.json file is missing or an entry is missing from the data 24 | # strict_mode: false 25 | 26 | # If you have multiple builds: 27 | # builds: 28 | # pass "frontend" as the 3rg arg to the Twig functions 29 | # {{ encore_entry_script_tags('entry1', null, 'frontend') }} 30 | 31 | # frontend: '%kernel.project_dir%/public/frontend/build' 32 | 33 | # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes) 34 | # Put in config/packages/prod/webpack_encore.yaml 35 | # cache: true 36 | -------------------------------------------------------------------------------- /config/preload.php: -------------------------------------------------------------------------------- 1 | 4 | price: 5 | quantity: 6 | category: '@category_sneakers' 7 | isPublished: 1 -------------------------------------------------------------------------------- /fixtures/user.yaml: -------------------------------------------------------------------------------- 1 | App\Entity\User: 2 | user_1: 3 | email: test1@test.com 4 | # password decoded: test1test1 | add "\" before "$" 5 | password: '\$2y\$13\$FGkTIKTGahgHHQjXst/KPenIr1bsLjXbyTlYN8vShWtZD4D8cEPCK' 6 | isVerified: 1 7 | roles: '[ROLE_SUPER_ADMIN]' 8 | user_2: 9 | email: test2@test.com 10 | # password decoded: test2test2 | add "\" before "$" 11 | password: '\$2y\$13\$aYxXLEQWaZ7NSFVUhUIlIeV9bdmrs6pr5mAb.mvS0WH7yHFoen13G' 12 | isVerified: 1 13 | roles: '[ROLE_ADMIN]' 14 | user_3: 15 | email: test3@test.com 16 | # password decoded: test3test3 | add "\" before "$" 17 | password: '\$2y\$13\$FtG2KFbS5Oue3sr2xVi98OCYeLO00vuR10WoKu5nxa1cDEaa5wSkO' 18 | isVerified: 1 19 | roles: '[ROLE_USER]' 20 | -------------------------------------------------------------------------------- /migrations/Version20210923235603.php: -------------------------------------------------------------------------------- 1 | addSql('CREATE SEQUENCE product_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); 24 | $this->addSql('CREATE TABLE product (id INT NOT NULL, title VARCHAR(255) NOT NULL, price NUMERIC(6, 2) NOT NULL, quantity INT NOT NULL, create_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, description TEXT DEFAULT NULL, is_published BOOLEAN NOT NULL, is_deleted BOOLEAN NOT NULL, PRIMARY KEY(id))'); 25 | $this->addSql('COMMENT ON COLUMN product.create_at IS \'(DC2Type:datetime_immutable)\''); 26 | } 27 | 28 | public function down(Schema $schema): void 29 | { 30 | // this down() migration is auto-generated, please modify it to your needs 31 | $this->addSql('DROP SEQUENCE product_id_seq CASCADE'); 32 | $this->addSql('DROP TABLE product'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /migrations/Version20210924222154.php: -------------------------------------------------------------------------------- 1 | addSql('CREATE SEQUENCE "user_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); 24 | $this->addSql('CREATE TABLE "user" (id INT NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); 25 | $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649E7927C74 ON "user" (email)'); 26 | } 27 | 28 | public function down(Schema $schema): void 29 | { 30 | // this down() migration is auto-generated, please modify it to your needs 31 | $this->addSql('DROP SEQUENCE "user_id_seq" CASCADE'); 32 | $this->addSql('DROP TABLE "user"'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /migrations/Version20210924223952.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE "user" ADD is_verified BOOLEAN NOT NULL'); 24 | } 25 | 26 | public function down(Schema $schema): void 27 | { 28 | // this down() migration is auto-generated, please modify it to your needs 29 | $this->addSql('CREATE SCHEMA public'); 30 | $this->addSql('ALTER TABLE "user" DROP is_verified'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /migrations/Version20210927185157.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE "user" ADD full_name VARCHAR(255) DEFAULT NULL'); 24 | $this->addSql('ALTER TABLE "user" ADD phone VARCHAR(30) DEFAULT NULL'); 25 | $this->addSql('ALTER TABLE "user" ADD address VARCHAR(255) DEFAULT NULL'); 26 | $this->addSql('ALTER TABLE "user" ADD zip_code INT DEFAULT NULL'); 27 | $this->addSql('ALTER TABLE "user" ADD is_deleted BOOLEAN DEFAULT NULL'); 28 | } 29 | 30 | public function down(Schema $schema): void 31 | { 32 | // this down() migration is auto-generated, please modify it to your needs 33 | $this->addSql('ALTER TABLE "user" DROP full_name'); 34 | $this->addSql('ALTER TABLE "user" DROP phone'); 35 | $this->addSql('ALTER TABLE "user" DROP address'); 36 | $this->addSql('ALTER TABLE "user" DROP zip_code'); 37 | $this->addSql('ALTER TABLE "user" DROP is_deleted'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /migrations/Version20210927185503.php: -------------------------------------------------------------------------------- 1 | addSql('UPDATE "user" SET is_deleted=\'0\' WHERE is_deleted IS NULL'); 24 | } 25 | 26 | public function down(Schema $schema): void 27 | { 28 | // this down() migration is auto-generated, please modify it to your needs 29 | $this->addSql('UPDATE "user" SET is_deleted=NULL WHERE is_deleted IS NOT NULL'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /migrations/Version20210927190540.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE "user" ALTER is_deleted SET NOT NULL'); 24 | } 25 | 26 | public function down(Schema $schema): void 27 | { 28 | // this down() migration is auto-generated, please modify it to your needs 29 | $this->addSql('ALTER TABLE "user" ALTER is_deleted DROP NOT NULL'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /migrations/Version20211001104011.php: -------------------------------------------------------------------------------- 1 | addSql('CREATE SEQUENCE product_image_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); 24 | $this->addSql('CREATE TABLE product_image (id INT NOT NULL, product_id INT NOT NULL, filename_big VARCHAR(255) NOT NULL, filename_middle VARCHAR(255) NOT NULL, filename_small VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); 25 | $this->addSql('CREATE INDEX IDX_64617F034584665A ON product_image (product_id)'); 26 | $this->addSql('ALTER TABLE product_image ADD CONSTRAINT FK_64617F034584665A FOREIGN KEY (product_id) REFERENCES product (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); 27 | $this->addSql('ALTER TABLE "user" ALTER zip_code TYPE DOUBLE PRECISION'); 28 | $this->addSql('ALTER TABLE "user" ALTER zip_code DROP DEFAULT'); 29 | } 30 | 31 | public function down(Schema $schema): void 32 | { 33 | // this down() migration is auto-generated, please modify it to your needs 34 | $this->addSql('DROP SEQUENCE product_image_id_seq CASCADE'); 35 | $this->addSql('DROP TABLE product_image'); 36 | $this->addSql('ALTER TABLE "user" ALTER zip_code TYPE INT'); 37 | $this->addSql('ALTER TABLE "user" ALTER zip_code DROP DEFAULT'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /migrations/Version20211002150834.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE product ADD slug VARCHAR(128) DEFAULT NULL'); 24 | $this->addSql('CREATE UNIQUE INDEX UNIQ_D34A04AD989D9B62 ON product (slug)'); 25 | } 26 | 27 | public function down(Schema $schema): void 28 | { 29 | // this down() migration is auto-generated, please modify it to your needs 30 | $this->addSql('DROP INDEX UNIQ_D34A04AD989D9B62'); 31 | $this->addSql('ALTER TABLE product DROP slug'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /migrations/Version20211002222439.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE product ADD uuid UUID DEFAULT NULL'); 24 | $this->addSql('COMMENT ON COLUMN product.uuid IS \'(DC2Type:uuid)\''); 25 | $this->addSql('UPDATE product SET uuid=uuid_generate_v4() WHERE uuid IS NULL'); // ONLY postgresql 26 | } 27 | 28 | public function down(Schema $schema): void 29 | { 30 | // this down() migration is auto-generated, please modify it to your needs 31 | $this->addSql('ALTER TABLE product DROP uuid'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /migrations/Version20211002223343.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE product ALTER uuid SET NOT NULL'); 24 | } 25 | 26 | public function down(Schema $schema): void 27 | { 28 | // this down() migration is auto-generated, please modify it to your needs 29 | $this->addSql('ALTER TABLE product ALTER uuid DROP NOT NULL'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /migrations/Version20211003091352.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE product ALTER price TYPE NUMERIC(15, 2)'); 24 | } 25 | 26 | public function down(Schema $schema): void 27 | { 28 | // this down() migration is auto-generated, please modify it to your needs 29 | $this->addSql('ALTER TABLE product ALTER price TYPE NUMERIC(6, 2)'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /migrations/Version20211003203107.php: -------------------------------------------------------------------------------- 1 | addSql('CREATE SEQUENCE category_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); 24 | $this->addSql('CREATE TABLE category (id INT NOT NULL, title VARCHAR(100) DEFAULT NULL, slug VARCHAR(120) NOT NULL, PRIMARY KEY(id))'); 25 | $this->addSql('CREATE UNIQUE INDEX UNIQ_64C19C1989D9B62 ON category (slug)'); 26 | $this->addSql('ALTER TABLE product ADD category_id INT DEFAULT NULL'); 27 | $this->addSql('ALTER TABLE product ADD CONSTRAINT FK_D34A04AD12469DE2 FOREIGN KEY (category_id) REFERENCES category (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); 28 | $this->addSql('CREATE INDEX IDX_D34A04AD12469DE2 ON product (category_id)'); 29 | } 30 | 31 | public function down(Schema $schema): void 32 | { 33 | // this down() migration is auto-generated, please modify it to your needs 34 | $this->addSql('ALTER TABLE product DROP CONSTRAINT FK_D34A04AD12469DE2'); 35 | $this->addSql('DROP SEQUENCE category_id_seq CASCADE'); 36 | $this->addSql('DROP TABLE category'); 37 | $this->addSql('DROP INDEX IDX_D34A04AD12469DE2'); 38 | $this->addSql('ALTER TABLE product DROP category_id'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /migrations/Version20211004063505.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE category ADD is_deleted BOOLEAN DEFAULT NULL'); 24 | } 25 | 26 | public function down(Schema $schema): void 27 | { 28 | // this down() migration is auto-generated, please modify it to your needs 29 | $this->addSql('ALTER TABLE category DROP is_deleted'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /migrations/Version20211004210150.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE product RENAME COLUMN create_at TO created_at'); 24 | } 25 | 26 | public function down(Schema $schema): void 27 | { 28 | // this down() migration is auto-generated, please modify it to your needs 29 | $this->addSql('ALTER TABLE product RENAME COLUMN created_at TO create_at'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /migrations/Version20211118200708.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE "user" ADD facebook_id VARCHAR(50) DEFAULT NULL'); 24 | } 25 | 26 | public function down(Schema $schema): void 27 | { 28 | // this down() migration is auto-generated, please modify it to your needs 29 | $this->addSql('ALTER TABLE "user" DROP facebook_id'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /migrations/Version20211119072129.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE "user" ADD google_id VARCHAR(50) DEFAULT NULL'); 24 | } 25 | 26 | public function down(Schema $schema): void 27 | { 28 | // this down() migration is auto-generated, please modify it to your needs 29 | $this->addSql('ALTER TABLE "user" DROP google_id'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /migrations/Version20211125173610.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE cart ADD token VARCHAR(255) DEFAULT NULL'); 24 | $this->addSql('ALTER TABLE cart DROP session_id'); 25 | } 26 | 27 | public function down(Schema $schema): void 28 | { 29 | // this down() migration is auto-generated, please modify it to your needs 30 | $this->addSql('ALTER TABLE cart DROP token'); 31 | $this->addSql('ALTER TABLE cart ADD session_id VARCHAR(255) NOT NULL'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /migrations/Version20220325174952.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE "user" DROP facebook_id'); 24 | } 25 | 26 | public function down(Schema $schema): void 27 | { 28 | // this down() migration is auto-generated, please modify it to your needs 29 | $this->addSql('ALTER TABLE "user" ADD facebook_id VARCHAR(50) DEFAULT NULL'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /migrations/Version20220326090356.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE "user" ADD yandex_id VARCHAR(50) DEFAULT NULL'); 24 | } 25 | 26 | public function down(Schema $schema): void 27 | { 28 | // this down() migration is auto-generated, please modify it to your needs 29 | $this->addSql('ALTER TABLE "user" DROP yandex_id'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /migrations/Version20220326141147.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE "user" ADD vkontakte_id VARCHAR(50) DEFAULT NULL'); 24 | } 25 | 26 | public function down(Schema $schema): void 27 | { 28 | // this down() migration is auto-generated, please modify it to your needs 29 | $this->addSql('ALTER TABLE "user" DROP vkontakte_id'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /migrations/Version20220326151023.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE "user" ADD github_id VARCHAR(50) DEFAULT NULL'); 24 | } 25 | 26 | public function down(Schema $schema): void 27 | { 28 | // this down() migration is auto-generated, please modify it to your needs 29 | $this->addSql('ALTER TABLE "user" DROP github_id'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /migrations/Version20240714084855.php: -------------------------------------------------------------------------------- 1 | addSql('ALTER TABLE "user" ALTER zip_code TYPE INT'); 24 | } 25 | 26 | public function down(Schema $schema): void 27 | { 28 | // this down() migration is auto-generated, please modify it to your needs 29 | $this->addSql('ALTER TABLE "user" ALTER zip_code TYPE DOUBLE PRECISION'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@symfony/stimulus-bridge": "^2.0.0", 4 | "@symfony/webpack-encore": "^1.0.0", 5 | "core-js": "^3.0.0", 6 | "eslint": "^8.4.1", 7 | "eslint-config-prettier": "^8.3.0", 8 | "eslint-plugin-prettier": "^4.0.0", 9 | "eslint-plugin-vue": "^8.2.0", 10 | "file-loader": "^6.2.0", 11 | "regenerator-runtime": "^0.13.2", 12 | "sass": "1.32.13", 13 | "sass-loader": "^10.1.1", 14 | "stimulus": "^2.0.0", 15 | "webpack-notifier": "^1.6.0" 16 | }, 17 | "license": "UNLICENSED", 18 | "private": true, 19 | "scripts": { 20 | "dev-server": "encore dev-server", 21 | "dev": "encore dev", 22 | "watch": "encore dev --watch", 23 | "build": "encore production --progress" 24 | }, 25 | "dependencies": { 26 | "@fortawesome/fontawesome-free": "^5.15.4", 27 | "axios": "^0.21.1", 28 | "bootstrap": "^4.3.1", 29 | "chart.js": "^2.9.4", 30 | "foundation-emails": "^2.3.1", 31 | "http-status-codes": "^2.1.4", 32 | "jquery": "^3.5.1", 33 | "jquery.easing": "^1.4.1", 34 | "popper.js": "^1.16.1", 35 | "vue": "^2.6.12", 36 | "vue-loader": "^15.9.8", 37 | "vue-template-compiler": "^2.6.12", 38 | "vuex": "^3.6.2" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | tests 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | src 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /phpunit.xml.dist.bak: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | tests 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | src 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 46 | 47 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | getUser(); 16 | 17 | if (false === $user->isVerified()) { 18 | $this->addFlash('danger', 'You don\'t have enough rights! Contact the administrator.'); 19 | 20 | return false; 21 | } 22 | 23 | return true; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Controller/Admin/DashboardController.php: -------------------------------------------------------------------------------- 1 | render('admin/pages/dashboard.html.twig'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Controller/Admin/ProductImageController.php: -------------------------------------------------------------------------------- 1 | getProduct(); 27 | 28 | if (!$this->checkTheAccessLevel()) { 29 | return $this->redirect($request->server->get('HTTP_REFERER')); 30 | } 31 | 32 | $productImageDir = $productManager->getProductImagesDir($product); 33 | $imgId = $productImage->getId(); 34 | 35 | $productImageManager->removeImageFromProduct($productImage, $productImageDir); 36 | $this->addFlash('warning', "The image (ID: $imgId) was successfully deleted!"); 37 | 38 | return $this->redirectToRoute('admin_product_edit', [ 39 | 'id' => $product->getId(), 40 | ]); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Controller/Admin/SecurityController.php: -------------------------------------------------------------------------------- 1 | getLastAuthenticationError(); 21 | // last username entered by the user 22 | $lastUsername = $authenticationUtils->getLastUsername(); 23 | 24 | return $this->render('admin/security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]); 25 | } 26 | 27 | #[Route('/logout', name: 'admin_security_logout')] 28 | public function logout(): RedirectResponse 29 | { 30 | return $this->redirectToRoute(LoginFormAuthenticator::LOGIN_ROUTE); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Controller/Front/AuthGithubEnController.php: -------------------------------------------------------------------------------- 1 | getClient('github_en') 19 | ->redirect([], []); 20 | } 21 | 22 | #[Route('/connect/github-en/check', name: 'connect_github_en_check')] 23 | public function connectCheckAction(): void 24 | { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Controller/Front/AuthGithubRusController.php: -------------------------------------------------------------------------------- 1 | getClient('github_ru') 19 | ->redirect([], []); 20 | } 21 | 22 | #[Route('/connect/github-ru/check', name: 'connect_github_ru_check')] 23 | public function connectCheckAction(): void 24 | { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Controller/Front/AuthGoogleController.php: -------------------------------------------------------------------------------- 1 | getClient('google_main') 19 | ->redirect([], []); 20 | } 21 | 22 | #[Route('/connect/google/check', name: 'connect_google_check')] 23 | public function connectCheckAction(): void 24 | { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Controller/Front/AuthVkontakteController.php: -------------------------------------------------------------------------------- 1 | getClient('vkontakte_main') 19 | ->redirect([], []); 20 | } 21 | 22 | #[Route('/connect/vkontakte/check', name: 'connect_vkontakte_check')] 23 | public function connectCheckAction(): void 24 | { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Controller/Front/AuthYandexController.php: -------------------------------------------------------------------------------- 1 | getClient('yandex_main') 19 | ->redirect([], []); 20 | } 21 | 22 | #[Route('/connect/yandex/check', name: 'connect_yandex_check')] 23 | public function connectCheckAction(): void 24 | { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Controller/Front/CartController.php: -------------------------------------------------------------------------------- 1 | cookies->get('CART_TOKEN'); 22 | $cart = $cartRepository->findOneBy(['token' => $cartToken]); 23 | 24 | return $this->render('front/cart/show.html.twig', [ 25 | 'cart' => $cart, 26 | ]); 27 | } 28 | 29 | #[Route('/cart/create', name: 'main_cart_create')] 30 | public function create(Request $request, OrderManager $orderManager): Response 31 | { 32 | $cartToken = $request->cookies->get('CART_TOKEN'); 33 | 34 | /** @var User $user */ 35 | $user = $this->getUser(); 36 | 37 | $orderManager->createOrderFromCartByToken($cartToken, $user); 38 | 39 | $redirectUrl = $this->generateUrl('main_cart_show'); 40 | 41 | // Пример удаления куки 'CART_TOKEN' через контроллер 42 | $response = new RedirectResponse($redirectUrl); 43 | $response->headers->clearCookie('CART_TOKEN', '/', null); 44 | 45 | return $response; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Controller/Front/CategoryController.php: -------------------------------------------------------------------------------- 1 | getIsDeleted()) { 24 | $this->addFlash('warning', "The category {$category->getTitle()} not found!"); 25 | 26 | return $this->redirectToRoute('main_homepage'); 27 | } 28 | 29 | $products = $productRepository->findByCategoryAndCount($category->getId()); 30 | 31 | return $this->render('front/category/show.html.twig', [ 32 | 'category' => $category, 33 | 'products' => $products, 34 | ]); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Controller/Front/SecurityController.php: -------------------------------------------------------------------------------- 1 | getSession()->set('HTTP_REFERER', $request->server->get('HTTP_REFERER')); 20 | 21 | if ($this->getUser()) { 22 | return $this->redirectToRoute('main_profile_index'); 23 | } 24 | 25 | // get the login error if there is one 26 | $error = $authenticationUtils->getLastAuthenticationError(); 27 | // last username entered by the user 28 | $lastUsername = $authenticationUtils->getLastUsername(); 29 | 30 | return $this->render('front/security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]); 31 | } 32 | 33 | #[Route('/logout', name: 'main_logout')] 34 | public function logout(): RedirectResponse 35 | { 36 | return $this->redirectToRoute('main_profile_index'); 37 | // throw new LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Controller/Front/SitemapController.php: -------------------------------------------------------------------------------- 1 | $this->generateUrl('main_homepage', [], UrlGeneratorInterface::ABSOLUTE_URL), 20 | 'lastmod' => (new DateTimeImmutable())->format('Y-m-d'), 21 | 'changefreq' => 'weekly', 22 | 'priority' => 1, 23 | ]; 24 | 25 | return $this->render('front/sitemap.xml.twig', [ 26 | 'data' => $mainPageInfo, 27 | ]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Entity/ResetPasswordRequest.php: -------------------------------------------------------------------------------- 1 | id = null; 37 | $this->user = $user; 38 | $this->initialize($expiresAt, $selector, $hashedToken); 39 | } 40 | 41 | public function getId(): ?int 42 | { 43 | return $this->id; 44 | } 45 | 46 | public function getUser(): object 47 | { 48 | return $this->user; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Entity/StaticStorage/OrderStaticStorage.php: -------------------------------------------------------------------------------- 1 | 'Created', 22 | self::ORDER_STATUS_PROCESSED => 'Processed', 23 | self::ORDER_STATUS_COMPLECTED => 'Complected', 24 | self::ORDER_STATUS_DELIVERED => 'Delivered', 25 | self::ORDER_STATUS_DENIED => 'Denied', 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Entity/StaticStorage/UserStaticStorage.php: -------------------------------------------------------------------------------- 1 | 'User', 20 | self::USER_ROLE_ADMIN => 'Admin', 21 | self::USER_ROLE_SUPER_ADMIN => 'Super Admin', 22 | ]; 23 | } 24 | 25 | public static function getUserRoleHasAccessToAdminSection(): array 26 | { 27 | return [ 28 | self::USER_ROLE_ADMIN, 29 | self::USER_ROLE_SUPER_ADMIN, 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Event/OrderCreatedFromCartEvent.php: -------------------------------------------------------------------------------- 1 | order; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Event/UserLoggedInViaSocialNetworkEvent.php: -------------------------------------------------------------------------------- 1 | user; 22 | } 23 | 24 | public function getPlainPassword(): string 25 | { 26 | return $this->plainPassword; 27 | } 28 | 29 | public function getVerifyEmail(): array 30 | { 31 | return $this->verifyEmail; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/EventSubscriber/LocaleSubscriber.php: -------------------------------------------------------------------------------- 1 | getRequest(); 20 | 21 | if (!$request->hasPreviousSession()) { 22 | return; 23 | } 24 | 25 | $locale = $request->attributes->get('_locale'); 26 | 27 | if ($locale) { 28 | $request->getSession()->set('_locale', $locale); 29 | } else { 30 | $request->setLocale( 31 | $request->getSession()->get('_locale', $this->defaultLocale) 32 | ); 33 | } 34 | } 35 | 36 | public static function getSubscribedEvents(): array 37 | { 38 | return [ 39 | KernelEvents::REQUEST => [ 40 | [ 41 | 'onKernelRequest', 42 | 20, 43 | ], 44 | ], 45 | ]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/EventSubscriber/OrderCreatedFromCartSendNotificationSubscriber.php: -------------------------------------------------------------------------------- 1 | orderCreatedFromCartEmailSender = $orderCreatedFromCartEmailSender; 20 | 21 | return $this; 22 | } 23 | 24 | public function onOrderCreatedFromCartEvent(OrderCreatedFromCartEvent $event): void 25 | { 26 | $order = $event->getOrder(); 27 | 28 | $this->orderCreatedFromCartEmailSender->sendEmailToClient($order); 29 | $this->orderCreatedFromCartEmailSender->sendEmailToManager($order); 30 | } 31 | 32 | public static function getSubscribedEvents(): array 33 | { 34 | return [ 35 | OrderCreatedFromCartEvent::class => 'onOrderCreatedFromCartEvent', 36 | ]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/EventSubscriber/UserLoggedInViaSocialNetworkSendNotificationSubscriber.php: -------------------------------------------------------------------------------- 1 | mailerSender = $mailerSender; 20 | 21 | return $this; 22 | } 23 | 24 | public function onUserLoggedInViaSocialNetworkEvent(UserLoggedInViaSocialNetworkEvent $event): void 25 | { 26 | $user = $event->getUser(); 27 | $plainPassword = $event->getPlainPassword(); 28 | $verifyEmail = $event->getVerifyEmail(); 29 | 30 | $this->mailerSender->sendEmailToClient($user, $plainPassword, $verifyEmail); 31 | } 32 | 33 | public static function getSubscribedEvents(): array 34 | { 35 | return [ 36 | UserLoggedInViaSocialNetworkEvent::class => 'onUserLoggedInViaSocialNetworkEvent', 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Exception/Security/EmptyUserPlainPasswordException.php: -------------------------------------------------------------------------------- 1 | add('title', TextType::class, [ 21 | 'label' => 'Title', 22 | 'required' => true, 23 | 'trim' => true, 24 | 'attr' => [ 25 | 'class' => 'form-control', 26 | ], 27 | 'constraints' => [ 28 | new NotBlank(message: 'Please enter a title'), 29 | ], 30 | ]) 31 | ->add('submit', SubmitType::class, [ 32 | 'label' => 'Save changes', 33 | ]); 34 | } 35 | 36 | public function configureOptions(OptionsResolver $resolver): void 37 | { 38 | $resolver->setDefaults([ 39 | 'data_class' => EditCategoryModel::class, 40 | ]); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Form/DTO/EditCategoryModel.php: -------------------------------------------------------------------------------- 1 | id = $category->getId(); 26 | $model->title = $category->getTitle(); 27 | 28 | return $model; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Form/DTO/EditOrderModel.php: -------------------------------------------------------------------------------- 1 | id = $order->getId(); 32 | $model->owner = $order->getOwner(); 33 | $model->status = $order->getStatus(); 34 | $model->totalPrice = $order->getTotalPrice(); 35 | $model->createdAt = $order->getCreatedAt(); 36 | $model->isDeleted = $order->getIsDeleted(); 37 | 38 | return $model; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Form/DTO/EditProductModel.php: -------------------------------------------------------------------------------- 1 | id = $product->getId(); 37 | $model->title = $product->getTitle(); 38 | $model->price = $product->getPrice(); 39 | $model->quantity = $product->getQuantity(); 40 | $model->description = $product->getDescription(); 41 | $model->category = $product->getCategory(); 42 | $model->isPublished = $product->getIsPublished(); 43 | $model->isDeleted = $product->getIsDeleted(); 44 | $model->createdAt = $product->getCreatedAt(); 45 | 46 | return $model; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Form/Front/ProfileEditFormType.php: -------------------------------------------------------------------------------- 1 | add('fullName', TextType::class, [ 20 | 'label' => 'personal_account.edit.labels.full_name', 21 | 'trim' => true, 22 | ]) 23 | ->add('phone', TextType::class, [ 24 | 'label' => 'personal_account.edit.labels.phone', 25 | 'trim' => true, 26 | 'required' => false, 27 | ]) 28 | ->add('address', TextType::class, [ 29 | 'label' => 'personal_account.edit.labels.address', 30 | 'trim' => true, 31 | ]) 32 | ->add('zipCode', IntegerType::class, [ 33 | 'label' => 'personal_account.edit.labels.zipcode', 34 | 'required' => false, 35 | 'trim' => true, 36 | ]); 37 | } 38 | 39 | public function configureOptions(OptionsResolver $resolver): void 40 | { 41 | $resolver->setDefaults([ 42 | 'data_class' => User::class, 43 | ]); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Form/Front/ResetPasswordRequestFormType.php: -------------------------------------------------------------------------------- 1 | add('email', EmailType::class, [ 19 | 'attr' => [ 20 | 'autocomplete' => 'email', 21 | 'class' => 'form-control', 22 | ], 23 | 'constraints' => [ 24 | new NotBlank([ 25 | 'message' => 'Please enter your email', 26 | ]), 27 | ], 28 | ]); 29 | } 30 | 31 | public function configureOptions(OptionsResolver $resolver): void 32 | { 33 | $resolver->setDefaults([]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Form/Handler/CategoryFormHandler.php: -------------------------------------------------------------------------------- 1 | id) { 22 | $category = $this->categoryManager->find($editCategoryModel->id); 23 | } 24 | 25 | $this->categoryManager->persist($category); 26 | $category = $this->fillingCategoryData($category, $editCategoryModel); 27 | $this->categoryManager->flush(); 28 | 29 | return $category; 30 | } 31 | 32 | private function fillingCategoryData(Category $category, EditCategoryModel $editCategoryModel): Category 33 | { 34 | $title = (!is_string($editCategoryModel->title)) 35 | ? (string) $editCategoryModel->title 36 | : $editCategoryModel->title; 37 | 38 | $category->setTitle($title); 39 | 40 | return $category; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Form/Validator/GreaterThanOrEqualPrice.php: -------------------------------------------------------------------------------- 1 | context->buildViolation($constraint->message) 35 | ->addViolation(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Kernel.php: -------------------------------------------------------------------------------- 1 | getContainer()->getParameter('timezone'); 21 | if (empty($timezone)) { 22 | $timezone = date_default_timezone_get(); 23 | } 24 | date_default_timezone_set($timezone); 25 | } 26 | 27 | protected function configureContainer(ContainerConfigurator $container): void 28 | { 29 | $container->import('../config/{packages}/*.yaml'); 30 | $container->import('../config/{packages}/'.$this->environment.'/*.yaml'); 31 | 32 | if (is_file(dirname(__DIR__).'/config/services.yaml')) { 33 | $container->import('../config/services.yaml'); 34 | $container->import('../config/{services}_'.$this->environment.'.yaml'); 35 | } else { 36 | $container->import('../config/{services}.php'); 37 | } 38 | } 39 | 40 | protected function configureRoutes(RoutingConfigurator $routes): void 41 | { 42 | $routes->import('../config/{routes}/'.$this->environment.'/*.yaml'); 43 | $routes->import('../config/{routes}/*.yaml'); 44 | 45 | if (is_file(dirname(__DIR__).'/config/routes.yaml')) { 46 | $routes->import('../config/routes.yaml'); 47 | } else { 48 | $routes->import('../config/{routes}.php'); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Messenger/Message/Command/ResetUserPasswordCommand.php: -------------------------------------------------------------------------------- 1 | email; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Messenger/Message/Event/EventUserRegisteredEvent.php: -------------------------------------------------------------------------------- 1 | userId; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Messenger/MessageHandler/Command/ResetUserPasswordHandler.php: -------------------------------------------------------------------------------- 1 | getEmail(); 28 | 29 | /** @var User|null $user */ 30 | $user = $this->userManager->getRepository()->findOneBy(['email' => $email]); 31 | 32 | if (!$user) { 33 | return; 34 | } 35 | 36 | try { 37 | $resetToken = $this->resetPasswordHelper->generateResetToken($user); 38 | $this->userPasswordEmailSender->sendEmailToClient($user, $resetToken); 39 | } catch (ResetPasswordExceptionInterface $e) { 40 | // ... 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Messenger/MessageHandler/Event/UserRegisteredHandler.php: -------------------------------------------------------------------------------- 1 | getUserId(); 27 | 28 | /** @var User|null $user */ 29 | $user = $this->userManager->find($userId); 30 | 31 | if (!$user) { 32 | return; 33 | } 34 | 35 | $emailSignature = $this->emailVerifier 36 | ->generateEmailSignature('main_verify_email', $user); 37 | 38 | $this->emailSender->sendEmailToClient($user, $emailSignature); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Repository/CartProductRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('c') 31 | ->andWhere('c.exampleField = :val') 32 | ->setParameter('val', $value) 33 | ->orderBy('c.id', 'ASC') 34 | ->setMaxResults(10) 35 | ->getQuery() 36 | ->getResult() 37 | ; 38 | } 39 | */ 40 | 41 | /* 42 | public function findOneBySomeField($value): ?CartProduct 43 | { 44 | return $this->createQueryBuilder('c') 45 | ->andWhere('c.exampleField = :val') 46 | ->setParameter('val', $value) 47 | ->getQuery() 48 | ->getOneOrNullResult() 49 | ; 50 | } 51 | */ 52 | } 53 | -------------------------------------------------------------------------------- /src/Repository/CartRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('c') 31 | ->andWhere('c.exampleField = :val') 32 | ->setParameter('val', $value) 33 | ->orderBy('c.id', 'ASC') 34 | ->setMaxResults(10) 35 | ->getQuery() 36 | ->getResult() 37 | ; 38 | } 39 | */ 40 | 41 | /* 42 | public function findOneBySomeField($value): ?Cart 43 | { 44 | return $this->createQueryBuilder('c') 45 | ->andWhere('c.exampleField = :val') 46 | ->setParameter('val', $value) 47 | ->getQuery() 48 | ->getOneOrNullResult() 49 | ; 50 | } 51 | */ 52 | } 53 | -------------------------------------------------------------------------------- /src/Repository/CategoryRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('c') 28 | ->andWhere('c.isDeleted = FALSE') 29 | ->getQuery() 30 | ->getResult(); 31 | } 32 | 33 | public function forFormQueryBuilderFindActiveCategory(): QueryBuilder 34 | { 35 | return $this->createQueryBuilder('c') 36 | ->andWhere('c.isDeleted = FALSE'); 37 | } 38 | 39 | public function findActiveCategoryWithJoinProduct(): ?array 40 | { 41 | return $this->createQueryBuilder('c') 42 | ->where('c.isDeleted = FALSE') 43 | ->join('c.products', 'p') 44 | ->andWhere('p.isDeleted = FALSE') 45 | ->andWhere('p.isPublished = TRUE') 46 | ->getQuery() 47 | ->getResult(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Repository/OrderProductRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('o') 31 | ->andWhere('o.exampleField = :val') 32 | ->setParameter('val', $value) 33 | ->orderBy('o.id', 'ASC') 34 | ->setMaxResults(10) 35 | ->getQuery() 36 | ->getResult() 37 | ; 38 | } 39 | */ 40 | 41 | /* 42 | public function findOneBySomeField($value): ?OrderProduct 43 | { 44 | return $this->createQueryBuilder('o') 45 | ->andWhere('o.exampleField = :val') 46 | ->setParameter('val', $value) 47 | ->getQuery() 48 | ->getOneOrNullResult() 49 | ; 50 | } 51 | */ 52 | } 53 | -------------------------------------------------------------------------------- /src/Repository/OrderRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('o') 31 | ->andWhere('o.exampleField = :val') 32 | ->setParameter('val', $value) 33 | ->orderBy('o.id', 'ASC') 34 | ->setMaxResults(10) 35 | ->getQuery() 36 | ->getResult() 37 | ; 38 | } 39 | */ 40 | 41 | /* 42 | public function findOneBySomeField($value): ?Order 43 | { 44 | return $this->createQueryBuilder('o') 45 | ->andWhere('o.exampleField = :val') 46 | ->setParameter('val', $value) 47 | ->getQuery() 48 | ->getOneOrNullResult() 49 | ; 50 | } 51 | */ 52 | } 53 | -------------------------------------------------------------------------------- /src/Repository/ProductImageRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('p') 31 | ->andWhere('p.exampleField = :val') 32 | ->setParameter('val', $value) 33 | ->orderBy('p.id', 'ASC') 34 | ->setMaxResults(10) 35 | ->getQuery() 36 | ->getResult() 37 | ; 38 | } 39 | */ 40 | 41 | /* 42 | public function findOneBySomeField($value): ?ProductImage 43 | { 44 | return $this->createQueryBuilder('p') 45 | ->andWhere('p.exampleField = :val') 46 | ->setParameter('val', $value) 47 | ->getQuery() 48 | ->getOneOrNullResult() 49 | ; 50 | } 51 | */ 52 | } 53 | -------------------------------------------------------------------------------- /src/Repository/ResetPasswordRequestRepository.php: -------------------------------------------------------------------------------- 1 | urlGenerator = $urlGenerator; 22 | } 23 | 24 | public function start(Request $request, ?AuthenticationException $authException = null): RedirectResponse 25 | { 26 | /** @var Session $session */ 27 | $session = $request->getSession(); 28 | 29 | $session->getFlashBag()->add('warning', 'You have to login in order to access this page.'); 30 | 31 | return new RedirectResponse($this->urlGenerator->generate(LoginFormAuthenticator::LOGIN_ROUTE)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Security/EntryPoint/AuthenticationFrontEntryPoint.php: -------------------------------------------------------------------------------- 1 | urlGenerator = $urlGenerator; 21 | } 22 | 23 | public function start(Request $request, ?AuthenticationException $authException = null): RedirectResponse 24 | { 25 | /** @var Session $session */ 26 | $session = $request->getSession(); 27 | 28 | $session->getFlashBag()->add('warning', 'You have to login in order to access this page.'); 29 | 30 | return new RedirectResponse($this->urlGenerator->generate('main_login')); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Security/Handler/AccessAdminDeniedHandler.php: -------------------------------------------------------------------------------- 1 | urlGenerator = $urlGenerator; 23 | } 24 | 25 | public function handle(Request $request, AccessDeniedException $accessDeniedException): ?Response 26 | { 27 | /** @var Session $session */ 28 | $session = $request->getSession(); 29 | 30 | $session->getFlashBag()->add('warning', 'You have to login in order to access this page.'); 31 | 32 | return new RedirectResponse($this->urlGenerator->generate(LoginFormAuthenticator::LOGIN_ROUTE)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Security/Handler/AccessFrontDeniedHandler.php: -------------------------------------------------------------------------------- 1 | urlGenerator = $urlGenerator; 22 | } 23 | 24 | public function handle(Request $request, AccessDeniedException $accessDeniedException): ?Response 25 | { 26 | /** @var Session $session */ 27 | $session = $request->getSession(); 28 | 29 | $session->getFlashBag()->add('warning', 'You have to login in order to access this page.'); 30 | 31 | return new RedirectResponse($this->urlGenerator->generate('main_login')); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Security/Voters/AdminOrderEditVoter.php: -------------------------------------------------------------------------------- 1 | getUser(); 25 | $isVerified = $user->isVerified(); 26 | $isAdmin = $user->isVerified(); 27 | 28 | return $user instanceof User && $isAdmin && $isVerified; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Utils/ApiPlatform/Event/Subscriber/SetCartTokenSubscriber.php: -------------------------------------------------------------------------------- 1 | [ 22 | 'setCartTokenToCart', EventPriorities::PRE_WRITE, 23 | ], 24 | ]; 25 | } 26 | 27 | /** 28 | * @throws Exception 29 | */ 30 | public function setCartTokenToCart(ViewEvent $event): void 31 | { 32 | $cart = $event->getControllerResult(); 33 | $method = $event->getRequest()->getMethod(); 34 | 35 | if (!$cart instanceof Cart || Request::METHOD_POST !== $method) { 36 | return; 37 | } 38 | 39 | $cartToken = $event->getRequest()->cookies->get('CART_TOKEN'); 40 | 41 | if (!$cartToken) { 42 | $cartToken = TokenGenerator::generateToken(); 43 | } 44 | 45 | $cart->setToken($cartToken); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Utils/Authenticator/CheckingUserSocialNetworkBeforeAuthorization.php: -------------------------------------------------------------------------------- 1 | security = $security; 19 | 20 | return $this; 21 | } 22 | 23 | protected function checkingUserSocialNetworkBeforeAuthorization(string $socialNetworkUserEmail): bool 24 | { 25 | /** @var User|null $activeUser */ 26 | $activeUser = $this->security->getUser(); 27 | 28 | if ($activeUser) { 29 | $activeUserEmail = $activeUser->getEmail(); 30 | 31 | if ($activeUserEmail !== $socialNetworkUserEmail) { 32 | return true; 33 | } 34 | } 35 | 36 | return false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Utils/Event/Subscriber/MigrationEventSubscriber.php: -------------------------------------------------------------------------------- 1 | getSchema(); 28 | 29 | if (!$schema->hasNamespace('public')) { 30 | $schema->createNamespace('public'); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Utils/File/FileSaver.php: -------------------------------------------------------------------------------- 1 | getClientOriginalName(), PATHINFO_FILENAME); 28 | $saveFileName = $this->slugger->slug($originalFileName); 29 | $filename = sprintf('%s-%s.%s', $saveFileName, str_replace('.', '', uniqid('', true)), $uploadedFile->guessExtension()); 30 | $this->filesystemWorker->createFolderIfNotExist($this->uploadsTempDir); 31 | 32 | try { 33 | $uploadedFile->move($this->uploadsTempDir, $filename); 34 | } catch (FileException $exception) { 35 | return null; 36 | } 37 | 38 | return $filename; 39 | } 40 | 41 | public function getUploadsTempDir(): string 42 | { 43 | return $this->uploadsTempDir; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Utils/File/ImageResizer.php: -------------------------------------------------------------------------------- 1 | filesystemWorker->generatePathToFile($originalFileFolder, $originalFilename); 20 | [$imageWidth, $imageHeight] = getimagesize($originalFilePath); 21 | 22 | $ratio = $imageWidth / $imageHeight; 23 | $targetWidth = $targetParams['width']; 24 | $targetHeight = $targetParams['height']; 25 | 26 | if ($targetHeight && ($targetWidth / $targetHeight) > $ratio) { 27 | $targetWidth = $targetHeight * $ratio; 28 | } else { 29 | $targetHeight = $targetWidth / $ratio; 30 | } 31 | 32 | $targetFolder = $targetParams['newFolder']; 33 | $targetFilename = $targetParams['newFilename']; 34 | $targetFilePath = sprintf('%s/%s', $targetFolder, $targetFilename); 35 | 36 | $imagineFile = $this->imagine->open($originalFilePath); 37 | $imagineFile 38 | ->resize(new Box($targetWidth, $targetHeight)) 39 | ->save($targetFilePath); 40 | 41 | return $targetFilename; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Utils/FileSystem/FilesystemWorker.php: -------------------------------------------------------------------------------- 1 | filesystem->exists($folder)) { 19 | $this->filesystem->mkdir($folder); 20 | } 21 | } 22 | 23 | public function remove(string $item): void 24 | { 25 | if ($this->filesystem->exists($item)) { 26 | $this->filesystem->remove($item); 27 | } 28 | } 29 | 30 | public function removeFolderIfEmpty(string $pathToDir): void 31 | { 32 | if (is_dir($pathToDir)) { 33 | $iterator = new FilesystemIterator($pathToDir); 34 | if (!$iterator->valid()) { 35 | $this->filesystem->remove($pathToDir); 36 | } 37 | } 38 | } 39 | 40 | public function generatePathToFile(string $dir, string $filename): string 41 | { 42 | return $dir.DIRECTORY_SEPARATOR.$filename; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Utils/Generator/PasswordGenerator.php: -------------------------------------------------------------------------------- 1 | mailerSender = $mailerSender; 19 | 20 | return $this; 21 | } 22 | 23 | protected UrlGeneratorInterface $urlGenerator; 24 | 25 | #[Required] 26 | public function setUrlGenerator(UrlGeneratorInterface $urlGenerator): BaseSender 27 | { 28 | $this->urlGenerator = $urlGenerator; 29 | 30 | return $this; 31 | } 32 | 33 | protected ParameterBagInterface $parameterBag; 34 | 35 | public function __construct(ParameterBagInterface $parameterBag) 36 | { 37 | $this->parameterBag = $parameterBag; 38 | } 39 | 40 | protected function getMailerOptions(): MailerOptionModel 41 | { 42 | return new MailerOptionModel(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Utils/Mailer/Sender/OrderCreatedFromCartEmailSender.php: -------------------------------------------------------------------------------- 1 | getOwner(); 17 | $mailerOptions = $this->getMailerOptions() 18 | ->setRecipient($user->getEmail()) 19 | ->setCc($this->parameterBag->get('admin_email')) 20 | ->setSubject('Symfony shop - Thank you for your purchase!') 21 | ->setHtmlTemplate('front/email/client/created_order_from_cart.html.twig') 22 | ->setContext([ 23 | 'order' => $order, 24 | 'profileUrl' => $this->urlGenerator->generate('main_profile_index', [], UrlGeneratorInterface::ABSOLUTE_URL), 25 | ]); 26 | 27 | $this->mailerSender->sendTemplatedEmail($mailerOptions); 28 | } 29 | 30 | public function sendEmailToManager(Order $order): void 31 | { 32 | /** @var User $user */ 33 | $user = $order->getOwner(); 34 | $mailerOptions = $this->getMailerOptions() 35 | ->setRecipient($this->parameterBag->get('admin_email')) 36 | ->setSubject("Client created order (ID: {$order->getId()})") 37 | ->setHtmlTemplate('front/email/manager/created_order_from_cart.html.twig') 38 | ->setContext([ 39 | 'order' => $order, 40 | ]); 41 | 42 | $this->mailerSender->sendTemplatedEmail($mailerOptions); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Utils/Mailer/Sender/ResetUserPasswordEmailSender.php: -------------------------------------------------------------------------------- 1 | urlGenerator->generate('main_profile_index', [], UrlGeneratorInterface::ABSOLUTE_URL); 20 | 21 | $mailerOptions = $this->getMailerOptions() 22 | ->setRecipient($user->getEmail()) 23 | ->setSubject('Symfony shop - You password reset request!!') 24 | ->setHtmlTemplate('front/email/security/reset_password.html.twig') 25 | ->setContext($emailContext); 26 | 27 | $this->mailerSender->sendTemplatedEmail($mailerOptions); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Utils/Mailer/Sender/UserLoggedInViaSocialNetworkEmailSender.php: -------------------------------------------------------------------------------- 1 | getMailerOptions() 15 | ->setRecipient($user->getEmail()) 16 | ->setSubject('Symfony shop - You new password!') 17 | ->setHtmlTemplate('front/email/client/user_logged_in_via_social_network.html.twig') 18 | ->setContext([ 19 | 'user' => $user, 20 | 'plainPassword' => $plainPassword, 21 | 'profileUrl' => $this->urlGenerator->generate('main_profile_index', [], UrlGeneratorInterface::ABSOLUTE_URL), 22 | 'signedUrl' => $verifyEmail['signedUrl'], 23 | 'expiresAtMessageKey' => $verifyEmail['expiresAtMessageKey'], 24 | 'expiresAtMessageData' => $verifyEmail['expiresAtMessageData'], 25 | ]); 26 | 27 | $this->mailerSender->sendTemplatedEmail($mailerOptions); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Utils/Mailer/Sender/UserRegisteredEmailSender.php: -------------------------------------------------------------------------------- 1 | getSignedUrl(); 18 | $emailContext['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey(); 19 | $emailContext['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData(); 20 | $emailContext['user'] = $user; 21 | $emailContext['profileUrl'] = $this->urlGenerator->generate('main_profile_index', [], UrlGeneratorInterface::ABSOLUTE_URL); 22 | 23 | $mailerOptions = $this->getMailerOptions() 24 | ->setRecipient($user->getEmail()) 25 | ->setSubject('Symfony shop - Please confirm your email!') 26 | ->setHtmlTemplate('front/email/security/confirmation_email.html.twig') 27 | ->setContext($emailContext); 28 | 29 | $this->mailerSender->sendTemplatedEmail($mailerOptions); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Utils/Manager/AbstractBaseManager.php: -------------------------------------------------------------------------------- 1 | getRepository()->find($id); 21 | } 22 | 23 | public function persist(object $entity): void 24 | { 25 | $this->em->persist($entity); 26 | } 27 | 28 | public function flush(): void 29 | { 30 | $this->em->flush(); 31 | } 32 | 33 | public function remove(object $entity): void 34 | { 35 | $this->em->remove($entity); 36 | $this->em->flush(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Utils/Manager/CartManager.php: -------------------------------------------------------------------------------- 1 | em->getRepository(Cart::class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Utils/Manager/CategoryManager.php: -------------------------------------------------------------------------------- 1 | em->getRepository(Category::class); 16 | } 17 | 18 | public function remove(object $entity): void 19 | { 20 | /** @var Category $category */ 21 | $category = $entity; 22 | 23 | /** @var Product[] $linkedProducts */ 24 | $linkedProducts = $category->getProducts()->getValues(); 25 | 26 | $this->em->persist($category); 27 | 28 | $category->setIsDeleted(true); 29 | foreach ($linkedProducts as $linkedProduct) { 30 | $linkedProduct->setIsDeleted(true); 31 | } 32 | 33 | $this->em->flush(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Utils/Manager/UserManager.php: -------------------------------------------------------------------------------- 1 | userPasswordHasher = $userPasswordHasher; 21 | 22 | return $this; 23 | } 24 | 25 | public function getRepository(): EntityRepository 26 | { 27 | return $this->em->getRepository(User::class); 28 | } 29 | 30 | public function encodePassword(User $user, string $plainPassword): void 31 | { 32 | $preparedPassword = trim($plainPassword); 33 | 34 | if (!$preparedPassword) { 35 | throw new EmptyUserPlainPasswordException('Empty user\'s password'); 36 | } 37 | 38 | $hashPassword = $this->userPasswordHasher->hashPassword($user, $preparedPassword); 39 | $user->setPassword($hashPassword); 40 | } 41 | 42 | public function remove(object $entity): void 43 | { 44 | /** @var User $user */ 45 | $user = $entity; 46 | 47 | $this->em->persist($user); 48 | $user->setIsDeleted(true); 49 | $this->em->flush(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Utils/Oauth2/Vk/VkUser.php: -------------------------------------------------------------------------------- 1 | response['user_id']; 16 | } 17 | 18 | public function getEmail(): ?string 19 | { 20 | return $this->response['email'] ?? null; 21 | } 22 | 23 | public function getFullName(): ?string 24 | { 25 | $fullname = ''; 26 | 27 | if ($this->response['first_name']) { 28 | $fullname .= $this->response['first_name'].' '; 29 | } 30 | 31 | if ($this->response['last_name']) { 32 | $fullname .= $this->response['last_name']; 33 | } 34 | 35 | return $fullname; 36 | } 37 | 38 | public function toArray(): array 39 | { 40 | return $this->response; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /templates/admin/_embed/_card/_breadcrumbs.html.twig: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ sectionTitle }} 4 |
/
5 |
6 | {% if entity and entity.id %} 7 | {{ entity.title|default('Edit') }} 8 | {% else %} 9 | Add new 10 | {% endif %} 11 |
12 |
13 | 14 | Add new 15 | 16 |
-------------------------------------------------------------------------------- /templates/admin/_embed/_card/_form_buttons.html.twig: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | {{ submitBtn|raw }} 7 |
8 | {% if entity and entity.id %} 9 | 10 | Delete row 11 | 12 | {% endif %} 13 |
-------------------------------------------------------------------------------- /templates/admin/_embed/_card/_header_action.html.twig: -------------------------------------------------------------------------------- 1 |
2 |
{{ sectionTitle }}
3 | 4 | Add new 5 | 6 |
-------------------------------------------------------------------------------- /templates/admin/_embed/_filters/_header.html.twig: -------------------------------------------------------------------------------- 1 |
2 |
Filters
3 | 7 | Show/Hide filters 8 | 9 |
-------------------------------------------------------------------------------- /templates/admin/_embed/_utils/_error_authenticator.html.twig: -------------------------------------------------------------------------------- 1 | {% if error %} 2 | 8 | {% endif %} -------------------------------------------------------------------------------- /templates/admin/_embed/_utils/_flash_message.html.twig: -------------------------------------------------------------------------------- 1 | {% for label, messages in app.flashes(['success','warning','info', 'danger']) %} 2 | {% for message in messages %} 3 | 9 | {% endfor %} 10 | {% endfor %} 11 | -------------------------------------------------------------------------------- /templates/admin/_embed/_utils/_modal.html.twig: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/admin/base.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% block title %}Admin Panel - RankedChoice{% endblock %} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {{ encore_entry_link_tags('section-admin') }} 20 | {% block stylesheets %}{% endblock %} 21 | 22 | 23 | {% block layout_body %}{% endblock %} 24 | 25 | {{ encore_entry_script_tags('section-admin') }} 26 | -------------------------------------------------------------------------------- /templates/admin/category/list.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'admin/layout/layout_main.html.twig' %} 2 | 3 | {% block title_detailed %}All Categories{% endblock %} 4 | 5 | {% block body %} 6 |
7 |
8 | {% include 'admin/_embed/_card/_header_action.html.twig' with 9 | { 10 | 'sectionTitle': 'Categories', 11 | 'actionUrl': path('admin_category_add') 12 | } %} 13 |
14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% for category in categories %} 27 | 28 | 29 | 30 | 31 | 34 | 35 | {% endfor %} 36 | 37 |
IdTitleSlug
{{ category.id }}{{ category.title }}{{ category.slug }} 32 | Edit 33 |
38 |
39 |
40 |
41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /templates/admin/layout/layout_empty_page.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'admin/base.html.twig' %} 2 | 3 | {% block layout_body %} 4 | 5 | 6 |
7 | {% block body %}{% endblock %} 8 |
9 | 10 | {% block javascripts %}{% endblock %} 11 | 12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /templates/admin/layout/layout_main.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'admin/base.html.twig' %} 2 | 3 | {% block title %} 4 | Admin Panel - {% block title_detailed %} | RankedChoice{% endblock %} 5 | {% endblock %} 6 | 7 | {% block layout_body %} 8 | 9 |
10 | 11 | {{ include('admin/_embed/_main/_sidebar.html.twig') }} 12 | 13 |
14 |
15 | 16 | {{ include('admin/_embed/_main/_topbar.html.twig') }} 17 | 18 |
19 | {% include 'admin/_embed/_utils/_flash_message.html.twig' %} 20 | 21 | {% block body %}{% endblock %} 22 |
23 |
24 | 25 |
26 |
27 | 30 |
31 |
32 | 33 |
34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | {% block javascripts %}{% endblock %} 42 | 43 | {% endblock %} 44 | -------------------------------------------------------------------------------- /templates/admin/order/_embed/_table_actions_btn.html.twig: -------------------------------------------------------------------------------- 1 | 4 | Reset filters -------------------------------------------------------------------------------- /templates/admin/pages/dashboard.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'admin/layout/layout_main.html.twig' %} 2 | 3 | {% block title_detailed %}Dashboard{% endblock %} 4 | 5 | {% block body %} 6 |
7 | 8 |
9 |

Dashboard

10 |
11 | 12 |
13 | 14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /templates/admin/product/_embed/_table_actions_btn.html.twig: -------------------------------------------------------------------------------- 1 | 4 | Reset filters -------------------------------------------------------------------------------- /templates/bundles/TwigBundle/Exception/error.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'front/base.html.twig' %} 2 | 3 | {% block body_id 'error' %} 4 | 5 | {% block body %} 6 |
7 |
8 |
9 |
10 |
11 |
12 | Oops! We can't seem to find out the page you're looking for. 13 |
14 | 15 | Return to homepage 16 | 17 |
18 |
19 |
20 |
21 |
22 | {% endblock %} -------------------------------------------------------------------------------- /templates/front/_embed/_header.html.twig: -------------------------------------------------------------------------------- 1 | {% include 'front/_embed/_menu/_mobile_menu.html.twig' %} 2 | 3 |
4 | {% include 'front/_embed/_menu/_desktop_menu.html.twig' %} 5 |
-------------------------------------------------------------------------------- /templates/front/_embed/_menu/_login_via_social_network.html.twig: -------------------------------------------------------------------------------- 1 |
2 |
3 | 8 | 13 | 18 | {% if app.request.locale == 'ru' %} 19 | 24 | {% endif %} 25 | {% if app.request.locale == 'en' %} 26 | 31 | {% endif %} 32 |
33 |
-------------------------------------------------------------------------------- /templates/front/_embed/_menu/_menu_cart.html.twig: -------------------------------------------------------------------------------- 1 | {{ encore_entry_link_tags('appFrontMenuCart') }} 2 |
3 | 20 | {{ encore_entry_script_tags('appFrontMenuCart') }} -------------------------------------------------------------------------------- /templates/front/_embed/_menu/_menu_nav_item.twig: -------------------------------------------------------------------------------- 1 | {% for category in nav_categories %} 2 | 7 | {% endfor %} 8 | -------------------------------------------------------------------------------- /templates/front/_embed/_utils/_error_authenticator.html.twig: -------------------------------------------------------------------------------- 1 | {% if error %} 2 | 8 | {% endif %} -------------------------------------------------------------------------------- /templates/front/_embed/_utils/_flash_message.html.twig: -------------------------------------------------------------------------------- 1 | {% for label, messages in app.flashes(['success','warning','info', 'danger']) %} 2 | {% for message in messages %} 3 | 9 | {% endfor %} 10 | {% endfor %} -------------------------------------------------------------------------------- /templates/front/_embed/_utils/_social_network_link_unlink_btn.html.twig: -------------------------------------------------------------------------------- 1 | {% if socialNameId %} 2 | 5 | 8 | {% else %} 9 | {% if 'github' != socialName %} 10 | 13 | {% else %} 14 | 17 | {% endif %} 18 | 21 | {% endif %} 22 | -------------------------------------------------------------------------------- /templates/front/email/base.html.twig: -------------------------------------------------------------------------------- 1 | {% apply inky_to_html|inline_css(source('@public' ~ asset('build/email.css'))) %} 2 | 3 | 4 | 5 | 6 |
7 | 8 |
9 |
10 |
11 |
12 | 13 | {% block content %}{% endblock %} 14 | 15 | 16 | 17 | 18 | 19 |

20 |

21 | By Symfony shop (pet projects) 22 |
23 |

24 |
25 |
26 |
27 | {% endapply %} -------------------------------------------------------------------------------- /templates/front/email/client/user_logged_in_via_social_network.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'front/email/base.html.twig' %} 2 | 3 | {% block content %} 4 | 5 | 6 | 7 |
8 |

Symfony shop - Your new password!

9 |
10 | 11 |
12 |
13 | 14 | 15 |
Hello, {{ user.fullName }}!

16 |

17 | To login into your profile use this new password

18 | {{ plainPassword }} 19 |

20 |
21 |
22 | 23 | 24 | 25 |

Please confirm your email address by clicking the following link:

26 | Confirm my Email. 27 |

This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}.

28 |
29 |
30 | 31 | 32 | 33 | Visit our profile. 34 | 35 | 36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /templates/front/email/manager/created_order_from_cart.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'front/email/base.html.twig' %} 2 | 3 | {% block content %} 4 | 5 | 6 | 7 |
8 |

Client created order (ID: {{ order.id }})

9 |
10 | 11 |
12 |
13 | 14 | 15 |
16 |

Order info:

17 |
18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {% for orderProduct in order.orderProducts %} 33 | 34 | 35 | 36 | 37 | 38 | 39 | {% endfor %} 40 | 41 |
#TitleQuantityPrice
{{ loop.index }}{{ orderProduct.product.title }}{{ orderProduct.quantity }}{{ orderProduct.product.price }}
42 | 43 |

44 | Total price: {{ order.totalPrice }} 45 |

46 |
47 |
48 | {% endblock %} 49 | -------------------------------------------------------------------------------- /templates/front/email/security/confirmation_email.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'front/email/base.html.twig' %} 2 | 3 | {% block content %} 4 | 5 | 6 | 7 |
8 |

Hi! Please confirm your email!

9 |
10 |
11 | 12 |
13 | 14 | 15 | 16 |

Please confirm your email address by clicking the following link:

17 | Confirm my Email. 18 |

This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}.

19 |
20 |
21 | 22 | 23 | Visit our profile. 24 | 25 | 26 | {% endblock %} -------------------------------------------------------------------------------- /templates/front/email/security/reset_password.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'front/email/base.html.twig' %} 2 | 3 | {% block content %} 4 | 5 | 6 | 7 |
8 |

Your password reset request

9 |
10 |
11 |
12 | 13 | 14 | 15 | 16 |

To reset your password, please visit the following link

17 | {{ url('main_reset_password', {token: resetToken.token}) }} 18 |

This link will expire in {{ resetToken.expirationMessageKey|trans(resetToken.expirationMessageData, 'ResetPasswordBundle') }}.

19 |
20 |
21 | {% endblock %} -------------------------------------------------------------------------------- /templates/front/reset_password/check_email.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'front/base.html.twig' %} 2 | 3 | {% block title %}Password Reset Email Sent{% endblock %} 4 | 5 | {% block body %} 6 |
7 |
8 |
9 |
10 | 18 |
19 |
20 |
21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /templates/front/reset_password/request.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'front/base.html.twig' %} 2 | 3 | {% block title %}Reset your password{% endblock %} 4 | 5 | {% block body %} 6 |
7 |
8 |
9 |
10 | 30 |
31 |
32 |
33 |
34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /templates/front/reset_password/reset.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'front/base.html.twig' %} 2 | 3 | {% block title %}Reset your password{% endblock %} 4 | 5 | {% block body %} 6 |
7 |
8 |
9 |
10 | 33 |
34 |
35 |
36 |
37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /templates/front/robots.txt.twig: -------------------------------------------------------------------------------- 1 | # For All bots 2 | User-agent: * 3 | Disallow: / 4 | Allow: /$ 5 | 6 | # For Yandex bots 7 | User-agent: Yandex 8 | Disallow: / 9 | Allow: /$ 10 | 11 | # For Google bots 12 | User-agent: GoogleBot 13 | Disallow: / 14 | Allow: /$ 15 | 16 | Sitemap: {{ sitemap }} 17 | {# Для рабочего сайта #} 18 | {## For All bots#} 19 | {#User-agent: *#} 20 | {#Disallow: */admin/*#} 21 | {#Disallow: /admin/*#} 22 | {#Disallow: /bundles/*#} 23 | {#Disallow: /build/*#} 24 | {#Disallow: /upload/*#} 25 | {#Disallow: /admin/*?*#} 26 | {#Disallow: */admin/*?*#} 27 | {#Disallow: *_filter_form#} 28 | {#{% for activeCategory in activeCategories%}#} 29 | {#Allow: {{ activeCategory }}#} 30 | {#{% endfor %}#} 31 | 32 | {## For Yandex bots#} 33 | {#User-agent: Yandex#} 34 | {#Disallow: */admin/*#} 35 | {#Disallow: /admin/*#} 36 | {#Disallow: /bundles/*#} 37 | {#Disallow: /build/*#} 38 | {#Disallow: /upload/*#} 39 | {#Disallow: /admin/*?*#} 40 | {#Disallow: */admin/*?*#} 41 | {#Disallow: *_filter_form#} 42 | {#{% for activeCategory in activeCategories%}#} 43 | {#Allow: {{ activeCategory }}#} 44 | {#{% endfor %}#} 45 | 46 | {## For Google bots#} 47 | {#User-agent: GoogleBot#} 48 | {#Disallow: */admin/*#} 49 | {#Disallow: /admin/*#} 50 | {#Disallow: /bundles/*#} 51 | {#Disallow: /build/*#} 52 | {#Disallow: /upload/*#} 53 | {#Disallow: /admin/*?*#} 54 | {#Disallow: */admin/*?*#} 55 | {#Disallow: *_filter_form#} 56 | {#{% for activeCategory in activeCategories%}#} 57 | {#Allow: {{ activeCategory }}#} 58 | {#{% endfor %}#} -------------------------------------------------------------------------------- /templates/front/sitemap.xml.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ data.loc }} 6 | {{ data.lastmod }} 7 | {{ data.changefreq }} 8 | {{ data.priority }} 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/Functional/ApiPlatform/ResourceTestUtils.php: -------------------------------------------------------------------------------- 1 | 'application/ld+json', 14 | 'CONTENT_TYPE' => 'application/json', 15 | ]; 16 | 17 | protected const REQUEST_HEADERS_PATCH = [ 18 | 'HTTP_ACCEPT' => 'application/ld+json', 19 | 'CONTENT_TYPE' => 'application/merge-patch+json', 20 | ]; 21 | 22 | protected function getResponseDecodeContent(AbstractBrowser $client) 23 | { 24 | return json_decode($client->getResponse()->getContent()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/TestUtils/Fixtures/UserFixtures.php: -------------------------------------------------------------------------------- 1 | bootEnv(dirname(__DIR__).'/.env'); 14 | } 15 | -------------------------------------------------------------------------------- /translations/LexikFormFilterBundle.en.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Is Published 7 | Опубликовано 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /translations/LexikFormFilterBundle.en.yml: -------------------------------------------------------------------------------- 1 | 'Is Published': Опубликовано 2 | -------------------------------------------------------------------------------- /translations/LexikFormFilterBundle.ru.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Is Published 7 | Is Published 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /translations/LexikFormFilterBundle.ru.yml: -------------------------------------------------------------------------------- 1 | 'Is Published': 'Is Published' 2 | -------------------------------------------------------------------------------- /translations/validators.en.yml: -------------------------------------------------------------------------------- 1 | 'There is already an account with this email': 'There is already an account with this email' 2 | 'The password fields must match.': 'The password fields must match.' 3 | 'Please enter a title': 'Please enter a title' 4 | 'Please select user': 'Please select user' 5 | 'Please select status': 'Please select status' 6 | 'Please enter a price': 'Please enter a price' 7 | 'Please upload a valid image (*.jpg or *.png)': 'Please upload a valid image (*.jpg or *.png)' 8 | 'Please indicate a quantity': 'Please indicate a quantity' 9 | 'Please select a category': 'Please select a category' 10 | 'Price cannot be less than or equal to zero.': 'Price cannot be less than or equal to zero.' 11 | --------------------------------------------------------------------------------