├── database ├── migrations │ ├── .gitkeep │ ├── 2017_12_21_095425_create_api_quota_index.php │ ├── 2017_02_08_003907_alter_link_clicks_to_integer.php │ ├── 2015_11_04_015813_create_link_table.php │ ├── 2015_11_04_015823_create_users_table.php │ ├── 2016_12_27_232934_create_clicks_table.php │ └── 2017_02_04_025727_add_link_table_indexes.php ├── seeds │ └── DatabaseSeeder.php └── factories │ └── ModelFactory.php ├── resources ├── views │ ├── .gitkeep │ ├── errors │ │ ├── generic.blade.php │ │ ├── 404.blade.php │ │ └── 500.blade.php │ ├── error.blade.php │ ├── notice.blade.php │ ├── layouts │ │ ├── errors.blade.php │ │ ├── minimal.blade.php │ │ └── base.blade.php │ ├── snippets │ │ ├── user_table.blade.php │ │ ├── link_table.blade.php │ │ ├── modals.blade.php │ │ └── navbar.blade.php │ ├── lost_password.blade.php │ ├── shorten_result.blade.php │ ├── reset_password.blade.php │ ├── setup_thanks.blade.php │ ├── login.blade.php │ ├── emails │ │ ├── lost_password.blade.php │ │ └── activation.blade.php │ ├── signup.blade.php │ ├── about.blade.php │ ├── index.blade.php │ ├── link_stats.blade.php │ ├── env.blade.php │ └── admin.blade.php └── lang │ ├── en │ └── validation.php │ └── zh │ └── validation.php ├── app ├── Console │ ├── Commands │ │ └── .gitkeep │ └── Kernel.php ├── Helpers │ ├── AdminHelper.php │ ├── CryptoHelper.php │ ├── BaseHelper.php │ ├── ApiHelper.php │ ├── ClickHelper.php │ ├── StatsHelper.php │ ├── UserHelper.php │ └── LinkHelper.php ├── Models │ ├── Click.php │ ├── User.php │ └── Link.php ├── Events │ └── Event.php ├── Listeners │ └── Listener.php ├── Providers │ ├── AppServiceProvider.php │ └── EventServiceProvider.php ├── Http │ ├── Middleware │ │ ├── ExampleMiddleware.php │ │ ├── VerifyCsrfToken.php │ │ └── ApiMiddleware.php │ ├── Controllers │ │ ├── StaticPageController.php │ │ ├── IndexController.php │ │ ├── Api │ │ │ ├── ApiController.php │ │ │ ├── ApiLinkController.php │ │ │ └── ApiAnalyticsController.php │ │ ├── Controller.php │ │ ├── AdminController.php │ │ ├── StatsController.php │ │ ├── LinkController.php │ │ └── AdminPaginationController.php │ └── routes.php ├── Jobs │ └── Job.php ├── Factories │ ├── UserFactory.php │ └── LinkFactory.php └── Exceptions │ ├── Api │ └── ApiException.php │ └── Handler.php ├── docs ├── user-guide │ ├── troubleshooting.md │ └── upgrading.md ├── logo.png ├── about │ ├── contributors.md │ ├── contributing.md │ └── license.md ├── css │ └── base.css ├── index.md └── developer-guide │ ├── libraries.md │ └── api_errors.md ├── .jshintrc ├── storage ├── app │ └── .gitignore ├── logs │ └── .gitignore └── framework │ ├── cache │ └── .gitignore │ ├── views │ └── .gitignore │ └── sessions │ └── .gitignore ├── public ├── img │ ├── logo.png │ └── setup.jpg ├── js │ ├── constants.js │ ├── about.js │ ├── reset_password.js │ ├── SetupCtrl.js │ ├── api.js │ ├── shorten_result.js │ ├── base.js │ ├── index.js │ ├── toastr.min.js │ └── StatsCtrl.js ├── css │ ├── lost-password.css │ ├── user-panel.css │ ├── lost_password.css │ ├── reset_password.css │ ├── login.css │ ├── signup.css │ ├── shorten_result.css │ ├── stats.css │ ├── index.css │ ├── about.css │ ├── effects.css │ ├── base.css │ ├── admin.css │ ├── setup.css │ ├── toastr.min.css │ ├── jquery-jvectormap.css │ └── bootstrap-datetimepicker.min.css ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── fontawesome-webfont.woff2 │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── .htaccess ├── directives │ ├── editLongLinkModal.html │ └── editUserApiInfoModal.html └── index.php ├── .gitignore ├── util ├── restore_stock_env.sh └── version.py ├── .travis.yml ├── tests ├── LinkControllerTest.php ├── TestCase.php ├── AuthTest.php ├── IndexTest.php ├── BaseHelperTest.php ├── LinkHelperTest.php └── test_env ├── server.php ├── .env.setup ├── mkdocs.yml ├── .github ├── CONTRIBUTING.md └── ISSUE_TEMPLATE.md ├── phpunit.xml ├── composer.json ├── artisan ├── README.md ├── bootstrap └── app.php └── config └── geoip.php /database/migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/views/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Console/Commands/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/user-guide/troubleshooting.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true 3 | } 4 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywalker512/polr/HEAD/docs/logo.png -------------------------------------------------------------------------------- /resources/views/errors/generic.blade.php: -------------------------------------------------------------------------------- 1 | {{ $status_code }} {{ $status_message }} 2 | -------------------------------------------------------------------------------- /public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywalker512/polr/HEAD/public/img/logo.png -------------------------------------------------------------------------------- /public/img/setup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywalker512/polr/HEAD/public/img/setup.jpg -------------------------------------------------------------------------------- /public/js/constants.js: -------------------------------------------------------------------------------- 1 | /* Polr JS Constants */ 2 | 3 | const BASE_API_PATH = '/api/v2/'; 4 | -------------------------------------------------------------------------------- /public/css/lost-password.css: -------------------------------------------------------------------------------- 1 | .password-input { 2 | width: 200px; 3 | margin-bottom: 20px; 4 | } 5 | -------------------------------------------------------------------------------- /public/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywalker512/polr/HEAD/public/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | bootstrap/cache/ 3 | storage/ 4 | env 5 | .env 6 | .env.bak 7 | .env.example 8 | composer.phar 9 | -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywalker512/polr/HEAD/public/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywalker512/polr/HEAD/public/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywalker512/polr/HEAD/public/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywalker512/polr/HEAD/public/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /public/css/user-panel.css: -------------------------------------------------------------------------------- 1 | td { 2 | word-wrap: break-word; 3 | } 4 | table { 5 | width: 100%; 6 | table-layout:fixed; 7 | } 8 | -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywalker512/polr/HEAD/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywalker512/polr/HEAD/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /util/restore_stock_env.sh: -------------------------------------------------------------------------------- 1 | mv .env .env.bak 2 | wget https://raw.githubusercontent.com/cydrobolt/polr/master/.env.setup 3 | echo "Done!" 4 | -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywalker512/polr/HEAD/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywalker512/polr/HEAD/public/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /resources/views/error.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.errors') 2 | 3 | @section('content') 4 |

Error

5 |

{{$message}}

6 | @endsection 7 | -------------------------------------------------------------------------------- /resources/views/notice.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.base') 2 | 3 | @section('content') 4 |

提 示

5 |

{{$message}}

6 | @endsection 7 | -------------------------------------------------------------------------------- /app/Helpers/AdminHelper.php: -------------------------------------------------------------------------------- 1 | 404 5 |

This page could not be found.

6 | @endsection 7 | -------------------------------------------------------------------------------- /public/js/about.js: -------------------------------------------------------------------------------- 1 | $('#gpl-license').hide(); 2 | $('.license-btn').click(function () { 3 | $('#gpl-license').slideDown(); 4 | $('.license-btn').slideUp(); 5 | }); 6 | -------------------------------------------------------------------------------- /app/Models/Click.php: -------------------------------------------------------------------------------- 1 | 500 5 |

Oh dear. Something seems to have gone awry.

6 |

Please contact an administrator to report this issue.

7 | @endsection 8 | -------------------------------------------------------------------------------- /public/css/login.css: -------------------------------------------------------------------------------- 1 | .login-submit { 2 | width:80%; 3 | } 4 | 5 | .login-field { 6 | margin-bottom: 20px; 7 | } 8 | 9 | .login-prompts { 10 | padding-top: 15px; 11 | } 12 | 13 | .login-prompts small { 14 | display: block; 15 | } 16 | -------------------------------------------------------------------------------- /app/Helpers/CryptoHelper.php: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | @endsection 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - '5.6' 4 | - '7.0' 5 | services: 6 | - mysql 7 | before_script: 8 | - composer install 9 | - mv tests/test_env .env 10 | - mysql -e 'CREATE DATABASE polrci;' 11 | - php artisan migrate --force 12 | - composer install 13 | notifications: 14 | email: false 15 | -------------------------------------------------------------------------------- /public/css/signup.css: -------------------------------------------------------------------------------- 1 | .form-field { 2 | margin-bottom: 25px; 3 | } 4 | 5 | .right-col-one { 6 | margin-top: 83px; 7 | } 8 | 9 | .right-col-next { 10 | padding-top: 25px; 11 | } 12 | 13 | .title { 14 | padding-bottom: 20px; 15 | } 16 | 17 | .login-prompt { 18 | padding-top: 15px; 19 | } 20 | 21 | .g-recaptcha { 22 | margin-bottom: 2em; 23 | } 24 | -------------------------------------------------------------------------------- /public/js/reset_password.js: -------------------------------------------------------------------------------- 1 | $('#passwordConfirm').on('keyup', function() { 2 | var password = $('#passwordFirst').val(); 3 | var confirm_password = $('#passwordConfirm').val(); 4 | 5 | if (password != confirm_password) { 6 | this.setCustomValidity("Passwords do not match."); 7 | } else { 8 | this.setCustomValidity(''); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 用户名 5 | Email 6 | 注册时间 7 | 状态 8 | API 9 | 权限组 10 | 删除 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/about/contributors.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | ---------------- 3 | 4 | Polr was written and is maintained by [Chaoyi Zha](https://cydrobolt.com), 5 | but many other contributors have submitted code. 6 | Thank you to all who have submitted code, documentation, or bug reports to help make Polr 7 | a better project. 8 | 9 | [Contributors](https://github.com/cydrobolt/polr/graphs/contributors) 10 | -------------------------------------------------------------------------------- /public/css/shorten_result.css: -------------------------------------------------------------------------------- 1 | .result-box { 2 | width: 50%; 3 | display: block; 4 | margin : 0 auto; 5 | 6 | } 7 | 8 | .btn { 9 | margin-top: 30px; 10 | } 11 | 12 | .content-div { 13 | text-align: center; 14 | } 15 | 16 | .qr-code-container { 17 | display: none; 18 | margin-top: 2em; 19 | } 20 | 21 | .qr-code-container img { 22 | display: inline !important; 23 | } 24 | -------------------------------------------------------------------------------- /public/css/stats.css: -------------------------------------------------------------------------------- 1 | .stats-header h3 { 2 | text-align: center; 3 | } 4 | 5 | .bottom-padding { 6 | margin-bottom: 3em; 7 | } 8 | 9 | #mapChart { 10 | width: 100%; 11 | height: 32em; 12 | } 13 | 14 | .ng-root { 15 | margin-bottom: 4em; 16 | } 17 | 18 | h4 { 19 | display: inline-block; 20 | } 21 | 22 | .link-meta { 23 | word-wrap: break-word; 24 | overflow-wrap: break-word; 25 | } 26 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Redirect Trailing Slashes... 9 | RewriteRule ^(.*)/$ /$1 [L,R=301] 10 | 11 | # Handle Front Controller... 12 | RewriteCond %{REQUEST_FILENAME} !-d 13 | RewriteCond %{REQUEST_FILENAME} !-f 14 | RewriteRule ^ index.php [L] 15 | 16 | -------------------------------------------------------------------------------- /tests/LinkControllerTest.php: -------------------------------------------------------------------------------- 1 | call('GET', '/notexist'); 12 | $this->assertTrue($response->isRedirection()); 13 | $this->assertRedirectedTo(env('SETTING_INDEX_REDIRECT')); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call('UserTableSeeder'); 18 | 19 | Model::reguard(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Middleware/ExampleMiddleware.php: -------------------------------------------------------------------------------- 1 | $user_role, 'no_div_padding' => true]); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /public/css/index.css: -------------------------------------------------------------------------------- 1 | .custom-url-field { 2 | display: inline; 3 | width: 100px; 4 | } 5 | 6 | .site-url-field { 7 | color: green; 8 | display: inline; 9 | } 10 | 11 | .long-link-input { 12 | margin-bottom: 25px; 13 | } 14 | 15 | .tips { 16 | margin-top: 20px; 17 | } 18 | 19 | .check-btn { 20 | margin-bottom: 30px; 21 | } 22 | 23 | #link-availability-status { 24 | margin-bottom: 20px; 25 | } 26 | 27 | .visibility-toggler { 28 | margin-bottom: 2em; 29 | } 30 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'App\Listeners\EventListener', 17 | ], 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /docs/about/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | ------------------ 3 | 4 | Polr's source is available on GitHub, at [cydrobolt/polr](https://github.com/cydrobolt/polr) 5 | If you are familiar with PHP or any of the other technologies we use, your contribution would be greatly appreciated. We operate an IRC channel (`#polr`) on `irc.freenode.net:6667` for 6 | contributor collaboration and user support. 7 | 8 | For a list of tasks that may need help on, see [issues on GitHub](https://github.com/cydrobolt/polr/issues) 9 | -------------------------------------------------------------------------------- /tests/AuthTest.php: -------------------------------------------------------------------------------- 1 | visit('/') 12 | // ->type('polrci', 'username') 13 | // ->type('polrci', 'password ') 14 | // ->press('Sign In') 15 | // ->dontSee('name="login" value="Sign In" />') 16 | // ->see('>Dashboard'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /resources/views/snippets/link_table.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | @if ($table_id == "admin_links_table") 9 | {{-- Show action buttons only if admin view --}} 10 | 11 | 12 | 13 | @endif 14 | 15 | 16 |
短链接原地址点击数创建时间所有者禁用删除
17 | -------------------------------------------------------------------------------- /docs/css/base.css: -------------------------------------------------------------------------------- 1 | [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 2 | display: none !important; 3 | } 4 | 5 | h1, h2, h3, h4, h5, h6 { 6 | text-transform: none !important; 7 | } 8 | 9 | img { 10 | /* Force auto height & width to prevent deformed images on mobile */ 11 | width: auto !important; 12 | height: auto !important; 13 | } 14 | 15 | code:not(.hljs) { 16 | /* Do not wrap pre-formatted code snippets */ 17 | word-wrap: break-word; 18 | white-space: normal; 19 | } 20 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ![Polr Logo](logo.png) 2 | 3 | [![GitHub license](https://img.shields.io/badge/license-GPLv2%2B-blue.svg)](about/license) 4 | [![GitHub release](https://img.shields.io/github/release/cydrobolt/polr.svg)](https://github.com/cydrobolt/polr/releases) 5 | 6 | Polr is a beautiful, modern, lightweight, and minimalist open-source URL shortening application. It allows you to host your own URL shortener, to brand your URLs, and to gain control over your data. Polr is especially easy to use, and provides a modern, themable interface. 7 | -------------------------------------------------------------------------------- /docs/developer-guide/libraries.md: -------------------------------------------------------------------------------- 1 | ## Polr Libraries 2 | ----------------------- 3 | 4 | To interact with Polr's API, you may opt to use a library or write your own integration. 5 | As not all languages and frameworks are currently supported by a library, it is encouraged 6 | that you take a look at the [API documentation](api/) to integrate Polr into your application. 7 | 8 | ## Unofficial libraries 9 | ### Python 10 | - [mypolr](https://github.com/fauskanger/mypolr) is a Python 3 package for interacting with the Polr 2.0 API. ([Documentation](https://mypolr.readthedocs.io)) 11 | -------------------------------------------------------------------------------- /public/css/about.css: -------------------------------------------------------------------------------- 1 | .content-div { 2 | padding-top: 20px; 3 | text-align: left; 4 | } 5 | 6 | .license { 7 | font-size: 80%; 8 | margin-top: 40px; 9 | width: auto; 10 | color: black; 11 | } 12 | 13 | .license-btn { 14 | margin-top: 30px; 15 | margin-bottom: 3em; 16 | width: auto; 17 | } 18 | 19 | .logo-img { 20 | width: 17em; 21 | } 22 | 23 | .logo-well { 24 | width: 20.5em; 25 | 26 | -webkit-animation: colorpulse 20s infinite; 27 | animation: colorpulse 20s infinite; 28 | -moz-animation: colorpulse 20s infinite; 29 | } 30 | -------------------------------------------------------------------------------- /tests/IndexTest.php: -------------------------------------------------------------------------------- 1 | visit('/') 12 | ->see('

'. env('APP_NAME') .'

') // Ensure page loads correctly 13 | ->see('see('>Sign In') // Ensure log in buttons are shown when user is logged out 15 | ->dontSee('SQLSTATE'); // Ensure database connection is correct 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /resources/views/lost_password.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.base') 2 | 3 | @section('css') 4 | 5 | @endsection 6 | 7 | @section('content') 8 |

忘记密码

9 | 10 |
11 |
12 | 13 | 14 | 15 |
16 |
17 | @endsection 18 | -------------------------------------------------------------------------------- /public/js/SetupCtrl.js: -------------------------------------------------------------------------------- 1 | polr.directive('setupTooltip', function() { 2 | return { 3 | scope: { 4 | content: '@', 5 | }, 6 | replace: true, 7 | template: '' 8 | } 9 | }); 10 | 11 | polr.controller('SetupCtrl', function($scope) { 12 | $scope.init = function () { 13 | $('[data-toggle="popover"]').popover({ 14 | trigger: "hover", 15 | placement: "right" 16 | }); 17 | }; 18 | 19 | $scope.init(); 20 | }); 21 | -------------------------------------------------------------------------------- /resources/views/shorten_result.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.base') 2 | 3 | @section('css') 4 | 5 | @endsection 6 | 7 | @section('content') 8 |

短链接 已生成

9 | 10 | 创建二维码 11 | 再压缩一个 12 | 13 |
14 | 15 | @endsection 16 | 17 | 18 | @section('js') 19 | 20 | 21 | @endsection 22 | -------------------------------------------------------------------------------- /public/css/effects.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes colorpulse { 2 | 0% { 3 | background: #0bc0c2; } 4 | 25% { 5 | background: #7f8c8d; } 6 | 50% { 7 | background: #16a085; } 8 | 100% { 9 | background: #0bc0c2; } } 10 | @keyframes colorpulse { 11 | 0% { 12 | background: #0bc0c2; } 13 | 25% { 14 | background: #7f8c8d; } 15 | 50% { 16 | background: #16a085; } 17 | 100% { 18 | background: #0bc0c2; } } 19 | @-moz-keyframes colorpulse { 20 | 0% { 21 | background: #0bc0c2; } 22 | 25% { 23 | background: #7f8c8d; } 24 | 50% { 25 | background: #16a085; } 26 | 100% { 27 | background: #0bc0c2; } } 28 | -------------------------------------------------------------------------------- /public/js/api.js: -------------------------------------------------------------------------------- 1 | function apiCall(path, data, callback, fail_callback) { 2 | var base_api_path = BASE_API_PATH; 3 | var api_path = base_api_path + path; 4 | $.ajax({ 5 | url: api_path, 6 | data: data, 7 | method: 'POST', 8 | }).done(function(res) { 9 | if (callback) { 10 | callback(res); 11 | } 12 | }).fail(function (err) { 13 | if (fail_callback) { 14 | fail_callback(err); 15 | } 16 | }); 17 | } 18 | 19 | function res_value_to_text(res_val) { 20 | if (res_val == 1) { 21 | return 'True'; 22 | } 23 | else { 24 | return 'False'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docs/user-guide/upgrading.md: -------------------------------------------------------------------------------- 1 | # Upgrading Polr 2 | ----------------- 3 | To upgrade your Polr instance to the latest `master` or to a new release, you must back up your database and files before proceeding to avoid data loss. 4 | 5 | ## Upgrading from 2.x: 6 | 7 | - Back up your database and files 8 | - Update your files by using `git pull` or downloading a release 9 | - Run `composer install --no-dev -o` to ensure dependencies are up to date 10 | - Migrate database with `php artisan migrate` to ensure database structure is up to date 11 | 12 | ## Upgrading from 1.x: 13 | 14 | There are breaking changes between 2.x and 1.x; it is not yet possible to automatically upgrade to 2.x. 15 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | is('api/v*/action/*') || $request->is('api/v*/data/*')) { 15 | // Exclude public API from CSRF protection 16 | // but do not exclude private API endpoints 17 | return $next($request); 18 | } 19 | 20 | return parent::handle($request, $next); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | define(App\User::class, function ($faker) { 15 | return [ 16 | 'name' => $faker->name, 17 | 'email' => $faker->email, 18 | 'password' => str_random(10), 19 | 'remember_token' => str_random(10), 20 | ]; 21 | }); 22 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Polr Project 2 | pages: 3 | - Home: index.md 4 | - User Guide: 5 | - 'Installation': 'user-guide/installation.md' 6 | - 'Upgrading': 'user-guide/upgrading.md' 7 | - 'Troubleshooting': 'user-guide/troubleshooting.md' 8 | - Developer Guide: 9 | - 'Libraries': 'developer-guide/libraries.md' 10 | - 'API Documentation': 'developer-guide/api.md' 11 | - 'API Errors': 'developer-guide/api_errors.md' 12 | - About: 13 | - 'License': 'about/license.md' 14 | - 'Contributors': 'about/contributors.md' 15 | - 'Contributing': 'about/contributing.md' 16 | 17 | theme: readthedocs 18 | repo_url: https://github.com/cydrobolt/polr/ 19 | site_author: Chaoyi Zha 20 | extra_css: 21 | - 'css/base.css' 22 | -------------------------------------------------------------------------------- /resources/views/reset_password.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.base') 2 | 3 | @section('css') 4 | 5 | @endsection 6 | 7 | @section('content') 8 |

重置密码

9 | 10 |
11 |
12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | @endsection 20 | 21 | @section('js') 22 | 23 | @endsection 24 | -------------------------------------------------------------------------------- /app/Http/Controllers/IndexController.php: -------------------------------------------------------------------------------- 1 | to(env('SETTING_INDEX_REDIRECT')); 20 | } 21 | else { 22 | return redirect()->to(route('login')); 23 | } 24 | } 25 | 26 | return view('index', ['large' => true]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Jobs/Job.php: -------------------------------------------------------------------------------- 1 | index( 18 | ['created_at', 'creator', 'is_api'], 19 | 'api_quota_index' 20 | ); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::table('links', function (Blueprint $table) 32 | { 33 | $table->dropIndex('api_quota_index'); 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | We welcome contributions through pull requests on GitHub and encourage contributors to join our IRC channel on freenode, #polr 2 | 3 | Polr is written in PHP with the Lumen (Laravel-based) framework. As of 2016, we won't be officially supporting 1.x outside of fixing critical security bugs. The 1.x legacy branch uses MySQLi and requires the MySQL native driver -- whereas 2.x requires the PDO driver and uses the Eloquent ORM. 4 | 5 | You can run tests for Polr using `phpunit`, and we encourage all contributors submitting a pull request to run them beforehand. 6 | 7 | Polr is licensed under the __GPLv2+__ license: either version 2 of the GPL, or at your option, any later version. 8 | 9 | By contributing to Polr, you agree to have your work licensed under GPLv2+. 10 | 11 | Please be considerate towards others and respect all who participate in the community. 12 | 13 | Thank you so much for contributing! 14 | -------------------------------------------------------------------------------- /database/migrations/2017_02_08_003907_alter_link_clicks_to_integer.php: -------------------------------------------------------------------------------- 1 | integer('clicks')->change(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::table('links', function (Blueprint $table) 31 | { 32 | $table->string('clicks')->change(); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/js/base.js: -------------------------------------------------------------------------------- 1 | // AJAX settings 2 | $.ajaxSetup({ 3 | headers: { 4 | 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') 5 | } 6 | }); 7 | 8 | // Escape jQuery selectors 9 | function esc_selector(selector) { 10 | return selector.replace( /(:|\.|\[|\]|,)/g, "\\$1" ); 11 | } 12 | 13 | jQuery.fn.clearForm = function() { 14 | // http://stackoverflow.com/questions/6364289/clear-form-fields-with-jquery 15 | $(this).find('input').not(':button, :submit, :reset, :hidden') 16 | .val('') 17 | .removeAttr('checked') 18 | .removeAttr('selected'); 19 | 20 | return this; 21 | }; 22 | 23 | // Output helpful console message 24 | console.log('%cPolr', 'font-size:5em;color:green'); 25 | console.log('%cNeed help? Open a ticket: https://github.com/cydrobolt/polr', 'color:blue'); 26 | console.log('%cDocs: https://docs.polr.me', 'color:blue'); 27 | 28 | // Set up Angular module 29 | var polr = angular.module('polr',[]); 30 | -------------------------------------------------------------------------------- /app/Helpers/BaseHelper.php: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | -------------------------------------------------------------------------------- /tests/BaseHelperTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(true, self::checkBaseGen($n)); 36 | } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | run(); 29 | -------------------------------------------------------------------------------- /public/css/admin.css: -------------------------------------------------------------------------------- 1 | .admin-nav-item { 2 | margin-bottom: 5px; 3 | } 4 | 5 | .stats-icon { 6 | margin-left: 0.3em; 7 | } 8 | 9 | .change-password { 10 | width: 150px; 11 | } 12 | 13 | .change-password-btn { 14 | margin-top: 15px; 15 | } 16 | 17 | .password-box { 18 | margin-top: 10px; 19 | margin-bottom: 10px; 20 | } 21 | 22 | .hidden-metadata { 23 | display: none; 24 | } 25 | 26 | .api-quota { 27 | display: inline; 28 | } 29 | 30 | .users-heading { 31 | display: inline-block; 32 | margin-bottom: 0.9em; 33 | margin-right: 0.5em; 34 | } 35 | 36 | input.api-quota { 37 | display: inline; 38 | width: 9em; 39 | font-size: .85em; 40 | height: auto; 41 | padding-left: 0.8em; 42 | } 43 | 44 | .wrap-text { 45 | word-wrap: break-word; 46 | } 47 | 48 | @media (min-width: 700px) { 49 | table { 50 | table-layout: fixed; 51 | } 52 | } 53 | 54 | a.new-user-add { 55 | margin-left: 0.5em 56 | } 57 | 58 | .edit-long-link-btn { 59 | opacity: 0.45; 60 | } 61 | -------------------------------------------------------------------------------- /docs/about/license.md: -------------------------------------------------------------------------------- 1 | # License 2 | ---------------- 3 | 4 | Polr is licensed under the GPLv2+ 5 | 6 | ``` 7 | Copyright (C) 2013-2017 Chaoyi Zha 8 | 9 | This program is free software; you can redistribute it and/or 10 | modify it under the terms of the GNU General Public License 11 | as published by the Free Software Foundation; either version 2 12 | of the License, or (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program; if not, write to the Free Software 21 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 | ``` 23 | 24 | Full license text: [https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) 25 | -------------------------------------------------------------------------------- /app/Factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | username = $username; 20 | $user->password = $hashed_password; 21 | $user->email = $email; 22 | $user->recovery_key = $recovery_key; 23 | $user->active = $active; 24 | $user->ip = $ip; 25 | $user->role = $role; 26 | $user->api_key = $api_key; 27 | $user->api_active = $api_active; 28 | 29 | $user->save(); 30 | return $user; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /resources/views/setup_thanks.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.minimal') 2 | 3 | @section('title') 4 | Setup Completed 5 | @endsection 6 | 7 | @section('css') 8 | 9 | 10 | @endsection 11 | 12 | @section('content') 13 | 16 | 17 |
18 |
19 | 20 |
21 |
22 | 23 |
24 |

安装完成

25 |

你可以 登录 或者 26 | 访问 首页. 27 |

28 |

你可以访问 中文论坛寻求帮助。

29 |

感谢你使用 Polr!

30 |
31 | 32 |
33 |
34 | 35 | 36 | @endsection 37 | -------------------------------------------------------------------------------- /app/Helpers/ApiHelper.php: -------------------------------------------------------------------------------- 1 | setTimestamp($last_minute_unix); 16 | 17 | $user = UserHelper::getUserByUsername($username); 18 | 19 | if ($user) { 20 | $api_quota = $user->api_quota; 21 | } 22 | else { 23 | $api_quota = env('SETTING_ANON_API_QUOTA') ?: 5; 24 | } 25 | 26 | if ($api_quota < 0) { 27 | return false; 28 | } 29 | 30 | $links_last_minute = Link::where('is_api', 1) 31 | ->where('creator', $username) 32 | ->where('created_at', '>=', $last_minute) 33 | ->count(); 34 | 35 | return $links_last_minute >= $api_quota; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Models/Link.php: -------------------------------------------------------------------------------- 1 | attributes['long_url'] = $long_url; 18 | $this->attributes['long_url_hash'] = $crc32_hash; 19 | } 20 | 21 | public function scopeLongUrl($query, $long_url) { 22 | // Allow quick lookups with Link::longUrl that make use 23 | // of the indexed crc32 hash to quickly fetch link 24 | $crc32_hash = sprintf('%u', crc32($long_url)); 25 | 26 | return $query 27 | ->where('long_url_hash', $crc32_hash) 28 | ->where('long_url', $long_url); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/ApiController.php: -------------------------------------------------------------------------------- 1 | $action, 10 | "result" => $result 11 | ]; 12 | 13 | if ($response_type == 'json') { 14 | return response(json_encode($response)) 15 | ->header('Content-Type', 'application/json') 16 | ->header('Access-Control-Allow-Origin', '*'); 17 | } 18 | else { 19 | if ($plain_text_response) { 20 | // return alternative plain text response if provided 21 | $result = $plain_text_response; 22 | } 23 | // assume plain text if json not requested 24 | return response($result) 25 | ->header('Content-Type', 'text/plain') 26 | ->header('Access-Control-Allow-Origin', '*'); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /resources/views/snippets/modals.blade.php: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | app/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cydrobolt/polr", 3 | "description": "The Polr URL Shortener.", 4 | "keywords": ["url-shortener", "url", "cms"], 5 | "license": "GPLv2+", 6 | "type": "project", 7 | "require": { 8 | "php": ">=5.5.9", 9 | "laravel/lumen-framework": "5.1.*", 10 | "vlucas/phpdotenv": "~1.0", 11 | "illuminate/mail": "~5.1", 12 | "yajra/laravel-datatables-oracle": "~6.0", 13 | "paragonie/random_compat": "^1.0.6", 14 | "torann/geoip": "^1.0", 15 | "geoip2/geoip2": "^2.4", 16 | "nesbot/carbon": "^1.22", 17 | "doctrine/dbal": "^2.5", 18 | "google/recaptcha": "~1.1" 19 | }, 20 | "require-dev": { 21 | "fzaninotto/faker": "~1.0", 22 | "phpunit/phpunit": "^5.2", 23 | "symfony/css-selector": "^3.0" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "App\\": "app/" 28 | }, 29 | "classmap": [ 30 | "database/" 31 | ] 32 | }, 33 | "autoload-dev": { 34 | "classmap": [ 35 | "tests/" 36 | ] 37 | }, 38 | "config": { 39 | "preferred-install": "dist" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make( 32 | 'Illuminate\Contracts\Console\Kernel' 33 | ); 34 | 35 | exit($kernel->handle(new ArgvInput, new ConsoleOutput)); 36 | -------------------------------------------------------------------------------- /resources/views/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.base') 2 | 3 | @section('css') 4 | 5 | @endsection 6 | 7 | @section('content') 8 |
9 |

登 录



10 |
11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 27 |
28 |
29 |
30 |
19 | 20 | 21 | 22 | 23 | Polr @yield('title') 24 | @yield('css') 25 | 26 | 27 |
28 | @yield('content') 29 |
30 | 31 | 32 | @yield('js') 33 | 34 | 35 | -------------------------------------------------------------------------------- /database/migrations/2015_11_04_015813_create_link_table.php: -------------------------------------------------------------------------------- 1 | engine = 'InnoDB'; 19 | 20 | $table->increments('id'); 21 | 22 | $table->string('short_url'); 23 | $table->longText('long_url'); 24 | $table->string('ip'); 25 | $table->string('creator'); 26 | $table->string('clicks')->default(0); 27 | $table->string('secret_key'); 28 | 29 | $table->boolean('is_disabled')->default(0); 30 | $table->boolean('is_custom')->default(0); 31 | $table->boolean('is_api')->default(0); 32 | 33 | $table->timestamps(); 34 | }); 35 | } 36 | 37 | /** 38 | * Reverse the migrations. 39 | * 40 | * @return void 41 | */ 42 | public function down() 43 | { 44 | Schema::drop('links'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /util/version.py: -------------------------------------------------------------------------------- 1 | import re, datetime, string, random 2 | 3 | print "> New version name (e.g 2.0.0):" 4 | new_version = raw_input() 5 | 6 | print "> Is this a stable release? [y, n]" 7 | is_stable = raw_input() 8 | 9 | with open('.env.setup', 'r+') as setup_env: 10 | setup_env_lines = setup_env.read() 11 | now = datetime.datetime.now() 12 | 13 | new_setup_key = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits + string.punctuation) for _ in range(32)) 14 | 15 | # Update setup key 16 | setup_env_lines = re.sub(r'(?is)APP_KEY=[^\n]+', 'APP_KEY={}'.format(new_setup_key), setup_env_lines) 17 | # Update date and release in setup env 18 | setup_env_lines = re.sub(r'(?is)VERSION=[0-9a-zA-Z\.]+', 'VERSION={}'.format(new_version), setup_env_lines) 19 | setup_env_lines = re.sub(r'(?is)VERSION_RELMONTH=\w+', 'VERSION_RELMONTH={}'.format(now.strftime('%B')), setup_env_lines) 20 | setup_env_lines = re.sub(r'(?is)VERSION_RELDAY=\w+', 'VERSION_RELDAY={}'.format(now.day), setup_env_lines) 21 | setup_env_lines = re.sub(r'(?is)VERSION_RELYEAR=\w+', 'VERSION_RELYEAR={}'.format(now.year), setup_env_lines) 22 | 23 | # Overwite existing file 24 | setup_env.seek(0) 25 | setup_env.write(setup_env_lines) 26 | setup_env.truncate() 27 | 28 | print "Done!" 29 | -------------------------------------------------------------------------------- /database/migrations/2015_11_04_015823_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 19 | 20 | $table->string('username')->unique(); 21 | $table->string('password'); 22 | $table->string('email'); 23 | $table->text('ip'); 24 | 25 | $table->string('recovery_key'); 26 | $table->string('role'); 27 | $table->string('active'); 28 | 29 | $table->string('api_key')->nullable(); 30 | $table->boolean('api_active')->default(0); 31 | $table->string('api_quota')->default(60); 32 | 33 | $table->timestamps(); 34 | $table->softDeletes(); 35 | }); 36 | } 37 | 38 | /** 39 | * Reverse the migrations. 40 | * 41 | * @return void 42 | */ 43 | public function down() 44 | { 45 | Schema::drop('users'); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Helpers/ClickHelper.php: -------------------------------------------------------------------------------- 1 | getLocation($ip)->iso_code; 10 | return $country_iso; 11 | } 12 | 13 | static private function getHost($url) { 14 | // Return host given URL; NULL if host is 15 | // not found. 16 | return parse_url($url, PHP_URL_HOST); 17 | } 18 | 19 | static public function recordClick(Link $link, Request $request) { 20 | /** 21 | * Given a Link model instance and Request object, process post click operations. 22 | * @param Link model instance $link 23 | * @return boolean 24 | */ 25 | 26 | $ip = $request->ip(); 27 | $referer = $request->server('HTTP_REFERER'); 28 | 29 | $click = new Click; 30 | $click->link_id = $link->id; 31 | $click->ip = $ip; 32 | $click->country = self::getCountry($ip); 33 | $click->referer = $referer; 34 | $click->referer_host = ClickHelper::getHost($referer); 35 | $click->user_agent = $request->server('HTTP_USER_AGENT'); 36 | $click->save(); 37 | 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /resources/views/emails/lost_password.blade.php: -------------------------------------------------------------------------------- 1 |

中文

2 |

亲爱的 {{$username}} !

3 | 4 |

5 | 请点击下面的链接来重置您的密码! {{env('APP_NAME')}}. 6 |

7 | 8 | 9 | {{env('APP_PROTOCOL')}}{{env('APP_ADDRESS')}}/reset_password/{{$username}}/{{$recovery_key}} 10 | 11 | 12 |
13 | 14 |

感谢您的使用!

15 |

{{env('APP_NAME')}} 团队

16 | 17 | -- 18 |
19 | 因为有人在{{env('APP_PROTOCOL')}}{{env('APP_ADDRESS')}}请求重置密码,所以您才会收到这封邮件。 20 | 请求来自:IP {{$ip}} 21 | 如果不是您本人操作,请忽视本邮件,并提醒您修改您的密码。 22 | 23 |

English

24 |

Hello {{$username}}!

25 | 26 |

27 | You may use the link located in this email to reset your password for your 28 | account at {{env('APP_NAME')}}. 29 |

30 | 31 | 32 | {{env('APP_PROTOCOL')}}{{env('APP_ADDRESS')}}/reset_password/{{$username}}/{{$recovery_key}} 33 | 34 | 35 |
36 | 37 |

Thanks,

38 |

The {{env('APP_NAME')}} team.

39 | 40 | -- 41 |
42 | You received this email because someone with the IP {{$ip}} requested a password reset 43 | for an account at {{env('APP_PROTOCOL')}}{{env('APP_ADDRESS')}}. If this was not you, 44 | you may ignore this email. 45 | -------------------------------------------------------------------------------- /database/migrations/2016_12_27_232934_create_clicks_table.php: -------------------------------------------------------------------------------- 1 | engine = 'InnoDB'; 19 | 20 | $table->increments('id'); 21 | $table->string('ip'); 22 | $table->string('country')->nullable(); 23 | $table->string('referer')->nullable(); 24 | $table->string('referer_host')->nullable(); 25 | $table->text('user_agent')->nullable(); 26 | $table->integer('link_id')->unsigned(); 27 | 28 | $table->index('ip'); 29 | $table->index('referer_host'); 30 | $table->index('link_id'); 31 | $table->foreign('link_id')->references('id')->on('links')->onDelete('cascade'); 32 | 33 | $table->timestamps(); 34 | }); 35 | } 36 | /** 37 | * Reverse the migrations. 38 | * 39 | * @return void 40 | */ 41 | public function down() 42 | { 43 | Schema::drop('clicks'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Exceptions/Api/ApiException.php: -------------------------------------------------------------------------------- 1 | response_type = $response_type; 18 | $this->text_code = $text_code; 19 | parent::__construct($message, $status_code, $previous); 20 | } 21 | 22 | private function encodeJsonResponse($status_code, $message, $text_code) { 23 | $response = [ 24 | 'status_code' => $status_code, 25 | 'error_code' => $text_code, 26 | 'error' => $message 27 | ]; 28 | 29 | return json_encode($response); 30 | } 31 | 32 | public function getEncodedErrorMessage() { 33 | if ($this->response_type == 'json') { 34 | return $this->encodeJsonResponse($this->code, $this->message, $this->text_code); 35 | } 36 | else { 37 | return $this->code . ' ' . $this->message; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /resources/views/emails/activation.blade.php: -------------------------------------------------------------------------------- 1 |

中文

2 |

{{$username}} 恭喜您!

3 | 4 |

感谢您在 {{env('APP_NAME')}} 注册账号。 请点击下面的链接进行激活:

5 | 6 |
7 | 8 | 9 | {{env('APP_PROTOCOL')}}{{env('APP_ADDRESS')}}/activate/{{$username}}/{{$recovery_key}} 10 | 11 | 12 |
13 | 14 |

谢谢!

15 |

{{env('APP_NAME')}} 团队。

16 | 17 | -- 18 |
19 | You received this email because someone with the IP {{$ip}} signed up 20 | for an account at {{env('APP_PROTOCOL')}}{{env('APP_ADDRESS')}}. If this was not you, 21 | you may ignore this email. 22 | 23 |

English

24 |

Hello {{$username}}!

25 | 26 |

Thanks for registering at {{env('APP_NAME')}}. To use your account, 27 | you will need to activate it by clicking the following link:

28 | 29 |
30 | 31 | 32 | {{env('APP_PROTOCOL')}}{{env('APP_ADDRESS')}}/activate/{{$username}}/{{$recovery_key}} 33 | 34 | 35 |
36 | 37 |

Thanks,

38 |

The {{env('APP_NAME')}} team.

39 | 40 | -- 41 |
42 | You received this email because someone with the IP {{$ip}} signed up 43 | for an account at {{env('APP_PROTOCOL')}}{{env('APP_ADDRESS')}}. If this was not you, 44 | you may ignore this email. 45 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## Expected Behavior 5 | 6 | 7 | 8 | 9 | ## Current Behavior 10 | 11 | 12 | 13 | ## Possible Solution 14 | 15 | 16 | 17 | ## Steps to Reproduce (for bugs) 18 | 19 | 20 | 1. 21 | 2. 22 | 3. 23 | 4. 24 | 25 | ## Context 26 | 27 | 28 | 29 | ## Your Environment 30 | 31 | * Version or latest commit hash (`git rev-parse HEAD`): 32 | * Environment name and version (e.g. Chrome 39, PHP 7.0, etc): 33 | * Instance link (optional): 34 | -------------------------------------------------------------------------------- /public/css/setup.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Lobster); 2 | @import url(https://fonts.googleapis.com/css?family=Open+Sans); 3 | 4 | .navbar-brand { 5 | font-family: "Lobster"; 6 | } 7 | 8 | .setup-body { 9 | font-family: 'Open Sans', sans-serif; 10 | } 11 | 12 | .setup-logo { 13 | height: 70px; 14 | margin-bottom: 40px; 15 | } 16 | 17 | body { 18 | background-size: 100% 100%; 19 | background-attachment: fixed; 20 | background-position: center; 21 | background-repeat: no-repeat; 22 | background-image: url(/img/setup.jpg); 23 | } 24 | 25 | .setup-center { 26 | text-align: center; 27 | } 28 | 29 | .setup-form { 30 | margin: 0 auto; 31 | } 32 | 33 | .setup-item { 34 | width: 650px; 35 | } 36 | 37 | .setup-body { 38 | margin-top: 100px; 39 | } 40 | 41 | .setup-form-buttons { 42 | margin-top: 40px; 43 | margin-bottom: 25px; 44 | text-align: center; 45 | } 46 | 47 | .setup-form-buttons > input { 48 | width: 150px; 49 | margin-right: 20px; 50 | } 51 | 52 | .setup-footer { 53 | text-align: center; 54 | } 55 | 56 | .footer-link { 57 | color: grey; 58 | } 59 | 60 | .footer-link:hover { 61 | color: grey; 62 | } 63 | 64 | .footer-well { 65 | margin-top: 30px; 66 | } 67 | 68 | h4, p { 69 | margin-top: 20px; 70 | } 71 | 72 | .setup-qmark { 73 | width: 2em; 74 | margin-left: 0.3em; 75 | display: inline; 76 | } 77 | 78 | .popover-content { 79 | text-transform: none; 80 | } 81 | -------------------------------------------------------------------------------- /tests/LinkHelperTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(false, LinkHelper::checkIfAlreadyShortened($u)); 31 | } 32 | 33 | foreach ($shortened as $u) { 34 | $this->assertEquals(true, LinkHelper::checkIfAlreadyShortened($u)); 35 | } 36 | } 37 | 38 | public function testLinkExists() { 39 | $link = LinkFactory::createLink('http://example.com/ci', true, null, '127.0.0.1', false, true); 40 | // assert that existent link ending returns true 41 | $this->assertNotEquals(LinkHelper::linkExists($link->short_url), false); 42 | // assert that nonexistent link ending returns false 43 | $this->assertEquals(LinkHelper::linkExists('nonexistent'), false); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /public/directives/editUserApiInfoModal.html: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Polr Logo 2 | 3 | 4 | :aerial_tramway: 一个简单高效的网址缩短软件. 5 | 6 | [英文原版](https://github.com/cydrobolt/polr) - [GITHUB](https://github.com/skywalker512/polr) - [码云](https://gitee.com/skywalker512/polr) - [论坛](https://forum.flarumchina.org/t/polr) 7 | 8 | ### 快速开始 9 | 10 | 请到 https://forum.flarumchina.org/d/375 查看详情 11 | 12 | ### Demo 13 | 14 | To test out the demo, head to [demo.polr.me](http://demo.polr.me) and use the following credentials: 15 | 16 | - Username: `demo-admin` 17 | - Password: `demo-admin` 18 | 19 | ### 升级 20 | 21 | - 备份 22 | - 使用 `git pull` 23 | - 运行 `composer install --no-dev -o` 24 | - 运行 `php artisan migrate` 去确认数据库升级 25 | 26 | #### License 27 | 28 | 29 | Copyright (C) 2013-2018 Chaoyi Zha 30 | 31 | This program is free software; you can redistribute it and/or 32 | modify it under the terms of the GNU General Public License 33 | as published by the Free Software Foundation; either version 2 34 | of the License, or (at your option) any later version. 35 | 36 | This program is distributed in the hope that it will be useful, 37 | but WITHOUT ANY WARRANTY; without even the implied warranty of 38 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 39 | GNU General Public License for more details. 40 | 41 | You should have received a copy of the GNU General Public License 42 | along with this program; if not, write to the Free Software 43 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 44 | -------------------------------------------------------------------------------- /database/migrations/2017_02_04_025727_add_link_table_indexes.php: -------------------------------------------------------------------------------- 1 | unique('short_url'); 19 | $table->string('long_url_hash', 10)->nullable(); 20 | $table->index('long_url_hash', 'links_long_url_index'); 21 | }); 22 | 23 | // MySQL only statement 24 | // DB::statement("UPDATE links SET long_url_hash = crc32(long_url);"); 25 | 26 | DB::table('links')->select(['id', 'long_url_hash', 'long_url']) 27 | ->chunk(100, function($links) { 28 | foreach ($links as $link) { 29 | DB::table('links') 30 | ->where('id', $link->id) 31 | ->update([ 32 | 'long_url_hash' => sprintf('%u', crc32($link->long_url)) 33 | ]); 34 | } 35 | }); 36 | } 37 | 38 | public function down() 39 | { 40 | Schema::table('links', function (Blueprint $table) 41 | { 42 | $table->dropUnique('links_short_url_unique'); 43 | $table->dropIndex('links_long_url_index'); 44 | $table->dropColumn('long_url_hash'); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/test_env: -------------------------------------------------------------------------------- 1 | APP_ENV=production 2 | 3 | # Set to true if debugging 4 | APP_DEBUG=false 5 | 6 | # 32-character key (e.g 3EWBLwxTfh%*f&xRBqdGEIUVvn4%$Hfi) 7 | APP_KEY=a5bd6a61b1327670bedd712a358d9c2b 8 | 9 | # Your app's name (shown on interface) 10 | APP_NAME=Polr CI 11 | 12 | # Protocol to access your app. e.g https:// 13 | APP_PROTOCOL=http:// 14 | 15 | # Your app's external address (e.g example.com) 16 | APP_ADDRESS=travis.polr.me 17 | 18 | # Your app's bootstrap stylesheet 19 | # e.g https://maxcdn.bootstrapcdn.com/bootswatch/3.3.5/flatly/bootstrap.min.css 20 | APP_STYLESHEET=//maxcdn.bootstrapcdn.com/bootswatch/3.3.6/united/bootstrap.min.css 21 | 22 | # Set to today's date (e.g November 3, 2015) 23 | POLR_GENERATED_AT=Travis CI 24 | 25 | # Set to true after running setup script 26 | # e.g true 27 | POLR_SETUP_RAN=true 28 | 29 | DB_CONNECTION=mysql 30 | # Set to your DB host (e.g localhost) 31 | DB_HOST=localhost 32 | # DB port (e.g 3306) 33 | DB_PORT=3306 34 | # Set to your DB name (e.g polr) 35 | DB_DATABASE=polrci 36 | # DB credentials 37 | # e.g root 38 | DB_USERNAME=root 39 | DB_PASSWORD= 40 | 41 | # Polr Settings 42 | SETTING_PUBLIC_INTERFACE=true 43 | 44 | # Set to true to allow signups, false to disable (e.g true/false) 45 | POLR_ALLOW_ACCT_CREATION= 46 | 47 | # Set to true to require activation by email (e.g true/false) 48 | POLR_ACCT_ACTIVATION= 49 | 50 | SETTING_SHORTEN_PERMISSION=false 51 | SETTING_INDEX_REDIRECT= 52 | SETTING_PASSWORD_RECOV=false 53 | 54 | APP_LOCALE=en 55 | APP_FALLBACK_LOCALE=en 56 | 57 | CACHE_DRIVER=file 58 | SESSION_DRIVER=file 59 | QUEUE_DRIVER=database 60 | 61 | # Do not touch 62 | POLR_RELDATE=February 14, 2016 63 | POLR_VERSION=Travis CI 64 | POLR_BASE=32 65 | POLR_SECRET_BYTES=2 66 | -------------------------------------------------------------------------------- /resources/views/signup.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.base') 2 | 3 | @section('css') 4 | 5 | @endsection 6 | 7 | @section('content') 8 |
9 |

注 册

10 | 11 |
12 | Username: 13 | Password: 14 | Email: 15 | 16 | @if (env('POLR_ACCT_CREATION_RECAPTCHA')) 17 |
18 | @endif 19 | 20 | 21 | 22 | 25 |
26 |
27 |