├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── components │ └── Hash.php ├── config.sample.php ├── helpers.php ├── routes.php └── views │ └── index.php ├── composer.json ├── composer.lock ├── public ├── .htaccess ├── css │ └── app.css ├── index.php └── js │ └── index.js └── urls.sql /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /app/config.php 3 | vendor 4 | /config.yaml 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - '5.6' 5 | - '7.0' 6 | - '7.1' 7 | 8 | before_install: 9 | - composer install 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2011 Mike Cao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ourls 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/takashiki/ourls/v/stable)](https://packagist.org/packages/takashiki/ourls) 4 | [![Total Downloads](https://poser.pugx.org/takashiki/ourls/downloads)](https://packagist.org/packages/takashiki/ourls) 5 | [![Latest Unstable Version](https://poser.pugx.org/takashiki/ourls/v/unstable)](https://packagist.org/packages/takashiki/ourls) 6 | [![License](https://poser.pugx.org/takashiki/ourls/license)](https://packagist.org/packages/takashiki/ourls) 7 | 8 | Ourls是一个基于发号和hashid的短网址服务,灵感来源于知乎上关于短址算法的一个讨论—— 9 | [http://www.zhihu.com/question/29270034](http://www.zhihu.com/question/29270034)。 10 | 11 | ## 特征/Feature 12 | 13 | Ourls会根据sha1值来判断原url在数据库中是否已存在,若不存在则新增记录后对记录id进行hash,产生短网址。 14 | 15 | Ourls会对输入的url进行标准化处理,若为缺少scheme的url,会默认自动加上`http://`, 16 | 并且会对url的query参数进行排序和urlencode等。 17 | 18 | ## 演示/Demo 19 | 20 | [在线演示/Online Demo](http://skyx.in) 21 | 22 | ## 安装/Install 23 | 24 | 下载源码后运行`composer install`安装依赖包,或者运行`composer create-project takashiki/ourls`。 25 | 26 | 然后将urls.sql导入数据库中,将app目录下config.sample.php重命名为config.php并按自己实际情况修改相关配置项。 27 | 28 | > git clone and composer install or composer create-project takashiki/ourls 29 | 30 | > import urls.sql to your database 31 | 32 | > rename app/config.sample.php to app/config.php 33 | 34 | > modify the config file according to your situation 35 | 36 | ### License 37 | 38 | Ourls is open-sourced software licensed under the 39 | [MIT license](http://opensource.org/licenses/MIT) 40 | -------------------------------------------------------------------------------- /app/components/Hash.php: -------------------------------------------------------------------------------- 1 | hashids = new Hashids( 14 | $params['salt'], 15 | $params['length'], 16 | $params['alphabet'] 17 | ); 18 | } 19 | 20 | public function encode($id) 21 | { 22 | return $this->hashids->encode($id); 23 | } 24 | 25 | public function decode($hash) 26 | { 27 | $id = $this->hashids->decode($hash); 28 | 29 | return $id ? $id[0] : false; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/config.sample.php: -------------------------------------------------------------------------------- 1 | true, 5 | 'base_url' => 'YourSiteUrl', 6 | 'hash' => [ 7 | 'salt' => 'SomeRandomKey', 8 | 'length' => 5, 9 | 'alphabet' => 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', 10 | ], 11 | 'db' => [ 12 | 'database_type' => 'mysql', 13 | 'database_name' => 'name', 14 | 'server' => 'localhost', 15 | 'username' => 'your_username', 16 | 'password' => 'your_password', 17 | 'charset' => 'utf8', 18 | 'port' => 3306, 19 | 'option' => [ 20 | PDO::ATTR_CASE => PDO::CASE_NATURAL, 21 | ], 22 | ], 23 | 'db_read' => [ 24 | 'database_type' => 'mysql', 25 | 'database_name' => 'name', 26 | 'server' => 'localhost', 27 | 'username' => 'your_username', 28 | 'password' => 'your_password', 29 | 'charset' => 'utf8', 30 | 'port' => 3306, 31 | 'option' => [ 32 | PDO::ATTR_CASE => PDO::CASE_NATURAL, 33 | ], 34 | ], 35 | 'settings' => [ 36 | 'external_js' => null, 37 | ], 38 | 'proxies' => [ 39 | '127.0.0.0/8', 40 | '10.0.0.0/8', 41 | '172.16.0.0/12', 42 | '192.168.0.0/16', 43 | 'fd00::/8', 44 | ], 45 | ]; 46 | -------------------------------------------------------------------------------- /app/helpers.php: -------------------------------------------------------------------------------- 1 | normalize(); 19 | if (filter_var(IdnaConvert::encodeString($url), FILTER_VALIDATE_URL) === false) { 20 | return false; 21 | } else { 22 | $fragment = parse_url($url, PHP_URL_FRAGMENT); 23 | 24 | return str_replace('#'.$fragment, '#'.urldecode($fragment), $url); 25 | } 26 | } 27 | } 28 | 29 | if (!function_exists('real_remote_addr')) { 30 | function real_remote_addr() 31 | { 32 | $ip = Flight::request()->ip; 33 | $proxy = Flight::request()->proxy_ip; 34 | if ('' != $proxy && Flight::get('proxies')->match($ip)) { 35 | return $proxy; 36 | } else { 37 | return $ip; 38 | } 39 | } 40 | } 41 | 42 | /* 43 | * Registers a class and set a variable to framework method. 44 | * 45 | * @param string $name Method name 46 | * @param string $class Class name 47 | * @param array $params Class initialization parameters 48 | * @param callback $callback Function to call after object instantiation 49 | * @throws \Exception If trying to map over a framework method 50 | */ 51 | Flight::map('instance', function ($name, $class, array $params = [], $callback = null) { 52 | Flight::register($name, $class, $params, $callback); 53 | Flight::set($name, Flight::{$name}()); 54 | }); 55 | -------------------------------------------------------------------------------- /app/routes.php: -------------------------------------------------------------------------------- 1 | query['url']); 9 | if ($url) { 10 | if (strpos($url, Flight::get('flight.base_url')) !== false) { 11 | Flight::json(['status' => 0, 'msg' => '该地址无法被缩短']); 12 | } else { 13 | $sha1 = sha1($url); 14 | $store = Flight::get('db_read')->select('urls', ['id'], [ 15 | 'sha1' => $sha1, 16 | ]); 17 | if (!$store) { 18 | $id = Flight::get('db')->insert('urls', [ 19 | 'sha1' => $sha1, 20 | 'url' => $url, 21 | 'create_at' => time(), 22 | 'creator' => ip2long(real_remote_addr()), 23 | ]); 24 | } else { 25 | $id = $store[0]['id']; 26 | } 27 | $s_url = Flight::get('flight.base_url').Flight::get('hash')->encode($id); 28 | Flight::json(['status' => 1, 's_url' => $s_url]); 29 | } 30 | } else { 31 | Flight::json(['status' => 0, 'msg' => '请传入正确的url']); 32 | } 33 | }); 34 | 35 | Flight::route('/expand', function () { 36 | $s_url = Flight::request()->query['s_url']; 37 | if ($s_url) { 38 | $hash = str_replace(Flight::get('flight.base_url'), '', $s_url); 39 | if (!preg_match('/^['.Flight::get('alphabet').']+$/', $hash)) { 40 | Flight::json(['status' => 0, 'msg' => '短址不正确']); 41 | } else { 42 | $id = Flight::get('hash')->decode($hash); 43 | if (!$id) { 44 | Flight::json(['status' => 0, 'msg' => '短址无法解析']); 45 | } else { 46 | $store = Flight::get('db_read')->select('urls', ['url'], [ 47 | 'id' => $id, 48 | ]); 49 | if (!$store) { 50 | Flight::json(['status' => 0, 'msg' => '地址不存在']); 51 | } else { 52 | Flight::json(['status' => 1, 'url' => $store[0]['url']]); 53 | } 54 | } 55 | } 56 | } 57 | }); 58 | 59 | Flight::route('/@hash', function ($hash) { 60 | $id = Flight::get('hash')->decode($hash); 61 | if (!$id) { 62 | Flight::notFound('短址无法解析'); 63 | } else { 64 | $store = Flight::get('db_read')->select('urls', ['url'], [ 65 | 'id' => $id, 66 | ]); 67 | if (!$store) { 68 | Flight::notFound('地址不存在'); 69 | } else { 70 | Flight::get('db')->update('urls', ['count[+]' => 1], [ 71 | 'id' => $id, 72 | ]); 73 | Flight::redirect($store[0]['url'], 302); 74 | } 75 | } 76 | }); 77 | 78 | Flight::map('notFound', function ($message) { 79 | Flight::response()->status(404) 80 | ->header('content-type', 'text/html; charset=utf-8') 81 | ->write( 82 | '

404 页面未找到

'. 83 | "

{$message}

". 84 | '

回到首页

'. 85 | str_repeat(' ', 512) 86 | ) 87 | ->send(); 88 | }); 89 | 90 | Flight::map('error', function (Exception $ex) { 91 | $message = Flight::get('flight.log_errors') ? $ex->getTraceAsString() : '出错了'; 92 | Flight::response()->status(500) 93 | ->header('content-type', 'text/html; charset=utf-8') 94 | ->write( 95 | '

500 服务器内部错误

'. 96 | "

{$message}

". 97 | '

回到首页

'. 98 | str_repeat(' ', 512) 99 | ) 100 | ->send(); 101 | }); 102 | -------------------------------------------------------------------------------- /app/views/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | Ourls 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Fork me on GitHub 34 | 35 | 36 |
37 |
38 |

Ourls

39 |

Url Shorten Service
基于发号加hash id的短网址服务

40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 | 48 |
49 |
50 | 51 | 52 |
53 |
54 |
55 |
56 |

© Ourls . Licensed under MIT license.

57 |
58 |
59 | 60 | 61 | 62 | 63 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "takashiki/ourls", 3 | "description": "A url shorten service system base on hash id", 4 | "keywords": ["url shorten", "hashids"], 5 | "homepage": "https://github.com/takashiki/ourls", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "takashiki", 10 | "email": "857995137@qq.com", 11 | "homepage": "http://blog.skyx.in/" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.6.4", 16 | "catfan/medoo": "^0.9.8", 17 | "etechnika/idna-convert": "^1.1", 18 | "glenscott/url-normalizer": "*", 19 | "hashids/hashids": "^2.0", 20 | "mikecao/flight": "^1.2", 21 | "wikimedia/ip-set": "1.1.0" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "app\\": "app/" 26 | } 27 | }, 28 | "config": { 29 | "preferred-install": "dist", 30 | "sort-packages": true, 31 | "optimize-autoloader": true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "d0cf054e4a3c1b0908683bb963b45130", 8 | "packages": [ 9 | { 10 | "name": "catfan/medoo", 11 | "version": "v0.9.8", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/catfan/Medoo.git", 15 | "reference": "920ae293ee87286f955a5265124509695e172434" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "http://packagist.phpcomposer.com/files/catfan/Medoo/920ae293ee87286f955a5265124509695e172434.zip", 20 | "reference": "920ae293ee87286f955a5265124509695e172434", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-pdo": "*", 25 | "php": ">=5.1" 26 | }, 27 | "suggest": { 28 | "ext-pdo-mysql": "For MySQL or MariaDB databases", 29 | "ext-pdo-pqsql": "For PostgreSQL databases", 30 | "ext-pdo-sqlite": "For SQLite databases", 31 | "ext-pdo_dblib": "For MSSQL or Sybase databases on Liunx/UNIX platform", 32 | "ext-pdo_oci": "For Oracle databases", 33 | "ext-pdo_sqlsrv": "For MSSQL databases on Windows platform" 34 | }, 35 | "type": "framework", 36 | "autoload": { 37 | "files": [ 38 | "medoo.php" 39 | ] 40 | }, 41 | "notification-url": "https://packagist.org/downloads/", 42 | "license": [ 43 | "MIT" 44 | ], 45 | "authors": [ 46 | { 47 | "name": "Angel Lai", 48 | "email": "angel.lai.cat@gmail.com" 49 | } 50 | ], 51 | "description": "The Lightest PHP database framework to accelerate development", 52 | "homepage": "http://medoo.in", 53 | "keywords": [ 54 | "database", 55 | "lightweight", 56 | "mssql", 57 | "mysql", 58 | "php framework", 59 | "sql", 60 | "sqlite" 61 | ], 62 | "time": "2015-02-11T03:54:23+00:00" 63 | }, 64 | { 65 | "name": "etechnika/idna-convert", 66 | "version": "1.2.0", 67 | "source": { 68 | "type": "git", 69 | "url": "https://github.com/etechnika/idna-convert.git", 70 | "reference": "e6c49ff6f13b18f2f22ed26ba4f6e870788b8033" 71 | }, 72 | "dist": { 73 | "type": "zip", 74 | "url": "https://files.phpcomposer.com/files/etechnika/idna-convert/e6c49ff6f13b18f2f22ed26ba4f6e870788b8033.zip", 75 | "reference": "e6c49ff6f13b18f2f22ed26ba4f6e870788b8033", 76 | "shasum": "" 77 | }, 78 | "require": { 79 | "php": ">=5.3.0" 80 | }, 81 | "require-dev": { 82 | "fabpot/php-cs-fixer": "~1.0", 83 | "mayflower/php-codebrowser": "~1.1", 84 | "pdepend/pdepend": "~2.0", 85 | "phing/phing": "~2.10", 86 | "phploc/phploc": "~2.0", 87 | "phpmd/phpmd": "~2.0", 88 | "phpunit/php-code-coverage": "~2.0", 89 | "phpunit/phpcov": "~2.0", 90 | "phpunit/phpunit": "~4.1", 91 | "satooshi/php-coveralls": "~0.6", 92 | "sebastian/phpcpd": "~2.0", 93 | "sebastian/phpdcd": "~1.0", 94 | "squizlabs/php_codesniffer": "~2.0" 95 | }, 96 | "suggest": { 97 | "monolog/monolog": "You should used orginal package mso/idna-convert" 98 | }, 99 | "type": "library", 100 | "autoload": { 101 | "psr-0": { 102 | "Etechnika\\IdnaConvert": "src/" 103 | } 104 | }, 105 | "notification-url": "https://packagist.org/downloads/", 106 | "license": [ 107 | "LGPL-2.1" 108 | ], 109 | "authors": [ 110 | { 111 | "name": "Tomasz Rutkowski", 112 | "email": "majorserwis+etechnika@gmail.com", 113 | "homepage": "http://etechnika.eu", 114 | "role": "Developer" 115 | } 116 | ], 117 | "description": "Etechnika.eu idna-convert", 118 | "homepage": "https://github.com/etechnika/idna-convert", 119 | "keywords": [ 120 | "lib" 121 | ], 122 | "time": "2016-01-28T19:20:39+00:00" 123 | }, 124 | { 125 | "name": "glenscott/url-normalizer", 126 | "version": "1.4.0", 127 | "source": { 128 | "type": "git", 129 | "url": "https://github.com/glenscott/url-normalizer.git", 130 | "reference": "b8e79d3360a1bd7182398c9956bd74d219ad1b3c" 131 | }, 132 | "dist": { 133 | "type": "zip", 134 | "url": "http://packagist.phpcomposer.com/files/glenscott/url-normalizer/b8e79d3360a1bd7182398c9956bd74d219ad1b3c.zip", 135 | "reference": "b8e79d3360a1bd7182398c9956bd74d219ad1b3c", 136 | "shasum": "" 137 | }, 138 | "require": { 139 | "ext-mbstring": "*", 140 | "php": ">=5.3.0" 141 | }, 142 | "type": "library", 143 | "autoload": { 144 | "psr-4": { 145 | "URL\\": "src/URL" 146 | } 147 | }, 148 | "notification-url": "https://packagist.org/downloads/", 149 | "license": [ 150 | "MIT" 151 | ], 152 | "authors": [ 153 | { 154 | "name": "Glen Scott", 155 | "email": "glen@glenscott.co.uk" 156 | } 157 | ], 158 | "description": "Syntax based normalization of URL's", 159 | "time": "2015-06-11T16:06:02+00:00" 160 | }, 161 | { 162 | "name": "hashids/hashids", 163 | "version": "2.0.3", 164 | "source": { 165 | "type": "git", 166 | "url": "https://github.com/ivanakimov/hashids.php.git", 167 | "reference": "28889ed83cdc91f4a55637daff0fb5c799eb324e" 168 | }, 169 | "dist": { 170 | "type": "zip", 171 | "url": "https://files.phpcomposer.com/files/ivanakimov/hashids.php/28889ed83cdc91f4a55637daff0fb5c799eb324e.zip", 172 | "reference": "28889ed83cdc91f4a55637daff0fb5c799eb324e", 173 | "shasum": "" 174 | }, 175 | "require": { 176 | "ext-bcmath": "*", 177 | "php": "^5.6.4 || ^7.0" 178 | }, 179 | "require-dev": { 180 | "phpunit/phpunit": "^5.6" 181 | }, 182 | "type": "library", 183 | "extra": { 184 | "branch-alias": { 185 | "dev-master": "2.1-dev" 186 | } 187 | }, 188 | "autoload": { 189 | "psr-4": { 190 | "Hashids\\": "src/" 191 | } 192 | }, 193 | "notification-url": "https://packagist.org/downloads/", 194 | "license": [ 195 | "MIT" 196 | ], 197 | "authors": [ 198 | { 199 | "name": "Ivan Akimov", 200 | "email": "ivan@barreleye.com", 201 | "homepage": "https://twitter.com/IvanAkimov" 202 | }, 203 | { 204 | "name": "Vincent Klaiber", 205 | "email": "hello@vinkla.com", 206 | "homepage": "https://vinkla.com" 207 | } 208 | ], 209 | "description": "Generate short, unique, non-sequential ids (like YouTube and Bitly) from numbers", 210 | "homepage": "http://hashids.org/php", 211 | "keywords": [ 212 | "bitly", 213 | "decode", 214 | "encode", 215 | "hash", 216 | "hashid", 217 | "hashids", 218 | "ids", 219 | "obfuscate", 220 | "youtube" 221 | ], 222 | "time": "2017-01-01T13:33:33+00:00" 223 | }, 224 | { 225 | "name": "mikecao/flight", 226 | "version": "v1.3.2", 227 | "source": { 228 | "type": "git", 229 | "url": "https://github.com/mikecao/flight.git", 230 | "reference": "c3c6f689099937e6e4ecf25b7fe8c780df1500f9" 231 | }, 232 | "dist": { 233 | "type": "zip", 234 | "url": "https://files.phpcomposer.com/files/mikecao/flight/c3c6f689099937e6e4ecf25b7fe8c780df1500f9.zip", 235 | "reference": "c3c6f689099937e6e4ecf25b7fe8c780df1500f9", 236 | "shasum": "" 237 | }, 238 | "require": { 239 | "php": ">=5.3.0" 240 | }, 241 | "require-dev": { 242 | "phpunit/phpunit": "~4.6" 243 | }, 244 | "type": "library", 245 | "autoload": { 246 | "files": [ 247 | "flight/autoload.php", 248 | "flight/Flight.php" 249 | ] 250 | }, 251 | "notification-url": "https://packagist.org/downloads/", 252 | "license": [ 253 | "MIT" 254 | ], 255 | "authors": [ 256 | { 257 | "name": "Mike Cao", 258 | "email": "mike@mikecao.com", 259 | "homepage": "http://www.mikecao.com/", 260 | "role": "Original Developer" 261 | } 262 | ], 263 | "description": "Flight is a fast, simple, extensible framework for PHP. Flight enables you to quickly and easily build RESTful web applications.", 264 | "homepage": "http://flightphp.com", 265 | "time": "2016-10-25T19:27:20+00:00" 266 | }, 267 | { 268 | "name": "wikimedia/ip-set", 269 | "version": "1.1.0", 270 | "source": { 271 | "type": "git", 272 | "url": "https://github.com/wikimedia/IPSet.git", 273 | "reference": "b71a3834b42e2bcb2d9fa037abbb654e82117a01" 274 | }, 275 | "dist": { 276 | "type": "zip", 277 | "url": "https://files.phpcomposer.com/files/wikimedia/IPSet/b71a3834b42e2bcb2d9fa037abbb654e82117a01.zip", 278 | "reference": "b71a3834b42e2bcb2d9fa037abbb654e82117a01", 279 | "shasum": "" 280 | }, 281 | "require": { 282 | "php": ">=5.3.3" 283 | }, 284 | "require-dev": { 285 | "jakub-onderka/php-parallel-lint": "0.9.2", 286 | "mediawiki/mediawiki-codesniffer": "0.5.1", 287 | "phpunit/phpunit": "4.7.2" 288 | }, 289 | "type": "library", 290 | "autoload": { 291 | "classmap": [ 292 | "src/" 293 | ] 294 | }, 295 | "notification-url": "https://packagist.org/downloads/", 296 | "license": [ 297 | "GPL-2.0+" 298 | ], 299 | "authors": [ 300 | { 301 | "name": "Brandon Black", 302 | "email": "blblack@gmail.com" 303 | } 304 | ], 305 | "description": "Efficiently match IP addresses against a set of CIDR specifications.", 306 | "homepage": "https://github.com/wikimedia/IPSet", 307 | "time": "2016-02-12T15:19:10+00:00" 308 | } 309 | ], 310 | "packages-dev": [], 311 | "aliases": [], 312 | "minimum-stability": "stable", 313 | "stability-flags": [], 314 | "prefer-stable": false, 315 | "prefer-lowest": false, 316 | "platform": { 317 | "php": ">=5.5.0" 318 | }, 319 | "platform-dev": [] 320 | } 321 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{REQUEST_FILENAME} !-f 3 | RewriteCond %{REQUEST_FILENAME} !-d 4 | RewriteRule ^(.*)$ index.php [QSA,L] -------------------------------------------------------------------------------- /public/css/app.css: -------------------------------------------------------------------------------- 1 | .header { 2 | text-align: center; 3 | } 4 | 5 | .header h1 { 6 | font-size: 200%; 7 | color: #333; 8 | margin-top: 30px; 9 | } -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 |