Added FAQ
This commit is contained in:
parent
5cdf3889f9
commit
857ed23548
|
@ -35,12 +35,15 @@ return [
|
||||||
// Footer links
|
// Footer links
|
||||||
'footer_items' => [
|
'footer_items' => [
|
||||||
// URL to the angel faq and job description
|
// URL to the angel faq and job description
|
||||||
'FAQ' => env('FAQ_URL', 'https://events.ccc.de/congress/2013/wiki/Static:Volunteers'),
|
'FAQ' => env('FAQ_URL', '/faq'),
|
||||||
|
|
||||||
// Contact email address, linked on every page
|
// Contact email address, linked on every page
|
||||||
'Contact' => env('CONTACT_EMAIL', 'mailto:ticket@c3heaven.de'),
|
'Contact' => env('CONTACT_EMAIL', 'mailto:ticket@c3heaven.de'),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// Text displayed on the FAQ page, rendered as markdown
|
||||||
|
'faq_text' => env('FAQ_TEXT', null),
|
||||||
|
|
||||||
// Link to documentation/help
|
// Link to documentation/help
|
||||||
'documentation_url' => 'https://engelsystem.de/doc/',
|
'documentation_url' => 'https://engelsystem.de/doc/',
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,9 @@ $route->get('/meetings', 'NewsController@meetings');
|
||||||
$route->get('/news/{id:\d+}', 'NewsController@show');
|
$route->get('/news/{id:\d+}', 'NewsController@show');
|
||||||
$route->post('/news/{id:\d+}', 'NewsController@comment');
|
$route->post('/news/{id:\d+}', 'NewsController@comment');
|
||||||
|
|
||||||
|
// FAQ
|
||||||
|
$route->get('/faq', 'FaqController@index');
|
||||||
|
|
||||||
// API
|
// API
|
||||||
$route->get('/api[/{resource:.+}]', 'ApiController@index');
|
$route->get('/api[/{resource:.+}]', 'ApiController@index');
|
||||||
|
|
||||||
|
@ -50,6 +53,15 @@ $route->get('/design', 'DesignController@index');
|
||||||
$route->addGroup(
|
$route->addGroup(
|
||||||
'/admin',
|
'/admin',
|
||||||
function (RouteCollector $route) {
|
function (RouteCollector $route) {
|
||||||
|
// FAQ
|
||||||
|
$route->addGroup(
|
||||||
|
'/faq',
|
||||||
|
function (RouteCollector $route) {
|
||||||
|
$route->get('[/{id:\d+}]', 'Admin\\FaqController@edit');
|
||||||
|
$route->post('[/{id:\d+}]', 'Admin\\FaqController@save');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Log
|
// Log
|
||||||
$route->get('/logs', 'Admin\\LogsController@index');
|
$route->get('/logs', 'Admin\\LogsController@index');
|
||||||
$route->post('/logs', 'Admin\\LogsController@index');
|
$route->post('/logs', 'Admin\\LogsController@index');
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Engelsystem\Migrations;
|
||||||
|
|
||||||
|
use Engelsystem\Database\Migration\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
|
||||||
|
class CreateFaqTableAndPermissions extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migration
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
$this->schema->create('faq', function (Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->string('question');
|
||||||
|
$table->text('text');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($this->schema->hasTable('Privileges')) {
|
||||||
|
$db = $this->schema->getConnection();
|
||||||
|
$db->table('Privileges')->insert([
|
||||||
|
['name' => 'faq.view', 'desc' => 'View FAQ entries'],
|
||||||
|
['name' => 'faq.edit', 'desc' => 'Edit FAQ entries'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$guestGroup = -10;
|
||||||
|
$angelGroup = -20;
|
||||||
|
$shiftCoordinatorGroup = -40;
|
||||||
|
$viewId = $db->table('Privileges')->where('name', 'faq.view')->first()->id;
|
||||||
|
$editId = $db->table('Privileges')->where('name', 'faq.edit')->first()->id;
|
||||||
|
$db->table('GroupPrivileges')->insert([
|
||||||
|
['group_id' => $guestGroup, 'privilege_id' => $viewId],
|
||||||
|
['group_id' => $angelGroup, 'privilege_id' => $viewId],
|
||||||
|
['group_id' => $shiftCoordinatorGroup, 'privilege_id' => $editId],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$db->table('Privileges')
|
||||||
|
->whereIn('name', ['admin_faq'])
|
||||||
|
->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migration
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
$this->schema->drop('faq');
|
||||||
|
|
||||||
|
if ($this->schema->hasTable('Privileges')) {
|
||||||
|
$db = $this->schema->getConnection();
|
||||||
|
$db->table('Privileges')
|
||||||
|
->whereIn('name', ['faq.view', 'faq.edit'])
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
$db->table('Privileges')->insert([
|
||||||
|
['name' => 'admin_faq', 'desc' => 'Edit FAQs'],
|
||||||
|
]);
|
||||||
|
$bureaucratGroup = -60;
|
||||||
|
$adminFaqId = $db->table('Privileges')->where('name', 'admin_faq')->first()->id;
|
||||||
|
$db->table('GroupPrivileges')->insert([
|
||||||
|
['group_id' => $bureaucratGroup, 'privilege_id' => $adminFaqId],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -108,3 +108,9 @@ msgstr "OAuth-Provider nicht gefunden"
|
||||||
|
|
||||||
msgid "settings.profile"
|
msgid "settings.profile"
|
||||||
msgstr "Profil"
|
msgstr "Profil"
|
||||||
|
|
||||||
|
msgid "faq.delete.success"
|
||||||
|
msgstr "FAQ Eintrag erfolgreich gelöscht."
|
||||||
|
|
||||||
|
msgid "faq.edit.success"
|
||||||
|
msgstr "FAQ Eintrag erfolgreich aktualisiert."
|
||||||
|
|
|
@ -2776,6 +2776,15 @@ msgstr "Bearbeiten"
|
||||||
msgid "form.save"
|
msgid "form.save"
|
||||||
msgstr "Speichern"
|
msgstr "Speichern"
|
||||||
|
|
||||||
|
msgid "form.preview"
|
||||||
|
msgstr "Vorschau"
|
||||||
|
|
||||||
|
msgid "form.delete"
|
||||||
|
msgstr "Löschen"
|
||||||
|
|
||||||
|
msgid "form.updated"
|
||||||
|
msgstr "Aktualisiert"
|
||||||
|
|
||||||
msgid "schedule.import"
|
msgid "schedule.import"
|
||||||
msgstr "Programm importieren"
|
msgstr "Programm importieren"
|
||||||
|
|
||||||
|
@ -2879,9 +2888,6 @@ msgstr "Treffen"
|
||||||
msgid "news.edit.message"
|
msgid "news.edit.message"
|
||||||
msgstr "Nachricht"
|
msgstr "Nachricht"
|
||||||
|
|
||||||
msgid "news.preview"
|
|
||||||
msgstr "Vorschau"
|
|
||||||
|
|
||||||
msgid "form.search"
|
msgid "form.search"
|
||||||
msgstr "Suchen"
|
msgstr "Suchen"
|
||||||
|
|
||||||
|
@ -2936,3 +2942,18 @@ msgstr "Verbinden"
|
||||||
|
|
||||||
msgid "form.disconnect"
|
msgid "form.disconnect"
|
||||||
msgstr "Trennen"
|
msgstr "Trennen"
|
||||||
|
|
||||||
|
msgid "faq.faq"
|
||||||
|
msgstr "FAQ"
|
||||||
|
|
||||||
|
msgid "faq.edit"
|
||||||
|
msgstr "FAQ Eintrag bearbeiten"
|
||||||
|
|
||||||
|
msgid "faq.add"
|
||||||
|
msgstr "FAQ Eintrag erstellen"
|
||||||
|
|
||||||
|
msgid "faq.question"
|
||||||
|
msgstr "Frage"
|
||||||
|
|
||||||
|
msgid "faq.message"
|
||||||
|
msgstr "Antwort"
|
||||||
|
|
|
@ -104,3 +104,9 @@ msgstr "Unable to find OAuth provider"
|
||||||
|
|
||||||
msgid "settings.profile"
|
msgid "settings.profile"
|
||||||
msgstr "Profile"
|
msgstr "Profile"
|
||||||
|
|
||||||
|
msgid "faq.delete.success"
|
||||||
|
msgstr "FAQ entry successfully deleted."
|
||||||
|
|
||||||
|
msgid "faq.edit.success"
|
||||||
|
msgstr "FAQ entry successfully updated."
|
||||||
|
|
|
@ -60,6 +60,15 @@ msgstr "Save"
|
||||||
msgid "form.edit"
|
msgid "form.edit"
|
||||||
msgstr "Bearbeiten"
|
msgstr "Bearbeiten"
|
||||||
|
|
||||||
|
msgid "form.preview"
|
||||||
|
msgstr "Preview"
|
||||||
|
|
||||||
|
msgid "form.delete"
|
||||||
|
msgstr "Delete"
|
||||||
|
|
||||||
|
msgid "form.updated"
|
||||||
|
msgstr "Updated"
|
||||||
|
|
||||||
msgid "schedule.import"
|
msgid "schedule.import"
|
||||||
msgstr "Import schedule"
|
msgstr "Import schedule"
|
||||||
|
|
||||||
|
@ -165,9 +174,6 @@ msgstr "Meeting"
|
||||||
msgid "news.edit.message"
|
msgid "news.edit.message"
|
||||||
msgstr "Message"
|
msgstr "Message"
|
||||||
|
|
||||||
msgid "news.preview"
|
|
||||||
msgstr "Preview"
|
|
||||||
|
|
||||||
msgid "form.search"
|
msgid "form.search"
|
||||||
msgstr "Search"
|
msgstr "Search"
|
||||||
|
|
||||||
|
@ -221,3 +227,18 @@ msgstr "Connect"
|
||||||
|
|
||||||
msgid "form.disconnect"
|
msgid "form.disconnect"
|
||||||
msgstr "Disconnect"
|
msgstr "Disconnect"
|
||||||
|
|
||||||
|
msgid "faq.faq"
|
||||||
|
msgstr "FAQ"
|
||||||
|
|
||||||
|
msgid "faq.edit"
|
||||||
|
msgstr "Edit FAQ entry"
|
||||||
|
|
||||||
|
msgid "faq.add"
|
||||||
|
msgstr "Add FAQ entry"
|
||||||
|
|
||||||
|
msgid "faq.question"
|
||||||
|
msgstr "Question"
|
||||||
|
|
||||||
|
msgid "faq.message"
|
||||||
|
msgstr "Answer"
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% for name,url in config('footer_items') %}
|
{% for name,url in config('footer_items') %}
|
||||||
<a href="{{ url }}">
|
<a href="{% if url starts with '/' %}{{ url(url) }}{% else %}{{ url }}{% endif %}">
|
||||||
{% if '@' in url %}<span class="glyphicon glyphicon-envelope"></span>{% endif %}
|
{% if '@' in url %}<span class="glyphicon glyphicon-envelope"></span>{% endif %}
|
||||||
{{ __(name) }}
|
{{ __(name) }}
|
||||||
</a> ·
|
</a> ·
|
||||||
|
|
|
@ -69,6 +69,7 @@
|
||||||
<button class="btn btn-{{ opt.btn_type|default('primary') }} btn-{{ opt.btn_size|default('m') }}"
|
<button class="btn btn-{{ opt.btn_type|default('primary') }} btn-{{ opt.btn_size|default('m') }}"
|
||||||
{%- if opt.type is defined %} type="{{ opt.type }}"{% endif %}
|
{%- if opt.type is defined %} type="{{ opt.type }}"{% endif %}
|
||||||
{%- if opt.name is defined %} name="{{ opt.name }}"{% endif %}
|
{%- if opt.name is defined %} name="{{ opt.name }}"{% endif %}
|
||||||
|
{%- if opt.title is defined %} title="{{ opt.title }}"{% endif %}
|
||||||
{%- if opt.value is defined or opt.name is defined %} value="{{ opt.value|default('1') }}"{% endif -%}
|
{%- if opt.value is defined or opt.name is defined %} value="{{ opt.value|default('1') }}"{% endif -%}
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
{% extends 'layouts/app.twig' %}
|
||||||
|
{% import 'macros/base.twig' as m %}
|
||||||
|
{% import 'macros/form.twig' as f %}
|
||||||
|
|
||||||
|
{% block title %}{{ faq and faq.id ? __('faq.edit') : __('faq.add') }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<h1>{{ block('title') }}</h1>
|
||||||
|
|
||||||
|
{% include 'layouts/parts/messages.twig' %}
|
||||||
|
|
||||||
|
{% if faq and faq.id %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<p>
|
||||||
|
{{ m.glyphicon('time') }} {{ faq.updated_at.format(__('Y-m-d H:i')) }}
|
||||||
|
|
||||||
|
{% if faq.updated_at != faq.created_at %}
|
||||||
|
 {{ __('form.updated') }}
|
||||||
|
<br>
|
||||||
|
{{ m.glyphicon('time') }} {{ faq.created_at.format(__('Y-m-d H:i')) }}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form action="" enctype="multipart/form-data" method="post">
|
||||||
|
{{ csrf() }}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
{{ f.input('question', __('faq.question'), null, {'required': true, 'value': faq ? faq.question : ''}) }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12">
|
||||||
|
{{ f.textarea('text', __('faq.message'), {'required': true, 'rows': 10, 'value': faq ? faq.text : ''}) }}
|
||||||
|
|
||||||
|
{{ f.submit() }}
|
||||||
|
|
||||||
|
{{ f.submit(m.glyphicon('eye-close'), {'name': 'preview', 'btn_type': 'info', 'title': __('form.preview')}) }}
|
||||||
|
|
||||||
|
{% if faq and faq.id %}
|
||||||
|
{{ f.submit(m.glyphicon('trash'), {'name': 'delete', 'btn_type': 'danger', 'title': __('form.delete')}) }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if faq %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h2>{{ __('form.preview') }}</h2>
|
||||||
|
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
{{ faq.question }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-body">
|
||||||
|
{{ faq.text|markdown }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,56 @@
|
||||||
|
{% extends 'layouts/app.twig' %}
|
||||||
|
{% import 'macros/base.twig' as m %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{{ __('faq.faq') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<h1>
|
||||||
|
{{ block('title') }}
|
||||||
|
|
||||||
|
{%- if has_permission_to('faq.edit') -%}
|
||||||
|
{{ m.button(m.glyphicon('plus'), url('admin/faq')) }}
|
||||||
|
{%- endif %}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{% include 'layouts/parts/messages.twig' %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
{% block text %}
|
||||||
|
{% if text|default(null) %}
|
||||||
|
<div class="col-md-12">
|
||||||
|
{{ text|markdown }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block row %}
|
||||||
|
{% for item in items %}
|
||||||
|
<div class="col-md-12" id="faq-{{ item.id }}">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
{{ item.question }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-body">
|
||||||
|
{{ item.text|markdown }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-footer">
|
||||||
|
{{ m.glyphicon('time') }} {{ item.updated_at.format(__('Y-m-d H:i')) }}
|
||||||
|
|
||||||
|
{% if has_permission_to('faq.edit') %}
|
||||||
|
<span class="pull-right">
|
||||||
|
{{ m.button(m.glyphicon('edit'), url('admin/faq/' ~ item.id), null, 'xs') }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -2,7 +2,7 @@
|
||||||
{% import 'macros/base.twig' as m %}
|
{% import 'macros/base.twig' as m %}
|
||||||
{% import 'macros/form.twig' as f %}
|
{% import 'macros/form.twig' as f %}
|
||||||
|
|
||||||
{% block title %}{{ news ? __('news.edit.edit') : __('news.edit.add') }}{% endblock %}
|
{% block title %}{{ news and news.id ? __('news.edit.edit') : __('news.edit.add') }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
{% include 'layouts/parts/messages.twig' %}
|
{% include 'layouts/parts/messages.twig' %}
|
||||||
|
|
||||||
{% if news %}
|
{% if news and news.id %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<p>
|
<p>
|
||||||
|
@ -51,10 +51,10 @@
|
||||||
|
|
||||||
{{ f.submit() }}
|
{{ f.submit() }}
|
||||||
|
|
||||||
{{ f.submit(m.glyphicon('eye-close'), {'name': 'preview', 'btn_type': 'info'}) }}
|
{{ f.submit(m.glyphicon('eye-close'), {'name': 'preview', 'btn_type': 'info', 'title': __('form.preview')}) }}
|
||||||
|
|
||||||
{% if news %}
|
{% if news and news.id %}
|
||||||
{{ f.submit(m.glyphicon('trash'), {'name': 'delete', 'btn_type': 'danger'}) }}
|
{{ f.submit(m.glyphicon('trash'), {'name': 'delete', 'btn_type': 'danger', 'title': __('form.delete')}) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -62,7 +62,7 @@
|
||||||
{% if news %}
|
{% if news %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<h2>{{ __('news.preview') }}</h2>
|
<h2>{{ __('form.preview') }}</h2>
|
||||||
|
|
||||||
<div class="panel {% if not news.is_meeting %}panel-default{% else %}panel-info{% endif %}">
|
<div class="panel {% if not news.is_meeting %}panel-default{% else %}panel-info{% endif %}">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Engelsystem\Controllers\Admin;
|
||||||
|
|
||||||
|
use Engelsystem\Controllers\BaseController;
|
||||||
|
use Engelsystem\Controllers\CleanupModel;
|
||||||
|
use Engelsystem\Controllers\HasUserNotifications;
|
||||||
|
use Engelsystem\Http\Redirector;
|
||||||
|
use Engelsystem\Http\Request;
|
||||||
|
use Engelsystem\Http\Response;
|
||||||
|
use Engelsystem\Models\Faq;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
class FaqController extends BaseController
|
||||||
|
{
|
||||||
|
use HasUserNotifications;
|
||||||
|
use CleanupModel;
|
||||||
|
|
||||||
|
/** @var LoggerInterface */
|
||||||
|
protected $log;
|
||||||
|
|
||||||
|
/** @var Faq */
|
||||||
|
protected $faq;
|
||||||
|
|
||||||
|
/** @var Redirector */
|
||||||
|
protected $redirect;
|
||||||
|
|
||||||
|
/** @var Response */
|
||||||
|
protected $response;
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
protected $permissions = [
|
||||||
|
'faq.view',
|
||||||
|
'faq.edit',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param LoggerInterface $log
|
||||||
|
* @param Faq $faq
|
||||||
|
* @param Redirector $redirector
|
||||||
|
* @param Response $response
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
LoggerInterface $log,
|
||||||
|
Faq $faq,
|
||||||
|
Redirector $redirector,
|
||||||
|
Response $response
|
||||||
|
) {
|
||||||
|
$this->log = $log;
|
||||||
|
$this->faq = $faq;
|
||||||
|
$this->redirect = $redirector;
|
||||||
|
$this->response = $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function edit(Request $request): Response
|
||||||
|
{
|
||||||
|
$id = $request->getAttribute('id');
|
||||||
|
$faq = $this->faq->find($id);
|
||||||
|
|
||||||
|
return $this->showEdit($faq);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function save(Request $request): Response
|
||||||
|
{
|
||||||
|
$id = $request->getAttribute('id');
|
||||||
|
/** @var Faq $faq */
|
||||||
|
$faq = $this->faq->findOrNew($id);
|
||||||
|
|
||||||
|
$data = $this->validate($request, [
|
||||||
|
'question' => 'required',
|
||||||
|
'text' => 'required',
|
||||||
|
'delete' => 'optional|checked',
|
||||||
|
'preview' => 'optional|checked',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!is_null($data['delete'])) {
|
||||||
|
$faq->delete();
|
||||||
|
|
||||||
|
$this->log->info('Deleted faq "{question}"', ['question' => $faq->question]);
|
||||||
|
|
||||||
|
$this->addNotification('faq.delete.success');
|
||||||
|
|
||||||
|
return $this->redirect->to('/faq');
|
||||||
|
}
|
||||||
|
|
||||||
|
$faq->question = $data['question'];
|
||||||
|
$faq->text = $data['text'];
|
||||||
|
|
||||||
|
if (!is_null($data['preview'])) {
|
||||||
|
return $this->showEdit($faq);
|
||||||
|
}
|
||||||
|
|
||||||
|
$faq->save();
|
||||||
|
|
||||||
|
$this->log->info('Updated faq "{question}": {text}', ['question' => $faq->question, 'text' => $faq->text]);
|
||||||
|
|
||||||
|
$this->addNotification('faq.edit.success');
|
||||||
|
|
||||||
|
return $this->redirect->to('/faq#faq-' . $faq->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Faq|null $faq
|
||||||
|
*
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
protected function showEdit(?Faq $faq): Response
|
||||||
|
{
|
||||||
|
$this->cleanupModelNullValues($faq);
|
||||||
|
|
||||||
|
return $this->response->withView(
|
||||||
|
'pages/faq/edit.twig',
|
||||||
|
['faq' => $faq] + $this->getNotifications()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Engelsystem\Controllers;
|
||||||
|
|
||||||
|
use Engelsystem\Config\Config;
|
||||||
|
use Engelsystem\Http\Response;
|
||||||
|
use Engelsystem\Models\Faq;
|
||||||
|
|
||||||
|
class FaqController extends BaseController
|
||||||
|
{
|
||||||
|
use HasUserNotifications;
|
||||||
|
|
||||||
|
/** @var Config */
|
||||||
|
protected $config;
|
||||||
|
|
||||||
|
/** @var Faq */
|
||||||
|
protected $faq;
|
||||||
|
|
||||||
|
/** @var Response */
|
||||||
|
protected $response;
|
||||||
|
|
||||||
|
/** @var string[] */
|
||||||
|
protected $permissions = [
|
||||||
|
'faq.view',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Config $config
|
||||||
|
* @param Faq $faq
|
||||||
|
* @param Response $response
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
Config $config,
|
||||||
|
Faq $faq,
|
||||||
|
Response $response
|
||||||
|
) {
|
||||||
|
$this->config = $config;
|
||||||
|
$this->faq = $faq;
|
||||||
|
$this->response = $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function index(): Response
|
||||||
|
{
|
||||||
|
$text = $this->config->get('faq_text');
|
||||||
|
|
||||||
|
$faq = $this->faq->orderBy('question')->get();
|
||||||
|
|
||||||
|
return $this->response->withView(
|
||||||
|
'pages/faq/overview.twig',
|
||||||
|
['text' => $text, 'items' => $faq] + $this->getNotifications()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -172,6 +172,7 @@ class Controller extends BaseController
|
||||||
['labels' => ['state' => 'answered'], 'value' => $this->stats->questions(true)],
|
['labels' => ['state' => 'answered'], 'value' => $this->stats->questions(true)],
|
||||||
['labels' => ['state' => 'pending'], 'value' => $this->stats->questions(false)],
|
['labels' => ['state' => 'pending'], 'value' => $this->stats->questions(false)],
|
||||||
],
|
],
|
||||||
|
'faq' => ['type' => 'gauge', $this->stats->faq()],
|
||||||
'messages' => ['type' => 'gauge', $this->stats->messages()],
|
'messages' => ['type' => 'gauge', $this->stats->messages()],
|
||||||
'password_resets' => ['type' => 'gauge', $this->stats->passwordResets()],
|
'password_resets' => ['type' => 'gauge', $this->stats->passwordResets()],
|
||||||
'registration_enabled' => ['type' => 'gauge', $this->config->get('registration_enabled')],
|
'registration_enabled' => ['type' => 'gauge', $this->config->get('registration_enabled')],
|
||||||
|
|
|
@ -7,6 +7,7 @@ namespace Engelsystem\Controllers\Metrics;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Engelsystem\Database\Database;
|
use Engelsystem\Database\Database;
|
||||||
use Engelsystem\Models\EventConfig;
|
use Engelsystem\Models\EventConfig;
|
||||||
|
use Engelsystem\Models\Faq;
|
||||||
use Engelsystem\Models\LogEntry;
|
use Engelsystem\Models\LogEntry;
|
||||||
use Engelsystem\Models\Message;
|
use Engelsystem\Models\Message;
|
||||||
use Engelsystem\Models\News;
|
use Engelsystem\Models\News;
|
||||||
|
@ -413,6 +414,14 @@ class Stats
|
||||||
return $query->count();
|
return $query->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function faq(): int
|
||||||
|
{
|
||||||
|
return Faq::query()->count();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Engelsystem\Models;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property string $question
|
||||||
|
* @property string $text
|
||||||
|
* @property Carbon|null $created_at
|
||||||
|
* @property Carbon|null $updated_at
|
||||||
|
*
|
||||||
|
* @method static Builder|Faq whereId($value)
|
||||||
|
* @method static Builder|Faq whereQuestion($value)
|
||||||
|
* @method static Builder|Faq whereText($value)
|
||||||
|
*/
|
||||||
|
class Faq extends BaseModel
|
||||||
|
{
|
||||||
|
/** @var bool Enable timestamps */
|
||||||
|
public $timestamps = true;
|
||||||
|
|
||||||
|
/** @var string The models table */
|
||||||
|
public $table = 'faq';
|
||||||
|
|
||||||
|
/** @var string[] */
|
||||||
|
protected $fillable = [
|
||||||
|
'question',
|
||||||
|
'text',
|
||||||
|
];
|
||||||
|
}
|
|
@ -0,0 +1,210 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Engelsystem\Test\Unit\Controllers\Admin;
|
||||||
|
|
||||||
|
use Engelsystem\Config\Config;
|
||||||
|
use Engelsystem\Controllers\Admin\FaqController;
|
||||||
|
use Engelsystem\Helpers\Authenticator;
|
||||||
|
use Engelsystem\Http\Exceptions\ValidationException;
|
||||||
|
use Engelsystem\Http\Request;
|
||||||
|
use Engelsystem\Http\Response;
|
||||||
|
use Engelsystem\Http\UrlGenerator;
|
||||||
|
use Engelsystem\Http\UrlGeneratorInterface;
|
||||||
|
use Engelsystem\Http\Validation\Validator;
|
||||||
|
use Engelsystem\Models\Faq;
|
||||||
|
use Engelsystem\Test\Unit\HasDatabase;
|
||||||
|
use Engelsystem\Test\Unit\TestCase;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Psr\Log\Test\TestLogger;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Session;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||||
|
|
||||||
|
class FaqControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
use HasDatabase;
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
protected $data = [
|
||||||
|
'question' => 'Foo?',
|
||||||
|
'text' => 'Bar!',
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var TestLogger */
|
||||||
|
protected $log;
|
||||||
|
|
||||||
|
/** @var Response|MockObject */
|
||||||
|
protected $response;
|
||||||
|
|
||||||
|
/** @var Request */
|
||||||
|
protected $request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Controllers\Admin\FaqController::__construct
|
||||||
|
* @covers \Engelsystem\Controllers\Admin\FaqController::edit
|
||||||
|
* @covers \Engelsystem\Controllers\Admin\FaqController::showEdit
|
||||||
|
*/
|
||||||
|
public function testEdit()
|
||||||
|
{
|
||||||
|
$this->request->attributes->set('id', 1);
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('withView')
|
||||||
|
->willReturnCallback(function ($view, $data) {
|
||||||
|
$this->assertEquals('pages/faq/edit.twig', $view);
|
||||||
|
|
||||||
|
/** @var Collection $warnings */
|
||||||
|
$warnings = $data['messages'];
|
||||||
|
$this->assertNotEmpty($data['faq']);
|
||||||
|
$this->assertTrue($warnings->isEmpty());
|
||||||
|
|
||||||
|
return $this->response;
|
||||||
|
});
|
||||||
|
|
||||||
|
/** @var FaqController $controller */
|
||||||
|
$controller = $this->app->make(FaqController::class);
|
||||||
|
|
||||||
|
$controller->edit($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Controllers\Admin\FaqController::save
|
||||||
|
*/
|
||||||
|
public function testSaveCreateInvalid()
|
||||||
|
{
|
||||||
|
/** @var FaqController $controller */
|
||||||
|
$this->expectException(ValidationException::class);
|
||||||
|
|
||||||
|
$controller = $this->app->make(FaqController::class);
|
||||||
|
$controller->setValidator(new Validator());
|
||||||
|
$controller->save($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Controllers\Admin\FaqController::save
|
||||||
|
*/
|
||||||
|
public function testSaveCreateEdit()
|
||||||
|
{
|
||||||
|
$this->request->attributes->set('id', 2);
|
||||||
|
$body = $this->data;
|
||||||
|
|
||||||
|
$this->request = $this->request->withParsedBody($body);
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('redirectTo')
|
||||||
|
->with('http://localhost/faq#faq-2')
|
||||||
|
->willReturn($this->response);
|
||||||
|
|
||||||
|
/** @var FaqController $controller */
|
||||||
|
$controller = $this->app->make(FaqController::class);
|
||||||
|
$controller->setValidator(new Validator());
|
||||||
|
|
||||||
|
$controller->save($this->request);
|
||||||
|
|
||||||
|
$this->assertTrue($this->log->hasInfoThatContains('Updated'));
|
||||||
|
|
||||||
|
/** @var Session $session */
|
||||||
|
$session = $this->app->get('session');
|
||||||
|
$messages = $session->get('messages');
|
||||||
|
$this->assertEquals('faq.edit.success', $messages[0]);
|
||||||
|
|
||||||
|
$faq = (new Faq())->find(2);
|
||||||
|
$this->assertEquals('Foo?', $faq->question);
|
||||||
|
$this->assertEquals('Bar!', $faq->text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Controllers\Admin\FaqController::save
|
||||||
|
*/
|
||||||
|
public function testSavePreview()
|
||||||
|
{
|
||||||
|
$this->request->attributes->set('id', 1);
|
||||||
|
$this->request = $this->request->withParsedBody([
|
||||||
|
'question' => 'New question',
|
||||||
|
'text' => 'New text',
|
||||||
|
'preview' => '1',
|
||||||
|
]);
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('withView')
|
||||||
|
->willReturnCallback(function ($view, $data) {
|
||||||
|
$this->assertEquals('pages/faq/edit.twig', $view);
|
||||||
|
|
||||||
|
/** @var Faq $faq */
|
||||||
|
$faq = $data['faq'];
|
||||||
|
// Contains new text
|
||||||
|
$this->assertEquals('New question', $faq->question);
|
||||||
|
$this->assertEquals('New text', $faq->text);
|
||||||
|
|
||||||
|
return $this->response;
|
||||||
|
});
|
||||||
|
|
||||||
|
/** @var FaqController $controller */
|
||||||
|
$controller = $this->app->make(FaqController::class);
|
||||||
|
$controller->setValidator(new Validator());
|
||||||
|
|
||||||
|
$controller->save($this->request);
|
||||||
|
|
||||||
|
// Assert no changes
|
||||||
|
$faq = Faq::find(1);
|
||||||
|
$this->assertEquals('Lorem', $faq->question);
|
||||||
|
$this->assertEquals('Ipsum!', $faq->text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Controllers\Admin\FaqController::save
|
||||||
|
*/
|
||||||
|
public function testSaveDelete()
|
||||||
|
{
|
||||||
|
$this->request->attributes->set('id', 1);
|
||||||
|
$this->request = $this->request->withParsedBody([
|
||||||
|
'question' => '.',
|
||||||
|
'text' => '.',
|
||||||
|
'delete' => '1',
|
||||||
|
]);
|
||||||
|
$this->response->expects($this->once())
|
||||||
|
->method('redirectTo')
|
||||||
|
->with('http://localhost/faq')
|
||||||
|
->willReturn($this->response);
|
||||||
|
|
||||||
|
/** @var FaqController $controller */
|
||||||
|
$controller = $this->app->make(FaqController::class);
|
||||||
|
$controller->setValidator(new Validator());
|
||||||
|
|
||||||
|
$controller->save($this->request);
|
||||||
|
|
||||||
|
$this->assertTrue($this->log->hasInfoThatContains('Deleted'));
|
||||||
|
|
||||||
|
/** @var Session $session */
|
||||||
|
$session = $this->app->get('session');
|
||||||
|
$messages = $session->get('messages');
|
||||||
|
$this->assertEquals('faq.delete.success', $messages[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup environment
|
||||||
|
*/
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->initDatabase();
|
||||||
|
|
||||||
|
$this->request = Request::create('http://localhost');
|
||||||
|
$this->app->instance('request', $this->request);
|
||||||
|
|
||||||
|
$this->response = $this->createMock(Response::class);
|
||||||
|
$this->app->instance(Response::class, $this->response);
|
||||||
|
|
||||||
|
$this->log = new TestLogger();
|
||||||
|
$this->app->instance(LoggerInterface::class, $this->log);
|
||||||
|
|
||||||
|
$this->app->instance('session', new Session(new MockArraySessionStorage()));
|
||||||
|
|
||||||
|
$this->app->bind(UrlGeneratorInterface::class, UrlGenerator::class);
|
||||||
|
|
||||||
|
$this->app->instance('config', new Config());
|
||||||
|
|
||||||
|
(new Faq([
|
||||||
|
'question' => 'Lorem',
|
||||||
|
'text' => 'Ipsum!',
|
||||||
|
]))->save();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Engelsystem\Test\Unit\Controllers;
|
||||||
|
|
||||||
|
use Engelsystem\Config\Config;
|
||||||
|
use Engelsystem\Controllers\FaqController;
|
||||||
|
use Engelsystem\Http\Response;
|
||||||
|
use Engelsystem\Models\Faq;
|
||||||
|
use Engelsystem\Test\Unit\HasDatabase;
|
||||||
|
use Engelsystem\Test\Unit\TestCase;
|
||||||
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Session;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
|
||||||
|
|
||||||
|
class FaqControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
use HasDatabase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Controllers\FaqController::__construct
|
||||||
|
* @covers \Engelsystem\Controllers\FaqController::index
|
||||||
|
*/
|
||||||
|
public function testIndex()
|
||||||
|
{
|
||||||
|
$this->initDatabase();
|
||||||
|
(new Faq(['question' => 'Xyz', 'text' => 'Abc']))->save();
|
||||||
|
(new Faq(['question' => 'Something\'s wrong?', 'text' => 'Nah!']))->save();
|
||||||
|
|
||||||
|
$this->app->instance('session', new Session(new MockArraySessionStorage()));
|
||||||
|
$config = new Config(['faq_text' => 'Some Text']);
|
||||||
|
/** @var Response|MockObject $response */
|
||||||
|
$response = $this->createMock(Response::class);
|
||||||
|
$response->expects($this->once())
|
||||||
|
->method('withView')
|
||||||
|
->willReturnCallback(function (string $view, array $data) use ($response) {
|
||||||
|
$this->assertEquals('pages/faq/overview.twig', $view);
|
||||||
|
$this->assertEquals('Some Text', $data['text']);
|
||||||
|
$this->assertEquals('Nah!', $data['items'][0]->text);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
});
|
||||||
|
|
||||||
|
$controller = new FaqController($config, new Faq(), $response);
|
||||||
|
$controller->index();
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ namespace Engelsystem\Test\Unit\Controllers\Metrics;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Engelsystem\Controllers\Metrics\Stats;
|
use Engelsystem\Controllers\Metrics\Stats;
|
||||||
|
use Engelsystem\Models\Faq;
|
||||||
use Engelsystem\Models\LogEntry;
|
use Engelsystem\Models\LogEntry;
|
||||||
use Engelsystem\Models\Message;
|
use Engelsystem\Models\Message;
|
||||||
use Engelsystem\Models\News;
|
use Engelsystem\Models\News;
|
||||||
|
@ -250,6 +251,18 @@ class StatsTest extends TestCase
|
||||||
$this->assertEquals(3, $stats->email('humans'));
|
$this->assertEquals(3, $stats->email('humans'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Controllers\Metrics\Stats::faq
|
||||||
|
*/
|
||||||
|
public function testFaq()
|
||||||
|
{
|
||||||
|
(new Faq(['question' => 'Foo?', 'text' => 'Bar!']))->save();
|
||||||
|
(new Faq(['question' => 'Lorem??', 'text' => 'Ipsum!!!']))->save();
|
||||||
|
|
||||||
|
$stats = new Stats($this->database);
|
||||||
|
$this->assertEquals(2, $stats->faq());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers \Engelsystem\Controllers\Metrics\Stats::messages
|
* @covers \Engelsystem\Controllers\Metrics\Stats::messages
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue