diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9191d51 --- /dev/null +++ b/.env.example @@ -0,0 +1,19 @@ +APP_HOST="localhost:8080" +APP_NAME="BuysForLife" +SQLITE_DB="db-dev.sqlite" +# SMTP for login, order, and admin notifications +SMTP_HOST="smtp.example.com" # SMTP uses TLS +SMTP_USER="user@example.com" +SMTP_PASS="your-super-secure-pass" +SMTP_FROM="noreply@example.com" +# Bitcoin Lightning Netowrk Address LNURL +# Used for recieving payment at checkout +# !! Choose your LN_SERVICE carefully!! +# NOTE: The LN_SERVICE must support LUD-21 Payment Verification +LN_ADDRESS="your@node.win" +# maps.co for GeoCoder (postal address verification) +# Get your free API key: https://geocode.maps.co +GEOCODE_MAPS_CO_API_KEY="your-api-key-from-geocode.maps.co" +# Plausible for privacy-respecting page analytics +# https://github.com/plausible/analytics +PLAUSIBLE_HOST="https://plausible.io/" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8eb148 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +vendor +db.sqlite +.env +public/style.css \ No newline at end of file diff --git a/README.md b/README.md index a1545eb..2d54a6e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,73 @@ -# ecomm-store +# Ecomm Store +PHP MVC Ecomm App w/SqliteDB + +## Goals for MVP + +- Avoid using JavaScript - make it usable without +- Multicurrency sats/cents +- Store Credit +- Products, carts, checkout, orders, returns/refunds. +- Subscriptions/recurring payments +- Email sign-in links, email notifications +- Light/dark theme + +## Initial setup/install + +Copy the `.env.example` into `.env` and edit with your preferences/credentials. + +`cp .env.example .env` + +`vim .env` + +install composer packages using the `composer.phar` executable included in this repo + +`chmod +x composer.phar` + +`php composer.phar install` + +Use tailwind-cli to automatically generate `/public/style.css` from `/src/style.css` + +This repo includes an older `tailwindcss` binary for Apple ARM M-series processors. I could not get recent versions to work properly. + +`chmod +x tailwindcss` + +`./tailwindcss -i ./src/style.css -o ./public/style.css --watch` + +You should only use `--watch` in development. + +Tailwind (JavaScript) only generates the CSS file used in prod. We're still "avoiding JS". + +## Run it + +Start the local Php server from the project root, serves only the /public folder + +`php -S localhost:8080 -t public` + +Goto `http://localhost:8080` in your browser. + +## Development + +Fork it and make changes if you want! Make a PR into `main`. + +### Routing + +`/public/index.php` is the router, define all your urls there and assign a controller method for each route + +Other app-wide defaults and vars are also set here. + +### Themeing + +`public/index.php` is also where all colors are defined. It uses tailwind class strings to cover both light and dark themes. + +`tailwind.config.js` can be used to define a custom color pallete. + +There is a bit of JS used to set a cookie that the server can read. This cookie just contains the user's browser's theme preference so the server can efficiently deliver light/dark image assets based on the theme. + +### Composer + +To add additional composer packages: + +`php composer.phar require ` + +Then commit the `composer.json`. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..96eee48 --- /dev/null +++ b/composer.json @@ -0,0 +1,15 @@ +{ + "name": "lyberry/lyberry.com", + "description": "homepage", + "autoload": { + "psr-4": { + "app\\": "src/" + } + }, + "require": { + "phpmailer/phpmailer": "^6.9.2", + "vlucas/phpdotenv": "^5.6", + "web-auth/webauthn-lib": "^5.0", + "twig/twig": "^3.0" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..f70a913 --- /dev/null +++ b/composer.lock @@ -0,0 +1,2677 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "ea128e458544f87060ad53880489fcc2", + "packages": [ + { + "name": "brick/math", + "version": "0.12.1", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "5.16.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.12.1" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2023-11-29T23:19:16+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12", + "phpstan/phpstan": "1.4.10 || 2.0.3", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.4" + }, + "time": "2024-12-07T21:18:45+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:45:45+00:00" + }, + { + "name": "paragonie/constant_time_encoding", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512", + "shasum": "" + }, + "require": { + "php": "^8" + }, + "require-dev": { + "phpunit/phpunit": "^9", + "vimeo/psalm": "^4|^5" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2024-05-08T12:36:18+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", + "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.1" + }, + "time": "2024-12-07T09:39:29+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.18|^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" + }, + "time": "2024-11-09T15:12:26+00:00" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.9.3", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2f5c94fe7493efc213f643c23b1b1c249d40f47e", + "reference": "2f5c94fe7493efc213f643c23b1b1c249d40f47e", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.3" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2024-11-24T18:04:13+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.3", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:41:07+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/c00d78fb6b29658347f9d37ebe104bffadf36299", + "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.0" + }, + "time": "2024-10-13T11:29:49+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "spomky-labs/cbor-php", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/cbor-php.git", + "reference": "499d9bff0a6d59c4f1b813cc617fc3fd56d6dca4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/499d9bff0a6d59c4f1b813cc617fc3fd56d6dca4", + "reference": "499d9bff0a6d59c4f1b813cc617fc3fd56d6dca4", + "shasum": "" + }, + "require": { + "brick/math": "^0.9|^0.10|^0.11|^0.12", + "ext-mbstring": "*", + "php": ">=8.0" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0", + "ext-json": "*", + "infection/infection": "^0.29", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-beberlei-assert": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^10.1|^11.0", + "qossmic/deptrac": "^2.0", + "rector/rector": "^1.0", + "roave/security-advisories": "dev-latest", + "symfony/var-dumper": "^6.0|^7.0", + "symplify/easy-coding-standard": "^12.0" + }, + "suggest": { + "ext-bcmath": "GMP or BCMath extensions will drastically improve the library performance. BCMath extension needed to handle the Big Float and Decimal Fraction Tags", + "ext-gmp": "GMP or BCMath extensions will drastically improve the library performance" + }, + "type": "library", + "autoload": { + "psr-4": { + "CBOR\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/cbor-php/contributors" + } + ], + "description": "CBOR Encoder/Decoder for PHP", + "keywords": [ + "Concise Binary Object Representation", + "RFC7049", + "cbor" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/cbor-php/issues", + "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.1.0" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2024-07-18T08:37:03+00:00" + }, + { + "name": "spomky-labs/pki-framework", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/pki-framework.git", + "reference": "5ac374c3e295c8b917208ff41b4d30f76668478c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/5ac374c3e295c8b917208ff41b4d30f76668478c", + "reference": "5ac374c3e295c8b917208ff41b4d30f76668478c", + "shasum": "" + }, + "require": { + "brick/math": "^0.10|^0.11|^0.12", + "ext-mbstring": "*", + "php": ">=8.1" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", + "ext-gmp": "*", + "ext-openssl": "*", + "infection/infection": "^0.28|^0.29", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/extension-installer": "^1.3|^2.0", + "phpstan/phpstan": "^1.8|^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", + "phpstan/phpstan-phpunit": "^1.1|^2.0", + "phpstan/phpstan-strict-rules": "^1.3|^2.0", + "phpunit/phpunit": "^10.1|^11.0", + "rector/rector": "^1.0|^2.0", + "roave/security-advisories": "dev-latest", + "symfony/string": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symplify/easy-coding-standard": "^12.0" + }, + "suggest": { + "ext-bcmath": "For better performance (or GMP)", + "ext-gmp": "For better performance (or BCMath)", + "ext-openssl": "For OpenSSL based cyphering" + }, + "type": "library", + "autoload": { + "psr-4": { + "SpomkyLabs\\Pki\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joni Eskelinen", + "email": "jonieske@gmail.com", + "role": "Original developer" + }, + { + "name": "Florent Morselli", + "email": "florent.morselli@spomky-labs.com", + "role": "Spomky-Labs PKI Framework developer" + } + ], + "description": "A PHP framework for managing Public Key Infrastructures. It comprises X.509 public key certificates, attribute certificates, certification requests and certification path validation.", + "homepage": "https://github.com/spomky-labs/pki-framework", + "keywords": [ + "DER", + "Private Key", + "ac", + "algorithm identifier", + "asn.1", + "asn1", + "attribute certificate", + "certificate", + "certification request", + "cryptography", + "csr", + "decrypt", + "ec", + "encrypt", + "pem", + "pkcs", + "public key", + "rsa", + "sign", + "signature", + "verify", + "x.509", + "x.690", + "x509", + "x690" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/pki-framework/issues", + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.2.2" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2025-01-03T09:35:48+00:00" + }, + { + "name": "symfony/clock", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/property-access", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-access.git", + "reference": "3ae42efba01e45aaedecf5c93c8d6a3ab3a82276" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-access/zipball/3ae42efba01e45aaedecf5c93c8d6a3ab3a82276", + "reference": "3ae42efba01e45aaedecf5c93c8d6a3ab3a82276", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/property-info": "^6.4|^7.0" + }, + "require-dev": { + "symfony/cache": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyAccess\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides functions to read and write from/to an object or array using a simple string notation", + "homepage": "https://symfony.com", + "keywords": [ + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" + ], + "support": { + "source": "https://github.com/symfony/property-access/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-26T12:28:35+00:00" + }, + { + "name": "symfony/property-info", + "version": "v7.2.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/property-info.git", + "reference": "1dfeb0dac7a99f7b3be42db9ccc299c5a6483fcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/property-info/zipball/1dfeb0dac7a99f7b3be42db9ccc299c5a6483fcf", + "reference": "1dfeb0dac7a99f7b3be42db9ccc299c5a6483fcf", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/string": "^6.4|^7.0", + "symfony/type-info": "~7.1.9|^7.2.2" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/dependency-injection": "<6.4" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^5.2", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PropertyInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts information about PHP class' properties using metadata of popular sources", + "homepage": "https://symfony.com", + "keywords": [ + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" + ], + "support": { + "source": "https://github.com/symfony/property-info/tree/v7.2.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-31T11:04:50+00:00" + }, + { + "name": "symfony/serializer", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/serializer.git", + "reference": "3f5ed9f5e6c02e3853109190ba38408f5e1d2dd0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/serializer/zipball/3f5ed9f5e6c02e3853109190ba38408f5e1d2dd0", + "reference": "3f5ed9f5e6c02e3853109190ba38408f5e1d2dd0", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/dependency-injection": "<6.4", + "symfony/property-access": "<6.4", + "symfony/property-info": "<6.4", + "symfony/uid": "<6.4", + "symfony/validator": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "seld/jsonlint": "^1.10", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^7.2", + "symfony/error-handler": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/type-info": "^7.1", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Serializer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/serializer/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-25T15:21:05+00:00" + }, + { + "name": "symfony/string", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-11-13T13:31:26+00:00" + }, + { + "name": "symfony/type-info", + "version": "v7.2.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/type-info.git", + "reference": "3b5a17470fff0034f25fd4287cbdaa0010d2f749" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/type-info/zipball/3b5a17470fff0034f25fd4287cbdaa0010d2f749", + "reference": "3b5a17470fff0034f25fd4287cbdaa0010d2f749", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^1.0|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\TypeInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts PHP types information.", + "homepage": "https://symfony.com", + "keywords": [ + "PHPStan", + "phpdoc", + "symfony", + "type" + ], + "support": { + "source": "https://github.com/symfony/type-info/tree/v7.2.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-20T13:38:37+00:00" + }, + { + "name": "symfony/uid", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "2d294d0c48df244c71c105a169d0190bfb080426" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/2d294d0c48df244c71c105a169d0190bfb080426", + "reference": "2d294d0c48df244c71c105a169d0190bfb080426", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "twig/twig", + "version": "v3.18.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50", + "reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php81": "^1.29" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.18.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-12-29T10:51:50+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.1", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.3", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:52:34+00:00" + }, + { + "name": "web-auth/cose-lib", + "version": "4.4.0", + "source": { + "type": "git", + "url": "https://github.com/web-auth/cose-lib.git", + "reference": "2166016e48e0214f4f63320a7758a9386d14c92a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/2166016e48e0214f4f63320a7758a9386d14c92a", + "reference": "2166016e48e0214f4f63320a7758a9386d14c92a", + "shasum": "" + }, + "require": { + "brick/math": "^0.9|^0.10|^0.11|^0.12", + "ext-json": "*", + "ext-openssl": "*", + "php": ">=8.1", + "spomky-labs/pki-framework": "^1.0" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0", + "infection/infection": "^0.29", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan": "^1.7", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "phpunit/phpunit": "^10.1|^11.0", + "qossmic/deptrac": "^2.0", + "rector/rector": "^1.0", + "symfony/phpunit-bridge": "^6.4|^7.0", + "symplify/easy-coding-standard": "^12.0" + }, + "suggest": { + "ext-bcmath": "For better performance, please install either GMP (recommended) or BCMath extension", + "ext-gmp": "For better performance, please install either GMP (recommended) or BCMath extension" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cose\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-auth/cose/contributors" + } + ], + "description": "CBOR Object Signing and Encryption (COSE) For PHP", + "homepage": "https://github.com/web-auth", + "keywords": [ + "COSE", + "RFC8152" + ], + "support": { + "issues": "https://github.com/web-auth/cose-lib/issues", + "source": "https://github.com/web-auth/cose-lib/tree/4.4.0" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2024-07-18T08:47:32+00:00" + }, + { + "name": "web-auth/webauthn-lib", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/web-auth/webauthn-lib.git", + "reference": "2cc8262b885cf01eee3c4c10ca3985bdd2614c97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/2cc8262b885cf01eee3c4c10ca3985bdd2614c97", + "reference": "2cc8262b885cf01eee3c4c10ca3985bdd2614c97", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "paragonie/constant_time_encoding": "^2.6|^3.0", + "php": ">=8.2", + "phpdocumentor/reflection-docblock": "^5.3", + "psr/clock": "^1.0", + "psr/event-dispatcher": "^1.0", + "psr/log": "^1.0|^2.0|^3.0", + "spomky-labs/cbor-php": "^3.0", + "spomky-labs/pki-framework": "^1.0", + "symfony/clock": "^6.4|^7.0", + "symfony/deprecation-contracts": "^3.2", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "web-auth/cose-lib": "^4.2.3" + }, + "suggest": { + "psr/log-implementation": "Recommended to receive logs from the library", + "symfony/event-dispatcher": "Recommended to use dispatched events", + "web-token/jwt-library": "Mandatory for fetching Metadata Statement from distant sources" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/web-auth/webauthn-framework", + "name": "web-auth/webauthn-framework" + } + }, + "autoload": { + "psr-4": { + "Webauthn\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-auth/webauthn-library/contributors" + } + ], + "description": "FIDO2/Webauthn Support For PHP", + "homepage": "https://github.com/web-auth", + "keywords": [ + "FIDO2", + "fido", + "webauthn" + ], + "support": { + "source": "https://github.com/web-auth/webauthn-lib/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2024-07-20T05:24:59+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/composer.phar b/composer.phar new file mode 100755 index 0000000..b59d7e1 Binary files /dev/null and b/composer.phar differ diff --git a/public/.DS_Store b/public/.DS_Store new file mode 100644 index 0000000..97866ee Binary files /dev/null and b/public/.DS_Store differ diff --git a/public/img/.DS_Store b/public/img/.DS_Store new file mode 100644 index 0000000..597b10a Binary files /dev/null and b/public/img/.DS_Store differ diff --git a/public/img/empty/.DS_Store b/public/img/empty/.DS_Store new file mode 100644 index 0000000..321a871 Binary files /dev/null and b/public/img/empty/.DS_Store differ diff --git a/public/img/empty/404-dark.svg b/public/img/empty/404-dark.svg new file mode 100644 index 0000000..a8f2f45 --- /dev/null +++ b/public/img/empty/404-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/404-light.svg b/public/img/empty/404-light.svg new file mode 100644 index 0000000..f7f395b --- /dev/null +++ b/public/img/empty/404-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/cart-dark.svg b/public/img/empty/cart-dark.svg new file mode 100644 index 0000000..2ed319e --- /dev/null +++ b/public/img/empty/cart-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/cart-light.svg b/public/img/empty/cart-light.svg new file mode 100644 index 0000000..9549987 --- /dev/null +++ b/public/img/empty/cart-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/error-dark.svg b/public/img/empty/error-dark.svg new file mode 100644 index 0000000..d0f8c74 --- /dev/null +++ b/public/img/empty/error-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/error-light.svg b/public/img/empty/error-light.svg new file mode 100644 index 0000000..42083ec --- /dev/null +++ b/public/img/empty/error-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/file-dark.svg b/public/img/empty/file-dark.svg new file mode 100644 index 0000000..60f8f59 --- /dev/null +++ b/public/img/empty/file-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/file-light.svg b/public/img/empty/file-light.svg new file mode 100644 index 0000000..5eba2f2 --- /dev/null +++ b/public/img/empty/file-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/message-dark.svg b/public/img/empty/message-dark.svg new file mode 100644 index 0000000..62523e5 --- /dev/null +++ b/public/img/empty/message-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/message-light.svg b/public/img/empty/message-light.svg new file mode 100644 index 0000000..c3477c2 --- /dev/null +++ b/public/img/empty/message-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/notice-dark.svg b/public/img/empty/notice-dark.svg new file mode 100644 index 0000000..6473510 --- /dev/null +++ b/public/img/empty/notice-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/notice-light.svg b/public/img/empty/notice-light.svg new file mode 100644 index 0000000..fc834ba --- /dev/null +++ b/public/img/empty/notice-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/order-dark.svg b/public/img/empty/order-dark.svg new file mode 100644 index 0000000..67cabfb --- /dev/null +++ b/public/img/empty/order-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/order-light.svg b/public/img/empty/order-light.svg new file mode 100644 index 0000000..787e591 --- /dev/null +++ b/public/img/empty/order-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/payment-dark.svg b/public/img/empty/payment-dark.svg new file mode 100644 index 0000000..13548c8 --- /dev/null +++ b/public/img/empty/payment-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/payment-light.svg b/public/img/empty/payment-light.svg new file mode 100644 index 0000000..2c026ae --- /dev/null +++ b/public/img/empty/payment-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/plug-dark.svg b/public/img/empty/plug-dark.svg new file mode 100644 index 0000000..cf4f89e --- /dev/null +++ b/public/img/empty/plug-dark.svg @@ -0,0 +1 @@ +Monochromatic \ No newline at end of file diff --git a/public/img/empty/plug-light.svg b/public/img/empty/plug-light.svg new file mode 100644 index 0000000..e5abe5b --- /dev/null +++ b/public/img/empty/plug-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/search-dark.svg b/public/img/empty/search-dark.svg new file mode 100644 index 0000000..2c7c715 --- /dev/null +++ b/public/img/empty/search-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/empty/search-light.svg b/public/img/empty/search-light.svg new file mode 100644 index 0000000..eb3f281 --- /dev/null +++ b/public/img/empty/search-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/icon.png b/public/img/icon.png new file mode 100644 index 0000000..8392239 Binary files /dev/null and b/public/img/icon.png differ diff --git a/public/img/logo-dark.webp b/public/img/logo-dark.webp new file mode 100644 index 0000000..ce1ac92 Binary files /dev/null and b/public/img/logo-dark.webp differ diff --git a/public/img/logo-light.webp b/public/img/logo-light.webp new file mode 100644 index 0000000..de40ebe Binary files /dev/null and b/public/img/logo-light.webp differ diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..406eaac --- /dev/null +++ b/public/index.php @@ -0,0 +1,165 @@ +load(); + +// Start the session +app::init_db(); +use app\models\addresses; +use app\models\carts; +use app\models\magic_links; +use app\models\orders; +use app\models\products; +use app\models\user_addresses; +use app\models\users; + +if (!app::$db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")->fetch()) { + addresses::init(); + carts::init(); + magic_links::init(); + orders::init(); + products::init(); + user_addresses::init(); + users::init(); +} + +session_start(); +session_regenerate_id(true); // prevent session fixation attacks + +// prevent session hijack +if (!isset($_SESSION['fingerprint'])) { + $_SESSION['fingerprint'] = hash('sha256', $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT']); +} else { + if ($_SESSION['fingerprint'] !== hash('sha256', $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'])) { + session_unset(); + session_destroy(); + } +} + + +// these will be available to use in all twig templates +$defaults = [ + 'copyright_year' => date('Y'), + 'session' => $_SESSION, + 'env' => $_ENV, + // uses cookie-js to get the client's preferred theme + // used to conditionally deliver image assets + // or styles based on theme + 'theme' => isset($_COOKIE["theme"]) ? $_COOKIE["theme"] : 'light', + // set your tailwind colors here for app themeing + // the idea is to avoid using colors in your templates + 'colors' => [ + 'header' => [ + 'banner' => 'bg-gray-100 dark:bg-gray-600 text-gray-200 dark:text-gray-200', + + ], + 'anchor' => [ + 'primary' => 'text-blue-400 dark:text-blue-200' + ], + 'body' => 'bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300', + 'button' => [ + 'primary' => 'border-blue-400 dark:border-blue-600 dark:hover:border-blue-800 bg-blue-400 dark:bg-blue-600 hover:bg-blue-600 hover:dark:bg-blue-800 text-white dark:text-white', + 'default' => 'hover:bg-gray-50 dark:hover:bg-gray-900' + ], + 'breadcrumb' => [ + 'parent' => 'text-gray-300 dark:text-gray-400 hover:text-gray-400 dark:hover:text-gray-500', + 'seperator' => 'text-gray-200 dark:text-gray-200', + 'child' => 'text-gray-200 dark:text-gray-300' + ], + 'dropdown' => [ + 'list' => 'bg-white dark:bg-blue-900 border-gray-600 dark:border-gray-300', + 'item' => 'hover:bg-gray-200 dark:hover:bg-gray-900' + ], + 'input' => 'text-gray-800 dark:text-gray-300 bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-500 focus:ring-blue-500', + 'error' => [ + 'text' => 'text-red-600', + 'alert' => 'bg-red-100 text-gray-800 border-red-600' + ], + 'warning' => [ + 'text' => 'text-yellow-400', + 'alert' => 'bg-yellow-100 text-gray-800 border-yellow-400' + ], + 'success' => [ + 'text' => 'text-green-600', + 'alert' => 'bg-green-100 text-gray-800 border-green-600' + ], + 'info' => [ + 'text' => 'text-blue-400', + 'alert' => 'bg-blue-200 text-gray-800 border-blue-400' + ], + 'modal' => [ + 'content' => 'bg-white dark:bg-blue-900 border-gray-600 dark:border-gray-300', + 'shadow' => 'bg-black/70' + ], + 'nav' => [ + 'bar' => 'bg-blue-400 dark:bg-blue-600 text-gray-200 dark:text-gray-200', + 'item' => 'hover:bg-blue-600 dark:hover:bg-blue-800 hover:text-gray-200 dark:hover:text-gray-300 text-white border-blue-400 dark:border-blue-600', + 'hovercontent' => 'bg-white dark:bg-slate-700 text-gray-800 dark:text-gray-300' + ], + 'rule' => 'border-gray-400 dark:border-gray-400', + 'text' => [ + 'muted' => 'text-gray-400 dark:text-gray-300' + ], + 'toggle' => "bg-gray-300 peer-checked:bg-green-400 after:bg-white", + 'footer' => [ + "primary" => "bg-gray-200 dark:bg-slate-600 text-gray-500 dark:text-gray-300", + "policy" => "bg-slate-400 dark:bg-slate-800 text-gray-200 dark:text-gray-400" + ], + ] +]; + +// Setup a twig +$loader = new \Twig\Loader\FilesystemLoader(paths: dirname(__DIR__) . '/src/views'); +$GLOBALS['twig'] = new \Twig\Environment($loader, [ + //'cache' => dirname(__DIR__) . '/cache', + 'cache' => false, +]); + +$route = explode(separator: '?', string: $_SERVER['REQUEST_URI'])[0]; +if (str_starts_with(haystack: $route, needle: '/.well-known/lnurlp/')) { + $route = '/lnurlp'; +} + +$controller = match ($route) { + '/' => home::index($defaults), + '/account' => account::index($defaults), + '/account/profile' => account::profile(), + '/account/login' => account::login($defaults), + '/account/logout' => account::logout(), + '/magic-link' => magic_link::index(), + '/account/returns' => account::returns($defaults), + '/account/signup' => account::signup($defaults), + '/account/billing' => account::billing($defaults), + '/account/orders' => account::orders($defaults), + '/account/shipping' => account::shipping($defaults), + '/checkout/confirmed' => checkout::confirmed($defaults), + '/checkout/review-pay' => checkout::review_pay($defaults), + '/checkout/shipping-billing' => checkout::shipping_billing($defaults), + '/support/ask' => support::index($defaults), + '/support/bitcoin' => support::bitcoin($defaults), + '/cart' => cart::index($defaults), + '/lnurlp' => lnurlp::index(), + // product categories + '/power-meters' => category::power_meters($defaults), + default => lost::index($defaults) +}; + +// Clear alerts after rendering +foreach (['error', 'warning', 'info', 'success'] as $alert) { + unset($_SESSION[$alert]); +} diff --git a/src/app.php b/src/app.php new file mode 100644 index 0000000..3eef2ac --- /dev/null +++ b/src/app.php @@ -0,0 +1,50 @@ +setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + } catch (\PDOException $e) { + die("Database error: " . $e->getMessage()); + } + } + public static function send_mail($to, $from, $from_name, $subject, $message, $HTML_message) + { + $mail = new PHPMailer(exceptions: true); + //Server settings + $mail->SMTPDebug = SMTP::DEBUG_SERVER; + $mail->isSMTP(); + $mail->Host = $_ENV['SMTP_HOST']; + $mail->SMTPAuth = true; + $mail->Username = $_ENV['SMTP_USER']; + $mail->Password = $_ENV['SMTP_PASS']; + $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + $mail->Port = 587; + $mail->isHTML(true); + $mail->setFrom($from, $from_name); + $mail->addAddress(address: $to); + $mail->Subject = $subject; + $mail->Body = $HTML_message; + $mail->AltBody = $message; + + // Buffer the output + ob_start(); + $mail->send(); + ob_end_clean(); + } + public static function sendJson($data, $status = 200) + { + http_response_code($status); + header('Content-Type: application/json'); + echo json_encode($data); + exit; + } +} diff --git a/src/controllers/account.php b/src/controllers/account.php new file mode 100644 index 0000000..cb13e1e --- /dev/null +++ b/src/controllers/account.php @@ -0,0 +1,331 @@ +render('lib/page/index.twig', array_merge($defaults, [ + 'child_template' => 'account/index.twig', + 'page_title' => 'Manage Account - ' . $_ENV['APP_NAME'], + 'user' => $user, + 'shipping' => $ship_addrs, + 'billing' => $bill_addrs, + 'default_shipping' => $default_shipping, + 'default_billing' => $default_billing, + 'breadcrumbs' => [ + [ + 'url' => null, + 'title' => 'My Account', + ] + ] + ])); + } + public static function billing($defaults) + { + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + if (!$_SESSION['user_id']) { + http_response_code(403); + } + $bill = addresses::validatePost("billing"); + $bill_id = addresses::add( + $bill['name'], + $bill['company'], + $bill['street'], + $bill['boxapt'], + $bill['city'], + $bill['state'], + $bill['zip'], + $bill['phone'], + 1, + 0 + ); + user_addresses::add( + $_SESSION['user_id'], + $bill_id + ); + $_SESSION['success'] = "Billing address saved!"; + header('Location: /account/billing'); + } + $email = $_SESSION['user_email']; + $user = users::getByEmail($email); + $default_billing = null; + $bill_addrs = []; + $bill_addresses = user_addresses::getBillingByUserId($_SESSION['user_id']); + foreach ($bill_addresses as $addr) { + if ($addr['id'] == $user['billing_address_id']){ + $default_billing = $addr; + } else { + $bill_addrs[] = $addr; + } + } + echo $GLOBALS['twig']->render('lib/page/index.twig', array_merge($defaults, [ + 'child_template' => 'account/billing.twig', + 'page_title' => 'Billing Information - ' . $_ENV['APP_NAME'], + 'billing' => $bill_addrs, + 'default_billing' => $default_billing, + 'breadcrumbs' => [ + [ + 'url' => '/account', + 'title' => 'My Account' + ], + [ + 'url' => null, + 'title' => 'Billing' + ] + ] + ])); + } + public static function profile() + { + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + if (!$_SESSION['user_id']) { + http_response_code(403); + } + users::updateProfileById($_SESSION['user_id'], $_POST); + header('Location: /account'); + } + } + public static function login($defaults) + { + if (isset($_SESSION['user_id'])) { + header('Location: /account'); + } + echo $GLOBALS['twig']->render('lib/page/index.twig', array_merge($defaults, [ + 'child_template' => 'account/login.twig', + 'page_title' => 'Sign In or Create an Account!', + 'breadcrumbs' => [ + [ + 'url' => null, + 'title' => 'My Account' + ], + ] + ])); + } + public static function logout() + { + session_unset(); + session_destroy(); + header('Location: /'); + } + public static function orders($defaults) + { + if (!isset($_SESSION['user_id'])) { + header('Location: /account/login'); + } + echo $GLOBALS['twig']->render('lib/page/index.twig', array_merge($defaults, [ + 'child_template' => 'account/orders.twig', + 'page_title' => 'View ' . $_ENV['APP_NAME'] . ' Orders', + 'breadcrumbs' => [ + [ + 'url' => '/account', + 'title' => 'My Account' + ], + [ + 'url' => null, + 'title' => 'Orders' + ] + ] + ])); + } + + public static function returns($defaults) + { + echo $GLOBALS['twig']->render('lib/page/index.twig', array_merge($defaults, [ + 'child_template' => 'account/returns.twig', + 'page_title' => 'View ' . $_ENV['APP_NAME'] . ' Returns', + 'breadcrumbs' => [ + [ + 'url' => '/account', + 'title' => 'My Account' + ], + [ + 'url' => null, + 'title' => 'Returns' + ] + ] + ])); + } + public static function shipping($defaults) + { + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + if (!$_SESSION['user_id']) { + http_response_code(403); + } + $ship = addresses::validatePost("shipping"); + $ship_id = addresses::add( + $ship['name'], + $ship['company'], + $ship['street'], + $ship['boxapt'], + $ship['city'], + $ship['state'], + $ship['zip'], + $ship['phone'], + 0, + 1 + ); + user_addresses::add( + $_SESSION['user_id'], + $ship_id + ); + $_SESSION['success'] = "Shipping address saved!"; + header('Location: /account/shipping'); + } + $email = $_SESSION['user_email']; + $user = users::getByEmail($email); + $addresses = user_addresses::getShippingByUserId($user['id']); + $default_shipping = null; + $ship_addrs = []; + foreach ($addresses as $addr) { + if ($addr['id'] == $user['shipping_address_id']){ + $default_shipping = $addr; + } else { + $ship_addrs[] = $addr; + } + } + echo $GLOBALS['twig']->render('lib/page/index.twig', array_merge($defaults, [ + 'child_template' => 'account/shipping.twig', + 'page_title' => $_ENV['APP_NAME'] . ' Shipping', + 'shipping' => $ship_addrs, + 'default_shipping' => $default_shipping, + 'breadcrumbs' => [ + [ + 'url' => '/account', + 'title' => 'My Account' + ], + [ + 'url' => null, + 'title' => 'Shipping' + ] + ] + ])); + } + + public static function signup($defaults) + { + if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $email = $_POST['email']; + $existingUser = users::getByEmail($email); + if ($existingUser) { + $_SESSION['error'] = 'Email already exists. Please choose a different email or log in.'; + header('Location: /account/signup'); + exit; + } + if (empty($email)) { + $_SESSION['error'] = 'Email is required.'; + } + if (isset($_SESSION['error'])) { + header('Location: /account/signup'); + } + $useShipping = $_POST['use_shipping'] ?? false; + if ($useShipping) { + $ship = addresses::validatePost("shipping"); + } else { + $ship = addresses::validatePost("shipping"); + $bill = addresses::validatePost("billing"); + } + if (empty($email)) { + $_SESSION['error'] = 'Email is required.'; + } + if (isset($_SESSION['error'])) { + $_SESSION['last_post'] = $_POST; + header('Location: /account/signup'); + } + $ship_id = addresses::add( + $ship['name'], + $ship['company'], + $ship['street'], + $ship['boxapt'], + $ship['city'], + $ship['state'], + $ship['zip'], + $ship['phone'], + $useShipping == 'on', + 1 + ); + $bill_id = $ship_id; + if (!$useShipping) { + $bill_id = addresses::add( + $bill['name'], + $bill['company'], + $bill['street'], + $bill['boxapt'], + $bill['city'], + $bill['state'], + $bill['zip'], + $bill['phone'], + 1, + 0 + ); + } + $opt_in_promotional = $_POST['opt_in_promotional'] ?? false; + $verified = isset($_SESSION['user_email']); + $dark_theme = $defaults['theme'] == 'dark'; + $user_id = users::add( + $email, + $ship_id, + $bill_id, + $opt_in_promotional, + $verified, + $dark_theme + ); + user_addresses::add( + user_id: $user_id, + address_id: $ship_id + ); + if (!$useShipping) { + user_addresses::add( + user_id: $user_id, + address_id: $bill_id + ); + } + $_SESSION['user_id'] = $user_id; + if (!$verified) { + header("Location: /magic-link?email=$email&signup=1"); + exit; + } + header('Location: /account'); + exit; + } // endif request === POST + + if (isset($_SESSION['user_id'])) { + header('Location: /account'); + exit; + } + + echo $GLOBALS['twig']->render('lib/page/index.twig', array_merge($defaults, [ + 'child_template' => 'account/signup.twig', + 'page_title' => 'Create an Account - ' . $_ENV['APP_NAME'] + ])); + } +} \ No newline at end of file diff --git a/src/controllers/cart.php b/src/controllers/cart.php new file mode 100644 index 0000000..ba43a70 --- /dev/null +++ b/src/controllers/cart.php @@ -0,0 +1,18 @@ +render('lib/page/index.twig', array_merge($defaults, [ + 'child_template' => 'cart.twig', + 'page_title' => $_ENV['APP_NAME'] . ' Cart', + 'breadcrumbs' => [ + [ + 'url' => null, + 'title' => 'Cart' + ] + ], + ])); + } +} \ No newline at end of file diff --git a/src/controllers/category.php b/src/controllers/category.php new file mode 100644 index 0000000..31094dd --- /dev/null +++ b/src/controllers/category.php @@ -0,0 +1,14 @@ +render('lib/page/index.twig', context: array_merge($defaults, [ + 'child_template' => 'lib/page/category.twig', + 'page_title' => 'Power Meters - ' . $_ENV['APP_NAME'], + 'product_category' => 'power_meters', + ])); + } +} + diff --git a/src/controllers/checkout.php b/src/controllers/checkout.php new file mode 100644 index 0000000..7f64b94 --- /dev/null +++ b/src/controllers/checkout.php @@ -0,0 +1,26 @@ +render('lib/page/flow.twig', array_merge($defaults, [ + 'child_template' => 'checkout/shipping_billing.twig', + 'page_title' => 'Checkout with ' . $_ENV['APP_NAME'], + ])); + } + public static function review_pay($defaults) + { + echo $GLOBALS['twig']->render('lib/page/flow.twig', array_merge($defaults, [ + 'child_template' => 'checkout/review_pay.twig', + 'page_title' => 'Review & Payment | ' . $_ENV['APP_NAME'] + ])); + } + public static function confirmed($defaults) + { + echo $GLOBALS['twig']->render('lib/page/flow.twig', array_merge($defaults, [ + 'child_template' => 'checkout/confirmed.twig', + 'page_title' => 'Order Recieved! - Thank You' + ])); + } +} \ No newline at end of file diff --git a/src/controllers/home.php b/src/controllers/home.php new file mode 100644 index 0000000..b36f828 --- /dev/null +++ b/src/controllers/home.php @@ -0,0 +1,12 @@ +render(name: 'lib/page/index.twig', context: array_merge($defaults, [ + 'child_template' => 'home.twig', + 'page_title' => $_ENV['APP_NAME'] . ": Specialty Hardware" + ])); + } +} \ No newline at end of file diff --git a/src/controllers/lnurlp.php b/src/controllers/lnurlp.php new file mode 100644 index 0000000..2ffcf20 --- /dev/null +++ b/src/controllers/lnurlp.php @@ -0,0 +1,90 @@ + 'ERROR', + 'reason' => 'invalid value for `pay` param (set `pay=1` or exclude `pay` from the url)', + ]); + } + // for when the user is missing + if ($user == false && $verify == false) { + returnJson([ + 'status' => 'ERROR', + 'reason' => 'no user specified (set `username=` in the url)', + ]); + } + + + list($proxy_user, $proxy_host) = explode("@", $_ENV['LN_ADDRESS']); + + + // for when the client makes it's first call (querying the lightning address) + $metadata = "[[\"text/plain\",\"Funding @$user on $host\"],[\"text/identifier\",\"$user@$host\"]]"; + if ($paymentRequest == false && $verify == false) { + $res = json_decode(file_get_contents("https://$proxy_host/.well-known/lnurlp/$proxy_user"), true); + returnJson( + [ + 'callback' => "https://$host/lnurlp?pay=1&username=$user", + 'maxSendable' => $res['maxSendable'], + 'minSendable' => $res['minSendable'], + 'metadata' => $metadata, + 'commentAllowed' => $res['commentAllowed'], + 'payerData' => $res['payerData'], + 'tag' => "payRequest", + ] + ); + } + + // for when the client makes it's second call (callback) + if ($paymentRequest == "1") { + $proxy_url = "https://$proxy_host/lnurlp?pay=1&username=$proxy_user"; + if (isset($_GET["amount"])) { + $proxy_url .= "&amount=" . urlencode($_GET["amount"]); + } + $res = json_decode(file_get_contents($proxy_url), true); + if ($res['status'] === 'OK'){ + $boom = explode("=", $res['verify']); + $proxy_verify = end($boom); + returnJson([ + 'status' => 'OK', + 'pr' => $res['pr'], + 'routes' => $res['routes'], + 'verify' => "https://$host/lnurlp?verify=$proxy_verify" + ]); + } else { + returnJson($res); + } + } + + // for when they want to verify the payment succeeded + if ($verify) { + $res = json_decode(file_get_contents("https://$proxy_host/lnurlp?verify=$verify"), true); + returnJson($res); + } + + // for when none of the above conditions are met + returnJson([ + 'status' => 'ERROR', + 'reason' => 'unhandled error (how did you get here?)', + ]); + } +} \ No newline at end of file diff --git a/src/controllers/lost.php b/src/controllers/lost.php new file mode 100644 index 0000000..39ecf0b --- /dev/null +++ b/src/controllers/lost.php @@ -0,0 +1,12 @@ +render('lib/page/index.twig', array_merge($defaults, [ + 'child_template' => '404.twig', + 'page_title' => 'Not Found - ' . $_ENV['APP_NAME'], + ])); + } +} \ No newline at end of file diff --git a/src/controllers/magic_link.php b/src/controllers/magic_link.php new file mode 100644 index 0000000..11af968 --- /dev/null +++ b/src/controllers/magic_link.php @@ -0,0 +1,67 @@ +$link"; + app::send_mail(to: $email, from: $_ENV['SMTP_FROM'], from_name: $_ENV['APP_NAME'], subject: $subject, message: $message, HTML_message: $HTML_message); + $_SESSION['success'] = 'Link sent to your email!'; + header('Location: /account/login'); + exit; + } + + if ($email && empty($token) && $signup == "1") { + $link = magic_links::add(email: $email); + $subject = "Your Magic Sign-In Link"; + $message = "Copy and paste the link into your browser: $link"; + $HTML_message = "Click the link to sign in: $link"; + app::send_mail(to: $email, from: $_ENV['SMTP_FROM'], from_name: $_ENV['APP_NAME'], subject: $subject, message: $message, HTML_message: $HTML_message); + $_SESSION['success'] = 'Account created! Please check your email inbox for the verification link.'; + header('Location: /account/login'); + exit; + } + + if ($token && empty($email)) { + $link = magic_links::validate(token: $token); + + if (!$link) { + $_SESSION['error'] = "Invalid or expired link."; + header('Location: /account/login'); + } + // handle signup vs. login + $user = users::getByEmail($link['email']); + if ($user) { + $_SESSION['user_email'] = $link['email']; + $_SESSION['user_id'] = $user['id']; + if (!$user['verified']) { + users::verify($link['email']); + } + header('Location: /account'); + } else { + // used to pre-fill email signup field + $_SESSION['user_email'] = $link['email']; + header('Location: /account/signup'); + } + exit(); + } + } +} diff --git a/src/controllers/support.php b/src/controllers/support.php new file mode 100644 index 0000000..37339cb --- /dev/null +++ b/src/controllers/support.php @@ -0,0 +1,35 @@ +render('lib/page/index.twig', array_merge($defaults, [ + 'child_template' => 'support/ask.twig', + 'page_title' => $_ENV['APP_NAME'] . ': Frequently Asked Questions', + 'breadcrumbs' => [ + [ + 'url' => null, + 'title' => 'Support' + ] + ] + ])); + } + public static function bitcoin($defaults) + { + $GLOBALS['twig']->render('lib/page/index.twig', array_merge($defaults, [ + 'child_template' => 'support/bitcoin.twig', + 'page_title' => $_ENV['APP_NAME'] . ' Bitcoin Accepted', + 'breadcrumbs' => [ + [ + 'url' => '/support/ask', + 'title' => 'Support' + ], + [ + 'url' => null, + 'title' => 'Bitcoin' + ] + ], + ])); + } +} \ No newline at end of file diff --git a/src/models/addresses.php b/src/models/addresses.php new file mode 100644 index 0000000..e29adaa --- /dev/null +++ b/src/models/addresses.php @@ -0,0 +1,91 @@ +exec("CREATE TABLE IF NOT EXISTS addresses ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + company TEXT, + street TEXT NOT NULL, + boxapt TEXT NOT NULL, + city TEXT NOT NULL, + state TEXT NOT NULL, + zip TEXT NOT NULL, + phone TEXT, + billing BOOLEAN NOT NULL, + shipping BOOLEAN NOT NULL + )"); + } + + public static function validatePost($type) + { + $name = $_POST["{$type}_name"]; + $company = $_POST["{$type}_company"] ?? null; + $boxapt = $_POST["{$type}_boxapt"] ?? null; + $street = $_POST["{$type}_street"]; + $city = $_POST["{$type}_city"]; + $state = $_POST["{$type}_state"]; + $zip = $_POST["{$type}_zip"]; + $phone = $_POST["{$type}_phone"]; + // check all required fields are set + if (empty($name) || empty($street) || empty($city) || empty($state) || empty($zip) || empty($phone)) { + $_SESSION['error'] = "Missing required {$type} information."; + } + // TODO: find a match using postal database and return that + return [ + 'name' => $name, + 'company' => $company, + 'street' => $street, + 'boxapt' => $boxapt, + 'city' => $city, + 'state' => $state, + 'zip' => $zip, + 'phone' => $phone + ]; + } + + public static function add($name, $company, $street, $boxapt, $city, $state, $zip, $phone, $billing, $shipping) + { + $query = "INSERT INTO addresses ( + name, + company, + street, + boxapt, + city, + state, + zip, + phone, + billing, + shipping + ) VALUES ( + :name, + :company, + :street, + :boxapt, + :city, + :state, + :zip, + :phone, + :billing, + :shipping + )"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':name', $name); + $stmt->bindParam(':company', $company); + $stmt->bindParam(':street', $street); + $stmt->bindParam(':boxapt', $boxapt); + $stmt->bindParam(':city', $city); + $stmt->bindParam(':state', $state); + $stmt->bindParam(':zip', $zip); + $stmt->bindParam(':phone', $phone); + $stmt->bindParam(':billing', $billing); + $stmt->bindParam(':shipping', $shipping); + $stmt->execute(); + return app::$db->lastInsertId(); + } + +} diff --git a/src/models/carts.php b/src/models/carts.php new file mode 100644 index 0000000..d9cdc70 --- /dev/null +++ b/src/models/carts.php @@ -0,0 +1,21 @@ +exec("CREATE TABLE IF NOT EXISTS order_items ( + order_item_id INTEGER PRIMARY KEY AUTOINCREMENT, + order_id INTEGER NOT NULL, + product_id INTEGER NOT NULL, + quantity INTEGER NOT NULL CHECK(quantity > 0), + price REAL NOT NULL CHECK(price >= 0), + FOREIGN KEY (order_id) REFERENCES orders(order_id), + FOREIGN KEY (product_id) REFERENCES products(product_id) + );"); + } +} + + diff --git a/src/models/magic_links.php b/src/models/magic_links.php new file mode 100644 index 0000000..b70d4cc --- /dev/null +++ b/src/models/magic_links.php @@ -0,0 +1,45 @@ +exec("CREATE TABLE IF NOT EXISTS magic_links ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT NOT NULL, + token TEXT NOT NULL, + expires_at DATETIME NOT NULL, + used BOOLEAN DEFAULT FALSE + )"); + } + + public static function add($email) + { + $token = bin2hex(random_bytes(32)); + $expires_at = date('Y-m-d H:i:s', time() + 60 * 15); + app::$db->query("INSERT INTO magic_links ( + email, + token, + expires_at + ) VALUES ( + '$email', + '$token', + '$expires_at' + )"); + return $_ENV['APP_HOST'] . "/magic-link?token=" . urlencode($token); + } + + public static function validate($token) + { + $link = app::$db->query("SELECT * FROM magic_links + WHERE token = '$token' + AND used = FALSE + AND expires_at > datetime('now') + ")->fetch(\PDO::FETCH_ASSOC); + // void the token once validated + app::$db->query("UPDATE magic_links SET used = TRUE WHERE token = '$token'"); + return $link; + } +} diff --git a/src/models/orders.php b/src/models/orders.php new file mode 100644 index 0000000..14b2296 --- /dev/null +++ b/src/models/orders.php @@ -0,0 +1,18 @@ +exec("CREATE TABLE IF NOT EXISTS orders ( + order_id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + total_amount REAL NOT NULL CHECK(total_amount >= 0), + status TEXT NOT NULL CHECK(status IN ('pending', 'completed', 'cancelled')), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(user_id) + );"); + } +} diff --git a/src/models/products.php b/src/models/products.php new file mode 100644 index 0000000..45bf7ab --- /dev/null +++ b/src/models/products.php @@ -0,0 +1,17 @@ +exec("CREATE TABLE IF NOT EXISTS products ( + product_id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + description TEXT, + price REAL NOT NULL CHECK(price >= 0), + qty INTEGER NOT NULL DEFAULT 0 CHECK(qty >= 0) + )"); + } +} diff --git a/src/models/transactions.php b/src/models/transactions.php new file mode 100644 index 0000000..4bc3450 --- /dev/null +++ b/src/models/transactions.php @@ -0,0 +1,54 @@ +exec("CREATE TABLE transactions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + type TEXT CHECK(transaction_type IN ('credit', 'spend', 'withdraw')) NOT NULL, + cents REAL DEFAULT 0, + sats REAL DEFAULT 0, + date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) + )"); + } + + public static function add($user_id, $transaction_type, $cents, $sats_amount) + { + $query = "INSERT INTO transactions ( + user_id, + type, + cents, + sats + ) VALUES ( + :user_id, + :transaction_type, + :cents, + :sats + )"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':user_id', $user_id); + $stmt->bindParam(':transaction_type', $transaction_type); + $stmt->bindParam(':cents', $cents); + $stmt->bindParam(':sats', $sats_amount); + $stmt->execute(); + return app::$db->lastInsertId(); + } + + public static function getUserBalance($user_id) + { + $query = "SELECT SUM(cents) AS total_cents, + SUM(sats) AS total_sats + FROM transactions + WHERE user_id = :user_id"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':user_id', $user_id); + $stmt->execute(); + $result = $stmt->fetch(); + return $result; + } +} diff --git a/src/models/user_addresses.php b/src/models/user_addresses.php new file mode 100644 index 0000000..418f678 --- /dev/null +++ b/src/models/user_addresses.php @@ -0,0 +1,53 @@ +exec("CREATE TABLE IF NOT EXISTS user_addresses ( + user_id INTEGER NOT NULL, + address_id INTEGER NOT NULL, + PRIMARY KEY (user_id, address_id), + FOREIGN KEY (address_id) REFERENCES addresses(id) + )"); + } + + public static function getShippingByUserId($id) + { + $addrs = app::$db->query("SELECT a.* FROM users u + JOIN user_addresses ua ON u.id = ua.user_id + JOIN addresses a ON ua.address_id = a.id + WHERE u.id = '$id' AND a.shipping = 1")->fetch(\PDO::FETCH_ASSOC); + return [$addrs]; + } + + public static function getBillingByUserId($id) + { + $addrs = app::$db->query("SELECT a.* FROM users u + JOIN user_addresses ua ON u.id = ua.user_id + JOIN addresses a ON ua.address_id = a.id + WHERE u.id = '$id' AND a.billing = 1")->fetch(\PDO::FETCH_ASSOC); + return [$addrs]; + } + + public static function add($user_id, $address_id) + { + $query = "INSERT INTO user_addresses ( + user_id, + address_id + ) VALUES ( + :user_id, + :address_id + )"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':user_id', $user_id); + $stmt->bindParam(':address_id', $address_id); + $stmt->execute(); + return app::$db->lastInsertId(); + } + +} diff --git a/src/models/users.php b/src/models/users.php new file mode 100644 index 0000000..4504003 --- /dev/null +++ b/src/models/users.php @@ -0,0 +1,83 @@ +exec("CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT UNIQUE, + shipping_address_id INTEGER, + billing_address_id INTEGER, + opt_in_promotional BOOLEAN NOT NULL, + verified BOOLEAN NOT NULL, + dark_theme BOOLEAN NOT NULL, + generated_base58 TEXT UNIQUE, + attached_lightning_address TEXT, + name TEXT, + company_name TEXT, + company_type TEXT, + company_size TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + )"); + app::$db->exec('CREATE INDEX IF NOT EXISTS idx_user_email ON users (email)'); + } + + public static function updateProfileById($user_id, $post) + { + $query = "UPDATE users SET + name = :name, + company_name = :company_name, + company_type = :company_type, + company_size = :company_size + WHERE id = :user_id"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':name', $post['name']); + $stmt->bindParam(':company_name', $post['company_name']); + $stmt->bindParam(':company_type', $post['company_type']); + $stmt->bindParam(':company_size', $post['company_size']); + $stmt->bindParam(':user_id', $user_id); + $stmt->execute(); + } + + public static function add($email, $ship_id, $bill_id, $opt_in_promotional, $verified, $dark_theme) + { + $query = "INSERT INTO users ( + email, + shipping_address_id, + billing_address_id, + opt_in_promotional, + verified, + dark_theme + ) VALUES ( + :email, + :shipping_address_id, + :billing_address_id, + :opt_in_promotional, + :verified, + :dark_theme + )"; + $stmt = app::$db->prepare($query); + $stmt->bindParam(':email', $email); + $stmt->bindParam(':shipping_address_id', $ship_id); + $stmt->bindParam(':billing_address_id', $bill_id); + $stmt->bindParam(':opt_in_promotional', $opt_in_promotional); + $stmt->bindParam(':verified', $verified); + $stmt->bindParam(':dark_theme', $dark_theme); + $stmt->execute(); + return app::$db->lastInsertId(); + + } + + public static function verify($email) + { + app::$db->exec("UPDATE users SET verified = 1 WHERE email = '$email'"); + } + + public static function getByEmail($email) + { + return app::$db->query("SELECT * FROM users WHERE email = '$email'")->fetch(\PDO::FETCH_ASSOC); + } +} diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..7ceb693 --- /dev/null +++ b/src/style.css @@ -0,0 +1,32 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; +/* #Mega Menu Styles + –––––––––––––––––––––––––––––––––––––––––––––––––– */ +.mega-menu { + opacity: 0; + visibility: hidden; + z-index: -900; + left: 0; + top: 38px; + position: absolute; + text-align: left; + width: 100%; + transition: all 0.15s linear 0s; +} +/* #hoverable Class Styles */ +.hoverable { + position: static; +} +.hoverable > a:after { + content: "\25BC"; + font-size: 10px; + padding-left: 6px; + position: relative; + top: -1px; +} +.hoverable:hover .mega-menu { + opacity: 1; + visibility: visible; + z-index: 900; +} diff --git a/src/views/404.twig b/src/views/404.twig new file mode 100644 index 0000000..8598f2d --- /dev/null +++ b/src/views/404.twig @@ -0,0 +1,7 @@ +
+ {% include 'lib/empty.twig' with { + type: '404', + title: 'Page not found.', + subtitle: 'Shop our latest products!' + } %} +
\ No newline at end of file diff --git a/src/views/account/billing.twig b/src/views/account/billing.twig new file mode 100644 index 0000000..a45938c --- /dev/null +++ b/src/views/account/billing.twig @@ -0,0 +1,40 @@ +
+ +
+
+

Payment Methods

+ {% include 'lib/rule.twig' %} +
+ {% include 'lib/empty.twig' with { + type: 'payment', + title: "Let's get you set up!", + subtitle: 'Add a payment method' + } %} +
+

Billing Address

+ {% include 'lib/rule.twig' %} +

Your billing information must match the information associatied with the credit card making the purchase.

+
+ {{ default_billing.name }} + {{ default_billing.company }} + {{ default_billing.street }} + {{ default_billing.boxapt }} + {{ default_billing.city }}, {{ default_billing.state }} {{ default_billing.zip }} + {{ default_billing.phone }} +
+
+
+ {% include 'lib/form/address.twig' with { + action: 'billing' + } %} + {% include 'lib/button.twig' with { + label: 'Add Address', + onclick: 'this.parentNode.submit()', + } %} +
+ {% include 'lib/rule.twig' with { + text: 'OR' + } %} + Use saved address +
+
\ No newline at end of file diff --git a/src/views/account/index.twig b/src/views/account/index.twig new file mode 100644 index 0000000..0758cbb --- /dev/null +++ b/src/views/account/index.twig @@ -0,0 +1,114 @@ +
+
+ {% include 'lib/alert.twig' %} +

Profile

+ {% include 'lib/rule.twig' %} +
+ {% include 'lib/form/profile.twig' with { + name: user.name, + company_name: user.company_name, + company_type: user.company_type, + company_size: user.company_size + } %} + {% include 'lib/button.twig' with { + label: 'Save Profile', + onclick: 'this.parentNode.submit()', + } %} +
+
+
+

Email

+
+ {% include 'lib/input.twig' with { + type: 'text', + name: 'email', + value: user.email + } %} +

Verified: {{ user.verified ? 'Yes' : 'No' }}

+ {% include 'lib/button.twig' with { + label: 'Save Email', + onclick: 'this.parentNode.submit()', + } %} +
+
+
+
+

Shipping

+ Edit +
+ {% include 'lib/rule.twig' %} +
+

{{ default_shipping.name }}

+

{{ default_shipping.company }}

+

{{ default_shipping.street }}

+

{{ default_billing.boxapt }}

+

{{ default_shipping.city }}, {{ default_shipping.state }}, {{ default_shipping.zip }}

+

{{ default_shipping.phone }}

+
+
+
+
+

Billing

+ Edit +
+ {% include 'lib/rule.twig' %} +
+

{{ default_billing.name }}

+

{{ default_billing.company }}

+

{{ default_billing.street }}

+

{{ default_billing.boxapt }}

+

{{ default_billing.city }}, {{ default_billing.state }}, {{ default_billing.zip }}

+

{{ default_billing.phone }}

+
+
+
+
+

Credit Card

+ Edit +
+ {% include 'lib/rule.twig' %} +
+
+
+
+

Store Credit

+ +
+ $0.00 +
+ {% include 'lib/rule.twig' %} +
+
+
+
+

Sats

+ +
+ 0 +
+ {% include 'lib/rule.twig' %} +
+
+

Marketing

+ {% include 'lib/rule.twig' %} +
+ {% include 'lib/toggle.twig' with { + label: 'Recieve coupons & more', + name: 'opt_in_promotional', + on: user.opt_in_promotional + } %} + {% include 'lib/button.twig' with { + label: 'Save', + onclick: 'this.parentNode.submit()', + } %} +
+
+
+{% include 'lib/modal.twig' with { + id: 'store-credit', + content: 'lib/policy/credit.twig' +} %} +{% include 'lib/modal.twig' with { + id: 'sats', + content: 'lib/policy/sats.twig' +} %} \ No newline at end of file diff --git a/src/views/account/login.twig b/src/views/account/login.twig new file mode 100644 index 0000000..1bf280a --- /dev/null +++ b/src/views/account/login.twig @@ -0,0 +1,23 @@ +
+
+
+

Login

+ {% include 'lib/rule.twig' %} +
+ {% include 'lib/alert.twig' %} +
+ {% include 'lib/input.twig' with { + type: 'email', + name: 'email', + label: 'Email link', + subtext: 'Get a one-time link sent to your email. No passwords!', + placeholder: 'Enter your e-mail' + } %} + {% include 'lib/button.twig' with { + label: 'Get login link', + onclick: 'this.parentNode.submit()', + captcha: true + } %} +
+
+
\ No newline at end of file diff --git a/src/views/account/orders.twig b/src/views/account/orders.twig new file mode 100644 index 0000000..63001c0 --- /dev/null +++ b/src/views/account/orders.twig @@ -0,0 +1,11 @@ +
+
+

Order History

+ {% include 'lib/rule.twig' %} +
+ {% include 'lib/empty.twig' with { + type: 'order', + title: 'No Orders Yet', + subtitle: 'Your orders will show here.' + } %} +
\ No newline at end of file diff --git a/src/views/account/returns.twig b/src/views/account/returns.twig new file mode 100644 index 0000000..1eacd43 --- /dev/null +++ b/src/views/account/returns.twig @@ -0,0 +1,11 @@ +
+
+

Returns

+ {% include 'lib/rule.twig' %} +
+ {% include 'lib/empty.twig' with { + type: 'file', + title: "No returns started", + subtitle: 'Click here to start a return' + } %} +
\ No newline at end of file diff --git a/src/views/account/shipping.twig b/src/views/account/shipping.twig new file mode 100644 index 0000000..1b1b49f --- /dev/null +++ b/src/views/account/shipping.twig @@ -0,0 +1,31 @@ +
+
+
+

Saved Shipping Address

+ {% include 'lib/rule.twig' %} +

This is your default shipping address for orders at checkout.

+
+
+ {{ default_shipping.name }} + {{ default_shipping.company }} + {{ default_shipping.street }} + {{ default_shipping.boxapt }} + {{ default_shipping.city }}, {{ default_shipping.state }} {{ default_shipping.zip }} + {{ default_shipping.phone }} +
+
+
+ {% include 'lib/form/address.twig' with { + action: 'shipping' + } %} + {% include 'lib/button.twig' with { + label: 'Add Address', + onclick: 'this.parentNode.submit()' + } %} +
+ {% include 'lib/rule.twig' with { + text: 'OR' + } %} + Use a saved address + +
\ No newline at end of file diff --git a/src/views/account/signup.twig b/src/views/account/signup.twig new file mode 100644 index 0000000..ff45978 --- /dev/null +++ b/src/views/account/signup.twig @@ -0,0 +1,91 @@ +
+
+
+

Create an Account

+ You can manage all your orders, addresses, and payment cards in one place! +
+ {% include 'lib/alert.twig' %} +
+ {% include 'lib/rule.twig' with { text: 'STEP 1' } %} +
+

Email Address

+ Recieve login link to verify. Order and account updates will be sent here. +
+
+
+ {% include 'lib/input.twig' with { + type: 'text', + name: 'email', + value: session.user_email is defined ? session.user_email : null, + readonly: session.user_email is defined ? true : null + } %} +
+ {% include 'lib/toggle.twig' with { + label: 'Recieve coupons & more', + name: 'opt_in_promotional', + on: true + } %} +
+ {% include 'lib/rule.twig' with { text: 'STEP 2' } %} +
+

Shipping Address

+ Your orders will ship to this address (USA only). +
+ {% include 'lib/form/address.twig' with { + action: 'shipping', + name: session.last_post.shipping_name, + street: session.last_post.shipping_street, + company: session.last_post.shipping_company, + boxapt: session.last_post.shipping_boxapt, + city: session.last_post.shipping_city, + state: session.last_post.shipping_state, + zip: session.last_post.shipping_zip, + phone: session.last_post.shipping_phone, + } %} + {% include 'lib/rule.twig' with { text: 'STEP 3' } %} +
+
+

Billing Address

+ Info must match the credit card making the purchase. +
+ + {% include 'lib/toggle.twig' with { + label: 'Same as shipping', + name: 'use_shipping' + } %} +
+ + {% include 'lib/rule.twig' with { text: 'ALL DONE!' } %} + {% include 'lib/button.twig' with { + label: 'Register', + onclick: 'this.parentNode.submit()', + captcha: true + } %} +
+
+ +
\ No newline at end of file diff --git a/src/views/cart.twig b/src/views/cart.twig new file mode 100644 index 0000000..e8ecff2 --- /dev/null +++ b/src/views/cart.twig @@ -0,0 +1,11 @@ +
+
+

Cart

+ {% include 'lib/rule.twig' %} +
+ {% include 'lib/empty.twig' with { + type: 'cart', + title: 'Your cart is empty.', + subtitle: 'Log in to see items you may have added previously, or continue shopping.' + } %} +
\ No newline at end of file diff --git a/src/views/checkout/confirmed.twig b/src/views/checkout/confirmed.twig new file mode 100644 index 0000000..5835b12 --- /dev/null +++ b/src/views/checkout/confirmed.twig @@ -0,0 +1,3 @@ +
+ checkout order confirmed boi +
\ No newline at end of file diff --git a/src/views/checkout/review_pay.twig b/src/views/checkout/review_pay.twig new file mode 100644 index 0000000..fd27bf5 --- /dev/null +++ b/src/views/checkout/review_pay.twig @@ -0,0 +1,3 @@ +
+ checkout review pay boi +
\ No newline at end of file diff --git a/src/views/checkout/shipping_billing.twig b/src/views/checkout/shipping_billing.twig new file mode 100644 index 0000000..80747d4 --- /dev/null +++ b/src/views/checkout/shipping_billing.twig @@ -0,0 +1,3 @@ +
+ checkout shipping/billing boi +
\ No newline at end of file diff --git a/src/views/footer.twig b/src/views/footer.twig new file mode 100644 index 0000000..60f5d0e --- /dev/null +++ b/src/views/footer.twig @@ -0,0 +1,60 @@ +
+
+
+
+
+ + + + Since 2021, we offer specialty hardware solutions, the best pricing, and fast shipping to keep your + business + functioning at its best. +
+
+ + + + Since 2021, we offer specialty hardware solutions, the best pricing, and fast shipping to keep your + business + functioning at its best. +
+
+ + + + Since 2021, we offer specialty hardware solutions, the best pricing, and fast shipping to keep your + business + functioning at its best. +
+
+ + + + Since 2021, we offer specialty hardware solutions, the best pricing, and fast shipping to keep your + business + functioning at its best. +
+
+ + + + Since 2021, we offer specialty hardware solutions, the best pricing, and fast shipping to keep your + business + functioning at its best. +
+
+
+
+
+
+
© {{ copyright_year }} BuysForLife - All Rights Reserved.
+ +
+
+
\ No newline at end of file diff --git a/src/views/head.twig b/src/views/head.twig new file mode 100644 index 0000000..c8703ed --- /dev/null +++ b/src/views/head.twig @@ -0,0 +1,27 @@ + + + + + + + {{ page_title }} + + + + \ No newline at end of file diff --git a/src/views/header.twig b/src/views/header.twig new file mode 100644 index 0000000..e3962ae --- /dev/null +++ b/src/views/header.twig @@ -0,0 +1,178 @@ +
+
+
+ Help Center + Save 5% when you pay with BitcoinLearn More +
+
+ +
+
+
+ + + +
+
+ {% include 'lib/input.twig' with { + name: 'search', + type: 'search', + placeholder: 'What are you looking for?', + submit: true, + icon: 'search' + } %} +
+
+
+ + + + + + + + +
+
Returns &
+
Orders
+
+
+
+ +
+
+ + + + + + +
+
+ 7
+
+
+
+
+ + + + {% if breadcrumbs is defined %} +
+
+ + {{ env.APP_NAME }} + + > + {% for crumb in breadcrumbs %} + {% if crumb.url is null %} + {{ crumb.title }} + {% else %} + + {{ crumb.title }} + + {% endif %} + {% if loop.index < breadcrumbs|length %} + > + {% endif %} + {% endfor %} +
+ {% include 'lib/rule.twig' %} +
+ {% endif %} +
+
\ No newline at end of file diff --git a/src/views/home.twig b/src/views/home.twig new file mode 100644 index 0000000..3ca4f8e --- /dev/null +++ b/src/views/home.twig @@ -0,0 +1,3 @@ +
+ home boi +
\ No newline at end of file diff --git a/src/views/lib/alert.twig b/src/views/lib/alert.twig new file mode 100644 index 0000000..9568237 --- /dev/null +++ b/src/views/lib/alert.twig @@ -0,0 +1,45 @@ +{% if session.error is defined %} +
+ + + + + +

{{ session.error }}

+
+{% endif %} +{% if session.warning is defined %} +
+ + + + + +

{{ session.warning }}

+
+{% endif %} +{% if session.success is defined %} +
+ + + + +

{{ session.success }}

+
+{% endif %} +{% if session.info is defined %} +
+ + + + + +

{{ session.info }}

+
+{% endif %} \ No newline at end of file diff --git a/src/views/lib/button.twig b/src/views/lib/button.twig new file mode 100644 index 0000000..813aaa1 --- /dev/null +++ b/src/views/lib/button.twig @@ -0,0 +1,37 @@ +
+ {% if label is defined %} + {{ label }} + {% endif %} + {% if icon is defined %} + {% if icon == 'search' %} + + + + + {% elseif icon == 'add' %} + + + + + {% elseif icon == 'enter' %} + + + + + {% endif %} + {% endif %} +
+{% if captcha is defined %} +
+

This form is protected by reCAPTCHA and the Google + Privacy Policy and Terms of Service apply. +

+
+{% endif %} \ No newline at end of file diff --git a/src/views/lib/empty.twig b/src/views/lib/empty.twig new file mode 100644 index 0000000..3965ea6 --- /dev/null +++ b/src/views/lib/empty.twig @@ -0,0 +1,5 @@ +
+ +

{{ title }}

+

{{ subtitle }}

+
\ No newline at end of file diff --git a/src/views/lib/form/address.twig b/src/views/lib/form/address.twig new file mode 100644 index 0000000..df7e196 --- /dev/null +++ b/src/views/lib/form/address.twig @@ -0,0 +1,54 @@ +
+ {% include 'lib/input.twig' with { + type: 'text', + name: action ~ '_name', + label: 'Name', + value: name + } %} + {% include 'lib/input.twig' with { + type: 'text', + name: action ~ '_company', + label: 'Company', + optional: true, + value: company + } %} + {% include 'lib/input.twig' with { + type: 'text', + name: action ~ '_street', + label: 'Street', + value: street + } %} + {% include 'lib/input.twig' with { + type: 'text', + name: action ~ '_boxapt', + label: 'PO Box/Apt#', + optional: true, + value: boxapt + } %} +
+ {% include 'lib/input.twig' with { + type: 'text', + name: action ~ '_city', + label: 'City', + value: city + } %} + {% include 'lib/input.twig' with { + type: 'text', + name: action ~ '_state', + label: 'State', + value: state + } %} + {% include 'lib/input.twig' with { + type: 'text', + name: action ~ '_zip', + label: 'Zip', + value: zip + } %} +
+ {% include 'lib/input.twig' with { + type: 'text', + name: action ~ '_phone', + label: 'Phone', + value: phone + } %} +
diff --git a/src/views/lib/form/profile.twig b/src/views/lib/form/profile.twig new file mode 100644 index 0000000..64ac6a1 --- /dev/null +++ b/src/views/lib/form/profile.twig @@ -0,0 +1,33 @@ +
+ {% include 'lib/input.twig' with { + type: 'text', + name: 'name', + label: 'Name', + value: user.name + } %} +
+ {% include 'lib/input.twig' with { + type: 'text', + name: 'company_name', + label: 'Company Name', + value: user.company_name + } %} + {% include 'lib/input.twig' with { + type: 'text', + name: 'company_type', + label: 'Company Type', + value: user.company_type + } %} + {% include 'lib/input.twig' with { + type: 'text', + name: 'company_size', + label: 'Company Size', + value: user.company_size + } %} +
+ {% include 'lib/toggle.twig' with { + name: 'dark_theme', + label: 'Use dark theme', + on: user.dark_theme + } %} +
diff --git a/src/views/lib/input.twig b/src/views/lib/input.twig new file mode 100644 index 0000000..d03bdfc --- /dev/null +++ b/src/views/lib/input.twig @@ -0,0 +1,40 @@ +
+ {% if label is defined %} + + {% endif %} + {% if submit is defined %} +
+ {% endif %} + + {% if submit is defined %} + {% include 'lib/button.twig' with { + icon: 'search' + } %} + {% endif %} + {% if submit is defined %} +
+ {% endif %} +
\ No newline at end of file diff --git a/src/views/lib/modal.twig b/src/views/lib/modal.twig new file mode 100644 index 0000000..b1986a5 --- /dev/null +++ b/src/views/lib/modal.twig @@ -0,0 +1,14 @@ + + \ No newline at end of file diff --git a/src/views/lib/page/category.twig b/src/views/lib/page/category.twig new file mode 100644 index 0000000..d13c56c --- /dev/null +++ b/src/views/lib/page/category.twig @@ -0,0 +1,3 @@ +
+ category: {{ product_category }} +
\ No newline at end of file diff --git a/src/views/lib/page/flow.twig b/src/views/lib/page/flow.twig new file mode 100644 index 0000000..81d9394 --- /dev/null +++ b/src/views/lib/page/flow.twig @@ -0,0 +1,18 @@ + + + + + + + + + + {{ page_title }} + + + + + {% include child_template %} + + + \ No newline at end of file diff --git a/src/views/lib/page/index.twig b/src/views/lib/page/index.twig new file mode 100644 index 0000000..fa4f49c --- /dev/null +++ b/src/views/lib/page/index.twig @@ -0,0 +1,15 @@ + + +{% include 'head.twig' %} +{% include 'header.twig' %} + + + +
+ {% include child_template %} +
+ +{% include 'footer.twig' %} + + \ No newline at end of file diff --git a/src/views/lib/policy/credit.twig b/src/views/lib/policy/credit.twig new file mode 100644 index 0000000..c29bb94 --- /dev/null +++ b/src/views/lib/policy/credit.twig @@ -0,0 +1,12 @@ +
+
Why do I have store credit?
+
    +
  • You may have received credit as a refund, dispute resolution, or promotional event.
  • +
  • You can also reload your store credit by ordering gift cards.
  • +
+
What can I do with store credit?
+
    +
  • You may spend store credit at checkout.
  • +
  • Your subscriptions and recurring purchases can also be paid with store credit.
  • +
+
\ No newline at end of file diff --git a/src/views/lib/policy/sats.twig b/src/views/lib/policy/sats.twig new file mode 100644 index 0000000..3669cdd --- /dev/null +++ b/src/views/lib/policy/sats.twig @@ -0,0 +1,13 @@ +
+
Why do I have sats?
+
    +
  • You may have received sats from a promotional event
  • +
  • You may have recieved sats sent to your default generated Lightning Address (LNURL)
  • +
+
What can I do with sats?
+
    +
  • You may spend sats at checkout
  • +
  • Your subscriptions and recurring purchases can also be paid with sats
  • +
  • You can configure these sats to autowithdraw by attaching a Lightning Address (LNURL)
  • +
+
\ No newline at end of file diff --git a/src/views/lib/rule.twig b/src/views/lib/rule.twig new file mode 100644 index 0000000..4973976 --- /dev/null +++ b/src/views/lib/rule.twig @@ -0,0 +1,10 @@ +{% if text is defined %} +
+
+
{{ text }} +
+
+{% else %} +
+{% endif %} \ No newline at end of file diff --git a/src/views/lib/toggle.twig b/src/views/lib/toggle.twig new file mode 100644 index 0000000..a1c6ff9 --- /dev/null +++ b/src/views/lib/toggle.twig @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/src/views/policies.twig b/src/views/policies.twig new file mode 100644 index 0000000..177d01d --- /dev/null +++ b/src/views/policies.twig @@ -0,0 +1,3 @@ +
+ policy boi +
\ No newline at end of file diff --git a/src/views/support/ask.twig b/src/views/support/ask.twig new file mode 100644 index 0000000..9d07413 --- /dev/null +++ b/src/views/support/ask.twig @@ -0,0 +1,3 @@ +
+ faq and chat boi +
\ No newline at end of file diff --git a/src/views/support/bitcoin.twig b/src/views/support/bitcoin.twig new file mode 100644 index 0000000..7d50b08 --- /dev/null +++ b/src/views/support/bitcoin.twig @@ -0,0 +1,3 @@ +
+ bitcoin promo and how to get sats +
\ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..4a68197 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,3 @@ +module.exports = { + content: ["./src/**/*.twig", "./public/index.php"], +}; diff --git a/tailwindcss b/tailwindcss new file mode 100755 index 0000000..8f2c1cd Binary files /dev/null and b/tailwindcss differ