Compare commits
11 Commits
4fce91b055
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b6d4c49f5 | |||
| 9094b58b6d | |||
| 1a30c45d62 | |||
| 3a99bd6683 | |||
| 60bd4ae03d | |||
| cf634bd788 | |||
| 06991a02ad | |||
| cee7246896 | |||
| 330c09d2af | |||
| 823154ef9c | |||
| 9a888d14c3 |
15
.gitignore
vendored
15
.gitignore
vendored
@@ -4,4 +4,17 @@
|
||||
# don't commit all the dependencies fetched via Composer
|
||||
/vendor/
|
||||
|
||||
.idea/
|
||||
./.idea
|
||||
./.idea/*
|
||||
./.crush
|
||||
/AGENTS.md
|
||||
/composer.lock
|
||||
/.idea/copilot.data.migration.agent.xml
|
||||
/.idea/copilot.data.migration.ask.xml
|
||||
/.idea/copilot.data.migration.ask2agent.xml
|
||||
/.idea/copilot.data.migration.edit.xml
|
||||
/.idea/php.xml
|
||||
/.idea/phpunit.xml
|
||||
/PROFILES_README.md
|
||||
/.idea/vcs.xml
|
||||
/.idea/workspace.xml
|
||||
|
||||
52
.htaccess
Normal file
52
.htaccess
Normal file
@@ -0,0 +1,52 @@
|
||||
# This file is - if you set up HUGE correctly - not needed.
|
||||
# But, for fallback reasons (if you don't route your vhost to /public), it will stay here.
|
||||
RewriteEngine on
|
||||
RewriteRule ^(.*) public/$1 [L]
|
||||
|
||||
# Everything from is for browser caching and is totally optional
|
||||
|
||||
# Deflate Compression by FileType
|
||||
<IfModule mod_deflate.c>
|
||||
AddOutputFilterByType DEFLATE text/plain
|
||||
AddOutputFilterByType DEFLATE text/html
|
||||
AddOutputFilterByType DEFLATE text/xml
|
||||
AddOutputFilterByType DEFLATE text/css
|
||||
AddOutputFilterByType DEFLATE text/javascript
|
||||
AddOutputFilterByType DEFLATE application/xml
|
||||
AddOutputFilterByType DEFLATE application/xhtml+xml
|
||||
AddOutputFilterByType DEFLATE application/rss+xml
|
||||
AddOutputFilterByType DEFLATE application/atom_xml
|
||||
AddOutputFilterByType DEFLATE application/javascript
|
||||
AddOutputFilterByType DEFLATE application/x-javascript
|
||||
AddOutputFilterByType DEFLATE application/x-shockwave-flash
|
||||
</IfModule>
|
||||
|
||||
# Set browser caching to 1 month
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive On
|
||||
ExpiresByType text/css "access plus 1 month"
|
||||
ExpiresByType text/javascript "access plus 1 month"
|
||||
ExpiresByType text/html "access plus 1 month"
|
||||
ExpiresByType application/javascript "access plus 1 month"
|
||||
ExpiresByType image/gif "access plus 1 month"
|
||||
ExpiresByType image/jpeg "access plus 1 month"
|
||||
ExpiresByType image/png "access plus 1 month"
|
||||
ExpiresByType image/x-icon "access plus 1 month"
|
||||
</IfModule>
|
||||
|
||||
<ifmodule mod_headers.c>
|
||||
# if you want to prevent your site from being embedded into other sites via an iframe (sometimes used for scam), then
|
||||
# simply uncomment these lines below. you need to have apache rewrite headers activated, usually via
|
||||
# "a2enmod rewrite headers" on the command line
|
||||
#Header set X-Frame-Options Deny
|
||||
#Header always append X-Frame-Options SAMEORIGIN
|
||||
<filesmatch "\\.(ico|jpe?g|png|gif|swf)$">
|
||||
Header set Cache-Control "max-age=2592000, public"
|
||||
</filesmatch>
|
||||
<filesmatch "\\.(css)$">
|
||||
Header set Cache-Control "max-age=604800, public"
|
||||
</filesmatch>
|
||||
<filesmatch "\\.(js)$">
|
||||
Header set Cache-Control "max-age=216000, private"
|
||||
</filesmatch>
|
||||
</ifmodule>
|
||||
9
.scrutinizer.yml
Normal file
9
.scrutinizer.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
# This file just tells the wonderful code quality analyzer Scrutinizer (https://scrutinizer-ci.com/g/panique/huge/)
|
||||
# that we are using external services (Travis) to generate code coverage stats
|
||||
checks:
|
||||
php:
|
||||
code_rating: true
|
||||
duplication: true
|
||||
# test coverage is commented out as this does not seem to work properly right now
|
||||
#tools:
|
||||
# external_code_coverage: true
|
||||
38
.travis.yml
Normal file
38
.travis.yml
Normal file
@@ -0,0 +1,38 @@
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 5.6
|
||||
- 7.0
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
- 7.4
|
||||
|
||||
# optional, define the environment, in this case useful to test stuff that only runs in development / production
|
||||
env:
|
||||
global:
|
||||
- APPLICATION_ENV=testing
|
||||
|
||||
before_install:
|
||||
- sudo apt-get update > /dev/null
|
||||
|
||||
before_script:
|
||||
- sudo apt-get install apache2
|
||||
- sudo a2enmod rewrite
|
||||
# configure apache virtual hosts, create vhost via travis-ci-apache file template
|
||||
- sudo cp -f travis-ci-apache /etc/apache2/sites-available/default
|
||||
- sudo sed -e "s?%TRAVIS_BUILD_DIR%?$(pwd)?g" --in-place /etc/apache2/sites-available/default
|
||||
- sudo service apache2 restart
|
||||
# composer
|
||||
- composer self-update
|
||||
- composer install --prefer-source --no-interaction
|
||||
# go to tests folder
|
||||
- cd tests
|
||||
|
||||
# run unit tests, create result file
|
||||
script: ../vendor/bin/phpunit --configuration phpunit.xml --coverage-text --coverage-clover=coverage.clover
|
||||
|
||||
# gets tools from Scrutinizer, uploads unit tests results to Scrutinizer (?)
|
||||
after_script:
|
||||
- wget https://scrutinizer-ci.com/ocular.phar
|
||||
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover
|
||||
786
README.md
786
README.md
@@ -1,632 +1,220 @@
|
||||

|
||||
# HUGE – Installation und Setup
|
||||
|
||||
# HUGE
|
||||
## Schnellstart
|
||||
|
||||
[](https://scrutinizer-ci.com/g/panique/huge/?branch=master)
|
||||
[](https://codeclimate.com/github/panique/huge)
|
||||
[](https://www.codacy.com/app/panique/huge?utm_source=github.com&utm_medium=referral&utm_content=panique/huge&utm_campaign=Badge_Grade)
|
||||
[](https://travis-ci.org/panique/huge)
|
||||
[](https://www.versioneye.com/user/projects/54ca11fbde7924f81a000010)
|
||||
[](https://supporterhq.com/give/9guz00i6rep05k1mwxyquz30k)
|
||||
- Voraussetzungen prüfen (siehe unten)
|
||||
- Abhängigkeiten installieren
|
||||
- Datenbank anlegen
|
||||
- Webserver auf `public/` zeigen lassen
|
||||
- Starten und testen
|
||||
|
||||
Just a simple user authentication solution inside a super-simple framework skeleton that works out-of-the-box
|
||||
(and comes with an auto-installer), using the future-proof official bcrypt password hashing/salting implementation of
|
||||
PHP 5.5+, plus some nice features that will speed up the time from idea to first usable prototype application
|
||||
dramatically. Nothing more. This project has its focus on hardcore simplicity. Everything is as simple as possible,
|
||||
made for smaller projects, typical agency work and quick drafts. If you want to build massive corporate
|
||||
applications with all the features modern frameworks have, then have a look at [Laravel](http://laravel.com),
|
||||
[Symfony](http://symfony.com) or [Yii](http://www.yiiframework.com), but if you just want to quickly create something
|
||||
that just works, then this script might be interesting for you.
|
||||
Befehle (Beispiele):
|
||||
|
||||
HUGE's simple-as-possible architecture was inspired by several conference talks, slides and articles about huge
|
||||
applications that - surprisingly and intentionally - go back to the basics of programming, using procedural programming,
|
||||
static classes, extremely simple constructs, not-totally-DRY code etc. while keeping the code extremely readable
|
||||
([StackOverflow](http://www.dev-metal.com/architecture-stackoverflow/), Wikipedia, SoundCloud).
|
||||
|
||||
Some interesting Buzzwords in this context: [KISS](http://en.wikipedia.org/wiki/KISS_principle),
|
||||
[YAGNI](http://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it), [Feature Creep](https://en.wikipedia.org/wiki/Feature_creep),
|
||||
[Minimum viable product](https://en.wikipedia.org/wiki/Minimum_viable_product).
|
||||
|
||||
#### HUGE has reached "soft End Of Life"
|
||||
|
||||
To keep this project stable, secure, clean and minimal I've decided to reduce the development of HUGE to a
|
||||
minimum. *Don't worry, this is actually a good thing:* New features usually mean new bugs, lots of testing, fixes,
|
||||
incompatibilities, and for some people even hardcore update stress. As HUGE is a security-critical script new features
|
||||
are not as important as a stable and secure core, this is why people use it. This means:
|
||||
|
||||
- HUGE will not get new features
|
||||
- but will be maintained, so it will get bugfixes, corrections etc for sure, maybe for years
|
||||
|
||||
And to be honest, maintaining a framework for free in my rare free-time is also not what I want to do permanently. :)
|
||||
|
||||
Finally a little note: The PHP world has evolved dramatically, we have excellent frameworks with awesome features and
|
||||
big professional teams behind, very well written documentations and large communities, so there's simply no reason
|
||||
to put much work into another framework. Instead, please commit to the popular frameworks, then your work will have
|
||||
much more impact and is used by much more people!
|
||||
|
||||
Thanks to everybody around this project, have a wonderful time!
|
||||
XOXO,
|
||||
Chris
|
||||
|
||||
#### Releases & development
|
||||
|
||||
* stable [v3.1](https://github.com/panique/huge/releases/tag/v3.1),
|
||||
* public beta branch: [master](https://github.com/panique/huge)
|
||||
* public in-development branch (please commit new code here): [develop](https://github.com/panique/huge/tree/develop)
|
||||
|
||||
#### Quick-Index
|
||||
|
||||
+ [Features](#features)
|
||||
+ [Live-Demo](#live-demo)
|
||||
+ [Support](#support)
|
||||
+ [Follow the project](#follow)
|
||||
+ [License](#license)
|
||||
+ [Requirements](#requirements)
|
||||
+ [Auto-Installation](#auto-installation)
|
||||
- [Auto-Installation in Vagrant](#auto-installation-vagrant) (also useful for 100% reproducible installation of HUGE)
|
||||
- [Auto-Installation in Ubuntu 14.04 LTS server](#auto-installation-ubuntu)
|
||||
+ [Installation (Ubuntu 14.04 LTS)](#installation)
|
||||
- [Quick Installation](#quick-installation)
|
||||
- [Detailed Installation](#detailed-installation)
|
||||
- [NGINX setup](#nginx-setup)
|
||||
- [IIS setup](#iis-setup)
|
||||
+ [Documentation](#documentation)
|
||||
- [How to use the user roles](#user_roles)
|
||||
- [How to use the CSRF feature](#csrf)
|
||||
+ [Community-provided features & feature discussions](#community)
|
||||
+ [Future of the project, announcing soft EOL](#future)
|
||||
+ [Why is there no support forum anymore ?](#why-no-support-forum)
|
||||
+ [Zero tolerance for idiots, trolls and vandals](#zero-tolerance)
|
||||
+ [Contribute](#contribute)
|
||||
+ [Code-Quality scanner links](#code-quality)
|
||||
+ [Report a bug](#bug-report)
|
||||
|
||||
### The History of HUGE
|
||||
|
||||
Back in 2010/2011 there were no useful login solutions in the PHP world, at least not for non-experts. So I did the worst
|
||||
mistake every young developer does: Trying to build something by myself without having any clue about security basics.
|
||||
What made it even worse was: The web was (and is) full of totally broken tutorials about building user authentication
|
||||
systems, even the biggest companies in the world did this completely wrong (we are talking about SONY, LinkedIn and
|
||||
Adobe here), and also lots of major framework in all big programming languages (!) used totally outdated and insecure
|
||||
password saving technologies.
|
||||
|
||||
However, in 2012 security expert [Anthony Ferrara](https://github.com/ircmaxell) published a [little PHP library](https://github.com/ircmaxell/password_compat),
|
||||
allowing extremely secure, modern and correct hashing of passwords in PHP 5.3 and 5.4, usable by every developer without any stress and without any knowledge
|
||||
about security internals. The script was so awesome that it was written into the core of PHP 5.5, it's the de-facto standard these days.
|
||||
|
||||
When this came out I tried to use this naked library to build a fully working out-of-the-box login system for several private and commercial projects,
|
||||
and put the code on GitHub. Lots of people found this useful, contributed and bugfixed the project, made forks, smaller and larger versions.
|
||||
The result is this project.
|
||||
|
||||
Please note: Now, in 2015, most major frameworks have excellent user authentication logic embedded by default. This was
|
||||
not the case years ago. So, from today's perspective it might be smarter to chose Laravel, Yii or Symfony for serious
|
||||
projects. But feel free to try out HUGE, the auto-installer will spin up a fully working installation within minutes and
|
||||
without any configuration.
|
||||
|
||||
And why the name "HUGE" ? It's a nice combination to
|
||||
[TINY](https://github.com/panique/tiny),
|
||||
[MINI](https://github.com/panique/mini) and
|
||||
[MINI2](https://github.com/panique/mini2),
|
||||
[MINI3](https://github.com/panique/mini3),
|
||||
which are some of my other older projects. Super-minimal micro frameworks for extremely fast and simple development of simple websites.
|
||||
|
||||
### Features <a name="features"></a>
|
||||
* built with the official PHP password hashing functions, fitting the most modern password hashing/salting web standards
|
||||
* proper security features, like CSRF blocking (via form tokens), encryption of cookie contents etc.
|
||||
* users can register, login, logout (with username, email, password)
|
||||
* password-forget / reset
|
||||
* remember-me (login via cookie)
|
||||
* account verification via mail
|
||||
* captcha
|
||||
* failed-login-throttling
|
||||
* user profiles
|
||||
* account upgrade / downgrade
|
||||
* simple user types (type 1, type 2, admin)
|
||||
* supports local avatars and remote Gravatars
|
||||
* supports native mail and SMTP sending (via PHPMailer and other tools)
|
||||
* uses PDO for database access for sure, has nice DatabaseFactory (in case your project goes big)
|
||||
* uses URL rewriting ("beautiful URLs")
|
||||
* proper split of application and public files (requests only go into /public)
|
||||
* uses Composer to load external dependencies (PHPMailer, Captcha-Generator, etc.) for sure
|
||||
* fits PSR-0/1/2/4 coding guidelines
|
||||
* uses [Post-Redirect-Get pattern](https://en.wikipedia.org/wiki/Post/Redirect/Get) for nice application flow
|
||||
* masses of comments
|
||||
* is actively maintained and bug-fixed (however, no big new features as project slowly reaches End of Life)
|
||||
|
||||
### Planned features
|
||||
|
||||
* A real documentation (currently there's none, but the code is well commented)
|
||||
|
||||
### Live-Demo <a name="live-demo"></a>
|
||||
|
||||
See a [live demo of older 3.0 version here](http://104.131.8.128) and [the server's phpinfo() here](104.131.8.128/info.php).
|
||||
|
||||
### Support the project <a name="support"></a>
|
||||
|
||||
There is a lot of work behind this project. I might save you hundreds, maybe thousands of hours of work (calculate that
|
||||
in developer costs). So when you are earning money by using HUGE, be fair and give something back to open-source.
|
||||
HUGE is totally free to private and commercial use.
|
||||
|
||||
Support the project by renting a server at [DigitalOcean](https://www.digitalocean.com/?refcode=40d978532a20) or just tipping a coffee at BuyMeACoffee.com. Thanks! :)
|
||||
|
||||
<a href="https://www.buymeacoffee.com/panique" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
||||
|
||||
Also feel free to contribute to this project.
|
||||
|
||||
### License <a name="license"></a>
|
||||
|
||||
Licensed under [MIT](http://www.opensource.org/licenses/mit-license.php).
|
||||
Totally free for private or commercial projects.
|
||||
|
||||
### Requirements <a name="requirements"></a>
|
||||
|
||||
Make sure you know the basics of object-oriented programming and MVC, are able to use the command line and have
|
||||
used Composer before. This script is not for beginners.
|
||||
|
||||
* **PHP 5.5+**
|
||||
* **MySQL 5** database (better use versions 5.5+ as very old versions have a [PDO injection bug](http://stackoverflow.com/q/134099/1114320)
|
||||
* installed PHP extensions: pdo, gd, openssl (the install guideline shows how to do)
|
||||
* installed tools on your server: git, curl, composer (the install guideline shows how to do)
|
||||
* for professional mail sending: an SMTP account (I use [SMTP2GO](http://www.smtp2go.com/?s=devmetal))
|
||||
* activated mod_rewrite on your server (the install guideline shows how to do)
|
||||
|
||||
### Auto-Installations <a name="auto-installation"></a>
|
||||
|
||||
Yo, fully automatic. Why ? Because I always hated it to spend days trying to find out how to install a thing.
|
||||
This will save you masses of time and nerves. Donate a coffee if you like it.
|
||||
|
||||
#### Auto-Installation (in Vagrant) <a name="auto-installation-vagrant"></a>
|
||||
|
||||
If you are using Vagrant for your development, then simply
|
||||
|
||||
1. Add the official Ubuntu 14.04 LTS box to your Vagrant: `vagrant box add ubuntu/trusty64`
|
||||
2. Move *Vagrantfile* and *bootstrap.sh* (from *_one-click-installation* folder) to a folder where you want to initialize your project.
|
||||
3. Do `vagrant up` in that folder.
|
||||
|
||||
5 minutes later you'll have a fully installed HUGE inside Ubuntu 14.04 LTS. The full code will be auto-synced with
|
||||
the current folder. MySQL root password and the PHPMyAdmin root password are set to *12345678*. By default
|
||||
192.168.33.111 is the IP of your new box.
|
||||
|
||||
#### Auto-Installation in a naked Ubuntu 14.04 LTS server <a name="auto-installation-ubuntu"></a>
|
||||
|
||||
Extremely simple installation in a fresh and naked typical Ubuntu 14.04 LTS server:
|
||||
|
||||
Download the installer script
|
||||
```bash
|
||||
wget https://raw.githubusercontent.com/panique/huge/master/_one-click-installation/bootstrap.sh
|
||||
# Composer installieren (macOS – über Homebrew)
|
||||
brew install composer
|
||||
|
||||
# Abhängigkeiten holen (im Projektordner)
|
||||
composer install
|
||||
|
||||
# Datenbank & Tabellen anlegen
|
||||
mysql -u root -p < application/_installation/01-create-database.sql
|
||||
mysql -u root -p < application/_installation/02-create-table-users.sql
|
||||
mysql -u root -p < application/_installation/03-create-table-notes.sql
|
||||
|
||||
# Rechte für Avatare (je nach OS anpassen)
|
||||
# Ubuntu/Debian:
|
||||
sudo chown -R www-data:www-data public/avatars
|
||||
sudo chmod -R 775 public/avatars
|
||||
|
||||
# macOS (Apache Standardnutzer _www):
|
||||
sudo chown -R _www:_www public/avatars
|
||||
sudo chmod -R 775 public/avatars
|
||||
```
|
||||
|
||||
Make it executable
|
||||
---
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- PHP >= 5.5 (laut composer.json)
|
||||
- Composer
|
||||
- Apache 2.4 mit `mod_rewrite`
|
||||
- MySQL/MariaDB
|
||||
- PHP-Erweiterungen: PDO + pdo_mysql, OpenSSL, mbstring
|
||||
- Schreibrechte für `public/avatars/`
|
||||
|
||||
---
|
||||
|
||||
## Projekt beziehen
|
||||
|
||||
- Repository klonen oder entpacken
|
||||
- In den Projektordner wechseln
|
||||
|
||||
---
|
||||
|
||||
## Abhängigkeiten installieren (Composer)
|
||||
|
||||
- Im Projektordner ausführen:
|
||||
|
||||
```bash
|
||||
chmod +x bootstrap.sh
|
||||
composer install
|
||||
```
|
||||
|
||||
Run it! Give it some minutes to perform all the tasks. And yes, you can thank me later :)
|
||||
```bash
|
||||
sudo ./bootstrap.sh
|
||||
```
|
||||
### Installation <a name="installation"></a>
|
||||
---
|
||||
|
||||
#### Quick guide: <a name="quick-installation"></a>
|
||||
## Webserver konfigurieren (Apache)
|
||||
|
||||
0. Make sure you have Apache, PHP, MySQL installed. [Tutorial](http://www.dev-metal.com/installsetup-basic-lamp-stack-linux-apache-mysql-php-ubuntu-14-04-lts/).
|
||||
1. Clone the repo to a folder on your server
|
||||
2. Activate mod_rewrite, route all traffic to application's /public folder. [Tutorial](http://www.dev-metal.com/enable-mod_rewrite-ubuntu-14-04-lts/).
|
||||
3. Edit application/config: Set your database credentials
|
||||
4. Execute SQL statements from application/_installation to setup database tables
|
||||
5. [Install Composer](http://www.dev-metal.com/install-update-composer-windows-7-ubuntu-debian-centos/),
|
||||
run `Composer install` on application's root folder to install dependencies
|
||||
6. Make avatar folder (application/public/avatars) writable
|
||||
7. For proper email usage: Set SMTP credentials in config file, set EMAIL_USE_SMTP to true
|
||||
- DocumentRoot auf `.../huge/public` setzen
|
||||
- `AllowOverride All` aktivieren (damit `.htaccess` greift)
|
||||
- `mod_rewrite` aktivieren
|
||||
- Apache neu starten
|
||||
|
||||
"Email does not work" ? See the troubleshooting below. TODO
|
||||
Minimalbeispiel VirtualHost:
|
||||
|
||||
#### Detailed guide (Ubuntu 14.04 LTS): <a name="detailed-installation"></a>
|
||||
|
||||
This is just a quick guideline for easy setup of a development environment!
|
||||
|
||||
Make sure you have Apache, PHP 5.5+ and MySQL installed. [Tutorial here](http://www.dev-metal.com/installsetup-basic-lamp-stack-linux-apache-mysql-php-ubuntu-14-04-lts/).
|
||||
Nginx will work for sure too, but no install guidelines are available yet.
|
||||
|
||||
Edit vhost to make clean URLs possible and route all traffic to /public folder of your project:
|
||||
```bash
|
||||
sudo nano /etc/apache2/sites-available/000-default.conf
|
||||
```
|
||||
|
||||
and make the file look like
|
||||
```
|
||||
```apacheconf
|
||||
<VirtualHost *:80>
|
||||
DocumentRoot "/var/www/html/public"
|
||||
<Directory "/var/www/html/public">
|
||||
ServerName huge.local
|
||||
DocumentRoot "/pfad/zu/huge/public"
|
||||
|
||||
<Directory "/pfad/zu/huge/public">
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
SetEnv APPLICATION_ENV development
|
||||
ErrorLog "${APACHE_LOG_DIR}/huge_error.log"
|
||||
CustomLog "${APACHE_LOG_DIR}/huge_access.log" combined
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
Enable mod_rewrite and restart apache.
|
||||
Hinweis: `.htaccess` leitet auf `public/index.php` um.
|
||||
|
||||
---
|
||||
|
||||
## Datenbank anlegen
|
||||
|
||||
- SQL-Skripte in dieser Reihenfolge ausführen (`application/_installation/`):
|
||||
- `01-create-database.sql`
|
||||
- `02-create-table-users.sql`
|
||||
- `03-create-table-notes.sql`
|
||||
|
||||
Beispiel in der Shell:
|
||||
|
||||
```bash
|
||||
sudo a2enmod rewrite
|
||||
service apache2 restart
|
||||
mysql -u root -p < application/_installation/01-create-database.sql
|
||||
mysql -u root -p < application/_installation/02-create-table-users.sql
|
||||
mysql -u root -p < application/_installation/03-create-table-notes.sql
|
||||
```
|
||||
|
||||
Install curl (needed to use git), openssl (needed to clone from GitHub, as github is https only),
|
||||
PHP GD, the graphic lib (we create captchas and avatars), and git.
|
||||
---
|
||||
|
||||
## Framework-Konfiguration (Entwicklung)
|
||||
|
||||
- Datei: `application/config/config.development.php`
|
||||
- Wichtige Schlüssel:
|
||||
- URL
|
||||
- `URL` (Basis-URL, endet mit `/`; auto-detect möglich)
|
||||
- Pfade
|
||||
- `PATH_CONTROLLER`, `PATH_VIEW` (meist unverändert)
|
||||
- Routing
|
||||
- `DEFAULT_CONTROLLER`, `DEFAULT_ACTION`
|
||||
- Datenbank
|
||||
- `DB_TYPE`, `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASS`, `DB_CHARSET`
|
||||
- Captcha
|
||||
- `CAPTCHA_WIDTH`, `CAPTCHA_HEIGHT`
|
||||
- Cookies & Session
|
||||
- `COOKIE_RUNTIME`, `COOKIE_PATH`, `COOKIE_DOMAIN`, `COOKIE_SECURE`, `COOKIE_HTTP`
|
||||
- `SESSION_RUNTIME`
|
||||
- Avatare/Gravatar
|
||||
- `USE_GRAVATAR`, `GRAVATAR_DEFAULT_IMAGESET`, `GRAVATAR_RATING`
|
||||
- `AVATAR_SIZE`, `AVATAR_JPEG_QUALITY`, `AVATAR_DEFAULT_IMAGE`
|
||||
- Ordner `public/avatars/` beschreibbar machen
|
||||
- Sicherheit
|
||||
- `ENCRYPTION_KEY`, `HMAC_SALT` (für eigene Installation ändern)
|
||||
- E-Mail
|
||||
- `EMAIL_USED_MAILER`, `EMAIL_USE_SMTP`
|
||||
- `EMAIL_SMTP_HOST`, `EMAIL_SMTP_AUTH`, `EMAIL_SMTP_USERNAME`, `EMAIL_SMTP_PASSWORD`, `EMAIL_SMTP_PORT`, `EMAIL_SMTP_ENCRYPTION`
|
||||
- `EMAIL_PASSWORD_RESET_*`, `EMAIL_VERIFICATION_*`
|
||||
|
||||
---
|
||||
|
||||
## Umgebungen (Environment)
|
||||
|
||||
- Klasse: `application/core/Environment.php`
|
||||
- Ermittelt `APPLICATION_ENV` (Fallback: `development`)
|
||||
- Weitere Datei möglich: `config.production.php`
|
||||
- Apache-Variable setzen:
|
||||
|
||||
```apacheconf
|
||||
SetEnv APPLICATION_ENV production
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verzeichnisrechte
|
||||
|
||||
- `public/avatars/` beschreibbar
|
||||
- Optional Logs/Uploads je nach Bedarf
|
||||
|
||||
---
|
||||
|
||||
## Starten
|
||||
|
||||
- Browser: `http://<host>/`
|
||||
- Registrierung/Login testen
|
||||
- Mailversand nur mit korrekt konfiguriertem SMTP
|
||||
|
||||
---
|
||||
|
||||
## Tests ausführen
|
||||
|
||||
- PHPUnit unter `vendor/bin/phpunit`
|
||||
- Konfiguration: `tests/phpunit.xml`
|
||||
|
||||
```bash
|
||||
sudo apt-get -y install curl
|
||||
sudo apt-get -y install php5-curl
|
||||
sudo apt-get -y install openssl
|
||||
sudo apt-get -y install php5-gd
|
||||
sudo apt-get -y install git
|
||||
vendor/bin/phpunit -c tests/phpunit.xml
|
||||
```
|
||||
|
||||
git clone HUGE
|
||||
---
|
||||
|
||||
## Häufige Probleme
|
||||
|
||||
- 404 bei allen Routen
|
||||
- `mod_rewrite` aktivieren
|
||||
- `AllowOverride All` setzen
|
||||
- DocumentRoot auf `public/`
|
||||
- Falsche Links/Assets
|
||||
- `URL` in der Config prüfen
|
||||
- Datenbankfehler
|
||||
- `DB_*`-Werte und Rechte prüfen
|
||||
- E-Mails kommen nicht an
|
||||
- `EMAIL_USE_SMTP=true` und SMTP-Daten prüfen
|
||||
- Avatare fehlen
|
||||
- Schreibrechte für `public/avatars/`
|
||||
|
||||
---
|
||||
|
||||
## Option: Vagrant „One-Click-Installation“
|
||||
|
||||
- Ordner: `_one-click-installation/`
|
||||
- Voraussetzungen: Vagrant + VirtualBox
|
||||
|
||||
```bash
|
||||
sudo git clone https://github.com/panique/huge "/var/www/html"
|
||||
cd _one-click-installation
|
||||
vagrant up
|
||||
```
|
||||
|
||||
Install Composer
|
||||
```bash
|
||||
curl -s https://getcomposer.org/installer | php
|
||||
mv composer.phar /usr/local/bin/composer
|
||||
```
|
||||
- Projekt über die VM nutzen (Host/Ports laut Vagrant-Ausgabe)
|
||||
|
||||
Go to project folder, load Composer packages (--dev is optional, you know the deal)
|
||||
```bash
|
||||
cd /var/www/html
|
||||
composer install --dev
|
||||
```
|
||||
---
|
||||
|
||||
Execute the SQL statements. Via phpmyadmin or via the command line for example. 12345678 is the example password.
|
||||
Note that this is written without a space.
|
||||
```bash
|
||||
sudo mysql -h "localhost" -u "root" "-p12345678" < "/var/www/html/application/_installation/01-create-database.sql"
|
||||
sudo mysql -h "localhost" -u "root" "-p12345678" < "/var/www/html/application/_installation/02-create-table-users.sql"
|
||||
sudo mysql -h "localhost" -u "root" "-p12345678" < "/var/www/html/application/_installation/03-create-table-notes.sql"
|
||||
```
|
||||
## Struktur (Kurz)
|
||||
|
||||
Make avatar folder writable (make sure it's the correct path!)
|
||||
```bash
|
||||
sudo chown -R www-data "/var/www/html/public/avatars"
|
||||
```
|
||||
If this doesn't work for you, then you might try the hard way by setting alternatively
|
||||
```bash
|
||||
sudo chmod 0777 -R "/var/www/html/public/avatars"
|
||||
```
|
||||
|
||||
Remove Apache's default demo file
|
||||
```bash
|
||||
sudo rm "/var/www/html/index.html"
|
||||
```
|
||||
|
||||
Edit the application's config in application/config/config.development.php and put in your database credentials.
|
||||
|
||||
Last part (not needed for a first test): Set your SMTP credentials in the same file and set EMAIL_USE_SMTP to true, so
|
||||
you can send proper emails. It's highly recommended to use SMTP for mail sending! Native sending via PHP's mail() will
|
||||
not work in nearly every case (spam blocking). I use [SMTP2GO](http://www.smtp2go.com/?s=devmetal).
|
||||
|
||||
Then check your server's IP / domain. Everything should work fine.
|
||||
|
||||
#### NGINX setup: <a name="nginx-setup"></a>
|
||||
|
||||
This is an untested NGINX setup. Please comment [on the ticket](https://github.com/panique/huge/issues/622) if you see
|
||||
issues.
|
||||
|
||||
```
|
||||
server {
|
||||
# your listening port
|
||||
listen 80;
|
||||
|
||||
# your server name
|
||||
server_name example.com;
|
||||
|
||||
# your path to access log files
|
||||
access_log /srv/www/example.com/logs/access.log;
|
||||
error_log /srv/www/example.com/logs/error.log;
|
||||
|
||||
# your root
|
||||
root /srv/www/example.com/public_html;
|
||||
|
||||
# huge
|
||||
index index.php;
|
||||
|
||||
# huge
|
||||
location / {
|
||||
try_files $uri /index.php?url=$uri&$args;
|
||||
}
|
||||
|
||||
# your PHP config
|
||||
location ~ \.php$ {
|
||||
try_files $uri = 401;
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_pass unix:/var/run/php-fastcgi/php-fastcgi.socket;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### IIS setup: <a name="iis-setup"></a>
|
||||
|
||||
Big thanks to razuro for this fine setup: Put this inside your root folder, but don't put any web.config in your public
|
||||
folder.
|
||||
|
||||
```
|
||||
<?xml version="1.0" encoding="UTF-8"?><configuration>
|
||||
<system.webServer>
|
||||
<rewrite>
|
||||
<rules>
|
||||
|
||||
<rule name="Imported Rule 1" stopProcessing="true">
|
||||
<match url="^(.*)$" ignoreCase="false" />
|
||||
<conditions logicalGrouping="MatchAll">
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
|
||||
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
|
||||
</conditions>
|
||||
<action type="Rewrite" url="public/index.php?url={R:1}" />
|
||||
</rule>
|
||||
</rules>
|
||||
</rewrite>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
Find the original [ticket here](https://github.com/panique/huge/issues/788).
|
||||
|
||||
#### Testing with demo users
|
||||
|
||||
By default there are two demo users, a normal user and an admin user. For more info on that please have a look on the
|
||||
user role part of the small documentation block inside this readme.
|
||||
|
||||
Normal user: Username is `demo2`, password is `12345678`. The user is already activated.
|
||||
Admin user (can delete and suspend other users): Username is `demo`, password is `12345678`. The user is already activated.
|
||||
|
||||
### What the hell are .travis.yml, .scrutinizer.yml etc. ?
|
||||
|
||||
There are several files in the root folder of the project that might be irritating:
|
||||
|
||||
- *.htaccess* (optionally) routes all traffic to /public/index.php! If you installed this project correctly, then this
|
||||
file is not necessary, but as lots of people have problems setting up the vhost correctly, .htaccess it still there
|
||||
to increase security, even on partly-broken-installations.
|
||||
- *.scrutinizer.yml* (can be deleted): Configs for the external code quality analyzer Scrutinizer, just used here on
|
||||
GitHub, you don't need this for your project.
|
||||
- *.travis.yml* (can be deleted): Same like above. Travis is an external service that creates installations of this
|
||||
repo after each code change to make sure everything runs fine. Also runs the unit tests. You don't need this inside
|
||||
your project.
|
||||
- *composer.json* (important): You should know what this does. ;) This file says what external dependencies are used.
|
||||
- *travis-ci-apache* (can be deleted): Config file for Travis, see above, so Travis knows how to setup the Apache.
|
||||
|
||||
*README* and *CHANGELOG* are self-explaining.
|
||||
|
||||
### Documentation <a name="documentation"></a>
|
||||
|
||||
A real documentation is in the making. Until then, please have a look at the code and use your IDE's code completion
|
||||
features to get an idea how things work, it's quite obvious when you look at the controller files, the model files and
|
||||
how data is shown in the view files. A big sorry that there's no documentation yet, but time is rare and we are all
|
||||
doing this for free in our free time :)
|
||||
|
||||
- TODO: Full documentation
|
||||
- TODO: Basic examples on how to do things
|
||||
|
||||
#### How to use the different user roles <a name="user_roles"></a>
|
||||
|
||||
Currently there are two types of users: Normal users and admins. There are exactly the same, but...
|
||||
|
||||
1. Admin users can delete and suspend other users, they have an additional button "admin" in the navigation. Admin users
|
||||
have a value of `7` inside the database table field `user_account_type`. They cannot upgrade or downgrade their accounts
|
||||
(as this wouldn't make sense).
|
||||
|
||||
2. Normal users don't have admin features for sure. But they can upgrade and downgrade their accounts (try it out via
|
||||
/user/changeUserRole), which is basically a super-simple implementation of the basic-user / premium-user concept.
|
||||
Normal users have a value of `1` or `2` inside the database table field `user_account_type`. By default all new
|
||||
registered users are normal users with user role 1 for sure.
|
||||
|
||||
See the "Testing with demo users" section of this readme for more info.
|
||||
|
||||
There's also a very interesting [pull request adding user roles and user permissions](https://github.com/panique/huge/pull/691),
|
||||
which is not integrated into the project as it's too advanced and complex. But, this might be exactly what you need,
|
||||
feel free to try.
|
||||
|
||||
#### How to use the CSRF feature <a name="csrf"></a>
|
||||
|
||||
To prevent [CSRF attacks](https://en.wikipedia.org/wiki/Cross-site_request_forgery), HUGE does this in the most common
|
||||
way, by using a security *token* when the user submits critical forms. This means: When PHP renders a form for the user,
|
||||
the application puts a "random string" inside the form (as a hidden input field), generated via Csrf::makeToken()
|
||||
(application/core/Csrf.php), which also saves this token to the session. When the form is submitted, the application
|
||||
checks if the POST request contains exactly the form token that is inside the session.
|
||||
|
||||
This CSRF prevention feature is currently implemented on the login form process (see *application/view/login/index.php*)
|
||||
and user name change form process (see *application/view/user/editUsername.php*), most other forms are not security-
|
||||
critical and should stay as simple as possible.
|
||||
|
||||
So, to do this with a normal form, simply: At your form, before the submit button put:
|
||||
`<input type="hidden" name="csrf_token" value="<?= Csrf::makeToken(); ?>" />`
|
||||
Then, in the controller action validate the CSRF token submitted with the form by doing:
|
||||
```
|
||||
// check if csrf token is valid
|
||||
if (!Csrf::isTokenValid()) {
|
||||
LoginModel::logout();
|
||||
Redirect::home();
|
||||
exit();
|
||||
}
|
||||
```
|
||||
|
||||
A big thanks to OmarElGabry for implementing this!
|
||||
|
||||
#### Can a user be logged in from multiple devices ?
|
||||
|
||||
In theory: Yes, but this feature didn't work in my tests. As it's an external feature please have a look into the
|
||||
[according ticket](https://github.com/panique/huge/pull/693) for more.
|
||||
|
||||
#### Troubleshooting & Glitches
|
||||
|
||||
* In 3.0 and 3.1 a user could log into the application from different devices / browsers / locations. This was intended
|
||||
behaviour as this is standard in most web applications these days. In 3.2 still feature is "missing" by default, a
|
||||
user will only be able to log in from one browser at the same time. This is a security improvement, but for sure not
|
||||
optimal for many developers. The plan is to implement a config switch that will allow / disallow logins from multiple
|
||||
browsers.
|
||||
* Using this on a sub-domain ? You might get problems with the cookies in IE11. Fix this by replacing "/" with "./" of
|
||||
the cookie location COOKIE_PATH inside application/config/config.xxx.php!
|
||||
Check [ticket #733](https://github.com/panique/huge/issues/733) for more info. Thanks to jahbiuabft for figuring this
|
||||
out. Update: There's another ticket focusing on the same issue: [ticket #681](https://github.com/panique/huge/issues/681)
|
||||
|
||||
### Community-provided features & feature discussions <a name="community"></a>
|
||||
|
||||
There are some awesome features or feature ideas build by awesome people, but these features are too special-interest
|
||||
to go into the main version of HUGE, but have a look into these tickets if you are interested:
|
||||
|
||||
- [Caching system](https://github.com/panique/huge/issues/643)
|
||||
- [ReCaptcha as captcha](https://github.com/panique/huge/issues/665)
|
||||
- [Internationalization feature](https://github.com/panique/huge/issues/582)
|
||||
- [Using controller A inside controller B](https://github.com/panique/huge/issues/706)
|
||||
- [HTML mails](https://github.com/panique/huge/issues/738)
|
||||
- [Deep user roles / user permission system](https://github.com/panique/huge/pull/691)
|
||||
|
||||
### Future of HUGE: Announcing "soft End Of Life" <a name="future"></a>
|
||||
|
||||
The idea of this project is and was to provide a super-simple barebone application with a full user authentication
|
||||
system inside that just works fine and stable. Due to the highly security-related nature of this script any changes
|
||||
mean a lot of work, lots of testing, catching edge cases etc., and in the end I spent 90% of the time testing and fixing
|
||||
new features or new features break existing stuff, and doing this is really not what anybody wants to do for free in
|
||||
the rare free-time :)
|
||||
|
||||
To keep the project stable, clean and maintainable, I would kindly announce the "soft-End of Life" for this project,
|
||||
meaning:
|
||||
|
||||
A. HUGE will not get any new features in the future, but ...
|
||||
B. bugfixes and corrections will be made, probably for years
|
||||
|
||||
### Coding guideline behind HUGE
|
||||
|
||||
While HUGE was in development, there were 3 main rules that helped me (and probably others) to write minimal, clean
|
||||
and working code. Might be useful for you too:
|
||||
|
||||
1. Reduce features to the bare minimum.
|
||||
2. Don't implement features that are not needed by most users.
|
||||
3. Only build everything for the most common use case (like MySQL, not PostGre, NoSQL etc).
|
||||
|
||||
As noted in the intro of this README, there are also some powerful concepts that might help you when developing cool
|
||||
stuff: [KISS](http://en.wikipedia.org/wiki/KISS_principle),
|
||||
[YAGNI](http://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it), [Feature Creep](https://en.wikipedia.org/wiki/Feature_creep),
|
||||
[Minimum viable product](https://en.wikipedia.org/wiki/Minimum_viable_product).
|
||||
|
||||
#### List of features / ideas provided in tickets / pull requests
|
||||
|
||||
To avoid unnecessary work for all of us I would kindly recommend everybody to use HUGE for simple project that only
|
||||
need the features that already exist, and if you really need a RESTful architecture, migrations, routing, 2FA etc,
|
||||
then it's easier, cleaner and faster to simply use Laravel, Symfony or Zend.
|
||||
|
||||
However, here are the community-suggested possible features, taken from lots of tickets. Feel free to implement them
|
||||
into your forks of the project:
|
||||
|
||||
* OAuth2 implementation (let your users create accounts and login via 3rd party auth, like Facebook, Twitter, GitHub,
|
||||
etc). As this is a lot of work and would make the project much more complicated it might make sense to do this in a
|
||||
fork or totally skip it. (see [Ticket #528](https://github.com/panique/huge/issues/528))
|
||||
* Router (map all URLs to according controller-methods inside one file), [Ticket 727](https://github.com/panique/huge/issues/727)
|
||||
* RESTful architecture (see [ticket #488](https://github.com/panique/huge/issues/488) for discussion)
|
||||
* Horizontal MySQL scaling (see [ticket #423](https://github.com/panique/huge/issues/423) for discussion)
|
||||
* Modules / middleware
|
||||
* Logging
|
||||
* Two-Factor-Authentication (see [ticket #732](https://github.com/panique/huge/issues/732))
|
||||
* Controller-less URLs (see [ticket #704](https://github.com/panique/huge/issues/704))
|
||||
* Email-re-validation after email change (see [ticket #705](https://github.com/panique/huge/issues/705))
|
||||
* Connect to multiple databases (see [ticket #702](https://github.com/panique/huge/issues/702))
|
||||
* A deeper user role system (see [ticket #701](https://github.com/panique/huge/issues/701),
|
||||
[pull-request #691](https://github.com/panique/huge/pull/691)),
|
||||
[ticket #603](https://github.com/panique/huge/issues/603)
|
||||
* How to run without using Composer [ticket #826](https://github.com/panique/huge/issues/826)
|
||||
|
||||
### Why is there no support forum (anymore) ? <a name="why-no-support-forum"></a>
|
||||
|
||||
There were two (!) support forums for v1 and v2 of this project (HUGE is v3), and both were vandalized by people who
|
||||
didn't even read the readme and / or the install guidelines. Most asked question was "script does not work plz help"
|
||||
without giving any useful information (like code or server setup or even the version used). While I'm writing these
|
||||
lines somebody just asked via Twitter "how to install without Composer". You know what I mean :) - 99% of the questions
|
||||
were not necessary if the people would had read the guidelines, do a minimal research on their own or would stop making
|
||||
things so unnecessarily complicated. And even when writing detailed answers most of them still messed it up, resulting
|
||||
in rants and complaints (for free support for a free software!). It was just frustrating to deal with this every day,
|
||||
especially when people take it for totally granted that *it's the duty* of open-source developers to give detailed,
|
||||
free and personal support for every "plz help"-request.
|
||||
|
||||
So I decided to completely stop any free support. For serious questions about real problems inside the script please
|
||||
use the GitHub issues feature.
|
||||
|
||||
### Zero tolerance for idiots, trolls and vandals! <a name="zero-tolerance"></a>
|
||||
|
||||
Harsh words, but as basically every public internet project gets harassed, vandalized and trolled these days by very
|
||||
strange people it's necessary: Some simple rules.
|
||||
|
||||
1. Respect that this is just a simple script written by unpaid volunteers in their free-time.
|
||||
This is NOT business-software you've bought for $10.000.
|
||||
There's no reason to complain (!) about free open-source software. The attitude against free software
|
||||
is really frustrating these days, people take everything for granted without realizing the work behind it, and the
|
||||
fact that they get serious software totally for free, saving thousands of dollars. If you don't like it, then don't
|
||||
use it. If you want a feature, try to take part in the process, maybe even build it by yourself and add it to the
|
||||
project! Be nice and respectful. Constructive criticism is for sure always welcome!
|
||||
|
||||
2. Don't bash, don't hate, don't spam, don't vandalize. Please don't ask for personal free support, don't ask if
|
||||
somebody could do your work for you. Before you ask something, make sure you've read the README, followed every
|
||||
tutorial, double-checked the code and tried to solve the problem by yourself.
|
||||
|
||||
Trolls and very annoying people will get a permanent ban / block. GitHub has a very powerful anti-abuse team.
|
||||
|
||||
### Contribute <a name="contribute"></a>
|
||||
|
||||
Please commit only in *develop* branch. The *master* branch will always contain the stable version.
|
||||
|
||||
### Code-Quality scanner links <a name="code-quality"></a>
|
||||
|
||||
[Scrutinizer (master branch)](https://scrutinizer-ci.com/g/panique/huge/?branch=master),
|
||||
[Scrutinizer (develop branch)](https://scrutinizer-ci.com/g/panique/huge/?branch=develop),
|
||||
[Code Climate](https://codeclimate.com/github/panique/huge),
|
||||
[Codacy](https://www.codacy.com/public/panique/phplogin/dashboard?bid=789836),
|
||||
[SensioLabs Insight](https://insight.sensiolabs.com/projects/d4f4e3c0-1445-4245-8cb2-d75026c11fa7/analyses/2).
|
||||
|
||||
### Found a bug (Responsible Disclosure) ? <a name="bug-report"></a>
|
||||
|
||||
Due to the possible consequences when publishing a bug on a public open-source project I'd kindly ask you to send really
|
||||
big bugs to my email address, not posting this here. If the bug is not interesting for attackers: Feel free to create
|
||||
an normal GitHub issue.
|
||||
|
||||
### Current and further development
|
||||
|
||||
See active issues here:
|
||||
https://github.com/panique/huge/issues?state=open
|
||||
|
||||
### Why you should use a favicon.ico in your project :)
|
||||
|
||||
Interesting issue: When a user hits your website, the user's browser will also request one or more (!) favicons
|
||||
(different sizes). If these static files don't exist, your application will start to generate a 404 response and a 404
|
||||
page for each file. This wastes a lot of server power and is also useless, therefore make sure you always have favicons
|
||||
or handle this from Apache/nginx level.
|
||||
|
||||
HUGE tries to handle this by sending an empty image in the head of the view/_templates/header.php !
|
||||
|
||||
More inside this ticket: [Return proper 404 for missing favicon.ico, missing images etc.](https://github.com/panique/huge/issues/530)
|
||||
|
||||
More here on Stackflow: [How to prevent favicon.ico requests?](http://stackoverflow.com/questions/1321878/how-to-prevent-favicon-ico-requests),
|
||||
[Isn't it silly that a tiny favicon requires yet another HTTP request? How to make favicon go into a sprite?](http://stackoverflow.com/questions/5199902/isnt-it-silly-that-a-tiny-favicon-requires-yet-another-http-request-how-to-mak?lq=1).
|
||||
|
||||
### Useful links
|
||||
|
||||
- [How long will my session last?](http://stackoverflow.com/questions/1516266/how-long-will-my-session-last/1516338#1516338)
|
||||
- [How to do expire a PHP session after X minutes?](http://stackoverflow.com/questions/520237/how-do-i-expire-a-php-session-after-30-minutes/1270960#1270960)
|
||||
- [How to use PDO](http://wiki.hashphp.org/PDO_Tutorial_for_MySQL_Developers)
|
||||
- [A short guideline on how to use the PHP 5.5 password hashing functions and its PHP 5.3 & 5.4 implementations](http://www.dev-metal.com/use-php-5-5-password-hashing-functions/)
|
||||
- [How to setup latest version of PHP 5.5 on Ubuntu 12.04 LTS](http://www.dev-metal.com/how-to-setup-latest-version-of-php-5-5-on-ubuntu-12-04-lts/)
|
||||
- [How to setup latest version of PHP 5.5 on Debian Wheezy 7.0/7.1 (and how to fix the GPG key error)](http://www.dev-metal.com/setup-latest-version-php-5-5-debian-wheezy-7-07-1-fix-gpg-key-error/)
|
||||
- [Notes on password & hashing salting in upcoming PHP versions (PHP 5.5.x & 5.6 etc.)](https://github.com/panique/huge/wiki/Notes-on-password-&-hashing-salting-in-upcoming-PHP-versions-%28PHP-5.5.x-&-5.6-etc.%29)
|
||||
- [Some basic "benchmarks" of all PHP hash/salt algorithms](https://github.com/panique/huge/wiki/Which-hashing-&-salting-algorithm-should-be-used-%3F)
|
||||
- [How to prevent PHP sessions being shared between different apache vhosts / different applications](http://www.dev-metal.com/prevent-php-sessions-shared-different-apache-vhosts-different-applications/)
|
||||
|
||||
## Interesting links regarding user authentication and application security
|
||||
|
||||
- [interesting article about password resets (by Troy Hunt, security expert)](http://www.troyhunt.com/2012/05/everything-you-ever-wanted-to-know.html)
|
||||
- Password-Free Email Logins: [Ticket & discussion](https://github.com/panique/huge/issues/674), [article](http://techcrunch.com/2015/06/30/blogging-site-medium-rolls-out-password-free-email-logins/?ref=webdesignernews.com)
|
||||
- Logging in via QR code: [Ticket & discussion](https://github.com/panique/huge/issues/290), [english article](https://www.grc.com/sqrl/sqrl.htm),
|
||||
[german article](http://www.phpgangsta.de/sesam-oeffne-dich-sicher-einloggen-im-internetcafe),
|
||||
[repo](https://github.com/PHPGangsta/Sesame), [live-demo](http://sesame.phpgangsta.de/). Big thanks to *PHPGangsta* for writing this!
|
||||
|
||||
### My blog
|
||||
|
||||
I'm also blogging at **[Dev Metal](http://www.dev-metal.com)**.
|
||||
- `public/` (Webroot, `.htaccess`, `index.php`)
|
||||
- `application/controller/` (Controller)
|
||||
- `application/model/` (Modelle)
|
||||
- `application/view/` (Views)
|
||||
- `application/config/` (Konfiguration je Environment)
|
||||
- `application/_installation/` (SQL-Skripte)
|
||||
- `vendor/` (Composer-Abhängigkeiten)
|
||||
- `tests/` (PHPUnit)
|
||||
|
||||
15
application/_installation/04-create-table-user-groups.sql
Normal file
15
application/_installation/04-create-table-user-groups.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
CREATE TABLE IF NOT EXISTS `huge`.`user_groups` (
|
||||
`group_id` TINYINT(1) NOT NULL COMMENT 'numeric user group id, matches users.user_account_type',
|
||||
`group_name` VARCHAR(64) COLLATE utf8_unicode_ci NOT NULL COMMENT 'human readable group name',
|
||||
PRIMARY KEY (`group_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='user groups lookup';
|
||||
|
||||
INSERT INTO `huge`.`user_groups` (`group_id`, `group_name`) VALUES
|
||||
(1, 'Gast'),
|
||||
(2, 'Benutzer'),
|
||||
(3, 'Gruppe 3'),
|
||||
(4, 'Gruppe 4'),
|
||||
(5, 'Gruppe 5'),
|
||||
(6, 'Gruppe 6'),
|
||||
(7, 'Admin')
|
||||
ON DUPLICATE KEY UPDATE `group_name` = VALUES(`group_name`);
|
||||
@@ -28,129 +28,130 @@ ini_set('session.cookie_httponly', 1);
|
||||
* This is used by the core/Config class.
|
||||
*/
|
||||
return array(
|
||||
/**
|
||||
* Configuration for: Base URL
|
||||
* This detects your URL/IP incl. sub-folder automatically. You can also deactivate auto-detection and provide the
|
||||
* URL manually. This should then look like 'http://192.168.33.44/' ! Note the slash in the end.
|
||||
*/
|
||||
'URL' => 'http://' . $_SERVER['HTTP_HOST'] . str_replace('public', '', dirname($_SERVER['SCRIPT_NAME'])),
|
||||
/**
|
||||
* Configuration for: Folders
|
||||
* Usually there's no reason to change this.
|
||||
*/
|
||||
'PATH_CONTROLLER' => realpath(dirname(__FILE__) . '/../../') . '/application/controller/',
|
||||
'PATH_VIEW' => realpath(dirname(__FILE__) . '/../../') . '/application/view/',
|
||||
/**
|
||||
* Configuration for: Avatar paths
|
||||
* Internal path to save avatars. Make sure this folder is writable. The slash at the end is VERY important!
|
||||
*/
|
||||
'PATH_AVATARS' => realpath(dirname(__FILE__) . '/../../') . '/public/avatars/',
|
||||
'PATH_AVATARS_PUBLIC' => 'avatars/',
|
||||
/**
|
||||
* Configuration for: Default controller and action
|
||||
*/
|
||||
'DEFAULT_CONTROLLER' => 'index',
|
||||
'DEFAULT_ACTION' => 'index',
|
||||
/**
|
||||
* Configuration for: Database
|
||||
* DB_TYPE The used database type. Note that other types than "mysql" might break the db construction currently.
|
||||
* DB_HOST The mysql hostname, usually localhost or 127.0.0.1
|
||||
* DB_NAME The database name
|
||||
* DB_USER The username
|
||||
* DB_PASS The password
|
||||
* DB_PORT The mysql port, 3306 by default (?), find out via phpinfo() and look for mysqli.default_port.
|
||||
* DB_CHARSET The charset, necessary for security reasons. Check Database.php class for more info.
|
||||
*/
|
||||
'DB_TYPE' => 'mysql',
|
||||
'DB_HOST' => 'localhost',
|
||||
'DB_NAME' => 'huge',
|
||||
'DB_USER' => 'root',
|
||||
'DB_PASS' => 'root',
|
||||
'DB_PORT' => '3306',
|
||||
'DB_CHARSET' => 'utf8',
|
||||
/**
|
||||
* Configuration for: Captcha size
|
||||
* The currently used Captcha generator (https://github.com/Gregwar/Captcha) also runs without giving a size,
|
||||
* so feel free to use ->build(); inside CaptchaModel.
|
||||
*/
|
||||
'CAPTCHA_WIDTH' => 359,
|
||||
'CAPTCHA_HEIGHT' => 100,
|
||||
/**
|
||||
* Configuration for: Cookies
|
||||
* 1209600 seconds = 2 weeks
|
||||
* COOKIE_PATH is the path the cookie is valid on, usually "/" to make it valid on the whole domain.
|
||||
* @see http://stackoverflow.com/q/9618217/1114320
|
||||
* @see php.net/manual/en/function.setcookie.php
|
||||
*
|
||||
* COOKIE_DOMAIN: The domain where the cookie is valid for. Usually this does not work with "localhost",
|
||||
* ".localhost", "127.0.0.1", or ".127.0.0.1". If so, leave it as empty string, false or null.
|
||||
* When using real domains make sure you have a dot (!) in front of the domain, like ".mydomain.com". This is
|
||||
* strange, but explained here:
|
||||
* @see http://stackoverflow.com/questions/2285010/php-setcookie-domain
|
||||
* @see http://stackoverflow.com/questions/1134290/cookies-on-localhost-with-explicit-domain
|
||||
* @see http://php.net/manual/en/function.setcookie.php#73107
|
||||
*
|
||||
* COOKIE_SECURE: If the cookie will be transferred through secured connection(SSL). It's highly recommended to set it to true if you have secured connection.
|
||||
* COOKIE_HTTP: If set to true, Cookies that can't be accessed by JS - Highly recommended!
|
||||
* SESSION_RUNTIME: How long should a session cookie be valid by seconds, 604800 = 1 week.
|
||||
*/
|
||||
'COOKIE_RUNTIME' => 1209600,
|
||||
'COOKIE_PATH' => '/',
|
||||
'COOKIE_DOMAIN' => "",
|
||||
'COOKIE_SECURE' => false,
|
||||
'COOKIE_HTTP' => true,
|
||||
'SESSION_RUNTIME' => 604800,
|
||||
/**
|
||||
* Configuration for: Avatars/Gravatar support
|
||||
* Set to true if you want to use "Gravatar(s)", a service that automatically gets avatar pictures via using email
|
||||
* addresses of users by requesting images from the gravatar.com API. Set to false to use own locally saved avatars.
|
||||
* AVATAR_SIZE set the pixel size of avatars/gravatars (will be 44x44 by default). Avatars are always squares.
|
||||
* AVATAR_DEFAULT_IMAGE is the default image in public/avatars/
|
||||
*/
|
||||
'USE_GRAVATAR' => false,
|
||||
'GRAVATAR_DEFAULT_IMAGESET' => 'mm',
|
||||
'GRAVATAR_RATING' => 'pg',
|
||||
'AVATAR_SIZE' => 44,
|
||||
'AVATAR_JPEG_QUALITY' => 85,
|
||||
'AVATAR_DEFAULT_IMAGE' => 'default.jpg',
|
||||
/**
|
||||
* Configuration for: Encryption Keys
|
||||
* ENCRYPTION_KEY, HMAC_SALT: Currently used to encrypt and decrypt publicly visible values, like the user id in
|
||||
* the cookie. Change these values for increased security, but don't touch if you have no idea what this means.
|
||||
*/
|
||||
'ENCRYPTION_KEY' => '6#x0gÊìf^25cL1f$08&',
|
||||
'HMAC_SALT' => '8qk9c^4L6d#15tM8z7n0%',
|
||||
/**
|
||||
* Configuration for: Email server credentials
|
||||
*
|
||||
* Here you can define how you want to send emails.
|
||||
* If you have successfully set up a mail server on your linux server and you know
|
||||
* what you do, then you can skip this section. Otherwise please set EMAIL_USE_SMTP to true
|
||||
* and fill in your SMTP provider account data.
|
||||
*
|
||||
* EMAIL_USED_MAILER: Check Mail class for alternatives
|
||||
* EMAIL_USE_SMTP: Use SMTP or not
|
||||
* EMAIL_SMTP_AUTH: leave this true unless your SMTP service does not need authentication
|
||||
*/
|
||||
'EMAIL_USED_MAILER' => 'phpmailer',
|
||||
'EMAIL_USE_SMTP' => false,
|
||||
'EMAIL_SMTP_HOST' => 'yourhost',
|
||||
'EMAIL_SMTP_AUTH' => true,
|
||||
'EMAIL_SMTP_USERNAME' => 'yourusername',
|
||||
'EMAIL_SMTP_PASSWORD' => 'yourpassword',
|
||||
'EMAIL_SMTP_PORT' => 465,
|
||||
'EMAIL_SMTP_ENCRYPTION' => 'ssl',
|
||||
/**
|
||||
* Configuration for: Email content data
|
||||
*/
|
||||
'EMAIL_PASSWORD_RESET_URL' => 'login/verifypasswordreset',
|
||||
'EMAIL_PASSWORD_RESET_FROM_EMAIL' => 'no-reply@example.com',
|
||||
'EMAIL_PASSWORD_RESET_FROM_NAME' => 'My Project',
|
||||
'EMAIL_PASSWORD_RESET_SUBJECT' => 'Password reset for PROJECT XY',
|
||||
'EMAIL_PASSWORD_RESET_CONTENT' => 'Please click on this link to reset your password: ',
|
||||
'EMAIL_VERIFICATION_URL' => 'register/verify',
|
||||
'EMAIL_VERIFICATION_FROM_EMAIL' => 'no-reply@example.com',
|
||||
'EMAIL_VERIFICATION_FROM_NAME' => 'My Project',
|
||||
'EMAIL_VERIFICATION_SUBJECT' => 'Account activation for PROJECT XY',
|
||||
'EMAIL_VERIFICATION_CONTENT' => 'Please click on this link to activate your account: ',
|
||||
/**
|
||||
* Configuration for: Base URL
|
||||
* This detects your URL/IP incl. sub-folder automatically. You can also deactivate auto-detection and provide the
|
||||
* URL manually. This should then look like 'http://192.168.33.44/' ! Note the slash in the end.
|
||||
*/
|
||||
// 'URL' => 'http://' . $_SERVER['HTTP_HOST'] . str_replace('public', '', dirname($_SERVER['SCRIPT_NAME'])),
|
||||
'URL' => 'http://localhost:8000/', // Use this for a local environment that doesnt run on a normal webserver.
|
||||
/**
|
||||
* Configuration for: Folders
|
||||
* Usually there's no reason to change this.
|
||||
*/
|
||||
'PATH_CONTROLLER' => realpath(dirname(__FILE__).'/../../') . '/application/controller/',
|
||||
'PATH_VIEW' => realpath(dirname(__FILE__).'/../../') . '/application/view/',
|
||||
/**
|
||||
* Configuration for: Avatar paths
|
||||
* Internal path to save avatars. Make sure this folder is writable. The slash at the end is VERY important!
|
||||
*/
|
||||
'PATH_AVATARS' => realpath(dirname(__FILE__).'/../../') . '/public/avatars/',
|
||||
'PATH_AVATARS_PUBLIC' => 'avatars/',
|
||||
/**
|
||||
* Configuration for: Default controller and action
|
||||
*/
|
||||
'DEFAULT_CONTROLLER' => 'index',
|
||||
'DEFAULT_ACTION' => 'index',
|
||||
/**
|
||||
* Configuration for: Database
|
||||
* DB_TYPE The used database type. Note that other types than "mysql" might break the db construction currently.
|
||||
* DB_HOST The mysql hostname, usually localhost or 127.0.0.1
|
||||
* DB_NAME The database name
|
||||
* DB_USER The username
|
||||
* DB_PASS The password
|
||||
* DB_PORT The mysql port, 3306 by default (?), find out via phpinfo() and look for mysqli.default_port.
|
||||
* DB_CHARSET The charset, necessary for security reasons. Check Database.php class for more info.
|
||||
*/
|
||||
'DB_TYPE' => 'mysql',
|
||||
'DB_HOST' => '127.0.0.1',
|
||||
'DB_NAME' => 'huge',
|
||||
'DB_USER' => 'root',
|
||||
'DB_PASS' => 'root',
|
||||
'DB_PORT' => '3306',
|
||||
'DB_CHARSET' => 'utf8',
|
||||
/**
|
||||
* Configuration for: Captcha size
|
||||
* The currently used Captcha generator (https://github.com/Gregwar/Captcha) also runs without giving a size,
|
||||
* so feel free to use ->build(); inside CaptchaModel.
|
||||
*/
|
||||
'CAPTCHA_WIDTH' => 359,
|
||||
'CAPTCHA_HEIGHT' => 100,
|
||||
/**
|
||||
* Configuration for: Cookies
|
||||
* 1209600 seconds = 2 weeks
|
||||
* COOKIE_PATH is the path the cookie is valid on, usually "/" to make it valid on the whole domain.
|
||||
* @see http://stackoverflow.com/q/9618217/1114320
|
||||
* @see php.net/manual/en/function.setcookie.php
|
||||
*
|
||||
* COOKIE_DOMAIN: The domain where the cookie is valid for. Usually this does not work with "localhost",
|
||||
* ".localhost", "127.0.0.1", or ".127.0.0.1". If so, leave it as empty string, false or null.
|
||||
* When using real domains make sure you have a dot (!) in front of the domain, like ".mydomain.com". This is
|
||||
* strange, but explained here:
|
||||
* @see http://stackoverflow.com/questions/2285010/php-setcookie-domain
|
||||
* @see http://stackoverflow.com/questions/1134290/cookies-on-localhost-with-explicit-domain
|
||||
* @see http://php.net/manual/en/function.setcookie.php#73107
|
||||
*
|
||||
* COOKIE_SECURE: If the cookie will be transferred through secured connection(SSL). It's highly recommended to set it to true if you have secured connection.
|
||||
* COOKIE_HTTP: If set to true, Cookies that can't be accessed by JS - Highly recommended!
|
||||
* SESSION_RUNTIME: How long should a session cookie be valid by seconds, 604800 = 1 week.
|
||||
*/
|
||||
'COOKIE_RUNTIME' => 1209600,
|
||||
'COOKIE_PATH' => '/',
|
||||
'COOKIE_DOMAIN' => "",
|
||||
'COOKIE_SECURE' => false,
|
||||
'COOKIE_HTTP' => true,
|
||||
'SESSION_RUNTIME' => 604800,
|
||||
/**
|
||||
* Configuration for: Avatars/Gravatar support
|
||||
* Set to true if you want to use "Gravatar(s)", a service that automatically gets avatar pictures via using email
|
||||
* addresses of users by requesting images from the gravatar.com API. Set to false to use own locally saved avatars.
|
||||
* AVATAR_SIZE set the pixel size of avatars/gravatars (will be 44x44 by default). Avatars are always squares.
|
||||
* AVATAR_DEFAULT_IMAGE is the default image in public/avatars/
|
||||
*/
|
||||
'USE_GRAVATAR' => false,
|
||||
'GRAVATAR_DEFAULT_IMAGESET' => 'mm',
|
||||
'GRAVATAR_RATING' => 'pg',
|
||||
'AVATAR_SIZE' => 44,
|
||||
'AVATAR_JPEG_QUALITY' => 85,
|
||||
'AVATAR_DEFAULT_IMAGE' => 'default.jpg',
|
||||
/**
|
||||
* Configuration for: Encryption Keys
|
||||
* ENCRYPTION_KEY, HMAC_SALT: Currently used to encrypt and decrypt publicly visible values, like the user id in
|
||||
* the cookie. Change these values for increased security, but don't touch if you have no idea what this means.
|
||||
*/
|
||||
'ENCRYPTION_KEY' => '6#x0gÊìf^25cL1f$08&',
|
||||
'HMAC_SALT' => '8qk9c^4L6d#15tM8z7n0%',
|
||||
/**
|
||||
* Configuration for: Email server credentials
|
||||
*
|
||||
* Here you can define how you want to send emails.
|
||||
* If you have successfully set up a mail server on your linux server and you know
|
||||
* what you do, then you can skip this section. Otherwise please set EMAIL_USE_SMTP to true
|
||||
* and fill in your SMTP provider account data.
|
||||
*
|
||||
* EMAIL_USED_MAILER: Check Mail class for alternatives
|
||||
* EMAIL_USE_SMTP: Use SMTP or not
|
||||
* EMAIL_SMTP_AUTH: leave this true unless your SMTP service does not need authentication
|
||||
*/
|
||||
'EMAIL_USED_MAILER' => 'phpmailer',
|
||||
'EMAIL_USE_SMTP' => false,
|
||||
'EMAIL_SMTP_HOST' => 'yourhost',
|
||||
'EMAIL_SMTP_AUTH' => true,
|
||||
'EMAIL_SMTP_USERNAME' => 'yourusername',
|
||||
'EMAIL_SMTP_PASSWORD' => 'yourpassword',
|
||||
'EMAIL_SMTP_PORT' => 465,
|
||||
'EMAIL_SMTP_ENCRYPTION' => 'ssl',
|
||||
/**
|
||||
* Configuration for: Email content data
|
||||
*/
|
||||
'EMAIL_PASSWORD_RESET_URL' => 'login/verifypasswordreset',
|
||||
'EMAIL_PASSWORD_RESET_FROM_EMAIL' => 'no-reply@example.com',
|
||||
'EMAIL_PASSWORD_RESET_FROM_NAME' => 'My Project',
|
||||
'EMAIL_PASSWORD_RESET_SUBJECT' => 'Password reset for PROJECT XY',
|
||||
'EMAIL_PASSWORD_RESET_CONTENT' => 'Please click on this link to reset your password: ',
|
||||
'EMAIL_VERIFICATION_URL' => 'register/verify',
|
||||
'EMAIL_VERIFICATION_FROM_EMAIL' => 'no-reply@example.com',
|
||||
'EMAIL_VERIFICATION_FROM_NAME' => 'My Project',
|
||||
'EMAIL_VERIFICATION_SUBJECT' => 'Account activation for PROJECT XY',
|
||||
'EMAIL_VERIFICATION_CONTENT' => 'Please click on this link to activate your account: ',
|
||||
);
|
||||
|
||||
@@ -38,7 +38,7 @@ return array(
|
||||
"FEEDBACK_PASSWORD_REPEAT_WRONG" => "Password and password repeat are not the same.",
|
||||
"FEEDBACK_PASSWORD_TOO_SHORT" => "Password has a minimum length of 6 characters.",
|
||||
"FEEDBACK_USERNAME_TOO_SHORT_OR_TOO_LONG" => "Username cannot be shorter than 2 or longer than 64 characters.",
|
||||
"FEEDBACK_ACCOUNT_SUCCESSFULLY_CREATED" => "Your account has been created successfully and we have sent you an email. Please click the VERIFICATION LINK within that mail.",
|
||||
"FEEDBACK_ACCOUNT_SUCCESSFULLY_CREATED" => "The account has been created successfully.",
|
||||
"FEEDBACK_VERIFICATION_MAIL_SENDING_FAILED" => "Sorry, we could not send you an verification mail. Your account has NOT been created.",
|
||||
"FEEDBACK_ACCOUNT_CREATION_FAILED" => "Sorry, your registration failed. Please go back and try again.",
|
||||
"FEEDBACK_VERIFICATION_MAIL_SENDING_ERROR" => "Verification mail could not be sent due to: ",
|
||||
|
||||
@@ -20,7 +20,9 @@ class AdminController extends Controller
|
||||
public function index()
|
||||
{
|
||||
$this->View->render('admin/index', array(
|
||||
'users' => UserModel::getPublicProfilesOfAllUsers())
|
||||
'users' => UserModel::getPublicProfilesOfAllUsers(),
|
||||
'groups' => GroupModel::getAllGroups()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,4 +34,10 @@ class AdminController extends Controller
|
||||
|
||||
Redirect::to("admin");
|
||||
}
|
||||
|
||||
public function changeUserGroup()
|
||||
{
|
||||
GroupModel::setUserGroup(Request::post('user_id'), Request::post('group_id'));
|
||||
Redirect::to("admin");
|
||||
}
|
||||
}
|
||||
|
||||
16
application/controller/DirectoryController.php
Normal file
16
application/controller/DirectoryController.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
class DirectoryController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->View->render('directory/index', [
|
||||
'users' => UserModel::getUsersWithGroups()
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,9 @@ class RegisterController extends Controller
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if (LoginModel::isUserLoggedIn()) {
|
||||
Redirect::home();
|
||||
} else {
|
||||
$this->View->render('register/index');
|
||||
}
|
||||
// only admins can access registration; reuse existing admin auth check
|
||||
Auth::checkAdminAuthentication();
|
||||
$this->View->render('register/index');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,13 +33,12 @@ class RegisterController extends Controller
|
||||
*/
|
||||
public function register_action()
|
||||
{
|
||||
$registration_successful = RegistrationModel::registerNewUser();
|
||||
// enforce admin-only for registration
|
||||
Auth::checkAdminAuthentication();
|
||||
|
||||
if ($registration_successful) {
|
||||
Redirect::to('login/index');
|
||||
} else {
|
||||
Redirect::to('register/index');
|
||||
}
|
||||
RegistrationModel::registerNewUser();
|
||||
|
||||
Redirect::to('admin/index');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,13 +59,12 @@ class RegisterController extends Controller
|
||||
/**
|
||||
* Generate a captcha, write the characters into $_SESSION['captcha'] and returns a real image which will be used
|
||||
* like this: <img src="......./login/showCaptcha" />
|
||||
* IMPORTANT: As this action is called via <img ...> AFTER the real application has finished executing (!), the
|
||||
* SESSION["captcha"] has no content when the application is loaded. The SESSION["captcha"] gets filled at the
|
||||
* moment the end-user requests the <img .. >
|
||||
* Maybe refactor this sometime.
|
||||
*
|
||||
* This method is now deprecated as Captcha is no longer used in the registration process.
|
||||
*/
|
||||
public function showCaptcha()
|
||||
{
|
||||
CaptchaModel::generateAndShowCaptcha();
|
||||
// Captcha no longer used
|
||||
Redirect::to('register/index');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
class Config
|
||||
{
|
||||
// this is public to allow better Unit Testing
|
||||
public static $config;
|
||||
|
||||
public static function get($key)
|
||||
{
|
||||
if (!self::$config) {
|
||||
|
||||
$config_file = '../application/config/config.' . Environment::get() . '.php';
|
||||
$config_file = __DIR__ . '/../config/config.' . Environment::get() . '.php';
|
||||
|
||||
if (!file_exists($config_file)) {
|
||||
return false;
|
||||
|
||||
@@ -21,44 +21,76 @@
|
||||
*/
|
||||
class DatabaseFactory
|
||||
{
|
||||
private static $factory;
|
||||
private $database;
|
||||
private static $factory;
|
||||
private $database;
|
||||
|
||||
public static function getFactory()
|
||||
{
|
||||
if (!self::$factory) {
|
||||
self::$factory = new DatabaseFactory();
|
||||
}
|
||||
return self::$factory;
|
||||
public static function getFactory()
|
||||
{
|
||||
if (!self::$factory) {
|
||||
self::$factory = new DatabaseFactory();
|
||||
}
|
||||
return self::$factory;
|
||||
}
|
||||
|
||||
public function getConnectionWithMySQLI()
|
||||
{
|
||||
if (!$this->database) {
|
||||
// Throw exceptions and prevent also throwing credentials.
|
||||
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
|
||||
|
||||
try {
|
||||
$host = Config::get('DB_HOST');
|
||||
$user = Config::get('DB_USER');
|
||||
$pass = Config::get('DB_PASS');
|
||||
$name = Config::get('DB_NAME');
|
||||
$port = (int) Config::get('DB_PORT');
|
||||
$charset = Config::get('DB_CHARSET') ? Config::get('DB_CHARSET') : 'utf8mb4';
|
||||
|
||||
$this->database = new mysqli($host, $user, $pass, $name, $port);
|
||||
|
||||
// Set charset (important for security + correct encoding)
|
||||
$this->database->set_charset($charset);
|
||||
} catch (mysqli_sql_exception $e) {
|
||||
echo 'Database connection can not be estabilished. Please try again later.' . '<br>';
|
||||
echo 'Error code: ' . $e->getCode();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
public function getConnection() {
|
||||
if (!$this->database) {
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check DB connection in try/catch block. Also when PDO is not constructed properly,
|
||||
* prevent to exposing database host, username and password in plain text as:
|
||||
* PDO->__construct('mysql:host=127....', 'root', '12345678', Array)
|
||||
* by throwing custom error message
|
||||
*/
|
||||
try {
|
||||
$options = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ, PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING);
|
||||
$this->database = new PDO(
|
||||
Config::get('DB_TYPE') . ':host=' . Config::get('DB_HOST') . ';dbname=' .
|
||||
Config::get('DB_NAME') . ';port=' . Config::get('DB_PORT') . ';charset=' . Config::get('DB_CHARSET'),
|
||||
Config::get('DB_USER'), Config::get('DB_PASS'), $options
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
public function getConnection()
|
||||
{
|
||||
if (!$this->database) {
|
||||
|
||||
// Echo custom message. Echo error code gives you some info.
|
||||
echo 'Database connection can not be estabilished. Please try again later.' . '<br>';
|
||||
echo 'Error code: ' . $e->getCode();
|
||||
/**
|
||||
* Check DB connection in try/catch block. Also when PDO is not constructed properly,
|
||||
* prevent to exposing database host, username and password in plain text as:
|
||||
* PDO->__construct('mysql:host=127....', 'root', '12345678', Array)
|
||||
* by throwing custom error message
|
||||
*/
|
||||
try {
|
||||
$options = array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ, PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING);
|
||||
$this->database = new PDO(
|
||||
Config::get('DB_TYPE') . ':host=' . Config::get('DB_HOST') . ';dbname=' .
|
||||
Config::get('DB_NAME') . ';port=' . Config::get('DB_PORT') . ';charset=' . Config::get('DB_CHARSET'),
|
||||
Config::get('DB_USER'),
|
||||
Config::get('DB_PASS'),
|
||||
$options
|
||||
);
|
||||
} catch (PDOException $e) {
|
||||
|
||||
// Stop application :(
|
||||
// No connection, reached limit connections etc. so no point to keep it running
|
||||
exit;
|
||||
}
|
||||
}
|
||||
return $this->database;
|
||||
// Echo custom message. Echo error code gives you some info.
|
||||
echo 'Database connection can not be estabilished. Please try again later.' . '<br>';
|
||||
echo 'Error code: ' . $e->getCode();
|
||||
|
||||
// Stop application :(
|
||||
// No connection, reached limit connections etc. so no point to keep it running
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->database;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,29 +2,16 @@
|
||||
|
||||
class Text
|
||||
{
|
||||
private static $texts;
|
||||
public static $texts;
|
||||
|
||||
public static function get($key, $data = null)
|
||||
public static function get($key)
|
||||
{
|
||||
// if not $key
|
||||
if (!$key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($data) {
|
||||
foreach ($data as $var => $value) {
|
||||
${$var} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// load config file (this is only done once per application lifecycle)
|
||||
if (!self::$texts) {
|
||||
self::$texts = require('../application/config/texts.php');
|
||||
self::$texts = require(__DIR__ . '/../config/texts.php');
|
||||
}
|
||||
|
||||
// check if array key exists
|
||||
if (!array_key_exists($key, self::$texts)) {
|
||||
return null;
|
||||
return "TEXT NOT FOUND";
|
||||
}
|
||||
|
||||
return self::$texts[$key];
|
||||
|
||||
@@ -6,6 +6,17 @@
|
||||
*/
|
||||
class View
|
||||
{
|
||||
/* Resolve all the deprecation errors happening in the views, since they cant be disabled by error_reporting level in 8.4.*/
|
||||
public $redirect;
|
||||
public $users;
|
||||
public $notes;
|
||||
public $user_name;
|
||||
public $user_email;
|
||||
public $user_gravatar_image_url;
|
||||
public $user_avatar_file;
|
||||
public $avatar_file_path;
|
||||
public $user_account_type;
|
||||
|
||||
/**
|
||||
* simply includes (=shows) the view. this is done from the controller. In the controller, you usually say
|
||||
* $this->view->render('help/index'); to show (in this example) the view index.php in the folder help.
|
||||
|
||||
54
application/model/GroupModel.php
Normal file
54
application/model/GroupModel.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
class GroupModel
|
||||
{
|
||||
public static function getAllGroups()
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
$sql = "SELECT group_id, group_name FROM user_groups ORDER BY group_id";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute();
|
||||
return $query->fetchAll();
|
||||
}
|
||||
|
||||
public static function getGroupNameById($group_id)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
$sql = "SELECT group_name FROM user_groups WHERE group_id = :gid LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':gid' => $group_id));
|
||||
$row = $query->fetch();
|
||||
return $row ? $row->group_name : null;
|
||||
}
|
||||
|
||||
public static function setUserGroup($userId, $groupId)
|
||||
{
|
||||
if (!is_numeric($userId) || !is_numeric($groupId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not allow changing own group via admin UI to prevent lockout
|
||||
if ((int)$userId === (int)Session::get('user_id')) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_ACCOUNT_CANT_DELETE_SUSPEND_OWN'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only allow groups that exist in lookup
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
$check = $database->prepare("SELECT 1 FROM user_groups WHERE group_id = :gid LIMIT 1");
|
||||
$check->execute([':gid' => $groupId]);
|
||||
if ($check->rowCount() !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = $database->prepare("UPDATE users SET user_account_type = :gid WHERE user_id = :uid LIMIT 1");
|
||||
$query->execute([':gid' => $groupId, ':uid' => $userId]);
|
||||
|
||||
if ($query->rowCount() === 1) {
|
||||
Session::add('feedback_positive', 'Benutzergruppe aktualisiert.');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -6,115 +6,114 @@
|
||||
*/
|
||||
class NoteModel
|
||||
{
|
||||
/**
|
||||
* Get all notes (notes are just example data that the user has created)
|
||||
* @return array an array with several objects (the results)
|
||||
*/
|
||||
public static function getAllNotes()
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
/**
|
||||
* Get all notes (notes are just example data that the user has created)
|
||||
* @return array an array with several objects (the results)
|
||||
*/
|
||||
public static function getAllNotes()
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "SELECT user_id, note_id, note_text FROM notes WHERE user_id = :user_id";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':user_id' => Session::get('user_id')));
|
||||
$sql = "SELECT user_id, note_id, note_text FROM notes WHERE user_id = :user_id";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':user_id' => Session::get('user_id')));
|
||||
|
||||
// fetchAll() is the PDO method that gets all result rows
|
||||
return $query->fetchAll();
|
||||
// fetchAll() is the PDO method that gets all result rows
|
||||
return $query->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single note
|
||||
* @param int $note_id id of the specific note
|
||||
* @return object a single object (the result)
|
||||
*/
|
||||
public static function getNote($note_id)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnectionWithMySQLI();
|
||||
|
||||
$sql = "SELECT user_id, note_id, note_text FROM notes WHERE user_id = :user_id AND note_id = :note_id LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':user_id' => Session::get('user_id'), ':note_id' => $note_id));
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a note (create a new one)
|
||||
* @param string $note_text note text that will be created
|
||||
* @return bool feedback (was the note created properly ?)
|
||||
*/
|
||||
public static function createNote($note_text)
|
||||
{
|
||||
if (!$note_text || strlen($note_text) == 0) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_NOTE_CREATION_FAILED'));
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single note
|
||||
* @param int $note_id id of the specific note
|
||||
* @return object a single object (the result)
|
||||
*/
|
||||
public static function getNote($note_id)
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "SELECT user_id, note_id, note_text FROM notes WHERE user_id = :user_id AND note_id = :note_id LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':user_id' => Session::get('user_id'), ':note_id' => $note_id));
|
||||
$sql = "INSERT INTO notes (note_text, user_id) VALUES (:note_text, :user_id)";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':note_text' => $note_text, ':user_id' => Session::get('user_id')));
|
||||
|
||||
// fetch() is the PDO method that gets a single result
|
||||
return $query->fetch();
|
||||
if ($query->rowCount() == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a note (create a new one)
|
||||
* @param string $note_text note text that will be created
|
||||
* @return bool feedback (was the note created properly ?)
|
||||
*/
|
||||
public static function createNote($note_text)
|
||||
{
|
||||
if (!$note_text || strlen($note_text) == 0) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_NOTE_CREATION_FAILED'));
|
||||
return false;
|
||||
}
|
||||
// default return
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_NOTE_CREATION_FAILED'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "INSERT INTO notes (note_text, user_id) VALUES (:note_text, :user_id)";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':note_text' => $note_text, ':user_id' => Session::get('user_id')));
|
||||
|
||||
if ($query->rowCount() == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// default return
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_NOTE_CREATION_FAILED'));
|
||||
return false;
|
||||
/**
|
||||
* Update an existing note
|
||||
* @param int $note_id id of the specific note
|
||||
* @param string $note_text new text of the specific note
|
||||
* @return bool feedback (was the update successful ?)
|
||||
*/
|
||||
public static function updateNote($note_id, $note_text)
|
||||
{
|
||||
if (!$note_id || !$note_text) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing note
|
||||
* @param int $note_id id of the specific note
|
||||
* @param string $note_text new text of the specific note
|
||||
* @return bool feedback (was the update successful ?)
|
||||
*/
|
||||
public static function updateNote($note_id, $note_text)
|
||||
{
|
||||
if (!$note_id || !$note_text) {
|
||||
return false;
|
||||
}
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
$sql = "UPDATE notes SET note_text = :note_text WHERE note_id = :note_id AND user_id = :user_id LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':note_id' => $note_id, ':note_text' => $note_text, ':user_id' => Session::get('user_id')));
|
||||
|
||||
$sql = "UPDATE notes SET note_text = :note_text WHERE note_id = :note_id AND user_id = :user_id LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':note_id' => $note_id, ':note_text' => $note_text, ':user_id' => Session::get('user_id')));
|
||||
|
||||
if ($query->rowCount() == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_NOTE_EDITING_FAILED'));
|
||||
return false;
|
||||
if ($query->rowCount() == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a specific note
|
||||
* @param int $note_id id of the note
|
||||
* @return bool feedback (was the note deleted properly ?)
|
||||
*/
|
||||
public static function deleteNote($note_id)
|
||||
{
|
||||
if (!$note_id) {
|
||||
return false;
|
||||
}
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_NOTE_EDITING_FAILED'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "DELETE FROM notes WHERE note_id = :note_id AND user_id = :user_id LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':note_id' => $note_id, ':user_id' => Session::get('user_id')));
|
||||
|
||||
if ($query->rowCount() == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// default return
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_NOTE_DELETION_FAILED'));
|
||||
return false;
|
||||
/**
|
||||
* Delete a specific note
|
||||
* @param int $note_id id of the note
|
||||
* @return bool feedback (was the note deleted properly ?)
|
||||
*/
|
||||
public static function deleteNote($note_id)
|
||||
{
|
||||
if (!$note_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "DELETE FROM notes WHERE note_id = :note_id AND user_id = :user_id LIMIT 1";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':note_id' => $note_id, ':user_id' => Session::get('user_id')));
|
||||
|
||||
if ($query->rowCount() == 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// default return
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_NOTE_DELETION_FAILED'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,101 +13,68 @@ class RegistrationModel
|
||||
*
|
||||
* @return boolean Gives back the success status of the registration
|
||||
*/
|
||||
public static function registerNewUser()
|
||||
public static function registerNewUser($isAdmin = false)
|
||||
{
|
||||
// clean the input
|
||||
$user_name = strip_tags(Request::post('user_name'));
|
||||
$user_email = strip_tags(Request::post('user_email'));
|
||||
$user_email_repeat = strip_tags(Request::post('user_email_repeat'));
|
||||
$user_password_new = Request::post('user_password_new');
|
||||
$user_password_repeat = Request::post('user_password_repeat');
|
||||
// Use 'user_password' if provided (admin registration), otherwise 'user_password_new'
|
||||
$user_password_new = $isAdmin ? Request::post('user_password_new') : Request::post('user_password_new');
|
||||
$user_password_repeat = $user_password_new; // no repeat field
|
||||
|
||||
// stop registration flow if registrationInputValidation() returns false (= anything breaks the input check rules)
|
||||
$validation_result = self::registrationInputValidation(Request::post('captcha'), $user_name, $user_password_new, $user_password_repeat, $user_email, $user_email_repeat);
|
||||
if (!$validation_result) {
|
||||
return false;
|
||||
}
|
||||
// validate using existing validators and messages
|
||||
$valid = true;
|
||||
if (!self::validateUserName($user_name)) { $valid = false; }
|
||||
if (!self::validateUserEmail($user_email, $user_email)) { $valid = false; }
|
||||
if (!self::validateUserPassword($user_password_new, $user_password_repeat)) { $valid = false; }
|
||||
if (!$valid) { return false; }
|
||||
|
||||
// crypt the password with the PHP 5.5's password_hash() function, results in a 60 character hash string.
|
||||
// @see php.net/manual/en/function.password-hash.php for more, especially for potential options
|
||||
// hash the password
|
||||
$user_password_hash = password_hash($user_password_new, PASSWORD_DEFAULT);
|
||||
|
||||
// make return a bool variable, so both errors can come up at once if needed
|
||||
$return = true;
|
||||
|
||||
// check if username already exists
|
||||
if (UserModel::doesUsernameAlreadyExist($user_name)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USERNAME_ALREADY_TAKEN'));
|
||||
$return = false;
|
||||
}
|
||||
|
||||
// check if email already exists
|
||||
if (UserModel::doesEmailAlreadyExist($user_email)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_USER_EMAIL_ALREADY_TAKEN'));
|
||||
$return = false;
|
||||
}
|
||||
|
||||
// if Username or Email were false, return false
|
||||
if (!$return) return false;
|
||||
|
||||
// generate random hash for email verification (40 bytes)
|
||||
$user_activation_hash = bin2hex(random_bytes(40));
|
||||
// directly activate user: set empty activation hash
|
||||
$user_activation_hash = null;
|
||||
|
||||
// write user data to database
|
||||
if (!self::writeNewUserToDatabase($user_name, $user_password_hash, $user_email, time(), $user_activation_hash)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_ACCOUNT_CREATION_FAILED'));
|
||||
return false; // no reason not to return false here
|
||||
}
|
||||
|
||||
// get user_id of the user that has been created, to keep things clean we DON'T use lastInsertId() here
|
||||
$user_id = UserModel::getUserIdByUsername($user_name);
|
||||
|
||||
if (!$user_id) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_UNKNOWN_ERROR'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// send verification email
|
||||
if (self::sendVerificationEmail($user_id, $user_email, $user_activation_hash)) {
|
||||
Session::add('feedback_positive', Text::get('FEEDBACK_ACCOUNT_SUCCESSFULLY_CREATED'));
|
||||
return true;
|
||||
}
|
||||
|
||||
// if verification email sending failed: instantly delete the user
|
||||
self::rollbackRegistrationByUserId($user_id);
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_VERIFICATION_MAIL_SENDING_FAILED'));
|
||||
return false;
|
||||
Session::add('feedback_positive', Text::get('FEEDBACK_ACCOUNT_SUCCESSFULLY_CREATED'));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the registration input
|
||||
*
|
||||
* @param $captcha
|
||||
* @param $user_name
|
||||
* @param $user_password_new
|
||||
* @param $user_password_repeat
|
||||
* @param $user_email
|
||||
* @param $user_email_repeat
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function registrationInputValidation($captcha, $user_name, $user_password_new, $user_password_repeat, $user_email, $user_email_repeat)
|
||||
public static function registrationInputValidation($user_name, $user_password_new, $user_email)
|
||||
{
|
||||
$return = true;
|
||||
|
||||
// perform all necessary checks
|
||||
if (!CaptchaModel::checkCaptcha($captcha)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_CAPTCHA_WRONG'));
|
||||
if (empty($user_name) || empty($user_password_new) || empty($user_email)) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_FIELDS_EMPTY'));
|
||||
$return = false;
|
||||
}
|
||||
|
||||
// if username, email and password are all correctly validated, but make sure they all run on first sumbit
|
||||
if (self::validateUserName($user_name) AND self::validateUserEmail($user_email, $user_email_repeat) AND self::validateUserPassword($user_password_new, $user_password_repeat) AND $return) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// otherwise, return false
|
||||
return false;
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,11 +148,7 @@ class RegistrationModel
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strlen($user_password_new) < 6) {
|
||||
Session::add('feedback_negative', Text::get('FEEDBACK_PASSWORD_TOO_SHORT'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// no minimum length restriction
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -204,16 +167,23 @@ class RegistrationModel
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
// write new users data into database
|
||||
$sql = "INSERT INTO users (user_name, user_password_hash, user_email, user_creation_timestamp, user_activation_hash, user_provider_type)
|
||||
VALUES (:user_name, :user_password_hash, :user_email, :user_creation_timestamp, :user_activation_hash, :user_provider_type)";
|
||||
// write new users data into database; set user_active=1 and user_activation_hash to provided value (can be null)
|
||||
$sql = "INSERT INTO users (user_name, user_password_hash, user_email, user_creation_timestamp, user_activation_hash, user_provider_type, user_active)
|
||||
VALUES (:user_name, :user_password_hash, :user_email, :user_creation_timestamp, :user_activation_hash, :user_provider_type, 1)";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute(array(':user_name' => $user_name,
|
||||
':user_password_hash' => $user_password_hash,
|
||||
':user_email' => $user_email,
|
||||
':user_creation_timestamp' => $user_creation_timestamp,
|
||||
':user_activation_hash' => $user_activation_hash,
|
||||
':user_provider_type' => 'DEFAULT'));
|
||||
try {
|
||||
$query->execute(array(
|
||||
':user_name' => $user_name,
|
||||
':user_password_hash' => $user_password_hash,
|
||||
':user_email' => $user_email,
|
||||
':user_creation_timestamp' => $user_creation_timestamp,
|
||||
':user_activation_hash' => $user_activation_hash,
|
||||
':user_provider_type' => 'DEFAULT'
|
||||
));
|
||||
} catch (PDOException $e) {
|
||||
// only one feedback message on failure
|
||||
return false;
|
||||
}
|
||||
$count = $query->rowCount();
|
||||
if ($count == 1) {
|
||||
return true;
|
||||
|
||||
@@ -19,7 +19,7 @@ class UserModel
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "SELECT user_id, user_name, user_email, user_active, user_has_avatar, user_deleted FROM users";
|
||||
$sql = "SELECT user_id, user_name, user_email, user_active, user_has_avatar, user_deleted, user_account_type FROM users";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute();
|
||||
|
||||
@@ -39,11 +39,46 @@ class UserModel
|
||||
$all_users_profiles[$user->user_id]->user_active = $user->user_active;
|
||||
$all_users_profiles[$user->user_id]->user_deleted = $user->user_deleted;
|
||||
$all_users_profiles[$user->user_id]->user_avatar_link = (Config::get('USE_GRAVATAR') ? AvatarModel::getGravatarLinkByEmail($user->user_email) : AvatarModel::getPublicAvatarFilePathOfUser($user->user_has_avatar, $user->user_id));
|
||||
$all_users_profiles[$user->user_id]->user_account_type = $user->user_account_type;
|
||||
}
|
||||
|
||||
return $all_users_profiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets list of users including their group name via user_groups lookup.
|
||||
* @return array
|
||||
*/
|
||||
public static function getUsersWithGroups()
|
||||
{
|
||||
$database = DatabaseFactory::getFactory()->getConnection();
|
||||
|
||||
$sql = "SELECT u.user_id, u.user_name, u.user_email, u.user_active, u.user_has_avatar, u.user_deleted, u.user_account_type,
|
||||
g.group_name
|
||||
FROM users u
|
||||
LEFT JOIN user_groups g ON g.group_id = u.user_account_type";
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute();
|
||||
|
||||
$result = [];
|
||||
foreach ($query->fetchAll() as $user) {
|
||||
array_walk_recursive($user, 'Filter::XSSFilter');
|
||||
|
||||
$obj = new stdClass();
|
||||
$obj->user_id = $user->user_id;
|
||||
$obj->user_name = $user->user_name;
|
||||
$obj->user_email = $user->user_email;
|
||||
$obj->user_active = $user->user_active;
|
||||
$obj->user_deleted = $user->user_deleted;
|
||||
$obj->user_account_type = $user->user_account_type;
|
||||
$obj->group_name = $user->group_name;
|
||||
$obj->user_avatar_link = (Config::get('USE_GRAVATAR') ? AvatarModel::getGravatarLinkByEmail($user->user_email) : AvatarModel::getPublicAvatarFilePathOfUser($user->user_has_avatar, $user->user_id));
|
||||
|
||||
$result[] = $obj;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a user's profile data, according to the given $user_id
|
||||
* @param int $user_id The user's id
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
<li <?php if (View::checkForActiveController($filename, "profile")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>profile/index">Profiles</a>
|
||||
</li>
|
||||
<li <?php if (View::checkForActiveController($filename, "directory")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>directory/index">Benutzer</a>
|
||||
</li>
|
||||
<?php if (Session::userIsLoggedIn()) { ?>
|
||||
<li <?php if (View::checkForActiveController($filename, "dashboard")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>dashboard/index">Dashboard</a>
|
||||
@@ -36,9 +39,6 @@
|
||||
<li <?php if (View::checkForActiveControllerAndAction($filename, "login/index")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>login/index">Login</a>
|
||||
</li>
|
||||
<li <?php if (View::checkForActiveControllerAndAction($filename, "register/index")) { echo ' class="active" '; } ?> >
|
||||
<a href="<?php echo Config::get('URL'); ?>register/index">Register</a>
|
||||
</li>
|
||||
<?php } ?>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<td>User's email</td>
|
||||
<td>Activated ?</td>
|
||||
<td>Link to user's profile</td>
|
||||
<td>Group</td>
|
||||
<td>suspension Time in days</td>
|
||||
<td>Soft delete</td>
|
||||
<td>Submit</td>
|
||||
@@ -41,6 +42,19 @@
|
||||
<td>
|
||||
<a href="<?= Config::get('URL') . 'profile/showProfile/' . $user->user_id; ?>">Profile</a>
|
||||
</td>
|
||||
<td>
|
||||
<form action="<?= Config::get('URL'); ?>admin/changeUserGroup" method="post">
|
||||
<input type="hidden" name="user_id" value="<?= $user->user_id; ?>" />
|
||||
<select name="group_id">
|
||||
<?php foreach ($this->groups as $group) { ?>
|
||||
<option value="<?= $group->group_id; ?>" <?= (isset($user->user_account_type) && (int)$user->user_account_type === (int)$group->group_id ? 'selected' : '') ?>>
|
||||
<?= (int)$group->group_id; ?> - <?= htmlspecialchars($group->group_name, ENT_QUOTES, 'UTF-8'); ?>
|
||||
</option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
<input type="submit" value="Save" />
|
||||
</form>
|
||||
</td>
|
||||
<form action="<?= config::get("URL"); ?>admin/actionAccountSettings" method="post">
|
||||
<td><input type="number" name="suspension" /></td>
|
||||
<td><input type="checkbox" name="softDelete" <?php if ($user->user_deleted) { ?> checked <?php } ?> /></td>
|
||||
@@ -53,5 +67,15 @@
|
||||
<?php } ?>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3>Register a new user</h3>
|
||||
|
||||
<form method="post" action="<?php echo Config::get('URL'); ?>register/register_action">
|
||||
<input type="text" name="user_name" placeholder="Username" required />
|
||||
<input type="email" name="user_email" placeholder="Email address" required />
|
||||
<input type="password" name="user_password_new" placeholder="Password" required autocomplete="off" />
|
||||
<input type="submit" value="Register User" />
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
54
application/view/directory/index.php
Normal file
54
application/view/directory/index.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<div class="container">
|
||||
<h1>Benutzerverzeichnis</h1>
|
||||
|
||||
<div class="box">
|
||||
<?php $this->renderFeedbackMessages(); ?>
|
||||
|
||||
<table id="users-table" class="overview-table" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Id</td>
|
||||
<td>Avatar</td>
|
||||
<td>Benutzername</td>
|
||||
<td>Email</td>
|
||||
<td>Aktiv?</td>
|
||||
<td>Gruppe</td>
|
||||
<td>Profil</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($this->users as $user) { ?>
|
||||
<tr class="<?= ($user->user_active == 0 ? 'inactive' : 'active'); ?>">
|
||||
<td><?= $user->user_id; ?></td>
|
||||
<td class="avatar">
|
||||
<?php if (isset($user->user_avatar_link)) { ?>
|
||||
<img src="<?= $user->user_avatar_link; ?>"/>
|
||||
<?php } ?>
|
||||
</td>
|
||||
<td><?= $user->user_name; ?></td>
|
||||
<td><?= $user->user_email; ?></td>
|
||||
<td><?= ($user->user_active == 0 ? 'Nein' : 'Ja'); ?></td>
|
||||
<td><?= htmlspecialchars($user->group_name ?: $user->user_account_type, ENT_QUOTES, 'UTF-8'); ?></td>
|
||||
<td><a href="<?= Config::get('URL') . 'profile/showProfile/' . $user->user_id; ?>">Profil</a></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- jQuery & DataTables CDN -->
|
||||
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.8/css/jquery.dataTables.min.css">
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdn.datatables.net/1.13.8/js/jquery.dataTables.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('#users-table').DataTable({
|
||||
pageLength: 10,
|
||||
order: [[ 0, 'asc' ]],
|
||||
language: {
|
||||
url: 'https://cdn.datatables.net/plug-ins/1.13.8/i18n/de-DE.json'
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -38,13 +38,6 @@
|
||||
<a href="<?php echo Config::get('URL'); ?>login/requestPasswordReset">I forgot my password</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- register box on right side -->
|
||||
<div class="register-box">
|
||||
<h2>No account yet ?</h2>
|
||||
<a href="<?php echo Config::get('URL'); ?>register/index">Register</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,30 +9,10 @@
|
||||
|
||||
<!-- register form -->
|
||||
<form method="post" action="<?php echo Config::get('URL'); ?>register/register_action">
|
||||
<!-- the user name input field uses a HTML5 pattern check -->
|
||||
<input type="text" pattern="[a-zA-Z0-9]{2,64}" name="user_name" placeholder="Username (letters/numbers, 2-64 chars)" required />
|
||||
<input type="text" name="user_email" placeholder="email address (a real address)" required />
|
||||
<input type="text" name="user_email_repeat" placeholder="repeat email address (to prevent typos)" required />
|
||||
<input type="password" name="user_password_new" pattern=".{6,}" placeholder="Password (6+ characters)" required autocomplete="off" />
|
||||
<input type="password" name="user_password_repeat" pattern=".{6,}" required placeholder="Repeat your password" autocomplete="off" />
|
||||
|
||||
<!-- show the captcha by calling the login/showCaptcha-method in the src attribute of the img tag -->
|
||||
<img id="captcha" src="<?php echo Config::get('URL'); ?>register/showCaptcha" />
|
||||
<input type="text" name="captcha" placeholder="Please enter above characters" required />
|
||||
|
||||
<!-- quick & dirty captcha reloader -->
|
||||
<a href="#" style="display: block; font-size: 11px; margin: 5px 0 15px 0; text-align: center"
|
||||
onclick="document.getElementById('captcha').src = '<?php echo Config::get('URL'); ?>register/showCaptcha?' + Math.random(); return false">Reload Captcha</a>
|
||||
|
||||
<input type="submit" value="Register" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<p style="display: block; font-size: 11px; color: #999;">
|
||||
Please note: This captcha will be generated when the img tag requests the captcha-generation
|
||||
(= a real image) from YOURURL/register/showcaptcha. As this is a client-side triggered request, a
|
||||
$_SESSION["captcha"] dump will not show the captcha characters. The captcha generation
|
||||
happens AFTER the request that generates THIS page has been finished.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
1374
composer.lock
generated
1374
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@
|
||||
|
||||
// auto-loading the classes (currently only from application/libs) via Composer's PSR-4 auto-loader
|
||||
// later it might be useful to use a namespace here, but for now let's keep it as simple as possible
|
||||
require '../vendor/autoload.php';
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
// start our application
|
||||
new Application();
|
||||
|
||||
11
public/router.php
Normal file
11
public/router.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
if (php_sapi_name() === 'cli-server') {
|
||||
$filePath = __DIR__ . parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||
if (is_file($filePath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$_GET['url'] = isset($_SERVER['PATH_INFO']) ? ltrim($_SERVER['PATH_INFO'], '/') : '';
|
||||
require __DIR__ . '/index.php';
|
||||
Reference in New Issue
Block a user