summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.md19
-rw-r--r--contrib/Dockerfile9
-rw-r--r--contrib/docker-compose.yml3
-rw-r--r--includes/controller/shifts_controller.php16
-rw-r--r--includes/helper/error_helper.php11
-rw-r--r--includes/includes.php1
-rw-r--r--includes/pages/user_atom.php16
-rw-r--r--includes/pages/user_ical.php17
-rw-r--r--package.json2
-rw-r--r--resources/assets/js/forms.js35
-rw-r--r--resources/assets/js/vendor.js7
-rw-r--r--resources/lang/de_DE/default.mobin46206 -> 0 bytes
-rw-r--r--resources/lang/en_US/default.mobin770 -> 0 bytes
-rw-r--r--resources/lang/pt_BR/default.mobin41129 -> 0 bytes
-rw-r--r--resources/views/layouts/parts/navbar.twig18
-rw-r--r--resources/views/pages/login.twig8
-rw-r--r--resources/views/pages/user-shifts.html8
-rw-r--r--src/Helpers/Translation/TranslationServiceProvider.php25
-rw-r--r--tests/Unit/Helpers/Translation/Assets/ba_RR/default.po3
-rw-r--r--tests/Unit/Helpers/Translation/TranslationServiceProviderTest.php22
-rw-r--r--yarn.lock2
22 files changed, 159 insertions, 64 deletions
diff --git a/.gitignore b/.gitignore
index 77a62c1e..cda5cf92 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,6 +24,7 @@ _vimrc_local.vim
/public/coverage
/coverage
/unittests.xml
+/resources/lang/*/*.mo
# Composer files
/vendor/
diff --git a/README.md b/README.md
index 02508157..9b4a3aaf 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,11 @@ The following instructions explain how to get, build and run the latest engelsys
```bash
yarn build
```
+ * Optionally (for better performance)
+ * Generate translation files
+ ```bash
+ find resources/lang/ -type f -name '*.po' -exec sh -c 'file="{}"; msgfmt "${file%.*}.po" -o "${file%.*}.mo"' \;
+ ```
### Configuration and Setup
* The webserver must have write access to the ```import``` and ```storage``` directories and read access for all other directories
@@ -127,6 +132,20 @@ Import database
docker exec -it engelsystem bin/migrate
```
+#### Local development
+To use the working directory in the container the docker-compose file has to be changed:
+```yaml
+[...]
+ nginx:
+ volumes:
+ - ../public/assets:/var/www/public/assets
+[...]
+ engelsystem:
+ volumes:
+ - ../:/var/www
+[...]
+```
+
#### Scripts
##### bin/deploy.sh
The `bin/deploy.sh` script can be used to deploy the engelsystem. It uses rsync to deploy the application to a server over ssh.
diff --git a/contrib/Dockerfile b/contrib/Dockerfile
index a7fd403b..013ccf1d 100644
--- a/contrib/Dockerfile
+++ b/contrib/Dockerfile
@@ -4,7 +4,12 @@ COPY ./ /app/
RUN composer --no-ansi install --no-dev --ignore-platform-reqs
RUN composer --no-ansi dump-autoload --optimize
-# Intermediate container for less layers
+# Intermediate containers for less layers
+FROM alpine as translation
+RUN apk add gettext
+COPY resources/lang/ /data
+RUN find /data -type f -name '*.po' -exec sh -c 'file="{}"; msgfmt "${file%.*}.po" -o "${file%.*}.mo"' \;
+
FROM alpine as data
COPY .babelrc .browserslistrc composer.json LICENSE package.json README.md webpack.config.js yarn.lock /app/
COPY bin/ /app/bin
@@ -13,11 +18,11 @@ COPY db/ /app/db
RUN mkdir /app/import/
COPY includes/ /app/includes
COPY public/ /app/public
-COPY resources/lang /app/resources/lang
COPY resources/views /app/resources/views
COPY src/ /app/src
COPY storage/ /app/storage
+COPY --from=translation /data/ /app/resources/lang
COPY --from=composer /app/vendor/ /app/vendor
COPY --from=composer /app/composer.lock /app/
diff --git a/contrib/docker-compose.yml b/contrib/docker-compose.yml
index 197e2281..4624cce4 100644
--- a/contrib/docker-compose.yml
+++ b/contrib/docker-compose.yml
@@ -27,12 +27,13 @@ services:
depends_on:
- database
database:
- image: mariadb:latest
+ image: mariadb:10.2
environment:
MYSQL_DATABASE: engelsystem
MYSQL_USER: engelsystem
MYSQL_PASSWORD: engelsystem
MYSQL_RANDOM_ROOT_PASSWORD: 1
+ MYSQL_INITDB_SKIP_TZINFO: "yes"
volumes:
- db:/var/lib/mysql
networks:
diff --git a/includes/controller/shifts_controller.php b/includes/controller/shifts_controller.php
index caf124ba..726814cf 100644
--- a/includes/controller/shifts_controller.php
+++ b/includes/controller/shifts_controller.php
@@ -1,5 +1,6 @@
<?php
+use Engelsystem\Http\Exceptions\HttpForbidden;
use Engelsystem\ShiftSignupState;
/**
@@ -348,17 +349,18 @@ function shift_next_controller()
function shifts_json_export_controller()
{
$request = request();
+ $user = auth()->apiUser('key');
- if (!$request->has('key') || !preg_match('/^[\da-f]{32}$/', $request->input('key'))) {
- engelsystem_error('Missing key.');
+ if (
+ !$request->has('key')
+ || !preg_match('/^[\da-f]{32}$/', $request->input('key'))
+ || !$user
+ ) {
+ throw new HttpForbidden('{"error":"Missing or invalid key"}', ['content-type' => 'application/json']);
}
- $user = auth()->apiUser('key');
- if (!$user) {
- engelsystem_error('Key invalid.');
- }
if (!auth()->can('shifts_json_export')) {
- engelsystem_error('No privilege for shifts_json_export.');
+ throw new HttpForbidden('{"error":"Not allowed"}', ['content-type' => 'application/json']);
}
$shifts = load_ical_shifts();
diff --git a/includes/helper/error_helper.php b/includes/helper/error_helper.php
deleted file mode 100644
index 9314a57a..00000000
--- a/includes/helper/error_helper.php
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-/**
- * Displays a fatal message and stops execution.
- *
- * @param string $message
- */
-function engelsystem_error($message)
-{
- raw_output($message);
-}
diff --git a/includes/includes.php b/includes/includes.php
index 855ff359..601a6ca2 100644
--- a/includes/includes.php
+++ b/includes/includes.php
@@ -60,7 +60,6 @@ $includeFiles = [
__DIR__ . '/../includes/helper/graph_helper.php',
__DIR__ . '/../includes/helper/message_helper.php',
- __DIR__ . '/../includes/helper/error_helper.php',
__DIR__ . '/../includes/helper/email_helper.php',
__DIR__ . '/../includes/mailer/shifts_mailer.php',
diff --git a/includes/pages/user_atom.php b/includes/pages/user_atom.php
index 8e5b4858..a491fea7 100644
--- a/includes/pages/user_atom.php
+++ b/includes/pages/user_atom.php
@@ -1,6 +1,7 @@
<?php
use Engelsystem\Database\DB;
+use Engelsystem\Http\Exceptions\HttpForbidden;
/**
* Publically available page to feed the news to feed readers
@@ -8,17 +9,18 @@ use Engelsystem\Database\DB;
function user_atom()
{
$request = request();
+ $user = auth()->apiUser('key');
- if (!$request->has('key') || !preg_match('/^[\da-f]{32}$/', $request->input('key'))) {
- engelsystem_error('Missing key.');
+ if (
+ !$request->has('key')
+ || !preg_match('/^[\da-f]{32}$/', $request->input('key'))
+ || empty($user)
+ ) {
+ throw new HttpForbidden('Missing or invalid key', ['content-type' => 'text/text']);
}
- $user = auth()->apiUser('key');
- if (empty($user)) {
- engelsystem_error('Key invalid.');
- }
if (!auth()->can('atom')) {
- engelsystem_error('No privilege for atom.');
+ throw new HttpForbidden('Not allowed', ['content-type' => 'text/text']);
}
$news = DB::select('
diff --git a/includes/pages/user_ical.php b/includes/pages/user_ical.php
index ee3a8340..2f3a7ccc 100644
--- a/includes/pages/user_ical.php
+++ b/includes/pages/user_ical.php
@@ -1,22 +1,25 @@
<?php
+use Engelsystem\Http\Exceptions\HttpForbidden;
+
/**
* Controller for ical output of users own shifts or any user_shifts filter.
*/
function user_ical()
{
$request = request();
+ $user = auth()->apiUser('key');
- if (!$request->has('key') || !preg_match('/^[\da-f]{32}$/', $request->input('key'))) {
- engelsystem_error('Missing key.');
+ if (
+ !$request->has('key')
+ || !preg_match('/^[\da-f]{32}$/', $request->input('key'))
+ || !$user
+ ) {
+ throw new HttpForbidden('Missing or invalid key', ['content-type' => 'text/text']);
}
- $user = auth()->apiUser('key');
- if (!$user) {
- engelsystem_error('Key invalid.');
- }
if (!auth()->can('ical')) {
- engelsystem_error('No privilege for ical.');
+ throw new HttpForbidden('Not allowed', ['content-type' => 'text/text']);
}
$ical_shifts = load_ical_shifts();
diff --git a/package.json b/package.json
index 5a8a5a44..8a623a3b 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,7 @@
"eonasdan-bootstrap-datetimepicker": "^4.17.47",
"jquery": "^3.3.1",
"jquery-ui": "^1.11.2",
- "moment": "^2.8.2",
+ "moment": "^2.12.0",
"moment-timezone": "^0.4.0",
"select2": "^4.0.6-rc.1",
"select2-bootstrap-theme": "0.1.0-beta.10"
diff --git a/resources/assets/js/forms.js b/resources/assets/js/forms.js
index f5818e97..9970b907 100644
--- a/resources/assets/js/forms.js
+++ b/resources/assets/js/forms.js
@@ -16,7 +16,7 @@ global.checkAll = (id, checked) => {
* Sets the checkboxes according to the given type
*
* @param {string} id The elements ID
- * @param {list} shifts_list A list of numbers
+ * @param {list} shiftsList A list of numbers
*/
global.checkOwnTypes = (id, shiftsList) => {
$('#' + id + ' input[type="checkbox"]').each(function () {
@@ -144,10 +144,10 @@ $(function () {
elem.children('input').on('click', function (ev) {
ev.stopImmediatePropagation();
if (typeof elem.data('DateTimePicker') === 'undefined') {
- elem.datetimepicker(opts);
- elem.data('DateTimePicker').show();
+ elem.datetimepicker(opts);
+ elem.data('DateTimePicker').show();
} else {
- elem.data('DateTimePicker').toggle();
+ elem.data('DateTimePicker').toggle();
}
});
});
@@ -173,3 +173,30 @@ $(function () {
});
});
});
+
+/**
+ * Set the filter selects to latest state
+ *
+ * Uses DOMContentLoaded to prevent flickering
+ */
+window.addEventListener('DOMContentLoaded', () => {
+ const filter = document.getElementById('collapseShiftsFilterSelect');
+ if (!filter || localStorage.getItem('collapseShiftsFilterSelect') !== 'hidden') {
+ return;
+ }
+
+ filter.classList.remove('in');
+});
+$(() => {
+ if (typeof (localStorage) === 'undefined') {
+ return;
+ }
+
+ const onChange = (e) => {
+ localStorage.setItem('collapseShiftsFilterSelect', e.type);
+ };
+
+ $('#collapseShiftsFilterSelect')
+ .on('hidden.bs.collapse', onChange)
+ .on('shown.bs.collapse', onChange);
+});
diff --git a/resources/assets/js/vendor.js b/resources/assets/js/vendor.js
index bf3807f7..b4b6487d 100644
--- a/resources/assets/js/vendor.js
+++ b/resources/assets/js/vendor.js
@@ -10,6 +10,13 @@ require('./forms');
require('./sticky-headers');
require('./moment-countdown');
+moment.updateLocale('en', {
+ week : {
+ dow : 1, // Monday is the first day of the week.
+ doy : 4 // The week that contains Jan 4th is the first week of the year.
+ }
+});
+
$.ajaxSetup({
headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')}
});
diff --git a/resources/lang/de_DE/default.mo b/resources/lang/de_DE/default.mo
deleted file mode 100644
index fb93d590..00000000
--- a/resources/lang/de_DE/default.mo
+++ /dev/null
Binary files differ
diff --git a/resources/lang/en_US/default.mo b/resources/lang/en_US/default.mo
deleted file mode 100644
index 7ef9c3b2..00000000
--- a/resources/lang/en_US/default.mo
+++ /dev/null
Binary files differ
diff --git a/resources/lang/pt_BR/default.mo b/resources/lang/pt_BR/default.mo
deleted file mode 100644
index 8b864156..00000000
--- a/resources/lang/pt_BR/default.mo
+++ /dev/null
Binary files differ
diff --git a/resources/views/layouts/parts/navbar.twig b/resources/views/layouts/parts/navbar.twig
index 61c6a10b..0b2eee63 100644
--- a/resources/views/layouts/parts/navbar.twig
+++ b/resources/views/layouts/parts/navbar.twig
@@ -55,16 +55,14 @@
{{ elements.toolbar_item(user.name, url('users', {'action': 'view'}), 'users', 'icon icon-icon_angel') }}
{% endif %}
- {% if has_permission_to('user_settings') or has_permission_to('logout') %}
- <li class="dropdown">
- <a href="#" class="dropdown-toggle" data-toggle="dropdown">
- <span class="caret"></span>
- </a>
- <ul class="dropdown-menu" role="menu">
- {{ menuUserSubmenu()|join(" ")|raw }}
- </ul>
- </li>
- {% endif %}
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">
+ <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu" role="menu">
+ {{ menuUserSubmenu()|join(" ")|raw }}
+ </ul>
+ </li>
</ul>
{% endblock %}
diff --git a/resources/views/pages/login.twig b/resources/views/pages/login.twig
index da6f4fdf..88326429 100644
--- a/resources/views/pages/login.twig
+++ b/resources/views/pages/login.twig
@@ -50,7 +50,7 @@
<div class="input-group">
<span class="input-group-addon input-lg">{{ m.glyphicon('lock') }}</span>
<input class="form-control input-lg" id="form_password"
- type="password" name="password" value="" placeholder="{{ __('Password') }}">
+ type="password" name="password" value="" placeholder="{{ __('Password') }}">
</div>
</div>
@@ -93,8 +93,10 @@
</a>
</div>
</div>
- </div>
- {{ m.glyphicon('info-sign') }} {{ __('Please note: You have to activate cookies!') }}
+ <div class="col-md-12 text-center">
+ {{ m.glyphicon('info-sign') }} {{ __('Please note: You have to activate cookies!') }}
+ </div>
+ </div>
</div>
{% endblock %}
diff --git a/resources/views/pages/user-shifts.html b/resources/views/pages/user-shifts.html
index 9ac501da..d5a98f80 100644
--- a/resources/views/pages/user-shifts.html
+++ b/resources/views/pages/user-shifts.html
@@ -55,12 +55,12 @@
<div class="col-md-6">
<button class="btn btn-info btn-sm hidden-print" style="margin-top: 20px; margin-bottom:10px" type="button"
data-toggle="collapse"
- data-target="#collapseRoomSelect" aria-expanded="false"
- aria-controls="collapseRoomSelect"
+ data-target="#collapseShiftsFilterSelect" aria-expanded="true"
+ aria-controls="collapseShiftsFilterSelect"
>
collapse/show filters
</button>
- <div class="collapse in" id="collapseRoomSelect">
+ <div class="collapse in" id="collapseShiftsFilterSelect">
<div class="row">
<div class="col-xs-4 col-xxs-12">%room_select%</div>
<div class="col-xs-4 col-xxs-12">%type_select%</div>
@@ -79,5 +79,5 @@
%shifts_table%
<div class="hidden-print">
-%ical_text%
+ %ical_text%
</div> \ No newline at end of file
diff --git a/src/Helpers/Translation/TranslationServiceProvider.php b/src/Helpers/Translation/TranslationServiceProvider.php
index 09337dad..62247000 100644
--- a/src/Helpers/Translation/TranslationServiceProvider.php
+++ b/src/Helpers/Translation/TranslationServiceProvider.php
@@ -5,6 +5,7 @@ namespace Engelsystem\Helpers\Translation;
use Engelsystem\Config\Config;
use Engelsystem\Container\ServiceProvider;
use Gettext\Translations;
+use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Session\Session;
class TranslationServiceProvider extends ServiceProvider
@@ -67,14 +68,18 @@ class TranslationServiceProvider extends ServiceProvider
public function getTranslator(string $locale): GettextTranslator
{
if (!isset($this->translators[$locale])) {
- $file = $this->app->get('path.lang') . '/' . $locale . '/default.mo';
+ $file = $this->getFile($locale);
/** @var GettextTranslator $translator */
$translator = $this->app->make(GettextTranslator::class);
/** @var Translations $translations */
$translations = $this->app->make(Translations::class);
- $translations->addFromMoFile($file);
+ if (Str::endsWith($file, '.mo')) {
+ $translations->addFromMoFile($file);
+ } else {
+ $translations->addFromPoFile($file);
+ }
$translator->loadTranslations($translations);
@@ -83,4 +88,20 @@ class TranslationServiceProvider extends ServiceProvider
return $this->translators[$locale];
}
+
+ /**
+ * @param string $locale
+ * @return string
+ */
+ protected function getFile(string $locale): string
+ {
+ $filepath = $file = $this->app->get('path.lang') . '/' . $locale . '/default';
+ $file = $filepath . '.mo';
+
+ if (!file_exists($file)) {
+ $file = $filepath . '.po';
+ }
+
+ return $file;
+ }
}
diff --git a/tests/Unit/Helpers/Translation/Assets/ba_RR/default.po b/tests/Unit/Helpers/Translation/Assets/ba_RR/default.po
new file mode 100644
index 00000000..887e2daa
--- /dev/null
+++ b/tests/Unit/Helpers/Translation/Assets/ba_RR/default.po
@@ -0,0 +1,3 @@
+# Testing content
+msgid "foo.bar"
+msgstr "B Arr!"
diff --git a/tests/Unit/Helpers/Translation/TranslationServiceProviderTest.php b/tests/Unit/Helpers/Translation/TranslationServiceProviderTest.php
index 91307bdd..dc8e8af5 100644
--- a/tests/Unit/Helpers/Translation/TranslationServiceProviderTest.php
+++ b/tests/Unit/Helpers/Translation/TranslationServiceProviderTest.php
@@ -12,7 +12,7 @@ use Symfony\Component\HttpFoundation\Session\Session;
class TranslationServiceProviderTest extends ServiceProviderTest
{
/**
- * @covers \Engelsystem\Helpers\Translation\TranslationServiceProvider::register()
+ * @covers \Engelsystem\Helpers\Translation\TranslationServiceProvider::register
*/
public function testRegister(): void
{
@@ -30,7 +30,7 @@ class TranslationServiceProviderTest extends ServiceProviderTest
/** @var TranslationServiceProvider|MockObject $serviceProvider */
$serviceProvider = $this->getMockBuilder(TranslationServiceProvider::class)
->setConstructorArgs([$app])
- ->setMethods(['setLocale'])
+ ->onlyMethods(['setLocale'])
->getMock();
$app->expects($this->exactly(2))
@@ -71,7 +71,7 @@ class TranslationServiceProviderTest extends ServiceProviderTest
}
/**
- * @covers \Engelsystem\Helpers\Translation\TranslationServiceProvider::getTranslator()
+ * @covers \Engelsystem\Helpers\Translation\TranslationServiceProvider::getTranslator
*/
public function testGetTranslator(): void
{
@@ -87,4 +87,20 @@ class TranslationServiceProviderTest extends ServiceProviderTest
// Retry from cache
$serviceProvider->getTranslator('fo_OO');
}
+
+ /**
+ * @covers \Engelsystem\Helpers\Translation\TranslationServiceProvider::getTranslator
+ * @covers \Engelsystem\Helpers\Translation\TranslationServiceProvider::getFile
+ */
+ public function testGetTranslatorFromPo(): void
+ {
+ $app = $this->getApp(['get']);
+ $this->setExpects($app, 'get', ['path.lang'], __DIR__ . '/Assets');
+
+ $serviceProvider = new TranslationServiceProvider($app);
+
+ // Get translator using a .po file
+ $translator = $serviceProvider->getTranslator('ba_RR');
+ $this->assertEquals('B Arr!', $translator->gettext('foo.bar'));
+ }
}
diff --git a/yarn.lock b/yarn.lock
index cec94e2e..c49eb596 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3328,7 +3328,7 @@ moment-timezone@^0.4.0:
dependencies:
moment ">= 2.6.0"
-"moment@>= 2.6.0", moment@^2.10, moment@^2.10.2, moment@^2.8.2:
+"moment@>= 2.6.0", moment@^2.10, moment@^2.10.2, moment@^2.12.0:
version "2.24.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==