=7.0), * PDO and (PDO) SQLite3 * 2. place this file in your webroot (you even can rename it, too!) * 3. configure (if needed, standard values work as well) everything * from line 68 to 72 * 3.1 $dbname is the location of the SQLite3 database. * default: atajlio.db in the same directory as this script * 3.2 $minLinkLength is the length of the generated shortlinks * 3.3 $defaultUrl is the redirect URL when no arguments are given * (no shortlink,...) * tip: use your homepage or so * 4. configure a URL rewrite to this script and the GET parameter * ?l=... * e.g. https://vgapps.de/-id1234 --> index.php?l=id1234 * 5. use this pattern to configure $linkPrefix * e.g. private static $linkPrefix = 'https://vgapps.de/-'; * 6. ---------------- VERY IMPORTANT! ---------------- * SECURE THE DATABASE: DENY ACCESS FROM OUTSIDE! * * -------------------------------------------------------------------- * * LICENSE (MIT-License): * Copyright 2018 Viktor Garske * * Permission is hereby granted, free of charge, * to any person obtaining a copy of this software * and associated documentation files (the "Software"), * to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice * shall be included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Configuration class * Configure your settings here */ class Config { private static $dbtype = 'sqlite'; private static $dbname = './atajlio.db'; /*private static $dbhost = ''; private static $dbuser = ''; private static $dbpass = '';*/ private static $minLinkLength = 4; private static $defaultUrl = ''; private static $linkPrefix = ''; public static function getConfig() { return array( 'dbtype' => self::$dbtype, 'dbname' => self::$dbname, /*'dbhost' => self::$dbhost, 'dbuser' => self::$dbuser, 'dbpass' => self::$dbpass, --- NOT READY --- */ 'minlinklength' => self::$minLinkLength, ); } public static function getMinLinkLength() { return static::$minLinkLength; } public static function getLinkPrefix() { return static::$linkPrefix; } public static function getAdminUrl() { $fs = new FirstSetup(); if(!$fs->checkSetupNeeded()) { // Database init $db = new Database(); if($db->connect()) $pdo = $db->getPdo(); else return false; $sql = Database::getSelectConfigValueByKeySql(); $statement = $pdo->prepare($sql); $statement->execute([':key' => 'admin_url']); $result = []; while($row = $statement->fetch(PDO::FETCH_ASSOC)) { $result[] = [ 'key' => $row['key'], 'value' => $row['value'] ]; } if(sizeof($result) > 0) { return $result[0]['value']; } else { echo "No key"; return false; } } else { echo "Error E-GAU-DB: Setup error: database not available"; } } public static function getDefaultUrl() { if(!static::$defaultUrl == '') return $this->defaultUrl; else return false; } } /** * Checks whether database is existing and creates it when needed */ class FirstSetup { private $setupNeeded = false; private $adminUrl; function __construct() { $config = Config::getConfig(); if($config['dbtype'] == 'sqlite') { if(!file_exists($config['dbname'])) { $this->setupNeeded = true; } } } public function checkSetupNeeded() { return $this->setupNeeded; } private function getSqlSchema() { return " BEGIN TRANSACTION; CREATE TABLE IF NOT EXISTS `link` ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, `shortlink` TEXT NOT NULL UNIQUE, `url` TEXT NOT NULL, `created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `counter` INTEGER ); CREATE TABLE IF NOT EXISTS `config` ( `key` TEXT NOT NULL, `value` TEXT, PRIMARY KEY(`key`) ); COMMIT; "; } public function setup() { if(!$this->setupNeeded) return false; $db = new Database(); if($db->connect()) $pdo = $db->getPdo(); else return false; // create the tables $pdo->exec($this->getSqlSchema()); // schema version $stmt_schemaversion = $pdo->prepare(Database::getNewConfigValueSql()); $stmt_schemaversion->bindValue(':key', 'schema_version'); $stmt_schemaversion->bindValue(':value', '1'); $stmt_schemaversion->execute(); // admin url $this->adminUrl = StringGenerator::generateRandomString(16); $stmt_adminurl = $pdo->prepare(Database::getNewConfigValueSql()); $stmt_adminurl->bindValue(':key', 'admin_url'); $stmt_adminurl->bindValue(':value', $this->adminUrl); $stmt_adminurl->execute(); return true; } public function getAdminUrl() { return $this->adminUrl; } } /** * Database methods */ class Database { private $pdo; private $config; function __construct() { $this->config = Config::getConfig(); } public function connect() { if($this->pdo == NULL) { try { if($this->config['dbtype'] == 'sqlite') { $this->pdo = new PDO('sqlite:' . $this->config['dbname']); return true; } } catch (PDOException $e) { echo "Error while establishing connection: " . $e; } } return false; } public function getPdo() { if($this->pdo != NULL) return $this->pdo; } public static function getNewConfigValueSql() { return "INSERT INTO config(key, value) VALUES(:key, :value)"; } public static function getNewUrlSql() { return "INSERT INTO link(shortlink, url, counter) VALUES(:shortlink, :url, 0)"; } public static function getSelectUrlByShortlinkSql() { return "SELECT * FROM link WHERE shortlink = :shortlink"; } public static function getSelectConfigValueByKeySql() { return "SELECT * FROM config WHERE key = :key"; } } /** * StringGenerator provides methods for createing a random string */ class StringGenerator { /* Source and author: Stephen Watkins, StackOverflow community https://stackoverflow.com/a/4356295/3994578 License: https://creativecommons.org/licenses/by-sa/3.0/ */ public static function generateRandomString($length = 10) { $characters = '0123456789'; $characters .= 'abcdefghijklmnopqrstuvwxyz'; $characters .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; $charactersLength = strlen($characters); $randomString = ''; for ($i = 0; $i < $length; $i++) { $randomString .= $characters[rand(0, $charactersLength - 1)]; } return $randomString; } } /** * Link represents an object carrying the original and shortened URL */ class Link { private $url; private $shortlink; private $pdo; private $valid = false; function __construct($url, $customShortlink = NULL, $pdo = NULL) { $this->url = $url; if(filter_var($url, FILTER_VALIDATE_URL)) { $this->valid = true; if($customShortlink == NULL) { $this->generateShortlink(); } else { // query whether shortlink isn't in use $query = new Query($customShortlink, $this->pdo); if($query->getSuccess()) { $this->generateShortlink(); } else { $this->shortlink = $customShortlink; } } } else { return false; } // Database if($pdo == NULL) { $db = new Database(); if($db->connect()) $this->pdo = $db->getPdo(); else return false; } else { $this->pdo = $pdo; } } public function getUrl() { return $this->url; } public function getValidStatus() { return $this->valid; } public function getShortlink() { return ($this->valid ? $this->shortlink : false); } private function generateShortlink() { $linkProposal = ''; $query = false; /* generate link proposals for the shortlink until a not already used link is found */ do { $linkProposal = StringGenerator::generateRandomString( Config::getMinLinkLength() ); $query = new Query($linkProposal, $this->pdo); } while ($query->getSuccess()); // check whether used already $this->shortlink = $linkProposal; } public function save() { if($this->pdo == NULL) return false; $sql = Database::getNewUrlSql(); $statement = $this->pdo->prepare($sql); $statement->bindValue(':shortlink', $this->shortlink); $statement->bindValue(':url', $this->url); $statement->execute(); return true; } } /** * Query handler gets the shortlink query and returns the url if possible */ class Query { private $query; private $pdo; private $successful = false; private $url; function __construct($query, $pdo=NULL) { $this->query = $query; // Database if($pdo == NULL) { $db = new Database(); if($db->connect()) $this->pdo = $db->getPdo(); else return false; } else { $this->pdo = $pdo; } $this->handle(); } private function handle() { if($this->pdo == NULL) return false; $sql = Database::getSelectUrlByShortlinkSql(); $statement = $this->pdo->prepare($sql); $statement->execute([':shortlink' => $this->query]); $result = []; while($row = $statement->fetch(PDO::FETCH_ASSOC)) { $result[] = [ 'shortlink' => $row['shortlink'], 'url' => $row['url'] ]; } if(sizeof($result) > 0) { $this->url = $result[0]['url']; $this->successful = true; } } public function getSuccess() { return $this->successful; } public function getUrl() { if($this->successful) return $this->url; else return false; } } /** * The requests handler handles incoming requests */ class RequestHandler { private $getRequest = []; function __construct($getRequest) { $this->getRequest = $getRequest; $this->handle(); } private function handle() { if(array_key_exists('l', $this->getRequest)) { $value = htmlspecialchars($this->getRequest['l']); $query = new Query($value); if($query->getSuccess()) { HttpHelper::redirect($query->getUrl()); } else { if(Config::getAdminUrl() != false && Config::getAdminUrl() == $value) { /* Administration */ /* POST request -> creating a link */ if(isset($_POST['action']) && isset($_POST['url'])) { if($_POST['action'] == "newlink" && $_POST['url'] != "") { /* Create the link */ $link = new Link( $_POST['url'], ($_POST['customurl'] != '' ? $_POST['customurl'] : NULL) ); if($link->save()) { /* Return the shortlink */ echo "Your new link: " . Config::getLinkPrefix() . $link->getShortlink(); } else { echo "Something went wrong... Did you ". "use a valid URL?"; } } else { Template::getAdminPage(); } } else { Template::getAdminPage(); } } else { if(Config::getDefaultUrl() != false) { HttpHelper::redirect(Config::getDefaultUrl()); } else { echo "Couldn't find link."; } } } } } } class HttpHelper { public static function redirect($url) { Header('Location: ' . $url); } } class Template { public static function getAdminPage() { ?> Atajlio Admin

Atajlio Link-Shortener

Welcome at Atajlio!"; echo "

The initial configuration was successful!

"; echo "

Your secret admin URL for adding URLs is: " . $adminurl . "

"; echo "

You can start now. Have fun!

"; } } /* ------------------------------------------------------------------------------ */ /* check whether DB exists - otherwise try to create it */ $fs = new FirstSetup(); if($fs->checkSetupNeeded()) { if($fs->setup()) Template::getFirstStartPage($fs->getAdminUrl()); else echo "Error while initializing the database"; } /* handle incoming requests */ new RequestHandler($_GET); ?>