Installing and managing modules from admin panel (#847)

This commit is contained in:
Yash Govekar
2020-10-19 19:40:28 +05:30
committed by GitHub
parent ca220f1cdf
commit 5803487d51
92 changed files with 1259 additions and 669 deletions

1
modules/.gitignore vendored
View File

@@ -8,3 +8,4 @@
!/Installer
!/Updater
!/Vacentral
!/ModulesManager

View File

@@ -1,7 +0,0 @@
<?php
return [
'importer' => [
'batch_size' => 20,
],
];

View File

@@ -1,40 +0,0 @@
<?php
namespace Modules\Importer\Console\Commands;
use App\Contracts\Command;
use Illuminate\Support\Facades\Log;
use Modules\Importer\Services\ImporterService;
class ImportFromClassicCommand extends Command
{
protected $signature = 'phpvms:importer {db_host} {db_name} {db_user} {db_pass?} {table_prefix=phpvms_}';
protected $description = 'Import from an older version of phpVMS';
/**
* Run dev related commands
*/
public function handle()
{
$creds = [
'host' => $this->argument('db_host'),
'name' => $this->argument('db_name'),
'user' => $this->argument('db_user'),
'pass' => $this->argument('db_pass'),
'table_prefix' => $this->argument('table_prefix'),
];
$importerSvc = new ImporterService();
$importerSvc->saveCredentials($creds);
$manifest = $importerSvc->generateImportManifest();
foreach ($manifest as $record) {
try {
$importerSvc->run($record['importer'], $record['start']);
} catch (\Exception $e) {
Log::error($e->getMessage());
}
}
}
}

View File

@@ -1,144 +0,0 @@
<?php
namespace Modules\Importer\Http\Controllers;
use App\Contracts\Controller;
use App\Services\Installer\DatabaseService;
use App\Services\Installer\InstallerService;
use App\Support\Utils;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Modules\Importer\Services\ImporterService;
class ImporterController extends Controller
{
private $dbSvc;
private $importerSvc;
public function __construct(DatabaseService $dbSvc, ImporterService $importerSvc)
{
$this->dbSvc = $dbSvc;
$this->importerSvc = $importerSvc;
Utils::disableDebugToolbar();
}
/**
* Show the main page for the importer; show form for the admin email
* and the credentials for the other database
*
* @param \Illuminate\Http\Request $request
*
* @return mixed
*/
public function index(Request $request)
{
return view('importer::step1-configure');
}
protected function testDb(Request $request)
{
$this->dbSvc->checkDbConnection(
$request->post('db_conn'),
$request->post('db_host'),
$request->post('db_port'),
$request->post('db_name'),
$request->post('db_user'),
$request->post('db_pass')
);
}
/**
* Check the database connection
*
* @param \Illuminate\Http\Request $request
*
* @return mixed
*/
public function dbtest(Request $request)
{
$status = 'success'; // success|warn|danger
$message = 'Database connection looks good!';
try {
$this->testDb($request);
} catch (\Exception $e) {
$status = 'danger';
$message = 'Failed! '.$e->getMessage();
}
return view('importer::dbtest', [
'status' => $status,
'message' => $message,
]);
}
/**
* The post from the above
*
* @param \Illuminate\Http\Request $request
*
* @return mixed
*/
public function config(Request $request)
{
try {
$this->testDb($request);
// Save the credentials to use later
$this->importerSvc->saveCredentialsFromRequest($request);
// Generate the import manifest
$manifest = $this->importerSvc->generateImportManifest();
} catch (\Exception $e) {
Log::error($e->getMessage());
// Send it to run, step1
return view('importer::error', [
'error' => $e->getMessage(),
]);
}
// Send it to run, step1
return view('importer::step2-processing', [
'manifest' => $manifest,
]);
}
/**
* Run the importer. Pass in query string with a few different parameters:
*
* stage=STAGE NAME
* start=record_start
*
* @param \Illuminate\Http\Request $request
*
* @throws \Exception
*
* @return mixed
*/
public function run(Request $request)
{
$importer = $request->input('importer');
$start = $request->input('start');
Log::info('Starting stage '.$importer.' from offset '.$start);
$this->importerSvc->run($importer, $start);
return response()->json([
'message' => 'completed',
]);
}
/**
* Complete the import
*/
public function complete()
{
$installerSvc = app(InstallerService::class);
$installerSvc->disableInstallerModules();
return redirect('/');
}
}

View File

@@ -1,96 +0,0 @@
<?php
namespace Modules\Importer\Providers;
use App\Contracts\Modules\ServiceProvider;
use Illuminate\Support\Facades\Route;
use Modules\Importer\Console\Commands\ImportFromClassicCommand;
class ImporterServiceProvider extends ServiceProvider
{
/**
* Boot the application events.
*/
public function boot(): void
{
$this->registerCommands();
$this->registerRoutes();
$this->registerTranslations();
$this->registerConfig();
$this->registerViews();
}
/**
* Register console commands
*/
protected function registerCommands()
{
$this->commands([
ImportFromClassicCommand::class,
]);
}
/**
* Register the routes
*/
protected function registerRoutes()
{
Route::group([
'as' => 'importer.',
'prefix' => 'importer',
'middleware' => ['web'],
'namespace' => 'Modules\Importer\Http\Controllers',
], function () {
Route::get('/', 'ImporterController@index')->name('index');
Route::post('/config', 'ImporterController@config')->name('config');
Route::post('/dbtest', 'ImporterController@dbtest')->name('dbtest');
// Run the actual importer process. Additional middleware
Route::post('/run', 'ImporterController@run')->middleware('api')->name('run');
Route::post('/complete', 'ImporterController@complete')->name('complete');
});
}
/**
* Register config.
*/
protected function registerConfig()
{
$this->mergeConfigFrom(__DIR__.'/../Config/config.php', 'importer');
}
/**
* Register views.
*/
public function registerViews()
{
$viewPath = resource_path('views/modules/importer');
$sourcePath = __DIR__.'/../Resources/views';
$this->publishes([$sourcePath => $viewPath], 'views');
$paths = array_map(
function ($path) {
return $path.'/modules/importer';
},
\Config::get('view.paths')
);
$paths[] = $sourcePath;
$this->loadViewsFrom($paths, 'importer');
}
/**
* Register translations.
*/
public function registerTranslations()
{
$langPath = resource_path('lang/modules/importer');
if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, 'importer');
} else {
$this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'importer');
}
}
}

View File

@@ -1,90 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title') - installer</title>
<link rel="shortcut icon" type="image/png" href="{{ public_asset('/assets/img/favicon.png') }}"/>
<meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, shrink-to-fit=no'
name='viewport'/>
<meta name="base-url" content="{!! url('') !!}">
<meta name="api-key" content="{!! Auth::check() ? Auth::user()->api_key: '' !!}">
<meta name="csrf-token" content="{!! csrf_token() !!}">
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700,200" rel="stylesheet"/>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css" rel="stylesheet"/>
<link href="{{ public_asset('/assets/frontend/css/bootstrap.min.css') }}" rel="stylesheet"/>
<link href="{{ public_asset('/assets/frontend/css/now-ui-kit.css') }}" rel="stylesheet"/>
<link href="{{ public_asset('/assets/installer/css/vendor.css') }}" rel="stylesheet"/>
<link href="{{ public_asset('/assets/frontend/css/styles.css') }}" rel="stylesheet"/>
<link rel="stylesheet"
href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css">
<style>
.table tr:first-child td {
border-top: 0px;
}
@yield('css')
</style>
</head>
<body class="login-page" style="background: #067ec1;">
<div class="page-header clear-filter">
<div class="content">
<div class="container">
<div class="row">
<div class="col-md-8 ml-auto mr-auto content-center">
<div class="p-10" style="padding: 10px 0;">
<div class="row">
<div class="col-4">
<img src="{{ public_asset('/assets/img/logo_blue_bg.svg') }}" width="135px" style="" alt=""/>
</div>
<div class="col-8 text-right">
<h4 class="text-white mb-0 mr-0 ml-0" style="margin-top: 5px;">@yield('title')</h4>
</div>
</div>
</div>
<div class="card card-login card-plain" style="background: #FFF">
<div class="card-body">
@include('importer::flash.message')
@yield('content')
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{--<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>--}}
<script src="{{ public_mix('/assets/global/js/vendor.js') }}"></script>
<script src="{{ public_mix('/assets/installer/js/vendor.js') }}"></script>
<script src="{{ public_mix('/assets/installer/js/app.js') }}"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
<script>
hljs.configure({languages: ['sh']});
$(document).ready(function () {
$(".select2").select2();
$('pre code').each(function (i, block) {
hljs.fixMarkup(block);
hljs.highlightBlock(block);
});
});
</script>
@yield('scripts')
</body>
</html>

View File

@@ -1,20 +0,0 @@
@extends('importer::app')
@section('title', 'Import Completed!')
@section('content')
<div style="align-content: center;">
{{ Form::open(['route' => 'importer.complete', 'method' => 'GET']) }}
<h4>Installer Completed!</h4>
<p>Edit the <span class="code">config.php</span> to fill in some additional settings. </p>
<p>Click the button to proceed to the login screen!</p>
<p style="text-align: right">
{{ Form::submit('Import Complete! Continue to Log-In >>',
['class' => 'btn btn-success'])
}}
</p>
{{ Form::close() }}
</div>
@endsection

View File

@@ -1,8 +0,0 @@
<div class="alert alert-{{ $status }}" role="alert">
<div class="container">
<div class="alert-icon">
<i class="now-ui-icons ui-1_bell-53"></i>
</div>
{{ $message }}
</div>
</div>

View File

@@ -1,9 +0,0 @@
@extends('importer::app')
@section('title', 'Import Error!')
@section('content')
<div style="align-content: center;">
<h4>Error!</h4>
<p class="text-danger">{{ $error }}</p>
</div>
@endsection

View File

@@ -1,6 +0,0 @@
@if($errors->has($field))
<p class="text-danger" style="margin-top: 10px;">{{ $errors->first($field) }}</p>
{{--<div class="alert alert-danger" role="alert" style="margin-top: 10px;">
{{ $errors->first($field) }}
</div>--}}
@endif

View File

@@ -1,11 +0,0 @@
@foreach (session('flash_notification', collect())->toArray() as $message)
<div class="alert alert-danger" role="alert">
<div class="container">
<div class="alert-icon">
<i class="now-ui-icons ui-2_like"></i>
</div>
{{ $message['message'] }}
</div>
</div>
@endforeach
{{ session()->forget('flash_notification') }}

View File

@@ -1,129 +0,0 @@
@extends('importer::app')
@section('title', 'Import Configuration')
@section('content')
<div style="align-content: center;">
{{ Form::open(['route' => 'importer.config', 'method' => 'POST']) }}
<table class="table">
<tr>
<td colspan="2">
<h4>IMPORTANT NOTES</h4>
<ul>
<li>The first user's password (admin) will be "admin". Please change it after logging in</li>
<li>User passwords will be reset and they will need to use "Forgot Password" to reset it</li>
<li>If you have more than 1000 PIREPs or flights, it's best to use the command-line importer!
<a href="{{ docs_link('importing_legacy') }}" target="_blank">Click here</a> to
see the documentation of how to use it.
</li>
<li><strong>THIS WILL WIPE OUT YOUR EXISTING DATA</strong> - this is required to make sure that things like
pilot IDs match up
</li>
</ul>
</td>
</tr>
<tr>
<td colspan="2">
<h4>Database Config</h4>
<p>Enter the database information for your legacy phpVMS installation</p>
</td>
</tr>
<tbody id="mysql_settings">
<tr>
<td>Database Host</td>
<td style="text-align:center;">
<div class="form-group">
{{ Form::input('text', 'db_host', '127.0.0.1', ['class' => 'form-control']) }}
</div>
</td>
</tr>
<tr>
<td>Database Port</td>
<td style="text-align:center;">
<div class="form-group">
{{ Form::input('text', 'db_port', '3306', ['class' => 'form-control']) }}
</div>
</td>
</tr>
<tr>
<td>Database Name</td>
<td style="text-align:center;">
<div class="form-group">
{{ Form::input('text', 'db_name', 'phpvms', ['class' => 'form-control']) }}
</div>
</td>
</tr>
<tr>
<td>Database User</td>
<td style="text-align:center;">
<div class="form-group">
{{ Form::input('text', 'db_user', null, ['class' => 'form-control']) }}
</div>
</td>
</tr>
<tr>
<td>Database Password</td>
<td style="text-align:center;">
<div class="form-group">
{{ Form::input('text', 'db_pass', null, ['class' => 'form-control']) }}
</div>
</td>
</tr>
<tr>
<td colspan="2" style="text-align: right;">
{{ Form::submit('Test Database Credentials', ['class' => 'btn btn-info', 'id' => 'dbtest_button']) }}
</td>
</tr>
</tbody>
<tr>
<td>Database Prefix</td>
<td style="text-align:center;">
<div class="form-group">
{{ Form::input('text', 'db_prefix', 'phpvms_', ['class' => 'form-control']) }}
<p>Prefix of the tables, if you're using one</p>
</div>
</td>
</tr>
</table>
<div id="dbtest"></div>
<p style="text-align: right">
{{ Form::submit('Start Importer >>', ['class' => 'btn btn-success']) }}
</p>
{{ Form::close() }}
</div>
@endsection
@section('scripts')
<script>
$(document).ready(() => {
$("#dbtest_button").click((e) => {
e.preventDefault();
const opts = {
method: 'POST',
url: '/importer/dbtest',
data: {
_token: "{{ csrf_token() }}",
db_conn: 'mysql',
db_host: $("input[name=db_host]").val(),
db_port: $("input[name=db_port]").val(),
db_name: $("input[name=db_name]").val(),
db_user: $("input[name=db_user]").val(),
db_pass: $("input[name=db_pass]").val(),
},
};
phpvms.request(opts).then(response => {
$("#dbtest").html(response.data);
});
});
});
</script>
@endsection

View File

@@ -1,156 +0,0 @@
@extends('importer::app')
@section('title', 'Import Configuration')
@section('content')
<div style="align-content: center;">
{{ Form::open(['route' => 'importer.complete', 'method' => 'POST']) }}
<table class="table" width="25%">
<tr>
<td colspan="2"><h4>Running Importer</h4></td>
</tr>
<tr>
<td colspan="2">
<div class="progress">
<div id="progress" class="progress-bar" style="width: 0%"></div>
</div>
<div>
<p id="message" style="margin-top: 7px;"></p>
<p id="error" class="text-danger" style="margin-top: 7px;"></p>
</div>
</td>
</tr>
</table>
<p style="text-align: right">
{{ Form::submit('Complete Import', [
'id' => 'completebutton',
'class' => 'btn btn-success'
]) }}
</p>
{{ Form::close() }}
</div>
@endsection
@section('scripts')
<script>
const manifest = {!!json_encode($manifest) !!};
/**
* Run each step of the importer
*/
async function startImporter() {
let current = 1;
const total_steps = manifest.length;
/**
* Update the progress bar
*/
const setProgress = (current, message) => {
const percent = Math.round(current / total_steps * 100);
$("#progress").css("width", `${percent}%`);
$("#message").text(message);
};
/**
* Sleep for a given interval
*/
const sleep = (timeout) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, timeout);
});
};
const setError = (error) => {
let message = '';
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
message = error.response.data.message;
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
message = error.request;
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
message = error.message;
}
$("#error").text(`Error processing, check the logs: ${message}`);
console.log(error.config);
};
/**
* Call the endpoint as a POST
*/
const runStep = async function (stage) {
setProgress(current, stage.message);
try {
return await phpvms.request({
method: 'post',
url: '/importer/run',
data: {
importer: stage.importer,
start: stage.start,
}
});
} catch (e) {
if (e.response.status === 504) {
const err = $("#error");
console.log('got timeout, retrying');
err.text(`Timed out, attempting to retry`);
// await sleep(5000);
const val = await runStep(stage);
err.text('');
return val;
}
setError(e);
throw e;
}
};
let errors = false;
const complete_button = $("#completebutton");
complete_button.hide();
for (let stage of manifest) {
console.log(`Running ${stage.importer} step ${stage.start}`);
try {
await runStep(stage);
} catch (e) {
errors = true;
break;
}
current++;
}
if (!errors) {
$("#message").text('Done!');
complete_button.show();
}
}
$(document).ready(() => {
startImporter();
});
</script>
@endsection

View File

@@ -1,150 +0,0 @@
<?php
namespace Modules\Importer\Services;
use App\Services\Installer\LoggerTrait;
use Carbon\Carbon;
use Illuminate\Bus\Queueable;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
use Modules\Importer\Utils\IdMapper;
use Modules\Importer\Utils\ImporterDB;
abstract class BaseImporter
{
use LoggerTrait;
use Dispatchable;
use InteractsWithQueue;
use Queueable;
/**
* Holds the connection to the legacy database
*
* @var \Modules\Importer\Utils\ImporterDB
*/
protected $db;
/**
* The mapper class used for old IDs to new IDs
*
* @var IdMapper
*/
protected $idMapper;
/**
* The legacy table this importer targets
*
* @var string
*/
protected $table;
/**
* The column used for the ID, used for the ORDER BY
*
* @var string
*/
protected $idField = 'id';
public function __construct()
{
$importerService = app(ImporterService::class);
$this->db = new ImporterDB($importerService->getCredentials());
$this->idMapper = app(IdMapper::class);
}
/**
* The start method. Takes the offset to start from
*
* @param int $start
*
* @return mixed
*/
abstract public function run($start = 0);
/**
* Return a manifest of the import tasks to run. Returns an array of objects,
* which contain a start and end row
*
* @return array
*/
public function getManifest(): array
{
$manifest = [];
// Ensure that the table exists; if it doesn't skip it from the manifest
if (!$this->db->tableExists($this->table)) {
Log::info('Table '.$this->table.' doesn\'t exist');
return [];
}
$start = 0;
$total_rows = $this->db->getTotalRows($this->table);
Log::info('Found '.$total_rows.' rows for '.$this->table);
do {
$end = $start + $this->db->batchSize;
if ($end > $total_rows) {
$end = $total_rows;
}
$idx = $start + 1;
$manifest[] = [
'importer' => static::class,
'start' => $start,
'end' => $end,
'message' => 'Importing '.$this->table.' ('.$idx.' - '.$end.' of '.$total_rows.')',
];
$start += $this->db->batchSize;
} while ($start < $total_rows);
return $manifest;
}
/**
* Determine what columns exist, can be used for feature testing between v2/v5
*
* @return array
*/
public function getColumns(): array
{
}
/**
* @param $date
*
* @return Carbon
*/
protected function parseDate($date)
{
$carbon = Carbon::parse($date);
return $carbon;
}
/**
* Take a decimal duration and convert it to minutes
*
* @param $duration
*
* @return float|int
*/
protected function convertDuration($duration)
{
if (strpos($duration, '.') !== false) {
$delim = '.';
} elseif (strpos($duration, ':')) {
$delim = ':';
} else {
// no delimiter, assume it's just a straight hour
return (int) $duration * 60;
}
$hm = explode($delim, $duration);
$hours = (int) $hm[0] * 60;
$mins = (int) $hm[1];
return $hours + $mins;
}
}

View File

@@ -1,146 +0,0 @@
<?php
namespace Modules\Importer\Services;
use App\Contracts\Service;
use App\Repositories\KvpRepository;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Modules\Importer\Services\Importers\AircraftImporter;
use Modules\Importer\Services\Importers\AirlineImporter;
use Modules\Importer\Services\Importers\AirportImporter;
use Modules\Importer\Services\Importers\ClearDatabase;
use Modules\Importer\Services\Importers\ExpenseImporter;
use Modules\Importer\Services\Importers\FinalizeImporter;
use Modules\Importer\Services\Importers\FlightImporter;
use Modules\Importer\Services\Importers\GroupImporter;
use Modules\Importer\Services\Importers\LedgerImporter;
use Modules\Importer\Services\Importers\PirepImporter;
use Modules\Importer\Services\Importers\RankImport;
use Modules\Importer\Services\Importers\SettingsImporter;
use Modules\Importer\Services\Importers\UserImport;
class ImporterService extends Service
{
private $CREDENTIALS_KEY = 'legacy.importer.db';
/**
* @var KvpRepository
*/
private $kvpRepo;
/**
* The list of importers, in proper order
*/
private $importList = [
ClearDatabase::class,
RankImport::class,
GroupImporter::class,
AirlineImporter::class,
AircraftImporter::class,
AirportImporter::class,
FlightImporter::class,
UserImport::class,
PirepImporter::class,
ExpenseImporter::class,
LedgerImporter::class,
SettingsImporter::class,
FinalizeImporter::class,
];
public function __construct()
{
$this->kvpRepo = app(KvpRepository::class);
}
/**
* Save the credentials from a request
*
* @param \Illuminate\Http\Request $request
*/
public function saveCredentialsFromRequest(Request $request)
{
$creds = [
'host' => $request->post('db_host'),
'port' => $request->post('db_port'),
'name' => $request->post('db_name'),
'user' => $request->post('db_user'),
'pass' => $request->post('db_pass'),
'table_prefix' => $request->post('db_prefix'),
];
$this->saveCredentials($creds);
}
/**
* Save the given credentials
*
* @param array $creds
*/
public function saveCredentials(array $creds)
{
$creds = array_merge([
'admin_email' => '',
'host' => '',
'port' => '',
'name' => '',
'user' => '',
'pass' => 3306,
'table_prefix' => 'phpvms_',
], $creds);
$this->kvpRepo->save($this->CREDENTIALS_KEY, $creds);
}
/**
* Get the saved credentials
*/
public function getCredentials()
{
return $this->kvpRepo->get($this->CREDENTIALS_KEY);
}
/**
* Create a manifest of the import. Creates an array with the importer name,
* which then has a subarray of all of the different steps/stages it needs to run
*/
public function generateImportManifest()
{
$manifest = [];
foreach ($this->importList as $importerKlass) {
/** @var \Modules\Importer\Services\BaseImporter $importer */
$importer = new $importerKlass();
$manifest = array_merge($manifest, $importer->getManifest());
}
return $manifest;
}
/**
* Run a given stage
*
* @param $importer
* @param int $start
*
* @throws \Exception
*
* @return int|void
*/
public function run($importer, $start = 0)
{
if (!in_array($importer, $this->importList)) {
throw new Exception('Unknown importer "'.$importer.'"');
}
/** @var $importerInst \Modules\Importer\Services\BaseImporter */
$importerInst = new $importer();
try {
$importerInst->run($start);
} catch (Exception $e) {
Log::error('Error running importer: '.$e->getMessage());
}
}
}

View File

@@ -1,98 +0,0 @@
<?php
namespace Modules\Importer\Services\Importers;
use App\Models\Aircraft;
use App\Models\Airline;
use App\Models\Subfleet;
use Modules\Importer\Services\BaseImporter;
class AircraftImporter extends BaseImporter
{
protected $table = 'aircraft';
public function run($start = 0)
{
$this->comment('--- AIRCRAFT IMPORT ---');
$fields = [
'id',
'icao',
'name',
'fullname',
'registration',
'enabled',
];
// See if there is an airline column
$columns = $this->db->getColumns($this->table);
if (in_array('airline', $columns)) {
$fields[] = 'airline';
}
if (in_array('location', $columns)) {
$fields[] = 'location';
}
$count = 0;
$rows = $this->db->readRows($this->table, $this->idField, $start, $fields);
foreach ($rows as $row) {
$subfleet_name = $row->icao;
$airline_id = null;
if (!empty($row->airline)) {
$subfleet_name = $row->airline.' - '.$row->icao;
$airline_id = $this->idMapper->getMapping('airlines', $row->airline);
}
$subfleet = $this->getSubfleet($subfleet_name, $row->icao, $airline_id);
$where = [
'registration' => $row->registration,
];
$cols = [
'icao' => $row->icao,
'name' => $row->fullname,
'subfleet_id' => $subfleet->id,
'active' => $row->enabled,
];
if (!empty($row->location)) {
$cols['airport_id'] = $row->location;
}
$aircraft = Aircraft::firstOrCreate($where, $cols);
$this->idMapper->addMapping('aircraft', $row->id, $aircraft->id);
if ($aircraft->wasRecentlyCreated) {
$count++;
}
}
$this->info('Imported '.$count.' aircraft');
}
/**
* Return the subfleet
*
* @param string $name
* @param string $icao ICAO of the subfleet
* @param int $airline_id
*
* @return mixed
*/
protected function getSubfleet($name, $icao, $airline_id = null)
{
if (empty($airline_id)) {
$airline = Airline::first();
$airline_id = $airline->id;
}
return Subfleet::firstOrCreate([
'airline_id' => $airline_id,
'name' => $name,
], ['type' => $icao]);
}
}

View File

@@ -1,46 +0,0 @@
<?php
namespace Modules\Importer\Services\Importers;
use App\Models\Airline;
use Illuminate\Support\Facades\Log;
use Modules\Importer\Services\BaseImporter;
class AirlineImporter extends BaseImporter
{
public $table = 'airlines';
/**
* @param int $start
*/
public function run($start = 0)
{
$this->comment('--- AIRLINE IMPORT ---');
$count = 0;
$rows = $this->db->readRows($this->table, $this->idField, $start);
foreach ($rows as $row) {
$attrs = [
'iata' => $row->code,
'name' => $row->name,
'active' => $row->enabled,
];
$w = ['icao' => $row->code];
//$airline = Airline::firstOrCreate($w, $attrs);
$airline = Airline::create(array_merge($w, $attrs));
$this->idMapper->addMapping('airlines', $row->id, $airline->id);
$this->idMapper->addMapping('airlines', $row->code, $airline->id);
Log::debug('Mapping '.$row->id.'/'.$row->code.' to ID '.$airline->id);
if ($airline->wasRecentlyCreated) {
$count++;
}
}
$this->info('Imported '.$count.' airlines');
}
}

View File

@@ -1,63 +0,0 @@
<?php
namespace Modules\Importer\Services\Importers;
use App\Models\Airport;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Log;
use Modules\Importer\Services\BaseImporter;
class AirportImporter extends BaseImporter
{
protected $table = 'airports';
public function run($start = 0)
{
$this->comment('--- AIRPORT IMPORT ---');
$fields = [
'icao',
'name',
'country',
'lat',
'lng',
'hub',
];
$count = 0;
$rows = $this->db->readRows($this->table, $this->idField, $start, $fields);
foreach ($rows as $row) {
$attrs = [
'id' => trim($row->icao),
'icao' => trim($row->icao),
'name' => $row->name,
'country' => $row->country,
'lat' => $row->lat,
'lon' => $row->lng,
'hub' => $row->hub,
];
$w = ['id' => $attrs['id']];
//$airport = Airport::updateOrCreate($w, $attrs);
try {
$airport = Airport::create(array_merge($w, $attrs));
} catch (QueryException $e) {
$sqlState = $e->errorInfo[0];
$errorCode = $e->errorInfo[1];
if ($sqlState === '23000' && $errorCode === 1062) {
Log::info('Found duplicate for '.$row->icao.', ignoring');
return true;
}
return false;
}
if ($airport->wasRecentlyCreated) {
$count++;
}
}
$this->info('Imported '.$count.' airports');
}
}

View File

@@ -1,96 +0,0 @@
<?php
namespace Modules\Importer\Services\Importers;
use App\Models\Acars;
use App\Models\Aircraft;
use App\Models\Airline;
use App\Models\Airport;
use App\Models\Bid;
use App\Models\Expense;
use App\Models\File;
use App\Models\Flight;
use App\Models\FlightField;
use App\Models\FlightFieldValue;
use App\Models\Journal;
use App\Models\JournalTransaction;
use App\Models\Ledger;
use App\Models\News;
use App\Models\Pirep;
use App\Models\Role;
use App\Models\Subfleet;
use App\Models\User;
use App\Models\UserAward;
use Illuminate\Support\Facades\DB;
use Modules\Importer\Services\BaseImporter;
class ClearDatabase extends BaseImporter
{
/**
* Returns a default manifest just so this step gets run
*/
public function getManifest(): array
{
return [
[
'importer' => static::class,
'start' => 0,
'end' => 1,
'message' => 'Clearing database',
],
];
}
public function run($start = 0)
{
$this->cleanupDb();
}
/**
* Cleanup the local database of any users and other data that might conflict
* before running the importer
*/
protected function cleanupDb()
{
$this->info('Running database cleanup/empty before starting');
DB::statement('SET FOREIGN_KEY_CHECKS=0');
Bid::truncate();
File::truncate();
News::truncate();
Expense::truncate();
JournalTransaction::truncate();
Journal::truncate();
Ledger::truncate();
// Clear flights
DB::table('flight_fare')->truncate();
DB::table('flight_subfleet')->truncate();
FlightField::truncate();
FlightFieldValue::truncate();
Flight::truncate();
Subfleet::truncate();
Aircraft::truncate();
Airline::truncate();
Airport::truncate();
Acars::truncate();
Pirep::truncate();
UserAward::truncate();
User::truncate();
// Clear permissions
DB::table('permission_role')->truncate();
DB::table('permission_user')->truncate();
DB::table('role_user')->truncate();
// Role::truncate();
DB::statement('SET FOREIGN_KEY_CHECKS=1');
$this->idMapper->clear();
}
}

View File

@@ -1,48 +0,0 @@
<?php
namespace Modules\Importer\Services\Importers;
use App\Models\Enums\ExpenseType;
use App\Models\Expense;
use Modules\Importer\Services\BaseImporter;
class ExpenseImporter extends BaseImporter
{
protected $table = 'expenses';
private $expense_types = [
'M' => ExpenseType::MONTHLY,
'F' => ExpenseType::FLIGHT,
'P' => ExpenseType::MONTHLY, // percent, monthly
'G' => ExpenseType::FLIGHT, // percent, per-flight
];
/**
* {@inheritdoc}
*/
public function run($start = 0)
{
$this->comment('--- EXPENSES IMPORT ---');
$count = 0;
$rows = $this->db->readRows($this->table, $this->idField, $start);
foreach ($rows as $row) {
$attrs = [
'airline_id' => null,
'name' => $row->name,
'amount' => $row->amount,
'type' => $this->expense_types[$row->type],
'active' => 1,
'ref_model' => Expense::class,
];
$expense = Expense::updateOrCreate(['name' => $row->name], $attrs);
$this->idMapper->addMapping('expenses', $row->id, $expense->id);
$this->idMapper->addMapping('expenses', $row->name, $expense->id);
$count++;
}
$this->info('Imported '.$count.' expenses');
}
}

View File

@@ -1,50 +0,0 @@
<?php
namespace Modules\Importer\Services\Importers;
use App\Models\Airline;
use App\Models\Expense;
use App\Services\FinanceService;
use App\Support\Money;
use Modules\Importer\Services\BaseImporter;
class ExpenseLogImporter extends BaseImporter
{
protected $table = 'expenselog';
/**
* {@inheritdoc}
*
* @throws \Prettus\Validator\Exceptions\ValidatorException
*/
public function run($start = 0)
{
$this->comment('--- EXPENSE LOG IMPORT ---');
/** @var FinanceService $financeSvc */
$financeSvc = app(FinanceService::class);
$airline = Airline::first();
$count = 0;
$rows = $this->db->readRows($this->table, $this->idField, $start);
foreach ($rows as $row) {
$expense_id = $this->idMapper->getMapping('expenses', $row->name);
$expense = Expense::find($expense_id);
$debit = Money::createFromAmount($expense->amount);
$financeSvc->debitFromJournal(
$airline->journal,
$debit,
$airline,
'Expense: '.$expense->name,
$expense->transaction_group ?? 'Expenses',
'expense'
);
$count++;
}
$this->info('Imported '.$count.' expense logs');
}
}

View File

@@ -1,83 +0,0 @@
<?php
namespace Modules\Importer\Services\Importers;
use App\Models\User;
use App\Services\AircraftService;
use App\Services\UserService;
use Modules\Importer\Services\BaseImporter;
class FinalizeImporter extends BaseImporter
{
/**
* Returns a default manifest just so this step gets run
*/
public function getManifest(): array
{
return [
[
'importer' => static::class,
'start' => 0,
'end' => 1,
'message' => 'Finalizing import',
],
];
}
/**
* The start method. Takes the offset to start from
*
* @param int $start
*
* @return mixed
*/
public function run($start = 0)
{
$this->findLastPireps();
$this->recalculateUserStats();
$this->clearValueStore();
}
/**
* Go through and set the last PIREP ID for the users
*/
protected function findLastPireps()
{
// TODO
}
/**
* Recalculate all of the user stats
*/
protected function recalculateUserStats()
{
$this->comment('--- RECALCULATING USER STATS ---');
/** @var UserService $userSvc */
$userSvc = app(UserService::class);
User::all()->each(function ($user) use ($userSvc) {
$userSvc->recalculateStats($user);
});
}
/**
* Update the aircraft stats with the newest/latest PIREPs
*/
protected function recalculateAircraftStats()
{
$this->comment('--- RECALCULATING AIRCRAFT STATS ---');
/** @var AircraftService $aircraftSvc */
$aircraftSvc = app(AircraftService::class);
$aircraftSvc->recalculateStats();
}
/**
* Clear the value store of any old value mappings
*/
protected function clearValueStore()
{
$this->idMapper->clear();
}
}

View File

@@ -1,71 +0,0 @@
<?php
namespace Modules\Importer\Services\Importers;
use App\Models\Flight;
use Modules\Importer\Services\BaseImporter;
class FlightImporter extends BaseImporter
{
protected $table = 'schedules';
public function run($start = 0)
{
$this->comment('--- FLIGHT SCHEDULE IMPORT ---');
$fields = [
'id',
'code',
'flightnum',
'depicao',
'arricao',
'route',
'distance',
'flightlevel',
'deptime',
'arrtime',
'flighttime',
'notes',
'enabled',
];
$count = 0;
$rows = $this->db->readRows($this->table, $this->idField, $start, $fields);
foreach ($rows as $row) {
$airline_id = $this->idMapper->getMapping('airlines', $row->code);
$flight_num = trim($row->flightnum);
$attrs = [
'dpt_airport_id' => $row->depicao,
'arr_airport_id' => $row->arricao,
'route' => $row->route ?: '',
'distance' => round($row->distance ?: 0, 2),
'level' => $row->flightlevel ?: 0,
'dpt_time' => $row->deptime ?: '',
'arr_time' => $row->arrtime ?: '',
'flight_time' => $this->convertDuration($row->flighttime) ?: '',
'notes' => $row->notes ?: '',
'active' => $row->enabled ?: true,
];
try {
$w = ['airline_id' => $airline_id, 'flight_number' => $flight_num];
// $flight = Flight::updateOrCreate($w, $attrs);
$flight = Flight::create(array_merge($w, $attrs));
} catch (\Exception $e) {
$this->error($e);
}
$this->idMapper->addMapping('flights', $row->id, $flight->id);
// TODO: deserialize route_details into ACARS table
if ($flight->wasRecentlyCreated) {
$count++;
}
}
$this->info('Imported '.$count.' flights');
}
}

View File

@@ -1,166 +0,0 @@
<?php
namespace Modules\Importer\Services\Importers;
use App\Models\Permission;
use App\Models\Role;
use App\Services\RoleService;
use Illuminate\Support\Facades\Log;
use Modules\Importer\Services\BaseImporter;
/**
* Imports the groups into the permissions feature(s)
*/
class GroupImporter extends BaseImporter
{
protected $table = 'groups';
protected $idField = 'groupid';
/**
* Permissions in the legacy system, mapping them to the current system
*/
protected $legacy_permission_set = [
'EDIT_NEWS' => 0x1,
'EDIT_PAGES' => 0x2,
'EDIT_DOWNLOADS' => 0x4,
'EMAIL_PILOTS' => 0x8,
'EDIT_AIRLINES' => 0x10, //
'EDIT_FLEET' => 0x20, //
'EDIT_SCHEDULES' => 0x80, //
'IMPORT_SCHEDULES' => 0x100, //
'MODERATE_REGISTRATIONS' => 0x200,
'EDIT_PILOTS' => 0x400, //
'EDIT_GROUPS' => 0x800,
'EDIT_RANKS' => 0x1000, //
'EDIT_AWARDS' => 0x2000, //
'MODERATE_PIREPS' => 0x4000, //
'VIEW_FINANCES' => 0x8000, //
'EDIT_EXPENSES' => 0x10000, //
'EDIT_SETTINGS' => 0x20000, //
'EDIT_PIREPS_FIELDS' => 0x40000, //
'EDIT_PROFILE_FIELDS' => 0x80000, //
'EDIT_VACENTRAL' => 0x100000,
'ACCESS_ADMIN' => 0x2000000,
'FULL_ADMIN' => 35651519, //
];
/**
* Map a legacy value over to one of the current permission values
*/
protected $legacy_to_permission = [
'FULL_ADMIN' => 'admin',
'EDIT_AIRLINES' => 'airlines',
'EDIT_AWARDS' => 'awards',
'EDIT_FLEET' => 'fleet',
'EDIT_EXPENCES' => 'finances',
'VIEW_FINANCES' => 'finances',
'EDIT_SCHEDULES' => 'flights',
'EDIT_PILOTS' => 'users',
'EDIT_PROFILE_FIELDS' => 'users',
'EDIT_SETTINGS' => 'settings',
'MODERATE_PIREPS' => 'pireps',
'EDIT_PIREPS_FIELDS' => 'pireps',
'EDIT_RANKS' => 'ranks',
'MODERATE_REGISTRATIONS' => 'users',
];
public function run($start = 0)
{
$this->comment('--- ROLES/GROUPS IMPORT ---');
/** @var \App\Services\RoleService $roleSvc */
$roleSvc = app(RoleService::class);
$permMappings = $this->getPermissions();
$count = 0;
$permCount = 0;
$rows = $this->db->readRows($this->table, $this->idField, $start);
foreach ($rows as $row) {
// Legacy "administrator" role is now "admin", just map that 1:1
if (strtolower($row->name) === 'administrators') {
$role = Role::where('name', 'admin')->first();
$this->idMapper->addMapping('group', $row->groupid, $role->id);
continue;
}
// Map the "core" roles, which are active/inactive pilots to a new ID of
// -1; so then we can ignore/not add these groups, and then ignore them
// for any of the users that are being imported. these groups are unused
if ($row->core === 1 || $row->core === '1') {
$this->idMapper->addMapping('group', $row->groupid, -1);
continue;
}
$name = str_slug($row->name);
$role = Role::firstOrCreate(
['name' => $name],
['display_name' => $row->name]
);
$this->idMapper->addMapping('group', $row->groupid, $role->id);
// See if the permission set mask contains one of the mappings above
// Add all of the ones which apply, and then set them on the new role
$permissions = [];
foreach ($this->legacy_permission_set as $legacy_name => $mask) {
$val = $row->permissions & $mask;
if ($val === $mask) {
// Map this legacy permission to what it is under the new system
if (!array_key_exists($legacy_name, $this->legacy_to_permission)) {
continue;
}
// Get the ID of the permission
try {
$permName = $this->legacy_to_permission[$legacy_name];
if ($permName === 'admin') {
foreach ($permMappings as $name => $value) {
if (!in_array($value, $permissions)) {
$permissions[] = $value;
}
}
continue;
}
$permMapId = $permMappings[$permName];
if (!in_array($permMapId, $permissions)) {
$permissions[] = $permMapId;
}
} catch (\Exception $e) {
Log::error($e->getMessage());
}
}
}
if (count($permissions) > 0) {
$roleSvc->setPermissionsForRole($role, $permissions);
$permCount += count($permissions);
}
if ($role->wasRecentlyCreated) {
$count++;
}
}
$this->info('Imported '.$count.' roles, synced '.$permCount.' permissions');
}
/**
* Get all of the permissions from locally and return a kvp with the
* key being the permission short-name and the value being the ID
*
* @return array
*/
private function getPermissions(): array
{
$mappings = [];
$permissions = Permission::all();
/** @var \App\Models\Permission $p */
foreach ($permissions as $p) {
$mappings[$p->name] = $p->id;
}
return $mappings;
}
}

View File

@@ -1,75 +0,0 @@
<?php
namespace Modules\Importer\Services\Importers;
use App\Models\Pirep;
use App\Services\FinanceService;
use App\Support\Money;
use Modules\Importer\Services\BaseImporter;
class LedgerImporter extends BaseImporter
{
protected $table = 'ledger';
/**
* Legacy ID to the current ledger ref_model mapping
*
* @var array
*/
private static $legacy_paysource = [
1 => Pirep::class,
];
/**
* {@inheritdoc}
*
* @throws \Prettus\Validator\Exceptions\ValidatorException
*/
public function run($start = 0)
{
if (!$this->db->tableExists('ledger')) {
return;
}
$this->comment('--- LEDGER IMPORT ---');
/** @var FinanceService $financeSvc */
$financeSvc = app(FinanceService::class);
$count = 0;
$rows = $this->db->readRows($this->table, $this->idField, $start);
foreach ($rows as $row) {
$pirep = Pirep::find($this->idMapper->getMapping('pireps', $row->pirepid));
if (!$pirep) {
continue;
}
$pilot_pay = Money::createFromAmount($row->amount * 100);
$memo = 'Pilot payment';
$financeSvc->debitFromJournal(
$pirep->airline->journal,
$pilot_pay,
$pirep,
$memo,
'Pilot Pay',
'pilot_pay',
$row->submitdate
);
$financeSvc->creditToJournal(
$pirep->user->journal,
$pilot_pay,
$pirep,
$memo,
'Pilot Pay',
'pilot_pay',
$row->submitdate
);
$count++;
}
$this->info('Imported '.$count.' ledger entries');
}
}

View File

@@ -1,153 +0,0 @@
<?php
namespace Modules\Importer\Services\Importers;
use App\Models\Enums\FlightType;
use App\Models\Enums\PirepSource;
use App\Models\Enums\PirepState;
use App\Models\Pirep;
use Modules\Importer\Services\BaseImporter;
class PirepImporter extends BaseImporter
{
protected $table = 'pireps';
protected $idField = 'pirepid';
public function run($start = 0)
{
$this->comment('--- PIREP IMPORT ---');
$fields = [
'pirepid',
'pilotid',
'code',
'aircraft',
'flightnum',
'depicao',
'arricao',
'fuelused',
'route',
'source',
'accepted',
'submitdate',
'distance',
'flighttime_stamp',
'flighttype',
];
// See if there's a flightlevel column, export that if there is
$columns = $this->db->getColumns($this->table);
if (in_array('flightlevel', $columns)) {
$fields[] = 'flightlevel';
}
$count = 0;
$rows = $this->db->readRows($this->table, $this->idField, $start, $fields);
foreach ($rows as $row) {
$pirep_id = $row->pirepid;
$user_id = $this->idMapper->getMapping('users', $row->pilotid);
$airline_id = $this->idMapper->getMapping('airlines', $row->code);
$aircraft_id = $this->idMapper->getMapping('aircraft', $row->aircraft);
$attrs = [
'user_id' => $user_id,
'airline_id' => $airline_id,
'aircraft_id' => $aircraft_id,
'flight_number' => $row->flightnum ?: '',
'dpt_airport_id' => $row->depicao,
'arr_airport_id' => $row->arricao,
'block_fuel' => $row->fuelused,
'route' => $row->route ?: '',
'source_name' => $row->source,
'state' => $this->mapState($row->accepted),
'created_at' => $this->parseDate($row->submitdate),
'updated_at' => $this->parseDate($row->submitdate),
];
// Set the distance
$distance = round($row->distance ?: 0, 2);
$attrs['distance'] = $distance;
$attrs['planned_distance'] = $distance;
// Set the flight time properly
$duration = $this->convertDuration($row->flighttime_stamp);
$attrs['flight_time'] = $duration;
$attrs['planned_flight_time'] = $duration;
// Set how it was filed
if (strtoupper($row->source) === 'MANUAL') {
$attrs['source'] = PirepSource::MANUAL;
} else {
$attrs['source'] = PirepSource::ACARS;
}
// Set the flight type
$row->flighttype = strtoupper($row->flighttype);
if ($row->flighttype === 'P') {
$attrs['flight_type'] = FlightType::SCHED_PAX;
} elseif ($row->flighttype === 'C') {
$attrs['flight_type'] = FlightType::SCHED_CARGO;
} else {
$attrs['flight_type'] = FlightType::CHARTER_PAX_ONLY;
}
// Set the flight level of the PIREP is set
if (property_exists($row, 'flightlevel')) {
$attrs['level'] = $row->flightlevel;
} else {
$attrs['level'] = 0;
}
$w = ['id' => $pirep_id];
$pirep = Pirep::updateOrCreate($w, $attrs);
//$pirep = Pirep::create(array_merge($w, $attrs));
//Log::debug('pirep oldid='.$pirep_id.', olduserid='.$row->pilotid
// .'; new id='.$pirep->id.', user id='.$user_id);
$source = strtoupper($row->source);
if ($source === 'SMARTCARS') {
// TODO: Parse smartcars log into the acars table
} elseif ($source === 'KACARS') {
// TODO: Parse kACARS log into acars table
} elseif ($source === 'XACARS') {
// TODO: Parse XACARS log into acars table
}
// TODO: Add extra fields in as PIREP fields
$this->idMapper->addMapping('pireps', $row->pirepid, $pirep->id);
if ($pirep->wasRecentlyCreated) {
$count++;
}
}
$this->info('Imported '.$count.' pireps');
}
/**
* Map the old status to the current
* https://github.com/nabeelio/phpvms_v2/blob/master/core/app.config.php#L450
*
* @param int $old_state
*
* @return int
*/
private function mapState($old_state)
{
$map = [
0 => PirepState::PENDING,
1 => PirepState::ACCEPTED,
2 => PirepState::REJECTED,
3 => PirepState::IN_PROGRESS,
];
$old_state = (int) $old_state;
if (!in_array($old_state, $map)) {
return PirepState::PENDING;
}
return $map[$old_state];
}
}

View File

@@ -1,37 +0,0 @@
<?php
namespace Modules\Importer\Services\Importers;
use App\Models\Rank;
use Modules\Importer\Services\BaseImporter;
class RankImport extends BaseImporter
{
protected $table = 'ranks';
protected $idField = 'rankid';
public function run($start = 0)
{
$this->comment('--- RANK IMPORT ---');
$count = 0;
$rows = $this->db->readRows($this->table, $this->idField, $start);
foreach ($rows as $row) {
$rank = Rank::updateOrCreate(['name' => $row->rank], [
'image_url' => $row->rankimage,
'hours' => $row->minhours,
'acars_base_payrate' => $row->payrate,
'manual_base_payrate' => $row->payrate,
]);
$this->idMapper->addMapping('ranks', $row->rankid, $rank->id);
$this->idMapper->addMapping('ranks', $row->rank, $rank->id);
if ($rank->wasRecentlyCreated) {
$count++;
}
}
$this->info('Imported '.$count.' ranks');
}
}

View File

@@ -1,31 +0,0 @@
<?php
namespace Modules\Importer\Services\Importers;
use App\Repositories\SettingRepository;
use Modules\Importer\Services\BaseImporter;
class SettingsImporter extends BaseImporter
{
protected $table = 'settings';
public function run($start = 0)
{
$this->comment('--- SETTINGS IMPORT ---');
/** @var SettingRepository $settingsRepo */
$settingsRepo = app(SettingRepository::class);
$count = 0;
$rows = $this->db->readRows($this->table, $this->idField, $start);
foreach ($rows as $row) {
switch ($row->name) {
case 'ADMIN_EMAIL':
$settingsRepo->store('general.admin_email', $row->value);
break;
}
}
$this->info('Imported '.$count.' settings');
}
}

View File

@@ -1,166 +0,0 @@
<?php
namespace Modules\Importer\Services\Importers;
use App\Models\Enums\UserState;
use App\Models\Role;
use App\Models\User;
use App\Services\UserService;
use App\Support\Units\Time;
use App\Support\Utils;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Modules\Importer\Services\BaseImporter;
class UserImport extends BaseImporter
{
protected $table = 'pilots';
protected $idField = 'pilotid';
/**
* @var UserService
*/
private $userSvc;
public function run($start = 0)
{
$this->comment('--- USER IMPORT ---');
$this->userSvc = app(UserService::class);
$count = 0;
$first_row = true;
$rows = $this->db->readRows($this->table, $this->idField, $start);
foreach ($rows as $row) {
$pilot_id = $row->pilotid; // This isn't their actual ID
$name = $row->firstname.' '.$row->lastname;
// Figure out which airline, etc, they belong to
$airline_id = $this->idMapper->getMapping('airlines', $row->code);
// Log::info('User airline from '.$row->code.' to ID '.$airline_id);
$rank_id = $this->idMapper->getMapping('ranks', $row->rank);
$state = $this->getUserState($row->retired);
if ($first_row) {
$new_password = 'admin';
$first_row = false;
} else {
$new_password = Str::random(60);
}
// Look for a user with that pilot ID already. If someone has it
if ($this->userSvc->isPilotIdAlreadyUsed($pilot_id)) {
Log::info('User with pilot id '.$pilot_id.' exists');
// Is this the same user? If not, get a new pilot ID
$user_exist = User::where('pilot_id', $pilot_id)->first();
if ($user_exist->email !== $row->email) {
$pilot_id = $this->userSvc->getNextAvailablePilotId();
}
}
$attrs = [
'pilot_id' => $pilot_id,
'name' => $name,
'password' => Hash::make($new_password),
'api_key' => Utils::generateApiKey(),
'airline_id' => $airline_id,
'rank_id' => $rank_id,
'home_airport_id' => $row->hub,
'curr_airport_id' => $row->hub,
'country' => $row->location,
'flights' => (int) $row->totalflights,
'flight_time' => Time::hoursToMinutes($row->totalhours),
'state' => $state,
'created_at' => $this->parseDate($row->joindate),
];
$user = User::updateOrCreate(['email' => $row->email], $attrs);
$this->idMapper->addMapping('users', $row->pilotid, $user->id);
$this->updateUserRoles($user, $row->pilotid);
if ($user->wasRecentlyCreated) {
$count++;
}
}
$this->info('Imported '.$count.' users');
}
/**
* Update a user's roles and add them to the proper ones
*
* @param User $user
* @param string $old_pilot_id
*/
protected function updateUserRoles(User $user, $old_pilot_id)
{
// Be default add them to the user role, and then determine if they
// belong to any other groups, and add them to that
$newRoles = [];
// Figure out what other groups they belong to... read from the old table, and map
// them to the new group(s)
$old_user_groups = $this->db->findBy('groupmembers', ['pilotid' => $old_pilot_id]);
foreach ($old_user_groups as $oldGroup) {
$newRoleId = $this->idMapper->getMapping('group', $oldGroup->groupid);
// This role should be ignored
if ($newRoleId === -1) {
continue;
}
$newRoles[] = $newRoleId;
}
// Assign the groups to the new user
$user->attachRoles($newRoles);
}
/**
* Get the user's new state from their original state
*
* @param $state
*
* @return int
*/
protected function getUserState($state)
{
// Return active for now, let the stats/cron determine the status later
return UserState::ACTIVE;
/*$state = (int) $state;
// Declare array of classic states
$phpvms_classic_states = [
'ACTIVE' => 0,
'INACTIVE' => 1,
'BANNED' => 2,
'ON_LEAVE' => 3,
];
// Decide which state they will be in accordance with v7
if ($state === $phpvms_classic_states['ACTIVE']) {
return UserState::ACTIVE;
}
if ($state === $phpvms_classic_states['INACTIVE']) {
// TODO: Make an inactive state?
return UserState::REJECTED;
}
if ($state === $phpvms_classic_states['BANNED']) {
return UserState::SUSPENDED;
}
if ($state === $phpvms_classic_states['ON_LEAVE']) {
return UserState::ON_LEAVE;
}
$this->error('Unknown status: '.$state);
return UserState::ACTIVE;*/
}
}

View File

@@ -1,57 +0,0 @@
<?php
namespace Modules\Importer\Utils;
use App\Contracts\Service;
use Spatie\Valuestore\Valuestore;
class IdMapper extends Service
{
private $valueStore;
public function __construct()
{
$this->valueStore = Valuestore::make(storage_path('app/legacy_migration.json'));
}
/**
* Create a new mapping between an old ID and the new one
*
* @param string $entity Name of the entity (e,g table)
* @param string $old_id
* @param string $new_id
*/
public function addMapping($entity, $old_id, $new_id)
{
$key_name = $entity.'_'.$old_id;
if (!$this->valueStore->has($key_name)) {
$this->valueStore->put($key_name, $new_id);
}
}
/**
* Return the ID for a mapping
*
* @param $entity
* @param $old_id
*
* @return bool
*/
public function getMapping($entity, $old_id)
{
$key_name = $entity.'_'.$old_id;
if (!$this->valueStore->has($key_name)) {
return 0;
}
return $this->valueStore->get($key_name);
}
/**
* Clear the value store
*/
public function clear()
{
$this->valueStore->flush();
}
}

View File

@@ -1,256 +0,0 @@
<?php
namespace Modules\Importer\Utils;
use Illuminate\Support\Facades\Log;
use PDO;
use PDOException;
/**
* Real basic to interface with an importer
*/
class ImporterDB
{
/**
* @var int
*/
public $batchSize;
/**
* @var PDO
*/
private $conn;
/**
* @var string
*/
private $dsn;
/**
* @var array
*/
private $creds;
public function __construct($creds)
{
$this->creds = $creds;
$this->dsn = 'mysql:'.implode(';', [
'host='.$this->creds['host'],
'port='.$this->creds['port'],
'dbname='.$this->creds['name'],
]);
Log::info('Using DSN: '.$this->dsn);
$this->batchSize = config('installer.importer.batch_size', 20);
}
public function __destruct()
{
$this->close();
}
public function connect()
{
try {
$this->conn = new PDO($this->dsn, $this->creds['user'], $this->creds['pass']);
$this->conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
} catch (PDOException $e) {
Log::error($e);
throw $e;
}
}
public function close()
{
if ($this->conn) {
$this->conn = null;
}
}
/**
* Return the table name with the prefix
*
* @param $table
*
* @return string
*/
public function tableName($table)
{
if ($this->creds['table_prefix'] !== false) {
return $this->creds['table_prefix'].$table;
}
return $table;
}
/**
* Does a table exist? Try to get the column information on it.
* The result will be 'false' if that table isn't there
*
* @param $table
*
* @return bool
*/
public function tableExists($table): bool
{
$this->connect();
$sql = 'SHOW COLUMNS FROM '.$this->tableName($table);
$result = $this->conn->query($sql);
if (!$result) {
return false;
}
return true;
}
/**
* Get the names of the columns for a particular table
*
* @param $table
*
* @return mixed
*/
public function getColumns($table)
{
$this->connect();
$sql = 'SHOW COLUMNS FROM '.$this->tableName($table);
$result = $this->conn->query($sql)->fetchAll();
$rows = [];
foreach ($result as $row) {
$rows[] = $row->Field;
}
return $rows;
}
/**
* @param $table
*
* @return mixed
*/
public function getTotalRows($table)
{
$this->connect();
$sql = 'SELECT COUNT(*) FROM '.$this->tableName($table);
$rows = $this->conn->query($sql)->fetchColumn();
Log::info('Found '.$rows.' rows in '.$table);
return (int) $rows;
}
/**
* Read rows from a table with a given assoc array. Simple
*
* @param string $table
* @param array $attrs
*
* @return false|\PDOStatement
*/
public function findBy($table, array $attrs)
{
$this->connect();
$where = [];
foreach ($attrs as $col => $value) {
$where[] = $col.'=\''.$value.'\'';
}
$where = implode(' AND ', $where);
$sql = implode(' ', [
'SELECT',
'*',
'FROM',
$this->tableName($table),
'WHERE',
$where,
]);
return $this->conn->query($sql);
}
/**
* Read all the rows in a table, but read them in a batched manner
*
* @param string $table The name of the table
* @param string $order_by Column to order by
* @param int $start_offset
* @param string $fields
*
* @return array
*/
public function readRows($table, $order_by = 'id', $start_offset = 0, $fields = '*')
{
$this->connect();
$offset = $start_offset;
// $total_rows = $this->getTotalRows($table);
$rows = [];
$result = $this->readRowsOffset($table, $this->batchSize, $offset, $order_by, $fields);
if ($result === false || $result === null) {
return [];
}
try {
foreach ($result as $row) {
$rows[] = $row;
}
} catch (\Exception $e) {
Log::error('foreach rows error: '.$e->getMessage());
}
return $rows;
}
/**
* @param string $table
* @param int $limit Number of rows to read
* @param int $offset Where to start from
* @param $order_by
* @param string $fields
*
* @return false|\PDOStatement|void
*/
public function readRowsOffset($table, $limit, $offset, $order_by, $fields = '*')
{
if (is_array($fields)) {
$fields = implode(',', $fields);
}
$sql = implode(' ', [
'SELECT',
$fields,
'FROM',
$this->tableName($table),
'ORDER BY '.$order_by.' ASC',
'LIMIT '.$limit,
'OFFSET '.$offset,
]);
try {
$result = $this->conn->query($sql);
if (!$result || $result->rowCount() === 0) {
return;
}
return $result;
} catch (PDOException $e) {
// Without incrementing the offset, it should re-run the same query
Log::error('Error readRowsOffset: '.$e->getMessage());
if (strpos($e->getMessage(), 'server has gone away') !== false) {
$this->connect();
}
} catch (\Exception $e) {
Log::error('Error readRowsOffset: '.$e->getMessage());
}
}
}

View File

@@ -1,29 +0,0 @@
{
"name": "phpvms/importer",
"type": "laravel-library",
"description": "The importer module for phpVMS",
"authors": [
{
"name": "Nabeel Shahzad",
"email": ""
}
],
"require": {
"composer/installers": "~1.0"
},
"extra": {
"laravel": {
"providers": [
"Modules\\Importer\\Providers\\ImporterServiceProvider"
],
"aliases": {
}
}
},
"autoload": {
"psr-4": {
"Modules\\Importer\\": ""
}
}
}

View File

@@ -1,14 +0,0 @@
{
"name": "Importer",
"alias": "importer",
"description": "",
"keywords": [],
"active": 1,
"order": 0,
"providers": [
"Modules\\Importer\\Providers\\ImporterServiceProvider"
],
"aliases": {},
"files": [],
"requires": []
}

View File

@@ -1,42 +0,0 @@
<?php
return [
'php' => [
'version' => '7.3',
],
'cache' => [
// Default driver to use when no driver is present
'default' => 'file',
'drivers' => [
// 'Zend OPcache' => 'opcache',
// 'apc' => 'apc',
],
],
'extensions' => [
// 'bcmath',
'fileinfo',
'openssl',
'pdo',
'mbstring',
'tokenizer',
'json',
'curl',
],
// Make sure these are writable
'permissions' => [
base_path('bootstrap/cache'),
public_path('uploads'),
storage_path(),
storage_path('app/public'),
storage_path('app/public/avatars'),
storage_path('app/public/uploads'),
storage_path('framework'),
storage_path('framework/cache'),
storage_path('framework/sessions'),
storage_path('framework/views'),
storage_path('logs'),
],
];

View File

@@ -1,357 +0,0 @@
<?php
namespace Modules\Installer\Http\Controllers;
use App\Contracts\Controller;
use App\Services\AirlineService;
use App\Services\AnalyticsService;
use App\Services\Installer\DatabaseService;
use App\Services\Installer\InstallerService;
use App\Services\Installer\MigrationService;
use App\Services\Installer\SeederService;
use App\Services\UserService;
use App\Support\Countries;
use App\Support\Utils;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Laracasts\Flash\Flash;
use Modules\Installer\Services\ConfigService;
use Modules\Installer\Services\RequirementsService;
class InstallerController extends Controller
{
private $airlineSvc;
private $analyticsSvc;
private $dbSvc;
private $envSvc;
private $migrationSvc;
private $reqSvc;
private $seederSvc;
private $userService;
/**
* InstallerController constructor.
*
* @param AirlineService $airlineSvc
* @param AnalyticsService $analyticsSvc
* @param DatabaseService $dbService
* @param ConfigService $envService
* @param MigrationService $migrationSvc
* @param RequirementsService $reqSvc
* @param SeederService $seederSvc
* @param UserService $userService
*/
public function __construct(
AirlineService $airlineSvc,
AnalyticsService $analyticsSvc,
DatabaseService $dbService,
ConfigService $envService,
MigrationService $migrationSvc,
RequirementsService $reqSvc,
SeederService $seederSvc,
UserService $userService
) {
$this->airlineSvc = $airlineSvc;
$this->analyticsSvc = $analyticsSvc;
$this->dbSvc = $dbService;
$this->envSvc = $envService;
$this->migrationSvc = $migrationSvc;
$this->reqSvc = $reqSvc;
$this->seederSvc = $seederSvc;
$this->userService = $userService;
Utils::disableDebugToolbar();
}
/**
* Display a listing of the resource.
*/
public function index()
{
if (config('app.key') !== 'base64:zdgcDqu9PM8uGWCtMxd74ZqdGJIrnw812oRMmwDF6KY=') {
return view('installer::errors/already-installed');
}
return view('installer::install/index-start');
}
protected function testDb(Request $request)
{
$this->dbSvc->checkDbConnection(
$request->post('db_conn'),
$request->post('db_host'),
$request->post('db_port'),
$request->post('db_name'),
$request->post('db_user'),
$request->post('db_pass')
);
}
/**
* Check the database connection
*/
public function dbtest(Request $request)
{
$status = 'success'; // success|warn|danger
$message = 'Database connection looks good!';
try {
$this->testDb($request);
} catch (\Exception $e) {
$status = 'danger';
$message = 'Failed! '.$e->getMessage();
}
return view('installer::install/dbtest', [
'status' => $status,
'message' => $message,
]);
}
/**
* Check if any of the items has been marked as failed
*
* @param array $arr
*
* @return bool
*/
protected function allPassed(array $arr): bool
{
foreach ($arr as $item) {
if ($item['passed'] === false) {
return false;
}
}
return true;
}
/**
* Step 1. Check the modules and permissions
*
* @param Request $request
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function step1(Request $request)
{
$php_version = $this->reqSvc->checkPHPVersion();
$extensions = $this->reqSvc->checkExtensions();
$directories = $this->reqSvc->checkPermissions();
// Only pass if all the items in the ext and dirs are passed
$statuses = [
$php_version['passed'] === true,
$this->allPassed($extensions) === true,
$this->allPassed($directories) === true,
];
// Make sure there are no false values
$passed = !\in_array(false, $statuses, true);
return view('installer::install/steps/step1-requirements', [
'php' => $php_version,
'extensions' => $extensions,
'directories' => $directories,
'passed' => $passed,
]);
}
/**
* Step 2. Database Setup
*
* @param Request $request
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function step2(Request $request)
{
$db_types = ['mysql' => 'mysql', 'sqlite' => 'sqlite'];
return view('installer::install/steps/step2-db', [
'db_types' => $db_types,
]);
}
/**
* Step 2a. Create the .env
*
* @param Request $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function envsetup(Request $request)
{
$log_str = $request->post();
$log_str['db_pass'] = '';
Log::info('ENV setup', $log_str);
// Before writing out the env file, test the DB credentials
try {
$this->testDb($request);
} catch (\Exception $e) {
Log::error('Testing db before writing configs failed');
Log::error($e->getMessage());
Flash::error($e->getMessage());
return redirect(route('installer.step2'))->withInput();
}
// Now write out the env file
$attrs = [
'SITE_NAME' => $request->post('site_name'),
'SITE_URL' => $request->post('site_url'),
'DB_CONN' => $request->post('db_conn'),
'DB_HOST' => $request->post('db_host'),
'DB_PORT' => $request->post('db_port'),
'DB_NAME' => $request->post('db_name'),
'DB_USER' => $request->post('db_user'),
'DB_PASS' => $request->post('db_pass'),
'DB_PREFIX' => $request->post('db_prefix'),
];
/*
* Create the config files and then redirect so that the
* framework can pickup all those configs, etc, before we
* setup the database and stuff
*/
try {
$this->envSvc->createConfigFiles($attrs);
} catch (\Exception $e) {
Log::error('Config files failed to write');
Log::error($e->getMessage());
Flash::error($e->getMessage());
return redirect(route('installer.step2'))->withInput();
}
// Needs to redirect so it can load the new .env
Log::info('Redirecting to database setup');
return redirect(route('installer.dbsetup'));
}
/**
* Step 2b. Setup the database
*
* @param Request $request
*
* @return mixed
*/
public function dbsetup(Request $request)
{
$console_out = '';
try {
$console_out .= $this->dbSvc->setupDB();
$console_out .= $this->migrationSvc->runAllMigrations();
$this->seederSvc->syncAllSeeds();
} catch (QueryException $e) {
Log::error('Error on db setup: '.$e->getMessage());
dd($e);
$this->envSvc->removeConfigFiles();
Flash::error($e->getMessage());
return redirect(route('installer.step2'))->withInput();
}
$console_out = trim($console_out);
return view('installer::install/steps/step2a-db_output', [
'console_output' => $console_out,
]);
}
/**
* Step 3. Setup the admin user and initial settings
*/
public function step3(Request $request)
{
return view('installer::install/steps/step3-user', [
'countries' => Countries::getSelectList(),
]);
}
/**
* Step 3 submit
*
* @param Request $request
*
* @throws \RuntimeException
* @throws \Prettus\Validator\Exceptions\ValidatorException
* @throws \Exception
*
* @return mixed
*/
public function usersetup(Request $request)
{
$validator = Validator::make($request->all(), [
'airline_name' => 'required',
'airline_icao' => 'required|size:3|unique:airlines,icao',
'airline_country' => 'required',
'name' => 'required',
'email' => 'required|email|unique:users,email',
'password' => 'required|confirmed',
]);
if ($validator->fails()) {
return redirect('install/step3')
->withErrors($validator)
->withInput();
}
/**
* Create the first airline
*/
$attrs = [
'icao' => $request->get('airline_icao'),
'name' => $request->get('airline_name'),
'country' => $request->get('airline_country'),
];
$airline = $this->airlineSvc->createAirline($attrs);
/**
* Create the user, and associate to the airline
* Ensure the seed data at least has one airport
* KAUS, for giggles, though.
*/
$attrs = [
'name' => $request->get('name'),
'email' => $request->get('email'),
'api_key' => Utils::generateApiKey(),
'airline_id' => $airline->id,
'password' => Hash::make($request->get('password')),
];
$user = $this->userService->createUser($attrs, ['admin']);
Log::info('User registered: ', $user->toArray());
// Set the initial admin e-mail address
setting_save('general.admin_email', $user->email);
// Save telemetry setting
setting_save('general.telemetry', get_truth_state($request->get('telemetry')));
// Try sending telemetry info
$this->analyticsSvc->sendInstall();
return view('installer::install/steps/step3a-completed', []);
}
/**
* Final step
*
* @param Request $request
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function complete(Request $request)
{
$installerSvc = app(InstallerService::class);
$installerSvc->disableInstallerModules();
return redirect('/login');
}
}

View File

@@ -1,101 +0,0 @@
<?php
namespace Modules\Installer\Providers;
use App\Contracts\Modules\ServiceProvider;
use Illuminate\Support\Facades\Route;
class InstallerServiceProvider extends ServiceProvider
{
/**
* Boot the application events.
*/
public function boot(): void
{
$this->registerRoutes();
$this->registerTranslations();
$this->registerConfig();
$this->registerViews();
}
/**
* Register the routes
*/
protected function registerRoutes()
{
Route::group([
'as' => 'installer.',
'prefix' => 'install',
'middleware' => ['web'],
'namespace' => 'Modules\Installer\Http\Controllers',
], function () {
Route::get('/', 'InstallerController@index')->name('index');
Route::post('/dbtest', 'InstallerController@dbtest')->name('dbtest');
Route::get('/step1', 'InstallerController@step1')->name('step1');
Route::post('/step1', 'InstallerController@step1')->name('step1post');
Route::get('/step2', 'InstallerController@step2')->name('step2');
Route::post('/envsetup', 'InstallerController@envsetup')->name('envsetup');
Route::get('/dbsetup', 'InstallerController@dbsetup')->name('dbsetup');
Route::get('/step3', 'InstallerController@step3')->name('step3');
Route::post('/usersetup', 'InstallerController@usersetup')->name('usersetup');
Route::get('/complete', 'InstallerController@complete')->name('complete');
});
}
/**
* Register config.
*/
protected function registerConfig()
{
$this->mergeConfigFrom(__DIR__.'/../Config/config.php', 'installer');
}
/**
* Register views.
*/
public function registerViews()
{
$viewPath = resource_path('views/modules/installer');
$sourcePath = __DIR__.'/../Resources/views';
$this->publishes([
$sourcePath => $viewPath,
], 'views');
$paths = array_map(
function ($path) {
return $path.'/modules/installer';
},
\Config::get('view.paths')
);
$paths[] = $sourcePath;
$this->loadViewsFrom($paths, 'installer');
}
/**
* Register translations.
*/
public function registerTranslations()
{
$langPath = resource_path('lang/modules/installer');
if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, 'installer');
} else {
$this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'installer');
}
}
/**
* Get the services provided by the provider.
*/
public function provides(): array
{
return [];
}
}

View File

@@ -1,90 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title') - installer</title>
<link rel="shortcut icon" type="image/png" href="{{ public_asset('/assets/img/favicon.png') }}"/>
<meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, shrink-to-fit=no'
name='viewport'/>
<meta name="base-url" content="{!! url('') !!}">
<meta name="api-key" content="{!! Auth::check() ? Auth::user()->api_key: '' !!}">
<meta name="csrf-token" content="{!! csrf_token() !!}">
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700,200" rel="stylesheet"/>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css" rel="stylesheet"/>
<link href="{{ public_asset('/assets/frontend/css/bootstrap.min.css') }}" rel="stylesheet"/>
<link href="{{ public_asset('/assets/frontend/css/now-ui-kit.css') }}" rel="stylesheet"/>
<link href="{{ public_asset('/assets/installer/css/vendor.css') }}" rel="stylesheet"/>
<link href="{{ public_asset('/assets/frontend/css/styles.css') }}" rel="stylesheet"/>
<link rel="stylesheet"
href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css">
<style>
.table tr:first-child td {
border-top: 0px;
}
@yield('css')
</style>
</head>
<body class="login-page" style="background: #067ec1;">
<div class="page-header clear-filter">
<div class="content">
<div class="container">
<div class="row">
<div class="col-md-8 ml-auto mr-auto content-center">
<div class="p-10" style="padding: 10px 0;">
<div class="row">
<div class="col-4">
<img src="{{ public_asset('/assets/img/logo_blue_bg.svg') }}" width="135px" style="" alt=""/>
</div>
<div class="col-8 text-right">
<h4 class="text-white mb-0 mr-0 ml-0" style="margin-top: 5px;">@yield('title')</h4>
</div>
</div>
</div>
<div class="card card-login card-plain" style="background: #FFF">
<div class="card-body">
@include('installer::flash.message')
@yield('content')
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{--<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>--}}
<script src="{{ public_mix('/assets/global/js/vendor.js') }}"></script>
<script src="{{ public_mix('/assets/installer/js/vendor.js') }}"></script>
<script src="{{ public_mix('/assets/installer/js/app.js') }}"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
<script>
hljs.configure({languages: ['sh']});
$(document).ready(function () {
$(".select2").select2();
$('pre code').each(function (i, block) {
hljs.fixMarkup(block);
hljs.highlightBlock(block);
});
});
</script>
@yield('scripts')
</body>
</html>

View File

@@ -1,11 +0,0 @@
@extends('installer::app')
@section('content')
<h2>phpVMS already installed!</h2>
<p>phpVMS has already been installed! Please remove the modules/Installer folder.</p>
{{ Form::open(['url' => '/', 'method' => 'get']) }}
<p style="text-align: right">
{{ Form::submit('Go to your site >>', ['class' => 'btn btn-success']) }}
</p>
{{ Form::close() }}
@endsection

View File

@@ -1,6 +0,0 @@
@if($errors->has($field))
<p class="text-danger" style="margin-top: 10px;">{{ $errors->first($field) }}</p>
{{--<div class="alert alert-danger" role="alert" style="margin-top: 10px;">
{{ $errors->first($field) }}
</div>--}}
@endif

View File

@@ -1,19 +0,0 @@
@if (session()->has('flash_notification.message'))
@if (session()->has('flash_notification.overlay'))
@include('flash::modal', [
'modalClass' => 'flash-modal',
'title' => session('flash_notification.title'),
'body' => session('flash_notification.message')
])
@else
<div class="alert
alert-{{ session('flash_notification.level') }}
{{ session()->has('flash_notification.important') ? 'alert-important' : '' }}">
@if(session()->has('flash_notification.important'))
<button type="button" class="close" data-dismiss="alert">&times;</button>
@endif
{{ session('flash_notification.message') }}
</div>
@endif
@endif

View File

@@ -1,8 +0,0 @@
<div class="alert alert-{{ $status }}" role="alert">
<div class="container">
<div class="alert-icon">
<i class="now-ui-icons ui-1_bell-53"></i>
</div>
{{ $message }}
</div>
</div>

View File

@@ -1,11 +0,0 @@
@extends('installer::app')
@section('title', 'Install phpVMS')
@section('content')
<p>Press continue to start</p>
{{ Form::open(['route' => 'installer.step1post', 'method' => 'post']) }}
<p style="text-align: right">
{{ Form::submit('Start >>', ['class' => 'btn btn-success']) }}
</p>
{{ Form::close() }}
@endsection

View File

@@ -1,64 +0,0 @@
@extends('installer::app')
@section('title', 'Requirements Check')
@section('content')
<div style="align-content: center;">
{{ Form::open(['route' => 'installer.step2', 'method' => 'GET']) }}
<table class="table" width="25%">
<tr>
<td colspan="2"><h4>php version</h4></td>
</tr>
<tr>
<td>PHP Version: {{ $php['version'] }}</td>
<td style="text-align:right;">
@if($php['passed'] === true)
<span class="badge badge-success">OK</span>
@else
<span class="badge badge-danger">Failed</span>
@endif
</td>
</tr>
<tr>
<td colspan="2"><h4>PHP Extensions</h4></td>
</tr>
@foreach($extensions as $ext)
<tr>
<td>{{ $ext['ext'] }}</td>
<td style="text-align:right;">
@if($ext['passed'] === true)
<span class="badge badge-success">OK</span>
@else
<span class="badge badge-danger">Failed</span>
@endif
</td>
</tr>
@endforeach
<tr>
<td colspan="2">
<h4>Directory Permissions</h4>
<p>Make sure these directories have read and write permissions</p>
</td>
</tr>
@foreach($directories as $dir)
<tr>
<td>{{ $dir['dir'] }}</td>
<td style="text-align:right;">
@if($dir['passed'] === true)
<span class="badge badge-success">OK</span>
@else
<span class="badge badge-danger">Failed</span>
@endif
</td>
</tr>
@endforeach
</table>
@if($passed === true)
<p style="text-align: right">
{{ Form::submit('Database Setup >>', ['class' => 'btn btn-success']) }}
</p>
@endif
{{ Form::close() }}
</div>
@endsection

View File

@@ -1,161 +0,0 @@
@extends('installer::app')
@section('title', 'Database Setup')
@section('content')
<div style="align-content: center;">
{{ Form::open(['route' => 'installer.envsetup', 'method' => 'POST']) }}
<table class="table" width="25%">
<tr>
<td colspan="2"><h4>Site Config</h4></td>
</tr>
<tr>
<td>Site Name</td>
<td style="text-align:center;">
<div class="form-group">
{{ Form::input('text', 'site_name', 'phpvms', ['class' => 'form-control']) }}
</div>
</td>
</tr>
<tr>
<td>Site URL</td>
<td style="text-align:center;">
<div class="form-group">
{{ Form::input('text', 'site_url', Request::root(), ['class' => 'form-control']) }}
</div>
</td>
</tr>
<tr>
<td colspan="2">
<h4>Database Config</h4>
<p>Enter the target database information</p>
</td>
</tr>
<tr>
<td><p>Database Type</p></td>
<td style="text-align:center;">
<div class="form-group">
{{ Form::select('db_conn', $db_types, null, ['class' => 'form-control', 'id' => 'db_conn']) }}
</div>
</td>
</tr>
<tbody id="mysql_settings" class="settings_panel">
<tr>
<td>Database Host</td>
<td style="text-align:center;">
<div class="form-group">
{{ Form::input('text', 'db_host', '127.0.0.1', ['class' => 'form-control']) }}
</div>
</td>
</tr>
<tr>
<td>Database Port</td>
<td style="text-align:center;">
<div class="form-group">
{{ Form::input('text', 'db_port', '3306', ['class' => 'form-control']) }}
</div>
</td>
</tr>
<tr>
<td>Database Name</td>
<td style="text-align:center;">
<div class="form-group">
{{ Form::input('text', 'db_name', 'phpvms', ['class' => 'form-control']) }}
</div>
</td>
</tr>
<tr>
<td>Database User</td>
<td style="text-align:center;">
<div class="form-group">
{{ Form::input('text', 'db_user', null, ['class' => 'form-control']) }}
</div>
</td>
</tr>
<tr>
<td>Database Password</td>
<td style="text-align:center;">
<div class="form-group">
{{ Form::input('text', 'db_pass', null, ['class' => 'form-control']) }}
</div>
</td>
</tr>
<tr>
<td colspan="2" style="text-align: right;">
{{ Form::submit('Test Database Credentials', ['class' => 'btn btn-info', 'id' => 'dbtest_button']) }}
</td>
</tr>
</tbody>
<tbody id="sqlite_settings" class="settings_panel">
</tbody>
<tr>
<td>Database Prefix</td>
<td style="text-align:center;">
<div class="form-group">
{{ Form::input('text', 'db_prefix', '', ['class' => 'form-control']) }}
<p>Set this if you're sharing the database with another application.</p>
</div>
</td>
</tr>
</table>
<div id="dbtest"></div>
<p style="text-align: right">
{{ Form::submit('Setup Database >>', ['class' => 'btn btn-success']) }}
</p>
{{ Form::close() }}
</div>
@endsection
@section('scripts')
<script>
function changeForm(selected) {
$("tbody.settings_panel").hide();
$("tbody#" + selected + "_settings").show();
}
$(document).ready(() => {
const selValue = $("#db_conn option:selected").text();
changeForm(selValue);
$("#db_conn").change((e) => {
const selValue = $("#db_conn option:selected").text();
changeForm(selValue);
});
$("#dbtest_button").click((e) => {
e.preventDefault();
const opts = {
method: 'POST',
url: '/install/dbtest',
data: {
_token: "{{ csrf_token() }}",
db_conn: 'mysql',
db_host: $("input[name=db_host]").val(),
db_port: $("input[name=db_port]").val(),
db_name: $("input[name=db_name]").val(),
db_user: $("input[name=db_user]").val(),
db_pass: $("input[name=db_pass]").val(),
},
};
phpvms.request(opts).then(response => {
$("#dbtest").html(response.data);
});
})
});
</script>
@endsection

View File

@@ -1,19 +0,0 @@
@extends('installer::app')
@section('title', 'Database Setup Completed')
@section('content')
<div style="align-content: center;">
{{ Form::open(['route' => 'installer.step3', 'method' => 'GET']) }}
<pre class="lang-sh">
<code class="lang-sh">
{{--<code class="language-bash">--}}
{{ $console_output }}
</code>
</pre>
<p style="text-align: right">
{{ Form::submit('Continue >>', ['class' => 'btn btn-success']) }}
</p>
{{ Form::close() }}
</div>
@endsection

View File

@@ -1,119 +0,0 @@
@extends('installer::app')
@section('title', 'User Setup')
@section('content')
<div class="row">
<div class="col-md-12">
<div style="align-content: center;">
{{ Form::open(['route' => 'installer.usersetup', 'method' => 'POST']) }}
<table class="table" width="25%">
<tr>
<td colspan="2" style="text-align: right">
<a href="{{ route('importer.index') }}">Importing from a legacy install?</a>
</td>
</tr>
<tr>
<td colspan="2"><h4>Airline Information</h4></td>
</tr>
<tr>
<td><p>Airline ICAO</p></td>
<td>
<div class="form-group">
{{ Form::input('text', 'airline_icao', null, ['class' => 'form-control']) }}
@include('installer::flash/check_error', ['field' => 'airline_icao'])
</div>
</td>
</tr>
<tr>
<td><p>Airline Name</p></td>
<td>
<div class="form-group">
{{ Form::input('text', 'airline_name', null, ['class' => 'form-control']) }}
@include('installer::flash/check_error', ['field' => 'airline_name'])
</div>
</td>
</tr>
<tr>
<td><p>Airline Country</p></td>
<td>
<div class="form-group">
{{ Form::select('airline_country', $countries, null, ['class' => 'form-control select2' ]) }}
@include('installer::flash/check_error', ['field' => 'airline_country'])
</div>
</td>
</tr>
<tr>
<td colspan="2"><h4>First User</h4></td>
</tr>
<tr>
<td><p>Name</p></td>
<td>
<div class="form-group">
{{ Form::input('text', 'name', null, ['class' => 'form-control']) }}
@include('installer::flash/check_error', ['field' => 'name'])
</div>
</td>
</tr>
<tr>
<td><p>Email</p></td>
<td>
<div class="form-group">
{{ Form::input('text', 'email', null, ['class' => 'form-control']) }}
@include('installer::flash/check_error', ['field' => 'email'])
</div>
</td>
</tr>
<tr>
<td><p>Password</p></td>
<td>
{{ Form::password('password', ['class' => 'form-control']) }}
@include('installer::flash/check_error', ['field' => 'password'])
</td>
</tr>
<tr>
<td width="40%"><p>Password Confirm</p></td>
<td>
{{ Form::password('password_confirmation', ['class' => 'form-control']) }}
@include('installer::flash/check_error', ['field' => 'password_confirmation'])
</td>
</tr>
<tr>
<td colspan="2"><h4>Options</h4></td>
</tr>
<tr>
<td><p>Analytics</p></td>
<td>
<div class="form-group">
{{ Form::hidden('telemetry', 0) }}
{{ Form::checkbox('telemetry', 1, true, ['class' => 'form-control']) }}
<br/>
<p>
Allows collection of analytics. They won't identify you, and helps us to track
the PHP and database versions that are used, and help to figure out problems
and slowdowns when vaCentral integration is enabled.
</p>
</div>
</td>
</tr>
</table>
<div id="dbtest"></div>
<p style="text-align: right">
{{ Form::submit('Complete Setup >>', ['class' => 'btn btn-success']) }}
</p>
{{ Form::close() }}
</div>
</div>
</div>
@endsection

View File

@@ -1,20 +0,0 @@
@extends('installer::app')
@section('title', 'Installation Completed!')
@section('content')
<div style="align-content: center;">
{{ Form::open(['route' => 'installer.complete', 'method' => 'GET']) }}
<h4>Installer Completed!</h4>
<p>Edit the <span class="code">config.php</span> to fill in some additional settings. </p>
<p>Click the button to proceed to the login screen!</p>
<p style="text-align: right">
{{ Form::submit('Install Complete! Continue to Log-In >>',
['class' => 'btn btn-success'])
}}
</p>
{{ Form::close() }}
</div>
@endsection

View File

@@ -1,252 +0,0 @@
<?php
namespace Modules\Installer\Services;
use App\Contracts\Service;
use Exception;
use function extension_loaded;
use Illuminate\Encryption\Encrypter;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use function is_bool;
use Nwidart\Modules\Support\Stub;
use PDO;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
class ConfigService extends Service
{
/**
* Create the .env file
*
* @param $attrs
*
* @throws \Symfony\Component\HttpFoundation\File\Exception\FileException
*
* @return bool
*/
public function createConfigFiles($attrs): bool
{
$opts = [
'APP_ENV' => 'dev',
'APP_KEY' => $this->createAppKey(),
'SITE_NAME' => '',
'SITE_URL' => 'http://phpvms.test',
'CACHE_PREFIX' => '',
'DB_CONN' => '',
'DB_HOST' => '',
'DB_PORT' => 3306,
'DB_NAME' => '',
'DB_USER' => '',
'DB_PASS' => '',
'DB_PREFIX' => '',
'DB_EMULATE_PREPARES' => false,
];
$opts = array_merge($opts, $attrs);
$opts = $this->determinePdoOptions($opts);
$opts = $this->configCacheDriver($opts);
$opts = $this->configQueueDriver($opts);
$this->writeConfigFiles($opts);
return true;
}
/**
* Update the environment file and update certain keys/values
*
* @param array $kvp
*
* @return void
*/
public function updateKeysInEnv(array $kvp)
{
$app = app();
$env_file = file_get_contents($app->environmentFilePath());
foreach ($kvp as $key => $value) {
$key = strtoupper($key);
// cast for any boolean values
if (is_bool($value)) {
$value = $value === true ? 'true' : 'false';
}
// surround by quotes if there are any spaces in the value
if (strpos($value, ' ') !== false) {
$value = '"'.$value.'"';
}
Log::info('Replacing "'.$key.'" with '.$value);
$env_file = preg_replace(
'/^'.$key.'(.*)?/m',
$key.'='.$value,
$env_file
);
}
file_put_contents($app->environmentFilePath(), $env_file);
}
/**
* Generate a fresh new APP_KEY
*
* @return string
*/
protected function createAppKey(): string
{
return base64_encode(Encrypter::generateKey(config('app.cipher')));
}
/**
* Change a few options within the PDO driver, depending on the version
* of mysql/maria, etc used. ATM, only make a change for MariaDB
*
* @param $opts
*
* @return mixed
*/
protected function determinePdoOptions($opts)
{
if ($opts['DB_CONN'] !== 'mysql') {
return $opts;
}
$dsn = "mysql:host=$opts[DB_HOST];port=$opts[DB_PORT];";
Log::info('Connection string: '.$dsn);
$conn = new PDO($dsn, $opts['DB_USER'], $opts['DB_PASS']);
$version = strtolower($conn->getAttribute(PDO::ATTR_SERVER_VERSION));
Log::info('Detected DB Version: '.$version);
// If it's mariadb, enable the emulation for prepared statements
// seems to be throwing a problem on 000webhost
// https://github.com/nabeelio/phpvms/issues/132
if (strpos($version, 'mariadb') !== false) {
Log::info('Detected MariaDB, setting DB_EMULATE_PREPARES to true');
$opts['DB_EMULATE_PREPARES'] = true;
}
return $opts;
}
/**
* Determine is APC is installed, if so, then use it as a cache driver
*
* @param $opts
*
* @return mixed
*/
protected function configCacheDriver($opts)
{
// Set the cache prefix
$opts['CACHE_PREFIX'] = uniqid($opts['SITE_NAME'].'_');
// Figure out what cache driver to initially use, depending on
// what is installed. It won't detect redis or anything, though
foreach (config('installer.cache.drivers') as $ext => $driver) {
if (extension_loaded($ext)) {
Log::info('Detected extension "'.$ext.'", setting driver to "'.$driver.'"');
$opts['CACHE_DRIVER'] = $driver;
return $opts;
}
}
Log::info('No extension detected, using file cache');
$opts['CACHE_DRIVER'] = config('installer.cache.default');
return $opts;
}
/**
* Setup a queue driver that's not the default "sync"
* driver, if a database is being used
*
* @param $opts
*
* @return mixed
*/
protected function configQueueDriver($opts)
{
// If we're setting up a database, then also setup
// the default queue driver to use the database
if ($opts['DB_CONN'] === 'mysql' || $opts['DB_CONN'] === 'postgres') {
$opts['QUEUE_DRIVER'] = 'database';
} else {
$opts['QUEUE_DRIVER'] = 'sync';
}
return $opts;
}
/**
* Remove the config files
*/
public function removeConfigFiles()
{
$env_file = App::environmentFilePath();
$config_file = App::environmentPath().'/config.php';
if (file_exists($env_file)) {
try {
unlink($env_file);
} catch (Exception $e) {
Log::error($e->getMessage());
}
}
if (file_exists($config_file)) {
try {
unlink($config_file);
} catch (Exception $e) {
Log::error($e->getMessage());
}
}
}
/**
* Get the template file name and write it out
*
* @param $opts
*
* @throws \Symfony\Component\HttpFoundation\File\Exception\FileException
*/
protected function writeConfigFiles($opts)
{
Stub::setBasePath(resource_path('/stubs/installer'));
$env_file = App::environmentFilePath();
if (file_exists($env_file) && !is_writable($env_file)) {
Log::error('Permissions on existing env.php is not writable');
throw new FileException('Can\'t write to the env.php file! Check the permissions');
}
/*
* First write out the env file
*/
try {
$stub = new Stub('/env.stub', $opts);
$stub->render();
$stub->saveTo(App::environmentPath(), App::environmentFile());
} catch (Exception $e) {
throw new FileException('Couldn\'t write env.php. ('.$e.')');
}
/*
* Next write out the config file. If there's an error here,
* then throw an exception but delete the env file first
*/
try {
$stub = new Stub('/config.stub', $opts);
$stub->render();
$stub->saveTo(App::environmentPath(), 'config.php');
} catch (Exception $e) {
unlink(App::environmentPath().'/'.App::environmentFile());
throw new FileException('Couldn\'t write config.php. ('.$e.')');
}
}
}

View File

@@ -1,77 +0,0 @@
<?php
namespace Modules\Installer\Services;
use App\Contracts\Service;
class RequirementsService extends Service
{
/**
* Check the PHP version that it meets the minimum requirement
*
* @return array
*/
public function checkPHPVersion(): array
{
$passed = false;
if (version_compare(PHP_VERSION, config('installer.php.version')) >= 0) {
$passed = true;
}
return ['version' => PHP_VERSION, 'passed' => $passed];
}
/**
* Make sure the minimal extensions required are loaded
*
* @return array
*/
public function checkExtensions(): array
{
$extensions = [];
foreach (config('installer.extensions') as $ext) {
$pass = true;
if (!\extension_loaded($ext)) {
$pass = false;
}
$extensions[] = [
'ext' => $ext,
'passed' => $pass,
];
}
return $extensions;
}
/**
* Check the permissions for the directories specified
* Make sure they exist and are writable
*
* @return array
*/
public function checkPermissions(): array
{
clearstatcache();
$directories = [];
foreach (config('installer.permissions') as $path) {
$pass = true;
if (!file_exists($path)) {
$pass = false;
}
if (!is_writable($path)) {
$pass = false;
}
$directories[] = [
'dir' => $path,
'passed' => $pass,
];
}
return $directories;
}
}

View File

@@ -1,30 +0,0 @@
{
"name": "phpvms/installer",
"license": "",
"type": "laravel-library",
"description": "The installer module for phpVMS",
"authors": [
{
"name": "Nabeel Shahzad",
"email": "nabeel@phpvms.net"
}
],
"require": {
"composer/installers": "~1.0"
},
"extra": {
"laravel": {
"providers": [
"Modules\\Installer\\Providers\\InstallerServiceProvider"
],
"aliases": {
}
}
},
"autoload": {
"psr-4": {
"Modules\\Installer\\": ""
}
}
}

View File

@@ -1,14 +0,0 @@
{
"name": "Installer",
"alias": "installer",
"description": "",
"keywords": [],
"active": 1,
"order": 0,
"providers": [
"Modules\\Installer\\Providers\\InstallerServiceProvider"
],
"aliases": {},
"files": [],
"requires": []
}

View File

@@ -1,4 +0,0 @@
<?php
return [
];

View File

@@ -1,150 +0,0 @@
<?php
namespace Modules\Updater\Http\Controllers;
use App\Contracts\Controller;
use App\Repositories\KvpRepository;
use App\Services\AnalyticsService;
use App\Services\Installer\InstallerService;
use App\Services\Installer\MigrationService;
use App\Services\Installer\SeederService;
use Codedge\Updater\UpdaterManager;
use function count;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class UpdateController extends Controller
{
private $analyticsSvc;
private $installerSvc;
private $kvpRepo;
private $migrationSvc;
private $seederSvc;
private $updateManager;
/**
* @param AnalyticsService $analyticsSvc
* @param InstallerService $installerSvc
* @param KvpRepository $kvpRepo
* @param MigrationService $migrationSvc
* @param SeederService $seederSvc
* @param UpdaterManager $updateManager
*/
public function __construct(
AnalyticsService $analyticsSvc,
InstallerService $installerSvc,
KvpRepository $kvpRepo,
MigrationService $migrationSvc,
SeederService $seederSvc,
UpdaterManager $updateManager
) {
$this->analyticsSvc = $analyticsSvc;
$this->migrationSvc = $migrationSvc;
$this->seederSvc = $seederSvc;
$this->installerSvc = $installerSvc;
$this->kvpRepo = $kvpRepo;
$this->updateManager = $updateManager;
}
/**
* Display a listing of the resource.
*/
public function index()
{
return view('updater::index-start');
}
/**
* Step 1. Check if there's an update available. Check if there
* are any unrun migrations
*
* @param Request $request
*
* @return mixed
*/
public function step1(Request $request)
{
$this->installerSvc->clearCaches();
if ($this->installerSvc->isUpgradePending()) {
Log::info('Upgrade is pending');
}
return view('updater::steps/step1-update-available');
}
/**
* Step 2 Run all of the migrations
*
* @param Request $request
*
* @return mixed
*/
public function run_migrations(Request $request)
{
Log::info('Update: run_migrations', $request->post());
$migrations = $this->migrationSvc->migrationsAvailable();
if (count($migrations) === 0) {
$this->seederSvc->syncAllSeeds();
return view('updater::steps/step3-update-complete');
}
$output = $this->migrationSvc->runAllMigrations();
$this->seederSvc->syncAllSeeds();
return view('updater::steps/step2-migrations-done', [
'console_output' => $output,
]);
}
/**
* Final step
*
* @param Request $request
*
* @return mixed
*/
public function complete(Request $request)
{
return redirect('/login');
}
/**
* Show the update page with the latest version
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function updater(Request $request)
{
$version = $this->kvpRepo->get('latest_version_tag');
return view('updater::downloader/downloader', [
'version' => $version,
]);
}
/**
* Download the actual update and then forward the user to the updater page
*
* @param \Illuminate\Http\Request $request
*
* @return mixed
*/
public function update_download(Request $request)
{
$version = $this->kvpRepo->get('latest_version_tag');
if (empty($version)) {
return view('updater::steps/step1-no-update');
}
$release = $this->updateManager->source('github')->fetch($version);
$this->updateManager->source('github')->update($release);
$this->analyticsSvc->sendUpdate();
Log::info('Update completed to '.$version.', redirecting');
return redirect('/update');
}
}

View File

@@ -1,124 +0,0 @@
<?php
namespace Modules\Updater\Lib;
use App\Services\VersionService;
use Codedge\Updater\Contracts\GithubRepositoryTypeContract;
use Codedge\Updater\SourceRepositoryTypes\GithubRepositoryType;
use Codedge\Updater\Traits\SupportPrivateAccessToken;
use Codedge\Updater\Traits\UseVersionFile;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Psr\Http\Message\ResponseInterface;
/**
* Use SourceRepositoryTypes/GithubRepositoryTypes/GithubTagType.php as a reference
* Just replace the new update checks, etc, with the VersionService stubs. They're
* essentially the same except for the current version checks and all that. Adds some
* additional logging too, but the base update method is from GithubRepositoryType
*/
final class VmsRepositoryType extends GithubRepositoryType implements GithubRepositoryTypeContract
{
use UseVersionFile;
use SupportPrivateAccessToken;
/**
* @var Client
*/
protected $client;
/**
* @var VersionService
*/
protected $versionSvc;
public function __construct(array $config, Client $client)
{
$this->config = $config;
$this->client = $client;
$this->storagePath = Str::finish($this->config['download_path'], DIRECTORY_SEPARATOR);
$this->versionSvc = app(VersionService::class);
}
/**
* Check repository if a newer version than the installed one is available.
*
* @param string $currentVersion
*
* @throws InvalidArgumentException
* @throws Exception
*
* @return bool
*/
public function isNewVersionAvailable(string $currentVersion = ''): bool
{
return $this->versionSvc->isNewVersionAvailable($currentVersion);
}
/**
* Get the latest version that has been published in a certain repository.
* Example: 2.6.5 or v2.6.5.
*
* @param string $prepend Prepend a string to the latest version
* @param string $append Append a string to the latest version
*
* @throws Exception
*
* @return string
*/
public function getVersionAvailable(string $prepend = '', string $append = ''): string
{
return $this->versionSvc->getLatestVersion();
}
/**
* Fetches the latest version. If you do not want the latest version, specify one and pass it.
*
* @param string $version
*
* @throws Exception
*
* @return void
*/
public function fetch($version = ''): void
{
$response = $this->getRepositoryReleases();
$releaseCollection = collect(\GuzzleHttp\json_decode($response->getBody()->getContents()));
if ($releaseCollection->isEmpty()) {
throw new Exception('Cannot find a release to update. Please check the repository you\'re pulling from');
}
if (!File::exists($this->storagePath)) {
File::makeDirectory($this->storagePath, 493, true, true);
}
if (!empty($version)) {
$release = $releaseCollection->where('name', $version)->first();
} else {
$release = $releaseCollection->first();
}
Log::info('Found release='.$release->name.', path='.$release->zipball_url);
$storageFolder = $this->storagePath.$release->name.'-'.now()->timestamp;
$storageFilename = $storageFolder.'.zip';
if (!$this->isSourceAlreadyFetched($release->name)) {
$this->downloadRelease($this->client, $release->zipball_url, $storageFilename);
$this->unzipArchive($storageFilename, $storageFolder);
$this->createReleaseFolder($storageFolder, $release->name);
}
}
protected function getRepositoryReleases(): ResponseInterface
{
$url = self::GITHUB_API_URL
.'/repos/'.$this->config['repository_vendor']
.'/'.$this->config['repository_name']
.'/tags';
$headers = [];
return $this->client->request('GET', $url, ['headers' => $headers]);
}
}

View File

@@ -1,95 +0,0 @@
<?php
namespace Modules\Updater\Providers;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
class UpdateServiceProvider extends ServiceProvider
{
public function boot()
{
$this->registerRoutes();
$this->registerTranslations();
$this->registerConfig();
$this->registerViews();
}
/**
* Register the routes
*/
protected function registerRoutes()
{
Route::group([
'as' => 'update.',
'prefix' => 'update',
'middleware' => ['web', 'auth', 'ability:admin,admin-access'],
'namespace' => 'Modules\Updater\Http\Controllers',
], function () {
Route::get('/', 'UpdateController@index')->name('index');
Route::get('/step1', 'UpdateController@step1')->name('step1');
Route::post('/step1', 'UpdateController@step1')->name('step1post');
Route::post('/run-migrations', 'UpdateController@run_migrations')->name('run_migrations');
Route::get('/complete', 'UpdateController@complete')->name('complete');
// Routes for the update downloader
Route::get('/downloader', 'UpdateController@updater')->name('updater');
Route::post('/downloader', 'UpdateController@update_download')->name('update_download');
});
}
/**
* Register config.
*/
protected function registerConfig()
{
$this->mergeConfigFrom(__DIR__.'/../Config/config.php', 'updater');
}
/**
* Register views.
*/
public function registerViews()
{
$viewPath = resource_path('views/modules/updater');
$sourcePath = __DIR__.'/../Resources/views';
$this->publishes([
$sourcePath => $viewPath,
], 'views');
$paths = array_map(
function ($path) {
return $path.'/modules/updater';
},
\Config::get('view.paths')
);
$paths[] = $sourcePath;
$this->loadViewsFrom($paths, 'updater');
}
/**
* Register translations.
*/
public function registerTranslations()
{
$langPath = resource_path('lang/modules/updater');
if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, 'updater');
} else {
$this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'updater');
}
}
/**
* Get the services provided by the provider.
*/
public function provides(): array
{
return [];
}
}

View File

@@ -1,90 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title') - installer</title>
<link rel="shortcut icon" type="image/png" href="{{ public_asset('/assets/img/favicon.png') }}"/>
<meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, shrink-to-fit=no'
name='viewport'/>
<meta name="base-url" content="{!! url('') !!}">
<meta name="api-key" content="{!! Auth::check() ? Auth::user()->api_key: '' !!}">
<meta name="csrf-token" content="{!! csrf_token() !!}">
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700,200" rel="stylesheet"/>
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css" rel="stylesheet"/>
<link href="{{ public_asset('/assets/frontend/css/bootstrap.min.css') }}" rel="stylesheet"/>
<link href="{{ public_asset('/assets/frontend/css/now-ui-kit.css') }}" rel="stylesheet"/>
<link href="{{ public_asset('/assets/installer/css/vendor.css') }}" rel="stylesheet"/>
<link href="{{ public_asset('/assets/frontend/css/styles.css') }}" rel="stylesheet"/>
<link rel="stylesheet"
href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css">
<style>
.table tr:first-child td {
border-top: 0px;
}
@yield('css')
</style>
</head>
<body class="login-page" style="background: #067ec1;">
<div class="page-header clear-filter">
<div class="content">
<div class="container">
<div class="row">
<div class="col-md-8 ml-auto mr-auto content-center">
<div class="p-10" style="padding: 10px 0;">
<div class="row">
<div class="col-4">
<img src="{{ public_asset('/assets/img/logo_blue_bg.svg') }}" width="135px" style="" alt=""/>
</div>
<div class="col-8 text-right">
<h4 class="text-white mb-0 mr-0 ml-0" style="margin-top: 5px;">@yield('title')</h4>
</div>
</div>
</div>
<div class="card card-login card-plain" style="background: #FFF">
<div class="card-body">
@include('updater::flash.message')
@yield('content')
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{--<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>--}}
<script src="{{ public_mix('/assets/global/js/vendor.js') }}"></script>
<script src="{{ public_mix('/assets/installer/js/vendor.js') }}"></script>
<script src="{{ public_mix('/assets/installer/js/app.js') }}"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
<script>
hljs.configure({languages: ['sh']});
$(document).ready(function () {
$(".select2").select2();
$('pre code').each(function (i, block) {
hljs.fixMarkup(block);
hljs.highlightBlock(block);
});
});
</script>
@yield('scripts')
</body>
</html>

View File

@@ -1,12 +0,0 @@
@extends('updater::app')
@section('title', 'Update phpVMS')
@section('content')
<h2>phpvms updater</h2>
<p>Click run to complete the update to version {{ $version }}</p>
{{ Form::open(['route' => 'update.update_download', 'method' => 'post']) }}
<p style="text-align: right">
{{ Form::submit('Run >>', ['class' => 'btn btn-success']) }}
</p>
{{ Form::close() }}
@endsection

View File

@@ -1,6 +0,0 @@
@if($errors->has($field))
<p class="text-danger" style="margin-top: 10px;">{{ $errors->first($field) }}</p>
{{--<div class="alert alert-danger" role="alert" style="margin-top: 10px;">
{{ $errors->first($field) }}
</div>--}}
@endif

View File

@@ -1,11 +0,0 @@
@foreach (session('flash_notification', collect())->toArray() as $message)
<div class="alert alert-danger" role="alert">
<div class="container">
<div class="alert-icon">
<i class="now-ui-icons ui-2_like"></i>
</div>
{{ $message['message'] }}
</div>
</div>
@endforeach
{{ session()->forget('flash_notification') }}

View File

@@ -1,12 +0,0 @@
@extends('updater::app')
@section('title', 'Update phpVMS')
@section('content')
<h2>phpvms updater</h2>
<p>Press continue to check if there are any updates available.</p>
{{ Form::open(['route' => 'update.step1post', 'method' => 'post']) }}
<p style="text-align: right">
{{ Form::submit('Start >>', ['class' => 'btn btn-success']) }}
</p>
{{ Form::close() }}
@endsection

View File

@@ -1,13 +0,0 @@
@extends('updater::app')
@section('title', 'Update phpVMS')
@section('content')
<h2>phpvms updater</h2>
<p>It seems like you're up to date!</p>
{{ Form::open(['route' => 'update.complete', 'method' => 'GET']) }}
<p style="text-align: right">
{{ Form::submit('Complete >>', ['class' => 'btn btn-success']) }}
</p>
{{ Form::close() }}
@endsection

View File

@@ -1,12 +0,0 @@
@extends('updater::app')
@section('title', 'Update phpVMS')
@section('content')
<h2>phpvms updater</h2>
<p>Click run to complete the update!.</p>
{{ Form::open(['route' => 'update.run_migrations', 'method' => 'post']) }}
<p style="text-align: right">
{{ Form::submit('Run >>', ['class' => 'btn btn-success']) }}
</p>
{{ Form::close() }}
@endsection

View File

@@ -1,18 +0,0 @@
@extends('updater::app')
@section('title', 'Update Completed')
@section('content')
<div style="align-content: center;">
{{ Form::open(['route' => 'update.complete', 'method' => 'GET']) }}
<pre class="lang-sh">
<code class="lang-sh">
{{ $console_output }}
</code>
</pre>
<p style="text-align: right">
{{ Form::submit('Complete >>', ['class' => 'btn btn-success']) }}
</p>
{{ Form::close() }}
</div>
@endsection

View File

@@ -1,13 +0,0 @@
@extends('updater::app')
@section('title', 'Update Completed')
@section('content')
<h2>phpvms updater</h2>
<p>Update completed!.</p>
{{ Form::open(['route' => 'update.complete', 'method' => 'GET']) }}
<p style="text-align: right">
{{ Form::submit('Finish >>', ['class' => 'btn btn-success']) }}
</p>
{{ Form::close() }}
@endsection

View File

@@ -1,30 +0,0 @@
{
"name": "phpvms/updater",
"license": "",
"type": "laravel-library",
"description": "The installer module for phpVMS",
"authors": [
{
"name": "Nabeel Shahzad",
"email": "nabeel@phpvms.net"
}
],
"require": {
"composer/installers": "~1.0"
},
"extra": {
"laravel": {
"providers": [
"Modules\\Updater\\Providers\\UpdateServiceProvider"
],
"aliases": {
}
}
},
"autoload": {
"psr-4": {
"Modules\\Installer\\": ""
}
}
}

View File

@@ -1,14 +0,0 @@
{
"name": "Updater",
"alias": "updater",
"description": "",
"keywords": [],
"active": 1,
"order": 0,
"providers": [
"Modules\\Updater\\Providers\\UpdateServiceProvider"
],
"aliases": {},
"files": [],
"requires": []
}