Compare commits
171 Commits
master
..
9cdfbe7a10
| Author | SHA1 | Date | |
|---|---|---|---|
| 9cdfbe7a10 | |||
| be9164850a | |||
| f7d2de355e | |||
| b5ef4ac0cb | |||
| a1ab16d54c | |||
| feb5380b28 | |||
| 268a6a3f65 | |||
| 812db2bf9b | |||
| 48d7fefd02 | |||
| 27a5dbd466 | |||
| 2c3f2bb030 | |||
| e4d3af69b5 | |||
| 938d934b51 | |||
| cee6f4d3d5 | |||
| 1465ce02ad | |||
| ba57334142 | |||
| 615d983828 | |||
| b245694824 | |||
| 378968576f | |||
| 0d4368575b | |||
| 05e80a075a | |||
| 92a1f17dbe | |||
| d1505e1e08 | |||
| f05c073ad4 | |||
| 79d9ac71a3 | |||
| ffc5b64967 | |||
| 869dd2c799 | |||
| 531820e2a8 | |||
| 630bbf3aea | |||
| 8238a81494 | |||
| cc45f0659c | |||
| ad37224890 | |||
| 9b65bb8020 | |||
| 97481c2444 | |||
| 8151de4b9a | |||
| 477d7803f0 | |||
| 8cc5294a84 | |||
| e6aac416d9 | |||
| e84f853d80 | |||
| 7d5a4b889c | |||
| c157815ca8 | |||
| 2d9b6162e1 | |||
| 096c239715 | |||
| 5bdcb8e2bf | |||
| 98ae82a040 | |||
| 15b5ece4f9 | |||
| b03238b4c3 | |||
| 2287d98455 | |||
| 3e43b72eec | |||
| 5488ad0a84 | |||
| 9710ae0673 | |||
| 893b883dbf | |||
| 60620e15dd | |||
| ec786d2956 | |||
| a4d8455e5a | |||
| 5ac338945d | |||
| 6e213483a4 | |||
| 8a926ac0f3 | |||
| 0360176490 | |||
| 57930cf9db | |||
| 6682a207b2 | |||
| c326a30afc | |||
| 28545c2245 | |||
| 1085a13d38 | |||
| 13a807ac85 | |||
| 876b6736b6 | |||
| e36c26a37a | |||
| a2ce4b38b6 | |||
| 12e358b87c | |||
| 617d08c13d | |||
| 5612558ad1 | |||
| e827b55f6b | |||
| 3ea78d6a84 | |||
| e947153e74 | |||
| 4e3cfeeaa3 | |||
| f750d7aaca | |||
| c6cb83638a | |||
| 7e70ca94d8 | |||
| 7d7c326795 | |||
| 16327a0e0a | |||
| 53955af19c | |||
| 4aab7d637a | |||
| a6b2b29352 | |||
| dac89ba0b2 | |||
| f064d8f1ef | |||
| cccf3ada43 | |||
| b5aec38e45 | |||
| 654d395369 | |||
| f410322119 | |||
| cbc5246163 | |||
| d99e8132c9 | |||
| 99a361c0be | |||
| 64974c6c51 | |||
| 52e2347644 | |||
| 304b808bb6 | |||
| 9f376bd3c9 | |||
| fca443b6cc | |||
| 1f64d4cad7 | |||
| b7b090f6c5 | |||
| 52d890597d | |||
| 40ab7b256d | |||
| bbf115c2ae | |||
| b433a83578 | |||
| c005b52211 | |||
| e61c38473c | |||
| 1243b32de1 | |||
| a20810d650 | |||
| c4d5440453 | |||
| 975fde9c35 | |||
| af363723b2 | |||
| e2ff6e585b | |||
| 389123fe15 | |||
| a519d7bfe6 | |||
| a98c89075d | |||
| 00d7dfdb0b | |||
| 08ac31210c | |||
| fef45c401a | |||
| 21b779c581 | |||
| 618030e9d3 | |||
| 59cee36c3a | |||
| 88028393cd | |||
| de377902be | |||
| ee711a5796 | |||
| e599aed16f | |||
| 0d77e8f3a4 | |||
| 6ed9018086 | |||
| 8cee68e091 | |||
| 4615be4280 | |||
| 6e7b7cfa77 | |||
| 74f92ca2fa | |||
| 2dc2e2fe16 | |||
| 37dc9baa97 | |||
| 0337838319 | |||
| c544e0c7b5 | |||
| 087e3e83b5 | |||
| 2ae9f32cb8 | |||
| 5924afa3dc | |||
| b302e0cce1 | |||
| 1f9fa672c6 | |||
| bd610dc68f | |||
| dd9d5ae2fb | |||
| 0e62c29f11 | |||
| 14610fe212 | |||
| 8bcb852ea9 | |||
| 0bfe40eafe | |||
| 1e6bc3af4e | |||
| fa9e6730cc | |||
| 6709776cc5 | |||
| df04139a05 | |||
| 4a377b635c | |||
| 8262550a10 | |||
| 31ab8efc05 | |||
| 3ee55c9b17 | |||
| 93cd20c49d | |||
| eff10b3ee6 | |||
| 6de33ea45c | |||
| 34bfee494e | |||
| 3f8209a6a6 | |||
| 2b93634d3a | |||
| c698fc34f1 | |||
| 93d362aba2 | |||
| 8fe055a5b1 | |||
| 8a64294455 | |||
| 854b0ca77d | |||
| 7c707f865c | |||
| 439d136f54 | |||
| d0f2d526f1 | |||
| 9c855a19bb | |||
| a7e532867a | |||
| 67e6d85da9 | |||
| 35c72f6199 |
@@ -4,7 +4,7 @@ TYPE=docker
|
|||||||
QRCODE_GENERATOR=internal-chillerlan.qrcode
|
QRCODE_GENERATOR=internal-chillerlan.qrcode
|
||||||
BASE_URL=http://localhost
|
BASE_URL=http://localhost
|
||||||
|
|
||||||
DATABASE_HOST=qrforge-db
|
DATABASE_HOST=php-dynamic-qrcode-db
|
||||||
DATABASE_PORT=3306
|
DATABASE_PORT=3306
|
||||||
DATABASE_NAME=qrcode
|
DATABASE_NAME=qrcode
|
||||||
DATABASE_USER=qrcode
|
DATABASE_USER=qrcode
|
||||||
@@ -13,19 +13,3 @@ DATABASE_PREFIX=
|
|||||||
DATABASE_CHARSET=utf8
|
DATABASE_CHARSET=utf8
|
||||||
|
|
||||||
MYSQL_ROOT_PASSWORD=change-me-to-a-strong-root-password
|
MYSQL_ROOT_PASSWORD=change-me-to-a-strong-root-password
|
||||||
|
|
||||||
# Self-registration: lets visitors create their own free 'admin' account
|
|
||||||
# (email + CAPTCHA -> mailed password -> forced reset on first login).
|
|
||||||
ALLOW_SELF_REGISTRATION=false
|
|
||||||
|
|
||||||
# Only needed when ALLOW_SELF_REGISTRATION=true.
|
|
||||||
MAIL_HOST=
|
|
||||||
MAIL_PORT=587
|
|
||||||
MAIL_ENCRYPTION=tls
|
|
||||||
# Set to false to relay unauthenticated through an internal mail server (no
|
|
||||||
# MAIL_USERNAME/MAIL_PASSWORD needed in that case).
|
|
||||||
MAIL_SMTP_AUTH=true
|
|
||||||
MAIL_USERNAME=
|
|
||||||
MAIL_PASSWORD=
|
|
||||||
MAIL_FROM_ADDRESS=noreply@example.com
|
|
||||||
MAIL_FROM_NAME=QRForge
|
|
||||||
|
|||||||
@@ -2,5 +2,4 @@
|
|||||||
.project
|
.project
|
||||||
.idea
|
.idea
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env
|
.env
|
||||||
/data/
|
|
||||||
@@ -75,7 +75,6 @@ RUN cd /opt \
|
|||||||
&& rm /opt/composer-setup.php /opt/composer-setup.sha384sum
|
&& rm /opt/composer-setup.php /opt/composer-setup.sha384sum
|
||||||
|
|
||||||
RUN docker-php-source extract
|
RUN docker-php-source extract
|
||||||
RUN docker-php-ext-configure gd --with-freetype --with-jpeg
|
|
||||||
RUN docker-php-ext-install pdo_mysql zip exif pcntl gd
|
RUN docker-php-ext-install pdo_mysql zip exif pcntl gd
|
||||||
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli
|
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli
|
||||||
RUN docker-php-ext-install gettext && docker-php-ext-enable gettext
|
RUN docker-php-ext-install gettext && docker-php-ext-enable gettext
|
||||||
@@ -95,22 +94,12 @@ RUN cp -R ./php-qrcode/src /var/www/html/
|
|||||||
|
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
RUN composer update
|
RUN composer update
|
||||||
RUN composer require phpmailer/phpmailer:^6.9
|
|
||||||
COPY ./src ./
|
COPY ./src ./
|
||||||
COPY ./db/migrations ./db/migrations
|
|
||||||
RUN chmod 755 *;
|
RUN chmod 755 *;
|
||||||
|
|
||||||
# Qr code storage lives outside the document root so files can only be reached through
|
# Qr code storage lives outside the document root so files can only be reached through
|
||||||
# the authenticated qrcode_image.php / qrcode_zip_download.php endpoints.
|
# the authenticated qrcode_image.php / qrcode_zip_download.php endpoints.
|
||||||
RUN mkdir -p /var/www/qrcode-storage/zip && chmod -R 777 /var/www/qrcode-storage
|
RUN mkdir -p /var/www/qrcode-storage/zip && chmod -R 777 /var/www/qrcode-storage
|
||||||
|
|
||||||
# Applies any not-yet-applied db/migrations/*.sql on every container start (see
|
|
||||||
# src/scripts/migrate.php) - docker-entrypoint-initdb.d only runs db/init.sql, and only
|
|
||||||
# on a brand new volume, so without this an existing install's schema silently falls
|
|
||||||
# behind the code on every `git pull` + restart.
|
|
||||||
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
||||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
|
||||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
CMD ["php", "-S", "0.0.0.0:80"]
|
CMD ["php", "-S", "0.0.0.0:80"]
|
||||||
|
|||||||
@@ -51,9 +51,7 @@ RUN cp -R ./php-qrcode/src /var/www/html/
|
|||||||
|
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
RUN composer update
|
RUN composer update
|
||||||
RUN composer require phpmailer/phpmailer:^6.9
|
|
||||||
COPY ./src ./
|
COPY ./src ./
|
||||||
COPY ./db/migrations ./db/migrations
|
|
||||||
RUN chown -R www-data:www-data /var/www/html \
|
RUN chown -R www-data:www-data /var/www/html \
|
||||||
&& find /var/www/html -type f -exec chmod 644 {} \; \
|
&& find /var/www/html -type f -exec chmod 644 {} \; \
|
||||||
&& find /var/www/html -type d -exec chmod 755 {} \;
|
&& find /var/www/html -type d -exec chmod 755 {} \;
|
||||||
@@ -64,13 +62,5 @@ RUN mkdir -p /var/www/qrcode-storage/zip \
|
|||||||
&& chown -R www-data:www-data /var/www/qrcode-storage \
|
&& chown -R www-data:www-data /var/www/qrcode-storage \
|
||||||
&& chmod -R 775 /var/www/qrcode-storage
|
&& chmod -R 775 /var/www/qrcode-storage
|
||||||
|
|
||||||
# Applies any not-yet-applied db/migrations/*.sql on every container start (see
|
|
||||||
# src/scripts/migrate.php) - docker-entrypoint-initdb.d only runs db/init.sql, and only
|
|
||||||
# on a brand new volume, so without this an existing install's schema silently falls
|
|
||||||
# behind the code on every `git pull` + restart.
|
|
||||||
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
||||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
|
||||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
CMD ["php-fpm"]
|
CMD ["php-fpm"]
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
FROM nginx:1.27-alpine
|
|
||||||
|
|
||||||
# The php-dynamic-qrcode container builds the full app (incl. composer/vendor) into its
|
|
||||||
# own image at /var/www/html. nginx runs as a separate container and has no access to
|
|
||||||
# that filesystem, so it needs its own copy of just the static assets it serves directly
|
|
||||||
# via try_files - everything else (*.php) is proxied to php-fpm regardless. Without this,
|
|
||||||
# every static asset request (CSS/JS/manifest) falls through nginx's try_files to
|
|
||||||
# index.php, which requires a login and silently redirects there instead of serving the
|
|
||||||
# file.
|
|
||||||
COPY src/dist /var/www/html/dist
|
|
||||||
COPY src/plugins /var/www/html/plugins
|
|
||||||
COPY src/manifest.json /var/www/html/manifest.json
|
|
||||||
COPY src/service-worker.js /var/www/html/service-worker.js
|
|
||||||
COPY src/favicon.ico /var/www/html/favicon.ico
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2020 Giandonato Inverso
|
Copyright (c) 2020 Giandonato Inverso
|
||||||
Copyright (c) 2026 Dillard Blom
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,75 +1,65 @@
|
|||||||
<p align="center"><img src="src/dist/img/brand/logo.svg" alt="QRForge" width="320"></p>
|
<p align="center"><img src="https://www.giandonatoinverso.it/qrcode/dist/img/DynamicQRCode_Original.png"></p>
|
||||||
|
|
||||||
<p align="center"><strong>Self-hosted, open-source QR code generator.</strong></p>
|
**PHP Dynamic Qr code** is a script that allows the generation and saving of dynamic and static QR codes. It has a clean, responsive, and user-friendly design. It is based on [AdminLte](https://adminlte.io/), the "Best open source admin dashboard & control panel theme. Built on top of Bootstrap" and [Core PHP Admin Panel](https://github.com/chetans9/core-php-admin-panel), a simple Admin Panel written in core PHP that contains an implementation of general features you might need in your website admin panel like: record management (CRUD), secure authentication, pagination, filters.
|
||||||
|
|
||||||
**QRForge** creates and manages static and dynamic QR codes from a clean,
|
[LIVE DEMO](https://qrcode.giandonatoinverso.dev/)
|
||||||
responsive control panel. It's a security-hardened, actively maintained fork
|
|
||||||
of the original [PHP Dynamic Qr code](https://github.com/giandonatoinverso/PHP-Dynamic-Qr-code)
|
|
||||||
project by Giandonato Inverso, built on [AdminLTE](https://adminlte.io/).
|
|
||||||
|
|
||||||
- **Try it free:** [qr.ensembia.com](https://qr.ensembia.com) - fully functional OSS test
|
username: admin
|
||||||
instance. [Register your own free account](https://qr.ensembia.com/register.php)
|
|
||||||
(email + a self-hosted CAPTCHA, no third-party service) - no shared demo login needed.
|
password: admin
|
||||||
- **Commercial VIP edition:** the ability to give sub-users the ability to create
|
|
||||||
QR codes as well, from their own (sub)account. If you have a bigger organisation,
|
[DOCUMENTATION](https://giandonatoinverso.it/qrcode/documentation)
|
||||||
having more users being able to create new QR codes delegates your workload. To
|
|
||||||
fund our open-source project, a small fee (€49/year subscription per organisation
|
|
||||||
("tenant")) is requested for this. [www.qrforge.eu](https://www.qrforge.eu).
|
|
||||||
- **Self-host it yourself:** this repository, MIT-licensed.
|
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
- Dynamic QR codes with a database-backed URL shortener
|
- **#1 Dynamic Qr code generator on GitHub with a database to store Qr codes**
|
||||||
- Create, edit, delete, enable/disable the redirect
|
- Create unlimited Qr codes
|
||||||
- Download any time, bulk download/delete
|
- Docker compose support
|
||||||
- Batch-generate from a CSV file
|
- Control panel with 2 access levels
|
||||||
- 16 static QR code types: Text, Email, Phone, SMS, WhatsApp, Skype, Location,
|
- Multi-account
|
||||||
vCard, Event/calendar, Bookmark, WiFi (incl. WPA3), PayPal, Bitcoin, 2FA,
|
- Dashboard with advanced statistics on Qr codes created and on scans
|
||||||
App Link (Android intent / universal links), Bluetooth
|
- Bulk download, bulk delete
|
||||||
- QR code styling: 6 export formats, foreground/background color, 4 precision
|
- Dynamic Qr code
|
||||||
levels, 10 sizes, optional label text below the code (custom font + size),
|
- Create, modify, and delete Qr codes
|
||||||
optional icon shown above the code, save/load your own style presets
|
- You can download your Qr codes when you want
|
||||||
- Address search (OpenStreetMap Nominatim) for Location QR codes
|
- URL shortener with redirect
|
||||||
- Built-in QR scanner (camera or image upload, decodes entirely client-side)
|
- Enable or disable the link redirect
|
||||||
- Installable as a PWA
|
- Static Qr code
|
||||||
- Role-based access: `super` (full access + user management), `admin`
|
- Text QR Code
|
||||||
(scoped to their own codes and sub-users), `user` (read-only, with
|
- Email QR Code
|
||||||
optional view toggles set by an admin; per-account create rights are
|
- Phone QR Code
|
||||||
a VIP-edition feature, not available in this OSS version)
|
- Sms QR Code
|
||||||
- Dashboard with QR/scan statistics and a 7-day activity chart
|
- Whatsapp QR Code
|
||||||
- CSRF protection, login rate limiting, session hardening, audit log
|
- Skype QR Code
|
||||||
- Docker Compose setup, both a dev image and a production Nginx + PHP-FPM image
|
- Location QR Code
|
||||||
|
- Vcard QR Code
|
||||||
|
- Event/calendar QR Code
|
||||||
|
- Bookmark QR Code
|
||||||
|
- Wifi QR Code
|
||||||
|
- Paypal QR Code
|
||||||
|
- Bitcoin QR Code
|
||||||
|
- Customization of Qr codes
|
||||||
|
- 6 formats for images
|
||||||
|
- Foreground color
|
||||||
|
- Background color
|
||||||
|
- 4 levels of precision
|
||||||
|
- 10 sizes
|
||||||
|
- Responsive bootstrap-based design
|
||||||
|
- Easy to understand and expand code
|
||||||
|
- Full OOP with classes and well-documented
|
||||||
|
|
||||||
# What is included
|
## What is included
|
||||||
|
|
||||||
- PHP 8.4 application source
|
- PHP files
|
||||||
- Database schema + migrations (applied automatically on every container start,
|
- .sql file with sample data
|
||||||
so `git pull` + restart is enough to bring an existing install up to date)
|
- JS files
|
||||||
- Docker Compose files (dev and production)
|
- CSS files
|
||||||
- CSS/JS assets
|
- Docker compose file
|
||||||
|
|
||||||
# Setup with Docker Compose
|
## Setup with docker compose
|
||||||
|
1. download docker-compose.yml file
|
||||||
1. Clone this repository.
|
2. Start docker stack
|
||||||
2. Copy `.env.example` to `.env` and set a real `DATABASE_PASSWORD` /
|
```bash
|
||||||
`MYSQL_ROOT_PASSWORD`.
|
docker compose build --no-cache && docker compose up -d
|
||||||
3. Start the stack:
|
```
|
||||||
```bash
|
3. Open your browser at http://localhost:80 and login with (username: superadmin, password: superadmin)
|
||||||
docker compose up -d --build
|
|
||||||
```
|
|
||||||
4. Open `http://localhost` and log in with `superadmin` / `superadmin`. You'll
|
|
||||||
be required to set a new password on first login.
|
|
||||||
|
|
||||||
For a production deployment behind a reverse proxy, use
|
|
||||||
`docker-compose.prod.yml` (Nginx + PHP-FPM) instead of the dev stack.
|
|
||||||
|
|
||||||
# Credits
|
|
||||||
|
|
||||||
- Originally forked from [PHP Dynamic Qr code](https://github.com/giandonatoinverso/PHP-Dynamic-Qr-code)
|
|
||||||
by Giandonato Inverso.
|
|
||||||
- QR code rendering powered by [chillerlan/php-qrcode](https://github.com/chillerlan/php-qrcode).
|
|
||||||
- Admin panel UI built on [AdminLTE](https://adminlte.io/).
|
|
||||||
|
|
||||||
# License
|
|
||||||
|
|
||||||
MIT - see [LICENSE](LICENSE).
|
|
||||||
|
|||||||
@@ -19,20 +19,14 @@ CREATE TABLE IF NOT EXISTS `users` (
|
|||||||
`can_view_static` tinyint(1) NOT NULL DEFAULT 0,
|
`can_view_static` tinyint(1) NOT NULL DEFAULT 0,
|
||||||
`can_view_dynamic` tinyint(1) NOT NULL DEFAULT 0,
|
`can_view_dynamic` tinyint(1) NOT NULL DEFAULT 0,
|
||||||
`owner_admin_id` int(25) DEFAULT NULL,
|
`owner_admin_id` int(25) DEFAULT NULL,
|
||||||
`email` varchar(255) DEFAULT NULL,
|
|
||||||
`must_set_email` tinyint(1) NOT NULL DEFAULT 0,
|
|
||||||
`self_registered_at` datetime DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `username` (`username`),
|
UNIQUE KEY `username` (`username`)
|
||||||
UNIQUE KEY `email` (`email`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=0 ;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=0 ;
|
||||||
|
|
||||||
-- Default super admin account. Credentials: superadmin / superadmin
|
-- Default super admin account. Credentials: superadmin / superadmin
|
||||||
-- must_change_password=1 forces a password change on first login (see Fase 1 hardening).
|
-- must_change_password=1 forces a password change on first login (see Fase 1 hardening).
|
||||||
-- must_set_email=1: no email yet, so login falls back to username until it's set (same
|
INSERT INTO `users` (`id`, `username`, `password`, `series_id`, `remember_token`, `expires`, `type`, `must_change_password`, `password_changed_at`) VALUES
|
||||||
-- one-time interstitial pre-migration accounts get - see set_email.php).
|
(1, 'superadmin', '$2y$10$xpZc5KC.aU2XHkcqhuZGFuAnqmtL4Unt8MysOyylceq.19XIyoZpG', NULL, NULL, NULL, 'super', 1, NULL);
|
||||||
INSERT INTO `users` (`id`, `username`, `password`, `series_id`, `remember_token`, `expires`, `type`, `must_change_password`, `password_changed_at`, `must_set_email`) VALUES
|
|
||||||
(1, 'superadmin', '$2y$10$xpZc5KC.aU2XHkcqhuZGFuAnqmtL4Unt8MysOyylceq.19XIyoZpG', NULL, NULL, NULL, 'super', 1, NULL, 1);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `dynamic_qrcodes` (
|
CREATE TABLE IF NOT EXISTS `dynamic_qrcodes` (
|
||||||
`id` int(10) NOT NULL AUTO_INCREMENT,
|
`id` int(10) NOT NULL AUTO_INCREMENT,
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
-- Self-registration: adds email as the login identifier (mirrors qr-vip's
|
|
||||||
-- 006_vip_and_create_rights.sql email migration) plus a marker for
|
|
||||||
-- self-registered accounts.
|
|
||||||
|
|
||||||
SET @db := DATABASE();
|
|
||||||
|
|
||||||
-- 1. email: becomes the login identifier going forward. Nullable so existing accounts (which
|
|
||||||
-- have no email) don't violate a NOT NULL constraint; a unique index still allows unlimited
|
|
||||||
-- NULLs in InnoDB, so pre-existing NULL-email rows never collide with each other.
|
|
||||||
SET @col_exists := (
|
|
||||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'users' AND COLUMN_NAME = 'email'
|
|
||||||
);
|
|
||||||
SET @sql := IF(@col_exists = 0,
|
|
||||||
'ALTER TABLE `users` ADD COLUMN `email` VARCHAR(255) DEFAULT NULL',
|
|
||||||
'SELECT 1');
|
|
||||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
|
||||||
|
|
||||||
SET @idx_exists := (
|
|
||||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
|
||||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'users' AND INDEX_NAME = 'email'
|
|
||||||
);
|
|
||||||
SET @sql := IF(@idx_exists = 0,
|
|
||||||
'ALTER TABLE `users` ADD UNIQUE KEY `email` (`email`)',
|
|
||||||
'SELECT 1');
|
|
||||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
|
||||||
|
|
||||||
-- 2. must_set_email: forces existing (pre-migration) accounts through a one-time "set your
|
|
||||||
-- email" interstitial on next login, mirroring must_change_password. New accounts created
|
|
||||||
-- after this migration always have an email from creation, so they never get this flag.
|
|
||||||
SET @col_exists := (
|
|
||||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'users' AND COLUMN_NAME = 'must_set_email'
|
|
||||||
);
|
|
||||||
SET @sql := IF(@col_exists = 0,
|
|
||||||
'ALTER TABLE `users` ADD COLUMN `must_set_email` TINYINT(1) NOT NULL DEFAULT 0',
|
|
||||||
'SELECT 1');
|
|
||||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
|
||||||
|
|
||||||
UPDATE `users` SET `must_set_email` = 1 WHERE `email` IS NULL;
|
|
||||||
|
|
||||||
-- 3. self_registered_at: NULL for accounts created by an admin/super, set for accounts created
|
|
||||||
-- through register.php. Purely informational/reporting for now.
|
|
||||||
SET @col_exists := (
|
|
||||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
|
||||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'users' AND COLUMN_NAME = 'self_registered_at'
|
|
||||||
);
|
|
||||||
SET @sql := IF(@col_exists = 0,
|
|
||||||
'ALTER TABLE `users` ADD COLUMN `self_registered_at` DATETIME DEFAULT NULL',
|
|
||||||
'SELECT 1');
|
|
||||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
nginx:
|
nginx:
|
||||||
build:
|
image: "nginx:1.27-alpine"
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile.nginx
|
|
||||||
restart: "unless-stopped"
|
restart: "unless-stopped"
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
@@ -11,11 +9,11 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
- qrforge-app
|
- php-dynamic-qrcode
|
||||||
networks:
|
networks:
|
||||||
- qrforge-network
|
- php-dynamic-qrcode-network
|
||||||
|
|
||||||
qrforge-app:
|
php-dynamic-qrcode:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.fpm
|
dockerfile: Dockerfile.fpm
|
||||||
@@ -24,35 +22,26 @@ services:
|
|||||||
TYPE: "docker"
|
TYPE: "docker"
|
||||||
QRCODE_GENERATOR: "${QRCODE_GENERATOR:-internal-chillerlan.qrcode}"
|
QRCODE_GENERATOR: "${QRCODE_GENERATOR:-internal-chillerlan.qrcode}"
|
||||||
BASE_URL: "${BASE_URL:?set BASE_URL in .env, e.g. https://qr.ensembia.com}"
|
BASE_URL: "${BASE_URL:?set BASE_URL in .env, e.g. https://qr.ensembia.com}"
|
||||||
DATABASE_HOST: "qrforge-db"
|
DATABASE_HOST: "php-dynamic-qrcode-db"
|
||||||
DATABASE_PORT: "3306"
|
DATABASE_PORT: "3306"
|
||||||
DATABASE_NAME: "${DATABASE_NAME:-qrcode}"
|
DATABASE_NAME: "${DATABASE_NAME:-qrcode}"
|
||||||
DATABASE_USER: "${DATABASE_USER:-qrcode}"
|
DATABASE_USER: "${DATABASE_USER:-qrcode}"
|
||||||
DATABASE_PASSWORD: "${DATABASE_PASSWORD:?set DATABASE_PASSWORD in .env}"
|
DATABASE_PASSWORD: "${DATABASE_PASSWORD:?set DATABASE_PASSWORD in .env}"
|
||||||
DATABASE_PREFIX: "${DATABASE_PREFIX:-}"
|
DATABASE_PREFIX: "${DATABASE_PREFIX:-}"
|
||||||
DATABASE_CHARSET: "${DATABASE_CHARSET:-utf8}"
|
DATABASE_CHARSET: "${DATABASE_CHARSET:-utf8}"
|
||||||
ALLOW_SELF_REGISTRATION: "${ALLOW_SELF_REGISTRATION:-false}"
|
|
||||||
MAIL_HOST: "${MAIL_HOST:-}"
|
|
||||||
MAIL_PORT: "${MAIL_PORT:-587}"
|
|
||||||
MAIL_ENCRYPTION: "${MAIL_ENCRYPTION:-tls}"
|
|
||||||
MAIL_SMTP_AUTH: "${MAIL_SMTP_AUTH:-true}"
|
|
||||||
MAIL_USERNAME: "${MAIL_USERNAME:-}"
|
|
||||||
MAIL_PASSWORD: "${MAIL_PASSWORD:-}"
|
|
||||||
MAIL_FROM_ADDRESS: "${MAIL_FROM_ADDRESS:-noreply@example.com}"
|
|
||||||
MAIL_FROM_NAME: "${MAIL_FROM_NAME:-QRForge}"
|
|
||||||
depends_on:
|
depends_on:
|
||||||
qrforge-db:
|
php-dynamic-qrcode-db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/qrcode-storage:/var/www/qrcode-storage
|
- php_dynamic_qrcode_saved_qrcode_data:/var/www/qrcode-storage
|
||||||
networks:
|
networks:
|
||||||
- qrforge-network
|
- php-dynamic-qrcode-network
|
||||||
|
|
||||||
qrforge-db:
|
php-dynamic-qrcode-db:
|
||||||
image: "mysql:8.0"
|
image: "mysql:8.0"
|
||||||
restart: "unless-stopped"
|
restart: "unless-stopped"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/mysql:/var/lib/mysql
|
- php_dynamic_qrcode_db_data:/var/lib/mysql
|
||||||
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
|
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?set MYSQL_ROOT_PASSWORD in .env}"
|
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?set MYSQL_ROOT_PASSWORD in .env}"
|
||||||
@@ -65,8 +54,12 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
networks:
|
networks:
|
||||||
- qrforge-network
|
- php-dynamic-qrcode-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
php_dynamic_qrcode_db_data:
|
||||||
|
php_dynamic_qrcode_saved_qrcode_data:
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
qrforge-network:
|
php-dynamic-qrcode-network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
services:
|
services:
|
||||||
qrforge-app:
|
php-dynamic-qrcode:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
@@ -8,48 +8,28 @@ services:
|
|||||||
TYPE: "${TYPE:-docker}"
|
TYPE: "${TYPE:-docker}"
|
||||||
QRCODE_GENERATOR: "${QRCODE_GENERATOR:-internal-chillerlan.qrcode}"
|
QRCODE_GENERATOR: "${QRCODE_GENERATOR:-internal-chillerlan.qrcode}"
|
||||||
BASE_URL: "${BASE_URL:-http://localhost}"
|
BASE_URL: "${BASE_URL:-http://localhost}"
|
||||||
DATABASE_HOST: "qrforge-db"
|
DATABASE_HOST: "php-dynamic-qrcode-db"
|
||||||
DATABASE_PORT: "3306"
|
DATABASE_PORT: "3306"
|
||||||
DATABASE_NAME: "${DATABASE_NAME:-qrcode}"
|
DATABASE_NAME: "${DATABASE_NAME:-qrcode}"
|
||||||
DATABASE_USER: "${DATABASE_USER:-qrcode}"
|
DATABASE_USER: "${DATABASE_USER:-qrcode}"
|
||||||
DATABASE_PASSWORD: "${DATABASE_PASSWORD:?zet DATABASE_PASSWORD in .env}"
|
DATABASE_PASSWORD: "${DATABASE_PASSWORD:?zet DATABASE_PASSWORD in .env}"
|
||||||
DATABASE_PREFIX: "${DATABASE_PREFIX:-}"
|
DATABASE_PREFIX: "${DATABASE_PREFIX:-}"
|
||||||
DATABASE_CHARSET: "${DATABASE_CHARSET:-utf8}"
|
DATABASE_CHARSET: "${DATABASE_CHARSET:-utf8}"
|
||||||
ALLOW_SELF_REGISTRATION: "${ALLOW_SELF_REGISTRATION:-false}"
|
|
||||||
# Defaults here are dev-convenience only (mailhog, no auth) - a real deployment's
|
|
||||||
# .env must override MAIL_HOST/MAIL_SMTP_AUTH/MAIL_USERNAME/MAIL_PASSWORD
|
|
||||||
# explicitly, same as DATABASE_PASSWORD above already requires.
|
|
||||||
MAIL_HOST: "${MAIL_HOST:-mailhog}"
|
|
||||||
MAIL_PORT: "${MAIL_PORT:-1025}"
|
|
||||||
MAIL_ENCRYPTION: "${MAIL_ENCRYPTION:-}"
|
|
||||||
MAIL_SMTP_AUTH: "${MAIL_SMTP_AUTH:-false}"
|
|
||||||
MAIL_USERNAME: "${MAIL_USERNAME:-}"
|
|
||||||
MAIL_PASSWORD: "${MAIL_PASSWORD:-}"
|
|
||||||
MAIL_FROM_ADDRESS: "${MAIL_FROM_ADDRESS:-noreply@example.com}"
|
|
||||||
MAIL_FROM_NAME: "${MAIL_FROM_NAME:-QRForge}"
|
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
depends_on:
|
depends_on:
|
||||||
qrforge-db:
|
php-dynamic-qrcode-db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/qrcode-storage:/var/www/qrcode-storage
|
- php_dynamic_qrcode_saved_qrcode_data:/var/www/html/saved_qrcode
|
||||||
networks:
|
networks:
|
||||||
- qrforge-network
|
- php-dynamic-qrcode-network
|
||||||
|
|
||||||
mailhog:
|
php-dynamic-qrcode-db:
|
||||||
image: "mailhog/mailhog:v1.0.1"
|
|
||||||
restart: "unless-stopped"
|
|
||||||
ports:
|
|
||||||
- "8025:8025" # web UI: http://localhost:8025
|
|
||||||
networks:
|
|
||||||
- qrforge-network
|
|
||||||
|
|
||||||
qrforge-db:
|
|
||||||
image: "mysql:8.0"
|
image: "mysql:8.0"
|
||||||
restart: "unless-stopped"
|
restart: "unless-stopped"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/mysql:/var/lib/mysql
|
- php_dynamic_qrcode_db_data:/var/lib/mysql
|
||||||
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
|
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?zet MYSQL_ROOT_PASSWORD in .env}"
|
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:?zet MYSQL_ROOT_PASSWORD in .env}"
|
||||||
@@ -62,8 +42,12 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
networks:
|
networks:
|
||||||
- qrforge-network
|
- php-dynamic-qrcode-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
php_dynamic_qrcode_db_data:
|
||||||
|
php_dynamic_qrcode_saved_qrcode_data:
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
qrforge-network:
|
php-dynamic-qrcode-network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -e
|
|
||||||
|
|
||||||
php /var/www/html/scripts/migrate.php
|
|
||||||
|
|
||||||
exec docker-php-entrypoint "$@"
|
|
||||||
@@ -11,11 +11,11 @@ server {
|
|||||||
add_header Referrer-Policy "same-origin" always;
|
add_header Referrer-Policy "same-origin" always;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri /index.php$is_args$args;
|
try_files $uri $uri/ /index.php$is_args$args;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ \.php$ {
|
location ~ \.php$ {
|
||||||
fastcgi_pass qrforge-app:9000;
|
fastcgi_pass php-dynamic-qrcode:9000;
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
include fastcgi_params;
|
include fastcgi_params;
|
||||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'includes/bootstrap.php';
|
|
||||||
require_once BASE_PATH . '/includes/auth_validate.php';
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<title>About - QRForge</title>
|
|
||||||
<head>
|
|
||||||
<?php include './includes/head.php'; ?>
|
|
||||||
</head>
|
|
||||||
<body class="hold-transition sidebar-mini layout-fixed layout-navbar-fixed layout-footer-fixed">
|
|
||||||
<div class="wrapper">
|
|
||||||
<!-- Navbar -->
|
|
||||||
<?php include './includes/navbar.php'; ?>
|
|
||||||
<!-- /.navbar -->
|
|
||||||
|
|
||||||
<!-- Main Sidebar Container -->
|
|
||||||
<?php include './includes/sidebar.php'; ?>
|
|
||||||
<!-- /.Main Sidebar Container -->
|
|
||||||
|
|
||||||
<!-- Content Wrapper. Contains page content -->
|
|
||||||
<div class="content-wrapper">
|
|
||||||
<!-- Content Header (Page header) -->
|
|
||||||
<div class="content-header">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<h1 class="m-0 text-dark">About</h1>
|
|
||||||
</div><!-- /.col -->
|
|
||||||
</div><!-- /.row -->
|
|
||||||
</div><!-- /.container-fluid -->
|
|
||||||
</div>
|
|
||||||
<!-- /.content-header -->
|
|
||||||
|
|
||||||
<!-- Main content -->
|
|
||||||
<section class="content">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="card card-primary">
|
|
||||||
<div class="card-body text-center">
|
|
||||||
<img src="dist/img/brand/logo-stacked.svg" alt="QRForge" style="max-width: 220px; margin: 20px 0;">
|
|
||||||
<p class="text-muted">Self-hosted static and dynamic QR code generator. Version 3.0.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3 class="card-title">Commercial version</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p>
|
|
||||||
This is the free, open-source (MIT) edition of QRForge. It runs unmodified
|
|
||||||
as a live, fully functional try-out at
|
|
||||||
<a href="https://qr.ensembia.com" target="_blank">qr.ensembia.com</a> -
|
|
||||||
<a href="https://qr.ensembia.com/register.php" target="_blank">register your
|
|
||||||
own free account</a> there (email + a self-hosted CAPTCHA, no third-party
|
|
||||||
service).
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The commercial VIP edition (paid create-rights and logo-embedded QR codes)
|
|
||||||
is a separate product built on the same OSS core - see
|
|
||||||
<a href="https://www.qrforge.eu" target="_blank">www.qrforge.eu</a> for
|
|
||||||
pricing and details.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3 class="card-title">Self-hosting</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p>
|
|
||||||
Prefer to run your own instance? QRForge is MIT-licensed and available on
|
|
||||||
GitHub:
|
|
||||||
<a href="https://github.com/dillardblom/QRForge-selfhosted" target="_blank">github.com/dillardblom/QRForge-selfhosted</a>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3 class="card-title">Credits</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p>
|
|
||||||
Originally forked from
|
|
||||||
<a href="https://github.com/giandonatoinverso/PHP-Dynamic-Qr-code" target="_blank">PHP Qrcode Generator by Giandonato Inverso</a>.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
QR code rendering is powered by the
|
|
||||||
<a href="https://github.com/chillerlan/php-qrcode" target="_blank">chillerlan/php-qrcode</a>
|
|
||||||
library.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div><!--/. container-fluid -->
|
|
||||||
</section><!-- /.content -->
|
|
||||||
</div><!-- /.content-wrapper -->
|
|
||||||
|
|
||||||
<!-- Footer and scripts -->
|
|
||||||
<?php include './includes/footer.php'; ?>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -6,20 +6,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST')
|
|||||||
{
|
{
|
||||||
csrf_verify_or_die();
|
csrf_verify_or_die();
|
||||||
|
|
||||||
// Login moves from username to email. Accept either during the transition -
|
$username = filter_input(INPUT_POST, 'username');
|
||||||
// existing pre-migration accounts have no email yet (see must_set_email/set_email.php),
|
|
||||||
// so a plain username must keep working until they've set one.
|
|
||||||
$identifier = filter_input(INPUT_POST, 'email');
|
|
||||||
$password = filter_input(INPUT_POST, 'password');
|
$password = filter_input(INPUT_POST, 'password');
|
||||||
$remember = filter_input(INPUT_POST, 'remember');
|
$remember = filter_input(INPUT_POST, 'remember');
|
||||||
|
|
||||||
if (!$identifier || !$password) {
|
if (!$username || !$password) {
|
||||||
$_SESSION['login_failure'] = 'Invalid email or password';
|
$_SESSION['login_failure'] = 'Invalid username or password';
|
||||||
header('Location: login.php');
|
header('Location: login.php');
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qr_is_login_locked_out($identifier)) {
|
if (qr_is_login_locked_out($username)) {
|
||||||
$_SESSION['login_failure'] = 'Too many failed login attempts. Try again in 15 minutes.';
|
$_SESSION['login_failure'] = 'Too many failed login attempts. Try again in 15 minutes.';
|
||||||
header('Location: login.php');
|
header('Location: login.php');
|
||||||
exit;
|
exit;
|
||||||
@@ -28,19 +25,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST')
|
|||||||
// Get DB instance.
|
// Get DB instance.
|
||||||
$db = getDbInstance();
|
$db = getDbInstance();
|
||||||
|
|
||||||
$db->where('email', $identifier);
|
$db->where('username', $username);
|
||||||
$row = $db->getOne('users');
|
$row = $db->getOne('users');
|
||||||
|
|
||||||
if ($db->count < 1) {
|
|
||||||
// Compatibility fallback for accounts that haven't set an email yet.
|
|
||||||
$db = getDbInstance();
|
|
||||||
$db->where('username', $identifier);
|
|
||||||
$row = $db->getOne('users');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($db->count >= 1 && password_verify($password, $row['password']))
|
if ($db->count >= 1 && password_verify($password, $row['password']))
|
||||||
{
|
{
|
||||||
qr_record_login_attempt($identifier, true);
|
qr_record_login_attempt($username, true);
|
||||||
|
|
||||||
// Voorkom session fixation: nieuwe sessie-id na een geslaagde login.
|
// Voorkom session fixation: nieuwe sessie-id na een geslaagde login.
|
||||||
session_regenerate_id(true);
|
session_regenerate_id(true);
|
||||||
@@ -50,7 +40,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST')
|
|||||||
$_SESSION['user_id'] = $row['id'];
|
$_SESSION['user_id'] = $row['id'];
|
||||||
$_SESSION['username'] = $row['username'];
|
$_SESSION['username'] = $row['username'];
|
||||||
$_SESSION['must_change_password'] = !empty($row['must_change_password']);
|
$_SESSION['must_change_password'] = !empty($row['must_change_password']);
|
||||||
$_SESSION['must_set_email'] = !empty($row['must_set_email']);
|
|
||||||
$_SESSION['can_view_static'] = !empty($row['can_view_static']);
|
$_SESSION['can_view_static'] = !empty($row['can_view_static']);
|
||||||
$_SESSION['can_view_dynamic'] = !empty($row['can_view_dynamic']);
|
$_SESSION['can_view_dynamic'] = !empty($row['can_view_dynamic']);
|
||||||
$_SESSION['scope_owner_id'] = qr_compute_scope_owner_id($row);
|
$_SESSION['scope_owner_id'] = qr_compute_scope_owner_id($row);
|
||||||
@@ -97,8 +86,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST')
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
qr_record_login_attempt($identifier, false);
|
qr_record_login_attempt($username, false);
|
||||||
$_SESSION['login_failure'] = 'Invalid email or password';
|
$_SESSION['login_failure'] = 'Invalid username or password';
|
||||||
header('Location: login.php');
|
header('Location: login.php');
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<title>QRForge</title>
|
<title>Qrcode Generator</title>
|
||||||
<head>
|
<head>
|
||||||
<?php include './includes/head.php'; ?>
|
<?php include './includes/head.php'; ?>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Public endpoint (no auth): renders a self-hosted CAPTCHA image for register.php.
|
|
||||||
// No third-party service (reCAPTCHA/Turnstile/etc) - a simple math challenge drawn
|
|
||||||
// with GD onto a noisy background, expected answer kept server-side in the session.
|
|
||||||
require_once 'includes/bootstrap.php';
|
|
||||||
|
|
||||||
$a = random_int(1, 9);
|
|
||||||
$b = random_int(1, 9);
|
|
||||||
$_SESSION['captcha_answer'] = (string) ($a + $b);
|
|
||||||
$text = "{$a} + {$b} =";
|
|
||||||
|
|
||||||
$width = 160;
|
|
||||||
$height = 60;
|
|
||||||
$image = imagecreatetruecolor($width, $height);
|
|
||||||
$bg = imagecolorallocate($image, 245, 245, 245);
|
|
||||||
$fg = imagecolorallocate($image, 30, 30, 30);
|
|
||||||
imagefill($image, 0, 0, $bg);
|
|
||||||
|
|
||||||
// Noise: random lines behind the text, purely cosmetic distortion.
|
|
||||||
for ($i = 0; $i < 8; $i++) {
|
|
||||||
$lineColor = imagecolorallocate($image, random_int(180, 220), random_int(180, 220), random_int(180, 220));
|
|
||||||
imageline($image, random_int(0, $width), random_int(0, $height), random_int(0, $width), random_int(0, $height), $lineColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
$fontFile = '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf';
|
|
||||||
if (is_file($fontFile) && function_exists('imagettftext')) {
|
|
||||||
$fontSize = 22;
|
|
||||||
$bbox = imagettfbbox($fontSize, 0, $fontFile, $text);
|
|
||||||
$textWidth = abs($bbox[2] - $bbox[0]);
|
|
||||||
$textHeight = abs($bbox[1] - $bbox[7]);
|
|
||||||
$x = (int) (($width - $textWidth) / 2);
|
|
||||||
$y = (int) (($height + $textHeight) / 2);
|
|
||||||
imagettftext($image, $fontSize, 0, $x, $y, $fg, $fontFile, $text);
|
|
||||||
} else {
|
|
||||||
imagestring($image, 5, 10, 20, $text, $fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
header('Content-Type: image/png');
|
|
||||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
|
||||||
imagepng($image);
|
|
||||||
imagedestroy($image);
|
|
||||||
@@ -47,13 +47,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<title>Change password - QRForge</title>
|
<title>Change password - Qrcode Generator</title>
|
||||||
<?php include './includes/head.php'; ?>
|
<?php include './includes/head.php'; ?>
|
||||||
|
|
||||||
<body class="login-page" style="min-height: 512.391px;">
|
<body class="login-page" style="min-height: 512.391px;">
|
||||||
<div class="login-box">
|
<div class="login-box">
|
||||||
<div class="login-logo">
|
<div class="login-logo">
|
||||||
<img src="dist/img/brand/logo.svg" alt="QRForge" style="max-width: 260px;">
|
<img src="dist/img/DynamicQRCode_Original.png" style="width: 95%; height: 95%">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|||||||
@@ -16,14 +16,3 @@ define('DATABASE_CHARSET', getenv('DATABASE_CHARSET') ?: 'utf8');
|
|||||||
define('TYPE', getenv('TYPE') ?: 'local');
|
define('TYPE', getenv('TYPE') ?: 'local');
|
||||||
define('BASE_URL', getenv('BASE_URL') ?: 'http://localhost');
|
define('BASE_URL', getenv('BASE_URL') ?: 'http://localhost');
|
||||||
define('QRCODE_GENERATOR', getenv('QRCODE_GENERATOR') ?: 'external-api.qrserver.com'); // opties: external-api.qrserver.com of internal-chillerlan.qrcode
|
define('QRCODE_GENERATOR', getenv('QRCODE_GENERATOR') ?: 'external-api.qrserver.com'); // opties: external-api.qrserver.com of internal-chillerlan.qrcode
|
||||||
|
|
||||||
define('ALLOW_SELF_REGISTRATION', filter_var(getenv('ALLOW_SELF_REGISTRATION'), FILTER_VALIDATE_BOOLEAN));
|
|
||||||
|
|
||||||
define('MAIL_HOST', getenv('MAIL_HOST') ?: '');
|
|
||||||
define('MAIL_PORT', filter_var(getenv('MAIL_PORT'), FILTER_VALIDATE_INT) ?: 587);
|
|
||||||
define('MAIL_ENCRYPTION', getenv('MAIL_ENCRYPTION') !== false ? getenv('MAIL_ENCRYPTION') : 'tls'); // opties: tls, ssl, '' (geen)
|
|
||||||
define('MAIL_SMTP_AUTH', getenv('MAIL_SMTP_AUTH') !== false ? filter_var(getenv('MAIL_SMTP_AUTH'), FILTER_VALIDATE_BOOLEAN) : true);
|
|
||||||
define('MAIL_USERNAME', getenv('MAIL_USERNAME') ?: '');
|
|
||||||
define('MAIL_PASSWORD', getenv('MAIL_PASSWORD') ?: '');
|
|
||||||
define('MAIL_FROM_ADDRESS', getenv('MAIL_FROM_ADDRESS') ?: 'noreply@example.com');
|
|
||||||
define('MAIL_FROM_NAME', getenv('MAIL_FROM_NAME') ?: 'QRForge');
|
|
||||||
|
|||||||
@@ -6,21 +6,4 @@
|
|||||||
#err-msg {
|
#err-msg {
|
||||||
display: none;
|
display: none;
|
||||||
color: red;
|
color: red;
|
||||||
}
|
|
||||||
|
|
||||||
/* List-page qr code thumbnails: fixed box, but object-fit:contain instead of
|
|
||||||
width/height attrs so taller images (e.g. icon-above-qr) don't get squashed. */
|
|
||||||
.qr-thumb-link {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
background: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.qr-thumb-img {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
object-fit: contain;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
}
|
||||||
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 476 B |
|
Before Width: | Height: | Size: 755 B |
@@ -1,11 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 151.74 151.74" role="img" aria-labelledby="qfi-title qfi-desc">
|
|
||||||
<title id="qfi-title">QRForge icon</title>
|
|
||||||
<desc id="qfi-desc">QR-block mark with outbound arrow tile.</desc>
|
|
||||||
<g transform="translate(-8.95,-2.57)">
|
|
||||||
<rect id="block-top-left" x="5.24" y="16.81" width="42.29" height="40.70" rx="2.5" fill="#334155" />
|
|
||||||
<rect id="block-mid" x="59.57" y="68.79" width="24.90" height="23.46" rx="2.5" fill="#334155" />
|
|
||||||
<rect id="block-bottom-right" x="96.08" y="103.32" width="40.59" height="31.68" rx="2.5" fill="#334155" />
|
|
||||||
<path id="block-bottom-left-L" d="M 5.24,71.29 Q 5.24,68.79 7.74,68.79 L 45.03,68.79 Q 47.53,68.79 47.53,71.29 L 47.53,103.32 Q 47.53,103.32 47.53,103.32 L 81.97,103.32 Q 84.47,103.32 84.47,105.82 L 84.47,132.50 Q 84.47,135.00 81.97,135.00 L 7.74,135.00 Q 5.24,135.00 5.24,132.50 Z" fill="#334155" />
|
|
||||||
<path id="arrow-tile" d="M 105.68,6.94 Q 104.67,5.88 106.13,5.88 L 145.89,5.88 Q 148.39,5.88 148.39,8.38 L 148.39,44.14 Q 148.39,46.64 146.42,45.11 L 133.00,34.67 Q 133.00,34.67 133.00,34.67 L 112.50,56.87 Q 112.50,56.87 112.50,56.87 L 134.08,56.87 Q 136.58,56.87 136.58,59.37 L 136.58,89.50 Q 136.58,92.00 134.08,92.00 L 98.63,92.00 Q 96.13,92.00 96.13,89.50 L 96.13,56.87 Q 96.13,56.87 96.13,56.87 L 62.10,56.87 Q 59.60,56.87 59.60,54.37 L 59.60,19.60 Q 59.60,17.10 62.10,17.10 L 93.63,17.10 Q 96.13,17.10 96.13,19.60 L 96.13,41.00 Q 96.13,41.00 96.13,41.00 L 97.00,42.17 Q 97.00,42.17 97.00,42.17 L 117.19,22.73 Q 118.99,21.00 117.27,19.18 L 107.68,9.06 Q 106.68,8.00 105.68,6.94 Z" fill="#2563EB" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,11 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 151.74 151.74" role="img" aria-labelledby="qfi-title qfi-desc">
|
|
||||||
<title id="qfi-title">QRForge icon</title>
|
|
||||||
<desc id="qfi-desc">QR-block mark with outbound arrow tile.</desc>
|
|
||||||
<g transform="translate(-8.95,-2.57)">
|
|
||||||
<rect id="block-top-left" x="5.24" y="16.81" width="42.29" height="40.70" rx="2.5" fill="#FFFFFF" />
|
|
||||||
<rect id="block-mid" x="59.57" y="68.79" width="24.90" height="23.46" rx="2.5" fill="#FFFFFF" />
|
|
||||||
<rect id="block-bottom-right" x="96.08" y="103.32" width="40.59" height="31.68" rx="2.5" fill="#FFFFFF" />
|
|
||||||
<path id="block-bottom-left-L" d="M 5.24,71.29 Q 5.24,68.79 7.74,68.79 L 45.03,68.79 Q 47.53,68.79 47.53,71.29 L 47.53,103.32 Q 47.53,103.32 47.53,103.32 L 81.97,103.32 Q 84.47,103.32 84.47,105.82 L 84.47,132.50 Q 84.47,135.00 81.97,135.00 L 7.74,135.00 Q 5.24,135.00 5.24,132.50 Z" fill="#FFFFFF" />
|
|
||||||
<path id="arrow-tile" d="M 105.68,6.94 Q 104.67,5.88 106.13,5.88 L 145.89,5.88 Q 148.39,5.88 148.39,8.38 L 148.39,44.14 Q 148.39,46.64 146.42,45.11 L 133.00,34.67 Q 133.00,34.67 133.00,34.67 L 112.50,56.87 Q 112.50,56.87 112.50,56.87 L 134.08,56.87 Q 136.58,56.87 136.58,59.37 L 136.58,89.50 Q 136.58,92.00 134.08,92.00 L 98.63,92.00 Q 96.13,92.00 96.13,89.50 L 96.13,56.87 Q 96.13,56.87 96.13,56.87 L 62.10,56.87 Q 59.60,56.87 59.60,54.37 L 59.60,19.60 Q 59.60,17.10 62.10,17.10 L 93.63,17.10 Q 96.13,17.10 96.13,19.60 L 96.13,41.00 Q 96.13,41.00 96.13,41.00 L 97.00,42.17 Q 97.00,42.17 97.00,42.17 L 117.19,22.73 Q 118.99,21.00 117.27,19.18 L 107.68,9.06 Q 106.68,8.00 105.68,6.94 Z" fill="#2563EB" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,11 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 151.74 151.74" role="img" aria-labelledby="qfi-title qfi-desc">
|
|
||||||
<title id="qfi-title">QRForge icon</title>
|
|
||||||
<desc id="qfi-desc">QR-block mark with outbound arrow tile.</desc>
|
|
||||||
<g transform="translate(-8.95,-2.57)">
|
|
||||||
<rect id="block-top-left" x="5.24" y="16.81" width="42.29" height="40.70" rx="2.5" fill="#334155" />
|
|
||||||
<rect id="block-mid" x="59.57" y="68.79" width="24.90" height="23.46" rx="2.5" fill="#334155" />
|
|
||||||
<rect id="block-bottom-right" x="96.08" y="103.32" width="40.59" height="31.68" rx="2.5" fill="#334155" />
|
|
||||||
<path id="block-bottom-left-L" d="M 5.24,71.29 Q 5.24,68.79 7.74,68.79 L 45.03,68.79 Q 47.53,68.79 47.53,71.29 L 47.53,103.32 Q 47.53,103.32 47.53,103.32 L 81.97,103.32 Q 84.47,103.32 84.47,105.82 L 84.47,132.50 Q 84.47,135.00 81.97,135.00 L 7.74,135.00 Q 5.24,135.00 5.24,132.50 Z" fill="#334155" />
|
|
||||||
<path id="arrow-tile" d="M 105.68,6.94 Q 104.67,5.88 106.13,5.88 L 145.89,5.88 Q 148.39,5.88 148.39,8.38 L 148.39,44.14 Q 148.39,46.64 146.42,45.11 L 133.00,34.67 Q 133.00,34.67 133.00,34.67 L 112.50,56.87 Q 112.50,56.87 112.50,56.87 L 134.08,56.87 Q 136.58,56.87 136.58,59.37 L 136.58,89.50 Q 136.58,92.00 134.08,92.00 L 98.63,92.00 Q 96.13,92.00 96.13,89.50 L 96.13,56.87 Q 96.13,56.87 96.13,56.87 L 62.10,56.87 Q 59.60,56.87 59.60,54.37 L 59.60,19.60 Q 59.60,17.10 62.10,17.10 L 93.63,17.10 Q 96.13,17.10 96.13,19.60 L 96.13,41.00 Q 96.13,41.00 96.13,41.00 L 97.00,42.17 Q 97.00,42.17 97.00,42.17 L 117.19,22.73 Q 118.99,21.00 117.27,19.18 L 107.68,9.06 Q 106.68,8.00 105.68,6.94 Z" fill="#2563EB" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,12 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 236.20 246.50" role="img" aria-labelledby="qfs-title qfs-desc">
|
|
||||||
<title id="qfs-title">QRForge stacked logo</title>
|
|
||||||
<desc id="qfs-desc">Stacked QRForge logo.</desc>
|
|
||||||
<g transform="translate(33.28,14.75)">
|
|
||||||
<rect id="block-top-left" x="5.24" y="16.81" width="42.29" height="40.70" rx="2.5" fill="#334155" />
|
|
||||||
<rect id="block-mid" x="59.57" y="68.79" width="24.90" height="23.46" rx="2.5" fill="#334155" />
|
|
||||||
<rect id="block-bottom-right" x="96.08" y="103.32" width="40.59" height="31.68" rx="2.5" fill="#334155" />
|
|
||||||
<path id="block-bottom-left-L" d="M 5.24,71.29 Q 5.24,68.79 7.74,68.79 L 45.03,68.79 Q 47.53,68.79 47.53,71.29 L 47.53,103.32 Q 47.53,103.32 47.53,103.32 L 81.97,103.32 Q 84.47,103.32 84.47,105.82 L 84.47,132.50 Q 84.47,135.00 81.97,135.00 L 7.74,135.00 Q 5.24,135.00 5.24,132.50 Z" fill="#334155" />
|
|
||||||
<path id="arrow-tile" d="M 105.68,6.94 Q 104.67,5.88 106.13,5.88 L 145.89,5.88 Q 148.39,5.88 148.39,8.38 L 148.39,44.14 Q 148.39,46.64 146.42,45.11 L 133.00,34.67 Q 133.00,34.67 133.00,34.67 L 112.50,56.87 Q 112.50,56.87 112.50,56.87 L 134.08,56.87 Q 136.58,56.87 136.58,59.37 L 136.58,89.50 Q 136.58,92.00 134.08,92.00 L 98.63,92.00 Q 96.13,92.00 96.13,89.50 L 96.13,56.87 Q 96.13,56.87 96.13,56.87 L 62.10,56.87 Q 59.60,56.87 59.60,54.37 L 59.60,19.60 Q 59.60,17.10 62.10,17.10 L 93.63,17.10 Q 96.13,17.10 96.13,19.60 L 96.13,41.00 Q 96.13,41.00 96.13,41.00 L 97.00,42.17 Q 97.00,42.17 97.00,42.17 L 117.19,22.73 Q 118.99,21.00 117.27,19.18 L 107.68,9.06 Q 106.68,8.00 105.68,6.94 Z" fill="#2563EB" />
|
|
||||||
</g>
|
|
||||||
<text x="118.10" y="203.67" text-anchor="middle" font-family="Inter, Manrope, Geist, Arial, sans-serif" font-size="48.39" font-weight="700" letter-spacing="-3.43" fill="#334155">QRForge</text>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,12 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="5.24 5.88 573.81 145.12" role="img" aria-labelledby="qf-title qf-desc">
|
|
||||||
<title id="qf-title">QRForge logo</title>
|
|
||||||
<desc id="qf-desc">QRForge wordmark with a QR-block mark and outbound arrow tile.</desc>
|
|
||||||
<g id="icon">
|
|
||||||
<rect id="block-top-left" x="5.24" y="16.81" width="42.29" height="40.70" rx="2.5" fill="#334155" />
|
|
||||||
<rect id="block-mid" x="59.57" y="68.79" width="24.90" height="23.46" rx="2.5" fill="#334155" />
|
|
||||||
<rect id="block-bottom-right" x="96.08" y="103.32" width="40.59" height="31.68" rx="2.5" fill="#334155" />
|
|
||||||
<path id="block-bottom-left-L" d="M 5.24,71.29 Q 5.24,68.79 7.74,68.79 L 45.03,68.79 Q 47.53,68.79 47.53,71.29 L 47.53,103.32 Q 47.53,103.32 47.53,103.32 L 81.97,103.32 Q 84.47,103.32 84.47,105.82 L 84.47,132.50 Q 84.47,135.00 81.97,135.00 L 7.74,135.00 Q 5.24,135.00 5.24,132.50 Z" fill="#334155" />
|
|
||||||
<path id="arrow-tile" d="M 105.68,6.94 Q 104.67,5.88 106.13,5.88 L 145.89,5.88 Q 148.39,5.88 148.39,8.38 L 148.39,44.14 Q 148.39,46.64 146.42,45.11 L 133.00,34.67 Q 133.00,34.67 133.00,34.67 L 112.50,56.87 Q 112.50,56.87 112.50,56.87 L 134.08,56.87 Q 136.58,56.87 136.58,59.37 L 136.58,89.50 Q 136.58,92.00 134.08,92.00 L 98.63,92.00 Q 96.13,92.00 96.13,89.50 L 96.13,56.87 Q 96.13,56.87 96.13,56.87 L 62.10,56.87 Q 59.60,56.87 59.60,54.37 L 59.60,19.60 Q 59.60,17.10 62.10,17.10 L 93.63,17.10 Q 96.13,17.10 96.13,19.60 L 96.13,41.00 Q 96.13,41.00 96.13,41.00 L 97.00,42.17 Q 97.00,42.17 97.00,42.17 L 117.19,22.73 Q 118.99,21.00 117.27,19.18 L 107.68,9.06 Q 106.68,8.00 105.68,6.94 Z" fill="#2563EB" />
|
|
||||||
</g>
|
|
||||||
<text id="wordmark" x="177" y="101" font-family="Inter, Manrope, Geist, Arial, sans-serif" font-size="103" font-weight="700" letter-spacing="-7.3" fill="#334155">QRForge</text>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 26 KiB |
@@ -1,69 +0,0 @@
|
|||||||
(function () {
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
var searchInput = document.getElementById('location_search');
|
|
||||||
var searchBtn = document.getElementById('location_search_btn');
|
|
||||||
var resultsBox = document.getElementById('location_search_results');
|
|
||||||
var latInput = document.getElementById('latitude');
|
|
||||||
var lngInput = document.getElementById('longitude');
|
|
||||||
|
|
||||||
if (!searchInput || !searchBtn || !resultsBox || !latInput || !lngInput) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearResults() {
|
|
||||||
resultsBox.innerHTML = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function doSearch() {
|
|
||||||
var query = searchInput.value.trim();
|
|
||||||
if (!query) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearResults();
|
|
||||||
resultsBox.innerHTML = '<div class="list-group-item">Searching...</div>';
|
|
||||||
|
|
||||||
fetch('https://nominatim.openstreetmap.org/search?format=json&limit=5&q=' + encodeURIComponent(query))
|
|
||||||
.then(function (response) { return response.json(); })
|
|
||||||
.then(function (results) {
|
|
||||||
clearResults();
|
|
||||||
|
|
||||||
if (!results.length) {
|
|
||||||
resultsBox.innerHTML = '<div class="list-group-item">No results found.</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
results.forEach(function (place) {
|
|
||||||
var item = document.createElement('button');
|
|
||||||
item.type = 'button';
|
|
||||||
item.className = 'list-group-item list-group-item-action';
|
|
||||||
item.textContent = place.display_name;
|
|
||||||
item.addEventListener('click', function () {
|
|
||||||
latInput.value = place.lat;
|
|
||||||
lngInput.value = place.lon;
|
|
||||||
searchInput.value = place.display_name;
|
|
||||||
clearResults();
|
|
||||||
});
|
|
||||||
resultsBox.appendChild(item);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(function () {
|
|
||||||
resultsBox.innerHTML = '<div class="list-group-item text-danger">Search failed (network error).</div>';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
searchBtn.addEventListener('click', doSearch);
|
|
||||||
searchInput.addEventListener('keydown', function (e) {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
e.preventDefault();
|
|
||||||
doSearch();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('click', function (e) {
|
|
||||||
if (e.target !== searchInput && !resultsBox.contains(e.target)) {
|
|
||||||
clearResults();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
@@ -95,18 +95,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll('#foreground, #background').forEach(function (input) {
|
document.querySelectorAll('#foreground, #background').forEach(function (input) {
|
||||||
// bootstrap-colorpicker sets the value and fires 'change' via jQuery's
|
input.addEventListener('change', function () {
|
||||||
// synthetic .trigger(), which a native addEventListener never sees - so the
|
updateStylePreview(scopeOf(input));
|
||||||
// preview only updates via jQuery's event binding, not the native one below.
|
});
|
||||||
if (window.jQuery) {
|
|
||||||
jQuery(input).on('change', function () {
|
|
||||||
updateStylePreview(scopeOf(input));
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
input.addEventListener('change', function () {
|
|
||||||
updateStylePreview(scopeOf(input));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll('select[name="level"]').forEach(function (select) {
|
document.querySelectorAll('select[name="level"]').forEach(function (select) {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ if($_SERVER["REQUEST_METHOD"] === "POST" && !isset($_POST["edit"]) && !isset($_P
|
|||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<title>QRForge</title>
|
<title>Qrcode Generator</title>
|
||||||
<head>
|
<head>
|
||||||
<?php include './includes/head.php'; ?>
|
<?php include './includes/head.php'; ?>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ $total_pages = $db->totalPages;
|
|||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<title>QRForge</title>
|
<title>Qrcode Generator</title>
|
||||||
<head>
|
<head>
|
||||||
<?php include './includes/head.php'; ?>
|
<?php include './includes/head.php'; ?>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 31 KiB |
@@ -117,7 +117,27 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="filename">Filename *</label>
|
||||||
|
<input type="text" name="filename" value="" placeholder="My first Qrcode" class="form-control error" required="required" id="filename">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-6 col-md-1">
|
||||||
|
<label for="format">Format *</label>
|
||||||
|
<select name="format" class="form-control" required="required">
|
||||||
|
<option value="png" selected>PNG</option>
|
||||||
|
<option value="gif">GIF</option>
|
||||||
|
<option value="jpeg">JPEG</option>
|
||||||
|
<option value="jpg">JPG</option>
|
||||||
|
<option value="svg">SVG</option>
|
||||||
|
<option value="eps">EPS</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="frame_text">Frame text</label>
|
<label for="frame_text">Frame text</label>
|
||||||
@@ -157,32 +177,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php if($_SESSION['type'] === 'super') { ?>
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-4">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="filename">Filename *</label>
|
|
||||||
<input type="text" name="filename" value="" placeholder="My first Qrcode" class="form-control error" required="required" id="filename">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-6 col-md-1">
|
|
||||||
<label for="format">Format *</label>
|
|
||||||
<select name="format" class="form-control" required="required">
|
|
||||||
<option value="png" selected>PNG</option>
|
|
||||||
<option value="gif">GIF</option>
|
|
||||||
<option value="jpeg">JPEG</option>
|
|
||||||
<option value="jpg">JPG</option>
|
|
||||||
<option value="svg">SVG</option>
|
|
||||||
<option value="eps">EPS</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($_SESSION['type'] === 'super') { ?>
|
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="id_owner">Owner *</label>
|
<label for="id_owner">Owner *</label>
|
||||||
<select name="id_owner" id="id_owner" class="form-control">
|
<select name="id_owner" class="form-control">
|
||||||
<option value="">All (shared with every admin)</option>
|
<option value="">All (shared with every admin)</option>
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
@@ -198,9 +199,9 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php } else { ?>
|
|
||||||
<input type="hidden" name="id_owner" value="<?php echo $_SESSION["user_id"];?>"/>
|
|
||||||
<?php } ?>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php } else { ?>
|
||||||
|
<input type="hidden" name="id_owner" value="<?php echo $_SESSION["user_id"];?>"/>
|
||||||
|
<?php } ?>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -12,20 +12,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email">Email</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<div class="input-group-prepend">
|
|
||||||
<span class="input-group-text"><i class="fa fa-envelope"></i></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="email" name="email" placeholder="Email" class="form-control" value="<?php echo ($edit) ? htmlspecialchars($user['email'] ?? '', ENT_QUOTES, 'UTF-8') : ''; ?>" autocomplete="off">
|
|
||||||
</div>
|
|
||||||
<small class="form-text text-muted">Used to log in once set. Leave blank to prompt for it on next login.</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">Password *</label>
|
<label for="password">Password *</label>
|
||||||
@@ -40,29 +26,25 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if ($_SESSION['type'] === 'super'): ?>
|
<?php if ($_SESSION['type'] === 'super'): ?>
|
||||||
<?php $editing_self = $edit && (int) $user['id'] === (int) $_SESSION['user_id']; ?>
|
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<label for="user-type">User type *</label>
|
<label for="user-type">User type *</label>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="radio">
|
<div class="radio">
|
||||||
<label class="radio">
|
<label class="radio">
|
||||||
<input type="radio" name="type" value="super" required="required" <?php echo ($edit && $user['type'] =='super') ? "checked": "" ; ?> <?php echo $editing_self ? "disabled" : ""; ?>/> Super admin</label>
|
<input type="radio" name="type" value="super" required="required" <?php echo ($edit && $user['type'] =='super') ? "checked": "" ; ?>/> Super admin</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="radio">
|
<div class="radio">
|
||||||
<label class="radio">
|
<label class="radio">
|
||||||
<input type="radio" name="type" value="admin" required="required" <?php echo ($edit && $user['type'] =='admin') ? "checked": "" ; ?> <?php echo $editing_self ? "disabled" : ""; ?>/> Admin</label>
|
<input type="radio" name="type" value="admin" required="required" <?php echo ($edit && $user['type'] =='admin') ? "checked": "" ; ?>/> Admin</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="radio">
|
<div class="radio">
|
||||||
<label class="radio">
|
<label class="radio">
|
||||||
<input type="radio" name="type" value="user" required="required" id="type-user" <?php echo ($edit && $user['type'] =='user') ? "checked": "" ; ?> <?php echo $editing_self ? "disabled" : ""; ?>/> User (read-only)</label>
|
<input type="radio" name="type" value="user" required="required" id="type-user" <?php echo ($edit && $user['type'] =='user') ? "checked": "" ; ?>/> User (read-only)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php if ($editing_self): ?>
|
|
||||||
<small class="form-text text-muted">You can't change your own access level.</small>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-12 mt-2" id="user-view-toggles">
|
<div class="col-sm-12 mt-2" id="user-view-toggles">
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ if (QRCODE_GENERATOR === "internal-chillerlan.qrcode") {
|
|||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
<label>Randomize</label>
|
<label> </label>
|
||||||
<button type="button" id="random_style_btn" class="btn btn-outline-secondary btn-block">
|
<button type="button" id="random_style_btn" class="btn btn-outline-secondary btn-block">
|
||||||
<i class="fa fa-dice"></i> Random style
|
<i class="fa fa-dice"></i> Random style
|
||||||
</button>
|
</button>
|
||||||
@@ -113,7 +113,32 @@ if (QRCODE_GENERATOR === "internal-chillerlan.qrcode") {
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="filename">Filename *</label>
|
||||||
|
<input type="text" name="filename" value="" placeholder="My first Qrcode" class="form-control error" required="required" id = "filename">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-6 col-md-1">
|
||||||
|
<label for="format">Format</label>
|
||||||
|
<select name="format" class="form-control">
|
||||||
|
<option value="png">PNG</option>
|
||||||
|
<option value="gif">GIF</option>
|
||||||
|
<option value="jpeg">JPEG</option>
|
||||||
|
<option value="jpg">JPG</option>
|
||||||
|
<option value="svg">SVG</option>
|
||||||
|
<?php
|
||||||
|
if (QRCODE_GENERATOR === "internal-chillerlan.qrcode") {
|
||||||
|
echo ' <option value="svgbw">SVG (BW)</option>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<option value="eps">EPS</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="frame_text">Frame text</label>
|
<label for="frame_text">Frame text</label>
|
||||||
@@ -153,55 +178,31 @@ if (QRCODE_GENERATOR === "internal-chillerlan.qrcode") {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-12 mb-2">
|
<?php if($_SESSION['type'] === 'super') { ?>
|
||||||
<div class="row">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="col-sm-4">
|
<div class="row">
|
||||||
<div class="form-group">
|
<div class="col-sm-4">
|
||||||
<label for="filename">Filename *</label>
|
<div class="form-group">
|
||||||
<input type="text" name="filename" value="" placeholder="My first Qrcode" class="form-control error" required="required" id="filename">
|
<label for="id_owner">Owner *</label>
|
||||||
|
<select name="id_owner" class="form-control">
|
||||||
|
<option value="">All (shared with every admin)</option>
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once BASE_PATH . '/lib/Users/Users.php';
|
||||||
|
$users_instance = new Users();
|
||||||
|
$users = $users_instance->getAllUsers();
|
||||||
|
|
||||||
|
foreach ($users as $user) {
|
||||||
|
$is_self = (int) $user["id"] === (int) $_SESSION["user_id"];
|
||||||
|
?>
|
||||||
|
<option value="<?php echo $user["id"];?>" <?php echo $is_self ? 'selected' : ''; ?>><?php echo $user["username"];?></option>
|
||||||
|
<?php } ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-6 col-md-1">
|
|
||||||
<label for="format">Format</label>
|
|
||||||
<select name="format" class="form-control">
|
|
||||||
<option value="png">PNG</option>
|
|
||||||
<option value="gif">GIF</option>
|
|
||||||
<option value="jpeg">JPEG</option>
|
|
||||||
<option value="jpg">JPG</option>
|
|
||||||
<option value="svg">SVG</option>
|
|
||||||
<?php
|
|
||||||
if (QRCODE_GENERATOR === "internal-chillerlan.qrcode") {
|
|
||||||
echo ' <option value="svgbw">SVG (BW)</option>';
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<option value="eps">EPS</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($_SESSION['type'] === 'super') { ?>
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="id_owner">Owner *</label>
|
|
||||||
<select name="id_owner" id="id_owner" class="form-control">
|
|
||||||
<option value="">All (shared with every admin)</option>
|
|
||||||
<?php
|
|
||||||
|
|
||||||
require_once BASE_PATH . '/lib/Users/Users.php';
|
|
||||||
$users_instance = new Users();
|
|
||||||
$users = $users_instance->getAllUsers();
|
|
||||||
|
|
||||||
foreach ($users as $user) {
|
|
||||||
$is_self = (int) $user["id"] === (int) $_SESSION["user_id"];
|
|
||||||
?>
|
|
||||||
<option value="<?php echo $user["id"];?>" <?php echo $is_self ? 'selected' : ''; ?>><?php echo $user["username"];?></option>
|
|
||||||
<?php } ?>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php } else { ?>
|
|
||||||
<input type="hidden" name="id_owner" value="<?php echo $_SESSION["user_id"];?>"/>
|
|
||||||
<?php } ?>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<?php } else { ?>
|
||||||
|
<input type="hidden" name="id_owner" value="<?php echo $_SESSION["user_id"];?>"/>
|
||||||
|
<?php } ?>
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<form class="form" action="static_qrcode.php?type=2fa" method="post" id="static_form" enctype="multipart/form-data">
|
<form class="form" action="static_qrcode.php?type=2fa" method="post" id="static_form" enctype="multipart/form-data">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
|
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||||
<!-- Input forms -->
|
<!-- Input forms -->
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -37,7 +38,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<form class="form" action="static_qrcode.php?type=applink" method="post" id="static_form" enctype="multipart/form-data">
|
<form class="form" action="static_qrcode.php?type=applink" method="post" id="static_form" enctype="multipart/form-data">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
|
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||||
<!-- Input forms -->
|
<!-- Input forms -->
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -61,7 +62,6 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<form class="form" action="static_qrcode.php?type=bitcoin" method="post" id="static_form" enctype="multipart/form-data">
|
<form class="form" action="static_qrcode.php?type=bitcoin" method="post" id="static_form" enctype="multipart/form-data">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
|
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||||
<!-- Input forms -->
|
<!-- Input forms -->
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -46,7 +47,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<form class="form" action="static_qrcode.php?type=bluetooth" method="post" id="static_form" enctype="multipart/form-data">
|
<form class="form" action="static_qrcode.php?type=bluetooth" method="post" id="static_form" enctype="multipart/form-data">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
|
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||||
<!-- Input forms -->
|
<!-- Input forms -->
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<small class="form-text text-muted mb-2">
|
<small class="form-text text-muted mb-2">
|
||||||
@@ -25,7 +26,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<form class="form" action="static_qrcode.php?type=bookmark" method="post" id="static_form" enctype="multipart/form-data">
|
<form class="form" action="static_qrcode.php?type=bookmark" method="post" id="static_form" enctype="multipart/form-data">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
|
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||||
<!-- Input forms -->
|
<!-- Input forms -->
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -19,7 +20,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<form class="form" action="static_qrcode.php?type=email" method="post" id="static_form" enctype="multipart/form-data">
|
<form class="form" action="static_qrcode.php?type=email" method="post" id="static_form" enctype="multipart/form-data">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
|
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||||
<!-- Input forms -->
|
<!-- Input forms -->
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -27,7 +28,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<form class="form" action="static_qrcode.php?type=event" method="post" id="static_form" enctype="multipart/form-data">
|
<form class="form" action="static_qrcode.php?type=event" method="post" id="static_form" enctype="multipart/form-data">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
|
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||||
<!-- Input forms -->
|
<!-- Input forms -->
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -71,7 +72,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
|
|||||||
@@ -1,42 +1,27 @@
|
|||||||
<form class="form" action="static_qrcode.php?type=location" method="post" id="static_form" enctype="multipart/form-data">
|
<form class="form" action="static_qrcode.php?type=location" method="post" id="static_form" enctype="multipart/form-data">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
<!-- Input forms -->
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<div class="form-group" style="position: relative;">
|
|
||||||
<label for="location_search">Search address</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" id="location_search" class="form-control" placeholder="Search for an address or place...">
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button type="button" id="location_search_btn" class="btn btn-outline-secondary"><i class="fa fa-search"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="location_search_results" class="list-group" style="position:absolute;z-index:1000;width:100%;"></div>
|
|
||||||
<small class="form-text text-muted">Looks up coordinates via OpenStreetMap Nominatim (your search leaves this server and goes to nominatim.openstreetmap.org). Or enter coordinates directly below.</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="latitude">Latitude *</label>
|
|
||||||
<input type="text" name="latitude" id="latitude" value="" placeholder="40.7127753" class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="longitude">Longitude *</label>
|
|
||||||
<input type="text" name="longitude" id="longitude" value="" placeholder="-74.0059728" class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||||
|
<!-- Input forms -->
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Latitude *</label>
|
||||||
|
<input type="text" name="latitude" value="" placeholder="40.7127753" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Longitude *</label>
|
||||||
|
<input type="text" name="longitude" value="" placeholder="-74.0059728" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
<button type="submit" class="btn btn-primary">Submit</button>
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="dist/js/location-search.js"></script>
|
|
||||||
</form>
|
</form>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<form class="form" action="static_qrcode.php?type=paypal" method="post" id="static_form" enctype="multipart/form-data">
|
<form class="form" action="static_qrcode.php?type=paypal" method="post" id="static_form" enctype="multipart/form-data">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
|
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||||
<!-- Input forms -->
|
<!-- Input forms -->
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -99,7 +100,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<form class="form" action="static_qrcode.php?type=phone" method="post" id="static_form" enctype="multipart/form-data">
|
<form class="form" action="static_qrcode.php?type=phone" method="post" id="static_form" enctype="multipart/form-data">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
|
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||||
<!-- Input forms -->
|
<!-- Input forms -->
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -22,7 +23,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<form class="form" action="static_qrcode.php?type=skype" method="post" id="static_form" enctype="multipart/form-data">
|
<form class="form" action="static_qrcode.php?type=skype" method="post" id="static_form" enctype="multipart/form-data">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
|
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||||
<!-- Input forms -->
|
<!-- Input forms -->
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -8,7 +9,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<form class="form" action="static_qrcode.php?type=sms" method="post" id="static_form" enctype="multipart/form-data">
|
<form class="form" action="static_qrcode.php?type=sms" method="post" id="static_form" enctype="multipart/form-data">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
|
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||||
<!-- Input forms -->
|
<!-- Input forms -->
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -29,7 +30,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<form class="form" action="static_qrcode.php?type=text" method="post" id="static_form" enctype="multipart/form-data">
|
<form class="form" action="static_qrcode.php?type=text" method="post" id="static_form" enctype="multipart/form-data">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
|
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||||
<!-- Input forms -->
|
<!-- Input forms -->
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -8,7 +9,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<form class="form" action="static_qrcode.php?type=vcard" method="post" id="static_form" enctype="multipart/form-data">
|
<form class="form" action="static_qrcode.php?type=vcard" method="post" id="static_form" enctype="multipart/form-data">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
|
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||||
<!-- Input forms -->
|
<!-- Input forms -->
|
||||||
<!-- First row -->
|
<!-- First row -->
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
@@ -137,18 +138,10 @@
|
|||||||
<input type="text" name="state" value="" placeholder="" class="form-control">
|
<input type="text" name="state" value="" placeholder="" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-6 col-md-3">
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Country</label>
|
|
||||||
<input type="text" name="country" value="" placeholder="" class="form-control">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<form class="form" action="static_qrcode.php?type=whatsapp" method="post" id="static_form" enctype="multipart/form-data">
|
<form class="form" action="static_qrcode.php?type=whatsapp" method="post" id="static_form" enctype="multipart/form-data">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
|
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||||
<!-- Input forms -->
|
<!-- Input forms -->
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -29,7 +30,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<form class="form" action="static_qrcode.php?type=wifi" method="post" id="static_form" enctype="multipart/form-data">
|
<form class="form" action="static_qrcode.php?type=wifi" method="post" id="static_form" enctype="multipart/form-data">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
|
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
||||||
<!-- Input forms -->
|
<!-- Input forms -->
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -32,7 +33,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include BASE_PATH.'/forms/qrcode_options.php'; ?>
|
|
||||||
<div class="col-sm-12 mb-2">
|
<div class="col-sm-12 mb-2">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-3">
|
<div class="col-6 col-md-3">
|
||||||
|
|||||||
@@ -70,11 +70,7 @@
|
|||||||
<td><?php echo htmlspecialchars($row['identifier']); ?></td>
|
<td><?php echo htmlspecialchars($row['identifier']); ?></td>
|
||||||
<td><?php echo htmlspecialchars($row['link']); ?></td>
|
<td><?php echo htmlspecialchars($row['link']); ?></td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" class="qr-thumb-link" data-toggle="modal" data-target="#preview-modal"
|
<?php echo '<img src="qrcode_image.php?type=dynamic&id='.$row['id'].'" width="100" height="100">'; ?>
|
||||||
data-qr-src="qrcode_image.php?type=dynamic&id=<?php echo $row['id']; ?>"
|
|
||||||
data-qr-name="<?php echo htmlspecialchars($row['filename']); ?>">
|
|
||||||
<img src="qrcode_image.php?type=dynamic&id=<?php echo $row['id']; ?>" class="qr-thumb-img" alt="QR code for <?php echo htmlspecialchars($row['filename']); ?>">
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td><?php echo htmlspecialchars($row['scan']); ?></td>
|
<td><?php echo htmlspecialchars($row['scan']); ?></td>
|
||||||
<td><?php echo htmlspecialchars($row['state']); ?></td>
|
<td><?php echo htmlspecialchars($row['state']); ?></td>
|
||||||
@@ -139,32 +135,6 @@
|
|||||||
<!-- /.Delete Confirmation Modal -->
|
<!-- /.Delete Confirmation Modal -->
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Qr Code Preview Modal -->
|
|
||||||
<div class="modal fade" id="preview-modal" role="dialog">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h4 class="modal-title" id="preview-modal-title">QR code preview</h4>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body text-center">
|
|
||||||
<img id="preview-modal-img" src="" alt="" style="max-width:100%;max-height:70vh;">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- /.Qr Code Preview Modal -->
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.querySelectorAll('.qr-thumb-link').forEach(function (link) {
|
|
||||||
link.addEventListener('click', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
document.getElementById('preview-modal-img').src = link.getAttribute('data-qr-src');
|
|
||||||
document.getElementById('preview-modal-title').textContent = link.getAttribute('data-qr-name');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const deleteButtons = document.querySelectorAll('.delete_btn');
|
const deleteButtons = document.querySelectorAll('.delete_btn');
|
||||||
|
|
||||||
|
|||||||
@@ -68,11 +68,7 @@
|
|||||||
<td><?php echo htmlspecialchars($row['type']); ?></td>
|
<td><?php echo htmlspecialchars($row['type']); ?></td>
|
||||||
<td><?php echo htmlspecialchars_decode($row['content']); ?></td>
|
<td><?php echo htmlspecialchars_decode($row['content']); ?></td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" class="qr-thumb-link" data-toggle="modal" data-target="#preview-modal"
|
<?php echo '<img src="qrcode_image.php?type=static&id='.$row['id'].'" width="100" height="100">'; ?>
|
||||||
data-qr-src="qrcode_image.php?type=static&id=<?php echo $row['id']; ?>"
|
|
||||||
data-qr-name="<?php echo htmlspecialchars($row['filename']); ?>">
|
|
||||||
<img src="qrcode_image.php?type=static&id=<?php echo $row['id']; ?>" class="qr-thumb-img" alt="QR code for <?php echo htmlspecialchars($row['filename']); ?>">
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<?php if (!$is_readonly_user): ?>
|
<?php if (!$is_readonly_user): ?>
|
||||||
@@ -135,32 +131,6 @@
|
|||||||
<!-- /.Delete Confirmation Modal -->
|
<!-- /.Delete Confirmation Modal -->
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Qr Code Preview Modal -->
|
|
||||||
<div class="modal fade" id="preview-modal" role="dialog">
|
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h4 class="modal-title" id="preview-modal-title">QR code preview</h4>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body text-center">
|
|
||||||
<img id="preview-modal-img" src="" alt="" style="max-width:100%;max-height:70vh;">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- /.Qr Code Preview Modal -->
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.querySelectorAll('.qr-thumb-link').forEach(function (link) {
|
|
||||||
link.addEventListener('click', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
document.getElementById('preview-modal-img').src = link.getAttribute('data-qr-src');
|
|
||||||
document.getElementById('preview-modal-title').textContent = link.getAttribute('data-qr-name');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const deleteButtons = document.querySelectorAll('.delete_btn');
|
const deleteButtons = document.querySelectorAll('.delete_btn');
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,8 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th width="5%">ID</th>
|
<th width="5%">ID</th>
|
||||||
<th width="25%">Username</th>
|
<th width="45%">Username</th>
|
||||||
<th width="30%">Email</th>
|
<th width="40%">Type</th>
|
||||||
<th width="30%">Type</th>
|
|
||||||
<th width="10%">Actions</th>
|
<th width="10%">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -17,7 +16,6 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td><?php echo $row['id']; ?></td>
|
<td><?php echo $row['id']; ?></td>
|
||||||
<td><?php echo htmlspecialchars($row['username']); ?></td>
|
<td><?php echo htmlspecialchars($row['username']); ?></td>
|
||||||
<td><?php echo htmlspecialchars($row['email'] ?? ''); ?></td>
|
|
||||||
<td><?php echo htmlspecialchars($row['type']); ?></td>
|
<td><?php echo htmlspecialchars($row['type']); ?></td>
|
||||||
<td>
|
<td>
|
||||||
<!-- EDIT -->
|
<!-- EDIT -->
|
||||||
|
|||||||
@@ -12,4 +12,3 @@ require_once __DIR__ . '/security.php';
|
|||||||
qr_session_start();
|
qr_session_start();
|
||||||
qr_enforce_session_timeout();
|
qr_enforce_session_timeout();
|
||||||
qr_enforce_password_change();
|
qr_enforce_password_change();
|
||||||
qr_enforce_email_set();
|
|
||||||
|
|||||||
@@ -6,10 +6,9 @@
|
|||||||
|
|
||||||
<!-- Main Footer -->
|
<!-- Main Footer -->
|
||||||
<footer class="main-footer text-sm">
|
<footer class="main-footer text-sm">
|
||||||
<strong><a href="https://www.qrforge.eu" target="_blank">QRForge</a></strong> -
|
<strong><a href="https://github.com/giandonatoinverso/PHP-Dynamic-Qr-code" target="_blank">PHP Qrcode Generator</a> by </strong> Giandonato Inverso
|
||||||
<a href="./about.php">About</a> / credits
|
|
||||||
<div class="float-right d-none d-sm-inline-block">
|
<div class="float-right d-none d-sm-inline-block">
|
||||||
<b>Version</b> 3.0
|
<b>Version</b> 2.3.0
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,64 +34,4 @@
|
|||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.register('service-worker.js');
|
navigator.serviceWorker.register('service-worker.js');
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Idle-timeout warning: the session dies silently after
|
|
||||||
<?php echo SESSION_IDLE_TIMEOUT; ?> seconds of inactivity (no PHP page
|
|
||||||
load), which loses whatever form the user is filling in. This warns a
|
|
||||||
couple of minutes before that happens and offers a "stay logged in"
|
|
||||||
button that pings the server without navigating away.
|
|
||||||
-->
|
|
||||||
<div id="session-timeout-toast" class="toast" role="alert" aria-live="assertive" aria-atomic="true"
|
|
||||||
style="position:fixed;bottom:20px;right:20px;z-index:2000;min-width:320px;display:none;">
|
|
||||||
<div class="toast-header bg-warning">
|
|
||||||
<i class="fa fa-clock mr-2"></i>
|
|
||||||
<strong class="mr-auto">Session expiring soon</strong>
|
|
||||||
</div>
|
|
||||||
<div class="toast-body bg-white">
|
|
||||||
<span id="session-timeout-message">You'll be logged out in a couple of minutes due to inactivity.</span>
|
|
||||||
<div class="mt-2">
|
|
||||||
<button type="button" id="session-timeout-extend" class="btn btn-sm btn-primary">Stay logged in</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
var SESSION_IDLE_TIMEOUT = <?php echo (int) SESSION_IDLE_TIMEOUT; ?>;
|
|
||||||
var WARNING_LEAD_TIME = 120; // show the warning this many seconds before expiry
|
|
||||||
var toast = document.getElementById('session-timeout-toast');
|
|
||||||
var message = document.getElementById('session-timeout-message');
|
|
||||||
var extendBtn = document.getElementById('session-timeout-extend');
|
|
||||||
var warnTimer = null;
|
|
||||||
|
|
||||||
function showWarning() {
|
|
||||||
toast.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function scheduleWarning() {
|
|
||||||
clearTimeout(warnTimer);
|
|
||||||
var delayMs = Math.max(0, (SESSION_IDLE_TIMEOUT - WARNING_LEAD_TIME) * 1000);
|
|
||||||
warnTimer = setTimeout(showWarning, delayMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
extendBtn.addEventListener('click', function () {
|
|
||||||
fetch('session_ping.php', { method: 'GET', redirect: 'manual', credentials: 'same-origin' })
|
|
||||||
.then(function (response) {
|
|
||||||
// redirect: 'manual' turns a server-side redirect (session already
|
|
||||||
// dead) into an opaque response instead of silently following it.
|
|
||||||
if (response.type === 'opaqueredirect' || !response.ok) {
|
|
||||||
throw new Error('expired');
|
|
||||||
}
|
|
||||||
toast.style.display = 'none';
|
|
||||||
scheduleWarning();
|
|
||||||
})
|
|
||||||
.catch(function () {
|
|
||||||
message.textContent = 'Your session already expired - please copy any unsaved work before reloading.';
|
|
||||||
extendBtn.style.display = 'none';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
scheduleWarning();
|
|
||||||
})();
|
|
||||||
</script>
|
</script>
|
||||||
@@ -2,12 +2,9 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
<meta name="csrf-token" content="<?php echo csrf_token(); ?>">
|
<meta name="csrf-token" content="<?php echo csrf_token(); ?>">
|
||||||
<meta name="theme-color" content="#2563EB">
|
<meta name="theme-color" content="#007bff">
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
<link rel="icon" href="dist/img/brand/favicon.svg" type="image/svg+xml">
|
<link rel="apple-touch-icon" href="dist/img/icon-192.png">
|
||||||
<link rel="icon" href="dist/img/brand/favicon-32.png" sizes="32x32" type="image/png">
|
|
||||||
<link rel="icon" href="dist/img/brand/favicon-16.png" sizes="16x16" type="image/png">
|
|
||||||
<link rel="apple-touch-icon" href="dist/img/brand/apple-touch-icon.png">
|
|
||||||
|
|
||||||
<!-- Font Awesome Icons -->
|
<!-- Font Awesome Icons -->
|
||||||
<link rel="stylesheet" href="plugins/fontawesome-free/css/all.min.css">
|
<link rel="stylesheet" href="plugins/fontawesome-free/css/all.min.css">
|
||||||
|
|||||||
@@ -66,11 +66,8 @@ function qr_enforce_password_change() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also exempt set_email.php - an account can need both flows at once (e.g. a fresh
|
|
||||||
// self-registered row, or a pre-migration account that never set a password either),
|
|
||||||
// and each enforcer redirecting to its own page while blocking the other's would loop forever.
|
|
||||||
$current_script = basename(parse_url($_SERVER['SCRIPT_NAME'], PHP_URL_PATH));
|
$current_script = basename(parse_url($_SERVER['SCRIPT_NAME'], PHP_URL_PATH));
|
||||||
$exempt = ['change_password.php', 'set_email.php', 'logout.php'];
|
$exempt = ['change_password.php', 'logout.php'];
|
||||||
|
|
||||||
if (in_array($current_script, $exempt, true)) {
|
if (in_array($current_script, $exempt, true)) {
|
||||||
return;
|
return;
|
||||||
@@ -80,26 +77,6 @@ function qr_enforce_password_change() {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Stuurt ingelogde gebruikers zonder e-mailadres naar set_email.php, behalve op de
|
|
||||||
* wijzigingspagina's zelf en logout.
|
|
||||||
*/
|
|
||||||
function qr_enforce_email_set() {
|
|
||||||
if (empty($_SESSION['user_logged_in']) || empty($_SESSION['must_set_email'])) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$current_script = basename(parse_url($_SERVER['SCRIPT_NAME'], PHP_URL_PATH));
|
|
||||||
$exempt = ['set_email.php', 'change_password.php', 'logout.php'];
|
|
||||||
|
|
||||||
if (in_array($current_script, $exempt, true)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
header('Location: set_email.php');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CSRF-bescherming
|
* CSRF-bescherming
|
||||||
*/
|
*/
|
||||||
@@ -141,18 +118,6 @@ function csrf_verify_header_or_die() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifieert het antwoord op de zelf-gehoste CAPTCHA (captcha.php). Verbruikt het
|
|
||||||
* verwachte antwoord uit de sessie na de eerste check, zodat elke afbeelding maar
|
|
||||||
* eenmaal te gebruiken is (voorkomt hergebruik van hetzelfde plaatje/antwoord).
|
|
||||||
*/
|
|
||||||
function captcha_is_valid($submittedAnswer) {
|
|
||||||
$expected = $_SESSION['captcha_answer'] ?? null;
|
|
||||||
unset($_SESSION['captcha_answer']);
|
|
||||||
|
|
||||||
return $expected !== null && is_string($submittedAnswer) && hash_equals($expected, trim($submittedAnswer));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rate limiting op login
|
* Rate limiting op login
|
||||||
*/
|
*/
|
||||||
@@ -212,18 +177,12 @@ function qr_has_full_visibility() {
|
|||||||
/**
|
/**
|
||||||
* Apply the current session's owner scope to a MysqliDb query builder in place.
|
* Apply the current session's owner scope to a MysqliDb query builder in place.
|
||||||
* No-op when the session has full visibility.
|
* No-op when the session has full visibility.
|
||||||
*
|
|
||||||
* Uses a single raw, parenthesized condition rather than where()+orWhere() -
|
|
||||||
* the previous two-call form produced "WHERE id = ? AND id_owner = ? OR id_owner
|
|
||||||
* IS NULL" whenever a caller had already added its own where('id', ...) (e.g.
|
|
||||||
* qrcode_image.php, bulk_action.php), and AND binds tighter than OR in SQL, so
|
|
||||||
* the OR silently detached from the id filter and matched *any* id_owner-NULL
|
|
||||||
* row instead of the one actually requested.
|
|
||||||
*/
|
*/
|
||||||
function qr_apply_owner_scope($db) {
|
function qr_apply_owner_scope($db) {
|
||||||
$scope_owner_id = qr_scope_owner_id();
|
$scope_owner_id = qr_scope_owner_id();
|
||||||
if ($scope_owner_id !== null) {
|
if ($scope_owner_id !== null) {
|
||||||
$db->where('(id_owner = ' . (int) $scope_owner_id . ' OR id_owner IS NULL)');
|
$db->where('id_owner', $scope_owner_id);
|
||||||
|
$db->orWhere('id_owner', NULL, 'IS');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
<aside class="main-sidebar sidebar-dark-primary elevation-4">
|
<aside class="main-sidebar sidebar-dark-primary elevation-4">
|
||||||
<!-- Brand Logo -->
|
<!-- Brand Logo -->
|
||||||
<a href="./index.php" class="brand-link">
|
<a href="./index.php" class="brand-link">
|
||||||
<img src="dist/img/brand/icon-inverse.svg" alt="QRForge" class="brand-image"
|
<img src="dist/img/Symbol_WhiteBlue.png" alt="Logo" class="brand-image"
|
||||||
style="opacity: .8">
|
style="opacity: .8">
|
||||||
<span class="brand-text font-weight-light">QRForge</span>
|
<span class="brand-text font-weight-light">Qrcode Generator</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
@@ -53,20 +53,20 @@
|
|||||||
<ul class="nav nav-treeview">
|
<ul class="nav nav-treeview">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="./dynamic_qrcodes.php" <?php echo ((substr(CURRENT_PAGE, 0, 19) == 'dynamic_qrcodes.php')) ? ' class="nav-link active"' : ' class="nav-link"'; ?>>
|
<a href="./dynamic_qrcodes.php" <?php echo ((substr(CURRENT_PAGE, 0, 19) == 'dynamic_qrcodes.php')) ? ' class="nav-link active"' : ' class="nav-link"'; ?>>
|
||||||
<i class="fas fa-angle-right nav-icon"></i>
|
<i class="far fa-circle nav-icon"></i>
|
||||||
<p>List all</p>
|
<p>List all</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<?php if ($_SESSION['type'] !== 'user'): ?>
|
<?php if ($_SESSION['type'] !== 'user'): ?>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="./dynamic_qrcode.php" <?php echo (CURRENT_PAGE == 'dynamic_qrcode.php') ? ' class="nav-link active"' : ' class="nav-link"'; ?>>
|
<a href="./dynamic_qrcode.php" <?php echo (CURRENT_PAGE == 'dynamic_qrcode.php') ? ' class="nav-link active"' : ' class="nav-link"'; ?>>
|
||||||
<i class="fas fa-angle-right nav-icon"></i>
|
<i class="far fa-circle nav-icon"></i>
|
||||||
<p>Add new</p>
|
<p>Add new</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="./batch_qrcode.php" <?php echo (CURRENT_PAGE == 'batch_qrcode.php') ? ' class="nav-link active"' : ' class="nav-link"'; ?>>
|
<a href="./batch_qrcode.php" <?php echo (CURRENT_PAGE == 'batch_qrcode.php') ? ' class="nav-link active"' : ' class="nav-link"'; ?>>
|
||||||
<i class="fas fa-angle-right nav-icon"></i>
|
<i class="far fa-circle nav-icon"></i>
|
||||||
<p>Batch create (CSV)</p>
|
<p>Batch create (CSV)</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -86,14 +86,14 @@
|
|||||||
<ul class="nav nav-treeview">
|
<ul class="nav nav-treeview">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="./static_qrcodes.php" <?php echo ((substr(CURRENT_PAGE, 0, 18) == 'static_qrcodes.php')) ? ' class="nav-link active"' : ' class="nav-link"'; ?>>
|
<a href="./static_qrcodes.php" <?php echo ((substr(CURRENT_PAGE, 0, 18) == 'static_qrcodes.php')) ? ' class="nav-link active"' : ' class="nav-link"'; ?>>
|
||||||
<i class="fas fa-angle-right nav-icon"></i>
|
<i class="far fa-circle nav-icon"></i>
|
||||||
<p>List all</p>
|
<p>List all</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<?php if ($_SESSION['type'] !== 'user'): ?>
|
<?php if ($_SESSION['type'] !== 'user'): ?>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="./static_qrcode.php" <?php echo (CURRENT_PAGE == 'static_qrcode.php') ? ' class="nav-link active"' : ' class="nav-link"'; ?>>
|
<a href="./static_qrcode.php" <?php echo (CURRENT_PAGE == 'static_qrcode.php') ? ' class="nav-link active"' : ' class="nav-link"'; ?>>
|
||||||
<i class="fas fa-angle-right nav-icon"></i>
|
<i class="far fa-circle nav-icon"></i>
|
||||||
<p>Add new</p>
|
<p>Add new</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -109,12 +109,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<li class="nav-item">
|
|
||||||
<a href="./about.php" <?php echo (CURRENT_PAGE == 'about.php') ? ' class="nav-link active"' : ' class="nav-link"'; ?>>
|
|
||||||
<i class="fas fa-info-circle nav-icon"></i>
|
|
||||||
<p>About</p>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<!-- /.sidebar-menu -->
|
<!-- /.sidebar-menu -->
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ foreach ($createdQrcode_static as $row) {
|
|||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<title>QRForge</title>
|
<title>Qr Code Generator</title>
|
||||||
<head>
|
<head>
|
||||||
<?php include './includes/head.php'; ?>
|
<?php include './includes/head.php'; ?>
|
||||||
</head>
|
</head>
|
||||||
@@ -120,33 +120,29 @@ foreach ($createdQrcode_static as $row) {
|
|||||||
</div><!-- /.col -->
|
</div><!-- /.col -->
|
||||||
|
|
||||||
<div class="col-12 col-sm-6 col-md-3">
|
<div class="col-12 col-sm-6 col-md-3">
|
||||||
<a href="dynamic_qrcodes.php" class="d-block">
|
<div class="info-box mb-3 bg-success">
|
||||||
<div class="info-box mb-3 bg-success">
|
<span class="info-box-icon"><i class="fa fa-qrcode"></i></span>
|
||||||
<span class="info-box-icon"><i class="fa fa-qrcode"></i></span>
|
|
||||||
|
|
||||||
<div class="info-box-content">
|
<div class="info-box-content">
|
||||||
<span class="info-box-text">Dynamic Qr codes</span>
|
<span class="info-box-text">Dynamic Qr codes</span>
|
||||||
<span class="info-box-number"><?php echo $numQrcode_dynamic; ?></span>
|
<span class="info-box-number"><?php echo $numQrcode_dynamic; ?></span>
|
||||||
</div><!-- /.info-box-content -->
|
</div><!-- /.info-box-content -->
|
||||||
</div>
|
</div>
|
||||||
</a>
|
|
||||||
</div><!-- /.col -->
|
</div><!-- /.col -->
|
||||||
|
|
||||||
<!-- fix for small devices only -->
|
<!-- fix for small devices only -->
|
||||||
<div class="clearfix hidden-md-up"></div>
|
<div class="clearfix hidden-md-up"></div>
|
||||||
|
|
||||||
<div class="col-12 col-sm-6 col-md-3">
|
<div class="col-12 col-sm-6 col-md-3">
|
||||||
<a href="static_qrcodes.php" class="d-block">
|
<div class="info-box mb-3 bg-danger">
|
||||||
<div class="info-box mb-3 bg-danger">
|
<span class="info-box-icon"><i class="fa fa-qrcode"></i></span>
|
||||||
<span class="info-box-icon"><i class="fa fa-qrcode"></i></span>
|
|
||||||
|
|
||||||
<div class="info-box-content">
|
<div class="info-box-content">
|
||||||
<span class="info-box-text">Static QR codes</span>
|
<span class="info-box-text">Static QR codes</span>
|
||||||
<span class="info-box-number"><?php echo $numQrcode_static; ?></span>
|
<span class="info-box-number"><?php echo $numQrcode_static; ?></span>
|
||||||
</div><!-- /.info-box-content -->
|
</div><!-- /.info-box-content -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
|
||||||
</div><!-- /.col -->
|
</div><!-- /.col -->
|
||||||
|
|
||||||
<div class="col-12 col-sm-6 col-md-3">
|
<div class="col-12 col-sm-6 col-md-3">
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once __DIR__.'/../../vendor/autoload.php';
|
|
||||||
|
|
||||||
use PHPMailer\PHPMailer\PHPMailer;
|
|
||||||
use PHPMailer\PHPMailer\Exception as PHPMailerException;
|
|
||||||
|
|
||||||
class Mailer
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Sends a temporary/initial password to a newly created or provisioned account.
|
|
||||||
* Returns true on success, false on failure (never throws - callers decide how to
|
|
||||||
* surface a mail failure without blocking the account creation itself).
|
|
||||||
*/
|
|
||||||
public function sendInitialPassword($toEmail, $tempPassword) {
|
|
||||||
$subject = 'Your ' . MAIL_FROM_NAME . ' account';
|
|
||||||
$body = "An account was created for you.\n\n"
|
|
||||||
. "Email: {$toEmail}\n"
|
|
||||||
. "Temporary password: {$tempPassword}\n\n"
|
|
||||||
. "You'll be asked to set a new password the first time you log in.\n\n"
|
|
||||||
. rtrim(BASE_URL, '/') . "/login.php";
|
|
||||||
|
|
||||||
return $this->send($toEmail, $subject, $body);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function send($toEmail, $subject, $body) {
|
|
||||||
$mail = new PHPMailer(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$mail->isSMTP();
|
|
||||||
$mail->Host = MAIL_HOST;
|
|
||||||
$mail->Port = MAIL_PORT;
|
|
||||||
$mail->SMTPAuth = MAIL_SMTP_AUTH;
|
|
||||||
|
|
||||||
if (MAIL_SMTP_AUTH) {
|
|
||||||
$mail->Username = MAIL_USERNAME;
|
|
||||||
$mail->Password = MAIL_PASSWORD;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MAIL_ENCRYPTION !== '') {
|
|
||||||
$mail->SMTPSecure = MAIL_ENCRYPTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
$mail->setFrom(MAIL_FROM_ADDRESS, MAIL_FROM_NAME);
|
|
||||||
$mail->addAddress($toEmail);
|
|
||||||
$mail->Subject = $subject;
|
|
||||||
$mail->Body = $body;
|
|
||||||
$mail->isHTML(false);
|
|
||||||
|
|
||||||
$mail->send();
|
|
||||||
return true;
|
|
||||||
} catch (PHPMailerException $e) {
|
|
||||||
error_log('Mailer: failed to send to ' . $toEmail . ': ' . $mail->ErrorInfo);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -174,7 +174,7 @@ class Qrcode {
|
|||||||
$iconWidth = imagesx($icon);
|
$iconWidth = imagesx($icon);
|
||||||
$iconHeight = imagesy($icon);
|
$iconHeight = imagesy($icon);
|
||||||
|
|
||||||
$maxIconHeight = (int) ($height * 0.625);
|
$maxIconHeight = (int) ($height * 0.25);
|
||||||
$scale = min($maxIconHeight / $iconHeight, ($width * 0.6) / $iconWidth, 1);
|
$scale = min($maxIconHeight / $iconHeight, ($width * 0.6) / $iconWidth, 1);
|
||||||
$targetWidth = max(1, (int) ($iconWidth * $scale));
|
$targetWidth = max(1, (int) ($iconWidth * $scale));
|
||||||
$targetHeight = max(1, (int) ($iconHeight * $scale));
|
$targetHeight = max(1, (int) ($iconHeight * $scale));
|
||||||
@@ -207,12 +207,9 @@ class Qrcode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getQrcode($id) {
|
public function getQrcode($id) {
|
||||||
require_once BASE_PATH . '/includes/security.php';
|
|
||||||
|
|
||||||
$db = getDbInstance();
|
$db = getDbInstance();
|
||||||
|
|
||||||
$db->where('id', $id);
|
$db->where('id', $id);
|
||||||
qr_apply_owner_scope($db);
|
|
||||||
$result = $db->getOne($this->table);
|
$result = $db->getOne($this->table);
|
||||||
|
|
||||||
if($result !== NULL)
|
if($result !== NULL)
|
||||||
@@ -534,7 +531,6 @@ class Qrcode {
|
|||||||
|
|
||||||
if(!file_exists(SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.'.$old_qrcode["format"]) || $data_to_db['filename'] == $input_data["old_filename"]){
|
if(!file_exists(SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.'.$old_qrcode["format"]) || $data_to_db['filename'] == $input_data["old_filename"]){
|
||||||
$db->where('id', $input_data["id"]);
|
$db->where('id', $input_data["id"]);
|
||||||
qr_apply_owner_scope($db);
|
|
||||||
$stat = $db->update($this->table, $data_to_db);
|
$stat = $db->update($this->table, $data_to_db);
|
||||||
|
|
||||||
try{
|
try{
|
||||||
@@ -567,7 +563,6 @@ class Qrcode {
|
|||||||
$qrcode = $this->getQrcode($id);
|
$qrcode = $this->getQrcode($id);
|
||||||
|
|
||||||
$db->where('id', $id);
|
$db->where('id', $id);
|
||||||
qr_apply_owner_scope($db);
|
|
||||||
$status = $db->delete($this->table);
|
$status = $db->delete($this->table);
|
||||||
|
|
||||||
if ($status) {
|
if ($status) {
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ class Qrcode {
|
|||||||
$iconWidth = imagesx($icon);
|
$iconWidth = imagesx($icon);
|
||||||
$iconHeight = imagesy($icon);
|
$iconHeight = imagesy($icon);
|
||||||
|
|
||||||
$maxIconHeight = (int) ($height * 0.625);
|
$maxIconHeight = (int) ($height * 0.25);
|
||||||
$scale = min($maxIconHeight / $iconHeight, ($width * 0.6) / $iconWidth, 1);
|
$scale = min($maxIconHeight / $iconHeight, ($width * 0.6) / $iconWidth, 1);
|
||||||
$targetWidth = max(1, (int) ($iconWidth * $scale));
|
$targetWidth = max(1, (int) ($iconWidth * $scale));
|
||||||
$targetHeight = max(1, (int) ($iconHeight * $scale));
|
$targetHeight = max(1, (int) ($iconHeight * $scale));
|
||||||
@@ -192,12 +192,9 @@ class Qrcode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getQrcode($id) {
|
public function getQrcode($id) {
|
||||||
require_once BASE_PATH . '/includes/security.php';
|
|
||||||
|
|
||||||
$db = getDbInstance();
|
$db = getDbInstance();
|
||||||
|
|
||||||
$db->where('id', $id);
|
$db->where('id', $id);
|
||||||
qr_apply_owner_scope($db);
|
|
||||||
$result = $db->getOne($this->table);
|
$result = $db->getOne($this->table);
|
||||||
|
|
||||||
if($result !== NULL)
|
if($result !== NULL)
|
||||||
@@ -379,7 +376,6 @@ class Qrcode {
|
|||||||
|
|
||||||
if(!file_exists(SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.'.$old_qrcode["format"]) || $data_to_db['filename'] == $input_data["old_filename"]){
|
if(!file_exists(SAVED_QRCODE_DIRECTORY.$data_to_db['filename'].'.'.$old_qrcode["format"]) || $data_to_db['filename'] == $input_data["old_filename"]){
|
||||||
$db->where('id', $input_data["id"]);
|
$db->where('id', $input_data["id"]);
|
||||||
qr_apply_owner_scope($db);
|
|
||||||
$stat = $db->update($this->table, $data_to_db);
|
$stat = $db->update($this->table, $data_to_db);
|
||||||
|
|
||||||
try{
|
try{
|
||||||
@@ -412,7 +408,6 @@ class Qrcode {
|
|||||||
$qrcode = $this->getQrcode($id);
|
$qrcode = $this->getQrcode($id);
|
||||||
|
|
||||||
$db->where('id', $id);
|
$db->where('id', $id);
|
||||||
qr_apply_owner_scope($db);
|
|
||||||
$status = $db->delete($this->table);
|
$status = $db->delete($this->table);
|
||||||
|
|
||||||
if ($status) {
|
if ($status) {
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ class StaticQrcode {
|
|||||||
* create a qr code of type "vcard"
|
* create a qr code of type "vcard"
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public function vcardQrcode($fullname, $nickname, $email, $website, $phone, $home_phone, $work_phone, $company, $role, $categories, $note, $photo, $address, $city, $postcode, $state, $country)
|
public function vcardQrcode($fullname, $nickname, $email, $website, $phone, $home_phone, $work_phone, $company, $role, $categories, $note, $photo, $address, $city, $postcode, $state)
|
||||||
{
|
{
|
||||||
if($fullname != NULL && $phone != NULL){
|
if($fullname != NULL && $phone != NULL){
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ class StaticQrcode {
|
|||||||
$vcard->categories($categories);
|
$vcard->categories($categories);
|
||||||
$vcard->note($note);
|
$vcard->note($note);
|
||||||
$vcard->photo($photo);
|
$vcard->photo($photo);
|
||||||
$vcard->address($address, $city, $state, $postcode, $country);
|
$vcard->address($address, $city, $postcode, $state);
|
||||||
$vcard->create();
|
$vcard->create();
|
||||||
|
|
||||||
$this->sData = $vcard->get();
|
$this->sData = $vcard->get();
|
||||||
@@ -202,7 +202,7 @@ class StaticQrcode {
|
|||||||
|
|
||||||
$this->sContent .= '<div class="col-sm-4">';
|
$this->sContent .= '<div class="col-sm-4">';
|
||||||
|
|
||||||
$this->sContent .= '<strong>Phone:</strong> '.$phone.'<br>'.'<strong>Home Phone:</strong> '.$home_phone.'<br>'.'<strong>Work phone:</strong> '.$work_phone.'<br>'.'<strong>Address:</strong> '.$address.' '.$city.' '.$postcode.' '.$state.' '.$country.'</div>';
|
$this->sContent .= '<strong>Phone:</strong> '.$phone.'<br>'.'<strong>Home Phone:</strong> '.$home_phone.'<br>'.'<strong>Work phone:</strong> '.$work_phone.'<br>'.'<strong>Address:</strong> '.$address.' '.$city.' '.$postcode.' '.$state.'</div>';
|
||||||
|
|
||||||
$this->sContent .= '</div>';
|
$this->sContent .= '</div>';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once 'config/config.php';
|
require_once 'config/config.php';
|
||||||
require_once BASE_PATH . '/lib/Mailer/Mailer.php';
|
|
||||||
|
|
||||||
class Users
|
class Users
|
||||||
{
|
{
|
||||||
@@ -32,23 +31,6 @@ class Users
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Server-side validation of the (optional, for admin-created accounts) email address.
|
|
||||||
* Empty is allowed here - an account without one gets must_set_email=1, same as a
|
|
||||||
* pre-migration legacy account (see the callers below).
|
|
||||||
*/
|
|
||||||
private function validateEmail($email) {
|
|
||||||
if ($email === '' || $email === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_string($email) || strlen($email) > 255 || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
||||||
return 'Please enter a valid email address.';
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@@ -131,19 +113,11 @@ class Users
|
|||||||
$this->failure($validation_error, 'Location: user.php');
|
$this->failure($validation_error, 'Location: user.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
$email = trim($input_data['email'] ?? '');
|
|
||||||
$email_error = $this->validateEmail($email);
|
|
||||||
if ($email_error !== null) {
|
|
||||||
$this->failure($email_error, 'Location: user.php');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($input_data['password']) || strlen($input_data['password']) < 10) {
|
if (!isset($input_data['password']) || strlen($input_data['password']) < 10) {
|
||||||
$this->failure('Password must be at least 10 characters long.', 'Location: user.php');
|
$this->failure('Password must be at least 10 characters long.', 'Location: user.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
$data_to_db["username"] = $input_data["username"];
|
$data_to_db["username"] = $input_data["username"];
|
||||||
$data_to_db["email"] = $email !== '' ? $email : null;
|
|
||||||
$data_to_db['must_set_email'] = $email === '' ? 1 : 0;
|
|
||||||
$data_to_db['password'] = password_hash($input_data['password'], PASSWORD_DEFAULT);
|
$data_to_db['password'] = password_hash($input_data['password'], PASSWORD_DEFAULT);
|
||||||
$data_to_db["type"] = $requested_type;
|
$data_to_db["type"] = $requested_type;
|
||||||
$data_to_db['owner_admin_id'] = $owner_admin_id;
|
$data_to_db['owner_admin_id'] = $owner_admin_id;
|
||||||
@@ -156,16 +130,6 @@ class Users
|
|||||||
if ($db->count >= 1)
|
if ($db->count >= 1)
|
||||||
$this->failure('Username already exists');
|
$this->failure('Username already exists');
|
||||||
|
|
||||||
if ($email !== '') {
|
|
||||||
$db = getDbInstance();
|
|
||||||
$db->where('email', $email);
|
|
||||||
$db->get('users');
|
|
||||||
|
|
||||||
if ($db->count >= 1)
|
|
||||||
$this->failure('An account with this email already exists', 'Location: user.php');
|
|
||||||
}
|
|
||||||
|
|
||||||
$db = getDbInstance();
|
|
||||||
$last_id = $db->insert('users', $data_to_db);
|
$last_id = $db->insert('users', $data_to_db);
|
||||||
|
|
||||||
if ($last_id) {
|
if ($last_id) {
|
||||||
@@ -173,90 +137,7 @@ class Users
|
|||||||
$this->success('User added successfully');
|
$this->success('User added successfully');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Public self-registration (register.php). No session/permission checks - this is
|
|
||||||
* the one path where an unauthenticated visitor creates their own account. Always
|
|
||||||
* creates a free-forever 'admin' (self-scoped, no tenant), matching what a manually
|
|
||||||
* created OSS admin gets. Returns ['ok' => true] on success or
|
|
||||||
* ['ok' => false, 'error' => string] - callers are responsible for flash/redirect,
|
|
||||||
* unlike addUser()/editUser() which redirect themselves (this runs pre-login, on a
|
|
||||||
* page with its own layout).
|
|
||||||
*/
|
|
||||||
public function registerSelfUser($email) {
|
|
||||||
if (!ALLOW_SELF_REGISTRATION) {
|
|
||||||
return ['ok' => false, 'error' => 'Self-registration is not enabled.'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
||||||
return ['ok' => false, 'error' => 'Please enter a valid email address.'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$db = getDbInstance();
|
|
||||||
$db->where('email', $email);
|
|
||||||
$existing = $db->getOne('users');
|
|
||||||
|
|
||||||
if (!empty($existing)) {
|
|
||||||
return ['ok' => false, 'error' => 'An account with this email already exists.'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$username = $this->deriveUniqueUsername($email);
|
|
||||||
$tempPassword = bin2hex(random_bytes(8));
|
|
||||||
|
|
||||||
$data_to_db = [
|
|
||||||
'username' => $username,
|
|
||||||
'password' => password_hash($tempPassword, PASSWORD_DEFAULT),
|
|
||||||
'type' => 'admin',
|
|
||||||
'owner_admin_id' => null,
|
|
||||||
'email' => $email,
|
|
||||||
'must_change_password' => 1,
|
|
||||||
'self_registered_at' => date('Y-m-d H:i:s'),
|
|
||||||
];
|
|
||||||
|
|
||||||
$db = getDbInstance();
|
|
||||||
$last_id = $db->insert('users', $data_to_db);
|
|
||||||
|
|
||||||
if (!$last_id) {
|
|
||||||
return ['ok' => false, 'error' => 'Could not create the account: ' . $db->getLastError()];
|
|
||||||
}
|
|
||||||
|
|
||||||
audit_log('user_self_registered', 'user', $last_id);
|
|
||||||
|
|
||||||
$mailer = new Mailer();
|
|
||||||
$mailer->sendInitialPassword($email, $tempPassword);
|
|
||||||
|
|
||||||
return ['ok' => true];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derives a username candidate from the email's local part (letters/digits/dot/
|
|
||||||
* underscore/hyphen only, matching validateUsernameAndType()'s rules), appending a
|
|
||||||
* numeric suffix if it's already taken.
|
|
||||||
*/
|
|
||||||
private function deriveUniqueUsername($email) {
|
|
||||||
$localPart = strtolower(strstr($email, '@', true) ?: $email);
|
|
||||||
$base = preg_replace('/[^a-z0-9._-]/', '', $localPart);
|
|
||||||
$base = substr($base, 0, 45) ?: 'user';
|
|
||||||
|
|
||||||
if (strlen($base) < 3) {
|
|
||||||
$base = str_pad($base, 3, '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
$candidate = $base;
|
|
||||||
$suffix = 1;
|
|
||||||
|
|
||||||
$db = getDbInstance();
|
|
||||||
$db->where('username', $candidate);
|
|
||||||
while ($db->getOne('users') !== null) {
|
|
||||||
$candidate = $base . $suffix;
|
|
||||||
$suffix++;
|
|
||||||
$db = getDbInstance();
|
|
||||||
$db->where('username', $candidate);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $candidate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edit user.
|
* Edit user.
|
||||||
*
|
*
|
||||||
@@ -279,26 +160,13 @@ class Users
|
|||||||
'edit' => "true",
|
'edit' => "true",
|
||||||
));
|
));
|
||||||
|
|
||||||
$is_self_edit = (int) $input_data['id'] === (int) $_SESSION['user_id'];
|
$requested_type = $_SESSION['type'] === 'admin' ? 'user' : ($input_data['type'] ?? '');
|
||||||
|
|
||||||
// A user editing their own account keeps their current type, even if a
|
|
||||||
// different value was submitted - prevents accidentally (or deliberately)
|
|
||||||
// locking yourself out by downgrading your own access level.
|
|
||||||
$requested_type = $_SESSION['type'] === 'admin'
|
|
||||||
? 'user'
|
|
||||||
: ($is_self_edit ? $target['type'] : ($input_data['type'] ?? ''));
|
|
||||||
|
|
||||||
$validation_error = $this->validateUsernameAndType($input_data['username'] ?? '', $requested_type);
|
$validation_error = $this->validateUsernameAndType($input_data['username'] ?? '', $requested_type);
|
||||||
if ($validation_error !== null) {
|
if ($validation_error !== null) {
|
||||||
$this->failure($validation_error, 'Location: user.php?'.$query_string);
|
$this->failure($validation_error, 'Location: user.php?'.$query_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
$email = trim($input_data['email'] ?? '');
|
|
||||||
$email_error = $this->validateEmail($email);
|
|
||||||
if ($email_error !== null) {
|
|
||||||
$this->failure($email_error, 'Location: user.php?'.$query_string);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($input_data['password']) && strlen($input_data['password']) > 0 && strlen($input_data['password']) < 10) {
|
if (isset($input_data['password']) && strlen($input_data['password']) > 0 && strlen($input_data['password']) < 10) {
|
||||||
$this->failure('Password must be at least 10 characters long.', 'Location: user.php?'.$query_string);
|
$this->failure('Password must be at least 10 characters long.', 'Location: user.php?'.$query_string);
|
||||||
}
|
}
|
||||||
@@ -312,25 +180,8 @@ class Users
|
|||||||
$this->failure('Username already exists', 'Location: user.php?'.$query_string);
|
$this->failure('Username already exists', 'Location: user.php?'.$query_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($email !== '') {
|
|
||||||
$db = getDbInstance();
|
|
||||||
$db->where('email', $email);
|
|
||||||
$db->where('id', $input_data["id"], '!=');
|
|
||||||
$row = $db->getOne('users');
|
|
||||||
|
|
||||||
if (!empty($row['email'])) {
|
|
||||||
$this->failure('An account with this email already exists', 'Location: user.php?'.$query_string);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$data_to_db["username"] = $input_data["username"];
|
$data_to_db["username"] = $input_data["username"];
|
||||||
$data_to_db["type"] = $requested_type;
|
$data_to_db["type"] = $requested_type;
|
||||||
// Only touch email/must_set_email if an email was actually submitted - an admin
|
|
||||||
// leaving the field blank on an already-set account shouldn't wipe it back out.
|
|
||||||
if ($email !== '') {
|
|
||||||
$data_to_db['email'] = $email;
|
|
||||||
$data_to_db['must_set_email'] = 0;
|
|
||||||
}
|
|
||||||
$data_to_db['can_view_static'] = !empty($input_data['can_view_static']) ? 1 : 0;
|
$data_to_db['can_view_static'] = !empty($input_data['can_view_static']) ? 1 : 0;
|
||||||
$data_to_db['can_view_dynamic'] = !empty($input_data['can_view_dynamic']) ? 1 : 0;
|
$data_to_db['can_view_dynamic'] = !empty($input_data['can_view_dynamic']) ? 1 : 0;
|
||||||
|
|
||||||
|
|||||||
@@ -43,11 +43,10 @@ class vCard
|
|||||||
*
|
*
|
||||||
* @return self
|
* @return self
|
||||||
*/
|
*/
|
||||||
public function address($sAddress, $sCity, $sState, $sPostcode, $sCountry)
|
public function address($sAddress, $sCity, $sPostcode, $sState)
|
||||||
{
|
{
|
||||||
// Component order per vCard 4.0 ADR: pobox;ext;street;locality;region;code;country
|
|
||||||
$this->sData .= 'ADR:;;'.$sAddress.';';
|
$this->sData .= 'ADR:;;'.$sAddress.';';
|
||||||
$this->sData .= $sCity.';'.$sState.';'.$sPostcode.';'.$sCountry."\n";
|
$this->sData .= $sCity.';'.$sPostcode.';'.$sState."\n";
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ if (isset($_COOKIE['series_id']) && isset($_COOKIE['remember_token']))
|
|||||||
$_SESSION['type'] = $row['type'];
|
$_SESSION['type'] = $row['type'];
|
||||||
$_SESSION['username'] = $row['username'];
|
$_SESSION['username'] = $row['username'];
|
||||||
$_SESSION['must_change_password'] = !empty($row['must_change_password']);
|
$_SESSION['must_change_password'] = !empty($row['must_change_password']);
|
||||||
$_SESSION['must_set_email'] = !empty($row['must_set_email']);
|
|
||||||
$_SESSION['can_view_static'] = !empty($row['can_view_static']);
|
$_SESSION['can_view_static'] = !empty($row['can_view_static']);
|
||||||
$_SESSION['can_view_dynamic'] = !empty($row['can_view_dynamic']);
|
$_SESSION['can_view_dynamic'] = !empty($row['can_view_dynamic']);
|
||||||
$_SESSION['scope_owner_id'] = qr_compute_scope_owner_id($row);
|
$_SESSION['scope_owner_id'] = qr_compute_scope_owner_id($row);
|
||||||
@@ -69,25 +68,23 @@ if (isset($_COOKIE['series_id']) && isset($_COOKIE['remember_token']))
|
|||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<title>Login - QRForge</title>
|
<title>Login - Qrcode Generator</title>
|
||||||
<?php include './includes/head.php'; ?>
|
<?php include './includes/head.php'; ?>
|
||||||
|
|
||||||
<body class="login-page" style="min-height: 512.391px;">
|
<body class="login-page" style="min-height: 512.391px;">
|
||||||
<div class="login-box">
|
<div class="login-box">
|
||||||
<div class="login-logo">
|
<div class="login-logo">
|
||||||
<img src="dist/img/brand/logo.svg" alt="QRForge" style="max-width: 260px;">
|
<img src="dist/img/DynamicQRCode_Original.png" style="width: 95%; height: 95%">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body login-card-body">
|
<div class="card-body login-card-body">
|
||||||
<p class="login-box-msg">Sign in to start your session</p>
|
<p class="login-box-msg">Sign in to start your session</p>
|
||||||
|
|
||||||
<?php include './includes/flash_messages.php'; ?>
|
|
||||||
|
|
||||||
<form method="POST" action="authenticate.php">
|
<form method="POST" action="authenticate.php">
|
||||||
<?php echo csrf_field(); ?>
|
<?php echo csrf_field(); ?>
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<input type="text" name="email" class="form-control" placeholder="Email" required="required">
|
<input type="text" name="username" class="form-control" placeholder="Username" required="required">
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<div class="input-group-text">
|
<div class="input-group-text">
|
||||||
<span class="fa fa-user"></span>
|
<span class="fa fa-user"></span>
|
||||||
@@ -135,11 +132,8 @@ if (isset($_COOKIE['series_id']) && isset($_COOKIE['remember_token']))
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (ALLOW_SELF_REGISTRATION): ?>
|
|
||||||
<p class="mt-3 text-center"><a href="register.php">Register for free</a></p>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- /.login-card-body -->
|
<!-- /.login-card-body -->
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "QRForge",
|
"name": "Qrcode Generator",
|
||||||
"short_name": "QRForge",
|
"short_name": "QRcode",
|
||||||
"description": "Self-hosted static and dynamic qr code generator",
|
"description": "Self-hosted static and dynamic qr code generator",
|
||||||
"start_url": "index.php",
|
"start_url": "index.php",
|
||||||
"scope": "./",
|
"scope": "./",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#ffffff",
|
||||||
"theme_color": "#2563EB",
|
"theme_color": "#007bff",
|
||||||
"icons": [
|
"icons": [
|
||||||
{ "src": "dist/img/icon-192.png", "sizes": "192x192", "type": "image/png" },
|
{ "src": "dist/img/icon-192.png", "sizes": "192x192", "type": "image/png" },
|
||||||
{ "src": "dist/img/icon-512.png", "sizes": "512x512", "type": "image/png" }
|
{ "src": "dist/img/icon-512.png", "sizes": "512x512", "type": "image/png" }
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'includes/bootstrap.php';
|
|
||||||
require_once 'lib/Users/Users.php';
|
|
||||||
|
|
||||||
if (!ALLOW_SELF_REGISTRATION) {
|
|
||||||
header('Location: login.php');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($_SESSION['user_logged_in']) && $_SESSION['user_logged_in'] === TRUE) {
|
|
||||||
header('Location: index.php');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
csrf_verify_or_die();
|
|
||||||
|
|
||||||
$email = trim($_POST['email'] ?? '');
|
|
||||||
|
|
||||||
if (!captcha_is_valid($_POST['captcha'] ?? '')) {
|
|
||||||
$_SESSION['failure'] = 'Incorrect CAPTCHA answer, please try again.';
|
|
||||||
} else {
|
|
||||||
$users = new Users();
|
|
||||||
$result = $users->registerSelfUser($email);
|
|
||||||
|
|
||||||
if ($result['ok']) {
|
|
||||||
$_SESSION['success'] = 'Account created! Check your inbox for a temporary password.';
|
|
||||||
header('Location: login.php');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$_SESSION['failure'] = $result['error'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<title>Register - QRForge</title>
|
|
||||||
<?php include './includes/head.php'; ?>
|
|
||||||
|
|
||||||
<body class="login-page" style="min-height: 512.391px;">
|
|
||||||
<div class="login-box">
|
|
||||||
<div class="login-logo">
|
|
||||||
<img src="dist/img/brand/logo.svg" alt="QRForge" style="max-width: 260px;">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body login-card-body">
|
|
||||||
<p class="login-box-msg">Create your free account</p>
|
|
||||||
|
|
||||||
<?php include './includes/flash_messages.php'; ?>
|
|
||||||
|
|
||||||
<form method="POST" action="register.php">
|
|
||||||
<?php echo csrf_field(); ?>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input type="email" name="email" class="form-control" placeholder="Email address" required="required">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3 text-center">
|
|
||||||
<img src="captcha.php" alt="CAPTCHA" id="captcha-image" style="cursor:pointer;" title="Click to refresh">
|
|
||||||
</div>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input type="text" name="captcha" class="form-control" placeholder="Answer the sum above" required="required" autocomplete="off">
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<button type="submit" class="btn btn-primary btn-block">Create account</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<p class="mt-3 text-center"><a href="login.php">Back to login</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="../../plugins/jquery/jquery.min.js"></script>
|
|
||||||
<script src="../../plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
|
|
||||||
<script src="../../dist/js/adminlte.js"></script>
|
|
||||||
<script>
|
|
||||||
document.getElementById('captcha-image').addEventListener('click', function () {
|
|
||||||
this.src = 'captcha.php?' + Date.now();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -4,7 +4,7 @@ require_once BASE_PATH . '/includes/auth_validate.php';
|
|||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<title>QRForge</title>
|
<title>Qrcode Generator</title>
|
||||||
<head>
|
<head>
|
||||||
<?php include './includes/head.php'; ?>
|
<?php include './includes/head.php'; ?>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Lightweight migration runner: applies any db/migrations/*.sql file not yet recorded
|
|
||||||
// in schema_migrations, in filename order. Every migration file is itself idempotent
|
|
||||||
// (checks information_schema before altering), so re-running an already-applied file
|
|
||||||
// is a safe no-op - this script leans on that instead of needing transactional rollback.
|
|
||||||
// Run automatically by the container entrypoint on every start (see docker/entrypoint.sh),
|
|
||||||
// so a `git pull` + restart is enough to bring an existing install's schema up to date -
|
|
||||||
// docker-entrypoint-initdb.d only ever runs db/init.sql, and only on a brand new volume.
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../config/environment.php';
|
|
||||||
|
|
||||||
mysqli_report(MYSQLI_REPORT_OFF);
|
|
||||||
|
|
||||||
$mysqli = new mysqli(DATABASE_HOST, DATABASE_USER, DATABASE_PASSWORD, DATABASE_NAME, DATABASE_PORT);
|
|
||||||
if ($mysqli->connect_errno) {
|
|
||||||
fwrite(STDERR, "migrate.php: could not connect to database: {$mysqli->connect_error}\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
$mysqli->set_charset(DATABASE_CHARSET);
|
|
||||||
|
|
||||||
$mysqli->query(
|
|
||||||
'CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
||||||
filename VARCHAR(255) NOT NULL PRIMARY KEY,
|
|
||||||
applied_at DATETIME NOT NULL
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8'
|
|
||||||
);
|
|
||||||
|
|
||||||
$files = glob(__DIR__ . '/../db/migrations/*.sql');
|
|
||||||
sort($files, SORT_STRING);
|
|
||||||
|
|
||||||
$applied = [];
|
|
||||||
$result = $mysqli->query('SELECT filename FROM schema_migrations');
|
|
||||||
while ($row = $result->fetch_assoc()) {
|
|
||||||
$applied[$row['filename']] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$ran = 0;
|
|
||||||
foreach ($files as $file) {
|
|
||||||
$filename = basename($file);
|
|
||||||
if (isset($applied[$filename])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Applying migration: $filename\n";
|
|
||||||
|
|
||||||
if (!$mysqli->multi_query(file_get_contents($file))) {
|
|
||||||
fwrite(STDERR, "migrate.php: failed to apply $filename: {$mysqli->error}\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
// multi_query queues result sets asynchronously - drain them all before the next
|
|
||||||
// query, and check for a mid-batch error on each one.
|
|
||||||
do {
|
|
||||||
if ($res = $mysqli->store_result()) {
|
|
||||||
$res->free();
|
|
||||||
}
|
|
||||||
if ($mysqli->errno) {
|
|
||||||
fwrite(STDERR, "migrate.php: error while applying $filename: {$mysqli->error}\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
} while ($mysqli->more_results() && $mysqli->next_result());
|
|
||||||
|
|
||||||
$stmt = $mysqli->prepare('INSERT INTO schema_migrations (filename, applied_at) VALUES (?, NOW())');
|
|
||||||
$stmt->bind_param('s', $filename);
|
|
||||||
$stmt->execute();
|
|
||||||
$stmt->close();
|
|
||||||
|
|
||||||
$ran++;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo $ran === 0 ? "No pending migrations.\n" : "Applied $ran migration(s).\n";
|
|
||||||
|
|
||||||
$mysqli->close();
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<?php
|
|
||||||
// Lightweight keep-alive endpoint: including bootstrap.php refreshes
|
|
||||||
// $_SESSION['last_activity'], extending the idle timeout without navigating
|
|
||||||
// away from (and losing) whatever form the user is currently filling in.
|
|
||||||
// Deliberately doesn't use auth_validate.php's redirect-to-login-on-failure
|
|
||||||
// behavior: this is called from JS, and a 401 lets the caller show "your
|
|
||||||
// session already expired" instead of silently following a redirect.
|
|
||||||
require_once 'includes/bootstrap.php';
|
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
|
|
||||||
if (empty($_SESSION['user_logged_in'])) {
|
|
||||||
http_response_code(401);
|
|
||||||
echo json_encode(['ok' => false]);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo json_encode(['ok' => true]);
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
<?php
|
|
||||||
require_once 'includes/bootstrap.php';
|
|
||||||
|
|
||||||
if (empty($_SESSION['user_logged_in'])) {
|
|
||||||
header('Location: login.php');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$forced = !empty($_SESSION['must_set_email']);
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
csrf_verify_or_die();
|
|
||||||
|
|
||||||
$email = trim($_POST['email'] ?? '');
|
|
||||||
|
|
||||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
||||||
$_SESSION['failure'] = 'Please enter a valid email address.';
|
|
||||||
} else {
|
|
||||||
$db = getDbInstance();
|
|
||||||
$db->where('email', $email);
|
|
||||||
$db->where('id', $_SESSION['user_id'], '!=');
|
|
||||||
$existing = $db->getOne('users');
|
|
||||||
|
|
||||||
if (!empty($existing['email'])) {
|
|
||||||
$_SESSION['failure'] = 'An account with this email already exists.';
|
|
||||||
} else {
|
|
||||||
$db = getDbInstance();
|
|
||||||
$db->where('id', $_SESSION['user_id']);
|
|
||||||
$db->update('users', [
|
|
||||||
'email' => $email,
|
|
||||||
'must_set_email' => 0,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$_SESSION['must_set_email'] = false;
|
|
||||||
audit_log('email_set');
|
|
||||||
|
|
||||||
$_SESSION['success'] = 'Email address saved.';
|
|
||||||
header('Location: index.php');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<title>Set email - QRForge</title>
|
|
||||||
<?php include './includes/head.php'; ?>
|
|
||||||
|
|
||||||
<body class="login-page" style="min-height: 512.391px;">
|
|
||||||
<div class="login-box">
|
|
||||||
<div class="login-logo">
|
|
||||||
<img src="dist/img/brand/logo.svg" alt="QRForge" style="max-width: 260px;">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body login-card-body">
|
|
||||||
<p class="login-box-msg">
|
|
||||||
<?php echo $forced
|
|
||||||
? 'Please set an email address for your account before continuing. This will become your login.'
|
|
||||||
: 'Set your email address'; ?>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<?php include './includes/flash_messages.php'; ?>
|
|
||||||
|
|
||||||
<form method="POST" action="set_email.php">
|
|
||||||
<?php echo csrf_field(); ?>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input type="email" name="email" class="form-control" placeholder="Email address" required="required">
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<button type="submit" class="btn btn-primary btn-block">Save email</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<?php if (!$forced): ?>
|
|
||||||
<p class="mt-3 text-center"><a href="index.php">Back to dashboard</a></p>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="../../plugins/jquery/jquery.min.js"></script>
|
|
||||||
<script src="../../plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
|
|
||||||
<script src="../../dist/js/adminlte.js"></script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -65,7 +65,7 @@ if($_SERVER["REQUEST_METHOD"] === "POST" && !isset($_POST["edit"]) && !isset($_P
|
|||||||
case 'location': $static_qrcode_instance->locationQrcode($_POST['latitude'], $_POST['longitude']);
|
case 'location': $static_qrcode_instance->locationQrcode($_POST['latitude'], $_POST['longitude']);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'vcard': $static_qrcode_instance->vcardQrcode($_POST['full_name'], $_POST['nickname'], $_POST['email'], $_POST['website'], $_POST['phone'], $_POST['home_phone'], $_POST['work_phone'], $_POST['company'], $_POST['role'], $_POST['categories'], $_POST['note'], $_POST['photo'], $_POST['address'], $_POST['city'], $_POST['post_code'], $_POST['state'], $_POST['country']);
|
case 'vcard': $static_qrcode_instance->vcardQrcode($_POST['full_name'], $_POST['nickname'], $_POST['email'], $_POST['website'], $_POST['phone'], $_POST['home_phone'], $_POST['work_phone'], $_POST['company'], $_POST['role'], $_POST['categories'], $_POST['note'], $_POST['photo'], $_POST['address'], $_POST['city'], $_POST['post_code'], $_POST['state']);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'event': $static_qrcode_instance->eventQrcode($_POST['title'], $_POST['start'], $_POST['end'], $_POST['timezone'], $_POST['location'], $_POST['description'], $_POST['url']);
|
case 'event': $static_qrcode_instance->eventQrcode($_POST['title'], $_POST['start'], $_POST['end'], $_POST['timezone'], $_POST['location'], $_POST['description'], $_POST['url']);
|
||||||
@@ -96,7 +96,7 @@ if($_SERVER["REQUEST_METHOD"] === "POST" && !isset($_POST["edit"]) && !isset($_P
|
|||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<title>QRForge</title>
|
<title>Qrcode Generator</title>
|
||||||
<head>
|
<head>
|
||||||
<?php include './includes/head.php'; ?>
|
<?php include './includes/head.php'; ?>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ $total_pages = $db->totalPages;
|
|||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<title>QRForge</title>
|
<title>Qrcode Generator</title>
|
||||||
<head>
|
<head>
|
||||||
<?php include './includes/head.php'; ?>
|
<?php include './includes/head.php'; ?>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ if($_SERVER["REQUEST_METHOD"] === "POST" && !isset($_POST["edit"])) {
|
|||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<title>QRForge</title>
|
<title>Qrcode Generator</title>
|
||||||
<head>
|
<head>
|
||||||
<?php include './includes/head.php'; ?>
|
<?php include './includes/head.php'; ?>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ $users = new Users();
|
|||||||
if (!in_array($_SESSION['type'], ['super', 'admin'], true))
|
if (!in_array($_SESSION['type'], ['super', 'admin'], true))
|
||||||
$users->failure('Only "super admin" and "admin" accounts can access the user management page', 'Location: index.php');
|
$users->failure('Only "super admin" and "admin" accounts can access the user management page', 'Location: index.php');
|
||||||
|
|
||||||
$select = array('id', 'username', 'email', 'type');
|
$select = array('id', 'username', 'type');
|
||||||
$search_fields = array('username');
|
$search_fields = array('username');
|
||||||
require_once BASE_PATH . '/includes/search_order.php';
|
require_once BASE_PATH . '/includes/search_order.php';
|
||||||
$page = filter_input(INPUT_GET, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? 1;
|
$page = filter_input(INPUT_GET, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? 1;
|
||||||
@@ -27,7 +27,7 @@ $total_pages = $db->totalPages;
|
|||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<title>QRForge</title>
|
<title>Qrcode Generator</title>
|
||||||
<head>
|
<head>
|
||||||
<?php include './includes/head.php'; ?>
|
<?php include './includes/head.php'; ?>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||