Commit b04bf090 authored by Jack Stupple's avatar Jack Stupple

Merge branch 'dev' into 'master'

Go live! 馃帀

See merge request !3
parents 0bf8f98e 0b57f9b0
......@@ -5,3 +5,4 @@ Homestead.yaml
.env
.htpasswd
.DS_Store
/node_modules
\ No newline at end of file
RewriteEngine on
RewriteRule . public/index.php [L]
RewriteEngine On
RewriteCond %{THE_REQUEST} /public/([^\s?]*) [NC]
RewriteRule ^ %1 [L,NE,R=302]
RewriteRule ^((?!public/).*)$ public/$1 [L,NC]
\ No newline at end of file
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class AdminPasswordResetToken extends Model
{
public function user()
{
return $this->belongsTo(AdminUser::class);
}
}
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class AdminUser extends Model
{
public function userGroup()
{
return $this->belongsTo('App\AdminUserGroup');
}
public function passwordResetTokens()
{
return $this->hasMany(AdminPasswordResetToken::class);
}
public function createPasswordResetToken()
{
$this->passwordResetTokens()->delete();
$token = new AdminPasswordResetToken;
$token->admin_user_id = $this->id;
$token->token = random_str(32);
$token->save();
return $token;
}
}
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class AdminUserGroup extends Model
{
public function users()
{
return $this->hasMany('App\AdminUser');
}
}
......@@ -17,12 +17,12 @@ class Character extends Model
public function baseModifiers()
{
$player = Player::where('session_id', '=', $_SERVER['HTTP_SESSION_ID'])->first();
$player = Player::where('session_id', '=', $_SERVER['HTTP_SESSION_ID'])->with('language')->first();
if ($player->currency_id) {
if ($player->language) {
return $this->hasMany(Modifier::class, 'model_id')
->where('model', '=', 'character')
->where('currency_id', '=', $player->currency_id);
->where('currency_id', '=', $player->language->currency_id);
} else {
$default_currency = Currency::where('currency_code', 'EUR')->firstOrFail();
return $this->hasMany(Modifier::class, 'model_id')
......
<?php
namespace App\Console\Commands;
use App\Language;
use App\Resource;
use App\ResourceCategory;
use Illuminate\Console\Command;
/**
* Class ImportResources
* @package App\Console\Commands
* @see https://laravel.com/docs/5.6/artisan
*/
class ImportResourceCategories extends Command
{
protected $signature = 'import:resource-categories {csv_file_name}';
protected $description = 'Import resource categories into the API via a CSV file.';
protected $csv_file_name = null;
protected $csv_file_pathinfo = [];
protected $csv_headers = null;
protected $csv_data = [];
protected static $required_headers = [
'resource category',
'resource category parent'
];
protected static $cached_categories = [];
public function handle()
{
ini_set('auto_detect_line_endings', true);
$this->csv_file_name = $this->argument('csv_file_name');
try {
// exception will be thrown if file is invalid
$this->csv_file_pathinfo = $this->checkFileProperties($this->csv_file_name);
} catch (\Exception $exception) {
$this->output->writeln("<error>Error encountered:</error> {$exception->getMessage()}");
return false;
}
list($this->csv_headers, $this->csv_data) = $this->getFileData($this->csv_file_name);
$this->checkHeaders($this->csv_headers); // throws exception if fails
// we now have the resources list
foreach ($this->csv_data as $csv_row_number => $_category) {
$resource_category = ResourceCategory::firstOrCreate([
'name' => $_category['resource category'],
'parent_category' => $_category['resource category parent']
]);
if (!$resource_category->exists()) {
$resource_category->save();
}
}
}
protected function checkHeaders($csv_headers)
{
foreach (static::$required_headers as $required_header) {
if (in_array($required_header, $csv_headers) === false) {
throw new \Exception ("Missing required header in CSV: {$required_header}");
}
}
}
protected function getFileData($csv_file_name)
{
$file_handle = fopen($csv_file_name, 'r');
$csv_headers = null;
$csv_data = [];
$row_number = 0;
$empty_rows = 0; // if 3 empty rows are encountered we just break the loop- assumed end of data
while (($csv_row = fgetcsv($file_handle)) !== false) {
$row_number++; // the header is also a row in the CSV
if ($csv_headers === null) {
$_csv_headers = [];
foreach ($csv_row as $header) {
$_csv_headers[] = trim(strtolower($header));
}
$csv_headers = $_csv_headers;
unset($_csv_headers);
continue;
}
// skip empty rows
if (array_filter(array_unique($csv_row)) === []) {
$empty_rows++;
if ($empty_rows >= 3) {
// assume we have reached the end of the data
$this->output->writeln("<comment>Assumed end of data:</comment> {$row_number}");
break;
} else {
$this->output->writeln("<comment>Skip empty row:</comment> {$row_number}");
continue;
}
}
$_csv_data = [];
foreach ($csv_headers as $header_index => $header) {
$_csv_data[$header] = trim($csv_row[$header_index]); // we trim to ensure no additional spaces
// ensure the data is expected - primarily for the URL's
$validation_method = camel_case('validate ' . $header);
if (method_exists($this, $validation_method) && !empty($_csv_data[$header])) {
if (!$this->$validation_method($_csv_data[$header])) {
throw new \Exception("Issue with {$header} on row: {$row_number}");
}
}
}
$csv_data[$row_number] = $_csv_data; // add the row to the data with same row number as that in the CSV
unset($_csv_data);
}
return [$csv_headers, $csv_data];
}
protected function checkFileProperties($csv_file_name)
{
if (file_exists($csv_file_name) === false) {
throw new \Exception('File does not exist');
}
$csv_file_pathinfo = pathinfo($csv_file_name);
// extension is only defined if one is provided, eg /etc/hosts will have no extension
if (!isset($csv_file_pathinfo['extension']) || $csv_file_pathinfo['extension'] !== 'csv') {
throw new \Exception('File is not CSV');
}
return $csv_file_pathinfo;
}
protected function validateUkAsset($uk_asset)
{
return $this->validateUrl($uk_asset);
}
protected function validateFrAsset($asset)
{
return $this->validateUrl($asset);
}
protected function validateDeAsset($asset)
{
return $this->validateUrl($asset);
}
protected function validateNoAsset($asset)
{
return $this->validateUrl($asset);
}
protected function validateUrl($asset)
{
return filter_var($asset, FILTER_VALIDATE_URL);
}
}
\ No newline at end of file
......@@ -56,23 +56,29 @@ class ImportResources extends Command
// we now have the resources list
foreach ($this->csv_data as $csv_row_number => $_resource) {
$language = null;
$resource_link = null;
// add here cases for the different kinds of resources we are importing
if (!empty($_resource['uk asset'])) {
$language = $this->getLanguageFor('uk');
$resource_link = $_resource['uk asset'];
} else if (!empty($_resource['fr asset'])) {
$language = $this->getLanguageFor('fr');
$resource_link = $_resource['fr asset'];
} else if (!empty($_resource['de asset'])) {
$language = $this->getLanguageFor('de');
$resource_link = $_resource['de asset'];
} else if (!empty($_resource['no asset'])) {
$language = $this->getLanguageFor('no');
$resource_link = $_resource['no asset'];
}
$language = $this->getLanguageFor($_resource['language']);
$resource_link = $_resource['url'];
// // add here cases for the different kinds of resources we are importing
// if (!empty($_resource['uk asset'])) {
// $language = $this->getLanguageFor('uk');
// $resource_link = $_resource['uk asset'];
// } else if (!empty($_resource['nl asset'])) {
// $language = $this->getLanguageFor('nl');
// $resource_link = $_resource['nl asset'];
// } else if (!empty($_resource['de asset'])) {
// $language = $this->getLanguageFor('de');
// $resource_link = $_resource['de asset'];
// } else if (!empty($_resource['no asset'])) {
// $language = $this->getLanguageFor('no');
// $resource_link = $_resource['no asset'];
// } else if (!empty($_resource['sl asset'])) {
// $language = $this->getLanguageFor('sl');
// $resource_link = $_resource['sl asset'];
// } else {
// $this->output->writeln("<comment>Could not determine language</comment> {$csv_row_number}");
// continue;
// }
// check for duplicates
if ($this->isDuplicateResource($language->id, $resource_link)) {
......@@ -94,15 +100,14 @@ class ImportResources extends Command
}
}
protected function getResourceCategory($category, $parent_category)
protected function getResourceCategory($category)
{
if (isset(static::$cached_categories[$category])) {
return static::$cached_categories[$category];
}
$resource_category = ResourceCategory::firstOrCreate([
'name' => $category,
'parent_category' => $parent_category
'name' => $category
]);
if (!$resource_category->exists()) {
......
<?php
namespace App\Console\Commands\Make;
use App\AdminUser;
use App\AdminUserGroup;
use Illuminate\Console\Command;
class MakeAdminUser extends Command
{
protected $signature = 'make:admin-user {--E|email= : User\'s login email.} {--N|name= : Friendly name for the user.}';
protected $description = 'Create a new admin user.';
public function handle()
{
$user = new AdminUser();
$user->full_name = $this->option('name') ?? $this->output->ask('What is the users full name?');;
$user->email = $this->option('email') ?? $this->output->ask('What is the users email address?');;
$user_groups_formatted = [];
$user_groups = AdminUserGroup::all();
$user_groups->each(function ($user_group) use (&$user_groups_formatted) {
$user_groups_formatted[$user_group->id] = $user_group->name;
});
$user_group_name = $this->output->choice('What is their user group?', $user_groups_formatted, array_first($user_groups_formatted));
$user->admin_user_group_id = array_search($user_group_name, $user_groups_formatted);
$password = str_random(16);
$user->password = password_hash($password, PASSWORD_DEFAULT);
$user->save();
$this->output->writeln('<info>User created</info>');
$this->output->writeln("<comment>User Email:</comment> {$user->email}");
$this->output->writeln("<comment>User Password:</comment> {$password}");
}
}
\ No newline at end of file
......@@ -3,7 +3,9 @@
namespace App\Console;
use App\Console\Commands\AddCurrency;
use App\Console\Commands\ImportResourceCategories;
use App\Console\Commands\ImportResources;
use App\Console\Commands\Make\MakeAdminUser;
use Illuminate\Console\Scheduling\Schedule;
use Laravel\Lumen\Console\Kernel as ConsoleKernel;
......@@ -16,7 +18,9 @@ class Kernel extends ConsoleKernel
*/
protected $commands = [
ImportResources::class,
AddCurrency::class
ImportResourceCategories::class,
AddCurrency::class,
MakeAdminUser::class
];
/**
......
<?php
function is_logged_in()
{
return !empty($_SESSION['user_id']);
}
function is_logged_in_admin()
{
return !empty($_SESSION['admin_user_id']);
}
\ No newline at end of file
<?php
namespace App\Helpers;
class FlashMessage
{
protected static $identifier = 'flash-message';
public static function count()
{
return count($_SESSION[static::$identifier]);
}
public static function fetch()
{
$messages = $_SESSION[static::$identifier];
$_SESSION[static::$identifier] = [];
return collect($messages);
}
public static function destroy()
{
unset($_SESSION[static::$identifier]);
return true;
}
public static function init()
{
if (!isset($_SESSION[static::$identifier])) {
$_SESSION[static::$identifier] = [];
}
}
public function __construct($message, $type = 'info')
{
$_SESSION[static::$identifier][] = (object) compact('message', 'type');
}
}
\ No newline at end of file
<?php
/**
* @param $partial_name
* @param array $data
* @return string
*/
function get_partial($partial_name, array $data = [])
{
$partial_file = resource_path('partials') . DIRECTORY_SEPARATOR . $partial_name . '.php';
if (!file_exists($partial_file)) {
trigger_error('Partial not found: ' . $partial_file, E_USER_NOTICE);
return '';
}
ob_start();
extract($data);
include $partial_file;
return ob_get_clean();
}
\ No newline at end of file
<?php
/**
* Generate a random string, using a cryptographically secure
* pseudorandom number generator (random_int)
*
* For PHP 7, random_int is a PHP core function
* For PHP 5.x, depends on https://github.com/paragonie/random_compat
*
* @param int $length How many characters do we want?
* @param string $keyspace A string of all possible characters to select from
* @return string
* @throws Exception
*
* @see https://stackoverflow.com/questions/4356289/php-random-string-generator
*/
function random_str($length, $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
{
$pieces = [];
$max = mb_strlen($keyspace, '8bit') - 1;
for ($i = 0; $i < $length; ++$i) {
$pieces []= $keyspace[random_int(0, $max)];
}
return implode('', $pieces);
}
\ No newline at end of file
<?php
function time_ago($datetime, $full = false) {
$now = new DateTime;
$ago = is_a($datetime, \DateTime::class) ? $datetime : new DateTime($datetime);
$diff = $now->diff($ago);
$diff->w = floor($diff->d / 7);
$diff->d -= $diff->w * 7;
$string = array(
'y' => 'year',
'm' => 'month',
'w' => 'week',
'd' => 'day',
'h' => 'hour',
'i' => 'minute',
's' => 'second',
);
foreach ($string as $k => &$v) {
if ($diff->$k) {
$v = $diff->$k . ' ' . $v . ($diff->$k > 1 ? 's' : '');
} else {
unset($string[$k]);
}
}
if (!$full) $string = array_slice($string, 0, 1);
if (!$string) {
return 'just now';
}
if ($now < $ago) {
return 'in ' . implode(', ', $string);
} else {
return implode(', ', $string) . ' ago';
}
}
\ No newline at end of file
<?php
namespace App\Http\Controllers\Admin;
use App\AdminPasswordResetToken;
use App\AdminUser;
use App\Helpers\FlashMessage;
use App\Jobs\PasswordResetEmail;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Queue;
use Illuminate\Validation\ValidationException;
class AdminLoginController extends Controller
{
protected static $invalid_password = 'Your email or password is incorrect.';
public function index()
{
return view('admin/login/form');
}
public function verifyLogin(Request $request)
{
$admin_user = AdminUser::where('email', $request->input('email'))->first();
// user is invalid
if (!$admin_user || !password_verify($request->input('password'), $admin_user->password)) {
new FlashMessage(_(static::$invalid_password), 'danger');
return redirect('/admin/login');
}
$_SESSION['admin_user_id'] = $admin_user->id;
new FlashMessage('You have successfully logged in.', 'success');
return redirect('/admin');
}
public function destroy()
{
session_unset();
new \App\Helpers\FlashMessage('You have successfully been logged out.', 'success');
return redirect('/admin/login');
}
public function forgotPassword()
{
static::title('Forgot password');
return view('/admin/login/forgot-password');
}
public function verifyForgotPassword(Request $request)
{
$admin_user = AdminUser::where('email', $request->input('email'))->first();
// user is invalid
if (!$admin_user) {
new FlashMessage('You should expect an email soon.', 'success');
return redirect('/admin/login/forgot-password');
}
$token = $admin_user->createPasswordResetToken();
new FlashMessage('You should expect an email soon.', 'success');
Queue::push(new PasswordResetEmail($admin_user, \Illuminate\Support\Facades\Request::root() . '/admin/login/new-password/' . $token->token));
return redirect('/admin');
}
public function newPassword(Request $request, $token)
{
// validate token exists
$token = AdminPasswordResetToken::where('token', $token)->firstOrfail();
return view('/admin/login/new-password', compact('errors'));
}
public function verifyNewPassword(Request $request, $token)
{
try {
$this->validate($request, [
'password' => 'required|min:8|max:255|confirmed'
]);
} catch (ValidationException $exception) {
foreach ($exception->errors() as $error_field => $errors) {
foreach ($errors as $error) {
new FlashMessage($error, 'danger');
}
}
return $this->newPassword($request, $token);
}
$token = AdminPasswordResetToken::where('token', $token)->firstOrfail();
$user = AdminUser::where('id', $token->admin_user_id)->firstOrFail();
$user->password = password_hash($request->input('password'), PASSWORD_DEFAULT);
$user->save();
new FlashMessage('Password updated.', 'success');
return redirect('/admin/login');
}
}
<?php
namespace App\Http\Controllers\Admin;
use Laravel\Lumen\Routing\Controller as BaseController;
class Controller extends BaseController {
protected static $_title;
protected static $_description;
public static function title($new_title = '')
{
if ($new_title) {
static::$_title = $new_title;
}
return static::$_title;
}