Merge pull request #685 from MyIgel/schedule-import

Rebuild Schedule import
This commit is contained in:
msquare 2019-12-08 11:48:58 +01:00 committed by GitHub
commit a3a938a121
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
88 changed files with 3352 additions and 9644 deletions

View File

@ -65,7 +65,7 @@ The following instructions explain how to get, build and run the latest engelsys
```
### Configuration and Setup
* The webserver must have write access to the ```import``` and ```storage``` directories and read access for all other directories
* The webserver must have write access to the ```storage``` directory and read access for all other directories
* The webserver must point to the ```public``` directory.
* The webserver must read the ```.htaccess``` file and ```mod_rewrite``` must be enabled

View File

@ -28,6 +28,7 @@
"doctrine/dbal": "^2.9",
"erusev/parsedown": "^1.7",
"gettext/gettext": "^4.6",
"guzzlehttp/guzzle": "^6.3",
"illuminate/container": "5.8.*",
"illuminate/database": "5.8.*",
"illuminate/support": "5.8.*",

View File

@ -26,10 +26,12 @@ return [
\Engelsystem\Middleware\RequestHandlerServiceProvider::class,
\Engelsystem\Middleware\SessionHandlerServiceProvider::class,
\Engelsystem\Http\Validation\ValidationServiceProvider::class,
\Engelsystem\Http\RedirectServiceProvider::class,
// Additional services
\Engelsystem\Helpers\VersionServiceProvider::class,
\Engelsystem\Mail\MailerServiceProvider::class,
\Engelsystem\Http\HttpClientServiceProvider::class,
],
// Application middleware

View File

@ -25,3 +25,19 @@ $route->get('/stats', 'Metrics\\Controller@stats');
// API
$route->get('/api[/{resource:.+}]', 'ApiController@index');
// Administration
$route->addGroup(
'/admin',
function (RouteCollector $route) {
// Schedule
$route->addGroup(
'/schedule',
function (RouteCollector $route) {
$route->get('', 'Admin\\Schedule\\ImportSchedule@index');
$route->post('/load', 'Admin\\Schedule\\ImportSchedule@loadSchedule');
$route->post('/import', 'Admin\\Schedule\\ImportSchedule@importSchedule');
}
);
}
);

View File

@ -0,0 +1,48 @@
<?php
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
class MigrateAdminSchedulePermissions extends Migration
{
/**
* Run the migration
*/
public function up()
{
if (!$this->schema->hasTable('Privileges')) {
return;
}
$this->schema->getConnection()
->table('Privileges')
->where('name', 'admin_import')
->update(
[
'name' => 'schedule.import',
'desc' => 'Import rooms and shifts from schedule.xml',
]
);
}
/**
* Reverse the migration
*/
public function down()
{
if (!$this->schema->hasTable('Privileges')) {
return;
}
$this->schema->getConnection()
->table('Privileges')
->where('name', 'schedule.import')
->update(
[
'name' => 'admin_import',
'desc' => 'Import rooms and shifts from schedule.xcs/schedule.xcal',
]
);
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateScheduleShiftTable extends Migration
{
use Reference;
/**
* Run the migration
*/
public function up()
{
$this->schema->create(
'schedules',
function (Blueprint $table) {
$table->increments('id');
$table->string('url');
}
);
$this->schema->create(
'schedule_shift',
function (Blueprint $table) {
$table->integer('shift_id')->index()->unique();
if ($this->schema->hasTable('Shifts')) {
// Legacy table access
$table->foreign('shift_id')
->references('SID')->on('Shifts')
->onUpdate('cascade')
->onDelete('cascade');
}
$this->references($table, 'schedules');
$table->uuid('guid');
}
);
if ($this->schema->hasTable('Shifts')) {
$this->schema->table(
'Shifts',
function (Blueprint $table) {
$table->dropColumn('PSID');
}
);
}
if ($this->schema->hasTable('Room')) {
$this->schema->table(
'Room',
function (Blueprint $table) {
$table->dropColumn('from_frab');
}
);
}
}
/**
* Reverse the migration
*/
public function down()
{
if ($this->schema->hasTable('Room')) {
$this->schema->table(
'Room',
function (Blueprint $table) {
$table->boolean('from_frab')
->default(false);
}
);
}
if ($this->schema->hasTable('Shifts')) {
$this->schema->table(
'Shifts',
function (Blueprint $table) {
$table->integer('PSID')
->nullable()->default(null)
->unique();
}
);
}
$this->schema->drop('schedule_shift');
$this->schema->drop('schedules');
}
}

View File

@ -4,6 +4,7 @@ namespace Engelsystem\Migrations;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\ColumnDefinition;
use Illuminate\Support\Str;
trait Reference
{
@ -11,20 +12,25 @@ trait Reference
* @param Blueprint $table
* @param bool $setPrimary
*/
protected function referencesUser(Blueprint $table, $setPrimary = false)
protected function referencesUser(Blueprint $table, bool $setPrimary = false)
{
$this->references($table, 'users', 'user_id', $setPrimary);
$this->references($table, 'users', null, $setPrimary);
}
/**
* @param Blueprint $table
* @param string $targetTable
* @param string $fromColumn
* @param bool $setPrimary
* @param Blueprint $table
* @param string $targetTable
* @param string|null $fromColumn
* @param bool $setPrimary
* @return ColumnDefinition
*/
protected function references(Blueprint $table, $targetTable, $fromColumn, $setPrimary = false): ColumnDefinition
{
protected function references(
Blueprint $table,
string $targetTable,
?string $fromColumn = null,
bool $setPrimary = false
): ColumnDefinition {
$fromColumn = $fromColumn ?? Str::singular($targetTable) . '_id';
$col = $table->unsignedInteger($fromColumn);
if ($setPrimary) {

View File

@ -15,7 +15,6 @@ COPY .babelrc .browserslistrc composer.json LICENSE package.json README.md webpa
COPY bin/ /app/bin
COPY config/ /app/config
COPY db/ /app/db
RUN mkdir /app/import/
COPY includes/ /app/includes
COPY public/ /app/public
COPY resources/views /app/resources/views
@ -35,7 +34,7 @@ WORKDIR /var/www
RUN apk add --no-cache icu-dev && \
docker-php-ext-install intl pdo_mysql
COPY --from=data /app/ /var/www
RUN chown -R www-data:www-data /var/www/import/ /var/www/storage/ && \
RUN chown -R www-data:www-data /var/www/storage/ && \
rm -r /var/www/html
ARG VERSION

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -79,7 +79,7 @@ function angeltypes_about_controller()
function angeltype_delete_controller()
{
if (!auth()->can('admin_angel_types')) {
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
$angeltype = load_angeltype();
@ -87,7 +87,7 @@ function angeltype_delete_controller()
if (request()->hasPostData('delete')) {
AngelType_delete($angeltype);
success(sprintf(__('Angeltype %s deleted.'), AngelType_name_render($angeltype)));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
return [
@ -112,13 +112,13 @@ function angeltype_edit_controller()
$angeltype = load_angeltype();
if (!User_is_AngelType_supporter(auth()->user(), $angeltype)) {
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
} else {
// New angeltype
if ($supporter_mode) {
// Supporters aren't allowed to create new angeltypes.
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
$angeltype = AngelType_new();
}
@ -157,7 +157,7 @@ function angeltype_edit_controller()
}
success('Angel type saved.');
redirect(angeltype_link($angeltype['id']));
throw_redirect(angeltype_link($angeltype['id']));
}
}
@ -177,7 +177,7 @@ function angeltype_controller()
$user = auth()->user();
if (!auth()->can('angeltypes')) {
redirect(page_link_to('/'));
throw_redirect(page_link_to('/'));
}
$angeltype = load_angeltype();
@ -275,7 +275,7 @@ function angeltypes_list_controller()
$user = auth()->user();
if (!auth()->can('angeltypes')) {
redirect(page_link_to('/'));
throw_redirect(page_link_to('/'));
}
$angeltypes = AngelTypes_with_user($user->id);
@ -346,13 +346,13 @@ function load_angeltype()
{
$request = request();
if (!$request->has('angeltype_id')) {
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
$angeltype = AngelType($request->input('angeltype_id'));
if (empty($angeltype)) {
error(__('Angeltype doesn\'t exist . '));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
return $angeltype;

View File

@ -17,7 +17,7 @@ function event_config_title()
function event_config_edit_controller()
{
if (!auth()->can('admin_event_config')) {
redirect(page_link_to('/'));
throw_redirect(page_link_to('/'));
}
$request = request();
@ -118,7 +118,7 @@ function event_config_edit_controller()
)
);
success(__('Settings saved.'));
redirect(page_link_to('admin_event_config'));
throw_redirect(page_link_to('admin_event_config'));
}
}

View File

@ -15,7 +15,7 @@ use Engelsystem\ShiftsFilterRenderer;
function room_controller()
{
if (!auth()->can('view_rooms')) {
redirect(page_link_to());
throw_redirect(page_link_to());
}
$request = request();
@ -74,7 +74,7 @@ function rooms_controller()
return room_controller();
case 'list':
default:
redirect(page_link_to('admin_rooms'));
throw_redirect(page_link_to('admin_rooms'));
}
}
@ -104,12 +104,12 @@ function room_edit_link($room)
function load_room()
{
if (!test_request_int('room_id')) {
redirect(page_link_to());
throw_redirect(page_link_to());
}
$room = Room(request()->input('room_id'));
if (empty($room)) {
redirect(page_link_to());
throw_redirect(page_link_to());
}
return $room;

View File

@ -13,12 +13,12 @@ function shift_entries_controller()
{
$user = auth()->user();
if (!$user) {
redirect(page_link_to('login'));
throw_redirect(page_link_to('login'));
}
$action = strip_request_item('action');
if (empty($action)) {
redirect(user_link($user->id));
throw_redirect(user_link($user->id));
}
switch ($action) {
@ -40,12 +40,12 @@ function shift_entry_create_controller()
$request = request();
if (User_is_freeloader($user)) {
redirect(page_link_to('user_myshifts'));
throw_redirect(page_link_to('user_myshifts'));
}
$shift = Shift($request->input('shift_id'));
if (empty($shift)) {
redirect(user_link($user->id));
throw_redirect(user_link($user->id));
}
$angeltype = AngelType($request->input('angeltype_id'));
@ -55,7 +55,7 @@ function shift_entry_create_controller()
}
if (empty($angeltype)) {
redirect(user_link($user->id));
throw_redirect(user_link($user->id));
}
if (User_is_AngelType_supporter($user, $angeltype)) {
@ -82,7 +82,7 @@ function shift_entry_create_controller_admin($shift, $angeltype)
$signup_user = User::find($request->input('user_id'));
}
if (!$signup_user) {
redirect(shift_link($shift));
throw_redirect(shift_link($shift));
}
$angeltypes = AngelTypes();
@ -91,7 +91,7 @@ function shift_entry_create_controller_admin($shift, $angeltype)
}
if (empty($angeltype)) {
if (count($angeltypes) == 0) {
redirect(shift_link($shift));
throw_redirect(shift_link($shift));
}
$angeltype = $angeltypes[0];
}
@ -107,7 +107,7 @@ function shift_entry_create_controller_admin($shift, $angeltype)
]);
success(sprintf(__('%s has been subscribed to the shift.'), User_Nick_render($signup_user)));
redirect(shift_link($shift));
throw_redirect(shift_link($shift));
}
/** @var User[]|Collection $users */
@ -147,7 +147,7 @@ function shift_entry_create_controller_supporter($shift, $angeltype)
}
if (!UserAngelType_exists($signup_user->id, $angeltype)) {
error(__('User is not in angeltype.'));
redirect(shift_link($shift));
throw_redirect(shift_link($shift));
}
$needed_angeltype = NeededAngeltype_by_Shift_and_Angeltype($shift, $angeltype);
@ -165,7 +165,7 @@ function shift_entry_create_controller_supporter($shift, $angeltype)
if ($shift_signup_state->getState() == ShiftSignupState::OCCUPIED) {
error(__('This shift is already occupied.'));
}
redirect(shift_link($shift));
throw_redirect(shift_link($shift));
}
if ($request->hasPostData('submit')) {
@ -179,7 +179,7 @@ function shift_entry_create_controller_supporter($shift, $angeltype)
]);
success(sprintf(__('%s has been subscribed to the shift.'), User_Nick_render($signup_user)));
redirect(shift_link($shift));
throw_redirect(shift_link($shift));
}
$users = Users_by_angeltype($angeltype);
@ -245,7 +245,7 @@ function shift_entry_create_controller_user($shift, $angeltype)
);
if (!$shift_signup_state->isSignupAllowed()) {
shift_entry_error_message($shift_signup_state);
redirect(shift_link($shift));
throw_redirect(shift_link($shift));
}
$comment = '';
@ -265,7 +265,7 @@ function shift_entry_create_controller_user($shift, $angeltype)
}
success(__('You are subscribed. Thank you!'));
redirect(shift_link($shift));
throw_redirect(shift_link($shift));
}
$room = Room($shift['RID']);
@ -319,12 +319,12 @@ function shift_entry_load()
$request = request();
if (!$request->has('shift_entry_id') || !test_request_int('shift_entry_id')) {
redirect(page_link_to('user_shifts'));
throw_redirect(page_link_to('user_shifts'));
}
$shiftEntry = ShiftEntry($request->input('shift_entry_id'));
if (empty($shiftEntry)) {
error(__('Shift entry not found.'));
redirect(page_link_to('user_shifts'));
throw_redirect(page_link_to('user_shifts'));
}
return $shiftEntry;
@ -346,13 +346,13 @@ function shift_entry_delete_controller()
$signout_user = User::find($shiftEntry['UID']);
if (!Shift_signout_allowed($shift, $angeltype, $signout_user->id)) {
error(__('You are not allowed to remove this shift entry. If necessary, ask your supporter or heaven to do so.'));
redirect(user_link($signout_user->id));
throw_redirect(user_link($signout_user->id));
}
if ($request->hasPostData('delete')) {
ShiftEntry_delete($shiftEntry);
success(__('Shift entry removed.'));
redirect(shift_link($shift));
throw_redirect(shift_link($shift));
}
if ($user->id == $signout_user->id) {

View File

@ -49,11 +49,11 @@ function shift_edit_controller()
$request = request();
if (!auth()->can('admin_shifts')) {
redirect(page_link_to('user_shifts'));
throw_redirect(page_link_to('user_shifts'));
}
if (!$request->has('edit_shift') || !test_request_int('edit_shift')) {
redirect(page_link_to('user_shifts'));
throw_redirect(page_link_to('user_shifts'));
}
$shift_id = $request->input('edit_shift');
@ -164,7 +164,7 @@ function shift_edit_controller()
);
success(__('Shift updated.'));
redirect(shift_link([
throw_redirect(shift_link([
'SID' => $shift_id
]));
}
@ -205,18 +205,18 @@ function shift_delete_controller()
$request = request();
if (!auth()->can('user_shifts_admin')) {
redirect(page_link_to('user_shifts'));
throw_redirect(page_link_to('user_shifts'));
}
// Schicht komplett löschen (nur für admins/user mit user_shifts_admin privileg)
if (!$request->has('delete_shift') || !preg_match('/^\d+$/', $request->input('delete_shift'))) {
redirect(page_link_to('user_shifts'));
throw_redirect(page_link_to('user_shifts'));
}
$shift_id = $request->input('delete_shift');
$shift = Shift($shift_id);
if (empty($shift)) {
redirect(page_link_to('user_shifts'));
throw_redirect(page_link_to('user_shifts'));
}
// Schicht löschen bestätigt
@ -230,7 +230,7 @@ function shift_delete_controller()
. ' to ' . date('Y-m-d H:i', $shift['end'])
);
success(__('Shift deleted.'));
redirect(page_link_to('user_shifts'));
throw_redirect(page_link_to('user_shifts'));
}
return page_with_title(shifts_title(), [
@ -256,17 +256,17 @@ function shift_controller()
$request = request();
if (!auth()->can('user_shifts')) {
redirect(page_link_to('/'));
throw_redirect(page_link_to('/'));
}
if (!$request->has('shift_id')) {
redirect(page_link_to('user_shifts'));
throw_redirect(page_link_to('user_shifts'));
}
$shift = Shift($request->input('shift_id'));
if (empty($shift)) {
error(__('Shift could not be found.'));
redirect(page_link_to('user_shifts'));
throw_redirect(page_link_to('user_shifts'));
}
$shifttype = ShiftType($shift['shifttype_id']);
@ -309,7 +309,7 @@ function shifts_controller()
{
$request = request();
if (!$request->has('action')) {
redirect(page_link_to('user_shifts'));
throw_redirect(page_link_to('user_shifts'));
}
switch ($request->input('action')) {
@ -319,7 +319,7 @@ function shifts_controller()
case 'next':
shift_next_controller();
default:
redirect(page_link_to('/'));
throw_redirect(page_link_to('/'));
}
return false;
@ -331,16 +331,16 @@ function shifts_controller()
function shift_next_controller()
{
if (!auth()->can('user_shifts')) {
redirect(page_link_to('/'));
throw_redirect(page_link_to('/'));
}
$upcoming_shifts = ShiftEntries_upcoming_for_user(auth()->user()->id);
if (!empty($upcoming_shifts)) {
redirect(shift_link($upcoming_shifts[0]));
throw_redirect(shift_link($upcoming_shifts[0]));
}
redirect(page_link_to('user_shifts'));
throw_redirect(page_link_to('user_shifts'));
}
/**

View File

@ -18,12 +18,12 @@ function shifttype_delete_controller()
{
$request = request();
if (!$request->has('shifttype_id')) {
redirect(page_link_to('shifttypes'));
throw_redirect(page_link_to('shifttypes'));
}
$shifttype = ShiftType($request->input('shifttype_id'));
if (empty($shifttype)) {
redirect(page_link_to('shifttypes'));
throw_redirect(page_link_to('shifttypes'));
}
if ($request->hasPostData('delete')) {
@ -31,7 +31,7 @@ function shifttype_delete_controller()
engelsystem_log('Deleted shifttype ' . $shifttype['name']);
success(sprintf(__('Shifttype %s deleted.'), $shifttype['name']));
redirect(page_link_to('shifttypes'));
throw_redirect(page_link_to('shifttypes'));
}
return [
@ -59,7 +59,7 @@ function shifttype_edit_controller()
$shifttype = ShiftType($request->input('shifttype_id'));
if (empty($shifttype)) {
error(__('Shifttype not found.'));
redirect(page_link_to('shifttypes'));
throw_redirect(page_link_to('shifttypes'));
}
$shifttype_id = $shifttype['id'];
$name = $shifttype['name'];
@ -99,7 +99,7 @@ function shifttype_edit_controller()
engelsystem_log('Created shifttype ' . $name);
success(__('Created shifttype.'));
}
redirect(page_link_to('shifttypes', ['action' => 'view', 'shifttype_id' => $shifttype_id]));
throw_redirect(page_link_to('shifttypes', ['action' => 'view', 'shifttype_id' => $shifttype_id]));
}
}
@ -116,11 +116,11 @@ function shifttype_controller()
{
$request = request();
if (!$request->has('shifttype_id')) {
redirect(page_link_to('shifttypes'));
throw_redirect(page_link_to('shifttypes'));
}
$shifttype = ShiftType($request->input('shifttype_id'));
if (empty($shifttype)) {
redirect(page_link_to('shifttypes'));
throw_redirect(page_link_to('shifttypes'));
}
$angeltype = [];

View File

@ -45,18 +45,18 @@ function user_angeltypes_delete_all_controller()
if (!$request->has('angeltype_id')) {
error(__('Angeltype doesn\'t exist.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
$angeltype = AngelType($request->input('angeltype_id'));
if (empty($angeltype)) {
error(__('Angeltype doesn\'t exist.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
if (!User_is_AngelType_supporter(auth()->user(), $angeltype)) {
error(__('You are not allowed to delete all users for this angeltype.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
if ($request->hasPostData('deny_all')) {
@ -64,7 +64,7 @@ function user_angeltypes_delete_all_controller()
engelsystem_log(sprintf('Denied all users for angeltype %s', AngelType_name_render($angeltype, true)));
success(sprintf(__('Denied all users for angeltype %s.'), AngelType_name_render($angeltype)));
redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype['id']]));
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype['id']]));
}
return [
@ -85,18 +85,18 @@ function user_angeltypes_confirm_all_controller()
if (!$request->has('angeltype_id')) {
error(__('Angeltype doesn\'t exist.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
$angeltype = AngelType($request->input('angeltype_id'));
if (empty($angeltype)) {
error(__('Angeltype doesn\'t exist.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
if (!auth()->can('admin_user_angeltypes') && !User_is_AngelType_supporter($user, $angeltype)) {
error(__('You are not allowed to confirm all users for this angeltype.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
if ($request->hasPostData('confirm_all')) {
@ -104,7 +104,7 @@ function user_angeltypes_confirm_all_controller()
engelsystem_log(sprintf('Confirmed all users for angeltype %s', AngelType_name_render($angeltype, true)));
success(sprintf(__('Confirmed all users for angeltype %s.'), AngelType_name_render($angeltype)));
redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype['id']]));
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype['id']]));
}
return [
@ -125,30 +125,30 @@ function user_angeltype_confirm_controller()
if (!$request->has('user_angeltype_id')) {
error(__('User angeltype doesn\'t exist.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
$user_angeltype = UserAngelType($request->input('user_angeltype_id'));
if (empty($user_angeltype)) {
error(__('User angeltype doesn\'t exist.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
$angeltype = AngelType($user_angeltype['angeltype_id']);
if (empty($angeltype)) {
error(__('Angeltype doesn\'t exist.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
if (!User_is_AngelType_supporter($user, $angeltype)) {
error(__('You are not allowed to confirm this users angeltype.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
$user_source = User::find($user_angeltype['user_id']);
if (!$user_source) {
error(__('User doesn\'t exist.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
if ($request->hasPostData('confirm_user')) {
@ -164,7 +164,7 @@ function user_angeltype_confirm_controller()
User_Nick_render($user_source),
AngelType_name_render($angeltype)
));
redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype['id']]));
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype['id']]));
}
return [
@ -185,30 +185,30 @@ function user_angeltype_delete_controller()
if (!$request->has('user_angeltype_id')) {
error(__('User angeltype doesn\'t exist.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
$user_angeltype = UserAngelType($request->input('user_angeltype_id'));
if (empty($user_angeltype)) {
error(__('User angeltype doesn\'t exist.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
$angeltype = AngelType($user_angeltype['angeltype_id']);
if (empty($angeltype)) {
error(__('Angeltype doesn\'t exist.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
$user_source = User::find($user_angeltype['user_id']);
if (!$user_source) {
error(__('User doesn\'t exist.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
if ($user->id != $user_angeltype['user_id'] && !User_is_AngelType_supporter($user, $angeltype)) {
error(__('You are not allowed to delete this users angeltype.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
if ($request->hasPostData('delete')) {
@ -217,7 +217,7 @@ function user_angeltype_delete_controller()
engelsystem_log(sprintf('User %s removed from %s.', User_Nick_render($user_source, true), $angeltype['name']));
success(sprintf(__('User %s removed from %s.'), User_Nick_render($user_source), $angeltype['name']));
redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype['id']]));
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype['id']]));
}
return [
@ -238,37 +238,37 @@ function user_angeltype_update_controller()
if (!auth()->can('admin_angel_types')) {
error(__('You are not allowed to set supporter rights.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
if (!$request->has('user_angeltype_id')) {
error(__('User angeltype doesn\'t exist.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
if ($request->has('supporter') && preg_match('/^[01]$/', $request->input('supporter'))) {
$supporter = $request->input('supporter') == '1';
} else {
error(__('No supporter update given.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
$user_angeltype = UserAngelType($request->input('user_angeltype_id'));
if (empty($user_angeltype)) {
error(__('User angeltype doesn\'t exist.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
$angeltype = AngelType($user_angeltype['angeltype_id']);
if (empty($angeltype)) {
error(__('Angeltype doesn\'t exist.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
$user_source = User::find($user_angeltype['user_id']);
if (!$user_source) {
error(__('User doesn\'t exist.'));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
if ($request->hasPostData('submit')) {
@ -288,7 +288,7 @@ function user_angeltype_update_controller()
User_Nick_render($user_source)
));
redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype['id']]));
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype['id']]));
}
return [
@ -343,7 +343,7 @@ function user_angeltype_add_controller()
AngelType_name_render($angeltype, true)
));
redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype['id']]));
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype['id']]));
}
}
@ -366,7 +366,7 @@ function user_angeltype_join_controller($angeltype)
$user_angeltype = UserAngelType_by_User_and_AngelType($user->id, $angeltype);
if (!empty($user_angeltype)) {
error(sprintf(__('You are already a %s.'), $angeltype['name']));
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
if (request()->hasPostData('submit')) {
@ -389,7 +389,7 @@ function user_angeltype_join_controller($angeltype)
));
}
redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype['id']]));
throw_redirect(page_link_to('angeltypes', ['action' => 'view', 'angeltype_id' => $angeltype['id']]));
}
return [
@ -407,7 +407,7 @@ function user_angeltypes_controller()
{
$request = request();
if (!$request->has('action')) {
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
switch ($request->input('action')) {
@ -424,6 +424,6 @@ function user_angeltypes_controller()
case 'add':
return user_angeltype_add_controller();
default:
redirect(page_link_to('angeltypes'));
throw_redirect(page_link_to('angeltypes'));
}
}

View File

@ -42,7 +42,7 @@ function user_driver_licenses_controller()
$user = auth()->user();
if (!$user) {
redirect(page_link_to(''));
throw_redirect(page_link_to(''));
}
$action = strip_request_item('action', 'edit');
@ -82,7 +82,7 @@ function user_driver_license_load_user()
if ($request->has('user_id')) {
$user_source = User::find($request->input('user_id'));
if (empty($user_source)) {
redirect(user_driver_license_edit_link());
throw_redirect(user_driver_license_edit_link());
}
}
@ -102,7 +102,7 @@ function user_driver_license_edit_controller()
// only privilege admin_user can edit other users driver license information
if ($user->id != $user_source->id && !auth()->can('admin_user')) {
redirect(user_driver_license_edit_link());
throw_redirect(user_driver_license_edit_link());
}
$user_driver_license = UserDriverLicense($user_source->id);
@ -131,7 +131,7 @@ function user_driver_license_edit_controller()
}
engelsystem_log('Driver license information updated.');
success(__('Your driver license information has been saved.'));
redirect(user_link($user_source->id));
throw_redirect(user_link($user_source->id));
} else {
error(__('Please select at least one driving license.'));
}
@ -139,7 +139,7 @@ function user_driver_license_edit_controller()
UserDriverLicenses_delete($user_source->id);
engelsystem_log('Driver license information removed.');
success(__('Your driver license information has been removed.'));
redirect(user_link($user_source->id));
throw_redirect(user_link($user_source->id));
}
}

View File

@ -12,7 +12,7 @@ function user_worklog_delete_controller()
$request = request();
$userWorkLog = UserWorkLog($request->input('user_worklog_id'));
if (empty($userWorkLog)) {
redirect(user_link(auth()->user()->id));
throw_redirect(user_link(auth()->user()->id));
}
$user_source = User::find($userWorkLog['user_id']);
@ -20,7 +20,7 @@ function user_worklog_delete_controller()
UserWorkLog_delete($userWorkLog);
success(__('Work log entry deleted.'));
redirect(user_link($user_source->id));
throw_redirect(user_link($user_source->id));
}
return [
@ -39,7 +39,7 @@ function user_worklog_edit_controller()
$request = request();
$userWorkLog = UserWorkLog($request->input('user_worklog_id'));
if (empty($userWorkLog)) {
redirect(user_link(auth()->user()->id));
throw_redirect(user_link(auth()->user()->id));
}
$user_source = User::find($userWorkLog['user_id']);
@ -50,7 +50,7 @@ function user_worklog_edit_controller()
UserWorkLog_update($userWorkLog);
success(__('Work log entry updated.'));
redirect(user_link($user_source->id));
throw_redirect(user_link($user_source->id));
}
}
@ -109,7 +109,7 @@ function user_worklog_add_controller()
$request = request();
$user_source = User::find($request->input('user_id'));
if (!$user_source) {
redirect(user_link(auth()->user()->id));
throw_redirect(user_link(auth()->user()->id));
}
$userWorkLog = UserWorkLog_new($user_source->id);
@ -121,7 +121,7 @@ function user_worklog_add_controller()
UserWorkLog_create($userWorkLog);
success(__('Work log entry created.'));
redirect(user_link($user_source->id));
throw_redirect(user_link($user_source->id));
}
}
@ -185,13 +185,13 @@ function user_worklog_controller()
$user = auth()->user();
if (!auth()->can('admin_user_worklog')) {
redirect(user_link($user->id));
throw_redirect(user_link($user->id));
}
$request = request();
$action = $request->input('action');
if (!$request->has('action')) {
redirect(user_link($user->id));
throw_redirect(user_link($user->id));
}
switch ($action) {

View File

@ -17,7 +17,7 @@ function users_controller()
$request = request();
if (!$user) {
redirect(page_link_to(''));
throw_redirect(page_link_to(''));
}
$action = 'list';
@ -56,13 +56,13 @@ function user_delete_controller()
}
if (!auth()->can('admin_user')) {
redirect(page_link_to(''));
throw_redirect(page_link_to(''));
}
// You cannot delete yourself
if ($user->id == $user_source->id) {
error(__('You cannot delete yourself.'));
redirect(user_link($user->id));
throw_redirect(user_link($user->id));
}
if ($request->hasPostData('submit')) {
@ -85,7 +85,7 @@ function user_delete_controller()
success(__('User deleted.'));
engelsystem_log(sprintf('Deleted %s', User_Nick_render($user_source, true)));
redirect(users_link());
throw_redirect(users_link());
}
}
@ -145,7 +145,7 @@ function user_edit_vouchers_controller()
}
if (!auth()->can('admin_user')) {
redirect(page_link_to(''));
throw_redirect(page_link_to(''));
}
if ($request->hasPostData('submit')) {
@ -171,7 +171,7 @@ function user_edit_vouchers_controller()
engelsystem_log(User_Nick_render($user_source, true) . ': ' . sprintf('Got %s vouchers',
$user_source->state->got_voucher));
redirect(user_link($user_source->id));
throw_redirect(user_link($user_source->id));
}
}
@ -194,7 +194,7 @@ function user_controller()
$user_source = User::find($request->input('user_id'));
if (!$user_source) {
error(__('User not found.'));
redirect(page_link_to('/'));
throw_redirect(page_link_to('/'));
}
}
@ -261,7 +261,7 @@ function users_list_controller()
$request = request();
if (!auth()->can('admin_user')) {
redirect(page_link_to(''));
throw_redirect(page_link_to(''));
}
$order_by = 'name';
@ -319,13 +319,13 @@ function load_user()
{
$request = request();
if (!$request->has('user_id')) {
redirect(page_link_to());
throw_redirect(page_link_to());
}
$user = User::find($request->input('user_id'));
if (!$user) {
error(__('User doesn\'t exist.'));
redirect(page_link_to());
throw_redirect(page_link_to());
}
return $user;

View File

@ -69,7 +69,6 @@ $includeFiles = [
__DIR__ . '/../includes/pages/admin_arrive.php',
__DIR__ . '/../includes/pages/admin_free.php',
__DIR__ . '/../includes/pages/admin_groups.php',
__DIR__ . '/../includes/pages/admin_import.php',
__DIR__ . '/../includes/pages/admin_log.php',
__DIR__ . '/../includes/pages/admin_questions.php',
__DIR__ . '/../includes/pages/admin_rooms.php',
@ -82,6 +81,8 @@ $includeFiles = [
__DIR__ . '/../includes/pages/user_questions.php',
__DIR__ . '/../includes/pages/user_settings.php',
__DIR__ . '/../includes/pages/user_shifts.php',
__DIR__ . '/../includes/pages/schedule/ImportSchedule.php',
];
foreach ($includeFiles as $file) {

View File

@ -61,36 +61,21 @@ function Room_delete($room_id)
engelsystem_log('Room deleted: ' . $room['Name']);
}
/**
* Delete a room by its name
*
* @param string $name
*/
function Room_delete_by_name($name)
{
DB::delete('DELETE FROM `Room` WHERE `Name` = ?', [
$name
]);
engelsystem_log('Room deleted: ' . $name);
}
/**
* Create a new room
*
* @param string $name Name of the room
* @param boolean $from_frab Is this a frab imported room?
* @param string $map_url URL to a map tha can be displayed in an iframe
* @param string description Markdown description
* @return false|int
*/
function Room_create($name, $from_frab, $map_url, $description)
function Room_create($name, $map_url, $description)
{
DB::insert('
INSERT INTO `Room` (`Name`, `from_frab`, `map_url`, `description`)
VALUES (?, ?, ?, ?)
INSERT INTO `Room` (`Name`, `map_url`, `description`)
VALUES (?, ?, ?)
', [
$name,
(int)$from_frab,
$map_url,
$description
]);
@ -98,7 +83,6 @@ function Room_create($name, $from_frab, $map_url, $description)
engelsystem_log(
'Room created: ' . $name
. ', frab import: ' . ($from_frab ? 'Yes' : '')
. ', map_url: ' . $map_url
. ', description: ' . $description
);
@ -107,28 +91,25 @@ function Room_create($name, $from_frab, $map_url, $description)
}
/**
* update a room
* Update a room
*
* @param int $room_id The rooms id
* @param string $name Name of the room
* @param boolean $from_frab Is this a frab imported room?
* @param string $map_url URL to a map tha can be displayed in an iframe
* @param string $description Markdown description
* @return int
*/
function Room_update($room_id, $name, $from_frab, $map_url, $description)
function Room_update($room_id, $name, $map_url, $description)
{
$result = DB::update('
UPDATE `Room`
SET
`Name`=?,
`from_frab`=?,
`map_url`=?,
`description`=?
WHERE `RID`=?
LIMIT 1', [
$name,
(int)$from_frab,
$map_url,
$description,
$room_id
@ -136,7 +117,6 @@ function Room_update($room_id, $name, $from_frab, $map_url, $description)
engelsystem_log(
'Room updated: ' . $name .
', frab import: ' . ($from_frab ? 'Yes' : '') .
', map_url: ' . $map_url .
', description: ' . $description
);

View File

@ -14,17 +14,19 @@ function Shifts_by_angeltype($angeltype)
return DB::select('
SELECT DISTINCT `Shifts`.* FROM `Shifts`
JOIN `NeededAngelTypes` ON `NeededAngelTypes`.`shift_id` = `Shifts`.`SID`
LEFT JOIN schedule_shift AS s on Shifts.SID = s.shift_id
WHERE `NeededAngelTypes`.`angel_type_id` = ?
AND `NeededAngelTypes`.`count` > 0
AND `Shifts`.`PSID` IS NULL
AND s.shift_id IS NULL
UNION
SELECT DISTINCT `Shifts`.* FROM `Shifts`
JOIN `NeededAngelTypes` ON `NeededAngelTypes`.`room_id` = `Shifts`.`RID`
LEFT JOIN schedule_shift AS s on Shifts.SID = s.shift_id
WHERE `NeededAngelTypes`.`angel_type_id` = ?
AND `NeededAngelTypes`.`count` > 0
AND NOT `Shifts`.`PSID` IS NULL
AND NOT s.shift_id IS NULL
', [$angeltype['id'], $angeltype['id']]);
}
@ -41,19 +43,21 @@ function Shifts_free($start, $end)
SELECT * FROM (
SELECT *
FROM `Shifts`
LEFT JOIN schedule_shift AS s on Shifts.SID = s.shift_id
WHERE (`end` > ? AND `start` < ?)
AND (SELECT SUM(`count`) FROM `NeededAngelTypes` WHERE `NeededAngelTypes`.`shift_id`=`Shifts`.`SID`)
> (SELECT COUNT(*) FROM `ShiftEntry` WHERE `ShiftEntry`.`SID`=`Shifts`.`SID` AND `freeloaded`=0)
AND `Shifts`.`PSID` IS NULL
AND s.shift_id IS NULL
UNION
SELECT *
FROM `Shifts`
LEFT JOIN schedule_shift AS s on Shifts.SID = s.shift_id
WHERE (`end` > ? AND `start` < ?)
AND (SELECT SUM(`count`) FROM `NeededAngelTypes` WHERE `NeededAngelTypes`.`room_id`=`Shifts`.`RID`)
> (SELECT COUNT(*) FROM `ShiftEntry` WHERE `ShiftEntry`.`SID`=`Shifts`.`SID` AND `freeloaded`=0)
AND NOT `Shifts`.`PSID` IS NULL
AND NOT s.shift_id IS NULL
) AS `tmp`
ORDER BY `tmp`.`start`
", [
@ -69,16 +73,6 @@ function Shifts_free($start, $end)
return $free_shifts;
}
/**
* Returns all shifts with a PSID (from frab import)
*
* @return array[]
*/
function Shifts_from_frab()
{
return DB::select('SELECT * FROM `Shifts` WHERE `PSID` IS NOT NULL ORDER BY `start`');
}
/**
* @param array|int $room
* @return array[]
@ -103,11 +97,12 @@ function Shifts_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
JOIN `Room` USING (`RID`)
JOIN `ShiftTypes` ON `ShiftTypes`.`id` = `Shifts`.`shifttype_id`
JOIN `NeededAngelTypes` ON `NeededAngelTypes`.`shift_id` = `Shifts`.`SID`
LEFT JOIN schedule_shift AS s on Shifts.SID = s.shift_id
WHERE `Shifts`.`RID` IN (' . implode(',', $shiftsFilter->getRooms()) . ')
AND `start` BETWEEN ? AND ?
AND `NeededAngelTypes`.`angel_type_id` IN (' . implode(',', $shiftsFilter->getTypes()) . ')
AND `NeededAngelTypes`.`count` > 0
AND `Shifts`.`PSID` IS NULL
AND s.shift_id IS NULL
UNION
@ -116,11 +111,12 @@ function Shifts_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
JOIN `Room` USING (`RID`)
JOIN `ShiftTypes` ON `ShiftTypes`.`id` = `Shifts`.`shifttype_id`
JOIN `NeededAngelTypes` ON `NeededAngelTypes`.`room_id`=`Shifts`.`RID`
LEFT JOIN schedule_shift AS s on Shifts.SID = s.shift_id
WHERE `Shifts`.`RID` IN (' . implode(',', $shiftsFilter->getRooms()) . ')
AND `start` BETWEEN ? AND ?
AND `NeededAngelTypes`.`angel_type_id` IN (' . implode(',', $shiftsFilter->getTypes()) . ')
AND `NeededAngelTypes`.`count` > 0
AND NOT `Shifts`.`PSID` IS NULL) AS tmp_shifts
AND NOT s.shift_id IS NULL) AS tmp_shifts
ORDER BY `room_name`, `start`';
@ -152,9 +148,10 @@ function NeededAngeltypes_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
FROM `Shifts`
JOIN `NeededAngelTypes` ON `NeededAngelTypes`.`shift_id`=`Shifts`.`SID`
JOIN `AngelTypes` ON `AngelTypes`.`id`= `NeededAngelTypes`.`angel_type_id`
LEFT JOIN schedule_shift AS s on Shifts.SID = s.shift_id
WHERE `Shifts`.`RID` IN (' . implode(',', $shiftsFilter->getRooms()) . ')
AND `start` BETWEEN ? AND ?
AND `Shifts`.`PSID` IS NULL
AND s.shift_id IS NULL
UNION
@ -168,9 +165,10 @@ function NeededAngeltypes_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
FROM `Shifts`
JOIN `NeededAngelTypes` ON `NeededAngelTypes`.`room_id`=`Shifts`.`RID`
JOIN `AngelTypes` ON `AngelTypes`.`id`= `NeededAngelTypes`.`angel_type_id`
LEFT JOIN schedule_shift AS s on Shifts.SID = s.shift_id
WHERE `Shifts`.`RID` IN (' . implode(',', $shiftsFilter->getRooms()) . ')
AND `start` BETWEEN ? AND ?
AND NOT `Shifts`.`PSID` IS NULL';
AND NOT s.shift_id IS NULL';
return DB::select(
$sql,
@ -201,9 +199,10 @@ function NeededAngeltype_by_Shift_and_Angeltype($shift, $angeltype)
FROM `Shifts`
JOIN `NeededAngelTypes` ON `NeededAngelTypes`.`shift_id`=`Shifts`.`SID`
JOIN `AngelTypes` ON `AngelTypes`.`id`= `NeededAngelTypes`.`angel_type_id`
LEFT JOIN schedule_shift AS s on Shifts.SID = s.shift_id
WHERE `Shifts`.`SID`=?
AND `AngelTypes`.`id`=?
AND `Shifts`.`PSID` IS NULL
AND s.shift_id IS NULL
UNION
@ -217,9 +216,10 @@ function NeededAngeltype_by_Shift_and_Angeltype($shift, $angeltype)
FROM `Shifts`
JOIN `NeededAngelTypes` ON `NeededAngelTypes`.`room_id`=`Shifts`.`RID`
JOIN `AngelTypes` ON `AngelTypes`.`id`= `NeededAngelTypes`.`angel_type_id`
LEFT JOIN schedule_shift AS s on Shifts.SID = s.shift_id
WHERE `Shifts`.`SID`=?
AND `AngelTypes`.`id`=?
AND NOT `Shifts`.`PSID` IS NULL
AND NOT s.shift_id IS NULL
',
[
$shift['SID'],
@ -494,16 +494,6 @@ function Shift_signup_allowed(
);
}
/**
* Delete a shift by its external id.
*
* @param int $shift_psid
*/
function Shift_delete_by_psid($shift_psid)
{
DB::delete('DELETE FROM `Shifts` WHERE `PSID`=?', [$shift_psid]);
}
/**
* Delete a shift.
*
@ -535,7 +525,6 @@ function Shift_update($shift)
`RID` = ?,
`title` = ?,
`URL` = ?,
`PSID` = ?,
`edited_by_user_id` = ?,
`edited_at_timestamp` = ?
WHERE `SID` = ?
@ -547,7 +536,6 @@ function Shift_update($shift)
$shift['RID'],
$shift['title'],
$shift['URL'],
$shift['PSID'],
$user->id,
time(),
$shift['SID']
@ -555,25 +543,6 @@ function Shift_update($shift)
);
}
/**
* Update a shift by its external id.
*
* @param array $shift
* @return int
* @throws Exception
*/
function Shift_update_by_psid($shift)
{
$shift_source = DB::selectOne('SELECT `SID` FROM `Shifts` WHERE `PSID`=?', [$shift['PSID']]);
if (empty($shift_source)) {
throw new Exception('Shift not found.');
}
$shift['SID'] = $shift_source['SID'];
return Shift_update($shift);
}
/**
* Create a new shift.
*
@ -590,12 +559,11 @@ function Shift_create($shift)
`RID`,
`title`,
`URL`,
`PSID`,
`created_by_user_id`,
`edited_at_timestamp`,
`created_at_timestamp`
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
',
[
$shift['shifttype_id'],
@ -604,7 +572,6 @@ function Shift_create($shift)
$shift['RID'],
$shift['title'],
$shift['URL'],
$shift['PSID'],
auth()->user()->id,
time(),
time(),

View File

@ -39,8 +39,9 @@ function stats_hours_to_work()
(SELECT SUM(`count`) FROM `NeededAngelTypes` WHERE `NeededAngelTypes`.`shift_id`=`Shifts`.`SID`)
* (`Shifts`.`end` - `Shifts`.`start`)/3600 AS `count`
FROM `Shifts`
LEFT JOIN schedule_shift AS s on Shifts.SID = s.shift_id
WHERE `end` >= ?
AND `Shifts`.`PSID` IS NULL
AND s.shift_id IS NULL
UNION ALL
@ -48,8 +49,9 @@ function stats_hours_to_work()
(SELECT SUM(`count`) FROM `NeededAngelTypes` WHERE `NeededAngelTypes`.`room_id`=`Shifts`.`RID`)
* (`Shifts`.`end` - `Shifts`.`start`)/3600 AS `count`
FROM `Shifts`
LEFT JOIN schedule_shift AS s on Shifts.SID = s.shift_id
WHERE `end` >= ?
AND NOT `Shifts`.`PSID` IS NULL
AND NOT s.shift_id IS NULL
) AS `tmp`
", [
time(),
@ -90,8 +92,9 @@ function stats_angels_needed_three_hours()
)
AS `count`
FROM `Shifts`
LEFT JOIN schedule_shift AS s on Shifts.SID = s.shift_id
WHERE `end` > ? AND `start` < ?
AND `Shifts`.`PSID` IS NULL
AND s.shift_id IS NULL
UNION ALL
@ -113,8 +116,9 @@ function stats_angels_needed_three_hours()
)
AS `count`
FROM `Shifts`
LEFT JOIN schedule_shift AS s on Shifts.SID = s.shift_id
WHERE `end` > ? AND `start` < ?
AND NOT `Shifts`.`PSID` IS NULL
AND NOT s.shift_id IS NULL
) AS `tmp`", [
$now,
$in3hours,
@ -163,8 +167,9 @@ function stats_angels_needed_for_nightshifts()
)
AS `count`
FROM `Shifts`
LEFT JOIN schedule_shift AS s on Shifts.SID = s.shift_id
WHERE `end` > ? AND `start` < ?
AND `Shifts`.`PSID` IS NULL
AND s.shift_id IS NULL
UNION ALL
@ -186,8 +191,9 @@ function stats_angels_needed_for_nightshifts()
)
AS `count`
FROM `Shifts`
LEFT JOIN schedule_shift AS s on Shifts.SID = s.shift_id
WHERE `end` > ? AND `start` < ?
AND NOT `Shifts`.`PSID` IS NULL
AND NOT s.shift_id IS NULL
) AS `tmp`", [
$night_start,
$night_end,

View File

@ -42,11 +42,11 @@ function admin_active()
__('At least %s angels are forced to be active. The number has to be greater.'),
$forced_count
));
redirect(page_link_to('admin_active'));
throw_redirect(page_link_to('admin_active'));
}
} else {
$msg .= error(__('Please enter a number of angels to be marked as active.'));
redirect(page_link_to('admin_active'));
throw_redirect(page_link_to('admin_active'));
}
if ($request->hasPostData('ack')) {

View File

@ -39,7 +39,7 @@ function admin_arrive()
engelsystem_log('User set to not arrived: ' . User_Nick_render($user_source, true));
success(__('Reset done. Angel has not arrived.'));
redirect(user_link($user_source->id));
throw_redirect(user_link($user_source->id));
} else {
$msg = error(__('Angel not found.'), true);
}
@ -57,7 +57,7 @@ function admin_arrive()
engelsystem_log('User set has arrived: ' . User_Nick_render($user_source, true));
success(__('Angel has been marked as arrived.'));
redirect(user_link($user_source->id));
throw_redirect(user_link($user_source->id));
} else {
$msg = error(__('Angel not found.'), true);
}

View File

@ -148,7 +148,7 @@ function admin_groups()
'Group privileges of group ' . $group['Name']
. ' edited: ' . join(', ', $privilege_names)
);
redirect(page_link_to('admin_groups'));
throw_redirect(page_link_to('admin_groups'));
} else {
return error('No Group found.', true);
}

View File

@ -1,478 +0,0 @@
<?php
/**
* @return string
*/
function admin_import_title()
{
return __('Frab import');
}
/**
* @return string
*/
function admin_import()
{
global $rooms_import;
$user = auth()->user();
$html = '';
$import_dir = __DIR__ . '/../../import';
$request = request();
$step = 'input';
if (
$request->has('step')
&& in_array($request->input('step'), [
'input',
'check',
'import'
])
) {
$step = $request->input('step');
}
try {
$test_handle = @fopen($import_dir . '/tmp', 'w');
fclose($test_handle);
@unlink($import_dir . '/tmp');
} catch (Exception $e) {
error(__('Webserver has no write-permission on import directory.'));
}
$import_file = $import_dir . '/import_' . $user->id . '.xml';
$shifttype_id = null;
$add_minutes_start = 15;
$add_minutes_end = 15;
$shifttypes_source = ShiftTypes();
$shifttypes = [];
foreach ($shifttypes_source as $shifttype) {
$shifttypes[$shifttype['id']] = $shifttype['name'];
}
switch ($step) {
case 'input':
$valid = false;
if ($request->hasPostData('submit')) {
$valid = true;
if ($request->has('shifttype_id') && isset($shifttypes[$request->input('shifttype_id')])) {
$shifttype_id = $request->input('shifttype_id');
} else {
$valid = false;
error(__('Please select a shift type.'));
}
$minutes_start = trim($request->input('add_minutes_start'));
if ($request->has('add_minutes_start') && is_numeric($minutes_start)) {
$add_minutes_start = $minutes_start;
} else {
$valid = false;
error(__('Please enter an amount of minutes to add to a talk\'s begin.'));
}
if ($request->has('add_minutes_end') && is_numeric(trim($request->input('add_minutes_end')))) {
$add_minutes_end = trim($request->input('add_minutes_end'));
} else {
$valid = false;
error(__('Please enter an amount of minutes to add to a talk\'s end.'));
}
if (isset($_FILES['xcal_file']) && ($_FILES['xcal_file']['error'] == 0)) {
if (move_uploaded_file($_FILES['xcal_file']['tmp_name'], $import_file)) {
libxml_use_internal_errors(true);
if (simplexml_load_file($import_file) === false) {
$valid = false;
error(__('No valid xml/xcal file provided.'));
unlink($import_file);
}
} else {
$valid = false;
error(__('File upload went wrong.'));
}
} else {
$valid = false;
error(__('Please provide some data.'));
}
}
if ($valid) {
redirect(
page_link_to('admin_import', [
'step' => 'check',
'shifttype_id' => $shifttype_id,
'add_minutes_end' => $add_minutes_end,
'add_minutes_start' => $add_minutes_start,
])
);
} else {
$html .= div('well well-sm text-center', [
__('File Upload')
. mute(glyph('arrow-right'))
. mute(__('Validation'))
. mute(glyph('arrow-right'))
. mute(__('Import'))
]) . div('row', [
div('col-md-offset-3 col-md-6', [
form([
form_info(
'',
__('This import will create/update/delete rooms and shifts by given FRAB-export file. The needed file format is xcal.')
),
form_select('shifttype_id', __('Shifttype'), $shifttypes, $shifttype_id),
form_spinner('add_minutes_start', __('Add minutes to start'), $add_minutes_start),
form_spinner('add_minutes_end', __('Add minutes to end'), $add_minutes_end),
form_file('xcal_file', __('xcal-File (.xcal)')),
form_submit('submit', __('Import'))
])
])
]);
}
break;
case 'check':
if (!file_exists($import_file)) {
error(__('Missing import file.'));
redirect(page_link_to('admin_import'));
}
if ($request->has('shifttype_id') && isset($shifttypes[$request->input('shifttype_id')])) {
$shifttype_id = $request->input('shifttype_id');
} else {
error(__('Please select a shift type.'));
redirect(page_link_to('admin_import'));
}
if ($request->has('add_minutes_start') && is_numeric(trim($request->input('add_minutes_start')))) {
$add_minutes_start = trim($request->input('add_minutes_start'));
} else {
error(__('Please enter an amount of minutes to add to a talk\'s begin.'));
redirect(page_link_to('admin_import'));
}
if ($request->has('add_minutes_end') && is_numeric(trim($request->input(('add_minutes_end'))))) {
$add_minutes_end = trim($request->input('add_minutes_end'));
} else {
error(__('Please enter an amount of minutes to add to a talk\'s end.'));
redirect(page_link_to('admin_import'));
}
list($rooms_new, $rooms_deleted) = prepare_rooms($import_file);
list($events_new, $events_updated, $events_deleted) = prepare_events(
$import_file,
$shifttype_id,
$add_minutes_start,
$add_minutes_end
);
$html .= div(
'well well-sm text-center',
[
'<span class="text-success">' . __('File Upload') . glyph('ok-circle') . '</span>'
. mute(glyph('arrow-right'))
. __('Validation')
. mute(glyph('arrow-right'))
. mute(__('Import'))
]
)
. form(
[
div('row', [
div('col-sm-6', [
'<h3>' . __('Rooms to create') . '</h3>',
table(__('Name'), $rooms_new)
]),
div('col-sm-6', [
'<h3>' . __('Rooms to delete') . '</h3>',
table(__('Name'), $rooms_deleted)
])
]),
'<h3>' . __('Shifts to create') . '</h3>',
table([
'day' => __('Day'),
'start' => __('Start'),
'end' => __('End'),
'shifttype' => __('Shift type'),
'title' => __('Title'),
'room' => __('Room')
], shifts_printable($events_new, $shifttypes)),
'<h3>' . __('Shifts to update') . '</h3>',
table([
'day' => __('Day'),
'start' => __('Start'),
'end' => __('End'),
'shifttype' => __('Shift type'),
'title' => __('Title'),
'room' => __('Room')
], shifts_printable($events_updated, $shifttypes)),
'<h3>' . __('Shifts to delete') . '</h3>',
table([
'day' => __('Day'),
'start' => __('Start'),
'end' => __('End'),
'shifttype' => __('Shift type'),
'title' => __('Title'),
'room' => __('Room')
], shifts_printable($events_deleted, $shifttypes)),
form_submit('submit', __('Import'))
],
page_link_to('admin_import', [
'step' => 'import',
'shifttype_id' => $shifttype_id,
'add_minutes_end' => $add_minutes_end,
'add_minutes_start' => $add_minutes_start,
])
);
break;
case 'import':
if (!file_exists($import_file)) {
error(__('Missing import file.'));
redirect(page_link_to('admin_import'));
}
if (!file_exists($import_file)) {
redirect(page_link_to('admin_import'));
}
if ($request->has('shifttype_id') && isset($shifttypes[$request->input('shifttype_id')])) {
$shifttype_id = $request->input('shifttype_id');
} else {
error(__('Please select a shift type.'));
redirect(page_link_to('admin_import'));
}
if ($request->has('add_minutes_start') && is_numeric(trim($request->input('add_minutes_start')))) {
$add_minutes_start = trim($request->input('add_minutes_start'));
} else {
error(__('Please enter an amount of minutes to add to a talk\'s begin.'));
redirect(page_link_to('admin_import'));
}
if ($request->has('add_minutes_end') && is_numeric(trim($request->input('add_minutes_end')))) {
$add_minutes_end = trim($request->input('add_minutes_end'));
} else {
error(__('Please enter an amount of minutes to add to a talk\'s end.'));
redirect(page_link_to('admin_import'));
}
list($rooms_new, $rooms_deleted) = prepare_rooms($import_file);
foreach ($rooms_new as $room) {
$result = Room_create($room, true, null, null);
$rooms_import[trim($room)] = $result;
}
foreach ($rooms_deleted as $room) {
Room_delete_by_name($room);
}
list($events_new, $events_updated, $events_deleted) = prepare_events(
$import_file,
$shifttype_id,
$add_minutes_start,
$add_minutes_end
);
foreach ($events_new as $event) {
Shift_create($event);
}
foreach ($events_updated as $event) {
Shift_update_by_psid($event);
}
foreach ($events_deleted as $event) {
Shift_delete_by_psid($event['PSID']);
}
engelsystem_log('Frab import done');
unlink($import_file);
$html .= div('well well-sm text-center', [
'<span class="text-success">' . __('File Upload') . glyph('ok-circle') . '</span>'
. mute(glyph('arrow-right'))
. '<span class="text-success">' . __('Validation') . glyph('ok-circle') . '</span>'
. mute(glyph('arrow-right'))
. '<span class="text-success">' . __('Import') . glyph('ok-circle') . '</span>'
]) . success(__('It\'s done!'), true);
break;
default:
redirect(page_link_to('admin_import'));
}
return page_with_title(admin_import_title(), [
msg(),
$html
]);
}
/**
* @param string $file
* @return array
*/
function prepare_rooms($file)
{
global $rooms_import;
$data = read_xml($file);
// Load rooms from db for compare with input
$rooms = Rooms();
// Contains rooms from db with from_frab==true
$rooms_db = [];
// Contains all rooms from db
$rooms_db_all = [];
// Contains all rooms from db and frab
$rooms_import = [];
foreach ($rooms as $room) {
if ($room['from_frab']) {
$rooms_db[] = $room['Name'];
}
$rooms_db_all[] = $room['Name'];
$rooms_import[$room['Name']] = $room['RID'];
}
$events = $data->vcalendar->vevent;
$rooms_frab = [];
foreach ($events as $event) {
$rooms_frab[] = (string)$event->location;
if (!isset($rooms_import[trim($event->location)])) {
$rooms_import[trim($event->location)] = trim($event->location);
}
}
$rooms_frab = array_unique($rooms_frab);
$rooms_new = array_diff($rooms_frab, $rooms_db_all);
$rooms_deleted = array_diff($rooms_db, $rooms_frab);
return [
$rooms_new,
$rooms_deleted
];
}
/**
* @param string $file
* @param int $shifttype_id
* @param int $add_minutes_start
* @param int $add_minutes_end
* @return array
*/
function prepare_events($file, $shifttype_id, $add_minutes_start, $add_minutes_end)
{
global $rooms_import;
$data = read_xml($file);
$rooms = Rooms();
$rooms_db = [];
foreach ($rooms as $room) {
$rooms_db[$room['Name']] = $room['RID'];
}
$events = $data->vcalendar->vevent;
$shifts_pb = [];
foreach ($events as $event) {
$event_pb = $event->children('http://pentabarf.org');
$event_id = trim($event_pb->{'event-id'});
$shifts_pb[$event_id] = [
'shifttype_id' => $shifttype_id,
'start' => parse_date("Ymd\THis", $event->dtstart) - $add_minutes_start * 60,
'end' => parse_date("Ymd\THis", $event->dtend) + $add_minutes_end * 60,
'RID' => $rooms_import[trim($event->location)],
'title' => trim($event->summary),
'URL' => trim($event->url),
'PSID' => $event_id
];
}
$shifts = Shifts_from_frab();
$shifts_db = [];
foreach ($shifts as $shift) {
$shifts_db[$shift['PSID']] = $shift;
}
$shifts_new = [];
$shifts_updated = [];
foreach ($shifts_pb as $shift) {
if (!isset($shifts_db[$shift['PSID']])) {
$shifts_new[] = $shift;
} else {
$tmp = $shifts_db[$shift['PSID']];
if (
$shift['shifttype_id'] != $tmp['shifttype_id']
|| $shift['title'] != $tmp['title']
|| $shift['start'] != $tmp['start']
|| $shift['end'] != $tmp['end']
|| $shift['RID'] != $tmp['RID']
|| $shift['URL'] != $tmp['URL']
) {
$shifts_updated[] = $shift;
}
}
}
$shifts_deleted = [];
foreach ($shifts_db as $shift) {
if (!isset($shifts_pb[$shift['PSID']])) {
$shifts_deleted[] = $shift;
}
}
return [
$shifts_new,
$shifts_updated,
$shifts_deleted
];
}
/**
* @param string $file
* @return SimpleXMLElement
*/
function read_xml($file)
{
global $xml_import;
if (!isset($xml_import)) {
libxml_use_internal_errors(true);
$xml_import = simplexml_load_file($file);
}
return $xml_import;
}
/**
* @param array $shifts
* @param array $shifttypes
* @return array
*/
function shifts_printable($shifts, $shifttypes)
{
global $rooms_import;
$rooms = array_flip($rooms_import);
uasort($shifts, 'shift_sort');
$shifts_printable = [];
foreach ($shifts as $shift) {
$shifts_printable[] = [
'day' => date('l, Y-m-d', $shift['start']),
'start' => date('H:i', $shift['start']),
'shifttype' => ShiftType_name_render([
'id' => $shift['shifttype_id'],
'name' => $shifttypes[$shift['shifttype_id']]
]),
'title' => shorten($shift['title']),
'end' => date('H:i', $shift['end']),
'room' => $rooms[$shift['RID']]
];
}
return $shifts_printable;
}
/**
* @param array $shift_a
* @param array $shift_b
* @return int
*/
function shift_sort($shift_a, $shift_b)
{
return ($shift_a['start'] < $shift_b['start']) ? -1 : 1;
}

View File

@ -10,7 +10,7 @@ function admin_news()
$request = request();
if (!$request->has('action')) {
redirect(page_link_to('news'));
throw_redirect(page_link_to('news'));
}
$html = '<div class="col-md-12"><h1>' . __('Edit news entry') . '</h1>' . msg();
@ -70,17 +70,17 @@ function admin_news()
engelsystem_log('News updated: ' . $request->postData('eBetreff'));
success(__('News entry updated.'));
redirect(page_link_to('news'));
throw_redirect(page_link_to('news'));
break;
case 'delete':
$news->delete();
engelsystem_log('News deleted: ' . $news->title);
success(__('News entry deleted.'));
redirect(page_link_to('news'));
throw_redirect(page_link_to('news'));
break;
default:
redirect(page_link_to('news'));
throw_redirect(page_link_to('news'));
}
return $html . '</div>';
}

View File

@ -122,7 +122,7 @@ function admin_questions()
. ' answered: '
. $answer
);
redirect(page_link_to('admin_questions'));
throw_redirect(page_link_to('admin_questions'));
} else {
return error('Enter an answer!', true);
}
@ -145,7 +145,7 @@ function admin_questions()
if (!empty($question)) {
$question->delete();
engelsystem_log('Question deleted: ' . $question['Question']);
redirect(page_link_to('admin_questions'));
throw_redirect(page_link_to('admin_questions'));
} else {
return error('No question found.', true);
}

View File

@ -19,7 +19,6 @@ function admin_rooms()
foreach ($rooms_source as $room) {
$rooms[] = [
'name' => Room_name_render($room),
'from_frab' => glyph_bool($room['from_frab']),
'map_url' => glyph_bool(!empty($room['map_url'])),
'actions' => table_buttons([
button(
@ -40,7 +39,6 @@ function admin_rooms()
if ($request->has('show')) {
$msg = '';
$name = '';
$from_frab = false;
$map_url = null;
$description = null;
$room_id = 0;
@ -56,12 +54,11 @@ function admin_rooms()
if (test_request_int('id')) {
$room = Room($request->input('id'));
if (empty($room)) {
redirect(page_link_to('admin_rooms'));
throw_redirect(page_link_to('admin_rooms'));
}
$room_id = $request->input('id');
$name = $room['Name'];
$from_frab = $room['from_frab'];
$map_url = $room['map_url'];
$description = $room['description'];
@ -88,8 +85,6 @@ function admin_rooms()
$msg .= error(__('Please enter a name.'), true);
}
$from_frab = $request->has('from_frab');
if ($request->has('map_url')) {
$map_url = strip_request_item('map_url');
}
@ -118,9 +113,9 @@ function admin_rooms()
if ($valid) {
if (empty($room_id)) {
$room_id = Room_create($name, $from_frab, $map_url, $description);
$room_id = Room_create($name, $map_url, $description);
} else {
Room_update($room_id, $name, $from_frab, $map_url, $description);
Room_update($room_id, $name, $map_url, $description);
}
NeededAngelTypes_delete_by_room($room_id);
@ -140,7 +135,7 @@ function admin_rooms()
. ' to: ' . join(', ', $needed_angeltype_info)
);
success(__('Room saved.'));
redirect(page_link_to('admin_rooms'));
throw_redirect(page_link_to('admin_rooms'));
}
}
$angeltypes_count_form = [];
@ -159,7 +154,6 @@ function admin_rooms()
div('row', [
div('col-md-6', [
form_text('name', __('Name'), $name, false, 35),
form_checkbox('from_frab', __('Frab import'), $from_frab),
form_text('map_url', __('Map URL'), $map_url),
form_info('', __('The map url is used to display an iframe on the room page.')),
form_textarea('description', __('Description'), $description),
@ -190,7 +184,7 @@ function admin_rooms()
Room_delete($room_id);
success(sprintf(__('Room %s deleted.'), $name));
redirect(page_link_to('admin_rooms'));
throw_redirect(page_link_to('admin_rooms'));
}
return page_with_title(admin_rooms_title(), [
@ -212,7 +206,6 @@ function admin_rooms()
msg(),
table([
'name' => __('Name'),
'from_frab' => __('Frab import'),
'map_url' => __('Map'),
'actions' => ''
], $rooms)

View File

@ -345,12 +345,11 @@ function admin_shifts()
!is_array($session->get('admin_shifts_shifts'))
|| !is_array($session->get('admin_shifts_types'))
) {
redirect(page_link_to('admin_shifts'));
throw_redirect(page_link_to('admin_shifts'));
}
foreach ($session->get('admin_shifts_shifts', []) as $shift) {
$shift['URL'] = null;
$shift['PSID'] = null;
$shift_id = Shift_create($shift);
engelsystem_log(
@ -389,7 +388,7 @@ function admin_shifts()
}
success('Schichten angelegt.');
redirect(page_link_to('admin_shifts'));
throw_redirect(page_link_to('admin_shifts'));
} else {
$session->remove('admin_shifts_shifts');
$session->remove('admin_shifts_types');

View File

@ -22,7 +22,7 @@ function admin_user()
$html = '';
if (!$request->has('id')) {
redirect(users_link());
throw_redirect(users_link());
}
$user_id = $request->input('id');
@ -30,7 +30,7 @@ function admin_user()
$user_source = User::find($user_id);
if (!$user_source) {
error(__('This user does not exist.'));
redirect(users_link());
throw_redirect(users_link());
}
$html .= 'Hallo,<br />'

View File

@ -239,7 +239,7 @@ function guest_register()
// User is already logged in - that means a supporter has registered an angel. Return to register page.
if ($authUser) {
redirect(page_link_to('register'));
throw_redirect(page_link_to('register'));
}
// If a welcome message is present, display it on the next page
@ -247,7 +247,7 @@ function guest_register()
info((new Parsedown())->text($message));
}
redirect(page_link_to('/'));
throw_redirect(page_link_to('/'));
}
}

View File

@ -0,0 +1,614 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Controllers\Admin\Schedule;
use Carbon\Carbon;
use Engelsystem\Controllers\BaseController;
use Engelsystem\Helpers\Schedule\Event;
use Engelsystem\Helpers\Schedule\Room;
use Engelsystem\Helpers\Schedule\Schedule;
use Engelsystem\Helpers\Schedule\XmlParser;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Models\Shifts\Schedule as ScheduleUrl;
use Engelsystem\Models\Shifts\ScheduleShift;
use ErrorException;
use GuzzleHttp\Client as GuzzleClient;
use Illuminate\Database\Connection as DatabaseConnection;
use Illuminate\Database\Eloquent\Builder as QueryBuilder;
use Illuminate\Database\Eloquent\Collection as DatabaseCollection;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Psr\Log\LoggerInterface;
use stdClass;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class ImportSchedule extends BaseController
{
/** @var DatabaseConnection */
protected $db;
/** @var LoggerInterface */
protected $log;
/** @var array */
protected $permissions = [
'schedule.import',
];
/** @var XmlParser */
protected $parser;
/** @var Response */
protected $response;
/** @var SessionInterface */
protected $session;
/** @var string */
protected $url = '/admin/schedule';
/** @var GuzzleClient */
protected $guzzle;
/**
* @param Response $response
* @param SessionInterface $session
* @param GuzzleClient $guzzle
* @param XmlParser $parser
* @param DatabaseConnection $db
* @param LoggerInterface $log
*/
public function __construct(
Response $response,
SessionInterface $session,
GuzzleClient $guzzle,
XmlParser $parser,
DatabaseConnection $db,
LoggerInterface $log
) {
$this->guzzle = $guzzle;
$this->parser = $parser;
$this->response = $response;
$this->session = $session;
$this->db = $db;
$this->log = $log;
}
/**
* @return Response
*/
public function index(): Response
{
return $this->response->withView(
'admin/schedule/index.twig',
[
'errors' => $this->getFromSession('errors'),
'success' => $this->getFromSession('success'),
'shift_types' => $this->getShiftTypes(),
]
);
}
/**
* @param Request $request
* @return Response
*/
public function loadSchedule(Request $request): Response
{
try {
/**
* @var Event[] $newEvents
* @var Event[] $changeEvents
* @var Event[] $deleteEvents
* @var Room[] $newRooms
* @var int $shiftType
* @var ScheduleUrl $scheduleUrl
* @var Schedule $schedule
* @var int $minutesBefore
* @var int $minutesAfter
*/
list(
$newEvents,
$changeEvents,
$deleteEvents,
$newRooms,
$shiftType,
$scheduleUrl,
$schedule,
$minutesBefore,
$minutesAfter
) = $this->getScheduleData($request);
} catch (ErrorException $e) {
return back()->with('errors', [$e->getMessage()]);
}
return $this->response->withView(
'admin/schedule/load.twig',
[
'errors' => $this->getFromSession('errors'),
'schedule_url' => $scheduleUrl->url,
'shift_type' => $shiftType,
'minutes_before' => $minutesBefore,
'minutes_after' => $minutesAfter,
'schedule' => $schedule,
'rooms' => [
'add' => $newRooms,
],
'shifts' => [
'add' => $newEvents,
'update' => $changeEvents,
'delete' => $deleteEvents,
],
]
);
}
/**
* @param Request $request
*
* @return Response
*/
public function importSchedule(Request $request): Response
{
try {
/**
* @var Event[] $newEvents
* @var Event[] $changeEvents
* @var Event[] $deleteEvents
* @var Room[] $newRooms
* @var int $shiftType
* @var ScheduleUrl $scheduleUrl
*/
list(
$newEvents,
$changeEvents,
$deleteEvents,
$newRooms,
$shiftType,
$scheduleUrl
) = $this->getScheduleData($request);
} catch (ErrorException $e) {
return back()->with('errors', [$e->getMessage()]);
}
$this->log('Started schedule "{schedule}" import', ['schedule' => $scheduleUrl->url]);
foreach ($newRooms as $room) {
$this->createRoom($room);
}
$rooms = $this->getAllRooms();
foreach ($newEvents as $event) {
$this->createEvent(
$event,
(int)$shiftType,
$rooms
->where('name', $event->getRoom()->getName())
->first(),
$scheduleUrl
);
}
foreach ($changeEvents as $event) {
$this->updateEvent(
$event,
(int)$shiftType,
$rooms
->where('name', $event->getRoom()->getName())
->first()
);
}
foreach ($deleteEvents as $event) {
$this->deleteEvent($event);
}
$this->log('Ended schedule "{schedule}" import', ['schedule' => $scheduleUrl->url]);
return redirect($this->url, 303)
->with('success', ['schedule.import.success']);
}
/**
* @param Room $room
*/
protected function createRoom(Room $room): void
{
$this->db
->table('Room')
->insert(
[
'Name' => $room->getName(),
]
);
$this->log('Created schedule room "{room}"', ['room' => $room->getName()]);
}
/**
* @param Event $shift
* @param int $shiftTypeId
* @param stdClass $room
* @param ScheduleUrl $scheduleUrl
*/
protected function createEvent(Event $shift, int $shiftTypeId, stdClass $room, ScheduleUrl $scheduleUrl): void
{
$user = auth()->user();
$this->db
->table('Shifts')
->insert(
[
'title' => $shift->getTitle(),
'shifttype_id' => $shiftTypeId,
'start' => $shift->getDate()->unix(),
'end' => $shift->getEndDate()->unix(),
'RID' => $room->id,
'URL' => $shift->getUrl(),
'created_by_user_id' => $user->id,
'created_at_timestamp' => time(),
'edited_by_user_id' => null,
'edited_at_timestamp' => 0,
]
);
$shiftId = $this->db->getDoctrineConnection()->lastInsertId();
$scheduleShift = new ScheduleShift(['shift_id' => $shiftId, 'guid' => $shift->getGuid()]);
$scheduleShift->schedule()->associate($scheduleUrl);
$scheduleShift->save();
$this->log(
'Created schedule shift "{shift}" in "{room}" ({from} {to}, {guid})',
[
'shift' => $shift->getTitle(),
'room' => $room->name,
'from' => $shift->getDate()->format(Carbon::RFC3339),
'to' => $shift->getEndDate()->format(Carbon::RFC3339),
'guid' => $shift->getGuid(),
]
);
}
/**
* @param Event $shift
* @param int $shiftTypeId
* @param stdClass $room
*/
protected function updateEvent(Event $shift, int $shiftTypeId, stdClass $room): void
{
$user = auth()->user();
$this->db
->table('Shifts')
->join('schedule_shift', 'Shifts.SID', 'schedule_shift.shift_id')
->where('schedule_shift.guid', $shift->getGuid())
->update(
[
'title' => $shift->getTitle(),
'shifttype_id' => $shiftTypeId,
'start' => $shift->getDate()->unix(),
'end' => $shift->getEndDate()->unix(),
'RID' => $room->id,
'URL' => $shift->getUrl(),
'edited_by_user_id' => $user->id,
'edited_at_timestamp' => time(),
]
);
$this->log(
'Updated schedule shift "{shift}" in "{room}" ({from} {to}, {guid})',
[
'shift' => $shift->getTitle(),
'room' => $room->name,
'from' => $shift->getDate()->format(Carbon::RFC3339),
'to' => $shift->getEndDate()->format(Carbon::RFC3339),
'guid' => $shift->getGuid(),
]
);
}
/**
* @param Event $shift
*/
protected function deleteEvent(Event $shift): void
{
$this->db
->table('Shifts')
->join('schedule_shift', 'Shifts.SID', 'schedule_shift.shift_id')
->where('schedule_shift.guid', $shift->getGuid())
->delete();
$this->log(
'Deleted schedule shift "{shift}" ({from} {to}, {guid})',
[
'shift' => $shift->getTitle(),
'from' => $shift->getDate()->format(Carbon::RFC3339),
'to' => $shift->getEndDate()->format(Carbon::RFC3339),
'guid' => $shift->getGuid(),
]
);
}
/**
* @param Request $request
* @return Event[]|Room[]|ScheduleUrl|Schedule|string
* @throws ErrorException
*/
protected function getScheduleData(Request $request)
{
$data = $this->validate(
$request,
[
'schedule-url' => 'required|url',
'shift-type' => 'required|int',
'minutes-before' => 'optional|int',
'minutes-after' => 'optional|int',
]
);
$scheduleResponse = $this->guzzle->get($data['schedule-url']);
if ($scheduleResponse->getStatusCode() != 200) {
throw new ErrorException('schedule.import.request-error');
}
$scheduleData = (string)$scheduleResponse->getBody();
if (!$this->parser->load($scheduleData)) {
throw new ErrorException('schedule.import.read-error');
}
$shiftType = (int)$data['shift-type'];
if (!isset($this->getShiftTypes()[$shiftType])) {
throw new ErrorException('schedule.import.invalid-shift-type');
}
$scheduleUrl = $this->getScheduleUrl($data['schedule-url']);
$schedule = $this->parser->getSchedule();
$minutesBefore = isset($data['minutes-before']) ? (int)$data['minutes-before'] : 15;
$minutesAfter = isset($data['minutes-after']) ? (int)$data['minutes-after'] : 15;
$newRooms = $this->newRooms($schedule->getRooms());
return array_merge(
$this->shiftsDiff($schedule, $scheduleUrl, $shiftType, $minutesBefore, $minutesAfter),
[$newRooms, $shiftType, $scheduleUrl, $schedule, $minutesBefore, $minutesAfter]
);
}
/**
* @param string $name
* @return Collection
*/
protected function getFromSession(string $name): Collection
{
$data = Collection::make(Arr::flatten($this->session->get($name, [])));
$this->session->remove($name);
return $data;
}
/**
* @param Room[] $scheduleRooms
* @return Room[]
*/
protected function newRooms(array $scheduleRooms): array
{
$newRooms = [];
$allRooms = $this->getAllRooms();
foreach ($scheduleRooms as $room) {
if ($allRooms->where('name', $room->getName())->count()) {
continue;
}
$newRooms[] = $room;
}
return $newRooms;
}
/**
* @param Schedule $schedule
* @param ScheduleUrl $scheduleUrl
* @param int $shiftType
* @param int $minutesBefore
* @param int $minutesAfter
* @return Event[]
*/
protected function shiftsDiff(
Schedule $schedule,
ScheduleUrl $scheduleUrl,
int $shiftType,
int $minutesBefore,
int $minutesAfter
): array {
/** @var Event[] $newEvents */
$newEvents = [];
/** @var Event[] $changeEvents */
$changeEvents = [];
/** @var Event[] $scheduleEvents */
$scheduleEvents = [];
/** @var Event[] $deleteEvents */
$deleteEvents = [];
$rooms = $this->getAllRooms();
foreach ($schedule->getDay() as $day) {
foreach ($day->getRoom() as $room) {
foreach ($room->getEvent() as $event) {
$scheduleEvents[$event->getGuid()] = $event;
$event->getDate()->subMinutes($minutesBefore);
$event->getEndDate()->addMinutes($minutesAfter);
$event->setTitle(sprintf('%s [%s]', $event->getTitle(), $event->getLanguage()));
}
}
}
$scheduleEventsGuidList = array_keys($scheduleEvents);
$existingShifts = $this->getScheduleShiftsByGuid($scheduleUrl, $scheduleEventsGuidList);
foreach ($existingShifts as $shift) {
$guid = $shift->guid;
$shift = $this->loadShift($shift->shift_id);
$event = $scheduleEvents[$guid];
$room = $rooms->where('name', $event->getRoom()->getName())->first();
if (
$shift->title != $event->getTitle()
|| $shift->shift_type_id != $shiftType
|| Carbon::createFromTimestamp($shift->start) != $event->getDate()
|| Carbon::createFromTimestamp($shift->end) != $event->getEndDate()
|| $shift->room_id != ($room->id ?? '')
|| $shift->url != $event->getUrl()
) {
$changeEvents[$guid] = $event;
}
unset($scheduleEvents[$guid]);
}
foreach ($scheduleEvents as $scheduleEvent) {
$newEvents[$scheduleEvent->getGuid()] = $scheduleEvent;
}
$scheduleShifts = $this->getScheduleShiftsWhereNotGuid($scheduleUrl, $scheduleEventsGuidList);
foreach ($scheduleShifts as $shift) {
$event = $this->eventFromScheduleShift($shift);
$deleteEvents[$event->getGuid()] = $event;
}
return [$newEvents, $changeEvents, $deleteEvents];
}
/**
* @param ScheduleShift $scheduleShift
* @return Event
*/
protected function eventFromScheduleShift(ScheduleShift $scheduleShift): Event
{
$shift = $this->loadShift($scheduleShift->shift_id);
$start = Carbon::createFromTimestamp($shift->start);
$end = Carbon::createFromTimestamp($shift->end);
$duration = $start->diff($end);
$event = new Event(
$scheduleShift->guid,
0,
new Room($shift->room_name),
$shift->title,
'',
'n/a',
Carbon::createFromTimestamp($shift->start),
$start->format('H:i'),
$duration->format('%H:%I'),
'',
'',
''
);
return $event;
}
/**
* @return Collection
*/
protected function getAllRooms(): Collection
{
return new Collection($this->db->select('SELECT RID as id, Name as name FROM Room'));
}
/**
* @param ScheduleUrl $scheduleUrl
* @param string[] $events
* @return QueryBuilder[]|DatabaseCollection|ScheduleShift[]
*/
protected function getScheduleShiftsByGuid(ScheduleUrl $scheduleUrl, array $events)
{
return ScheduleShift::query()
->whereIn('guid', $events)
->where('schedule_id', $scheduleUrl->id)
->get();
}
/**
* @param ScheduleUrl $scheduleUrl
* @param string[] $events
* @return QueryBuilder[]|DatabaseCollection|ScheduleShift[]
*/
protected function getScheduleShiftsWhereNotGuid(ScheduleUrl $scheduleUrl, array $events)
{
return ScheduleShift::query()
->whereNotIn('guid', $events)
->where('schedule_id', $scheduleUrl->id)
->get();
}
/**
* @param $id
* @return stdClass|null
*/
protected function loadShift($id): ?stdClass
{
return $this->db->selectOne(
'
SELECT
s.SID AS id,
s.title,
s.start,
s.end,
s.shifttype_id AS shift_type_id,
s.RID AS room_id,
r.Name AS room_name,
s.URL as url
FROM Shifts AS s
LEFT JOIN Room r on s.RID = r.RID
WHERE SID = ?
',
[$id]
);
}
/**
* @return string[]
*/
protected function getShiftTypes()
{
$return = [];
/** @var stdClass[] $shiftTypes */
$shiftTypes = $this->db->select('SELECT t.id, t.name FROM ShiftTypes AS t');
foreach ($shiftTypes as $shiftType) {
$return[$shiftType->id] = $shiftType->name;
}
return $return;
}
/**
* @param string $scheduleUrl
* @return ScheduleUrl
*/
protected function getScheduleUrl(string $scheduleUrl): ScheduleUrl
{
if (!$schedule = ScheduleUrl::whereUrl($scheduleUrl)->first()) {
$schedule = new ScheduleUrl(['url' => $scheduleUrl]);
$schedule->save();
$this->log('Created schedule "{schedule}"', ['schedule' => $schedule->url]);
}
return $schedule;
}
/**
* @param string $message
* @param array $context
*/
protected function log(string $message, array $context = []): void
{
$user = auth()->user();
$message = sprintf('%s (%u): %s', $user->name, $user->id, $message);
$this->log->info($message, $context);
}
}

View File

@ -141,7 +141,7 @@ function user_messages()
'UPDATE `Messages` SET `isRead`=\'Y\' WHERE `id`=? LIMIT 1',
[$message_id]
);
redirect(page_link_to('user_messages'));
throw_redirect(page_link_to('user_messages'));
} else {
return error(__('No Message found.'), true);
}
@ -160,7 +160,7 @@ function user_messages()
);
if (!empty($message) && $message['SUID'] == $user->id) {
DB::delete('DELETE FROM `Messages` WHERE `id`=? LIMIT 1', [$message_id]);
redirect(page_link_to('user_messages'));
throw_redirect(page_link_to('user_messages'));
} else {
return error(__('No Message found.'), true);
}
@ -168,7 +168,7 @@ function user_messages()
case 'send':
if (Message_send($request->input('to'), $request->input('text'))) {
redirect(page_link_to('user_messages'));
throw_redirect(page_link_to('user_messages'));
} else {
return error(__('Transmitting was terminated with an Error.'), true);
}

View File

@ -37,7 +37,7 @@ function user_myshifts()
if ($request->input('reset') == 'ack') {
User_reset_api_key($user);
success(__('Key changed.'));
redirect(page_link_to('users', ['action' => 'view', 'user_id' => $shifts_user->id]));
throw_redirect(page_link_to('users', ['action' => 'view', 'user_id' => $shifts_user->id]));
}
return page_with_title(__('Reset API key'), [
error(
@ -109,7 +109,7 @@ function user_myshifts()
. '. Freeloaded: ' . ($freeloaded ? 'YES Comment: ' . $freeload_comment : 'NO')
);
success(__('Shift saved.'));
redirect(page_link_to('users', ['action' => 'view', 'user_id' => $shifts_user->id]));
throw_redirect(page_link_to('users', ['action' => 'view', 'user_id' => $shifts_user->id]));
}
}
@ -125,10 +125,10 @@ function user_myshifts()
auth()->can('user_shifts_admin')
);
} else {
redirect(page_link_to('user_myshifts'));
throw_redirect(page_link_to('user_myshifts'));
}
}
redirect(page_link_to('users', ['action' => 'view', 'user_id' => $shifts_user->id]));
throw_redirect(page_link_to('users', ['action' => 'view', 'user_id' => $shifts_user->id]));
return '';
}

View File

@ -197,7 +197,7 @@ function user_news()
engelsystem_log('Created news: ' . $news->title . ', is meeting: ' . ($news->is_meeting ? 'yes' : 'no'));
success(__('Entry saved.'));
redirect(page_link_to('news'));
throw_redirect(page_link_to('news'));
}
if (preg_match('/^\d{1,}$/', $request->input('page', 0))) {

View File

@ -38,7 +38,7 @@ function user_questions()
]);
success(__('You question was saved.'));
redirect(page_link_to('user_questions'));
throw_redirect(page_link_to('user_questions'));
} else {
return page_with_title(questions_title(), [
error(__('Please enter a question!'), true)
@ -59,7 +59,7 @@ function user_questions()
$question = Question::find($question_id);
if (!empty($question) && $question->user_id == $user->id) {
$question->delete();
redirect(page_link_to('user_questions'));
throw_redirect(page_link_to('user_questions'));
} else {
return page_with_title(questions_title(), [
error(__('No question found.'), true)

View File

@ -91,7 +91,7 @@ function user_settings_main($user_source, $enable_tshirt_size, $tshirt_sizes)
$user_source->settings->save();
success(__('Settings saved.'));
redirect(page_link_to('user_settings'));
throw_redirect(page_link_to('user_settings'));
}
return $user_source;
@ -119,7 +119,7 @@ function user_settings_password($user_source)
$auth->setPassword($user_source, $request->postData('new_password'));
success(__('Password saved.'));
}
redirect(page_link_to('user_settings'));
throw_redirect(page_link_to('user_settings'));
}
/**
@ -144,7 +144,7 @@ function user_settings_theme($user_source, $themes)
$user_source->settings->save();
success(__('Theme changed.'));
redirect(page_link_to('user_settings'));
throw_redirect(page_link_to('user_settings'));
}
return $user_source;
@ -174,7 +174,7 @@ function user_settings_locale($user_source, $locales)
$session->set('locale', $user_source->settings->language);
success('Language changed.');
redirect(page_link_to('user_settings'));
throw_redirect(page_link_to('user_settings'));
}
return $user_source;

View File

@ -25,7 +25,7 @@ function user_shifts()
$request = request();
if (User_is_freeloader(auth()->user())) {
redirect(page_link_to('user_myshifts'));
throw_redirect(page_link_to('user_myshifts'));
}
if ($request->has('edit_shift')) {
@ -99,7 +99,7 @@ function load_rooms()
);
if (empty($rooms)) {
error(__('The administration has not configured any rooms yet.'));
redirect(page_link_to('/'));
throw_redirect(page_link_to('/'));
}
return $rooms;
}
@ -120,7 +120,7 @@ function load_days()
error(__('The administration has not configured any shifts yet.'));
// Do not try to redirect to the current page
if (config('home_site') != 'user_shifts') {
redirect(page_link_to('/'));
throw_redirect(page_link_to('/'));
}
}
return $days;
@ -135,7 +135,7 @@ function load_types()
if (!count(DB::select('SELECT `id`, `name` FROM `AngelTypes`'))) {
error(__('The administration has not configured any angeltypes yet - or you are not subscribed to any angeltype.'));
redirect(page_link_to('/'));
throw_redirect(page_link_to('/'));
}
$types = DB::select('
SELECT

View File

@ -108,30 +108,38 @@ function make_navigation()
$admin_menu = [];
$admin_pages = [
'admin_arrive' => __('Arrived angels'),
'admin_active' => __('Active angels'),
'admin_user' => __('All Angels'),
'admin_free' => __('Free angels'),
'admin_questions' => __('Answer questions'),
'shifttypes' => __('Shifttypes'),
'admin_shifts' => __('Create shifts'),
'admin_rooms' => __('Rooms'),
'admin_groups' => __('Grouprights'),
'admin_import' => __('Frab import'),
'admin_log' => __('Log'),
'admin_event_config' => __('Event config'),
'admin_arrive' => 'Arrived angels',
'admin_active' => 'Active angels',
'admin_user' => 'All Angels',
'admin_free' => 'Free angels',
'admin_questions' => 'Answer questions',
'shifttypes' => 'Shifttypes',
'admin_shifts' => 'Create shifts',
'admin_rooms' => 'Rooms',
'admin_groups' => 'Grouprights',
'admin/schedule' => ['schedule.import', 'schedule.import'],
'admin_log' => 'Log',
'admin_event_config' => 'Event config',
];
if (config('autoarrive')) {
unset($admin_pages['admin_arrive']);
}
foreach ($admin_pages as $menu_page => $title) {
if (auth()->can($menu_page)) {
foreach ($admin_pages as $menu_page => $options) {
$options = (array)$options;
$permissions = $menu_page;
$title = $options[0];
if (isset($options[1])) {
$permissions = $options[1];
}
if (auth()->can($permissions)) {
$admin_menu[] = toolbar_item_link(
page_link_to($menu_page),
'',
$title,
__($title),
$menu_page == $page
);
}

View File

@ -1,6 +1,7 @@
<?php
use Carbon\Carbon;
use Engelsystem\Http\Exceptions\HttpTemporaryRedirect;
use Engelsystem\ValidationResult;
/**
@ -55,10 +56,9 @@ function parse_date($pattern, $value)
*
* @param string $url
*/
function redirect($url)
function throw_redirect($url)
{
header('Location: ' . $url, true, 302);
raw_output('');
throw new HttpTemporaryRedirect($url);
}
/**

View File

@ -409,42 +409,6 @@ function table_buttons($buttons = [])
return '<div class="btn-group">' . join(' ', $buttons) . '</div>';
}
/**
* @param string $str
* @param int $length
* @return string
*/
function shorten($str, $length = 50)
{
if (strlen($str) < $length) {
return $str;
}
return '<span title="' . htmlentities($str, ENT_COMPAT, 'UTF-8') . '">'
. substr($str, 0, $length - 3)
. '...</span>';
}
/**
* @param array[] $array
* @return string
*/
function table_body($array)
{
$html = '';
foreach ($array as $line) {
$html .= '<tr>';
if (is_array($line)) {
foreach ($line as $td) {
$html .= '<td>' . $td . '</td>';
}
} else {
$html .= '<td>' . $line . '</td>';
}
$html .= '</tr>';
}
return $html;
}
/**
* @param string $msg
* @return mixed

View File

@ -34,3 +34,36 @@ msgstr "Deine Passwörter stimmen nicht überein."
msgid "validation.password_confirmation.required"
msgstr "Du musst dein Passwort bestätigen."
msgid "schedule.import"
msgstr "Programm importieren"
msgid "schedule.import.request-error"
msgstr "Das Programm konnte nicht abgerufen werden."
msgid "schedule.import.read-error"
msgstr "Das Programm konnte nicht gelesen werden."
msgid "schedule.import.invalid-shift-type"
msgstr "Der Schichttyp konnte nicht gefunden werden."
msgid "schedule.import.success"
msgstr "Das Programm wurde erfolgreich importiert."
msgid "validation.schedule-url.required"
msgstr "Bitte gib eine Programm URL an."
msgid "validation.schedule-url.url"
msgstr "Die Programm URL muss eine URL sein."
msgid "validation.shift-type.required"
msgstr "Der Schichttyp ist erforderlich."
msgid "validation.shift-type.int"
msgstr "Der Schichttyp muss eine Zahl sein."
msgid "validation.minutes-before.int"
msgstr "Die Minuten vor dem Talk müssen eine Zahl sein."
msgid "validation.minutes-after.int"
msgstr "Die Minuten nach dem Talk müssen eine Zahl sein."

View File

@ -2806,3 +2806,60 @@ msgstr ""
#~ msgid "auth.no-nickname"
#~ msgstr "Gib bitte einen Nick an."
msgid "form.load_schedule"
msgstr "Programm laden"
msgid "form.import"
msgstr "Importieren"
msgid "schedule.import.title"
msgstr "Programm importieren"
msgid "schedule.import.text"
msgstr "Dieser Import erstellt Räume und erstellt, aktualisiert und löscht Schichten anhand des schedule.xml exportes."
msgid "schedule.import.load.title"
msgstr "Programm importieren: Vorschau"
msgid "schedule.import.load.info"
msgstr "Importiere \"%s\" (Version \"%s\")"
msgid "schedule.url"
msgstr "Programm URL (schedule.xml)"
msgid "schedule.shift-type"
msgstr "Schichttyp"
msgid "schedule.minutes-before"
msgstr "Minuten vor Talk beginn hinzufügen"
msgid "schedule.minutes-after"
msgstr "Minuten nach Talk ende hinzufügen"
msgid "schedule.import.rooms.add"
msgstr "Neue Räume"
msgid "schedule.import.shifts.add"
msgstr "Neue Schichten"
msgid "schedule.import.shifts.update"
msgstr "Zu aktualisierende Schichten"
msgid "schedule.import.shifts.delete"
msgstr "Zu löschende Schichten"
msgid "schedule.import.rooms.name"
msgstr "Name"
msgid "schedule.import.shift.dates"
msgstr "Zeit"
msgid "schedule.import.shift.type"
msgstr "Typ"
msgid "schedule.import.shift.title"
msgstr "Titel"
msgid "schedule.import.shift.room"
msgstr "Raum"

View File

@ -32,3 +32,36 @@ msgstr "Your passwords are not equal."
msgid "validation.password_confirmation.required"
msgstr "You have to confirm your password."
msgid "schedule.import"
msgstr "Import schedule"
msgid "schedule.import.request-error"
msgstr "The schedule could not be requested."
msgid "schedule.import.read-error"
msgstr "Unable to parse schedule."
msgid "schedule.import.invalid-shift-type"
msgstr "The shift type can't not be found."
msgid "schedule.import.success"
msgstr "Schedule import successful."
msgid "validation.schedule-url.required"
msgstr "The schedule URL is required."
msgid "validation.schedule-url.url"
msgstr "The schedule URL needs to be of type URL."
msgid "validation.shift-type.required"
msgstr "The shift type is required."
msgid "validation.shift-type.int"
msgstr "The shift type has to ba a number."
msgid "validation.minutes-before.int"
msgstr "The minutes before the talk have to be an integer."
msgid "validation.minutes-after.int"
msgstr "The minutes after the talk have to be an integer."

View File

@ -45,3 +45,63 @@ msgstr ""
"Please have a look at the "
"[contributors list on GitHub](https://github.com/engelsystem/engelsystem/graphs/contributors)"
" for a complete list."
msgid "form.load_schedule"
msgstr "Load schedule"
msgid "form.import"
msgstr "Import"
msgid "schedule.import.title"
msgstr "Import schedule"
msgid "schedule.import.text"
msgstr "This import creates rooms and creates, updates and deletes shifts according to the schedule.xml export."
msgid "schedule.import.load.title"
msgstr "Import schedule: Preview"
msgid "schedule.import.load.info"
msgstr "Import \"%s\" (version \"%s\")"
msgid "schedule.url"
msgstr "Schedule URL (schedule.xml)"
msgid "schedule.shift-type"
msgstr "Shift type"
msgid "schedule.minutes-before"
msgstr "Add minutes before talk begins"
msgid "schedule.minutes-after"
msgstr "Add minutes after talk ends"
msgid "schedule.import.request_error"
msgstr "Unable to load schedule."
msgid "schedule.import.rooms.add"
msgstr "Rooms to create"
msgid "schedule.import.shifts.add"
msgstr "Shifts to create"
msgid "schedule.import.shifts.update"
msgstr "Shifts to update"
msgid "schedule.import.shifts.delete"
msgstr "Shifts to delete"
msgid "schedule.import.rooms.name"
msgstr "Name"
msgid "schedule.import.shift.dates"
msgstr "Times"
msgid "schedule.import.shift.type"
msgstr "Type"
msgid "schedule.import.shift.title"
msgstr "Title"
msgid "schedule.import.shift.room"
msgstr "Room"

View File

@ -0,0 +1,41 @@
{% extends 'layouts/app.twig' %}
{% import 'macros/base.twig' as m %}
{% import 'macros/form.twig' as f %}
{% set title %}{% block title %}{{ __('schedule.import.title') }}{% endblock %}{% endset %}
{% block content %}
<div class="container">
<h1>{% block content_title %}{{ title }}{% endblock %}</h1>
{% for message in errors|default([]) %}
{{ m.alert(__(message), 'danger') }}
{% endfor %}
{% for message in success|default([]) %}
{{ m.alert(__(message), 'success') }}
{% endfor %}
<div class="row">
{% block row_content %}
<form method="POST" action="{{ url('/admin/schedule/load') }}">
{{ csrf() }}
<div class="col-md-12">
<p>{{ __('schedule.import.text') }}</p>
</div>
<div class="col-lg-6">
{{ f.input('schedule-url', __('schedule.url'), 'url', {'required': true}) }}
{{ f.select('shift-type', shift_types|default([]), __('schedule.shift-type')) }}
{{ f.input('minutes-before', __('schedule.minutes-before'), 'number', {'value': 15, 'required': true}) }}
{{ f.input('minutes-after', __('schedule.minutes-after'), 'number', {'value': 15, 'required': true}) }}
{{ f.submit(__('form.load_schedule')) }}
</div>
</form>
{% endblock %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,79 @@
{% extends 'admin/schedule/index.twig' %}
{% import 'macros/form.twig' as f %}
{% block title %}{{ __('schedule.import.load.title') }}{% endblock %}
{% block row_content %}
<form method="POST" action="{{ url('/admin/schedule/import') }}">
{{ csrf() }}
{{ f.hidden('schedule-url', schedule_url) }}
{{ f.hidden('shift-type', shift_type) }}
{{ f.hidden('minutes-before', minutes_before) }}
{{ f.hidden('minutes-after', minutes_after) }}
<div class="col-lg-12">
<p>{{ __('schedule.import.load.info', [schedule.conference.title, schedule.version]) }}</p>
<h2>{{ __('schedule.import.rooms.add') }}</h2>
{{ _self.roomsTable(rooms.add) }}
<h2>{{ __('schedule.import.shifts.add') }}</h2>
{{ _self.shiftsTable(shifts.add) }}
<h2>{{ __('schedule.import.shifts.update') }}</h2>
{{ _self.shiftsTable(shifts.update) }}
<h2>{{ __('schedule.import.shifts.delete') }}</h2>
{{ _self.shiftsTable(shifts.delete) }}
{{ f.submit(__('form.import')) }}
</div>
</form>
{% endblock %}
{% macro roomsTable(rooms) %}
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>{{ __('schedule.import.rooms.name') }}</th>
</tr>
</thead>
<tbody>
{% for room in rooms %}
<tr>
<td>{{ room.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endmacro %}
{% macro shiftsTable(shifts) %}
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>{{ __('schedule.import.shift.dates') }}</th>
<th>{{ __('schedule.import.shift.type') }}</th>
<th>{{ __('schedule.import.shift.title') }}</th>
<th>{{ __('schedule.import.shift.room') }}</th>
</tr>
</thead>
<tbody>
{% for shift in shifts %}
<tr>
<td>{{ shift.date.format(__('Y-m-d H:i')) }} - {{ shift.endDate.format(__('H:i')) }}</td>
<td>{{ shift.type }}</td>
<td>{{ shift.title }}{% if shift.subtitle %}<br><small>{{ shift.subtitle }}</small>{% endif %}</td>
<td>{{ shift.room.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endmacro %}

View File

@ -1,18 +1,35 @@
{% macro input(name, label, type, required) %}
{% macro input(name, label, type, opt) %}
<div class="form-group">
{% if label %}
{% if label -%}
<label for="{{ name }}">{{ label }}</label>
{% endif %}
<input type="{{ type|default('text') }}" class="form-control" id="{{ name }}" name="{{ name }}"
{%- if required|default(false) %} required="required"{% endif -%}
{%- endif %}
<input type="{{ type|default('text') }}" class="form-control"
id="{{ name }}" name="{{ name }}"
value="{{ opt.value|default('') }}"
{%- if opt.required|default(false) %}
required="required"
{%- endif -%}
>
</div>
{% endmacro %}
{%- endmacro %}
{% macro select(name, data, label, selected) %}
<div class="form-group">
{% if label -%}
<label for="{{ name }}">{{ label }}</label>
{% endif %}
<select id="{{ name }}" name="{{ name }}" class="form-control">
{% for value,decription in data -%}
<option value="{{ value }}" {% if name == selected %} selected{% endif %}>{{ decription }}</option>
{% endfor %}
</select>
</div>
{%- endmacro %}
{% macro hidden(name, value) %}
<input type="hidden" id="{{ name }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}
{%- endmacro %}
{% macro submit(label) %}
<button type="submit" class="btn btn-default">{{ label|default(__('form.submit')) }}</button>
{% endmacro %}
<button type="submit" class="btn btn-primary">{{ label|default(__('form.submit')) }}</button>
{%- endmacro %}

View File

@ -7,8 +7,8 @@
<form action="" enctype="multipart/form-data" method="post">
{{ csrf() }}
{{ f.input('password', __('Password'), 'password', true) }}
{{ f.input('password_confirmation', __('Confirm password'), 'password', true) }}
{{ f.input('password', __('Password'), 'password', {'required': true}) }}
{{ f.input('password_confirmation', __('Confirm password'), 'password', {'required': true}) }}
<div class="form-group">
{{ f.submit(__('Save')) }}

View File

@ -19,7 +19,7 @@
{{ csrf() }}
{{ __('We will send you an e-mail with a password recovery link. Please use the email address you used for registration.') }}
{{ f.input('email', __('E-Mail'), 'email', true) }}
{{ f.input('email', __('E-Mail'), 'email', {'required': true}) }}
<div class="form-group">
{{ f.submit(__('Recover')) }}

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Helpers\Schedule;
trait CalculatesTime
{
/**
* @param string $time
* @return int
*/
protected function secondsFromTime(string $time): int
{
$seconds = 0;
$duration = explode(':', $time);
foreach (array_slice($duration, 0, 2) as $key => $times) {
$seconds += [60 * 60, 60][$key] * $times;
}
return $seconds;
}
}

View File

@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Helpers\Schedule;
class Conference
{
use CalculatesTime;
/** @var string required */
protected $title;
/** @var string required */
protected $acronym;
/** @var string|null */
protected $start;
/** @var string|null */
protected $end;
/** @var int|null */
protected $days;
/** @var string|null */
protected $timeslotDuration;
/** @var string|null */
protected $baseUrl;
/**
* Event constructor.
*
* @param string $title
* @param string $acronym
* @param string|null $start
* @param string|null $end
* @param int|null $days
* @param string|null $timeslotDuration
* @param string|null $baseUrl
*/
public function __construct(
string $title,
string $acronym,
?string $start = null,
?string $end = null,
?int $days = null,
?string $timeslotDuration = null,
?string $baseUrl = null
) {
$this->title = $title;
$this->acronym = $acronym;
$this->start = $start;
$this->end = $end;
$this->days = $days;
$this->timeslotDuration = $timeslotDuration;
$this->baseUrl = $baseUrl;
}
/**
* @return string
*/
public function getTitle(): string
{
return $this->title;
}
/**
* @return string
*/
public function getAcronym(): string
{
return $this->acronym;
}
/**
* @return string|null
*/
public function getStart(): ?string
{
return $this->start;
}
/**
* @return string|null
*/
public function getEnd(): ?string
{
return $this->end;
}
/**
* @return int|null
*/
public function getDays(): ?int
{
return $this->days;
}
/**
* @return string|null
*/
public function getTimeslotDuration(): ?string
{
return $this->timeslotDuration;
}
/**
* @return int|null
*/
public function getTimeslotDurationSeconds(): ?int
{
$duration = $this->getTimeslotDuration();
if (!$duration) {
return null;
}
return $this->secondsFromTime($duration);
}
/**
* @return string|null
*/
public function getBaseUrl(): ?string
{
return $this->baseUrl;
}
}

View File

@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Helpers\Schedule;
use Carbon\Carbon;
class Day
{
/** @var string required */
protected $date;
/** @var Carbon required */
protected $start;
/** @var Carbon required */
protected $end;
/** @var int required */
protected $index;
/** @var Room[] */
protected $room;
/**
* Day constructor.
*
* @param string $date
* @param Carbon $start
* @param Carbon $end
* @param int $index
* @param Room[] $rooms
*/
public function __construct(
string $date,
Carbon $start,
Carbon $end,
int $index,
array $rooms = []
) {
$this->date = $date;
$this->start = $start;
$this->end = $end;
$this->index = $index;
$this->room = $rooms;
}
/**
* @return string
*/
public function getDate(): string
{
return $this->date;
}
/**
* @return Carbon
*/
public function getStart(): Carbon
{
return $this->start;
}
/**
* @return Carbon
*/
public function getEnd(): Carbon
{
return $this->end;
}
/**
* @return int
*/
public function getIndex(): int
{
return $this->index;
}
/**
* @return Room[]
*/
public function getRoom(): array
{
return $this->room;
}
}

View File

@ -0,0 +1,345 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Helpers\Schedule;
use Carbon\Carbon;
class Event
{
use CalculatesTime;
/** @var string required globally unique */
protected $guid;
/** @var int required globally unique */
protected $id;
/** @var Room required, string in XML */
protected $room;
/** @var string required */
protected $title;
/** @var string required */
protected $subtitle;
/** @var string required */
protected $type;
/** @var Carbon required */
protected $date;
/** @var string required time (hh:mm:ss || hh:mm) */
protected $start;
/** @var string required (h?h:mm:ss || h?h:mm) */
protected $duration;
/** @var string required */
protected $abstract;
/** @var string required globally unique */
protected $slug;
/** @var string required */
protected $track;
/** @var string|null */
protected $logo;
/** @var string[] id => name */
protected $persons;
/** @var string|null two letter code */
protected $language;
/** @var string|null */
protected $description;
/** @var string|null license (and opt out in XML, null if not recorded, empty if no license defined) */
protected $recording;
/** @var array href => title */
protected $links;
/** @var array href => name */
protected $attachments;
/** @var string|null */
protected $url;
/** @var string|null */
protected $videoDownloadUrl;
/** @var Carbon Calculated */
protected $endDate;
/**
* Event constructor.
*
* @param string $guid
* @param int $id
* @param Room $room
* @param string $title
* @param string $subtitle
* @param string $type
* @param Carbon $date
* @param string $start
* @param string $duration
* @param string $abstract
* @param string $slug
* @param string $track
* @param string|null $logo
* @param string[] $persons
* @param string|null $language
* @param string|null $description
* @param string|null $recording license
* @param array $links
* @param array $attachments
* @param string|null $url
* @param string|null $videoDownloadUrl
*/
public function __construct(
string $guid,
int $id,
Room $room,
string $title,
string $subtitle,
string $type,
Carbon $date,
string $start,
string $duration,
string $abstract,
string $slug,
string $track,
?string $logo = null,
array $persons = [],
?string $language = null,
?string $description = null,
string $recording = '',
array $links = [],
array $attachments = [],
?string $url = null,
?string $videoDownloadUrl = null
) {
$this->guid = $guid;
$this->id = $id;
$this->room = $room;
$this->title = $title;
$this->subtitle = $subtitle;
$this->type = $type;
$this->date = $date;
$this->start = $start;
$this->duration = $duration;
$this->abstract = $abstract;
$this->slug = $slug;
$this->track = $track;
$this->logo = $logo;
$this->persons = $persons;
$this->language = $language;
$this->description = $description;
$this->recording = $recording;
$this->links = $links;
$this->attachments = $attachments;
$this->url = $url;
$this->videoDownloadUrl = $videoDownloadUrl;
$this->endDate = $this->date
->copy()
->addSeconds($this->getDurationSeconds());
}
/**
* @return string
*/
public function getGuid(): string
{
return $this->guid;
}
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @return Room
*/
public function getRoom(): Room
{
return $this->room;
}
/**
* @return string
*/
public function getTitle(): string
{
return $this->title;
}
/**
* @param string $title
*/
public function setTitle(string $title): void
{
$this->title = $title;
}
/**
* @return string
*/
public function getSubtitle(): string
{
return $this->subtitle;
}
/**
* @return string
*/
public function getType(): string
{
return $this->type;
}
/**
* @return Carbon
*/
public function getDate(): Carbon
{
return $this->date;
}
/**
* @return string
*/
public function getStart(): string
{
return $this->start;
}
/**
* @return string
*/
public function getDuration(): string
{
return $this->duration;
}
/**
* @return int
*/
public function getDurationSeconds(): int
{
return $this->secondsFromTime($this->duration);
}
/**
* @return string
*/
public function getAbstract(): string
{
return $this->abstract;
}
/**
* @return string
*/
public function getSlug(): string
{
return $this->slug;
}
/**
* @return string
*/
public function getTrack(): string
{
return $this->track;
}
/**
* @return string|null
*/
public function getLogo(): ?string
{
return $this->logo;
}
/**
* @return string[]
*/
public function getPersons(): array
{
return $this->persons;
}
/**
* @return string|null
*/
public function getLanguage(): ?string
{
return $this->language;
}
/**
* @return string|null
*/
public function getDescription(): ?string
{
return $this->description;
}
/**
* @return string|null
*/
public function getRecording(): ?string
{
return $this->recording;
}
/**
* @return array
*/
public function getLinks(): array
{
return $this->links;
}
/**
* @return array
*/
public function getAttachments(): array
{
return $this->attachments;
}
/**
* @return string|null
*/
public function getUrl(): ?string
{
return $this->url;
}
/**
* @return string|null
*/
public function getVideoDownloadUrl(): ?string
{
return $this->videoDownloadUrl;
}
/**
* @return Carbon
*/
public function getEndDate(): Carbon
{
return $this->endDate;
}
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Helpers\Schedule;
class Room
{
/** @var string required */
protected $name;
/** @var Event[] */
protected $event;
/**
* Room constructor.
*
* @param string $name
* @param Event[] $events
*/
public function __construct(
string $name,
array $events = []
) {
$this->name = $name;
$this->event = $events;
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @return Event[]
*/
public function getEvent(): array
{
return $this->event;
}
/**
* @param Event[] $event
*/
public function setEvent(array $event): void
{
$this->event = $event;
}
}

View File

@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Helpers\Schedule;
use Carbon\Carbon;
class Schedule
{
/** @var string */
protected $version;
/** @var Conference */
protected $conference;
/** @var Day[] */
protected $day;
/**
* @param string $version
* @param Conference $conference
* @param Day[] $days
*/
public function __construct(
string $version,
Conference $conference,
array $days
) {
$this->version = $version;
$this->conference = $conference;
$this->day = $days;
}
/**
* @return string
*/
public function getVersion(): string
{
return $this->version;
}
/**
* @return Conference
*/
public function getConference(): Conference
{
return $this->conference;
}
/**
* @return Day[]
*/
public function getDay(): array
{
return $this->day;
}
/**
* @return Room[]
*/
public function getRooms(): array
{
$rooms = [];
foreach ($this->day as $day) {
foreach ($day->getRoom() as $room) {
$name = $room->getName();
$rooms[$name] = $room;
}
}
return $rooms;
}
/**
* @return Carbon|null
*/
public function getStartDateTime(): ?Carbon
{
$start = null;
foreach ($this->day as $day) {
$time = $day->getStart();
if ($time > $start && $start) {
continue;
}
$start = $time;
}
return $start;
}
/**
* @return Carbon|null
*/
public function getEndDateTime(): ?Carbon
{
$end = null;
foreach ($this->day as $day) {
$time = $day->getEnd();
if ($time < $end && $end) {
continue;
}
$end = $time;
}
return $end;
}
}

View File

@ -0,0 +1,172 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Helpers\Schedule;
use Carbon\Carbon;
use SimpleXMLElement;
class XmlParser
{
/** @var SimpleXMLElement */
protected $scheduleXML;
/** @var Schedule */
protected $schedule;
/**
* @param string $xml
* @return bool
*/
public function load(string $xml): bool
{
$this->scheduleXML = simplexml_load_string($xml);
if (!$this->scheduleXML) {
return false;
}
$this->parseXml();
return true;
}
/**
* Parse the predefined XML content
*/
protected function parseXml(): void
{
$version = $this->getFirstXpathContent('version');
$conference = new Conference(
$this->getFirstXpathContent('conference/title'),
$this->getFirstXpathContent('conference/acronym'),
$this->getFirstXpathContent('conference/start'),
$this->getFirstXpathContent('conference/end'),
(int)$this->getFirstXpathContent('conference/days'),
$this->getFirstXpathContent('conference/timeslot_duration'),
$this->getFirstXpathContent('conference/base_url')
);
$days = [];
foreach ($this->scheduleXML->xpath('day') as $day) {
$rooms = [];
foreach ($day->xpath('room') as $roomElement) {
$room = new Room(
(string)$roomElement->attributes()['name']
);
$events = $this->parseEvents($roomElement->xpath('event'), $room);
$room->setEvent($events);
$rooms[] = $room;
}
$days[] = new Day(
(string)$day->attributes()['date'],
new Carbon($day->attributes()['start']),
new Carbon($day->attributes()['end']),
(int)$day->attributes()['index'],
$rooms
);
}
$this->schedule = new Schedule(
$version,
$conference,
$days
);
}
/**
* @param SimpleXMLElement[] $eventElements
* @param Room $room
* @return array
*/
protected function parseEvents(array $eventElements, Room $room): array
{
$events = [];
foreach ($eventElements as $event) {
$persons = $this->getListFromSequence($event, 'persons', 'person', 'id');
$links = $this->getListFromSequence($event, 'links', 'link', 'href');
$attachments = $this->getListFromSequence($event, 'attachments', 'attachment', 'href');
$recording = '';
$recordingElement = $event->xpath('recording')[0];
if ($this->getFirstXpathContent('optout', $recordingElement) == 'false') {
$recording = $this->getFirstXpathContent('license', $recordingElement);
}
$events[] = new Event(
(string)$event->attributes()['guid'],
(int)$event->attributes()['id'],
$room,
$this->getFirstXpathContent('title', $event),
$this->getFirstXpathContent('subtitle', $event),
$this->getFirstXpathContent('type', $event),
new Carbon($this->getFirstXpathContent('date', $event)),
$this->getFirstXpathContent('start', $event),
$this->getFirstXpathContent('duration', $event),
$this->getFirstXpathContent('abstract', $event),
$this->getFirstXpathContent('slug', $event),
$this->getFirstXpathContent('track', $event),
$this->getFirstXpathContent('logo', $event) ?: null,
$persons,
$this->getFirstXpathContent('language', $event) ?: null,
$this->getFirstXpathContent('description', $event) ?: null,
$recording,
$links,
$attachments,
$this->getFirstXpathContent('url', $event) ?: null,
$this->getFirstXpathContent('video_download_url', $event) ?: null
);
}
return $events;
}
/**
* @param string $path
* @param SimpleXMLElement|null $xml
* @return string
*/
protected function getFirstXpathContent(string $path, ?SimpleXMLElement $xml = null): string
{
$element = ($xml ?: $this->scheduleXML)->xpath($path);
return $element ? (string)$element[0] : '';
}
/**
* Resolves a list from a sequence of elements
*
* @param SimpleXMLElement $element
* @param string $firstElement
* @param string $secondElement
* @param string $idAttribute
* @return array
*/
protected function getListFromSequence(
SimpleXMLElement $element,
string $firstElement,
string $secondElement,
string $idAttribute
): array {
$items = [];
foreach ($element->xpath($firstElement)[0]->xpath($secondElement) as $item) {
$items[(string)$item->attributes()[$idAttribute]] = (string)$item;
}
return $items;
}
/**
* @return Schedule
*/
public function getSchedule(): Schedule
{
return $this->schedule;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Engelsystem\Http;
use Engelsystem\Container\ServiceProvider;
use GuzzleHttp\Client as GuzzleClient;
class HttpClientServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->when(GuzzleClient::class)
->needs('$config')
->give(
function () {
return [
// No exception on >= 400 responses
'http_errors' => false,
// Wait max n seconds for a response
'timeout' => 2.0,
];
}
);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Engelsystem\Http;
use Engelsystem\Container\ServiceProvider;
class RedirectServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('redirect', Redirector::class);
}
}

55
src/Http/Redirector.php Normal file
View File

@ -0,0 +1,55 @@
<?php
namespace Engelsystem\Http;
class Redirector
{
/** @var Request */
protected $request;
/** @var Response */
protected $response;
/**
* @param Request $request
* @param Response $response
*/
public function __construct(Request $request, Response $response)
{
$this->request = $request;
$this->response = $response;
}
/**
* @param string $path
* @param int $status
* @param array $headers
* @return Response
*/
public function to(string $path, int $status = 302, array $headers = []): Response
{
return $this->response->redirectTo($path, $status, $headers);
}
/**
* @param int $status
* @param array $headers
* @return Response
*/
public function back(int $status = 302, array $headers = []): Response
{
return $this->to($this->getPreviousUrl(), $status, $headers);
}
/**
* @return string
*/
protected function getPreviousUrl(): string
{
if ($header = $this->request->getHeader('referer')) {
return array_pop($header);
}
return '/';
}
}

View File

@ -6,27 +6,37 @@ use Engelsystem\Renderer\Renderer;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class Response extends SymfonyResponse implements ResponseInterface
{
use MessageTrait;
/**
* @var SessionInterface
*/
protected $session;
/** @var Renderer */
protected $renderer;
/**
* @param string $content
* @param int $status
* @param array $headers
* @param Renderer $renderer
* @param string $content
* @param int $status
* @param array $headers
* @param Renderer $renderer
* @param SessionInterface $session
*/
public function __construct(
$content = '',
int $status = 200,
array $headers = [],
Renderer $renderer = null
Renderer $renderer = null,
SessionInterface $session = null
) {
$this->renderer = $renderer;
$this->session = $session;
parent::__construct($content, $status, $headers);
}
@ -155,4 +165,44 @@ class Response extends SymfonyResponse implements ResponseInterface
{
$this->renderer = $renderer;
}
/**
* Sets a session attribute (which is mutable)
*
* @param string $key
* @param mixed|mixed[] $value
* @return Response
*/
public function with(string $key, $value)
{
if (!$this->session instanceof SessionInterface) {
throw new InvalidArgumentException('Session not defined');
}
$data = $this->session->get($key);
if (is_array($data) && is_array($value)) {
$value = array_merge_recursive($data, $value);
}
$this->session->set($key, $value);
return $this;
}
/**
* Sets form data to the mutable session
*
* @param array $input
* @return Response
*/
public function withInput(array $input)
{
if (!$this->session instanceof SessionInterface) {
throw new InvalidArgumentException('Session not defined');
}
$this->session->set('form-data', $input);
return $this;
}
}

View File

@ -63,17 +63,11 @@ class ErrorHandler implements MiddlewareInterface
} catch (HttpException $e) {
$response = $this->createResponse($e->getMessage(), $e->getStatusCode(), $e->getHeaders());
} catch (ValidationException $e) {
$response = $this->createResponse('', 302, ['Location' => $this->getPreviousUrl($request)]);
$response = $this->redirectBack();
$response->with('errors', ['validation' => $e->getValidator()->getErrors()]);
if ($request instanceof Request) {
$session = $request->getSession();
$errors = array_merge_recursive(
$session->get('errors', []),
['validation' => $e->getValidator()->getErrors()]
);
$session->set('errors', $errors);
$session->set('form-data', Arr::except($request->request->all(), $this->formIgnore));
$response->withInput(Arr::except($request->request->all(), $this->formIgnore));
}
}
@ -140,15 +134,12 @@ class ErrorHandler implements MiddlewareInterface
}
/**
* @param ServerRequestInterface $request
* @return string
* Create a redirect back response
*
* @return Response
*/
protected function getPreviousUrl(ServerRequestInterface $request)
protected function redirectBack()
{
if ($header = $request->getHeader('referer')) {
return array_pop($header);
}
return '/';
return back();
}
}

View File

@ -205,10 +205,6 @@ class LegacyMiddleware implements MiddlewareInterface
$title = admin_groups_title();
$content = admin_groups();
return [$title, $content];
case 'admin_import':
$title = admin_import_title();
$content = admin_import();
return [$title, $content];
case 'admin_shifts':
$title = admin_shifts_title();
$content = admin_shifts();
@ -219,7 +215,7 @@ class LegacyMiddleware implements MiddlewareInterface
return [$title, $content];
}
redirect(page_link_to('login'));
throw_redirect(page_link_to('login'));
return [];
}
@ -239,9 +235,15 @@ class LegacyMiddleware implements MiddlewareInterface
return response($content, (int)$page);
}
return response(view('layouts/app', [
'title' => $title,
'content' => msg() . $content,
]), 200);
return response(
view(
'layouts/app',
[
'title' => $title,
'content' => msg() . $content,
]
),
200
);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Engelsystem\Models\Shifts;
use Engelsystem\Models\BaseModel;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Query\Builder as QueryBuilder;
/**
* @property int $id
* @property string $url
*
* @property-read QueryBuilder|Collection|ScheduleShift[] $scheduleShifts
*
* @method static QueryBuilder|Schedule[] whereId($value)
* @method static QueryBuilder|Schedule[] whereUrl($value)
*/
class Schedule extends BaseModel
{
/** @var array Values that are mass assignable */
protected $fillable = ['url'];
/**
* @return HasMany
*/
public function scheduleShifts()
{
return $this->hasMany(ScheduleShift::class);
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Engelsystem\Models\Shifts;
use Engelsystem\Models\BaseModel;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Query\Builder as QueryBuilder;
/**
* @property int $shift_id
* @property int $schedule_id
* @property string $guid
*
* @property-read QueryBuilder|Schedule $schedule
*
* @method static QueryBuilder|ScheduleShift[] whereShiftId($value)
* @method static QueryBuilder|ScheduleShift[] whereScheduleId($value)
* @method static QueryBuilder|ScheduleShift[] whereGuid($value)
*/
class ScheduleShift extends BaseModel
{
/** @var string The primary key for the model */
protected $primaryKey = 'shift_id';
/** @var string Required because it is not schedule_shifts */
protected $table = 'schedule_shift';
/** @var array Values that are mass assignable */
protected $fillable = ['shift_id', 'schedule_id', 'guid'];
/**
* @return BelongsTo
*/
public function schedule()
{
return $this->belongsTo(Schedule::class);
}
}

View File

@ -4,6 +4,7 @@ use Engelsystem\Application;
use Engelsystem\Config\Config;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Helpers\Translation\Translator;
use Engelsystem\Http\Redirector;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Http\UrlGeneratorInterface;
@ -28,7 +29,7 @@ function app($id = null)
/**
* @return Authenticator
*/
function auth()
function auth(): Authenticator
{
return app('authenticator');
}
@ -37,11 +38,24 @@ function auth()
* @param string $path
* @return string
*/
function base_path($path = '')
function base_path($path = ''): string
{
return app('path') . (empty($path) ? '' : DIRECTORY_SEPARATOR . $path);
}
/**
* @param int $status
* @param array $headers
* @return Response
*/
function back($status = 302, $headers = []): Response
{
/** @var Redirector $redirect */
$redirect = app('redirect');
return $redirect->back($status, $headers);
}
/**
* Get or set config values
*
@ -70,11 +84,25 @@ function config($key = null, $default = null)
* @param string $path
* @return string
*/
function config_path($path = '')
function config_path($path = ''): string
{
return app('path.config') . (empty($path) ? '' : DIRECTORY_SEPARATOR . $path);
}
/**
* @param string $path
* @param int $status
* @param array $headers
* @return Response
*/
function redirect(string $path, $status = 302, $headers = []): Response
{
/** @var Redirector $redirect */
$redirect = app('redirect');
return $redirect->to($path, $status, $headers);
}
/**
* @param string $key
* @param mixed $default
@ -97,7 +125,7 @@ function request($key = null, $default = null)
* @param array $headers
* @return Response
*/
function response($content = '', $status = 200, $headers = [])
function response($content = '', $status = 200, $headers = []): Response
{
/** @var Response $response */
$response = app('psr7.response');
@ -155,7 +183,7 @@ function trans($key = null, $replace = [])
* @param array $replace
* @return string
*/
function __($key, $replace = [])
function __($key, $replace = []): string
{
/** @var Translator $translator */
$translator = app('translator');
@ -172,7 +200,7 @@ function __($key, $replace = [])
* @param array $replace
* @return string
*/
function _e($key, $keyPlural, $number, $replace = [])
function _e($key, $keyPlural, $number, $replace = []): string
{
/** @var Translator $translator */
$translator = app('translator');

View File

@ -13,7 +13,7 @@ class RoomModelTest extends TestCase
*/
public function createRoom()
{
$this->room_id = Room_create('test', false, null, null);
$this->room_id = Room_create('test', null, null);
}
/**

View File

@ -36,14 +36,17 @@ trait HasDatabase
$this->database
->getConnection()
->table('migrations')
->insert([
['migration' => '2018_01_01_000001_import_install_sql'],
['migration' => '2018_01_01_000002_import_update_sql'],
['migration' => '2018_01_01_000003_fix_old_tables'],
['migration' => '2018_01_01_000004_cleanup_group_privileges'],
['migration' => '2018_01_01_000005_add_angel_supporter_permissions'],
['migration' => '2018_12_27_000000_fix_missing_arrival_dates'],
]);
->insert(
[
['migration' => '2018_01_01_000001_import_install_sql'],
['migration' => '2018_01_01_000002_import_update_sql'],
['migration' => '2018_01_01_000003_fix_old_tables'],
['migration' => '2018_01_01_000004_cleanup_group_privileges'],
['migration' => '2018_01_01_000005_add_angel_supporter_permissions'],
['migration' => '2018_12_27_000000_fix_missing_arrival_dates'],
['migration' => '2019_09_07_000000_migrate_admin_schedule_permissions'],
]
);
$migration->run(__DIR__ . '/../../db/migrations');
}

View File

@ -0,0 +1,46 @@
<?xml version='1.0' encoding='utf-8' ?>
<schedule>
<version>Some version string</version>
<conference>
<title>Test Event</title>
<acronym>Test1</acronym>
<start>2042-01-01</start>
<end>2042-01-01</end>
<days>1</days>
<timeslot_duration>00:15</timeslot_duration>
<base_url>https://foo.bar/baz/schedule/</base_url>
</conference>
<day index='1' date='2042-01-01' start='2042-01-01T01:00:00+02:00' end='2042-01-01T22:59:00+02:00'>
<room name='Rooming'>
<event guid='e427cfa9-9ba1-4b14-a99f-bce83ffe5a1c' id='1337'>
<date>2042-01-01T12:30:00+02:00</date>
<title>Foo Bar Test</title>
<subtitle>Some sub</subtitle>
<start>12:30</start>
<duration>00:30</duration>
<room>Rooming</room>
<slug>foo-bar-test</slug>
<recording>
<license>WTFPL</license>
<optout>false</optout>
</recording>
<track>Testing</track>
<type>Talk</type>
<language>de</language>
<abstract>Foo bar is da best</abstract>
<description>Any describing stuff?</description>
<url>https://foo.bar/baz/schedule/ipsum</url>
<logo>https://lorem.ipsum/foo/bar.png</logo>
<persons>
<person id='1234'>Some Person</person>
</persons>
<links>
<link href="https://foo.bar">Some Foo Bar</link>
</links>
<attachments>
<attachment href="https://foo.bar/stuff.pdf">A PDF File</attachment>
</attachments>
</event>
</room>
</day>
</schedule>

View File

@ -0,0 +1,33 @@
<?php
namespace Engelsystem\Test\Unit\Helpers\Schedule;
use Engelsystem\Helpers\Schedule\CalculatesTime;
use Engelsystem\Test\Unit\TestCase;
class CalculatesTimeTest extends TestCase
{
/**
* @covers \Engelsystem\Helpers\Schedule\CalculatesTime::secondsFromTime
*/
public function testSecondsFromTime()
{
$calc = new class {
use CalculatesTime;
/**
* @param string $time
* @return int
*/
public function calc(string $time): int
{
return $this->secondsFromTime($time);
}
};
$this->assertEquals(0, $calc->calc('0:00'));
$this->assertEquals(60, $calc->calc('0:01'));
$this->assertEquals(60 * 60, $calc->calc('01:00'));
$this->assertEquals(60 * 60 * 10 + 60 * 11, $calc->calc('10:11'));
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Engelsystem\Test\Unit\Helpers\Schedule;
use Engelsystem\Helpers\Schedule\Conference;
use Engelsystem\Test\Unit\TestCase;
class ConferenceTest extends TestCase
{
/**
* @covers \Engelsystem\Helpers\Schedule\Conference::__construct
* @covers \Engelsystem\Helpers\Schedule\Conference::getTitle
* @covers \Engelsystem\Helpers\Schedule\Conference::getAcronym
* @covers \Engelsystem\Helpers\Schedule\Conference::getStart
* @covers \Engelsystem\Helpers\Schedule\Conference::getEnd
* @covers \Engelsystem\Helpers\Schedule\Conference::getDays
* @covers \Engelsystem\Helpers\Schedule\Conference::getTimeslotDuration
* @covers \Engelsystem\Helpers\Schedule\Conference::getTimeslotDurationSeconds
* @covers \Engelsystem\Helpers\Schedule\Conference::getBaseUrl
*/
public function testCreate()
{
$conference = new Conference('Doing stuff', 'DS');
$this->assertEquals('Doing stuff', $conference->getTitle());
$this->assertEquals('DS', $conference->getAcronym());
$this->assertNull($conference->getStart());
$this->assertNull($conference->getEnd());
$this->assertNull($conference->getDays());
$this->assertNull($conference->getTimeslotDuration());
$this->assertNull($conference->getTimeslotDurationSeconds());
$this->assertNull($conference->getBaseUrl());
$conference = new Conference(
'Doing stuff',
'DS',
'2042-01-01',
'2042-01-10',
10,
'00:10',
'https://foo.bar/schedule'
);
$this->assertEquals('2042-01-01', $conference->getStart());
$this->assertEquals('2042-01-10', $conference->getEnd());
$this->assertEquals(10, $conference->getDays());
$this->assertEquals('00:10', $conference->getTimeslotDuration());
$this->assertEquals(60 * 10, $conference->getTimeslotDurationSeconds());
$this->assertEquals('https://foo.bar/schedule', $conference->getBaseUrl());
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Engelsystem\Test\Unit\Helpers\Schedule;
use Carbon\Carbon;
use Engelsystem\Helpers\Schedule\Day;
use Engelsystem\Helpers\Schedule\Room;
use Engelsystem\Test\Unit\TestCase;
class DayTest extends TestCase
{
/**
* @covers \Engelsystem\Helpers\Schedule\Day::__construct
* @covers \Engelsystem\Helpers\Schedule\Day::getDate
* @covers \Engelsystem\Helpers\Schedule\Day::getStart
* @covers \Engelsystem\Helpers\Schedule\Day::getEnd
* @covers \Engelsystem\Helpers\Schedule\Day::getIndex
* @covers \Engelsystem\Helpers\Schedule\Day::getRoom
*/
public function testCreate()
{
$day = new Day(
'2000-01-01',
new Carbon('2000-01-01T03:00:00+01:00'),
new Carbon('2000-01-02T05:59:00+00:00'),
1
);
$this->assertEquals('2000-01-01', $day->getDate());
$this->assertEquals('2000-01-01T03:00:00+01:00', $day->getStart()->format(Carbon::RFC3339));
$this->assertEquals('2000-01-02T05:59:00+00:00', $day->getEnd()->format(Carbon::RFC3339));
$this->assertEquals(1, $day->getIndex());
$this->assertEquals([], $day->getRoom());
$rooms = [
new Room('Foo'),
new Room('Bar'),
];
$day = new Day(
'2001-01-01',
new Carbon('2001-01-01T03:00:00+01:00'),
new Carbon('2001-01-02T05:59:00+00:00'),
1,
$rooms
);
$this->assertEquals($rooms, $day->getRoom());
}
}

View File

@ -0,0 +1,149 @@
<?php
namespace Engelsystem\Test\Unit\Helpers\Schedule;
use Carbon\Carbon;
use Engelsystem\Helpers\Schedule\Event;
use Engelsystem\Helpers\Schedule\Room;
use Engelsystem\Test\Unit\TestCase;
class EventTest extends TestCase
{
/**
* @covers \Engelsystem\Helpers\Schedule\Event::__construct
* @covers \Engelsystem\Helpers\Schedule\Event::getGuid
* @covers \Engelsystem\Helpers\Schedule\Event::getId
* @covers \Engelsystem\Helpers\Schedule\Event::getRoom
* @covers \Engelsystem\Helpers\Schedule\Event::getTitle
* @covers \Engelsystem\Helpers\Schedule\Event::getSubtitle
* @covers \Engelsystem\Helpers\Schedule\Event::getType
* @covers \Engelsystem\Helpers\Schedule\Event::getDate
* @covers \Engelsystem\Helpers\Schedule\Event::getStart
* @covers \Engelsystem\Helpers\Schedule\Event::getDuration
* @covers \Engelsystem\Helpers\Schedule\Event::getDurationSeconds
* @covers \Engelsystem\Helpers\Schedule\Event::getAbstract
* @covers \Engelsystem\Helpers\Schedule\Event::getSlug
* @covers \Engelsystem\Helpers\Schedule\Event::getTrack
* @covers \Engelsystem\Helpers\Schedule\Event::getLogo
* @covers \Engelsystem\Helpers\Schedule\Event::getPersons
* @covers \Engelsystem\Helpers\Schedule\Event::getLanguage
* @covers \Engelsystem\Helpers\Schedule\Event::getDescription
* @covers \Engelsystem\Helpers\Schedule\Event::getRecording
* @covers \Engelsystem\Helpers\Schedule\Event::getLinks
* @covers \Engelsystem\Helpers\Schedule\Event::getAttachments
* @covers \Engelsystem\Helpers\Schedule\Event::getUrl
* @covers \Engelsystem\Helpers\Schedule\Event::getVideoDownloadUrl
* @covers \Engelsystem\Helpers\Schedule\Event::getEndDate
*/
public function testCreate()
{
$room = new Room('Foo');
$date = new Carbon('2020-12-28T19:30:00+00:00');
$event = new Event(
'0-1-2-3',
1,
$room,
'Some stuff',
'sub stuff',
'Talk',
$date,
'19:30:00',
'00:50',
'Doing stuff is hard, plz try again',
'1-some-stuff',
'Security'
);
$this->assertEquals('0-1-2-3', $event->getGuid());
$this->assertEquals(1, $event->getId());
$this->assertEquals($room, $event->getRoom());
$this->assertEquals('Some stuff', $event->getTitle());
$this->assertEquals('sub stuff', $event->getSubtitle());
$this->assertEquals('Talk', $event->getType());
$this->assertEquals($date, $event->getDate());
$this->assertEquals('19:30:00', $event->getStart());
$this->assertEquals('00:50', $event->getDuration());
$this->assertEquals('Doing stuff is hard, plz try again', $event->getAbstract());
$this->assertEquals('1-some-stuff', $event->getSlug());
$this->assertEquals('Security', $event->getTrack());
$this->assertNull($event->getLogo());
$this->assertEquals([], $event->getPersons());
$this->assertNull($event->getLanguage());
$this->assertNull($event->getDescription());
$this->assertEquals('', $event->getRecording());
$this->assertEquals([], $event->getLinks());
$this->assertEquals([], $event->getAttachments());
$this->assertNull($event->getUrl());
$this->assertNull($event->getVideoDownloadUrl());
$this->assertEquals('2020-12-28T20:20:00+00:00', $event->getEndDate()->format(Carbon::RFC3339));
}
/**
* @covers \Engelsystem\Helpers\Schedule\Event::__construct
* @covers \Engelsystem\Helpers\Schedule\Event::getGuid
* @covers \Engelsystem\Helpers\Schedule\Event::getId
* @covers \Engelsystem\Helpers\Schedule\Event::getRoom
* @covers \Engelsystem\Helpers\Schedule\Event::getTitle
* @covers \Engelsystem\Helpers\Schedule\Event::setTitle
* @covers \Engelsystem\Helpers\Schedule\Event::getSubtitle
* @covers \Engelsystem\Helpers\Schedule\Event::getType
* @covers \Engelsystem\Helpers\Schedule\Event::getDate
* @covers \Engelsystem\Helpers\Schedule\Event::getStart
* @covers \Engelsystem\Helpers\Schedule\Event::getDuration
* @covers \Engelsystem\Helpers\Schedule\Event::getDurationSeconds
* @covers \Engelsystem\Helpers\Schedule\Event::getAbstract
* @covers \Engelsystem\Helpers\Schedule\Event::getSlug
* @covers \Engelsystem\Helpers\Schedule\Event::getTrack
* @covers \Engelsystem\Helpers\Schedule\Event::getLogo
* @covers \Engelsystem\Helpers\Schedule\Event::getPersons
* @covers \Engelsystem\Helpers\Schedule\Event::getLanguage
* @covers \Engelsystem\Helpers\Schedule\Event::getDescription
* @covers \Engelsystem\Helpers\Schedule\Event::getRecording
* @covers \Engelsystem\Helpers\Schedule\Event::getLinks
* @covers \Engelsystem\Helpers\Schedule\Event::getAttachments
* @covers \Engelsystem\Helpers\Schedule\Event::getUrl
* @covers \Engelsystem\Helpers\Schedule\Event::getVideoDownloadUrl
*/
public function testCreateNotDefault()
{
$persons = [1337 => 'Some Person'];
$links = ['https://foo.bar' => 'Foo Bar'];
$attachments = ['/files/foo.pdf' => 'Suspicious PDF'];
$event = new Event(
'3-2-1-0',
2,
new Room('Bar'),
'Lorem',
'Ipsum',
'Workshop',
new Carbon('2021-01-01T00:00:00+00:00'),
'00:00:00',
'00:30',
'Lorem ipsum dolor sit amet',
'2-lorem',
'DevOps',
'/foo/bar.png',
$persons,
'de',
'Foo bar is awesome! & That\'s why...',
'CC BY SA',
$links,
$attachments,
'https://foo.bar/2-lorem',
'https://videos.orem.ipsum/2-lorem.mp4'
);
$this->assertEquals('/foo/bar.png', $event->getLogo());
$this->assertEquals($persons, $event->getPersons());
$this->assertEquals('de', $event->getLanguage());
$this->assertEquals('Foo bar is awesome! & That\'s why...', $event->getDescription());
$this->assertEquals('CC BY SA', $event->getRecording());
$this->assertEquals($links, $event->getLinks());
$this->assertEquals($attachments, $event->getAttachments());
$this->assertEquals('https://foo.bar/2-lorem', $event->getUrl());
$this->assertEquals('https://videos.orem.ipsum/2-lorem.mp4', $event->getVideoDownloadUrl());
$event->setTitle('Event title');
$this->assertEquals('Event title', $event->getTitle());
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Engelsystem\Test\Unit\Helpers\Schedule;
use Engelsystem\Helpers\Schedule\Event;
use Engelsystem\Helpers\Schedule\Room;
use Engelsystem\Test\Unit\TestCase;
class RoomTest extends TestCase
{
/**
* @covers \Engelsystem\Helpers\Schedule\Room::__construct
* @covers \Engelsystem\Helpers\Schedule\Room::getName
* @covers \Engelsystem\Helpers\Schedule\Room::getEvent
* @covers \Engelsystem\Helpers\Schedule\Room::setEvent
*/
public function testCreate()
{
$room = new Room('Test');
$this->assertEquals('Test', $room->getName());
$this->assertEquals([], $room->getEvent());
$events = [$this->createMock(Event::class), $this->createMock(Event::class)];
$events2 = [$this->createMock(Event::class)];
$room = new Room('Test2', $events);
$this->assertEquals($events, $room->getEvent());
$room->setEvent($events2);
$this->assertEquals($events2, $room->getEvent());
}
}

View File

@ -0,0 +1,112 @@
<?php
namespace Engelsystem\Test\Unit\Helpers\Schedule;
use Carbon\Carbon;
use Engelsystem\Helpers\Schedule\Conference;
use Engelsystem\Helpers\Schedule\Day;
use Engelsystem\Helpers\Schedule\Room;
use Engelsystem\Helpers\Schedule\Schedule;
use Engelsystem\Test\Unit\HasDatabase;
use Engelsystem\Test\Unit\TestCase;
class ScheduleTest extends TestCase
{
use HasDatabase;
/**
* @covers \Engelsystem\Helpers\Schedule\Schedule::__construct
* @covers \Engelsystem\Helpers\Schedule\Schedule::getVersion
* @covers \Engelsystem\Helpers\Schedule\Schedule::getConference
* @covers \Engelsystem\Helpers\Schedule\Schedule::getDay
*/
public function testCreate()
{
$conference = new Conference('Foo Bar', 'FooB');
$days = [$this->createMock(Day::class)];
$schedule = new Schedule('Foo\'ing stuff 1.0', $conference, $days);
$this->assertEquals('Foo\'ing stuff 1.0', $schedule->getVersion());
$this->assertEquals($conference, $schedule->getConference());
$this->assertEquals($days, $schedule->getDay());
}
/**
* @covers \Engelsystem\Helpers\Schedule\Schedule::getRooms
*/
public function testGetRooms()
{
$conference = new Conference('Test', 'T');
$room1 = new Room('Test 1');
$room2 = new Room('Test 2');
$room3 = new Room('Test 3');
$days = [
new Day(
'2042-01-01',
new Carbon('2042-01-01T00:00:00+00:00'),
new Carbon('2042-01-01T23:59:00+00:00'),
1,
[$room1, $room2]
),
new Day(
'2042-01-02',
new Carbon('2042-02-01T00:00:00+00:00'),
new Carbon('2042-02-01T23:59:00+00:00'),
2,
[new Room('Test 2'), $room3]
),
];
$schedule = new Schedule('Lorem 1.3.3.7', $conference, $days);
$this->assertEquals(['Test 1' => $room1, 'Test 2' => $room2, 'Test 3' => $room3], $schedule->getRooms());
$schedule = new Schedule('Lorem 1.3.3.0', $conference, []);
$this->assertEquals([], $schedule->getRooms());
}
/**
* @covers \Engelsystem\Helpers\Schedule\Schedule::getStartDateTime
* @covers \Engelsystem\Helpers\Schedule\Schedule::getEndDateTime
*/
public function testGetDateTimes()
{
$conference = new Conference('Some Conference', 'SC');
$days = [
new Day(
'2042-01-02',
new Carbon('2042-01-02T00:00:00+00:00'),
new Carbon('2042-01-02T23:59:00+00:00'),
2
),
new Day(
'2042-01-01',
new Carbon('2042-01-01T00:00:00+00:00'),
new Carbon('2042-01-01T23:59:00+00:00'),
1
),
new Day(
'2042-01-04',
new Carbon('2042-01-04T00:00:00+00:00'),
new Carbon('2042-01-04T23:59:00+00:00'),
3
),
];
$schedule = new Schedule('Ipsum tester', $conference, $days);
$this->assertEquals('2042-01-01T00:00:00+00:00', $schedule->getStartDateTime()->format(Carbon::RFC3339));
$this->assertEquals('2042-01-04T23:59:00+00:00', $schedule->getEndDateTime()->format(Carbon::RFC3339));
$schedule = new Schedule('Ipsum old', $conference, []);
$this->assertNull($schedule->getStartDateTime());
$this->assertNull($schedule->getEndDateTime());
}
/**
* Prepare test
*/
protected function setUp(): void
{
parent::setUp();
$this->initDatabase();
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace Engelsystem\Test\Unit\Helpers\Schedule;
use Engelsystem\Helpers\Schedule\Day;
use Engelsystem\Helpers\Schedule\Event;
use Engelsystem\Helpers\Schedule\Room;
use Engelsystem\Helpers\Schedule\XmlParser;
use Engelsystem\Test\Unit\TestCase;
use Illuminate\Support\Arr;
class XmlParserTest extends TestCase
{
/**
* @covers \Engelsystem\Helpers\Schedule\XmlParser::load
* @covers \Engelsystem\Helpers\Schedule\XmlParser::parseXml
* @covers \Engelsystem\Helpers\Schedule\XmlParser::parseEvents
* @covers \Engelsystem\Helpers\Schedule\XmlParser::getFirstXpathContent
* @covers \Engelsystem\Helpers\Schedule\XmlParser::getListFromSequence
* @covers \Engelsystem\Helpers\Schedule\XmlParser::getSchedule
*/
public function testLoad()
{
libxml_use_internal_errors(true);
$parser = new XmlParser();
$this->assertFalse($parser->load('foo'));
$this->assertTrue($parser->load(file_get_contents(__DIR__ . '/Assets/schedule.xml')));
$schedule = $parser->getSchedule();
$this->assertEquals('Some version string', $schedule->getVersion());
$this->assertEquals('Test Event', $schedule->getConference()->getTitle());
/** @var Room $room */
$room = Arr::first($schedule->getRooms());
$this->assertEquals('Rooming', $room->getName());
/** @var Day $day */
$day = Arr::first($schedule->getDay());
$this->assertEquals('2042-01-01', $day->getDate());
$this->assertEquals(1, $day->getIndex());
/** @var Room $room */
$room = Arr::first($day->getRoom());
/** @var Event $event */
$event = Arr::first($room->getEvent());
$this->assertEquals('Foo Bar Test', $event->getTitle());
$this->assertEquals('WTFPL', $event->getRecording());
$this->assertEquals('de', $event->getLanguage());
$this->assertEquals('12:30', $event->getStart());
$this->assertEquals([1234 => 'Some Person'], $event->getPersons());
}
}

View File

@ -7,6 +7,7 @@ use Engelsystem\Config\Config;
use Engelsystem\Container\Container;
use Engelsystem\Helpers\Authenticator;
use Engelsystem\Helpers\Translation\Translator;
use Engelsystem\Http\Redirector;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Http\UrlGeneratorInterface;
@ -98,6 +99,29 @@ class HelpersTest extends TestCase
$this->assertEquals(['user' => 'FooBar'], config('mail'));
}
/**
* @covers \back
*/
public function testBack()
{
$response = new Response();
/** @var Redirector|MockObject $redirect */
$redirect = $this->createMock(Redirector::class);
$redirect->expects($this->exactly(2))
->method('back')
->withConsecutive([302, []], [303, ['test' => 'ing']])
->willReturn($response);
$app = new Application();
$app->instance('redirect', $redirect);
$return = back();
$this->assertEquals($response, $return);
$return = back(303, ['test' => 'ing']);
$this->assertEquals($response, $return);
}
/**
* @covers \config_path
*/
@ -117,6 +141,29 @@ class HelpersTest extends TestCase
$this->assertEquals('/foo/conf/bar.php', config_path('bar.php'));
}
/**
* @covers \redirect
*/
public function testRedirect()
{
$response = new Response();
/** @var Redirector|MockObject $redirect */
$redirect = $this->createMock(Redirector::class);
$redirect->expects($this->exactly(2))
->method('to')
->withConsecutive(['/lorem', 302, []], ['/ipsum', 303, ['test' => 'er']])
->willReturn($response);
$app = new Application();
$app->instance('redirect', $redirect);
$return = redirect('/lorem');
$this->assertEquals($response, $return);
$return = redirect('/ipsum', 303, ['test' => 'er']);
$this->assertEquals($response, $return);
}
/**
* @covers \request
*/

View File

@ -0,0 +1,29 @@
<?php
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Application;
use Engelsystem\Http\HttpClientServiceProvider;
use Engelsystem\Test\Unit\ServiceProviderTest;
use GuzzleHttp\Client as GuzzleClient;
class HttpClientServiceProviderTest extends ServiceProviderTest
{
/**
* @covers \Engelsystem\Http\HttpClientServiceProvider::register
*/
public function testRegister()
{
$app = new Application();
$serviceProvider = new HttpClientServiceProvider($app);
$serviceProvider->register();
/** @var GuzzleClient $guzzle */
$guzzle = $app->make(GuzzleClient::class);
$config = $guzzle->getConfig();
$this->assertFalse($config['http_errors']);
$this->assertArrayHasKey('timeout', $config);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Application;
use Engelsystem\Http\RedirectServiceProvider;
use Engelsystem\Test\Unit\ServiceProviderTest;
class RedirectServiceProviderTest extends ServiceProviderTest
{
/**
* @covers \Engelsystem\Http\RedirectServiceProvider::register
*/
public function testRegister()
{
$app = new Application();
$serviceProvider = new RedirectServiceProvider($app);
$serviceProvider->register();
$this->assertTrue($app->has('redirect'));
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace Engelsystem\Test\Unit\Http;
use Engelsystem\Http\Redirector;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use PHPUnit\Framework\TestCase;
class RedirectorTest extends TestCase
{
/**
* @covers \Engelsystem\Http\Redirector::__construct
* @covers \Engelsystem\Http\Redirector::to
*/
public function testTo()
{
$request = new Request();
$response = new Response();
$redirector = new Redirector($request, $response);
$return = $redirector->to('/test');
$this->assertEquals(['/test'], $return->getHeader('location'));
$this->assertEquals(302, $return->getStatusCode());
$return = $redirector->to('/foo', 303, ['test' => 'data']);
$this->assertEquals(['/foo'], $return->getHeader('location'));
$this->assertEquals(303, $return->getStatusCode());
$this->assertEquals(['data'], $return->getHeader('test'));
}
/**
* @covers \Engelsystem\Http\Redirector::back
* @covers \Engelsystem\Http\Redirector::getPreviousUrl
*/
public function testBack()
{
$request = new Request();
$response = new Response();
$redirector = new Redirector($request, $response);
$return = $redirector->back();
$this->assertEquals(['/'], $return->getHeader('location'));
$this->assertEquals(302, $return->getStatusCode());
$request = $request->withHeader('referer', '/old-page');
$redirector = new Redirector($request, $response);
$return = $redirector->back(303, ['foo' => 'bar']);
$this->assertEquals(303, $return->getStatusCode());
$this->assertEquals(['/old-page'], $return->getHeader('location'));
$this->assertEquals(['bar'], $return->getHeader('foo'));
}
}

View File

@ -10,6 +10,8 @@ use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
class ResponseTest extends TestCase
{
@ -116,4 +118,59 @@ class ResponseTest extends TestCase
$newResponse->getHeaders()
);
}
/**
* @covers \Engelsystem\Http\Response::with
*/
public function testWith()
{
$session = new Session(new MockArraySessionStorage());
$response = new Response('', 200, [], null, $session);
$response->with('foo', 'bar');
$this->assertEquals('bar', $session->get('foo'));
$response->with('lorem', ['ipsum', 'dolor' => ['foo' => 'bar']]);
$this->assertEquals(['ipsum', 'dolor' => ['foo' => 'bar']], $session->get('lorem'));
$response->with('lorem', ['dolor' => ['test' => 'er']]);
$this->assertEquals(['ipsum', 'dolor' => ['foo' => 'bar', 'test' => 'er']], $session->get('lorem'));
}
/**
* @covers \Engelsystem\Http\Response::with
*/
public function testWithNoSession()
{
$this->expectException(InvalidArgumentException::class);
$response = new Response();
$response->with('foo', 'bar');
}
/**
* @covers \Engelsystem\Http\Response::withInput
*/
public function testWithInput()
{
$session = new Session(new MockArraySessionStorage());
$response = new Response('', 200, [], null, $session);
$response->withInput(['some' => 'value']);
$this->assertEquals(['some' => 'value'], $session->get('form-data'));
$response->withInput(['lorem' => 'ipsum']);
$this->assertEquals(['lorem' => 'ipsum'], $session->get('form-data'));
}
/**
* @covers \Engelsystem\Http\Response::withInput
*/
public function testWithInputNoSession()
{
$this->expectException(InvalidArgumentException::class);
$response = new Response();
$response->withInput(['some' => 'value']);
}
}

View File

@ -6,6 +6,7 @@ use Engelsystem\Application;
use Engelsystem\Http\Exceptions\HttpException;
use Engelsystem\Http\Exceptions\ValidationException;
use Engelsystem\Http\Psr7ServiceProvider;
use Engelsystem\Http\RedirectServiceProvider;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Http\ResponseServiceProvider;
@ -18,6 +19,7 @@ use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Twig\Loader\LoaderInterface as TwigLoader;
@ -155,7 +157,7 @@ class ErrorHandlerTest extends TestCase
/**
* @covers \Engelsystem\Middleware\ErrorHandler::process
* @covers \Engelsystem\Middleware\ErrorHandler::getPreviousUrl
* @covers \Engelsystem\Middleware\ErrorHandler::redirectBack
*/
public function testProcessValidationException()
{
@ -185,11 +187,13 @@ class ErrorHandlerTest extends TestCase
/** @var Application $app */
$app = app();
$app->instance(Session::class, $session);
$app->bind(SessionInterface::class, Session::class);
(new ResponseServiceProvider($app))->register();
(new Psr7ServiceProvider($app))->register();
(new RedirectServiceProvider($app))->register();
$errorHandler = new ErrorHandler($twigLoader);
$return = $errorHandler->process($request, $handler);
$this->assertEquals(302, $return->getStatusCode());
@ -209,6 +213,7 @@ class ErrorHandlerTest extends TestCase
], $session->all());
$request = $request->withAddedHeader('referer', '/foo/batz');
$app->instance(Request::class, $request);
$return = $errorHandler->process($request, $handler);
$this->assertEquals('/foo/batz', $return->getHeaderLine('location'));

View File

@ -0,0 +1,41 @@
<?php
namespace Engelsystem\Test\Unit\Models\Shifts;
use Engelsystem\Models\Shifts\Schedule;
use Engelsystem\Models\Shifts\ScheduleShift;
use Engelsystem\Test\Unit\HasDatabase;
use Engelsystem\Test\Unit\TestCase;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ScheduleShiftTest extends TestCase
{
use HasDatabase;
/**
* @covers \Engelsystem\Models\Shifts\ScheduleShift::schedule
*/
public function testScheduleShifts()
{
$schedule = new Schedule(['url' => 'https://lorem.ipsum/schedule.xml']);
$schedule->save();
$scheduleShift = new ScheduleShift(['shift_id' => 1, 'guid' => 'a']);
$scheduleShift->schedule()->associate($schedule);
$scheduleShift->save();
/** @var ScheduleShift $scheduleShift */
$scheduleShift = (new ScheduleShift())->find(1);
$this->assertInstanceOf(BelongsTo::class, $scheduleShift->schedule());
$this->assertEquals($schedule->id, $scheduleShift->schedule->id);
}
/**
* Prepare test
*/
protected function setUp(): void
{
parent::setUp();
$this->initDatabase();
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Engelsystem\Test\Unit\Models\Shifts;
use Engelsystem\Models\Shifts\Schedule;
use Engelsystem\Models\Shifts\ScheduleShift;
use Engelsystem\Test\Unit\HasDatabase;
use Engelsystem\Test\Unit\TestCase;
class ScheduleTest extends TestCase
{
use HasDatabase;
/**
* @covers \Engelsystem\Models\Shifts\Schedule::scheduleShifts
*/
public function testScheduleShifts()
{
$schedule = new Schedule(['url' => 'https://foo.bar/schedule.xml']);
$schedule->save();
(new ScheduleShift(['shift_id' => 1, 'schedule_id' => $schedule->id, 'guid' => 'a']))->save();
(new ScheduleShift(['shift_id' => 2, 'schedule_id' => $schedule->id, 'guid' => 'b']))->save();
(new ScheduleShift(['shift_id' => 3, 'schedule_id' => $schedule->id, 'guid' => 'c']))->save();
$this->assertCount(3, $schedule->scheduleShifts);
}
/**
* Prepare test
*/
protected function setUp(): void
{
parent::setUp();
$this->initDatabase();
}
}