1
0
mirror of https://github.com/cydrobolt/polr.git synced 2024-11-09 19:52:28 +01:00

Merge branch 'master' into 157-replace-px-with-em

This commit is contained in:
sanderr 2022-04-22 22:54:55 +02:00 committed by GitHub
commit bed2488fd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1726 additions and 918 deletions

View File

@ -16,7 +16,7 @@ CACHE_DRIVER=file
SESSION_DRIVER=file SESSION_DRIVER=file
QUEUE_DRIVER=file QUEUE_DRIVER=file
VERSION=2.2.0 VERSION=2.3.0
VERSION_RELMONTH=May VERSION_RELMONTH=Jan
VERSION_RELDAY=6 VERSION_RELDAY=28
VERSION_RELYEAR=2017 VERSION_RELYEAR=2020

View File

@ -66,13 +66,13 @@ class LinkFactory {
// has custom ending // has custom ending
$ending_conforms = LinkHelper::validateEnding($custom_ending); $ending_conforms = LinkHelper::validateEnding($custom_ending);
if (!$ending_conforms) { if (!$ending_conforms) {
throw new \Exception('Sorry, but custom endings throw new \Exception('Custom endings
can only contain alphanumeric characters, hyphens, and underscores.'); can only contain alphanumeric characters, hyphens, and underscores.');
} }
$ending_in_use = LinkHelper::linkExists($custom_ending); $ending_in_use = LinkHelper::linkExists($custom_ending);
if ($ending_in_use) { if ($ending_in_use) {
throw new \Exception('Sorry, but this URL ending is already in use.'); throw new \Exception('This URL ending is already in use.');
} }
$link_ending = $custom_ending; $link_ending = $custom_ending;
@ -97,7 +97,6 @@ class LinkFactory {
$link->is_api = $is_api; $link->is_api = $is_api;
if ($creator) { if ($creator) {
// if user is logged in, save user as creator
$link->creator = $creator; $link->creator = $creator;
} }

View File

@ -61,6 +61,7 @@ class StatsHelper {
->select(DB::raw("country AS label, count(*) AS clicks")) ->select(DB::raw("country AS label, count(*) AS clicks"))
->groupBy('country') ->groupBy('country')
->orderBy('clicks', 'desc') ->orderBy('clicks', 'desc')
->whereNotNull('country')
->get(); ->get();
return $stats; return $stats;

View File

@ -7,12 +7,22 @@ use App\Helpers\LinkHelper;
use App\Exceptions\Api\ApiException; use App\Exceptions\Api\ApiException;
class ApiLinkController extends ApiController { class ApiLinkController extends ApiController {
protected function getShortenedLink($long_url, $is_secret, $custom_ending, $link_ip, $username, $response_type) {
try {
$formatted_link = LinkFactory::createLink(
$long_url, $is_secret, $custom_ending, $link_ip, $username, false, true);
}
catch (\Exception $e) {
throw new ApiException('CREATION_ERROR', $e->getMessage(), 400, $response_type);
}
return $formatted_link;
}
public function shortenLink(Request $request) { public function shortenLink(Request $request) {
$response_type = $request->input('response_type'); $response_type = $request->input('response_type');
$user = $request->user; $user = $request->user;
// Validate parameters
// Encode spaces as %20 to avoid validator conflicts
$validator = \Validator::make(array_merge([ $validator = \Validator::make(array_merge([
'url' => str_replace(' ', '%20', $request->input('url')) 'url' => str_replace(' ', '%20', $request->input('url'))
], $request->except('url')), [ ], $request->except('url')), [
@ -23,22 +33,71 @@ class ApiLinkController extends ApiController {
throw new ApiException('MISSING_PARAMETERS', 'Invalid or missing parameters.', 400, $response_type); throw new ApiException('MISSING_PARAMETERS', 'Invalid or missing parameters.', 400, $response_type);
} }
$long_url = $request->input('url'); // * required $formatted_link = $this->getShortenedLink(
$is_secret = ($request->input('is_secret') == 'true' ? true : false); $request->input('url'),
($request->input('is_secret') == 'true' ? true : false),
$link_ip = $request->ip(); $request->input('custom_ending'),
$custom_ending = $request->input('custom_ending'); $request->ip(),
$user->username,
try { $response_type
$formatted_link = LinkFactory::createLink($long_url, $is_secret, $custom_ending, $link_ip, $user->username, false, true); );
}
catch (\Exception $e) {
throw new ApiException('CREATION_ERROR', $e->getMessage(), 400, $response_type);
}
return self::encodeResponse($formatted_link, 'shorten', $response_type); return self::encodeResponse($formatted_link, 'shorten', $response_type);
} }
public function shortenLinksBulk(Request $request) {
$response_type = $request->input('response_type', 'json');
$request_data = $request->input('data');
$user = $request->user;
$link_ip = $request->ip();
$username = $user->username;
if ($response_type != 'json') {
throw new ApiException('JSON_ONLY', 'Only JSON-encoded responses are available for this endpoint.', 401, $response_type);
}
$links_array_raw_json = json_decode($request_data, true);
if ($links_array_raw_json === null) {
throw new ApiException('INVALID_PARAMETERS', 'Invalid JSON.', 400, $response_type);
}
$links_array = $links_array_raw_json['links'];
foreach ($links_array as $link) {
$validator = \Validator::make($link, [
'url' => 'required|url'
]);
if ($validator->fails()) {
throw new ApiException('MISSING_PARAMETERS', 'Invalid or missing parameters.', 400, $response_type);
}
}
$formatted_links = [];
foreach ($links_array as $link) {
$formatted_link = $this->getShortenedLink(
$link['url'],
(array_get($link, 'is_secret') == 'true' ? true : false),
array_get($link, 'custom_ending'),
$link_ip,
$username,
$response_type
);
$formatted_links[] = [
'long_url' => $link['url'],
'short_url' => $formatted_link
];
}
return self::encodeResponse([
'shortened_links' => $formatted_links
], 'shorten_bulk', 'json');
}
public function lookupLink(Request $request) { public function lookupLink(Request $request) {
$user = $request->user; $user = $request->user;
$response_type = $request->input('response_type'); $response_type = $request->input('response_type');

View File

@ -3,6 +3,7 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Redirect; use Illuminate\Http\Redirect;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schema;
use App\Helpers\CryptoHelper; use App\Helpers\CryptoHelper;
use App\Models\User; use App\Models\User;
@ -106,6 +107,8 @@ class SetupController extends Controller {
$polr_recaptcha_site_key = $request->input('setting:recaptcha_site_key'); $polr_recaptcha_site_key = $request->input('setting:recaptcha_site_key');
$polr_recaptcha_secret_key = $request->input('setting:recaptcha_secret_key'); $polr_recaptcha_secret_key = $request->input('setting:recaptcha_secret_key');
$maxmind_license_key = $request->input('maxmind:license_key');
$acct_username = $request->input('acct:username'); $acct_username = $request->input('acct:username');
$acct_email = $request->input('acct:email'); $acct_email = $request->input('acct:email');
$acct_password = $request->input('acct:password'); $acct_password = $request->input('acct:password');
@ -148,6 +151,7 @@ class SetupController extends Controller {
'APP_STYLESHEET' => $app_stylesheet, 'APP_STYLESHEET' => $app_stylesheet,
'POLR_GENERATED_AT' => $date_today, 'POLR_GENERATED_AT' => $date_today,
'POLR_SETUP_RAN' => $polr_setup_ran, 'POLR_SETUP_RAN' => $polr_setup_ran,
'MAXMIND_LICENSE_KEY' => $maxmind_license_key,
'DB_HOST' => $db_host, 'DB_HOST' => $db_host,
'DB_PORT' => $db_port, 'DB_PORT' => $db_port,
@ -215,8 +219,8 @@ class SetupController extends Controller {
} }
public static function finishSetup(Request $request) { public static function finishSetup(Request $request) {
// get data from cookie, decode JSON
if (!isset($_COOKIE['setup_arguments'])) { if (!isset($_COOKIE['setup_arguments'])) {
// Abort if setup arguments are missing.
abort(404); abort(404);
} }
@ -226,12 +230,19 @@ class SetupController extends Controller {
// unset cookie // unset cookie
setcookie('setup_arguments', '', time()-3600); setcookie('setup_arguments', '', time()-3600);
$transaction_authorised = env('TMP_SETUP_AUTH_KEY') == $setup_finish_args->setup_auth_key; $transaction_authorised = env('TMP_SETUP_AUTH_KEY') === $setup_finish_args->setup_auth_key;
if ($transaction_authorised != true) { if ($transaction_authorised != true) {
abort(403, 'Transaction unauthorised.'); abort(403, 'Transaction unauthorised.');
} }
$usersTableExists = Schema::hasTable('users');
if ($usersTableExists) {
// If the users table exists, then the setup process may have already been completed before.
abort(403, 'Setup has been completed already.');
}
$database_created = self::createDatabase(); $database_created = self::createDatabase();
if (!$database_created) { if (!$database_created) {
return redirect(route('setup'))->with('error', 'Could not create database. Perhaps your credentials were incorrect?'); return redirect(route('setup'))->with('error', 'Could not create database. Perhaps your credentials were incorrect?');
@ -240,7 +251,7 @@ class SetupController extends Controller {
if (env('SETTING_ADV_ANALYTICS')) { if (env('SETTING_ADV_ANALYTICS')) {
$geoip_db_created = self::updateGeoIP(); $geoip_db_created = self::updateGeoIP();
if (!$geoip_db_created) { if (!$geoip_db_created) {
return redirect(route('setup'))->with('error', 'Could not fetch GeoIP database for advanced analytics. Perhaps your server is not connected to the internet?'); return redirect(route('setup'))->with('error', 'Could not fetch GeoIP database for advanced analytics. Perhaps your server is not connected to the internet or your MAXMIND_LICENSE_KEY is incorrect?');
} }
} }

View File

@ -66,6 +66,7 @@ $app->group(['prefix' => '/api/v2', 'namespace' => 'App\Http\Controllers\Api', '
/* API shorten endpoints */ /* API shorten endpoints */
$app->post('action/shorten', ['as' => 'api_shorten_url', 'uses' => 'ApiLinkController@shortenLink']); $app->post('action/shorten', ['as' => 'api_shorten_url', 'uses' => 'ApiLinkController@shortenLink']);
$app->get('action/shorten', ['as' => 'api_shorten_url', 'uses' => 'ApiLinkController@shortenLink']); $app->get('action/shorten', ['as' => 'api_shorten_url', 'uses' => 'ApiLinkController@shortenLink']);
$app->post('action/shorten_bulk', ['as' => 'api_shorten_url_bulk', 'uses' => 'ApiLinkController@shortenLinksBulk']);
/* API lookup endpoints */ /* API lookup endpoints */
$app->post('action/lookup', ['as' => 'api_lookup_url', 'uses' => 'ApiLinkController@lookupLink']); $app->post('action/lookup', ['as' => 'api_lookup_url', 'uses' => 'ApiLinkController@lookupLink']);

View File

@ -14,8 +14,9 @@
"torann/geoip": "^1.0", "torann/geoip": "^1.0",
"geoip2/geoip2": "^2.4", "geoip2/geoip2": "^2.4",
"nesbot/carbon": "^1.22", "nesbot/carbon": "^1.22",
"doctrine/dbal": "^2.5", "doctrine/dbal": "2.5.11",
"google/recaptcha": "~1.1" "google/recaptcha": "~1.1",
"symfony/http-foundation": "2.7.51"
}, },
"require-dev": { "require-dev": {
"fzaninotto/faker": "~1.0", "fzaninotto/faker": "~1.0",

2323
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -54,7 +54,7 @@ return [
'maxmind_database' => [ 'maxmind_database' => [
'class' => \Torann\GeoIP\Services\MaxMindDatabase::class, 'class' => \Torann\GeoIP\Services\MaxMindDatabase::class,
'database_path' => storage_path('app/geoip.mmdb'), 'database_path' => storage_path('app/geoip.mmdb'),
'update_url' => 'https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz', 'update_url' => sprintf('https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz', env('MAXMIND_LICENSE_KEY')),
'locales' => ['en'], 'locales' => ['en'],
], ],
@ -126,18 +126,18 @@ return [
'default_location' => [ 'default_location' => [
'ip' => '127.0.0.0', 'ip' => '127.0.0.0',
'iso_code' => 'US', 'iso_code' => null,
'country' => 'United States', 'country' => null,
'city' => 'New Haven', 'city' => null,
'state' => 'CT', 'state' => null,
'state_name' => 'Connecticut', 'state_name' => null,
'postal_code' => '06510', 'postal_code' => null,
'lat' => 41.31, 'lat' => 0,
'lon' => -72.92, 'lon' => 0,
'timezone' => 'America/New_York', 'timezone' => 'UTC',
'continent' => 'NA', 'continent' => null,
'default' => true, 'default' => true,
'currency' => 'USD', 'currency' => null,
], ],
]; ];

View File

@ -2,8 +2,8 @@
----------------------------- -----------------------------
## API keys ## API keys
To authenticate a user to Polr, you will need to provide an API key along with To authenticate a user to Polr, you *must* provide an API key along with
each request to the Polr API, as a GET or POST parameter. (e.g `?key=API_KEY_HERE`) each request to Polr API endpoints, as a GET or POST parameter. (e.g `?key=API_KEY_HERE`)
## Assigning an API key ## Assigning an API key
To assign an API key, log on from an administrator account, head over to the "Admin" To assign an API key, log on from an administrator account, head over to the "Admin"
@ -26,11 +26,9 @@ See [API endpoints](#api-endpoints) for more information on the actions.
## Response Type ## Response Type
The Polr API will reply in `plain_text` or `json`. The response type can be The Polr API will reply in `plain_text` or `json`. The response type can be
set by providing the `response_type` argument to the request. If not provided, set by providing the `response_type` argument to the request. If this argument is not provided,
the response type will default to `plain_text`. the response type will default to either `plain_text` or `json` depending
on the endpoint.
Data endpoints will only return JSON-formatted data and will default to `json` if no
`response_type` is provided.
Example `json` responses: Example `json` responses:
``` ```
@ -84,7 +82,61 @@ Response:
} }
``` ```
Remember that the `url` argument must be URL encoded. The `url` argument must be URL encoded.
### /api/v2/action/shorten_bulk
_`POST` only_
Arguments:
- `data`: a string containing a JSON-encoded object with an array of links
Example `data` argument:
```json
{
"links": [
{
"url": "https://polrproject.org/"
},
{
"url": "https://youtube.com",
"is_secret": true
},
{
"url": "https://github.com/cydrobolt/polr",
"custom_ending": "polrgithub"
}
]
}
```
Response: A JSON-encoded object with a list of shortened links.
Example response:
```
{
"action": "shorten_bulk",
"result": {
"shortened_links": [
{
"long_url": "https://polrproject.org/",
"short_url": "http://demo.polr.me/81"
},
{
"long_url": "https://youtube.com",
"short_url": "http://demo.polr.me/84/b496"
},
{
"long_url": "https://github.com/cydrobolt/polr",
"short_url": "http://demo.polr.me/polrgithub"
}
]
}
}
```
### /api/v2/action/lookup ### /api/v2/action/lookup
The `lookup` action takes a single argument: `url_ending`. This is the URL to The `lookup` action takes a single argument: `url_ending`. This is the URL to

View File

@ -8,6 +8,8 @@ it to `true` in `.env`
`MISSING_PARAMETERS`: Invalid or missing parameters. `MISSING_PARAMETERS`: Invalid or missing parameters.
`INVALID_PARAMETERS`: Invalid parameters.
`NOT_FOUND`: Object not found. `NOT_FOUND`: Object not found.
`ACCESS_DENIED`: User is not authorized to access the object. `ACCESS_DENIED`: User is not authorized to access the object.

View File

@ -23,7 +23,7 @@ you may be interested in looking at a [legacy 1.x release](https://github.com/cy
- JSON PHP Extension - JSON PHP Extension
- PHP curl extension - PHP curl extension
## Downloading the source code ## Download the source code
If you would like to download a stable version of Polr, you may check out [the releases page](https://github.com/cydrobolt/polr/releases). If you would like to download a stable version of Polr, you may check out [the releases page](https://github.com/cydrobolt/polr/releases).
@ -49,7 +49,7 @@ $ chown -R apache polr
$ chcon -R -t httpd_sys_rw_content_t polr/storage polr/.env $ chcon -R -t httpd_sys_rw_content_t polr/storage polr/.env
``` ```
## Installing using `composer` ## Install `composer` dependencies
```bash ```bash
# download composer package # download composer package
@ -177,7 +177,7 @@ server {
To run Polr on another HTTP server or on shared hosting, you will need to set the home To run Polr on another HTTP server or on shared hosting, you will need to set the home
directory to `/PATH_TO_POLR/public`, not the root Polr folder. directory to `/PATH_TO_POLR/public`, not the root Polr folder.
## Creating the database ## Create the database
### MySQL ### MySQL

View File

@ -0,0 +1,20 @@
# Obtaining a MaxMind GeoIP License Key
-----------------
If you would like to use "advanced analytics" in order to get more insights on your users, you must input a MaxMind GeoIP license key during the setup process or add it later to your configuration file (`.env`).
To obtain a license key, create an account on [MaxMind's website](https://www.maxmind.com/en/geolite2/signup). After you create your account and confirm your email, go to your [MaxMind account page](https://www.maxmind.com/en/accounts/current/license-key) to generate a license key for use with Polr. If you are asked whether your license key will be used for "GeoIP Update", answer no.
Copy your newly generated license key and input it into Polr.
## Troubleshooting
### I'm having trouble running `php artisan geoip:update`, and I'm using an older version of Polr
If you are on an older version of Polr, your installation may be updated. Update Polr by running `git pull` and then add a new entry to your `.env` configuration file:
```
MAXMIND_LICENSE_KEY="LICENSE_KEY_GOES_HERE"
```
Then, run `php artisan config:cache` and `php artisan geoip:update` again.

View File

@ -4,6 +4,7 @@ pages:
- User Guide: - User Guide:
- 'Installation': 'user-guide/installation.md' - 'Installation': 'user-guide/installation.md'
- 'Upgrading': 'user-guide/upgrading.md' - 'Upgrading': 'user-guide/upgrading.md'
- 'Obtaining a MaxMind License Key': 'user-guide/maxmind-license.md'
- 'Troubleshooting': 'user-guide/troubleshooting.md' - 'Troubleshooting': 'user-guide/troubleshooting.md'
- Developer Guide: - Developer Guide:
- 'Libraries': 'developer-guide/libraries.md' - 'Libraries': 'developer-guide/libraries.md'

View File

@ -267,14 +267,16 @@ polr.controller('AdminCtrl', function($scope, $compile, $timeout) {
// Delete user // Delete user
$scope.deleteUser = function($event, user_id) { $scope.deleteUser = function($event, user_id) {
var el = $($event.target); var delete_user_confirm = window.confirm('Are you sure you would like to delete this user?');
apiCall('admin/delete_user', { if (delete_user_confirm) {
'user_id': user_id, apiCall('admin/delete_user', {
}, function(new_status) { 'user_id': user_id,
toastr.success('User successfully deleted.', 'Success'); }, function(new_status) {
$scope.reloadUserTables(); toastr.success('User successfully deleted.', 'Success');
}); $scope.reloadUserTables();
});
}
}; };
$scope.changeUserRole = function(role, user_id) { $scope.changeUserRole = function(role, user_id) {
@ -308,14 +310,16 @@ polr.controller('AdminCtrl', function($scope, $compile, $timeout) {
// Delete link // Delete link
$scope.deleteLink = function($event, link_ending) { $scope.deleteLink = function($event, link_ending) {
var el = $($event.target); var delete_link_confirm = window.confirm('Are you sure you would like to delete this link?');
apiCall('admin/delete_link', { if (delete_link_confirm) {
'link_ending': link_ending, apiCall('admin/delete_link', {
}, function(new_status) { 'link_ending': link_ending,
toastr.success('Link successfully deleted.', 'Success'); }, function(new_status) {
$scope.reloadLinkTables(); toastr.success('Link successfully deleted.', 'Success');
}); $scope.reloadLinkTables();
});
}
}; };
// Disable and enable links // Disable and enable links

View File

@ -1,5 +0,0 @@
$('#gpl-license').hide();
$('.license-btn').click(function () {
$('#gpl-license').slideDown();
$('.license-btn').slideUp();
});

View File

@ -18,35 +18,13 @@
<dt>Release date: {{env('POLR_RELDATE')}}</dt> <dt>Release date: {{env('POLR_RELDATE')}}</dt>
<dt>App Install: {{env('APP_NAME')}} on {{env('APP_ADDRESS')}} on {{env('POLR_GENERATED_AT')}}<dt> <dt>App Install: {{env('APP_NAME')}} on {{env('APP_ADDRESS')}} on {{env('POLR_GENERATED_AT')}}<dt>
</dl> </dl>
<p>You are seeing the information above because you are logged in as an administrator.</p> <p>You are seeing the information above because you are logged in as an administrator. You can edit the contents of this page by editing <code>resources/views/about.blade.php</code></p>
@endif @endif
<p>{{env('APP_NAME')}} is powered by Polr 2, an open source, minimalist link shortening platform. <p>{{env('APP_NAME')}} is powered by Polr 2, an open source, minimalist link shortening platform. The Polr Project is in no way associated with this site.
Learn more at <a href='https://github.com/Cydrobolt/polr'>its Github page</a> or its <a href="//project.polr.me">project site</a>. Learn more at <a href='https://github.com/Cydrobolt/polr'>its Github page</a> or its <a href="//project.polr.me">project site</a>.
<br />Polr is licensed under the GNU GPL License. <br />Polr is licensed under the GNU GPL License.
</p> </p>
</div> </div>
<a href='#' class='btn btn-success license-btn'>More Information</a>
<pre class="license" id="gpl-license">
Copyright (C) 2013-2017 Chaoyi Zha
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
</pre>
@endsection @endsection
@section('js')
<script src='/js/about.js'></script>
@endsection

View File

@ -26,6 +26,9 @@ POLR_GENERATED_AT="{{$POLR_GENERATED_AT}}"
# e.g true # e.g true
POLR_SETUP_RAN={{$POLR_SETUP_RAN}} POLR_SETUP_RAN={{$POLR_SETUP_RAN}}
# Steps for obtaining a Maxmind License Key: https://docs.polrproject.org/en/latest/user-guide/maxmind-license
MAXMIND_LICENSE_KEY="{{$MAXMIND_LICENSE_KEY}}"
DB_CONNECTION=mysql DB_CONNECTION=mysql
# Set to your DB host (e.g localhost) # Set to your DB host (e.g localhost)
DB_HOST="{{{$DB_HOST}}}" DB_HOST="{{{$DB_HOST}}}"

View File

@ -65,6 +65,16 @@ Setup
<option value='true'>Enable advanced analytics</option> <option value='true'>Enable advanced analytics</option>
</select> </select>
<p>
MaxMind GeoIP License Key (required for advanced analytics only):
</p>
<p>
<input type='text' class='form-control' name='maxmind:license_key' value=''>
<p class='text-muted'>
To obtain a free MaxMind GeoIP license key, follow <a href="https://docs.polrproject.org/en/latest/user-guide/maxmind-license">these instructions</a> on Polr's documentation website.
</p>
<p>Shortening Permissions:</p> <p>Shortening Permissions:</p>
<select name='setting:shorten_permission' class='form-control'> <select name='setting:shorten_permission' class='form-control'>
<option value='false' selected='selected'>Anyone can shorten URLs</option> <option value='false' selected='selected'>Anyone can shorten URLs</option>