Compare commits

..

913 commits
1.3 ... master

Author SHA1 Message Date
Sergio Brighenti
a00fd5a5e2
Merge pull request #579 from weblate/weblate-xbackbone-xbackbone
Translations update from Hosted Weblate
2024-08-15 15:10:15 +02:00
Oğuz Ersen
0fe1748077
Translated using Weblate (Turkish)
Currently translated at 100.0% (162 of 162 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/tr/
2024-08-04 20:28:59 +00:00
Hani Rouatbi
c58a114ac9
Translated using Weblate (Arabic)
Currently translated at 100.0% (162 of 162 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ar/
2024-08-04 20:28:58 +00:00
Sectly
23f6b18ffe
Translated using Weblate (Dutch)
Currently translated at 100.0% (162 of 162 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nl/
2024-08-04 20:28:58 +00:00
bittin1ddc447d824349b2
4ad4b20762
Translated using Weblate (Swedish)
Currently translated at 100.0% (162 of 162 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/sv/
2024-08-04 20:28:57 +00:00
gallegonovato
1c2801e2f0
Translated using Weblate (Spanish)
Currently translated at 100.0% (162 of 162 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/es/
2024-08-04 20:28:56 +00:00
Sergio Brighenti
a918c95a56
Merge pull request #578 from sergix44/dependabot/npm_and_yarn/braces-3.0.3
Bump braces from 3.0.2 to 3.0.3
2024-08-04 22:28:51 +02:00
Sergio Brighenti
fa85310d1d
Merge pull request #586 from samrzhevsky/master
Fix page count calculation
2024-08-04 22:28:21 +02:00
samrzhevsky
8fabc57f04
Fix page count calculation 2024-07-27 23:39:38 +03:00
dependabot[bot]
cd28fe11af
Bump braces from 3.0.2 to 3.0.3
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-16 14:57:30 +00:00
Sergio Brighenti
687c503efd
Merge pull request #561 from weblate/weblate-xbackbone-xbackbone
Translations update from Hosted Weblate
2024-06-15 21:59:26 +02:00
Vin
c6f087e092
Translated using Weblate (Russian)
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ru/
2024-06-15 21:57:04 +02:00
tabby
4718d8a0b1
Translated using Weblate (Korean)
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ko/
2024-06-15 21:57:04 +02:00
ssantos
bb97621041
Translated using Weblate (Portuguese)
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pt/
2024-06-15 21:57:04 +02:00
Eryk Michalak
49e3110273
Translated using Weblate (Polish)
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pl/
2024-06-15 21:57:04 +02:00
Mikachu
41b26f45b7
Translated using Weblate (Dutch)
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nl/
2024-06-15 21:57:04 +02:00
Gaming Tibor
f2ae33bcd7
Translated using Weblate (Hungarian)
Currently translated at 99.3% (160 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/hu/
2024-06-15 21:57:04 +02:00
LeonMtn05
96972b8222
Translated using Weblate (German)
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2024-06-15 21:57:04 +02:00
Sergio Brighenti
06562d194b
Merge pull request #572 from lsgd/master
Enable upload from clipboard
2024-06-15 21:57:01 +02:00
Lukas Schulze
a600a21753 Add link to remove tags filter 2024-04-30 16:15:41 +02:00
lsgd
1193c96966
Merge branch 'sergix44:master' into master 2024-04-30 15:49:54 +02:00
Lukas Schulze
4946750beb enable upload via clipboard paste 2024-04-30 15:49:01 +02:00
Sergio Brighenti
cc8ed75ec6 Update changelog 2024-01-14 14:24:40 +01:00
Sergio Brighenti
9dbf1400f6 Update lock 2024-01-14 14:16:56 +01:00
Sergio Brighenti
dd5e30abe2 Update list preview icon color 2024-01-14 14:15:16 +01:00
Sergio Brighenti
59c6691585
Merge pull request #553 from lsgd/master
Add link to file preview which opens the file itself
2024-01-14 14:04:28 +01:00
Sergio Brighenti
bd3ca55e20
Merge pull request #556 from sergix44/dependabot/composer/aws/aws-sdk-php-3.294.5
Bump aws/aws-sdk-php from 3.271.3 to 3.294.5
2024-01-04 00:06:30 +01:00
Sergio Brighenti
04a6770f19
Merge pull request #546 from weblate/weblate-xbackbone-xbackbone
Translations update from Hosted Weblate
2024-01-04 00:05:25 +01:00
ssantos
a8f26558a6
Translated using Weblate (Portuguese)
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pt/
2024-01-04 00:04:44 +01:00
Oğuz Ersen
853ed10e1c
Translated using Weblate (Turkish)
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/tr/
2024-01-04 00:04:44 +01:00
Xin Mu
25ed553ed1
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/zh_Hans/
2024-01-04 00:04:44 +01:00
Kalle Laine
ab211c571a
Translated using Weblate (Finnish)
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fi/
2024-01-04 00:04:44 +01:00
Reza Almanda
3f9bc8ae7c
Translated using Weblate (Indonesian)
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/id/
2024-01-04 00:04:44 +01:00
Caspar Coen Graafstal
2c33f19785
Translated using Weblate (Dutch)
Currently translated at 98.7% (159 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nl/
2024-01-04 00:04:44 +01:00
春末丶冬初
57a66f83bd
Translated using Weblate (Chinese (Simplified))
Currently translated at 98.1% (158 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/zh_Hans/
2024-01-04 00:04:44 +01:00
J. Lavoie
41870dce76
Translated using Weblate (French (Canada))
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr_CA/
2024-01-04 00:04:44 +01:00
J. Lavoie
f5fba276fd
Translated using Weblate (French)
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr/
2024-01-04 00:04:44 +01:00
J. Lavoie
b2403baf0b
Translated using Weblate (German)
Currently translated at 99.3% (160 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2024-01-04 00:04:44 +01:00
J. Lavoie
634d301e80
Translated using Weblate (Italian)
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/
2024-01-04 00:04:44 +01:00
a
a0f82ae4ab
Translated using Weblate (Russian)
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ru/
2024-01-04 00:04:44 +01:00
Luna Jernberg
a5e63dcd33
Translated using Weblate (Swedish)
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/sv/
2024-01-04 00:04:44 +01:00
gallegonovato
8cda536340
Translated using Weblate (Spanish)
Currently translated at 100.0% (161 of 161 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/es/
2024-01-04 00:04:44 +01:00
Sergio Brighenti
5d99a96f84
Merge pull request #554 from SrS2225a/master
Add support for kde integration
2024-01-04 00:04:40 +01:00
dependabot[bot]
68a428b33f
Bump aws/aws-sdk-php from 3.271.3 to 3.294.5
Bumps [aws/aws-sdk-php](https://github.com/aws/aws-sdk-php) from 3.271.3 to 3.294.5.
- [Release notes](https://github.com/aws/aws-sdk-php/releases)
- [Commits](https://github.com/aws/aws-sdk-php/compare/3.271.3...3.294.5)

---
updated-dependencies:
- dependency-name: aws/aws-sdk-php
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-22 00:11:25 +00:00
SrS2225a
76eecb298b added support for kde intergration 2023-12-17 13:44:29 -08:00
Sergio Brighenti
903f87e693
Merge pull request #547 from samrzhevsky/master
LDAP fixes
2023-12-17 10:45:49 +01:00
Lukas Schulze
eee497669e Add link to file preview which opens the file itself 2023-12-10 20:22:49 +01:00
samrzhevsky
e7a0a99fe6
LDAP fixes
1. Changed `ldap_connect` error handling. `ldap_error` expects `LDAP\Connection`, and `ldap_connect` returns `false` if the syntax check fails
2. Fixed deprecation: passing null to parameter (`$ignore`) of type string
3. Fixed PHPDoc types for compatibility with PHP 8.1+
2023-08-28 00:31:22 +03:00
Sergio Brighenti
ee55a9ea3a
Merge pull request #539 from weblate/weblate-xbackbone-xbackbone
Translations update from Hosted Weblate
2023-08-14 22:46:06 +02:00
Sergio Brighenti
7f07ecdfad
Merge pull request #507 from SrS2225a/master
Support For Vanity Urls
2023-08-14 22:45:48 +02:00
SrS2225a
a11a9307ea corrected styleCI issues for sql querys 2023-08-13 23:40:06 -07:00
SrS2225a
32b8a16c40 readded missing function createVanity 2023-08-13 23:29:41 -07:00
SrS2225a
c8c540ff50 fixed grunt conflicts and corrected the lang key 2023-08-13 22:45:37 -07:00
SrS2225a
c8fac0a578 Merge branch 'master' of https://github.com/SergiX44/XBackBone
# Conflicts:
#	app/Controllers/MediaController.php
2023-08-13 22:00:19 -07:00
J. Lavoie
35a2e59c6c
Translated using Weblate (French (Canada))
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr_CA/
2023-06-29 23:49:23 +02:00
J. Lavoie
95b4435e6f
Translated using Weblate (Italian)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/
2023-06-29 23:49:22 +02:00
Sergio Brighenti
0951638dc8
Merge pull request #533 from cursey/feature/upload-response-raw-url 2023-05-31 09:54:11 +02:00
cursey
d3fde3e298
Upload: Response now includes raw_url as well 2023-05-29 21:57:55 -07:00
Sergio Brighenti
c7a6dd1c16 update version 2023-05-27 17:09:22 +02:00
Sergio Brighenti
327005833c update changelog 2023-05-27 17:06:28 +02:00
Sergio Brighenti
49c7b60137
Merge pull request #532 from nebulade/patch-1 2023-05-25 10:39:29 +02:00
Johannes Zellner
4d791aad06
Make AuthController.php compatible with php8.1+
php api changed https://www.php.net/manual/en/class.ldap-result.php
2023-05-25 10:12:42 +02:00
Sergio Brighenti
9c85698c28 drop azure blob storage 2023-05-24 00:50:57 +02:00
Sergio Brighenti
91d05884b9
Merge pull request #513 from SergiX44/support_php_82 2023-05-23 23:03:21 +02:00
Sergio Brighenti
707d32ff25
Merge pull request #522 from weblate/weblate-xbackbone-xbackbone 2023-05-23 23:00:32 +02:00
Sergio Brighenti
3702753ade
Merge pull request #529 from SergiX44/analysis-JG7k4o 2023-05-23 23:00:17 +02:00
StyleCI Bot
5e6ba5a0b3
Apply fixes from StyleCI
[ci skip] [skip ci]
2023-05-23 21:00:00 +00:00
Sergio Brighenti
7a4be04917 fix failing test 2023-05-23 22:59:55 +02:00
Reza Almanda
4cf84fdafe
Translated using Weblate (Indonesian)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/id/
2023-05-23 21:42:28 +02:00
Daniel Benyšek
d86d3826d5
Translated using Weblate (Czech)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/cs/
2023-05-23 21:42:28 +02:00
Alexey Gudym
04e42bd456
Translated using Weblate (Russian)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ru/
2023-05-23 21:42:28 +02:00
Sergio Brighenti
c3ebf4d39b
Merge pull request #526 from SergiX44/dependabot/composer/guzzlehttp/psr7-1.9.1 2023-05-23 21:42:23 +02:00
Sergio Brighenti
e05600ec26
Merge pull request #527 from SergiX44/analysis-a60gDB 2023-04-19 23:52:54 +02:00
StyleCI Bot
ae5e2cffb3
Apply fixes from StyleCI
[ci skip] [skip ci]
2023-04-19 21:23:06 +00:00
dependabot[bot]
45dc6ff87b
Bump guzzlehttp/psr7 from 1.9.0 to 1.9.1
Bumps [guzzlehttp/psr7](https://github.com/guzzle/psr7) from 1.9.0 to 1.9.1.
- [Release notes](https://github.com/guzzle/psr7/releases)
- [Changelog](https://github.com/guzzle/psr7/blob/1.9.1/CHANGELOG.md)
- [Commits](https://github.com/guzzle/psr7/compare/1.9.0...1.9.1)

---
updated-dependencies:
- dependency-name: guzzlehttp/psr7
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-19 21:22:57 +00:00
SrS2225a
69d6ae298e style ci fix 2023-01-17 21:39:40 -08:00
SrS2225a
acaceea3f1 added fixes per request by author 2023-01-17 21:17:52 -08:00
Sergio Brighenti
589720d81b update lock file 2023-01-16 21:06:05 +01:00
Sergio Brighenti
46502074a0 swap implementation psr7
drop direct impl call
2023-01-16 21:03:36 +01:00
Sergio Brighenti
020ad75238 update deps 2023-01-16 18:41:26 +01:00
Sergio Brighenti
02be446c2e
Merge pull request #503 from weblate/weblate-xbackbone-xbackbone 2022-12-31 16:54:35 +01:00
SrS2225a
58744bd092 fixed code styling issues 2022-12-30 21:44:51 -08:00
Özgür
2a789d0f8a
Translated using Weblate (Turkish)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/tr/
2022-12-29 01:14:02 +01:00
Oğuz Ersen
e8c3b75b99
Translated using Weblate (Turkish)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/tr/
2022-12-29 01:14:02 +01:00
Sergio Brighenti
b790d21e01
Merge pull request #502 from SergiX44/dependabot/npm_and_yarn/qs-6.11.0 2022-12-29 01:13:58 +01:00
Sergio Brighenti
237e2f7a57
Merge pull request #501 from samrzhevsky/master 2022-12-29 00:58:19 +01:00
SrS2225a
4deedda3b6 bug fixes for vanity links 2022-12-19 19:42:08 -08:00
SrS2225a
d6dce885d2 added support for vanity links 2022-12-18 21:54:18 -08:00
SrS2225a
d823230c35 Merge branch 'master' of https://github.com/SrS2225a/XBackBone 2022-12-18 21:53:08 -08:00
SrS2225a
634956cb2d added support for vanity links 2022-12-18 21:22:15 -08:00
dependabot[bot]
6a05a5dac8
Bump qs from 6.10.1 to 6.11.0
Bumps [qs](https://github.com/ljharb/qs) from 6.10.1 to 6.11.0.
- [Release notes](https://github.com/ljharb/qs/releases)
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.10.1...v6.11.0)

---
updated-dependencies:
- dependency-name: qs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-06 14:36:59 +00:00
Sam Rzhevsky
9bddf5940b
Fix filter by tag
Fixed filter by non-existent tag
2022-11-30 21:13:58 +03:00
Sergio Brighenti
b4f720cf21 update changelog 2022-11-27 22:44:17 +01:00
Sergio Brighenti
2bce85ea6a upgraded phpunit 2022-11-27 22:37:22 +01:00
Sergio Brighenti
e2c3aaf89a update deps 2022-11-27 22:34:42 +01:00
Sergio Brighenti
a9c492c99d
Merge pull request #499 from samrzhevsky/master 2022-11-27 22:31:43 +01:00
Sam Rzhevsky
3bb2b7b4ea
Logging fix
PHP Notice: Undefined property: stdClass::$name in app/Controllers/ExportController.php on line 28
2022-11-27 21:37:37 +03:00
Sergio Brighenti
2ddaac8e5a
Merge pull request #496 from FingerlessGlov3s/patch-2
remove duplicate lines
2022-11-08 10:39:27 +01:00
FingerlessGloves
e4e67657d8
remove duplicate lines
meta tags are oddly repeated
2022-11-02 23:18:01 +00:00
Sergio Brighenti
1e0e549938
Merge pull request #495 from FingerlessGlov3s/patch-1
Fixes redirect bug for reverse proxy
2022-11-02 21:04:50 +01:00
FingerlessGloves
9caee0dc5b
Fixes redirect bug for reverse proxy
If you are logged out of XBackbone, and browse to `/upload` for example the direction holds the request URI, which isn't an issue unless you've got a reverse proxy in front of it.
For example, if the app is running in a container on port 8080, and Traefik is doing the reverse proxying, when you try login after being redirected to the login page, the redirectTo session contains the internal port number, which causes you to be redirected to `https://mydomain.com:8080/upload` instead of the correct `https://mydomain.com/upload`
2022-10-31 22:39:32 +00:00
Sergio Brighenti
99c9f50294
Merge pull request #480 from weblate/weblate-xbackbone-xbackbone
Translations update from Hosted Weblate
2022-10-26 10:05:38 +02:00
Ho_ Tsuyosi (스넾)
48ba6f009e
Translated using Weblate (Japanese)
Currently translated at 66.2% (106 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ja/
2022-10-26 10:05:28 +02:00
Zsolt Nagy
8379eb41ef
Translated using Weblate (Hungarian)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/hu/
2022-10-26 10:05:28 +02:00
PVPMaster0001
90ac32621a
Translated using Weblate (Finnish)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fi/
2022-10-26 10:05:28 +02:00
PVPMaster0001
80152ac31a
Translated using Weblate (Japanese)
Currently translated at 53.1% (85 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ja/
2022-10-26 10:05:28 +02:00
Denis Branisteanu
3aac808525
Translated using Weblate (Romanian)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ro/
2022-10-26 10:05:28 +02:00
Denis Branisteanu
57122a8f96
Translated using Weblate (Romanian)
Currently translated at 6.2% (10 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ro/
2022-10-26 10:05:28 +02:00
Denis Branisteanu
e155a33f86
Added translation using Weblate (Romanian) 2022-10-26 10:05:28 +02:00
Hien
46341b4e03
Translated using Weblate (Vietnamese)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/vi/
2022-10-26 10:05:28 +02:00
Özgür
a1b3ab47ab
Translated using Weblate (Turkish)
Currently translated at 91.2% (146 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/tr/
2022-10-26 10:05:28 +02:00
Özgür
904795029c
Translated using Weblate (Turkish)
Currently translated at 41.8% (67 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/tr/
2022-10-26 10:05:28 +02:00
Sergio Brighenti
1eebc961ab
Merge pull request #479 from SergiX44/dependabot/composer/guzzlehttp/guzzle-7.4.5
Bump guzzlehttp/guzzle from 7.4.4 to 7.4.5
2022-10-26 10:05:24 +02:00
Sergio Brighenti
0f6cc027f7
Merge pull request #490 from SergiX44/dependabot/composer/twig/twig-2.15.3
Bump twig/twig from 2.15.1 to 2.15.3
2022-10-26 10:05:16 +02:00
dependabot[bot]
c83539245b
Bump twig/twig from 2.15.1 to 2.15.3
Bumps [twig/twig](https://github.com/twigphp/Twig) from 2.15.1 to 2.15.3.
- [Release notes](https://github.com/twigphp/Twig/releases)
- [Changelog](https://github.com/twigphp/Twig/blob/v2.15.3/CHANGELOG)
- [Commits](https://github.com/twigphp/Twig/compare/v2.15.1...v2.15.3)

---
updated-dependencies:
- dependency-name: twig/twig
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-30 20:25:52 +00:00
Sergio Brighenti
7af93ae136
Merge pull request #484 from FunctionDJ/patch-1
Update minimum PHP version in docs
2022-08-04 14:44:00 +02:00
Function
96b61b5834
Update minimum PHP version in docs 2022-08-04 13:45:04 +02:00
dependabot[bot]
d6d4fbcf94
Bump guzzlehttp/guzzle from 7.4.4 to 7.4.5
Bumps [guzzlehttp/guzzle](https://github.com/guzzle/guzzle) from 7.4.4 to 7.4.5.
- [Release notes](https://github.com/guzzle/guzzle/releases)
- [Changelog](https://github.com/guzzle/guzzle/blob/master/CHANGELOG.md)
- [Commits](https://github.com/guzzle/guzzle/compare/7.4.4...7.4.5)

---
updated-dependencies:
- dependency-name: guzzlehttp/guzzle
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-21 21:00:12 +00:00
Sergio Brighenti
edc20c30f7 update changelog 2022-06-20 18:13:15 +02:00
Sergio Brighenti
66c708ab77
Update CHANGELOG.md 2022-06-20 18:11:49 +02:00
Sergio Brighenti
469ed2068c fix 2022-06-20 17:36:00 +02:00
Sergio Brighenti
8242ef5443 compress the release zip 2022-06-20 13:56:50 +02:00
Sergio Brighenti
0f1c458e76 fix resize 2022-06-20 13:06:05 +02:00
Sergio Brighenti
87a1b8517a remove workflow 2022-06-19 23:42:33 +02:00
Sergio Brighenti
5399133b65 updated changelog 2022-06-19 23:37:55 +02:00
Sergio Brighenti
59124e994a bump minimum php version 2022-06-19 23:30:14 +02:00
Sergio Brighenti
15e2c3512c
Merge pull request #474 from SergiX44/analysis-o7r0ep
Apply fixes from StyleCI
2022-06-19 23:21:46 +02:00
StyleCI Bot
7f4f262249
Apply fixes from StyleCI
[ci skip] [skip ci]
2022-06-19 21:21:38 +00:00
Sergio Brighenti
1979c3f318 fixed discord video embedding 2022-06-19 23:21:27 +02:00
Sergio Brighenti
8bf64a6537 fix deprecation issue 2022-06-19 22:00:36 +02:00
Sergio Brighenti
d21f678843 fix issue with post max size = 0
fixes #453
2022-06-19 20:20:21 +02:00
Sergio Brighenti
67fdb89ce3 update deps 2022-06-19 20:17:07 +02:00
Sergio Brighenti
badb0c681d
Merge pull request #470 from weblate/weblate-xbackbone-xbackbone
Translations update from Hosted Weblate
2022-06-19 14:39:48 +02:00
flakka2022
dfc3ab3bce
Translated using Weblate (Persian)
Currently translated at 16.2% (26 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fa/
2022-06-18 03:20:42 +02:00
flakka2022
87b7f245c9
Added translation using Weblate (Persian) 2022-06-17 02:32:48 +02:00
春末丶冬初
e8a9cfea92
Translated using Weblate (Chinese (Simplified))
Currently translated at 95.0% (152 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/zh_Hans/
2022-06-15 13:31:21 +02:00
Hien
6d43b62677
Translated using Weblate (Vietnamese)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/vi/
2022-06-15 12:18:18 +02:00
Hien
79ed8eeb5e
Added translation using Weblate (Vietnamese) 2022-06-14 06:42:37 +02:00
Sergio Brighenti
2ec7457fcd
Merge pull request #468 from cloudron-io/ldap_auto_create
ldap: ensure missing ldap user is auto-created
2022-06-12 18:12:55 +02:00
Sergio Brighenti
659b31548e
Merge pull request #467 from cloudron-io/ldap_docs
improve ldap docs
2022-06-12 17:44:04 +02:00
Girish Ramakrishnan
538f3d6bd8 ldap: ensure missing ldap user is auto-created 2022-06-12 08:35:22 -07:00
Girish Ramakrishnan
1a418d46e9 ldap docs: remove superfluous brackets in search filter
should also fix #376
2022-06-11 19:33:46 -07:00
Sergio Brighenti
83ec0b5201
Merge pull request #464 from SergiX44/dependabot/composer/guzzlehttp/guzzle-7.4.4
Bump guzzlehttp/guzzle from 7.4.1 to 7.4.4
2022-06-11 13:45:31 +02:00
Sergio Brighenti
ec6b692cc8
Merge pull request #463 from weblate/weblate-xbackbone-xbackbone
Translations update from Hosted Weblate
2022-06-11 13:45:23 +02:00
dependabot[bot]
aa2d85f5ee
Bump guzzlehttp/guzzle from 7.4.1 to 7.4.4
Bumps [guzzlehttp/guzzle](https://github.com/guzzle/guzzle) from 7.4.1 to 7.4.4.
- [Release notes](https://github.com/guzzle/guzzle/releases)
- [Changelog](https://github.com/guzzle/guzzle/blob/master/CHANGELOG.md)
- [Commits](https://github.com/guzzle/guzzle/compare/7.4.1...7.4.4)

---
updated-dependencies:
- dependency-name: guzzlehttp/guzzle
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-10 06:44:33 +00:00
Kowski
a6bfc32869
Translated using Weblate (Serbian)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/sr/
2022-06-06 11:26:47 +02:00
Kowski
188dce1a56
Added translation using Weblate (Serbian) 2022-06-06 10:38:05 +02:00
Sergio Brighenti
94638ae941
Merge pull request #461 from SergiX44/dependabot/npm_and_yarn/grunt-1.5.3
Bump grunt from 1.5.2 to 1.5.3
2022-06-01 14:30:13 +02:00
dependabot[bot]
f1728d7e91
Bump grunt from 1.5.2 to 1.5.3
Bumps [grunt](https://github.com/gruntjs/grunt) from 1.5.2 to 1.5.3.
- [Release notes](https://github.com/gruntjs/grunt/releases)
- [Changelog](https://github.com/gruntjs/grunt/blob/main/CHANGELOG)
- [Commits](https://github.com/gruntjs/grunt/compare/v1.5.2...v1.5.3)

---
updated-dependencies:
- dependency-name: grunt
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-26 00:12:41 +00:00
Sergio Brighenti
6a4b49f617
Merge pull request #456 from SergiX44/dependabot/npm_and_yarn/async-2.6.4
Bump async from 2.6.3 to 2.6.4
2022-05-17 21:42:56 +02:00
dependabot[bot]
b856ffd530
Bump async from 2.6.3 to 2.6.4
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-10 21:32:34 +00:00
Sergio Brighenti
1e12a57a0d
Merge pull request #455 from SergiX44/dependabot/npm_and_yarn/grunt-1.5.2
Bump grunt from 1.4.1 to 1.5.2
2022-05-10 23:20:30 +02:00
Sergio Brighenti
9f64b15f91
Merge pull request #454 from weblate/weblate-xbackbone-xbackbone
Translations update from Hosted Weblate
2022-05-10 23:20:20 +02:00
dependabot[bot]
daf3665414
Bump grunt from 1.4.1 to 1.5.2
Bumps [grunt](https://github.com/gruntjs/grunt) from 1.4.1 to 1.5.2.
- [Release notes](https://github.com/gruntjs/grunt/releases)
- [Changelog](https://github.com/gruntjs/grunt/blob/main/CHANGELOG)
- [Commits](https://github.com/gruntjs/grunt/compare/v1.4.1...v1.5.2)

---
updated-dependencies:
- dependency-name: grunt
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-27 06:27:02 +00:00
Myunker 1MP4C7 Eredzhebov
9c172ce417
Translated using Weblate (Bulgarian)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/bg/
2022-04-26 11:14:42 +02:00
Sergio Brighenti
d123eda772
Merge pull request #452 from R0GGER/patch-1
Fix for issue #450
2022-04-17 14:21:59 +02:00
R0GGER
c830a31157
Fix for issue #450 2022-04-16 22:56:38 +02:00
Sergio Brighenti
714a5e9d6a
Merge pull request #449 from weblate/weblate-xbackbone-xbackbone
Translations update from Hosted Weblate
2022-04-10 22:57:56 +02:00
Rubens
e995a2e539
Translated using Weblate (Catalan)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ca/
2022-04-10 22:57:49 +02:00
Rubens
063ecff83f
Added translation using Weblate (Catalan) 2022-04-10 22:57:49 +02:00
Sergio Brighenti
bfed200dcd
Merge pull request #446 from SergiX44/dependabot/composer/guzzlehttp/psr7-1.8.4
Bump guzzlehttp/psr7 from 1.8.3 to 1.8.4
2022-04-10 22:57:46 +02:00
dependabot[bot]
edb1ced704
Bump guzzlehttp/psr7 from 1.8.3 to 1.8.4
Bumps [guzzlehttp/psr7](https://github.com/guzzle/psr7) from 1.8.3 to 1.8.4.
- [Release notes](https://github.com/guzzle/psr7/releases)
- [Changelog](https://github.com/guzzle/psr7/blob/1.8.4/CHANGELOG.md)
- [Commits](https://github.com/guzzle/psr7/compare/1.8.3...1.8.4)

---
updated-dependencies:
- dependency-name: guzzlehttp/psr7
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-29 22:16:12 +00:00
Sergio Brighenti
8fc0a35749
Merge pull request #440 from weblate/weblate-xbackbone-xbackbone
Translations update from Hosted Weblate
2022-03-10 18:43:01 +01:00
Jaakko Törrö
6ce9041639
Translated using Weblate (Finnish)
Currently translated at 78.7% (126 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fi/
2022-02-18 23:55:58 +01:00
kubab6
d4aca7d3a5
Translated using Weblate (Polish)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pl/
2022-02-18 23:55:58 +01:00
Allan Nordhøy
114504bde0
Translated using Weblate (Norwegian Bokmål)
Currently translated at 92.5% (148 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nb_NO/
2022-02-12 19:54:36 +01:00
SergiX44
7634741676 implemented cache for remote filesystems
fixes #424
2022-02-08 22:24:16 +01:00
SergiX44
dea92d9ada fix test 2022-01-30 11:58:43 +01:00
Sergio Brighenti
1390ee76c6
Merge pull request #431 from weblate/weblate-xbackbone-xbackbone
Translations update from Hosted Weblate
2022-01-30 11:52:11 +01:00
Emilion DK
8f4f523b22
Translated using Weblate (Danish)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/da/
2022-01-17 12:56:08 +01:00
SAMI SAFHI
6134667b32
Translated using Weblate (Arabic)
Currently translated at 63.1% (101 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ar/
2022-01-02 10:52:20 +01:00
春末丶冬初
c16567d0a6
Translated using Weblate (Chinese (Simplified))
Currently translated at 93.7% (150 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/zh_Hans/
2021-12-29 14:53:11 +01:00
春末丶冬初
13979d9117
Translated using Weblate (Chinese (Simplified))
Currently translated at 3.7% (6 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/zh_Hans/
2021-12-28 13:52:38 +01:00
春末丶冬初
31ebf49944
Added translation using Weblate (Chinese (Simplified)) 2021-12-28 13:48:45 +01:00
Sergio Brighenti
a273235b0c
Add files via upload 2021-10-25 12:30:53 +02:00
SergiX44
2ae95bc409 release: 3.5.1 2021-10-22 19:50:34 +02:00
Sergio Brighenti
8894d2bb1c
Merge pull request #398 from TheDevFreak/patch-1
fix for discord UA change.
2021-10-16 21:08:36 +02:00
TheDevFreak
37b2f5a423
fix for discord UA change. 2021-10-16 17:44:57 +01:00
Sergio Brighenti
e0f18fa873
Merge pull request #395 from SergiX44/update_dependencies
update deps
2021-10-06 10:04:31 +02:00
SergiX44
23a44363c3 fix dependencies vulns 2021-10-06 09:58:48 +02:00
SergiX44
3f0f491e6e fix dependencies vulns 2021-10-06 09:53:57 +02:00
Sergio Brighenti
8469e05b02
Merge pull request #393 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2021-10-05 19:17:32 +02:00
Kami
ffe8d03b19
Translated using Weblate (Spanish)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/es/
2021-10-02 15:38:39 +02:00
Sergio Brighenti
11c0d578e6 update docs 2021-09-05 18:03:26 +02:00
Sergio Brighenti
26131aa1fa translations update 2021-09-05 18:01:51 +02:00
Sergio Brighenti
1954a3a18f
Merge pull request #379 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2021-09-05 18:00:45 +02:00
오로라
cfd1ff343b
Translated using Weblate (Korean)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ko/
2021-09-05 17:56:53 +02:00
오로라
d71bf25dbc
Translated using Weblate (Korean)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ko/
2021-09-05 17:56:53 +02:00
오로라
bb5ea36564
Translated using Weblate (Korean)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ko/
2021-09-05 17:56:53 +02:00
ssantos
c2dd20176f
Translated using Weblate (Portuguese)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pt/
2021-09-05 17:56:53 +02:00
오로라
dec28fa0e5
Translated using Weblate (Korean)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ko/
2021-09-05 17:56:53 +02:00
오로라
2cd0147888
Added translation using Weblate (Korean) 2021-09-05 17:56:53 +02:00
Sergio Brighenti
6523e87520 dropped theme cli command 2021-09-05 17:56:46 +02:00
Sergio Brighenti
a4077b7844
Merge pull request #377 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2021-08-24 23:38:16 +02:00
Gediminas Murauskas
0a48ebcab6
Translated using Weblate (Lithuanian)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/lt/
2021-08-20 22:35:03 +02:00
Gediminas Murauskas
52028f5f98
Translated using Weblate (Lithuanian)
Currently translated at 45.0% (72 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/lt/
2021-08-19 21:20:32 +02:00
J. Lavoie
434a5d89b2
Translated using Weblate (French (Canada))
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr_CA/
2021-08-19 21:20:31 +02:00
J. Lavoie
9da5582ff6
Translated using Weblate (French)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr/
2021-08-19 21:20:30 +02:00
J. Lavoie
addde5972a
Translated using Weblate (Spanish)
Currently translated at 96.8% (155 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/es/
2021-08-19 21:20:30 +02:00
J. Lavoie
01d3147af8
Translated using Weblate (German)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2021-08-19 21:20:30 +02:00
Gediminas Murauskas
662a746809
Added translation using Weblate (Lithuanian) 2021-08-19 19:50:50 +02:00
Sergio Brighenti
a9790f2e25
Merge pull request #374 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2021-08-18 08:59:34 +02:00
Luna Jernberg
a8ced21fef
Translated using Weblate (Swedish)
Currently translated at 100.0% (160 of 160 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/sv/
2021-08-18 08:57:26 +02:00
SergiX44
d8501f2b33 changed url building 2021-08-18 08:57:21 +02:00
Sergio Brighenti
ef0d3c1708
Update test_suite.yml 2021-08-16 14:11:16 +02:00
SergiX44
7c8a8a3920 update lock 2021-08-16 14:03:16 +02:00
Sergio Brighenti
bf47596765
Merge pull request #372 from SergiX44/analysis-orb6Z5
Apply fixes from StyleCI
2021-08-16 13:56:27 +02:00
Sergio Brighenti
ee796c7ded Apply fixes from StyleCI
[ci skip] [skip ci]
2021-08-16 11:56:07 +00:00
SergiX44
3d248c8cb4 support for theme-park.dev 2021-08-16 13:55:47 +02:00
Sergio Brighenti
6ca60757ae
Merge pull request #371 from SergiX44/analysis-L3eD7n
Apply fixes from StyleCI
2021-08-15 21:34:15 +02:00
Sergio Brighenti
f508686e20 Apply fixes from StyleCI
[ci skip] [skip ci]
2021-08-15 19:34:00 +00:00
SergiX44
54284424ff expose configuration option for s3 2021-08-15 21:33:52 +02:00
Sergio Brighenti
2be6d1994d
Merge pull request #370 from SergiX44/analysis-peb5ok
Apply fixes from StyleCI
2021-08-13 09:38:51 +02:00
Sergio Brighenti
fbcdc8f20e Apply fixes from StyleCI
[ci skip] [skip ci]
2021-08-13 07:17:35 +00:00
SergiX44
bfbbf89d79 refactoring 2021-08-13 09:17:24 +02:00
SergiX44
bddcf34c6d Fix default theme 2021-08-12 22:30:26 +02:00
SergiX44
aeeddfed80 update docs 2021-08-12 22:07:53 +02:00
SergiX44
4d494f3f6c update docs 2021-08-12 18:07:05 +02:00
Sergio Brighenti
d7d2b5801c
Merge pull request #366 from olegkaspersky/master
Fixed missing commas in configurations
2021-08-11 18:21:27 +02:00
Sergio Brighenti
748aa807ca
Merge pull request #367 from SergiX44/analysis-1b7dYN
Apply fixes from StyleCI
2021-08-11 18:20:46 +02:00
Sergio Brighenti
52883982ad Apply fixes from StyleCI
[ci skip] [skip ci]
2021-08-11 16:17:59 +00:00
SergiX44
aa402c9e9d release 3.4.1 2021-08-11 18:17:48 +02:00
olegkaspersky
492ad5e8ea
Update configuration.md 2021-08-11 15:17:49 +03:00
olegkaspersky
085704a3b5
Fixed missing commas in configs 2021-08-11 15:17:28 +03:00
SergiX44
f7d8174cb3 improved check for embeds 2021-08-10 23:52:55 +02:00
SergiX44
bcd5959c9a update lock file 2021-08-10 21:47:56 +02:00
SergiX44
405b42e178 missing extension 2021-08-10 21:42:36 +02:00
SergiX44
09e326326b update lock 2021-08-10 21:40:30 +02:00
Sergio Brighenti
d9aa867c9c
Update test_suite.yml 2021-08-10 17:25:18 +02:00
SergiX44
1fc7da479c update lock 2021-08-10 17:14:44 +02:00
SergiX44
db204f8c48 Merge remote-tracking branch 'origin/master' 2021-08-10 13:51:07 +02:00
SergiX44
11e5a52519 changed copy raw mode added toggle for embed 2021-08-10 13:50:58 +02:00
Sergio Brighenti
e5422f9e7d
Merge pull request #364 from olegkaspersky/patch-1
commas
2021-08-10 09:41:27 +02:00
olegkaspersky
f5922fc12c
commas 2021-08-09 20:17:51 +03:00
Sergio Brighenti
d140b1188a release 3.4.0 2021-08-01 13:24:39 +02:00
SergiX44
37c0701700 improve remember cookie security 2021-08-01 12:52:32 +02:00
Sergio Brighenti
0ef9f0e0a7 Merge remote-tracking branch 'origin/master' 2021-07-31 13:25:38 +02:00
Sergio Brighenti
ab259ed943 composer lock 2021-07-31 13:25:31 +02:00
Sergio Brighenti
7cd502837e
Update test_suite.yml 2021-07-31 13:24:39 +02:00
Sergio Brighenti
f99489cfe5 Merge remote-tracking branch 'origin/master' 2021-07-31 13:12:56 +02:00
Sergio Brighenti
01fca619c6 bump php version to 7.2 2021-07-31 13:09:45 +02:00
Sergio Brighenti
d79225cfc8
Merge pull request #353 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2021-07-31 13:00:55 +02:00
Sergio Brighenti
285c970933 fix 2021-07-31 13:00:38 +02:00
Sergio Brighenti
dc03734a20 fix 2021-07-31 13:00:21 +02:00
Sergio Brighenti
ab1409e108 add support for secure cookies 2021-07-31 12:55:56 +02:00
Sergio Brighenti
84020830ca address csrf vulnerability
improved discord embed support
2021-07-31 12:37:06 +02:00
Benjamin Jørgensen
ba9d566152
Translated using Weblate (Danish)
Currently translated at 72.9% (116 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/da/
2021-07-30 23:57:31 +02:00
Sergio Brighenti
bff37d6926
Update README.md 2021-07-30 23:57:29 +02:00
Sergio Brighenti
8fc06374a2
Create SECURITY.md 2021-07-30 23:57:11 +02:00
Sergio Brighenti
8668aab500
Merge pull request #351 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2021-07-16 09:35:34 +02:00
Zsolt Nagy
dd7ae0b89c
Translated using Weblate (Hungarian)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/hu/
2021-07-16 09:34:48 +02:00
Zsolt Nagy
41a5a6edbb
Translated using Weblate (Hungarian)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/hu/
2021-07-14 14:32:32 +02:00
Zsolt Nagy
8ca9626697
Added translation using Weblate (Hungarian) 2021-07-13 12:46:14 +02:00
Sergio Brighenti
0166755a60
Merge pull request #341 from BenTyger/master
Added support for LDAPS, StartTLS, and LDAP User search filters
2021-06-06 19:30:12 +02:00
Ben Tyger
a98f65c9f6 Fixed style error 2021-05-24 09:45:20 -04:00
Ben Tyger
4660ff5317 More style fixes 2021-05-22 13:59:33 -04:00
Ben Tyger
dfe73a34f1 More style fixes 2021-05-22 11:34:34 -04:00
Ben Tyger
53761bdc42 More style formatting fixes. 2021-05-22 11:01:25 -04:00
Ben Tyger
d3c556234b Fixed PHPDoc type and style errors 2021-05-22 10:57:08 -04:00
Ben Tyger
54381b4958 Fixed style formatting 2021-05-22 10:54:44 -04:00
Ben Tyger
3e53b56aec Fixed double if check 2021-05-22 10:53:34 -04:00
Ben Tyger
7c915032c5 Minor bug fixes. Also added ldap_close(); 2021-05-20 15:06:42 -04:00
Ben Tyger
1d5bb6ccc7 Added lots of debug logging. Fix ['schema'] var reference. 2021-05-20 15:05:08 -04:00
Ben Tyger
35ae42510f Updated LDAP configuration docs for use of ['ldap']['schema'] 2021-05-20 15:03:58 -04:00
Ben Tyger
289c0ab4de Documentation update 2021-05-19 14:25:58 -04:00
Ben Tyger
ef74962853 Added better config detection 'service_account_dn' 2021-05-19 13:49:30 -04:00
Ben Tyger
6a5e6431aa Better ldap_schema setting detection 2021-05-19 13:38:24 -04:00
Ben Tyger
ec5e7fc46f Added logic for LDAP user search 2021-05-19 13:11:20 -04:00
Ben Tyger
048b468ba5 Added Comments 2021-05-19 12:39:16 -04:00
Ben Tyger
3c63025a15 Switched from ldap_get_attributes() to more reliable ldap_get_dn() 2021-05-19 09:43:01 -04:00
Ben Tyger
46b20c4d97 Added username ldap escaping for filter search 2021-05-19 09:38:21 -04:00
Ben Tyger
6f46b70b83 Added LDAP StartTLS support 2021-05-18 14:57:28 -04:00
Ben Tyger
f8ec764c4c Added Comments to flow 2021-05-18 14:30:47 -04:00
Ben Tyger
8b333c935d Added LDAP Service account support for LDAP server that don't allow anon 2021-05-18 14:26:52 -04:00
Ben Tyger
7e0e0db384 Updated ldap_connect to use modern LDAP schema for better TLS support. 2021-05-18 14:14:59 -04:00
Sergio Brighenti
63e78d840d
Merge pull request #338 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2021-05-08 11:42:00 +02:00
ssantos
ea47f3ee93 Translated using Weblate (Portuguese)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pt/
2021-05-08 11:41:36 +02:00
Levi
33503092b2 Translated using Weblate (Swedish)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/sv/
2021-05-08 11:41:36 +02:00
Sergio Brighenti
01b8d96798
Merge pull request #340 from SergiX44/dependabot/npm_and_yarn/lodash-4.17.21
Bump lodash from 4.17.19 to 4.17.21
2021-05-08 11:41:34 +02:00
dependabot[bot]
559363925f
Bump lodash from 4.17.19 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-07 22:44:11 +00:00
Sergio Brighenti
9ea5a9971d Updated changelog 2021-04-25 13:27:49 +02:00
Sergio Brighenti
53715b9747
Update configuration.md 2021-04-25 13:26:05 +02:00
Sergio Brighenti
32de7376e1 Added missing discord bot 2021-04-25 13:07:50 +02:00
Sergio Brighenti
4adde17d25
Merge pull request #332 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2021-04-24 14:02:03 +02:00
Jonáš Loskot
9d33eb8191 Translated using Weblate (Czech)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/cs/
2021-04-24 14:01:24 +02:00
Jonáš Loskot
151d32f0f4 Translated using Weblate (Czech)
Currently translated at 63.5% (101 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/cs/
2021-04-24 14:01:24 +02:00
Sergio Brighenti
5b954a7853 Add discord back to the bot list 2021-04-24 14:01:18 +02:00
Sergio Brighenti
344e0281f5 Add discord back to the bot list 2021-04-24 13:56:36 +02:00
Sergio Brighenti
90062a668d
Merge pull request #325 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2021-03-30 19:09:59 +02:00
J. Lavoie
da0b4f92e9
Translated using Weblate (Portuguese)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pt/
2021-03-29 15:27:06 +02:00
J. Lavoie
4940fab973
Translated using Weblate (Spanish)
Currently translated at 96.8% (154 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/es/
2021-03-29 15:27:03 +02:00
J. Lavoie
f3c36e955c
Translated using Weblate (Finnish)
Currently translated at 57.2% (91 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fi/
2021-03-26 03:30:10 +01:00
J. Lavoie
c4ede9591b
Translated using Weblate (Hindi)
Currently translated at 39.6% (63 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/hi/
2021-03-26 03:30:09 +01:00
J. Lavoie
109e3fefbd
Translated using Weblate (Swedish)
Currently translated at 43.3% (69 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/sv/
2021-03-26 03:30:09 +01:00
J. Lavoie
6f7b1b26c5
Translated using Weblate (French (Canada))
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr_CA/
2021-03-26 03:30:09 +01:00
J. Lavoie
4b0f2af8bf
Translated using Weblate (Japanese)
Currently translated at 53.4% (85 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ja/
2021-03-26 03:30:09 +01:00
J. Lavoie
15f100ae79
Translated using Weblate (Danish)
Currently translated at 27.6% (44 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/da/
2021-03-26 03:30:09 +01:00
J. Lavoie
ffabd49990
Translated using Weblate (Bulgarian)
Currently translated at 40.2% (64 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/bg/
2021-03-26 03:30:09 +01:00
J. Lavoie
6a428eee47
Translated using Weblate (Russian)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ru/
2021-03-26 03:30:08 +01:00
J. Lavoie
181bbff605
Translated using Weblate (Portuguese)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pt/
2021-03-26 03:30:08 +01:00
J. Lavoie
3aa350b49e
Translated using Weblate (Dutch)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nl/
2021-03-26 03:30:08 +01:00
J. Lavoie
899a6eb794
Translated using Weblate (French)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr/
2021-03-26 03:30:08 +01:00
J. Lavoie
001c681bd8
Translated using Weblate (German)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2021-03-26 03:30:06 +01:00
J. Lavoie
bfefd9e42e
Translated using Weblate (Italian)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/
2021-03-26 03:30:06 +01:00
J. Lavoie
d11d73a825
Translated using Weblate (Finnish)
Currently translated at 57.2% (91 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fi/
2021-03-24 14:30:06 +01:00
J. Lavoie
49fb6bc39f
Translated using Weblate (French (Canada))
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr_CA/
2021-03-24 14:30:06 +01:00
J. Lavoie
30311025d1
Translated using Weblate (Indonesian)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/id/
2021-03-24 14:30:05 +01:00
J. Lavoie
349090b359
Translated using Weblate (Polish)
Currently translated at 96.8% (154 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pl/
2021-03-24 14:30:05 +01:00
J. Lavoie
4d6b8aeedd
Translated using Weblate (Japanese)
Currently translated at 52.2% (83 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ja/
2021-03-24 14:30:05 +01:00
J. Lavoie
7bcdfb575c
Translated using Weblate (Danish)
Currently translated at 26.4% (42 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/da/
2021-03-24 14:30:05 +01:00
J. Lavoie
c4f319b820
Translated using Weblate (Portuguese)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pt/
2021-03-24 14:30:04 +01:00
J. Lavoie
826ac367a4
Translated using Weblate (Dutch)
Currently translated at 96.2% (153 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nl/
2021-03-24 14:30:04 +01:00
J. Lavoie
709d6af44b
Translated using Weblate (French)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr/
2021-03-24 14:30:04 +01:00
J. Lavoie
a35dafd142
Translated using Weblate (Spanish)
Currently translated at 96.2% (153 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/es/
2021-03-24 14:30:04 +01:00
J. Lavoie
5175b9fe83
Translated using Weblate (German)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2021-03-24 14:30:03 +01:00
J. Lavoie
ecaa423f62
Translated using Weblate (Norwegian Bokmål)
Currently translated at 92.4% (147 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nb_NO/
2021-03-24 14:30:03 +01:00
J. Lavoie
fe92167c9c
Translated using Weblate (Italian)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/
2021-03-24 14:30:01 +01:00
Reza Almanda
9f6b8529a7
Translated using Weblate (Indonesian)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/id/
2021-03-16 15:03:12 +01:00
Tuğhan Belbek
802d767c0b
Translated using Weblate (Turkish)
Currently translated at 28.3% (45 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/tr/
2021-03-09 07:02:52 +01:00
Sergio Brighenti
47ad3353d6 Updated changelog 2021-03-07 18:38:27 +01:00
Sergio Brighenti
b9e7285be1 Fix tests 2021-03-07 18:33:29 +01:00
Sergio Brighenti
09583f6cfe Add ip logging 2021-03-07 18:23:41 +01:00
Sergio Brighenti
f6df098915 Added logging 2021-03-07 17:37:20 +01:00
Sergio Brighenti
b72e9cda30 Update lock 2021-03-07 17:25:32 +01:00
Sergio Brighenti
014b5428aa Improved og implementation
Bugfixes
2021-03-07 17:21:07 +01:00
Sergio Brighenti
7cd172510c
Merge pull request #321 from lukas-jo/master
Allow to change the LDAP rdn attribute
2021-03-04 13:40:19 +01:00
Sergio Brighenti
f73d43ab8b
Merge pull request #314 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2021-03-04 13:38:42 +01:00
Lukas
48d58fbf95 Allow to change the LDAP rdn attribute 2021-03-04 11:59:54 +01:00
Metroseksuaali
17a1c7b76f
Translated using Weblate (Finnish)
Currently translated at 57.2% (91 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fi/
2021-02-26 18:50:57 +01:00
Metroseksuaali
fd9f56c949
Added translation using Weblate (Finnish) 2021-02-25 18:15:37 +01:00
Sergei Myndria
b53f325743 Translated using Weblate (Russian)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ru/
2021-02-18 21:04:02 +01:00
Martim Pinheiro
28c751b1a2 Translated using Weblate (Portuguese)
Currently translated at 98.1% (156 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pt/
2021-02-18 21:04:02 +01:00
Cristian
34cb67c40e Translated using Weblate (Spanish)
Currently translated at 96.2% (153 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/es/
2021-02-18 21:04:02 +01:00
herodev hrd
8bacaa2d14 Translated using Weblate (German)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2021-02-18 21:04:02 +01:00
Amasuke
33c9a11e91 Translated using Weblate (German)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2021-02-18 21:04:02 +01:00
Patrick Fruh
cdaa200e4a
fix bin files for PHP8 (#315) 2021-02-18 21:03:58 +01:00
Weblate (bot)
2519b25612
Translations update from Weblate (#308)
* Added translation using Weblate (Turkish)

* Translated using Weblate (Turkish)

Currently translated at 19.4% (31 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/tr/

* Translated using Weblate (German)

Currently translated at 98.1% (156 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/

Co-authored-by: Hozan Şahin <hozansahin@gmail.com>
Co-authored-by: Joschua <joschua@familie-kreimer.de>
2021-02-15 00:13:43 +01:00
Weblate (bot)
cff3af89bb
Translated using Weblate (Indonesian) (#306)
Currently translated at 81.1% (129 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/id/

Co-authored-by: wickedr0se <andrejulian30@gmail.com>
2021-01-18 17:40:16 +01:00
Weblate (bot)
58a36e0e1a
Translations update from Weblate (#304)
* Translated using Weblate (French)

Currently translated at 97.4% (155 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr/

* Added translation using Weblate (Hindi)

* Translated using Weblate (Hindi)

Currently translated at 39.6% (63 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/hi/

Co-authored-by: Jean-Philippe Anthonioz <jp.anthonioz@gmail.com>
Co-authored-by: Lebyy <lebon1377@gmail.com>
2020-12-30 09:23:04 +01:00
Sergio Brighenti
b7a04fb2ca
Update test_suite.yml 2020-12-17 18:36:42 +01:00
Weblate (bot)
fe1c30fe4e
Translated using Weblate (Portuguese) (#300)
Currently translated at 74.2% (118 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pt/

Co-authored-by: ssantos <ssantos@web.de>
2020-12-17 18:33:33 +01:00
dependabot[bot]
b7d593ffc7
Bump ini from 1.3.5 to 1.3.8 (#301)
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-17 18:33:13 +01:00
Weblate (bot)
d9025e77dd
Translated using Weblate (Spanish) (#299)
Currently translated at 69.8% (111 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/es/

Co-authored-by: Slayer 20 <fdou20@gmail.com>
2020-12-03 19:46:49 +01:00
Sergio Brighenti
1d879a72fe
Update README.md 2020-11-25 00:12:56 +01:00
Weblate (bot)
c0f6827e79
Translated using Weblate (Spanish) (#294)
Currently translated at 64.1% (102 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/es/

Co-authored-by: xrillex <rojads54@gmail.com>
2020-11-14 21:33:38 +01:00
Sergio Brighenti
8f79ede78f Fixed issue with responsive menu on mobile
updated packages
2020-11-13 22:54:13 +01:00
Sergio Brighenti
614fcd0e83 Changelog 2020-11-12 23:29:06 +01:00
Sergio Brighenti
5f272a7176 Missing flag switch 2020-11-12 23:25:34 +01:00
Sergio Brighenti
763a45a65e Update version and changelog 2020-11-12 23:19:11 +01:00
Sergio Brighenti
c4ca9dc962 Fixed user default view 2020-11-12 23:16:53 +01:00
Sergio Brighenti
9cf8ed2ae0 Fix parameter issue on update check 2020-11-12 23:06:47 +01:00
Sergio Brighenti
37520bb571 Updated changelog 2020-11-12 22:57:39 +01:00
Sergio Brighenti
ab0acea8db Updated changelog 2020-11-12 22:55:30 +01:00
Sergio Brighenti
e04ad4d3f3 Updated changelog 2020-11-12 22:55:08 +01:00
Sergio Brighenti
3ff9fb181c Drop telegram share button 2020-11-12 22:50:18 +01:00
Weblate (bot)
6ddbc0fca3
Translated using Weblate (Norwegian Bokmål) (#292)
Currently translated at 92.4% (147 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nb_NO/

Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
2020-11-10 21:57:20 +01:00
Sergio Brighenti
f169d20b37
Update CHANGELOG.md 2020-10-25 15:42:25 +01:00
Sergio Brighenti
b04b6ff3ce
Apply fixes from StyleCI (#290)
[ci skip] [skip ci]

Co-authored-by: Sergio Brighenti <SergiX44@users.noreply.github.com>
2020-10-25 14:48:20 +01:00
Sergio Brighenti
e9354f8152 Added password recovery tests 2020-10-25 14:47:31 +01:00
Weblate (bot)
55451903b0
Translated using Weblate (English) (#289)
Currently translated at 100.0% (159 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/en/

Co-authored-by: Sergio Brighenti <sergio@brighenti.me>
2020-10-21 23:29:51 +02:00
Sergio Brighenti
d59be8e2f2 Update dependencies 2020-10-20 22:58:14 +02:00
Weblate (bot)
6bcd455d2e
Translated using Weblate (Italian) (#288)
Currently translated at 99.3% (158 of 159 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/

Co-authored-by: Sergio Brighenti <sergio@brighenti.me>
2020-10-20 22:55:51 +02:00
Sergio Brighenti
5b8cc0ecbd Update documentation 2020-10-20 19:56:45 +02:00
Sergio Brighenti
1c8e4dff47 Updated bash script 2020-10-20 19:28:22 +02:00
Sergio Brighenti
8ce337233c
Screencloud support (#287)
* Working on screencloud support

* Working screencloud implementation
2020-10-20 13:34:57 +02:00
Sergio Brighenti
1012984661
Apply fixes from StyleCI (#286)
[ci skip] [skip ci]

Co-authored-by: Sergio Brighenti <SergiX44@users.noreply.github.com>
2020-10-16 23:10:23 +02:00
Pietro Marangon
dbd40f6210
Fix #226 (#281)
* Fix #226

* Fix indentation

* Fix indentation

* Owner visibility
2020-10-16 23:10:00 +02:00
Weblate (bot)
30c83811b9
Translated using Weblate (Portuguese) (#284)
Currently translated at 77.2% (122 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pt/

Co-authored-by: Dinip <dinis_pimpao@hotmail.com>
2020-10-15 20:31:13 +02:00
Weblate (bot)
9abe5154f9
Translated using Weblate (Norwegian Bokmål) (#282)
Currently translated at 94.9% (150 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nb_NO/

Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
2020-10-13 09:20:54 +02:00
Sergio Brighenti
0bd56d5e1b Completed register controller tests 2020-10-11 17:42:41 +02:00
Sergio Brighenti
50c013af8b
Adding register tests (#280)
* Adding register test, faking mails

* Apply fixes from StyleCI (#279)

[ci skip] [skip ci]

Co-authored-by: Sergio Brighenti <SergiX44@users.noreply.github.com>

* Reduce method complexity

Co-authored-by: Sergio Brighenti <SergiX44@users.noreply.github.com>
2020-10-09 20:01:57 +02:00
Weblate (bot)
c34671d700
Translated using Weblate (Portuguese) (#276)
Currently translated at 68.9% (109 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pt/

Co-authored-by: ssantos <ssantos@web.de>
2020-10-07 09:21:05 +02:00
Pietro Marangon
0d81256980
Fix #269 (#273)
* Fix #269

* Update CHANGELOG.md
2020-10-03 17:33:21 +02:00
Weblate (bot)
cb48fe30eb
Translations update from Weblate (#270)
* Translated using Weblate (Portuguese)

Currently translated at 68.9% (109 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pt/

* Fix for php8, adding test for login

* Apply fixes from StyleCI

[ci skip] [skip ci]

* Added main login tests

* Added logout test

Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: Sergio Brighenti <sergio@brighenti.me>
Co-authored-by: Sergio Brighenti <SergiX44@users.noreply.github.com>
2020-10-03 17:12:46 +02:00
Sergio Brighenti
9a5335ae7f
Merge pull request #272 from SergiX44/login-tests
tests: add Login tests
2020-10-03 17:10:56 +02:00
Sergio Brighenti
00ae95e965 Added logout test 2020-10-03 17:08:31 +02:00
Sergio Brighenti
5af536273e Merge remote-tracking branch 'origin/login-tests' into login-tests
# Conflicts:
#	tests/Feature/LoginControllerTest.php
2020-10-03 16:38:59 +02:00
Sergio Brighenti
89a19006ad Added main login tests 2020-10-03 16:38:19 +02:00
Sergio Brighenti
2fecd2bc22
Merge pull request #271 from SergiX44/analysis-rdKrJQ
Apply fixes from StyleCI
2020-10-03 16:02:35 +02:00
Sergio Brighenti
a130bc17d0 Apply fixes from StyleCI
[ci skip] [skip ci]
2020-10-03 14:01:11 +00:00
Sergio Brighenti
125ae162ff Fix for php8, adding test for login 2020-10-03 16:01:00 +02:00
Sergio Brighenti
333926bbe9
Merge pull request #268 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-10-01 18:17:23 +02:00
Hosted Weblate
d3d06672a9
Merge branch 'origin/master' into Weblate. 2020-10-01 18:08:46 +02:00
Sergio
3d254a9822 Test suite finally working 2020-10-01 18:08:35 +02:00
Sergio
d9be2c7696 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	tests/Feature/LoginControllerTest.php
#	tests/TestCase.php
2020-09-29 09:47:35 +02:00
Artem
4f8b13fd90
Translated using Weblate (Russian)
Currently translated at 59.4% (94 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ru/
2020-09-28 21:41:02 +02:00
Sergio Brighenti
ff25de6ceb
Merge pull request #267 from SergiX44/analysis-EAQjOD
Apply fixes from StyleCI
2020-09-28 18:30:55 +02:00
Sergio Brighenti
20c08eb129 Apply fixes from StyleCI
[ci skip] [skip ci]
2020-09-28 16:20:48 +00:00
Sergio Brighenti
2eb269d6bf Keep an app instace in tests 2020-09-28 18:20:25 +02:00
Sergio
c34faca4d2 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	tests/Client.php
2020-09-28 09:26:24 +02:00
Sergio Brighenti
b94d50e11d
Merge pull request #266 from SergiX44/analysis-BMgalL
Apply fixes from StyleCI
2020-09-27 15:47:50 +02:00
Sergio Brighenti
870464a9ff Apply fixes from StyleCI
[ci skip] [skip ci]
2020-09-27 13:47:33 +00:00
Sergio Brighenti
f2523c24e5 Added temporary database 2020-09-27 15:47:19 +02:00
Sergio Brighenti
9de27e1278 Create config for the test suite 2020-09-26 20:53:07 +02:00
Sergio Brighenti
77bcc18807 Merge remote-tracking branch 'origin/master' 2020-09-26 18:48:53 +02:00
Sergio Brighenti
a11b9dd711 Fix license string 2020-09-26 18:48:42 +02:00
Sergio Brighenti
eb29f9f486
Update sponsor.md 2020-09-26 17:24:57 +02:00
Sergio Brighenti
3b71954b96 Create CNAME 2020-09-06 13:08:27 +02:00
Sergio Brighenti
ead80525ed Release 2020-09-05 11:45:12 +02:00
Sergio
61efafc991 Working on test suite 2020-09-04 17:17:57 +02:00
Sergio
75c7c644aa Fix exclude file 2020-09-02 11:19:19 +02:00
Sergio
619d192d14 Fix and update phpunit 2020-09-01 17:22:16 +02:00
Sergio
d042a3b84a Fix namespace
Improved testing methods
2020-09-01 16:55:40 +02:00
Sergio
afda056ba9 Updated dependencies
Fix namespace
2020-09-01 10:16:08 +02:00
Sergio Brighenti
6724537da2
Merge pull request #246 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-08-13 09:15:38 +02:00
Lexy Cat
f679c19d56
Added translation using Weblate (Czech) 2020-08-06 17:36:57 +02:00
Thomas
cea09407f8
Translated using Weblate (German)
Currently translated at 100.0% (158 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2020-08-04 21:32:46 +02:00
Anosh Soltanabadi
7679212ea9
Translated using Weblate (Swedish)
Currently translated at 44.3% (70 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/sv/
2020-08-03 11:41:58 +02:00
Anosh Soltanabadi
bb8644092c
Added translation using Weblate (Swedish) 2020-08-02 11:12:43 +02:00
Sergio Brighenti
2f03a19b37
Merge pull request #243 from SergiX44/analysis-lKwlDd
Apply fixes from StyleCI
2020-07-28 08:53:50 +02:00
Sergio Brighenti
ae9f2eeee2 Apply fixes from StyleCI
[ci skip] [skip ci]
2020-07-28 06:51:56 +00:00
Sergio Brighenti
15f7cac660
Update sponsor.md 2020-07-28 08:51:49 +02:00
Sergio Brighenti
f30d37b7ba Merge remote-tracking branch 'origin/master' 2020-07-19 19:17:06 +02:00
Sergio Brighenti
ff9d749860 Updated slack image bot preview (fixes #238) 2020-07-19 19:16:49 +02:00
Sergio Brighenti
6e20a9ffc7
Merge pull request #240 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-07-19 18:37:28 +02:00
Hosted Weblate
ea42f93b2f
Merge branch 'origin/master' into Weblate. 2020-07-17 09:04:49 +02:00
Sergio Brighenti
0bee59cfda
Merge pull request #241 from SergiX44/dependabot/npm_and_yarn/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19
2020-07-17 09:04:44 +02:00
dependabot[bot]
77dd780063
Bump lodash from 4.17.15 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-16 14:01:12 +00:00
SlamaFR
b0f56acad5
Translated using Weblate (French)
Currently translated at 100.0% (158 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr/
2020-07-15 20:41:57 +02:00
J. Lavoie
da35036874
Translated using Weblate (French (Canada))
Currently translated at 100.0% (158 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr_CA/
2020-07-14 10:41:52 +02:00
J. Lavoie
28bbf80326
Translated using Weblate (Portuguese)
Currently translated at 68.9% (109 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pt/
2020-07-14 10:41:52 +02:00
J. Lavoie
f3537761d0
Translated using Weblate (French)
Currently translated at 100.0% (158 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr/
2020-07-14 10:41:51 +02:00
J. Lavoie
eeaa4fc710
Translated using Weblate (Spanish)
Currently translated at 65.8% (104 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/es/
2020-07-14 10:41:51 +02:00
J. Lavoie
fb276227b2
Translated using Weblate (German)
Currently translated at 100.0% (158 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2020-07-14 10:41:51 +02:00
J. Lavoie
3d96301051
Translated using Weblate (Italian)
Currently translated at 100.0% (158 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/
2020-07-14 10:41:51 +02:00
Sergio Brighenti
e89791fc80
Merge pull request #229 from SergiX44/dependabot/npm_and_yarn/websocket-extensions-0.1.4
Bump websocket-extensions from 0.1.3 to 0.1.4
2020-06-06 17:55:40 +02:00
dependabot[bot]
90276ce106
Bump websocket-extensions from 0.1.3 to 0.1.4
Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/faye/websocket-extensions-node/releases)
- [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-06 12:47:09 +00:00
Sergio Brighenti
eff0bae2c8
Merge pull request #225 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-05-31 13:00:31 +02:00
Brian Heldens
deea5038fb
Translated using Weblate (Dutch)
Currently translated at 99.3% (157 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nl/
2020-05-30 14:46:57 +02:00
Sergio Brighenti
c38a864c19
Update sponsor.md 2020-05-26 12:37:55 +02:00
Sergio Brighenti
db388a1533
Merge pull request #222 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-05-20 12:38:40 +02:00
r4ha
162c2312c2
Translated using Weblate (Japanese)
Currently translated at 54.4% (86 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ja/
2020-05-20 05:41:37 +02:00
Sergio Brighenti
1d31ded143
Update CHANGELOG.md 2020-05-19 17:17:48 +02:00
Sergio Brighenti
cdd58ffc6b
Update changelog.md 2020-05-19 17:17:31 +02:00
Sergio Brighenti
4965a979dd
Update CHANGELOG.md 2020-05-19 17:16:15 +02:00
Sergio Brighenti
de2678c0e5
Merge pull request #221 from watermelonpizza/master
Add Azure blob storage as file storage option
2020-05-19 17:14:57 +02:00
Daniel Miller
8d80bda629 Add Azure blob storage as file storage option 2020-05-20 00:55:30 +10:00
Sergio Brighenti
3711f0e01e
Merge pull request #220 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-05-18 21:42:30 +02:00
Artem
23dc7248a4
Translated using Weblate (Russian)
Currently translated at 59.4% (94 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ru/
2020-05-18 21:41:37 +02:00
Sergio Brighenti
e073f95c8f Added line numbers 2020-05-18 12:20:59 +02:00
Sergio Brighenti
45cea0e794
Merge pull request #216 from camjac251/master
Add S3 Endpoint Support
2020-05-14 13:41:17 +02:00
Sergio Brighenti
18911de752
Update index.php 2020-05-14 13:39:54 +02:00
Sergio Brighenti
a3092f56c2
Update install.twig 2020-05-14 13:38:32 +02:00
camjac251
ccf7b5d2c6 Fix required flag 2020-05-13 09:08:15 -05:00
camjac251
0484237954 wording 2020-05-12 19:49:18 -05:00
camjac251
4dced7dd20 Add S3 Endpoint Support
This will offer support for other storage platforms that offer S3 like APIs such as
Backblaze B2, Wasabi, and DigitalOcean Spaces
2020-05-12 19:46:56 -05:00
Sergio Brighenti
1d0f5b1527 Merge remote-tracking branch 'origin/master' 2020-05-12 18:03:07 +02:00
Sergio Brighenti
62655f9421 Implemented fix from #215 2020-05-12 18:02:46 +02:00
Sergio Brighenti
3753dae1a1
Merge pull request #212 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-05-06 21:28:09 +02:00
whiskeey
aeb82ffbf9
Translated using Weblate (Polish)
Currently translated at 100.0% (158 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pl/
2020-05-06 21:12:58 +02:00
Sergio Brighenti
4deee18be0
Merge pull request #210 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-05-01 13:18:09 +02:00
Johannes Krüger
a4f5cb97de
Translated using Weblate (German)
Currently translated at 96.2% (152 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2020-05-01 12:11:22 +02:00
Allan Nordhøy
2736ce21c9
Translated using Weblate (Norwegian Bokmål)
Currently translated at 91.7% (145 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nb_NO/
2020-05-01 12:11:21 +02:00
Allan Nordhøy
ee5ebb6f23
Translated using Weblate (English)
Currently translated at 100.0% (158 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/en/
2020-05-01 12:11:21 +02:00
Sergio Brighenti
927ba5dcd5
Merge pull request #209 from SergiX44/dependabot/npm_and_yarn/jquery-3.5.0
Bump jquery from 3.4.1 to 3.5.0
2020-04-30 17:29:53 +02:00
Sergio Brighenti
448395b887
Merge pull request #207 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-04-30 17:29:42 +02:00
dependabot[bot]
e416d8c831
Bump jquery from 3.4.1 to 3.5.0
Bumps [jquery](https://github.com/jquery/jquery) from 3.4.1 to 3.5.0.
- [Release notes](https://github.com/jquery/jquery/releases)
- [Commits](https://github.com/jquery/jquery/compare/3.4.1...3.5.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-04-30 00:44:03 +00:00
Allan Nordhøy
923ca80ed1
Translated using Weblate (Norwegian Bokmål)
Currently translated at 91.7% (145 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nb_NO/
2020-04-27 12:11:18 +02:00
Sergio Brighenti
40edde36c7
Merge pull request #206 from Pe46dro/patch-3
Update migrate
2020-04-26 21:10:38 +02:00
Pietro Marangon
988fcb3b92
Update migrate 2020-04-26 17:58:31 +02:00
Pietro Marangon
5d6754e5b1
Update migrate 2020-04-26 15:00:16 +02:00
Sergio Brighenti
edd00c9f73
Merge pull request #205 from SergiX44/analysis-ADeOEZ
Apply fixes from StyleCI
2020-04-22 16:41:53 +02:00
Sergio Brighenti
669611c8f6 Apply fixes from StyleCI
[ci skip] [skip ci]
2020-04-22 14:41:26 +00:00
Sergio Brighenti
71e9ead655
Update test_suite.yml 2020-04-22 16:41:18 +02:00
Sergio Brighenti
002b16d845 Merge remote-tracking branch 'origin/master' 2020-04-22 16:24:59 +02:00
Sergio Brighenti
edd26b0e35 Added close session before start file stream 2020-04-22 16:24:43 +02:00
Sergio Brighenti
696f47436b
Merge pull request #200 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-04-19 14:37:09 +02:00
Jeannette L
e37a7bf36b
Translated using Weblate (French (Canada))
Currently translated at 100.0% (158 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr_CA/
2020-04-19 03:11:26 +02:00
whiskeey
1a8f1101dc
Translated using Weblate (Polish)
Currently translated at 100.0% (158 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pl/
2020-04-19 03:11:23 +02:00
anonymous
9553c09991
Translated using Weblate (French)
Currently translated at 100.0% (158 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr/
2020-04-19 03:11:23 +02:00
Jeannette L
5241cc3366
Translated using Weblate (French)
Currently translated at 100.0% (158 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr/
2020-04-19 03:11:23 +02:00
Jeannette L
d7319faa02
Translated using Weblate (German)
Currently translated at 98.7% (156 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2020-04-19 03:11:22 +02:00
Sergio Brighenti
36498c4605
Merge pull request #199 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-04-18 22:58:02 +02:00
Jeannette L
8034fe1367
Translated using Weblate (German)
Currently translated at 98.7% (156 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2020-04-18 02:41:43 +02:00
anonymous
2141cf9ab7
Translated using Weblate (German)
Currently translated at 98.7% (156 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2020-04-18 02:41:42 +02:00
Jeannette L
ada419fc51
Added translation using Weblate (French (Canada)) 2020-04-18 02:39:57 +02:00
Sergio Brighenti
95a0c1c85e
Merge pull request #197 from Pe46dro/patch-2
Update xbackbone_uploader.sh.twig
2020-04-16 21:51:40 +02:00
Pietro Marangon
bed9a107d3
Update xbackbone_uploader.sh.twig 2020-04-16 21:49:35 +02:00
Sergio Brighenti
4f75f337be
Create php.yml 2020-04-15 00:15:45 +02:00
Sergio Brighenti
70e1ef826e
Merge pull request #196 from SergiX44/analysis-nNZaLy
Apply fixes from StyleCI
2020-04-15 00:10:32 +02:00
Sergio Brighenti
e6641fe1a9 Apply fixes from StyleCI
[ci skip] [skip ci]
2020-04-14 22:09:23 +00:00
Sergio Brighenti
ce2f0ef72d Merge remote-tracking branch 'origin/master' 2020-04-15 00:09:08 +02:00
Sergio Brighenti
1bb05f5c2b Added test suite 2020-04-15 00:08:51 +02:00
Sergio Brighenti
63ef35a475
Merge pull request #195 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-04-14 22:15:50 +02:00
Dominik
de8d4595ee
Translated using Weblate (Polish)
Currently translated at 96.8% (153 of 158 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pl/
2020-04-14 20:11:23 +02:00
Sergio Brighenti
704f76986f
Update README.md 2020-04-13 21:55:22 +02:00
Sergio Brighenti
2303cb263f Fixed error with cli command 2020-04-13 17:46:10 +02:00
Sergio Brighenti
5a7c74c5d9 Release 3.1.3 2020-04-13 17:10:17 +02:00
Sergio Brighenti
0629264002
Merge pull request #193 from Lukasss93/luca.patera
Improved changelog + new changelog page
2020-04-13 16:47:53 +02:00
Luca Patera
1689c8d3f3 Fixed .htaccess and nginx.conf 2020-04-13 16:46:53 +02:00
Luca Patera
a37854ef57 Added changelog page 2020-04-13 16:30:05 +02:00
Luca Patera
9cad1dc35b Improved .gitignore file 2020-04-13 16:28:44 +02:00
Luca Patera
0983149dd3 Merge remote-tracking branch 'remotes/upstream/master' into luca.patera 2020-04-13 16:25:48 +02:00
Luca Patera
37b6fdea28 Improved CHANGELOG file using 'Keep a Changelog' 2020-04-13 16:23:39 +02:00
Sergio Brighenti
82af995a75 Update langs names 2020-04-13 13:05:12 +02:00
Sergio Brighenti
8c7dd2bd3f
Merge pull request #192 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-04-13 12:37:37 +02:00
Serenium
4fbad4186b
Translated using Weblate (Indonesian)
Currently translated at 48.7% (76 of 156 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/id/
2020-04-13 12:34:53 +02:00
Serenium
f8aa2f9175
Added translation using Weblate (Indonesian) 2020-04-13 03:48:58 +02:00
Sergio Brighenti
da0a5e96ba Updated sample config 2020-04-12 19:58:15 +02:00
Sergio Brighenti
1ac9a0d7be Changelog 2020-04-12 19:55:59 +02:00
Sergio Brighenti
9bea86f8f5 Installer improvements
Updated sponsor list
2020-04-12 19:51:27 +02:00
Sergio Brighenti
8fbaded18d Added check of cases like #191 2020-04-12 18:46:25 +02:00
Sergio Brighenti
8fd6a1cb19 Updated website 2020-04-12 13:27:09 +02:00
Sergio Brighenti
96884c3c30 Merge remote-tracking branch 'origin/master' 2020-04-12 13:23:47 +02:00
Sergio Brighenti
004fc27c5f Updated website 2020-04-12 13:23:24 +02:00
Sergio Brighenti
29e63648ce
Merge pull request #189 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-04-11 18:10:50 +02:00
Johannes Krüger
4042faefb5
Translated using Weblate (German)
Currently translated at 100.0% (156 of 156 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2020-04-11 18:09:40 +02:00
Sergio Brighenti
fca9c1add6 composer lock 2020-04-11 18:03:47 +02:00
Sergio Brighenti
4cefa9ed24 Fixed installation error
(closes #188)
2020-04-11 17:59:14 +02:00
Sergio Brighenti
ccc10f6499 Clean up 2020-04-10 16:45:43 +02:00
Sergio Brighenti
71357755f3 improved mail spamassassin tips 2020-04-09 15:13:35 +02:00
Sergio Brighenti
d6cc4032b8 Fixed button bad layout 2020-04-09 15:10:04 +02:00
Sergio Brighenti
eb0d42a03f Fixed button bad layout 2020-04-09 15:09:49 +02:00
Sergio Brighenti
9890f09e81 Fixed error with raw preview
(Closes #184)
2020-04-09 15:02:05 +02:00
Sergio Brighenti
e81a590eb0
Merge pull request #183 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-04-09 13:53:13 +02:00
Sergio Brighenti
61bdf97463
Translated using Weblate (Italian)
Currently translated at 100.0% (156 of 156 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/
2020-04-09 13:52:37 +02:00
IDBLOCK
aa14e5c2ce Translated using Weblate (French)
Currently translated at 100.0% (155 of 155 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr/
2020-04-09 13:48:43 +02:00
Sergio Brighenti
6081b02dbe 3.1.RC5 2020-04-09 13:48:33 +02:00
Sergio Brighenti
e100cff3de Added check in the installer 2020-04-09 13:10:54 +02:00
Sergio Brighenti
a99d9a7fc7 Added check if the zip extension is not loaded 2020-04-09 13:09:57 +02:00
Sergio Brighenti
d77b83b1d6 Fixed upgrade with php 7.4 2020-04-08 19:02:37 +02:00
Sergio Brighenti
fd11209e08 Improved tag management 2020-04-08 18:36:10 +02:00
Sergio Brighenti
f42064d9ee 3.1.rc3 2020-04-08 14:17:50 +02:00
Sergio Brighenti
b1f9b46c94 Removed invalid translation strings, added check on lang string parameter binding 2020-04-08 14:02:24 +02:00
Sergio Brighenti
c9b6dfab85 enabled recaptcha on password recovery 2020-04-08 13:30:55 +02:00
Sergio Brighenti
90d73bfed1
Merge pull request #181 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-04-08 13:26:59 +02:00
Sergio Brighenti
fa92309f7b
Translated using Weblate (Italian)
Currently translated at 99.3% (154 of 155 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/
2020-04-08 13:24:12 +02:00
whiskeey
3d0ce204ba Translated using Weblate (Polish)
Currently translated at 100.0% (155 of 155 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pl/
2020-04-08 13:20:15 +02:00
Sergio Brighenti
e37cbbb025 enabled recaptcha on password recovery 2020-04-08 13:19:57 +02:00
Sergio Brighenti
f88bcbc9e7 Fixed message encoding 2020-04-07 23:28:53 +02:00
Sergio Brighenti
4891ce0f9f Fixing error during upload 2020-04-07 23:22:47 +02:00
Sergio Brighenti
041bb9fd55 added tags on the preview page
added a way to disable autotagging
Fixed issue with email content type
the uploads page previews are now with a link
Disabled logging of 404 errors
2020-04-07 18:43:34 +02:00
Sergio Brighenti
afefbfa99d Fixed issue #177 2020-04-06 19:24:26 +02:00
Sergio Brighenti
d5b95f542b 3.1.RC1
renabled remove dir
2020-04-06 12:55:58 +02:00
Sergio Brighenti
1c674e5e67 3.1.RC1
Fixed dns building for non-absolute paths
2020-04-06 12:54:34 +02:00
Sergio Brighenti
63c332a697 3.1.RC1
Fixed installer issues
2020-04-06 12:43:47 +02:00
Sergio Brighenti
ce497f9728 Fixed quota regex 2020-04-06 11:34:18 +02:00
Sergio Brighenti
06fcef7433 Fixed tag add bad alignment 2020-04-05 15:18:39 +02:00
Sergio Brighenti
7ddfb06729
Merge pull request #176 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-04-05 14:55:57 +02:00
IDBLOCK
5d036c3c18 Translated using Weblate (French)
Currently translated at 100.0% (154 of 154 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr/
2020-04-05 14:53:39 +02:00
IDBLOCK
315d0839c2 Translated using Weblate (English)
Currently translated at 100.0% (154 of 154 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/en/
2020-04-05 14:53:39 +02:00
Sergio Brighenti
a253234bd4 Fixed installer errors 2020-04-05 14:53:22 +02:00
Sergio Brighenti
7e9d43ec71 Added docs favicon 2020-04-04 19:45:28 +02:00
Sergio Brighenti
4bd422c08c Updated website 2020-04-04 19:44:22 +02:00
Sergio Brighenti
f6dccc2e9e Updated website 2020-04-04 19:41:17 +02:00
Sergio Brighenti
b2342f4acb
Merge pull request #175 from SergiX44/analysis-1bWrP1
Apply fixes from StyleCI
2020-04-04 19:29:29 +02:00
Sergio Brighenti
103819827b Apply fixes from StyleCI
[ci skip] [skip ci]
2020-04-04 17:29:15 +00:00
Sergio Brighenti
5606224339 Replaced validation system 2020-04-04 19:29:00 +02:00
Sergio Brighenti
f215dc70e8
Merge pull request #171 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-04-04 12:35:17 +02:00
IDBLOCK
a30a4fdf11
Translated using Weblate (French)
Currently translated at 100.0% (154 of 154 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr/
2020-04-04 04:55:45 +02:00
Johannes Krüger
f52453cd65
Translated using Weblate (German)
Currently translated at 99.3% (153 of 154 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2020-04-04 04:55:44 +02:00
Sergio Brighenti
d9b2e8cca3
Merge pull request #168 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-04-03 21:26:46 +02:00
Allan Nordhøy
f8837c751f
Translated using Weblate (Russian)
Currently translated at 62.3% (96 of 154 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ru/
2020-04-03 20:55:36 +02:00
Sergio Brighenti
f558af93bc Simplified class 2020-04-03 16:36:39 +02:00
Sergio Brighenti
b992d03c2c Fix alignment 2020-04-03 16:07:41 +02:00
Sergio Brighenti
9596ef9ae4 Fix alignment 2020-04-03 16:06:56 +02:00
Sergio Brighenti
ecbbca239c Updated config 2020-04-03 16:05:26 +02:00
Sergio Brighenti
5a7ff39ce9 Moved some settings from admin to user side
Updated docs
2020-04-03 15:59:49 +02:00
Sergio Brighenti
50051b3691 Updated docs 2020-04-02 19:07:35 +02:00
Sergio Brighenti
5babd409db Readme updated 2020-04-02 18:54:11 +02:00
Sergio Brighenti
fcadd00b1e docs 2020-04-02 18:48:16 +02:00
Sergio Brighenti
5a98e6f6a6 docs 2020-04-02 18:23:16 +02:00
Sergio Brighenti
97bec212dc docs 2020-04-02 15:34:07 +02:00
Sergio Brighenti
e074ad2015 docs 2020-04-02 15:23:24 +02:00
Sergio Brighenti
aa95a5d689 docs 2020-04-02 15:04:36 +02:00
Sergio Brighenti
a88726d9a3 Updated gh pages 2020-04-02 14:58:06 +02:00
Sergio Brighenti
6bad4a035e
Update index.md 2020-04-02 14:40:38 +02:00
Sergio Brighenti
316cb3572d
Update _config.yml 2020-04-02 14:32:34 +02:00
Sergio Brighenti
0fff769ec0
Update _config.yml 2020-04-02 14:30:00 +02:00
Sergio Brighenti
4b96ea3159 Changed gh theme 2020-04-02 14:08:00 +02:00
Sergio Brighenti
430c8324a6 test ghpages 2020-04-02 13:21:42 +02:00
Sergio Brighenti
3feba186ea
Create index.html 2020-04-02 13:04:10 +02:00
Sergio Brighenti
95a3ccf13b Set theme jekyll-theme-dinky 2020-04-02 12:52:25 +02:00
Sergio Brighenti
40bb7c267c Merge remote-tracking branch 'origin/master' 2020-04-02 12:49:18 +02:00
Sergio Brighenti
dae3c9d1c9 docs 2020-04-02 12:48:35 +02:00
Sergio Brighenti
5fc8fba140
Delete _config.yml 2020-04-02 12:32:55 +02:00
Sergio Brighenti
8b464fc2ff Set theme jekyll-theme-slate 2020-04-02 12:31:37 +02:00
Sergio Brighenti
5c7df0925d
Merge pull request #166 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-04-01 19:02:07 +02:00
Sergio Brighenti
ee1436fd1c
Translated using Weblate (Italian)
Currently translated at 100.0% (154 of 154 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/
2020-04-01 19:01:43 +02:00
Sergio Brighenti
ac9f1d1578 Added filter by tag list
Completed ldap implementation
2020-04-01 18:48:59 +02:00
Sergio Brighenti
64772d190c Improving ldap auth 2020-03-31 20:12:22 +02:00
Sergio Brighenti
b87faad0c9 Improved ldap auth 2020-03-31 17:23:09 +02:00
Sergio Brighenti
b9989e1126 Implemented ldap authentication
(closes #71)
2020-03-31 15:24:36 +02:00
Sergio Brighenti
1cf2711c57 Merge remote-tracking branch 'origin/master' 2020-03-31 15:21:29 +02:00
Sergio Brighenti
4e5c1a9675 Implemented ldap authentication
(closes #71)
2020-03-31 15:21:01 +02:00
Sergio Brighenti
e187cdbfff
Merge pull request #165 from SergiX44/analysis-QMPY7Z
Apply fixes from StyleCI
2020-03-30 18:29:14 +02:00
Sergio Brighenti
f1b4a29dc0 Apply fixes from StyleCI
[ci skip] [skip ci]
2020-03-30 16:27:02 +00:00
Sergio Brighenti
6326f52bd1 Added autotagging
(closes #75)
2020-03-30 18:26:47 +02:00
Sergio Brighenti
5990d5fba1 Merge remote-tracking branch 'origin/master' 2020-03-30 14:55:45 +02:00
Sergio Brighenti
d26f726400 Working tag search with all modes, tag delete.
closes #121
2020-03-30 14:55:37 +02:00
Sergio Brighenti
35ebe324d1
Merge pull request #164 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-03-30 14:54:10 +02:00
xrillex
b432fa7dcf Translated using Weblate (Spanish)
Currently translated at 66.2% (100 of 151 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/es/
2020-03-30 14:53:41 +02:00
Sergio Brighenti
008ae77718 Working tag search with all modes, tag delete.
closes #121
2020-03-30 14:53:27 +02:00
Sergio Brighenti
6a58bc4a32 Working on search by tag 2020-03-29 21:41:48 +02:00
Sergio Brighenti
3b4f0ad24e Remove tag after addition now working 2020-03-29 17:10:18 +02:00
Sergio Brighenti
4f6403a5e6 Working tag delete 2020-03-29 16:44:37 +02:00
Sergio Brighenti
ebc309c9a4 Merge remote-tracking branch 'origin/master' 2020-03-28 23:42:37 +01:00
Sergio Brighenti
3a1f858f4e Working on the tag system 2020-03-28 23:42:08 +01:00
Sergio Brighenti
772473dc11
Merge pull request #162 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-03-27 12:38:53 +01:00
xrillex
ff9805a015
Translated using Weblate (Spanish)
Currently translated at 64.9% (98 of 151 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/es/
2020-03-24 23:47:56 +01:00
Sergio Brighenti
bd3683f261 Retrieve media tag 2020-03-19 22:59:11 +01:00
Sergio Brighenti
8e75668d2f Reworked user controller 2020-03-19 16:47:28 +01:00
Sergio Brighenti
5bbbdd6160 Reworked media query 2020-03-19 16:43:15 +01:00
Sergio Brighenti
a86f4aff3f
Merge pull request #161 from SergiX44/analysis-4xmLV1
Apply fixes from StyleCI
2020-03-19 09:40:33 +01:00
Sergio Brighenti
f06070f611 Apply fixes from StyleCI
[ci skip] [skip ci]
2020-03-19 08:39:53 +00:00
Sergio Brighenti
576131b67b Tag create system 2020-03-19 09:39:32 +01:00
Sergio Brighenti
efa3e99816
Merge pull request #159 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-03-17 23:10:13 +01:00
Almar Shenwan
860deae292 Translated using Weblate (Arabic)
Currently translated at 73.5% (111 of 151 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ar/
2020-03-17 16:26:23 +01:00
Johannes Krüger
5bae22b7a5 Translated using Weblate (German)
Currently translated at 99.3% (150 of 151 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2020-03-17 16:26:23 +01:00
Almar Shenwan
11c235b3c3 Added translation using Weblate (Arabic) 2020-03-17 16:26:23 +01:00
Sergio Brighenti
753f1ca71c Frontend for tag system 2020-03-17 16:25:46 +01:00
Sergio Brighenti
fd10fe21da Improved grid layout @Lukasss93 2020-03-16 23:21:45 +01:00
Sergio Brighenti
f7d4952785 Improved grid layout 2020-03-16 23:05:20 +01:00
Sergio Brighenti
0facbf6aec Update deps 2020-03-16 13:13:22 +01:00
Sergio Brighenti
38fdd263e8 Added notifications on user create 2020-03-14 21:22:48 +01:00
Sergio Brighenti
5d9810d287 Fix for #157 2020-03-14 12:21:40 +01:00
Sergio Brighenti
6d573457dd Added tags tables
Avoid long files name breaking the table
2020-03-13 19:44:42 +01:00
Sergio Brighenti
d8a5e28577
Merge pull request #156 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-03-12 11:41:30 +01:00
Ivan Simonsen
fee201477f
Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (146 of 146 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nb_NO/
2020-03-11 19:33:46 +01:00
whiskeey
697eee998e
Translated using Weblate (Polish)
Currently translated at 100.0% (146 of 146 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pl/
2020-03-11 19:33:38 +01:00
Johannes Krüger
45ff870d7f
Translated using Weblate (German)
Currently translated at 100.0% (146 of 146 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2020-03-11 19:33:32 +01:00
Sergio Brighenti
c65539e447 Moved function 2020-03-10 19:40:11 +01:00
Sergio Brighenti
7d5790cb61
Merge pull request #155 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-03-06 18:52:27 +01:00
whiskeey
23e818f170
Translated using Weblate (Polish)
Currently translated at 100.0% (146 of 146 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pl/
2020-03-06 17:33:26 +01:00
Sergio Brighenti
509bf9e978
Merge pull request #154 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-03-04 22:56:16 +01:00
whiskeey
719f9aa8bd
Translated using Weblate (Polish)
Currently translated at 100.0% (146 of 146 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pl/
2020-03-04 20:28:53 +01:00
Sergio Brighenti
0917b6702e
Merge pull request #153 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-03-04 19:51:36 +01:00
Sergio Brighenti
fbcea909d3
Translated using Weblate (Italian)
Currently translated at 99.3% (145 of 146 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/
2020-03-04 19:51:14 +01:00
whiskeey
41d4137bad
Translated using Weblate (Polish)
Currently translated at 77.3% (113 of 146 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pl/
2020-03-04 19:51:07 +01:00
Sergio Brighenti
dedf6e12ac Updated lang 2020-03-04 18:22:24 +01:00
Sergio Brighenti
e5d70c00ce Added recaptcha verification option.
Added bulk delete function.
Added account clean function (closes #151)
2020-03-04 15:25:45 +01:00
Sergio Brighenti
a22f6afc68
Merge pull request #152 from SergiX44/analysis-QMeVKp
Apply fixes from StyleCI
2020-03-03 17:19:28 +01:00
Sergio Brighenti
1a4ceba0a4 Apply fixes from StyleCI
[ci skip] [skip ci]
2020-03-03 16:19:10 +00:00
Sergio Brighenti
db483bb53e Implemented validation helper
Dependencies resolved through container
2020-03-03 17:18:52 +01:00
Sergio Brighenti
623de4b5e0 Refactoring 2020-03-02 21:43:34 +01:00
Sergio Brighenti
e4ed4c1862
Merge pull request #149 from SergiX44/analysis-bQOWR5
Apply fixes from StyleCI
2020-03-02 19:17:06 +01:00
Sergio Brighenti
e2d18b5d0d
Merge pull request #148 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-03-02 19:16:56 +01:00
Sergio Brighenti
dd12185aaa Apply fixes from StyleCI
[ci skip] [skip ci]
2020-03-02 18:16:27 +00:00
Allan Nordhøy
9bb3c17be1 Translated using Weblate (Norwegian Bokmål)
Currently translated at 90.2% (121 of 134 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nb_NO/
2020-03-02 19:16:23 +01:00
Allan Nordhøy
94ab2636a6 Translated using Weblate (English)
Currently translated at 100.0% (134 of 134 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/en/
2020-03-02 19:16:23 +01:00
Johannes Krüger
ee4931add3 Translated using Weblate (German)
Currently translated at 100.0% (134 of 134 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2020-03-02 19:16:23 +01:00
Sergio Brighenti
7d7f08caa6 Translated using Weblate (German)
Currently translated at 100.0% (134 of 134 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2020-03-02 19:16:23 +01:00
Johannes Krüger
88cdb5aba5 Translated using Weblate (German)
Currently translated at 100.0% (133 of 133 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2020-03-02 19:16:23 +01:00
Sergio Brighenti
ac4429fe0c Upload improvements 2020-03-02 19:16:11 +01:00
Sergio Brighenti
038fb156c1 Completed user quota implementation 2020-03-01 21:16:57 +01:00
Sergio Brighenti
17c24860b2 Completed user quota implementation 2020-03-01 17:03:07 +01:00
Sergio Brighenti
a5b8db5330 Fixed minor bug
working on user quota
2020-02-29 23:35:43 +01:00
Sergio Brighenti
0c949563ab typo 2020-02-28 14:30:12 +01:00
Sergio Brighenti
4297683e74 Update quota counters on upload and deletion 2020-02-28 14:29:29 +01:00
Sergio Brighenti
b2e0d683a2 Start user quota implementation 2020-02-27 19:45:05 +01:00
Sergio Brighenti
0d375201c8 Completed registration system (closes #90) 2020-02-27 18:56:48 +01:00
Sergio Brighenti
c62f4bf401
Merge pull request #144 from SergiX44/analysis-J2MR6D
Apply fixes from StyleCI
2020-02-27 18:41:08 +01:00
Sergio Brighenti
2f518baf48 Apply fixes from StyleCI
[ci skip] [skip ci]
2020-02-27 17:38:19 +00:00
Sergio Brighenti
ec68f98019
Merge pull request #143 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-02-27 18:38:11 +01:00
Sergio Brighenti
c06260e670
Translated using Weblate (Italian)
Currently translated at 100.0% (129 of 129 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/
2020-02-27 18:37:40 +01:00
Sergio Brighenti
9d3d85f739 Working on password recovery 2020-02-27 18:14:08 +01:00
Sergio Brighenti
f30d2b4011
Merge pull request #141 from SergiX44/analysis-OM5eOR
Apply fixes from StyleCI
2020-02-27 17:38:47 +01:00
Sergio Brighenti
e3a2c7573a Apply fixes from StyleCI
[ci skip] [skip ci]
2020-02-27 16:38:28 +00:00
Sergio Brighenti
ed89d8de6f
Merge pull request #140 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-02-27 17:38:21 +01:00
Allan Nordhøy
3d48484076 Translated using Weblate (Norwegian Bokmål)
Currently translated at 99.1% (119 of 120 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nb_NO/
2020-02-27 15:21:13 +01:00
Allan Nordhøy
8e8162acac Translated using Weblate (English)
Currently translated at 100.0% (120 of 120 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/en/
2020-02-27 15:21:13 +01:00
Sergio Brighenti
0269eaa6f6 User registration procedure 2020-02-27 15:18:01 +01:00
Sergio Brighenti
3d8fee6f61
Merge pull request #138 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-02-26 18:44:53 +01:00
Sergio Brighenti
328936ceef
Translated using Weblate (Italian)
Currently translated at 99.1% (119 of 120 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/
2020-02-26 18:44:27 +01:00
Sergio Brighenti
56edec94c8
Merge pull request #137 from SergiX44/analysis-BMPZNA
Apply fixes from StyleCI
2020-02-26 18:39:39 +01:00
Sergio Brighenti
b14287eb9d Apply fixes from StyleCI
[ci skip] [skip ci]
2020-02-26 17:26:34 +00:00
Sergio Brighenti
49c9e48e5e Working on user registration, user disk quota, password recovery 2020-02-26 18:26:19 +01:00
Sergio Brighenti
e2478f5880
Merge pull request #135 from SergiX44/analysis-D2Mv5y
Apply fixes from StyleCI
2020-02-26 12:23:15 +01:00
Sergio Brighenti
2d29398e4f Apply fixes from StyleCI
[ci skip] [skip ci]
2020-02-26 11:22:40 +00:00
Sergio Brighenti
12179f1b06 Added export feature (closes #126)
Added copy mode option (closes #117)
2020-02-26 12:22:25 +01:00
Sergio Brighenti
8e85f251b8 Added #116 2020-02-25 21:54:54 +01:00
Sergio Brighenti
10d7e0654d Preparing for new features
Fixed #131
2020-02-25 17:09:36 +01:00
Sergio Brighenti
31268ba6e1
Merge pull request #132 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-02-17 20:15:02 +01:00
whiskeey
6523b65cba
Translated using Weblate (Polish)
Currently translated at 100.0% (110 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pl/
2020-02-17 19:04:38 +01:00
Sergio Brighenti
eea70dbb32
Merge pull request #130 from weblate/weblate-xbackbone-xbackbone
Translations update from Weblate
2020-02-17 08:54:53 +01:00
Dinip
f5371859ac
Translated using Weblate (Portuguese)
Currently translated at 91.8% (101 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pt/
2020-02-16 17:50:58 +01:00
Weblate (bot)
9c3fb9a042
Translations update from Weblate (#129)
* Translated using Weblate (Polish)

Currently translated at 43.6% (48 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pl/

* Translated using Weblate (French)

Currently translated at 100.0% (110 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr/

Co-authored-by: nightcomdev <nightcom.dev@gmail.com>
Co-authored-by: Wanted135 <34011489+Wanted135@users.noreply.github.com>
2020-02-11 11:10:55 +01:00
Weblate (bot)
ffb3edac4e Translations update from Weblate (#124)
* Translated using Weblate (German)

Currently translated at 100.0% (110 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/

* Added translation using Weblate (Polish)

* Translated using Weblate (Polish)

Currently translated at 16.4% (18 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pl/

Co-authored-by: milotype <43657314+milotype@users.noreply.github.com>
Co-authored-by: 0NinjaCat <Ninjakot2014@gmail.com>
2020-01-28 14:51:02 +01:00
Weblate (bot)
09ced8e1cc Update from Weblate (#120)
* Added translation using Weblate (Japanese)

* Translated using Weblate (Japanese)

Currently translated at 61.8% (68 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ja/

* Translated using Weblate (Japanese)

Currently translated at 61.8% (68 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ja/

* Translated using Weblate (Japanese)

Currently translated at 66.4% (73 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ja/

Co-authored-by: r4ha <index@r4ha.me>
Co-authored-by: Sergio Brighenti <sergio@brighenti.me>
2019-12-20 18:35:39 +01:00
Sergio Brighenti
3b7e2fadde Fixed error with migrate command (closes #115) 2019-12-04 12:27:23 +01:00
Weblate (bot)
3b8eba3b5e Translated using Weblate (Norwegian Bokmål) (#114)
Currently translated at 100.0% (110 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nb_NO/
2019-12-03 09:53:17 +01:00
Sergio Brighenti
7beae0ae05 Fixed config wrong compilation
Fixes #112
Fixes #111
2019-11-25 23:37:12 +01:00
Sergio Brighenti
6951f7796b Composer lock 2019-11-25 21:19:14 +01:00
Sergio Brighenti
73a3fc08f1 Rearranged installer 2019-11-25 21:15:57 +01:00
Sergio Brighenti
6f4e19f02b Fixed error in mysql migration 2019-11-25 20:54:30 +01:00
Weblate (bot)
18827044d4 Deleted translation using Weblate (Japanese) (#110) 2019-11-23 18:49:47 +01:00
Sergio Brighenti
f82841259d Refresh the token after the login 2019-11-23 13:18:00 +01:00
Sergio Brighenti
12063d4542 Unset debug after installation 2019-11-22 12:21:02 +01:00
Sergio Brighenti
0f93a96d6b Refactor 2019-11-22 11:59:14 +01:00
Weblate (bot)
918028db3a Translated using Weblate (Japanese) (#109)
Currently translated at 0.9% (1 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ja/
2019-11-22 11:44:51 +01:00
Sergio Brighenti
0ae9531e34 Updated readme 2019-11-22 11:41:22 +01:00
Sergio Brighenti
092aaf64d2
Update README.md 2019-11-22 11:35:54 +01:00
Sergio Brighenti
97eb2439a4 Added new project images 2019-11-22 11:32:55 +01:00
Sergio Brighenti
54ca6fd2a2
Update FUNDING.yml 2019-11-22 10:44:54 +01:00
Sergio Brighenti
9870a63b65
Update FUNDING.yml 2019-11-22 01:51:50 +01:00
Sergio Brighenti
5cd276aade
Apply fixes from StyleCI (#108)
[ci skip] [skip ci]
2019-11-21 18:08:57 +01:00
SergiX44
6c004f8f96 Revert previous CI hell 2019-11-21 18:00:47 +01:00
Sergio Brighenti
f82d61a129
Merge pull request #105 from SergiX44/analysis-qJvvQE
Apply fixes from StyleCI

[ci skip] [skip ci]
2019-11-21 17:46:01 +01:00
Sergio Brighenti
f6b186ad99 Apply fixes from StyleCI
[ci skip] [skip ci]
2019-11-21 16:45:53 +00:00
Weblate (bot)
ae23c08045 Update from Weblate (#104)
* Deleted translation using Weblate (Japanese)

* Translated using Weblate (Italian)

Currently translated at 100.0% (110 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/
2019-11-21 15:24:07 +01:00
Sergio Brighenti
fc5f719df4
Apply fixes from StyleCI (#103) 2019-11-21 15:23:33 +01:00
Sergio Brighenti
7589348daf Added cancel upload button
Added button delete
2019-11-21 15:23:02 +01:00
Sergio Brighenti
8730a42ad5
Apply fixes from StyleCI (#102) 2019-11-20 18:54:00 +01:00
Weblate (bot)
cefc34c8b6 Translated using Weblate (French) (#101)
Currently translated at 80.9% (89 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr/
2019-11-20 18:53:22 +01:00
Sergio Brighenti
a424f87523
Apply fixes from StyleCI (#100) 2019-11-20 18:49:31 +01:00
Sergio Brighenti
1ce14a24f4 Controllers refactoring 2019-11-20 18:46:47 +01:00
Sergio Brighenti
aa460a3bb3 Remove dir 2019-11-19 15:19:47 +01:00
Sergio Brighenti
75e4d1e264 Wrong modal binding 2019-11-19 15:11:29 +01:00
Sergio Brighenti
3a85b40ccc Updated readme 2019-11-19 14:57:49 +01:00
Sergio Brighenti
472d392eba Added progress bar for file upload 2019-11-19 14:52:44 +01:00
Weblate (bot)
ebe78b4e46 Update from Weblate (#97)
* Translated using Weblate (Italian)

Currently translated at 97.3% (107 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 75.5% (83 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nb_NO/

* Translated using Weblate (English)

Currently translated at 100.0% (110 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/en/

* Translated using Weblate (German)

Currently translated at 100.0% (110 of 110 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2019-11-19 14:07:18 +01:00
Sergio Brighenti
3cd5dfbcec Changed lang configuration option 2019-11-19 13:59:17 +01:00
Sergio Brighenti
29dd38feb9 User controller refactoring 2019-11-19 12:55:51 +01:00
Sergio Brighenti
cb86a32d80 Container refactoring 2019-11-19 12:32:58 +01:00
SergiX44
399901c7a8 Fixing bad base url 2019-11-18 11:42:42 +01:00
Sergio Brighenti
7e5a156939 Fixed wrong javascript url 2019-11-17 20:36:18 +01:00
Sergio Brighenti
eccd5d5070 Fixed installer and app base paths
Fixes #93
2019-11-17 19:48:37 +01:00
Sergio Brighenti
3250fa7093 Merge remote-tracking branch 'origin/master' 2019-11-16 13:07:03 +01:00
Sergio Brighenti
d7a78a0201 Avoid response-copying when resizing images. 2019-11-16 13:06:53 +01:00
Weblate (bot)
b7982aae46 Update from Weblate (#94)
* Translated using Weblate (German)

Currently translated at 100.0% (108 of 108 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/

* Added translation using Weblate (Japanese)
2019-11-16 12:40:01 +01:00
Sergio Brighenti
12c49dcd14 Update issue templates 2019-11-16 12:36:41 +01:00
Sergio Brighenti
994ee0be34 Update issue templates 2019-11-16 12:33:58 +01:00
Sergio Brighenti
6b625b8bd2
Update bug_report.md 2019-11-16 12:30:35 +01:00
SergiX44
94219c4fa2 composer lock 2019-11-15 17:08:07 +01:00
Sergio Brighenti
bbf365279d linux script can be used on headless systems 2019-11-15 16:56:43 +01:00
Weblate (bot)
1912f4f1d3 Translated using Weblate (Italian) (#92)
Currently translated at 100.0% (108 of 108 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/
2019-11-15 16:02:33 +01:00
Sergio Brighenti
a849f867ee
Update CHANGELOG.md 2019-11-15 15:50:43 +01:00
Sergio Brighenti
748bd98abf Added remember me
Fixed middleware execution order
closes #81
2019-11-15 15:47:51 +01:00
Sergio Brighenti
5adb29d700 Added ability to set custom html
improved session samesite implementation for older php versions
Fixes #82
2019-11-15 00:56:25 +01:00
SergiX44
56c58d50b3 Secure session via SameSite header 2019-11-14 17:11:59 +01:00
Sergio Brighenti
1f8d6bba7d
Update README.md 2019-11-14 14:34:07 +01:00
Sergio Brighenti
be9e78abb0
Update FUNDING.yml 2019-11-14 14:27:11 +01:00
Sergio Brighenti
6cb67d2dbc Improved thumbnail generation
Fixes #88
2019-11-14 12:31:18 +01:00
Sergio Brighenti
310b40767c Implemented web upload
Closes #50 and #70
2019-11-13 23:31:48 +01:00
SergiX44
2c8c1a3cb6 Merge remote-tracking branch 'origin/master' 2019-11-13 17:33:14 +01:00
SergiX44
2bb22b8ec6 Test dropzone configuration 2019-11-13 17:32:45 +01:00
Sergio Brighenti
93c30599f4
Update CHANGELOG.md 2019-11-13 16:33:53 +01:00
SergiX44
5a0b5adcad Partially added web upload
#70 #50
2019-11-13 16:23:10 +01:00
Sergio Brighenti
2567ea3c5a Changed videojs to plyr
Fixed migrate command
2019-11-13 13:53:45 +01:00
Sergio Brighenti
b5784abb52 Converted installer 2019-11-13 13:02:31 +01:00
Sergio Brighenti
9c228247c9 Merge remote-tracking branch 'origin/master' 2019-11-13 01:07:51 +01:00
Sergio Brighenti
20d13c9bd1 Raw url accept also the file extension
Implements and closes #78
2019-11-13 01:07:38 +01:00
Weblate (bot)
6c859972ea Update from Weblate (#91)
* Deleted translation using Weblate (Japanese)

* Deleted translation using Weblate (Greek)

* Deleted translation using Weblate (Romanian)

* Deleted translation using Weblate (Polish)
2019-11-13 00:49:24 +01:00
Sergio Brighenti
75eb5fd39a
Update README.md 2019-11-13 00:48:42 +01:00
Sergio Brighenti
09eeac3ed1 Removed container instance 2019-11-13 00:30:55 +01:00
Weblate (bot)
5628965859 Update from Weblate (#86)
* Translated using Weblate (Norwegian Bokmål)

Currently translated at 80.4% (82 of 102 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nb_NO/

* Added translation using Weblate (Danish)

* Translated using Weblate (Danish)

Currently translated at 2.9% (3 of 102 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/da/

* Translated using Weblate (Danish)

Currently translated at 39.2% (40 of 102 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/da/

* Translated using Weblate (Danish)

Currently translated at 39.2% (40 of 102 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/da/
2019-11-13 00:16:37 +01:00
Sergio Brighenti
d8df60040a Upgrade to slim 4 2019-11-13 00:13:23 +01:00
Sergio Brighenti
f898f4fbd4
Update CHANGELOG.md 2019-10-23 12:19:06 +02:00
Sergio Brighenti
3a160e61a6 Updated gruntfile 2019-10-23 12:12:24 +02:00
Sergio Brighenti
d4f85241d0 Only update to the latest stable version 2019-10-23 11:55:08 +02:00
Weblate (bot)
5514d8c661 Update from Weblate (#80)
* Added translation using Weblate (Bulgarian)

* Translated using Weblate (Bulgarian)

Currently translated at 68.3% (69 of 101 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/bg/
2019-10-12 21:49:32 +02:00
Weblate (bot)
18a876e42e Update from Weblate (#79)
* Added translation using Weblate (Bulgarian)

* Translated using Weblate (Bulgarian)

Currently translated at 68.3% (69 of 101 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/bg/
2019-10-08 09:45:20 +02:00
Weblate (bot)
b01ce9587f Translated using Weblate (Portuguese) (#77)
Currently translated at 40.6% (41 of 101 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pt/
2019-09-23 21:35:24 +02:00
Sergio Brighenti
a59e364417 Clear database after orphaned files removal
plaintext passwd not correctly removed from logs
Fixes #74
2019-09-17 22:12:06 +02:00
Weblate (bot)
7457a611e1 Update from Weblate (#73)
* Translated using Weblate (Spanish)

Currently translated at 98.0% (99 of 101 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/es/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (101 of 101 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nl/

* Translated using Weblate (Spanish)

Currently translated at 98.0% (99 of 101 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/es/
2019-09-16 19:23:43 +02:00
Sergio Brighenti
5ecd7fe43e
Update README.md 2019-09-16 09:50:51 +02:00
Sergio Brighenti
8fe739139f Fixed icons in installer 2019-09-15 16:09:31 +02:00
Sergio Brighenti
6838ca93c3 lock file 2019-09-15 16:00:12 +02:00
Pietro Marangon
33e8223222 Prevent "Undefined index" notice #68 (#69)
* Prevent "Undefined index" notice #68

* Update Lang.php
2019-09-15 13:29:30 +02:00
Sergio Brighenti
98fe1dabfc lock file 2019-09-14 14:54:22 +02:00
Sergio Brighenti
99f100c228 Fixed #67 2019-09-14 14:50:14 +02:00
Tin Tin Hamans
846131a001 Update meta description tag with app name (#66) 2019-09-12 21:46:11 +02:00
Sergio Brighenti
8bef7bd69c
Update README.md 2019-09-06 14:55:42 +02:00
Sergio Brighenti
50d2dc176c
Update CHANGELOG.md 2019-09-06 14:32:42 +02:00
Weblate (bot)
8f476db005 Update from Weblate (#65)
* Translated using Weblate (Russian)

Currently translated at 100.0% (101 of 101 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ru/

* Translated using Weblate (Russian)

Currently translated at 100.0% (101 of 101 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ru/
2019-09-06 14:31:35 +02:00
Sergio Brighenti
009ed3e674 lock file 2019-09-06 14:29:48 +02:00
Sergio Brighenti
9294c3382b Move to fontawesome webfonts
Added cache busting
2019-09-06 14:26:41 +02:00
Sergio Brighenti
111f220629 Fixed installed bad path check 2019-09-04 21:43:43 +02:00
Sergio Brighenti
42883417e8 Fixed bad redirects (closes #62)
Improved installer extensions recognitions
Improved cli commands
2019-09-03 21:53:12 +02:00
Weblate (bot)
8cbef5ef39 Update from Weblate (#61)
* Translated using Weblate (German)

Currently translated at 100.0% (101 of 101 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/

* Translated using Weblate (English)

Currently translated at 100.0% (101 of 101 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/en/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 81.2% (82 of 101 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nb_NO/
2019-08-23 23:58:45 +02:00
Sergio Brighenti
cabde35a68
Update README.md 2019-08-20 23:00:33 +02:00
Weblate (bot)
e85192523f Translated using Weblate (Italian) (#60)
Currently translated at 99.0% (100 of 101 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/
2019-08-20 15:03:49 +02:00
Sergio Brighenti
5af7567b1a Improved lang handling 2019-08-20 14:56:41 +02:00
Weblate (bot)
d7913430fa Update from Weblate (#59)
* Added translation using Weblate (Greek)

* Added translation using Weblate (Spanish)

* Added translation using Weblate (French)

* Added translation using Weblate (Japanese)

* Added translation using Weblate (Dutch)

* Added translation using Weblate (Polish)

* Added translation using Weblate (Portuguese)

* Added translation using Weblate (Romanian)

* Added translation using Weblate (Russian)

* Translated using Weblate (German)

Currently translated at 98.9% (93 of 94 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/

* Translated using Weblate (Greek)

Currently translated at 1.1% (1 of 94 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/el/

* Translated using Weblate (Russian)

Currently translated at 1.1% (1 of 94 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ru/

* Translated using Weblate (Romanian)

Currently translated at 1.1% (1 of 94 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ro/

* Translated using Weblate (Japanese)

Currently translated at 1.1% (1 of 94 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/ja/

* Translated using Weblate (Portuguese)

Currently translated at 1.1% (1 of 94 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pt/

* Translated using Weblate (Polish)

Currently translated at 1.1% (1 of 94 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/pl/

* Translated using Weblate (Italian)

Currently translated at 100.0% (94 of 94 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/

* Translated using Weblate (Dutch)

Currently translated at 1.1% (1 of 94 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nl/

* Translated using Weblate (Spanish)

Currently translated at 1.1% (1 of 94 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/es/

* Translated using Weblate (French)

Currently translated at 1.1% (1 of 94 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/fr/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 80.9% (76 of 94 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nb_NO/
2019-08-19 23:12:25 +02:00
Violinist bot
dc109c4dc0 Update phpstan/phpstan (#57) 2019-08-19 18:08:29 +02:00
Sergio Brighenti
c980bf8103
Update README.md 2019-08-18 16:16:16 +02:00
Sergio Brighenti
716774d50d
Update README.md 2019-08-18 16:15:08 +02:00
Weblate (bot)
41334ed726 Update from Weblate (#55)
* Added translation using Weblate (Norwegian Bokmål)

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 80.9% (76 of 94 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/nb_NO/

* Translated using Weblate (Italian)

Currently translated at 100.0% (94 of 94 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/it/

* Added translation using Weblate (German)

* Deleted translation using Weblate (German)

* Added translation using Weblate (German)

* Translated using Weblate (German)

Currently translated at 0.0% (0 of 94 strings)

Translation: XBackBone/XBackBone
Translate-URL: https://hosted.weblate.org/projects/xbackbone/xbackbone/de/
2019-08-18 16:11:23 +02:00
Violinist bot
e5442665f4 Update phpstan/phpstan (#53) 2019-08-18 16:11:09 +02:00
Violinist bot
0d341941b6 Update phpstan/phpstan (#52) 2019-08-18 16:09:54 +02:00
Sergio Brighenti
bb39e58bc0
Update README.md 2019-08-18 15:58:29 +02:00
Sergio Brighenti
87c73183e7
Update README.md 2019-07-29 19:35:18 +02:00
Violinist bot
9007fbe82d Update phpstan/phpstan (#48) 2019-07-10 08:23:11 +02:00
Sergio Brighenti
1d401bc306
Merge pull request #49 from Kombustor/master
Improved linux uploading script
2019-07-10 08:22:51 +02:00
Fabian Schliski
8d3af80ac2
Silencing curl output in linux script 2019-07-09 19:17:36 +02:00
Fabian Schliski
1d8f588d48
Improved response checking in linux script
Added check that prevents throwing a JSON parsing error when the server is down
2019-07-09 19:17:12 +02:00
Fabian Schliski
c89bd9c654
Added check if jq is installed 2019-07-09 19:10:42 +02:00
Fabian Schliski
53a36d6e22
Added icon for uploader .desktop 2019-07-09 19:09:46 +02:00
Violinist bot
0259d56718 Update phpstan/phpstan (#45) 2019-07-07 15:53:28 +02:00
Violinist bot
b99630cd6b Update intervention/image (#44) 2019-06-26 13:28:09 +02:00
Violinist bot
4d21604e04 Update league/flysystem-aws-s3-v3 (#42) 2019-06-22 12:35:27 +02:00
Violinist bot
5105b02d92 Update league/flysystem (#43) 2019-06-22 12:35:16 +02:00
Violinist bot
99ba4445dd Update spatie/flysystem-dropbox (#41) 2019-05-30 20:12:09 +02:00
Violinist bot
10fc61955e Update phpstan/phpstan (#40) 2019-05-30 20:11:58 +02:00
Sergio Brighenti
f47bc3241a Improved install wizard 2019-05-23 21:24:04 +02:00
Sergio Brighenti
55cd988c79
Create FUNDING.yml 2019-05-23 20:17:18 +02:00
Violinist bot
141e9d396d Update league/flysystem (#39) 2019-05-23 13:33:31 +02:00
Violinist bot
3a610579c4 Update phpstan/phpstan (#38) 2019-05-19 22:45:12 +02:00
Sergio Brighenti
abd45346e6 Added some html in the template
(should be close once completed #37)
2019-05-19 21:37:09 +02:00
Sergio Brighenti
f6a6be7312 Start adding support to use AWS S3, Google Cloud Storage, Dropbox and FTP(s) accounts as storage location 2019-05-19 15:39:42 +02:00
Sergio Brighenti
635cffeb20 Merge remote-tracking branch 'origin/master' 2019-05-12 15:58:52 +02:00
Sergio Brighenti
ba91b9e1a5 Bump version 2019-05-12 15:58:44 +02:00
Sergio Brighenti
65b74645aa
Update CHANGELOG.md 2019-05-12 15:52:44 +02:00
Sergio Brighenti
de42f2e8bf Improved exception logging 2019-05-12 15:52:26 +02:00
Sergio Brighenti
67c9d82931 Merge remote-tracking branch 'origin/master' 2019-05-12 15:40:36 +02:00
Sergio Brighenti
c1920285e3 Fixed bad css loading on Firefox (#35)
Fixed wrong style for publish/unpublish button
2019-05-12 15:40:16 +02:00
Violinist bot
9c926bb10f Update phpstan/phpstan (#34) 2019-05-12 15:10:52 +02:00
Sergio Brighenti
a79ef5c4ed Merge remote-tracking branch 'origin/master' 2019-05-09 13:38:27 +02:00
Sergio Brighenti
6a90d9a876 Fixed telegram share not working 2019-05-09 13:35:57 +02:00
Sergio Brighenti
37d65dbe0c Improvements to the package system 2019-05-09 13:32:35 +02:00
Sergio Brighenti
d8d45daa3e Merge remote-tracking branch 'origin/master' 2019-05-09 12:51:48 +02:00
Sergio Brighenti
ba3de850f7 Improvements to the package system 2019-05-09 12:51:33 +02:00
Sergio Brighenti
619d2004f2
Update CHANGELOG.md 2019-05-08 18:51:04 +02:00
Sergio Brighenti
abe3733b09 Added preloading
Avoid to load big text files
Other improvements
2019-05-08 18:44:42 +02:00
Sergio Brighenti
0ce9bde57c Improved session error handling
Added check for the minimum php version
Package update and cleanup
2019-05-07 00:49:24 +02:00
Violinist bot
d02bc0afa2 Update slim/slim (#29) 2019-04-17 16:49:36 +02:00
Sergio Brighenti
f433103480
Update CHANGELOG.md 2019-04-10 22:35:23 +02:00
Sergio Brighenti
5f274cb493 Improved partial content implementation
Fixed issue #27
2019-04-10 21:28:40 +02:00
Violinist bot
86ee13a491 Update slim/twig-view (#26) 2019-04-06 20:35:32 +02:00
Violinist bot
ba61c3db7e Update league/flysystem (#25) 2019-03-30 23:06:15 +01:00
Sergio Brighenti
7de2b75cdc Updated js deps 2019-03-03 15:13:31 +01:00
Sergio Brighenti
f20874ca98
Update README.md 2019-02-10 19:03:10 +01:00
Sergio Brighenti
d4ab8ff21b Updated readme 2019-02-10 19:02:03 +01:00
Sergio Brighenti
c3401fbdee Included favicon 2019-02-10 18:33:16 +01:00
Sergio Brighenti
4a8681c7af Added favicon 2019-02-10 18:14:35 +01:00
Sergio Brighenti
bb0e3c80e7 Improved error handling 2019-02-10 13:48:20 +01:00
Sergio Brighenti
4f7c91cc3b Keep clean the config file after the upgrade 2019-02-07 17:34:19 +01:00
Sergio Brighenti
1df8ae9592 Added nginx.conf
improved templates
2019-02-06 22:34:56 +01:00
Violinist bot
df712e43b5 Update league/flysystem (#19) 2019-02-01 10:55:04 +01:00
Sergio Brighenti
3ba38eff2c Completed maintenance impl.
Small fixes
2019-01-31 20:53:49 +01:00
Sergio Brighenti
7b81478667 Added maintenance mode (close #18)
Optimized release zip size
2019-01-31 11:48:39 +01:00
Sergio Brighenti
c60e3d059e Completed partial content implementation
videojs layout fixes
Added translations
2019-01-30 23:19:33 +01:00
Sergio Brighenti
edd9895684 Streaming Partial content impl 2019-01-29 13:04:49 +01:00
Sergio Brighenti
6072df496e
Update LICENSE 2019-01-28 13:11:28 +01:00
Sergio Brighenti
ea6b9a4ebb Working on self update feature
Removed column too big (fixes #17)
2019-01-26 18:37:00 +01:00
Sergio Brighenti
af92f3d70a Working on self update feature 2019-01-24 21:48:22 +01:00
Sergio Brighenti
7671962506 Merge remote-tracking branch 'origin/master' 2019-01-24 11:50:27 +01:00
Sergio Brighenti
dbc55bdb6f Fixed inaccurate message in case of file too large (fixes #15)
Minor fix to the mobile layout
2019-01-24 11:49:46 +01:00
Sergio Brighenti
33c5560a0a
Update issue templates 2019-01-23 19:11:49 +01:00
Sergio Brighenti
597c9df305
Create CODE_OF_CONDUCT.md (#16) 2019-01-23 19:08:46 +01:00
Sergio Brighenti
f49d6ef979 Default order desc 2019-01-22 23:54:17 +01:00
Sergio Brighenti
61f3d6e742 Hide admin switch mode in user mode 2019-01-22 23:42:18 +01:00
Sergio Brighenti
03ae321464 Added missing css class 2019-01-22 23:38:55 +01:00
Sergio Brighenti
64d72d8ef7 Fixed error during the upgrade process 2019-01-22 23:32:54 +01:00
Sergio Brighenti
fb8fd68340 Implemented search
Updated js deps
2019-01-22 17:39:57 +01:00
Sergio Brighenti
fe19e9dede Merge remote-tracking branch 'origin/master' 2019-01-20 22:39:19 +01:00
Sergio Brighenti
9c5b5a763f Added twig layout with search, order by and sorting 2019-01-20 22:37:58 +01:00
Violinist bot
5065b63218 Update slim/slim (#13) 2019-01-15 16:19:18 +01:00
Sergio Brighenti
2b8671f0c6 Implemented custom queries 2019-01-13 21:27:10 +01:00
Sergio Brighenti
ca361c8eef Refactoring and improvements 2019-01-10 23:22:19 +01:00
Sergio Brighenti
8e26b35e2a Added curl and wget to bots 2018-12-09 14:58:25 +01:00
Sergio Brighenti
4aee250929 Removed useless assets from install 2018-12-09 14:55:03 +01:00
Sergio Brighenti
da437a203b Refactoring storage access
Added checks during installation phase
2018-12-09 14:42:50 +01:00
Sergio Brighenti
535fce4f8f Added checks in the installer 2018-12-07 10:17:45 +01:00
Sergio Brighenti
0d21b2b21d Working for improving the installer 2018-12-06 21:20:50 +01:00
Sergio Brighenti
d0edb403c5 Updated deps 2018-12-04 08:52:25 +01:00
Sergio Brighenti
4dbfa5cd23 Added missing getter 2018-11-30 21:36:12 +01:00
Sergio Brighenti
791cdaf9d5 Merge remote-tracking branch 'origin/master' 2018-11-30 21:24:52 +01:00
Sergio Brighenti
ef86b9f012 Fixed namespace 2018-11-30 21:24:44 +01:00
Sergio Brighenti
01a21e8864
Update README.md 2018-11-30 19:45:12 +01:00
Sergio Brighenti
b0df80b391
Update README.md 2018-11-30 19:44:05 +01:00
Sergio Brighenti
14ad4cdb05 Package lock 2018-11-30 19:38:59 +01:00
Sergio Brighenti
b67a189fc6 Documented linux support 2018-11-30 19:36:51 +01:00
Sergio Brighenti
a360ef06f4 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	resources/templates/dashboard/home.twig
2018-11-30 19:17:00 +01:00
Sergio Brighenti
cae02d816f Small script improvement 2018-11-30 10:51:03 +01:00
Sergio Brighenti
662cf9bcd2 Added linux script 2018-11-29 10:10:59 +01:00
Sergio Brighenti
f639f4ee57 Improved user gallery
Small layout changes
updated dependencies
2018-11-28 14:57:04 +01:00
Sergio Brighenti
8bb89ecb62 Merge branch 'master' of C:\Users\Sergio\Documents\GitHub\XBackBone with conflicts. 2018-11-25 16:16:43 +01:00
Sergio Brighenti
1d345cf61e Removed unused css 2018-11-20 18:47:32 +01:00
Sergio Brighenti
117910c95d Merge remote-tracking branch 'origin/master' 2018-11-20 18:46:51 +01:00
Sergio Brighenti
7c4c02fac4 Improved user gallery
Improved public view
2018-11-20 18:46:39 +01:00
Sergio Brighenti
099fd08893
Update README.md 2018-11-19 21:56:19 +01:00
Sergio Brighenti
f2c6936b8a Added discord service bot 2018-11-19 21:35:58 +01:00
Sergio Brighenti
dcc850bf01 Consistent html lang attribute (#2) 2018-11-19 19:45:52 +01:00
Sergio Brighenti
deab83f737 Consistent html lang attribute 2018-11-19 19:38:47 +01:00
Sergio Brighenti
586385c3fe Merge remote-tracking branch 'origin/master' 2018-11-19 19:19:10 +01:00
Sergio Brighenti
94bb1d3c56 Wrong link building 2018-11-19 19:19:02 +01:00
Sergio Brighenti
e1da0c9987
Update README.md 2018-11-19 19:10:04 +01:00
Sergio Brighenti
5e454b3720 Updated dependencies 2018-11-19 19:07:36 +01:00
Sergio Brighenti
a950ec9af3 Completed multilang
Routes rework
2018-11-19 19:04:43 +01:00
Sergio Brighenti
0409a598d7 Preparing for multilanguage support 2018-11-19 12:19:47 +01:00
Sergio Brighenti
cc68de1779 Fixed session reset with HTTP/2 enabled 2018-11-19 11:50:42 +01:00
Sergio Brighenti
12c400116c
Update README.md 2018-11-17 23:35:58 +01:00
Sergio Brighenti
4b25b842b1 Added support for sharex deletion url 2018-11-17 21:54:05 +01:00
Sergio Brighenti
0444705121 Added warn when try do download an invalid config 2018-11-15 17:10:43 +01:00
Sergio Brighenti
332ef074be Readded deleted files 2018-11-15 16:08:11 +01:00
Sergio Brighenti
2465fdd172 Improved theme 2018-11-15 12:10:23 +01:00
Sergio Brighenti
8bc50c69d9 Fixed http2 push 2018-11-14 22:35:39 +01:00
Sergio Brighenti
201affab45 Changed redirect function 2018-11-14 19:45:47 +01:00
Sergio Brighenti
d9dbedb470 Missing title in install 2018-11-14 19:28:26 +01:00
Sergio Brighenti
7b71addd00 Merge remote-tracking branch 'origin/master' 2018-11-14 19:25:20 +01:00
Sergio Brighenti
a825b74a0d Removed useless templates
Reorganized css/js in pages
2018-11-14 19:25:08 +01:00
Sergio Brighenti
8304aeba1d
Release 2.0 2018-11-13 18:56:28 +01:00
Sergio Brighenti
2df1254f44 Code cleanup 2018-11-13 18:47:57 +01:00
Sergio Brighenti
5c362d7e26 Bug fixes
added route caching
added helpers
improved redirecting
2018-11-13 18:05:17 +01:00
Sergio Brighenti
945790bedd Update mode 2018-11-13 13:43:06 +01:00
Sergio Brighenti
d6a9fcf600 theme switcher
mysql support
used space indicator
2018-11-12 18:56:12 +01:00
Sergio Brighenti
ba6ed78bd9 added discord bot
Changelog
2018-11-11 20:33:19 +01:00
Sergio Brighenti
4ee5c41f37 Completed install wizard 2018-11-11 20:20:38 +01:00
Sergio Brighenti
72523843b6 Working on install wizard 2018-11-11 19:18:27 +01:00
Sergio Brighenti
ca9b8b5bc6 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	composer.lock
2018-11-11 17:03:17 +01:00
Sergio Brighenti
4a4c51996c 2.0 Flight -> Slim 3 2018-11-11 17:02:50 +01:00
Violinist bot
2f43439a24 Update monolog/monolog (#11) 2018-11-05 13:24:36 +01:00
Violinist bot
a9f2d72a80 Update league/flysystem (#10) 2018-10-15 16:56:00 +02:00
Sergio Brighenti
235c0409b6
Update README.md 2018-10-14 14:17:25 +02:00
Sergio Brighenti
2588094ec3
Update README.md 2018-10-14 14:15:34 +02:00
Sergio Brighenti
df60d9aa3d
Update README.md 2018-10-14 01:30:53 +02:00
Sergio Brighenti
4b53033f82
Update README.md 2018-10-14 01:29:24 +02:00
Sergio Brighenti
c8d2238935
Update README.md 2018-10-14 01:28:06 +02:00
Sergio Brighenti
381aa01bf8
Update README.md 2018-10-14 01:27:34 +02:00
Sergio Brighenti
932a3763ca Merge remote-tracking branch 'origin/master' 2018-10-14 01:18:12 +02:00
Sergio Brighenti
ed6be635f1 Fixed gruntfile 2018-10-14 01:18:02 +02:00
Sergio Brighenti
4d465dcec5
Update README.md 2018-10-14 00:34:49 +02:00
194 changed files with 27598 additions and 3353 deletions

8
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,8 @@
# These are supported funding model platforms
github: [SergiX44]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: #
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
custom: http://bit.ly/XBackBonePaypal

33
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,33 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**System Info**
+ PHP Version:
+ XBackBone Version:
+ Webserver: [Apache/Nginx/...]
+ Database backend: [SQLite/Mysql/...]
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Logs**
XBackBone and/or webserver logs.

View file

@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context or screenshots about the feature request here.

27
.github/workflows/test_suite.yml vendored Normal file
View file

@ -0,0 +1,27 @@
name: PHP Composer
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
operating-system: [ubuntu-latest]
php-versions: ["7.3", "7.4", "8.0", "8.1", "8.2"]
steps:
- uses: actions/checkout@v2
- name: Validate composer.json and composer.lock
run: composer validate --no-check-version
- name: Install dependencies
run: composer install --no-progress
- name: Run test suite
run: vendor/bin/phpunit --no-coverage

BIN
.github/xbackbone.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

12
.gitignore vendored
View file

@ -2,13 +2,10 @@
composer.phar
/vendor/
# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
composer.lock
### grunt ###
# Grunt usually compiles files inside this directory
dist/
release*.zip
# Grunt usually preprocesses files such as coffeescript, compass... inside the .tmp directory
.tmp/
@ -77,6 +74,7 @@ typings/
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
@ -130,10 +128,14 @@ fabric.properties
# Repo file
storage/
static/
install/installer.js
!resources/cache/.gitkeep
resources/cache/*
resources/database/*.db
resources/sessions/sess_*
logs/log-*.txt
config.php
release.zip
release.zip
/.settings/
/.project
/.buildpath

View file

@ -1,6 +1,8 @@
Options -Indexes
RewriteEngine On
RewriteRule ^(app|bin|bootstrap|resources|storage|vendor|logs)(/.*|)$ - [NC,F]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
Options -Indexes +SymLinksIfOwnerMatch
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^(app|bin|bootstrap|resources|storage|vendor|logs|CHANGELOG.md)(/.*|)$ - [NC,F]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>

View file

@ -1,24 +1,450 @@
## v1.3
+ Added command to switch between bootswatch.com themes.
+ Added popever to write the telegram message when sharing.
+ Packaging improvements.
+ Updated some dependencies.
+ Allow Facebook bots to display the preview.
# Changelog
All notable changes to this project will be documented in this file.
## v1.2
+ Previews are now scaled for better page load.
+ Added auto config generator for ShareX.
+ Show upload file size on the dashboard.
+ Fixed insert for admin user (running `php bin\migrate --install`).
+ Removed HTTP2 push from the dashboard to improve loading time.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## v1.1
+ Added logging.
+ Fixed back to top when click delete or publish/unpublish.
+ Improved migrate system.
+ Login redirect back to the requested page.
+ Updated Bootstrap theme.
+ Added share to Telegram.
## Unreleased
## v1.0
+ Initial version.
## [3.7.0] - 2024-01-14
### Added
- Added support for vanity urls.
- Added KDE integration for linux script.
### Changed
- Updated translations.
- File preview is now clickable to open the file.
### Fixed
- Fixes for LDAP authentication.
## [3.6.3] - 2023-05-27
### Fixed
- Fix LDAP for php >= 8.1
## [3.6.2] - 2023-05-24
### Changed
- Support for PHP 8.2
### Removed
- Azure blob storage driver
## [3.6.1] - 2022-11-27
### Changed
- Upgraded dependencies
- Updated translations
### Fixed
- Fixed error in export data (#499)
- Fixed issues with reverse proxies (#495)
- Fixed duplicated twitter tags
## [3.6.0] - 2022-06-20
### Changed
- Improved embedding on discord of large videos.
- Releases are now compressed for faster downloads
- Updated translations
### Fixed
- Fixed deprecation notices on php >= 8
- Fixed embed UA for Discord.
- Fixed error with post_max_size = 0
### Removed
- Support for php 7.2
## [3.5.1] - 2021-10-22
### Changed
- Fixed embed UA for Discord.
- Updated translations.
## [3.5.0] - 2021-09-05
### Added
- Support for theme-park.dev themes.
- Updated translations.
### Fixed
- Wrong css when reapplying the default theme.
### Removed
- Dropped theme cli command.
## [3.4.1] - 2021-08-11
### Added
- Toggle to disable embeds.
### Changed
- Raw url copying now contains also the file extension.
## [3.4.0] - 2021-08-01
### Added
- Added image support for OG for Discord only.
### Changed
- Updated translations.
- Dropped support for PHP 7.1
### Fixed
- Fixed possible XSS and CSRF attacks.
## [3.3.5] - 2021-04-25
### Fixed
- Removed OG integration for discord.
### Changed
- Updated translations.
## [3.3.4] - 2021-03-07
### Added
- Login failed logging.
- User identifier option for LDAP configurations.
### Fixed
- Fixed open graph meta tags for Discord.
- Fixed custom html tags are not displayed back in the admin setting.
- Fixed python plugin for newer version of Screencloud.
- Fixed accented chars in email subject.
- Fixed error on PHP 8.
## [3.3.3] - 2020-11-13
### Fixed
- Fixed issue with responsive menu on mobile.
## [3.3.2] - 2020-11-12
### Fixed
- Fixed switch not works for the first time for normal users.
## [3.3.1] - 2020-11-12
### Fixed
- Formatting error on the check for updates.
- Fixed default view for normal users.
## [3.3.0] - 2020-11-12
### Added
- Enabled PHP 8 support.
- Added Screencloud client support (https://screencloud.net).
- OpenGraph image tag (issue #269).
- Start adding unit tests.
### Changed
- The list mode is now available also for non-admin accounts (issue #226).
### Fixed
- Linux script strange response code in headless mode.
### Removed
- Dropped Telegram share button.
## [3.2.0] - 2020-09-05
### Added
- Added support to use Azure Blob Storage account as storage location.
- Support for other S3-compatible storage endpoint.
- Line number when showing text files.
### Fixed
- S3 driver file streaming not working properly.
- Fixed Slack image preview.
## [3.1.4] - 2020-04-13
### Changed
- Now the migrate command resync the system quota for each user.
### Fixed
- Fixed error with the migrate command.
## [3.1.3] - 2020-04-13
### Changed
- Added changelog page.
- Updated translations.
## [3.1.2] - 2020-04-12
### Changed
- Improved installer storage checks.
### Fixed
- Fixed upload table lost when updating very old instances.
## [3.1.1] - 2020-04-11
### Fixed
- Fixed error during a fresh installation with sqlite.
## [3.1] - 2020-04-10
### Added
- Added tagging system (add, delete, search of tagged files).
- Added basic media auto-tagging on upload.
- Added registration system.
- Added password recovery system.
- Added ability to export all media of an account.
- Added ability to choose between default and raw url on copy.
- Added hide by default option.
- Added user disk quota.
- Added reCAPTCHA login protection.
- Added bulk delete.
- Added account clean function.
- Added user disk quota system.
- Added notification option on account create.
- Added LDAP authentication.
### Changed
- The theme is now re-applied after every system update.
- Updated system settings page.
- Updated translations.
- Improved grid layout.
### Fixed
- Fixed bug html files raws are rendered in a browser.
- Fixes and improvements.
## [3.0.2] - 2019-12-04
### Changed
- Updated translations.
### Fixed
- Fixed error with migrate command.
## [3.0.1] - 2019-11-25
### Changed
- Small installer update.
### Fixed
- Fixed error with older mysql versions.
- Fixed config is compiled with the di container.
## [3.0] - 2019-11-23
### Added
- Added web upload.
- Added ability to add custom HTML in \<head\> tag.
- Added ability to show a preview of PDF files.
- Added remember me functionality.
- Added delete button on the preview page if the user is logged in.
- New project icon (by [@SerenaItalia](https://www.deviantart.com/serenaitalia)).
- The linux script can be used on headless systems.
- Raw URL now accept file extensions.
- Implemented SameSite XSS protection.
### Changed
- Upgraded from Slim3 to Slim 4.
- Replaced videojs player with Plyr.
- Improved installer.
- Improved thumbnail generation.
- Small fixes and improvements.
## [2.6.6] - 2019-10-23
### Added
- Ability to choose between releases and prereleases with the web updater.
### Changed
- Updated translations.
## [2.6.5] - 2019-09-17
### Changed
- Changed color to some buttons to address visibility with some themes.
### Fixed
- Fixed error after orphaned files removal #74.
- Fixed update password not correctly removed from log files (#74).
## [2.6.4] - 2019-09-15
### Added
- Filter on displayable images.
### Changed
- The generated random strings are now more human readable.
### Fixed
- Fixed during upload error on php compiled for 32 bit.
- Fixed icons on the installer page.
## [2.6.3] - 2019-09-14
### Fixed
- Fixed #67.
- Fixed bad preload statement.
- Fixed wrong redirect after install in subdirs.
## [2.6.2] - 2019-09-06
### Added
- Added method for cache busting when updating/change theme.
- Added russian translation from [Weblate](https://hosted.weblate.org/projects/xbackbone/xbackbone/).
### Changed
- Changed background default color.
- Use the Font Awesome web font for better performances.
## [2.6.1] - 2019-09-04
### Added
- Added alert if required extensions are not loaded.
### Changed
- Improved shell commands.
- Updated translations.
### Fixed
- Fixed bad redirects on the web installer (#62).
- Fixed login page with dark themes.
## [2.6] - 2019-08-20
### Added
- Added support to use AWS S3, Google Cloud Storage, Dropbox and FTP(s) accounts as storage location.
- Added german and norwegian translations from [Weblate](https://hosted.weblate.org/projects/xbackbone/xbackbone/).
- Added ability to force system language.
### Changed
- Improved lang detection.
### Fixed
- Fixed missing icon.
## [2.5.3] - 2019-05-12
### Changed
- Improved exception stacktrace logging.
### Fixed
- Fixed bad css loading on Firefox (#35).
- Fixed wrong style for publish/unpublish button.
## [2.5.2] - 2019-05-09
### Added
- Added preloading for some resources to improve performances.
- Added check for block execution on EOL and unsupported PHP versions.
### Changed
- Improved session handling.
- Other minor improvements.
### Fixed
- Fixed telegram share not working.
- Fix for big text file now are not rendered in the browser.
## [2.5.1] - 2019-04-10
### Changed
- Improved HTTP partial content implementation for large files.
### Fixed
- Fixed bad redirect if the theme folder is not writable. (#27)
## [2.5] - 2019-02-10
### Added
- Added partial content implementation (stream seeking on chromium based browsers).
- **[BETA]** Added self update feature.
- Added project favicon.
### Changed
- Updated project license to [AGPL v3.0](https://choosealicense.com/licenses/agpl-3.0/) (now releases ships with the new license).
- Improved video.js alignment with large videos.
- Optimized output zip release size.
- Templates cleanup and optimizations.
- Improved error handling.
## [2.4.1] - 2019-01-24
### Fixed
- Fixed error message when the file is too large. (#15)
- Fixed button alignment.
## [2.4] - 2019-01-22
### Added
- Added function to remove orphaned files.
- Multiple uploads sorting methods.
- Switch between tab and gallery mode using an admin account.
- Search in uploads.
### Changed
- Updated js dependencies.
- Internal refactoring and improvements
## [2.3.1] - 2018-12-09
### Added
- Added checks during the installation wizard.
- cURL and Wget can now directly download the file.
### Fixed
- Fixed english language.
- Fixed forced background with dark themes.
## [2.3] - 2018-11-30
### Added
- Added overlay on user gallery images.
- Added linux script to allow uploads from linux screenshot tools.
- Enable audio player with video.js.
- Font Awesome icon match the single file mime-type.
### Changed
- Improved image scaling in user gallery.
- Video and audio now starts with volume at 50%.
- Minor layout fixes.
### Fixed
- Fixed IT translation.
## [2.2] - 2018-11-20
### Added
- Added multi-language support.
### Fixed
- Improved routing.
- Minor improvements and bug fixes.
- Fixed HTTP/2 push is resetting the current session.
## [2.1] - 2018-11-20
### Added
- Added video.js support.
- Allow e-mail login.
- Support for ShareX deletion URL.
### Changed
- Improved theme style.
- Improved page redirecting.
### Fixed
- Fixed HTTP/2 push preload.
## [2.0] - 2018-11-13
### Added
- Added install wizard (using the CLI is no longer required).
- Added used space indicator per user.
- Allow discord bot to display the preview.
- Theme switcher on the web UI.
- MySQL support.
### Changed
- Migrated from Flight to Slim 3 framework.
- Improvements under the hood.
## [1.3] - 2018-10-14
### Added
- Added command to switch between bootswatch.com themes.
- Added popover to write the telegram message when sharing.
- Allow Facebook bots to display the preview.
### Changed
- Packaging improvements.
- Updated some dependencies.
## [1.2] - 2018-05-01
### Added
- Added auto config generator for ShareX.
- Show upload file size on the dashboard.
### Changed
- Previews are now scaled for better page load.
### Removed
- Removed HTTP2 push from the dashboard to improve loading time.
### Fixed
- Fixed insert for admin user (running `php bin\migrate --install`).
## [1.1] - 2018-04-28
### Added
- Added logging.
- Added share to Telegram.
### Changed
- Improved migrate system.
- Updated Bootstrap theme.
### Fixed
- Fixed back to top when click delete or publish/unpublish.
- Login redirect back to the requested page.
## [1.0] - 2018-04-28
### Added
- Initial version.

76
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at sergio@brighenti.me. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

View file

@ -1,9 +1,14 @@
module.exports = function (grunt) {
let version = grunt.file.readJSON('composer.json').version;
let releaseFilename = 'release-v' + version + '.zip';
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
jshint: {
all: ['Gruntfile.js', 'src/js/app.js']
all: ['Gruntfile.js', 'src/js/app.js'],
options: {
'esversion': 6,
}
},
cssmin: {
@ -26,6 +31,9 @@ module.exports = function (grunt) {
'static/app/app.js': [
'src/js/app.js'
],
'install/installer.js': [
'src/js/installer.js'
],
}
}
},
@ -41,6 +49,7 @@ module.exports = function (grunt) {
scripts: {
files: [
'src/js/app.js',
'src/js/installer.js',
],
tasks: ['uglify']
@ -50,24 +59,131 @@ module.exports = function (grunt) {
copy: {
main: {
files: [
{expand: true, cwd: 'node_modules/@fortawesome/fontawesome-free', src: ['css/**', 'js/**'], dest: 'static/fontawesome'},
{expand: true, cwd: 'node_modules/bootstrap/dist', src: ['**'], dest: 'static/bootstrap'},
{expand: true, cwd: 'node_modules/clipboard/dist', src: ['**'], dest: 'static/clipboardjs'},
{expand: true, cwd: 'node_modules/highlightjs', src: ['styles/**/*', 'highlight.pack.min.js'], dest: 'static/highlightjs'},
{
expand: true,
cwd: 'node_modules/@fortawesome/fontawesome-free',
src: ['css/all.min.css', 'webfonts/**/*'],
dest: 'static/fontawesome'
},
{
expand: true,
cwd: 'node_modules/bootstrap/dist/css',
src: ['bootstrap.min.css'],
dest: 'static/bootstrap/css'
},
{
expand: true,
cwd: 'node_modules/bootstrap/dist/js',
src: ['bootstrap.bundle.min.js'],
dest: 'static/bootstrap/js'
},
{
expand: true,
cwd: 'node_modules/clipboard/dist',
src: ['clipboard.min.js'],
dest: 'static/clipboardjs'
},
{
expand: true,
cwd: 'node_modules/plyr/dist',
src: ['plyr.min.js', 'plyr.css'],
dest: 'static/plyr'
},
{
expand: true,
cwd: 'node_modules/highlightjs',
src: ['styles/**/*', 'highlight.pack.min.js'],
dest: 'static/highlightjs'
}, {
expand: true,
cwd: 'node_modules/highlightjs-line-numbers.js/dist',
src: ['highlightjs-line-numbers.min.js'],
dest: 'static/highlightjs'
},
{
expand: true,
cwd: 'node_modules/dropzone/dist/min',
src: ['dropzone.min.css', 'dropzone.min.js'],
dest: 'static/dropzone'
},
{
expand: true,
cwd: 'node_modules/bootstrap4-toggle/css',
src: ['bootstrap4-toggle.min.css'],
dest: 'static/bootstrap/css'
},
{
expand: true,
cwd: 'node_modules/bootstrap4-toggle/js',
src: ['bootstrap4-toggle.min.js'],
dest: 'static/bootstrap/js'
},
{
expand: true,
cwd: 'src/images',
src: ['**/*'],
dest: 'static/images'
},
{expand: true, cwd: 'node_modules/jquery/dist', src: ['jquery.min.js'], dest: 'static/jquery'}
],
},
},
zip: {
'release.zip': ['app/**/*', 'bin/**/*', 'bootstrap/**/*', 'logs/**/*', 'resources/**/', 'static/**/*', '.htaccess', 'config.example.php', 'index.php', 'vendor']
}
shell: {
phpstan: {
command: '"./vendor/bin/phpstan" --level=0 analyse app resources/lang bin install'
},
composer_no_dev: {
command: 'composer install --no-dev --prefer-dist'
}
},
compress: {
main: {
options: {
archive: releaseFilename,
mode: 'zip',
level: 9,
},
files: [{
expand: true,
cwd: './',
src: [
'app/**/*',
'bin/**/*',
'bootstrap/**/*',
'install/**/*',
'logs/**/',
'resources/cache',
'resources/sessions',
'resources/database',
'resources/lang/**/*',
'resources/templates/**/*',
'resources/schemas/**/*',
'resources/lang/**/*',
'resources/uploaders/**/*',
'static/**/*',
'vendor/**/*',
'.htaccess',
'config.example.php',
'index.php',
'composer.json',
'composer.lock',
'LICENSE',
'favicon.ico',
'CHANGELOG.md'
],
dest: '/'
}]
}
},
});
require('load-grunt-tasks')(grunt);
grunt.registerTask('default', ['jshint', 'cssmin', 'uglify', 'copy']);
grunt.registerTask('test', ['jshint']);
grunt.registerTask('build-release', ['default', 'zip']);
grunt.registerTask('phpstan', ['shell:phpstan']);
grunt.registerTask('composer_no_dev', ['shell:composer_no_dev']);
grunt.registerTask('build-release', ['default', 'composer_no_dev', 'compress']);
};

149
LICENSE
View file

@ -1,23 +1,21 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
@ -72,7 +60,7 @@ modification follow.
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

138
README.md
View file

@ -1,107 +1,53 @@
# XBackBone 📤 [![Build Status](https://travis-ci.org/SergiX44/XBackBone.svg?branch=master)](https://travis-ci.org/SergiX44/XBackBone)
XBackBone is a simple, self-hosted, lightweight PHP backend for the instant sharing tool ShareX. It supports uploading and displaying images, GIF, video, code, formatted text, and file downloading and uploading. Also have a web UI with multi user management and past uploads history.
# <a href="https://hosted.weblate.org/engage/xbackbone/?utm_source=widget"><img src="https://hosted.weblate.org/widgets/xbackbone/-/xbackbone/svg-badge.svg" alt="Weblate"></a> <a href="https://codeclimate.com/github/SergiX44/XBackBone/maintainability"><img src="https://api.codeclimate.com/v1/badges/bf8ee4a8df9c9f0dfa08/maintainability" alt="Codeclimate"></a> <a href="http://bit.ly/XBackBonePaypal"><img src="https://img.shields.io/badge/donate-PayPal-yellow" alt="Donations"></a> <a href="https://discord.gg/ksPfXFbhDF"><img src="https://img.shields.io/discord/780922715393359904?label=discord%20chat" alt="Discord"></a>
## Features
<p align="center">
<img src=".github/xbackbone.png" width="350px">
</p>
XBackBone is a simple, self-hosted, lightweight PHP file manager that support the instant sharing tool ShareX and *NIX systems. It supports uploading and displaying images, GIF, video, code, formatted text, and file downloading and uploading. Also have a web UI with multi user management, past uploads history and search support.
## Documentation
All the installations, configuration, and usage instructions are available in the GitHub Pages:
[XBackBone Documentation](https://sergix44.github.io/XBackBone/)
## Main Features
+ Supports every upload type from ShareX.
+ User management, multi user features.
+ Config generator for ShareX.
+ Low memory footprint.
+ Multiple backends support: Local storage, AWS S3, Google Cloud, Azure Blob Storage, Dropbox, FTP(s).
+ Web file upload.
+ Code uploads syntax highlighting.
+ Video and audio uploads webplayer.
+ PDF viewer.
+ Files preview page.
+ Bootswatch themes support.
+ Responsive theme for mobile use.
+ Multi language support.
+ User management, multi user features, roles and disk quota.
+ Public and private uploads.
+ Web UI for each user.
+ Logging system.
+ Auto config generator for ShareX.
+ Share to Telegram.
+ Linux supported via a per-user custom generated script (server and desktop).
+ Direct downloads using curl or wget commands.
+ Direct images links support on Discord, Telegram, Facebook, etc.
+ System updates without FTP or CLI.
+ Easy web installer.
+ LDAP authentication.
+ Registration system.
+ Automatic uploads tagging system.
+ Tag uploads with custom tags for categorization.
+ ... and more.
## How to Install
XBackBone require PHP >= `7.1`, the composer package manager, writable storage path and PDO, with installed the needed extensions (ex. `php-sqlite3` for SQLite):
+ **[release, stable]** Download latest release from GitHub:
[https://github.com/SergiX44/XBackBone/releases/latest](https://github.com/SergiX44/XBackBone/releases/latest)
+ Run composer from your shell:
## Security Vulnerabilities
```sh
composer install --no-dev
```
+ Setup the config file:
If you discover a security vulnerability within XBackBone, please send an e-mail to Sergio at sergio@brighenti.me. All security vulnerabilities will be promptly addressed.
```sh
cp config.example.php config.php
```
By default, XBackBone will use Sqlite as DB engine, and a `storage` dir in the current directory. You can leave these settings unchanged for a simple personal installation.
You must set the `base_url`, or remove it for get dynamically the url from request (not raccomanded).
## License
This software is licensed under the <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>, available in this repository.
As a "copyright notice" it is sufficient to keep the small footer at the bottom of the page, also to help other people to learn about this project!
```php
return [
'base_url' => 'https://myaswesomedomain.com', // no trailing slash
'storage_dir' => 'storage',
'db' => [
'connection' => 'sqlite',
'dsn' => 'resources/database/xbackbone.db',
'username' => null, // username and password not needed for sqlite
'password' => null,
]
];
```
+ Finally, run the migrate script to setup the database
```sh
php bin/migrate --install
```
+ Now just login with `admin/admin`, **be sure to change these credentials after your first login**.
#### Other deployment method
+ [Docker container](https://hub.docker.com/r/pe46dro/xbackbone-docker)
## ShareX Configuration
Once you are logged in, just go in your profile settings and download the ShareX config file for your account.
## Notes
If you do not use Apache, or the Apache `.htaccess` is not enabled, set your web server so that the `static/` folder is the only one accessible from the outside, otherwise even private uploads and logs will be accessible!
The NGINX configuration should be something like this:
```
# nginx configuration
location /app {
return 403;
}
location /bin {
return 403;
}
location /bootstrap {
return 403;
}
location /resources {
return 403;
}
location /storage {
return 403;
}
location /vendor {
return 403;
}
location /logs {
return 403;
}
autoindex off;
location / {
if (!-e $request_filename){
rewrite ^(.*)$ /index.php break;
}
}
```
## Built with
+ FlightPHP (http://flightphp.com/)
+ Bootstrap 4 (https://getbootstrap.com/)
+ Font Awesome 5 (http://fontawesome.com)
+ ClipboardJS (https://clipboardjs.com/)
+ HighlightJS (https://highlightjs.org/)
+ JQuery (https://jquery.com/)

5
SECURITY.md Normal file
View file

@ -0,0 +1,5 @@
# Security Policy
## Security Vulnerabilities
If you discover a security vulnerability within XBackBone, please send an e-mail to Sergio at sergio@brighenti.me. All security vulnerabilities will be promptly addressed.

View file

@ -0,0 +1,110 @@
<?php
namespace App\Controllers;
use App\Database\Migrator;
use App\Web\Theme;
use League\Flysystem\FileNotFoundException;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class AdminController extends Controller
{
/**
* @param Request $request
* @param Response $response
*
* @return Response
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*
*/
public function system(Request $request, Response $response): Response
{
$settings = [];
foreach ($this->database->query('SELECT `key`, `value` FROM `settings`') as $setting) {
$settings[$setting->key] = $setting->value;
}
$settings['default_user_quota'] = humanFileSize(
$this->getSetting('default_user_quota', stringToBytes('1G')),
0,
true
);
return view()->render($response, 'dashboard/system.twig', [
'usersCount' => $this->database->query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count,
'mediasCount' => $this->database->query('SELECT COUNT(*) AS `count` FROM `uploads`')->fetch()->count,
'orphanFilesCount' => $this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `user_id` IS NULL')->fetch()->count,
'totalSize' => humanFileSize($this->database->query('SELECT SUM(`current_disk_quota`) AS `sum` FROM `users`')->fetch()->sum ?? 0),
'post_max_size' => ini_get('post_max_size'),
'upload_max_filesize' => ini_get('upload_max_filesize'),
'installed_lang' => $this->lang->getList(),
'forced_lang' => $request->getAttribute('forced_lang'),
'php_version' => PHP_VERSION,
'max_memory' => ini_get('memory_limit'),
'settings' => $settings,
]);
}
/**
* @param Response $response
*
* @return Response
*/
public function deleteOrphanFiles(Response $response): Response
{
$orphans = $this->database->query('SELECT * FROM `uploads` WHERE `user_id` IS NULL')->fetchAll();
$filesystem = $this->storage;
$deleted = 0;
foreach ($orphans as $orphan) {
try {
$filesystem->delete($orphan->storage_path);
$deleted++;
} catch (FileNotFoundException $e) {
}
}
$this->database->query('DELETE FROM `uploads` WHERE `user_id` IS NULL');
$this->session->alert(lang('deleted_orphans', [$deleted]));
return redirect($response, route('system'));
}
/**
* @param Response $response
*
* @return Response
*/
public function getThemes(Response $response): Response
{
$themes = make(Theme::class)->availableThemes();
$out = [];
foreach ($themes as $vendor => $list) {
$out["-- {$vendor} --"] = null;
foreach ($list as $name => $url) {
$out[$name] = "{$vendor}|{$url}";
}
}
return json($response, $out);
}
/**
* @param Response $response
* @return Response
*/
public function recalculateUserQuota(Response $response): Response
{
$migrator = new Migrator($this->database, null);
$migrator->reSyncQuotas($this->storage);
$this->session->alert(lang('quota_recalculated'));
return redirect($response, route('system'));
}
}

View file

@ -0,0 +1,123 @@
<?php
namespace App\Controllers\Auth;
use App\Controllers\Controller;
use App\Web\Session;
use App\Web\ValidationHelper;
use Psr\Http\Message\ServerRequestInterface as Request;
abstract class AuthController extends Controller
{
protected function checkRecaptcha(ValidationHelper $validator, Request $request)
{
$validator->callIf($this->getSetting('recaptcha_enabled') === 'on', function (Session $session) use (&$request) {
$recaptcha = json_decode(file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret='.$this->getSetting('recaptcha_secret_key').'&response='.param($request, 'recaptcha_token')));
if ($recaptcha->success && $recaptcha->score < 0.5) {
$session->alert(lang('recaptcha_failed'), 'danger');
return false;
}
return true;
});
return $validator;
}
/**
* Connects to LDAP server and logs in with service account (if configured)
* @return \LDAP\Connection|resource|false
*/
public function ldapConnect()
{
if (!extension_loaded('ldap')) {
$this->logger->error('The LDAP extension is not loaded.');
return false;
}
// Building LDAP URI
$ldapSchema=(@is_string($this->config['ldap']['schema'])) ?
strtolower($this->config['ldap']['schema']) : 'ldap';
$ldapURI="$ldapSchema://".$this->config['ldap']['host'].':'.$this->config['ldap']['port'];
// Connecting to LDAP server
$this->logger->debug("Connecting to $ldapURI");
$server = ldap_connect($ldapURI);
if ($server) {
ldap_set_option($server, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($server, LDAP_OPT_REFERRALS, 0);
ldap_set_option($server, LDAP_OPT_NETWORK_TIMEOUT, 10);
} else {
$this->logger->error('LDAP-URI was not parseable');
return false;
}
// Upgrade to StartTLS
$useStartTLS = @is_bool($this->config['ldap']['useStartTLS']) ? $this->config['ldap']['useStartTLS'] : false;
if (($useStartTLS === true) && (ldap_start_tls($server) === false)) {
$this->logger->debug(ldap_error($server));
$this->logger->error("Failed to establish secure LDAP swith StartTLS");
return false;
}
// Authenticating LDAP service account (if configured)
$serviceAccountFQDN= (@is_string($this->config['ldap']['service_account_dn'])) ?
$this->config['ldap']['service_account_dn'] : null;
if (is_string($serviceAccountFQDN)) {
if (ldap_bind($server, $serviceAccountFQDN, $this->config['ldap']['service_account_password']) === false) {
$this->logger->debug(ldap_error($server));
$this->logger->error("Bind with service account ($serviceAccountFQDN) failed.");
return false;
}
}
return $server;
}
/**
* Returns User's LDAP DN
* @param string $username
* @param \LDAP\Connection|resource $server LDAP Server Resource
* @return string|null
*/
protected function getLdapRdn(string $username, $server)
{
//Dynamic LDAP User Binding
if (@is_string($this->config['ldap']['search_filter'])) {
//Replace ???? with username
$searchFilter = str_replace('????', ldap_escape($username, '', LDAP_ESCAPE_FILTER), $this->config['ldap']['search_filter']);
$ldapAddributes = array('dn');
$this->logger->debug("LDAP Search filter: $searchFilter");
$ldapSearchResp = ldap_search(
$server,
$this->config['ldap']['base_domain'],
$searchFilter,
$ldapAddributes
);
if (!$ldapSearchResp) {
$this->logger->debug(ldap_error($server));
$this->logger->error("User LDAP search for user $username failed");
return null;
}
if (ldap_count_entries($server, $ldapSearchResp) !== 1) {
$this->logger->notice("LDAP search for $username not found or had multiple entries");
return null;
}
$ldapEntry = ldap_first_entry($server, $ldapSearchResp);
//Returns full DN
$bindString = ldap_get_dn($server, $ldapEntry);
} else {
// Static LDAP Binding
$bindString = ($this->config['ldap']['rdn_attribute'] ?? 'uid=').addslashes($username);
if ($this->config['ldap']['user_domain'] !== null) {
$bindString .= ','.$this->config['ldap']['user_domain'];
}
if ($this->config['ldap']['base_domain'] !== null) {
$bindString .= ','.$this->config['ldap']['base_domain'];
}
//returns partial DN
}
return $bindString;
}
}

View file

@ -0,0 +1,183 @@
<?php
namespace App\Controllers\Auth;
use App\Database\Repositories\UserRepository;
use App\Web\ValidationHelper;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class LoginController extends AuthController
{
/**
* @param Response $response
*
* @return Response
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*
* @throws \Twig\Error\LoaderError
*/
public function show(Response $response): Response
{
if ($this->session->get('logged', false)) {
return redirect($response, route('home'));
}
return view()->render($response, 'auth/login.twig', [
'register_enabled' => $this->getSetting('register_enabled', 'off'),
'recaptcha_site_key' => $this->getSetting('recaptcha_enabled') === 'on' ? $this->getSetting('recaptcha_site_key') : null,
]);
}
/**
* @param Request $request
* @param Response $response
*
* @return Response
* @throws \Exception
*
*/
public function login(Request $request, Response $response): Response
{
/** @var ValidationHelper $validator */
$validator = make(ValidationHelper::class);
if ($this->checkRecaptcha($validator, $request)->fails()) {
return redirect($response, route('login'));
}
$username = param($request, 'username');
$password = param($request, 'password');
$user = $this->database->query('SELECT `id`, `email`, `username`, `password`,`is_admin`, `active`, `current_disk_quota`, `max_disk_quota`, `ldap`, `copy_raw` FROM `users` WHERE `username` = ? OR `email` = ? LIMIT 1', [$username, $username])->fetch();
if ($this->config['ldap']['enabled'] && (!$user || $user->ldap ?? true)) {
$user = $this->ldapLogin($request, $username, param($request, 'password'), $user);
}
$validator
->alertIf(!$user || !password_verify($password, $user->password), 'bad_login')
->alertIf(isset($this->config['maintenance']) && $this->config['maintenance'] && !($user->is_admin ?? true), 'maintenance_in_progress', 'info')
->alertIf(!($user->active ?? false), 'account_disabled');
if ($validator->fails()) {
if (!empty($request->getHeaderLine('X-Forwarded-For'))) {
$ip = $request->getHeaderLine('X-Forwarded-For');
} else {
$ip = $request->getServerParams()['REMOTE_ADDR'] ?? null;
}
$this->logger->info("Login failed with username='{$username}', ip={$ip}.");
return redirect($response, route('login'));
}
$this->session->set('logged', true)
->set('user_id', $user->id)
->set('username', $user->username)
->set('admin', $user->is_admin)
->set('copy_raw', $user->copy_raw);
$this->setSessionQuotaInfo($user->current_disk_quota, $user->max_disk_quota);
$this->session->alert(lang('welcome', [$user->username]), 'info');
$this->logger->info("User $user->username logged in.");
if (param($request, 'remember') === 'on') {
$this->refreshRememberCookie($user->id);
}
if ($this->session->has('redirectTo')) {
return redirect($response, $this->session->get('redirectTo'));
}
return redirect($response, route('home'));
}
/**
* @param Request $request
* @param Response $response
*
* @return Response
*/
public function logout(Request $request, Response $response): Response
{
$this->session->clear();
$this->session->set('logged', false);
$this->session->alert(lang('goodbye'), 'warning');
if (!empty($request->getCookieParams()['remember'])) {
setcookie('remember', null, 0, '', '', false, true);
}
return redirect($response, route('login.show'));
}
/**
* @param Request $request
* @param string $username
* @param string $password
* @param $dbUser
* @return bool|null
* @throws \Slim\Exception\HttpNotFoundException
* @throws \Slim\Exception\HttpUnauthorizedException
*/
protected function ldapLogin(Request $request, string $username, string $password, $dbUser)
{
// Build LDAP connection
$server = $this->ldapConnect();
if (!$server) {
$this->session->alert(lang('ldap_cant_connect'), 'warning');
return $dbUser;
}
//Get LDAP user's (R)DN
$userDN=$this->getLdapRdn($username, $server);
if (!is_string($userDN)) {
return null;
}
//Bind as user to validate password
if (@ldap_bind($server, $userDN, $password)) {
$this->logger->debug("$userDN authenticated against LDAP sucessfully");
} else {
$this->logger->debug("$userDN authenticated against LDAP unsucessfully");
if ($dbUser && !$dbUser->ldap) {
return $dbUser;
}
return null;
}
if (!$dbUser) {
$email = $username;
if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
if (@is_string($this->config['ldap']['search_filter'])) {
$search = ldap_read(
$server,
$userDN,
'objectClass=*',
array('mail',$this->config['ldap']['rdn_attribute'])
);
} else {
$search = ldap_search($server, $this->config['ldap']['base_domain'], ($this->config['ldap']['rdn_attribute'] ?? 'uid=').addslashes($username), ['mail']);
}
$entry = ldap_first_entry($server, $search);
$email = @ldap_get_values($server, $entry, 'mail')[0] ?? platform_mail($username.rand(0, 100)); // if the mail is not set, generate a placeholder
}
/** @var UserRepository $userQuery */
$userQuery = make(UserRepository::class);
$userQuery->create($email, $username, $password, 0, 1, (int) $this->getSetting('default_user_quota', -1), null, 1);
return $userQuery->get($request, $this->database->getPdo()->lastInsertId());
}
if ($server) {
ldap_close($server);
}
if (!password_verify($password, $dbUser->password)) {
$userQuery = make(UserRepository::class);
$userQuery->update($dbUser->id, $dbUser->email, $username, $password, $dbUser->is_admin, $dbUser->active, $dbUser->max_disk_quota, $dbUser->ldap);
return $userQuery->get($request, $dbUser->id);
}
return $dbUser;
}
}

View file

@ -0,0 +1,130 @@
<?php
namespace App\Controllers\Auth;
use App\Web\Mail;
use App\Web\ValidationHelper;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Exception\HttpNotFoundException;
class PasswordRecoveryController extends AuthController
{
/**
* @param Response $response
* @return Response
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public function recover(Response $response): Response
{
return view()->render($response, 'auth/recover_mail.twig', [
'recaptcha_site_key' => $this->getSetting('recaptcha_enabled') === 'on' ? $this->getSetting('recaptcha_site_key') : null,
]);
}
/**
* @param Request $request
* @param Response $response
* @return Response
* @throws \Exception
*/
public function recoverMail(Request $request, Response $response): Response
{
if ($this->session->get('logged', false)) {
return redirect($response, route('home'));
}
if ($this->checkRecaptcha(make(ValidationHelper::class), $request)->fails()) {
return redirect($response, route('recover'));
}
$user = $this->database->query('SELECT `id`, `username` FROM `users` WHERE `email` = ? AND NOT `ldap` LIMIT 1', param($request, 'email'))->fetch();
if (!isset($user->id)) {
$this->session->alert(lang('recover_email_sent'), 'success');
return redirect($response, route('recover'));
}
$resetToken = bin2hex(random_bytes(16));
$this->database->query('UPDATE `users` SET `reset_token`=? WHERE `id` = ?', [
$resetToken,
$user->id,
]);
Mail::make()
->from(platform_mail(), $this->config['app_name'])
->to(param($request, 'email'))
->subject(lang('mail.recover_password', [$this->config['app_name']]))
->message(lang('mail.recover_text', [
$user->username,
route('recover.password', ['resetToken' => $resetToken]),
route('recover.password', ['resetToken' => $resetToken]),
]))
->send();
$this->session->alert(lang('recover_email_sent'), 'success');
return redirect($response, route('recover'));
}
/**
* @param Request $request
* @param Response $response
* @param string $resetToken
* @return Response
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
* @throws HttpNotFoundException
*/
public function recoverPasswordForm(Request $request, Response $response, string $resetToken): Response
{
$user = $this->database->query('SELECT `id` FROM `users` WHERE `reset_token` = ? LIMIT 1', $resetToken)->fetch();
if (!$user) {
throw new HttpNotFoundException($request);
}
return view()->render($response, 'auth/recover_password.twig', [
'reset_token' => $resetToken
]);
}
/**
* @param Request $request
* @param Response $response
* @param string $resetToken
* @return Response
* @throws HttpNotFoundException
*/
public function recoverPassword(Request $request, Response $response, string $resetToken): Response
{
$user = $this->database->query('SELECT `id` FROM `users` WHERE `reset_token` = ? LIMIT 1', $resetToken)->fetch();
if (!$user) {
throw new HttpNotFoundException($request);
}
/** @var ValidationHelper $validator */
$validator = make(ValidationHelper::class)
->alertIf(empty(param($request, 'password')), 'password_required')
->alertIf(param($request, 'password') !== param($request, 'password_repeat'), 'password_match');
if ($validator->fails()) {
return redirect($response, route('recover.password', ['resetToken' => $resetToken]));
}
$this->database->query('UPDATE `users` SET `password`=?, `reset_token`=? WHERE `id` = ?', [
password_hash(param($request, 'password'), PASSWORD_DEFAULT),
null,
$user->id,
]);
$this->session->alert(lang('password_restored'), 'success');
return redirect($response, route('login.show'));
}
}

View file

@ -0,0 +1,126 @@
<?php
namespace App\Controllers\Auth;
use App\Controllers\Controller;
use App\Database\Repositories\UserRepository;
use App\Web\Mail;
use App\Web\ValidationHelper;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Exception\HttpNotFoundException;
class RegisterController extends AuthController
{
/**
* @param Request $request
* @param Response $response
* @return Response
* @throws HttpNotFoundException
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public function registerForm(Request $request, Response $response): Response
{
if ($this->session->get('logged', false)) {
return redirect($response, route('home'));
}
if ($this->getSetting('register_enabled', 'off') === 'off') {
throw new HttpNotFoundException($request);
}
return view()->render($response, 'auth/register.twig', [
'recaptcha_site_key' => $this->getSetting('recaptcha_enabled') === 'on' ? $this->getSetting('recaptcha_site_key') : null,
]);
}
/**
* @param Request $request
* @param Response $response
* @return Response
* @throws HttpNotFoundException
* @throws \Exception
*/
public function register(Request $request, Response $response): Response
{
if ($this->session->get('logged', false)) {
return redirect($response, route('home'));
}
if ($this->getSetting('register_enabled', 'off') === 'off') {
throw new HttpNotFoundException($request);
}
if ($this->checkRecaptcha(make(ValidationHelper::class), $request)->fails()) {
return redirect($response, route('register.show'));
}
$validator = $this->getUserCreateValidator($request)->alertIf(empty(param($request, 'password')), 'password_required');
if ($validator->fails()) {
return redirect($response, route('register.show'));
}
$activateToken = bin2hex(random_bytes(16));
make(UserRepository::class)->create(
param($request, 'email'),
param($request, 'username'),
param($request, 'password'),
0,
0,
(int) $this->getSetting('default_user_quota', -1),
$activateToken
);
Mail::make()
->from(platform_mail(), $this->config['app_name'])
->to(param($request, 'email'))
->subject(lang('mail.activate_account', [$this->config['app_name']]))
->message(lang('mail.activate_text', [
param($request, 'username'),
$this->config['app_name'],
$this->config['base_url'],
$this->config['base_url'],
route('activate', ['activateToken' => $activateToken]),
route('activate', ['activateToken' => $activateToken]),
]))
->send();
$this->session->alert(lang('register_success', [param($request, 'username')]), 'success');
$this->logger->info('New user registered.', [array_diff_key($request->getParsedBody(), array_flip(['password']))]);
return redirect($response, route('login.show'));
}
/**
* @param Response $response
* @param string $activateToken
* @return Response
*/
public function activateUser(Response $response, string $activateToken): Response
{
if ($this->session->get('logged', false)) {
return redirect($response, route('home'));
}
$userId = $this->database->query('SELECT `id` FROM `users` WHERE `activate_token` = ? LIMIT 1', $activateToken)->fetch()->id ?? null;
if ($userId === null) {
$this->session->alert(lang('account_not_found'), 'warning');
return redirect($response, route('login.show'));
}
$this->database->query('UPDATE `users` SET `activate_token`=?, `active`=? WHERE `id` = ?', [
null,
1,
$userId,
]);
$this->session->alert(lang('account_activated'), 'success');
return redirect($response, route('login.show'));
}
}

View file

@ -0,0 +1,148 @@
<?php
namespace App\Controllers;
use App\Database\Repositories\UserRepository;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Exception\HttpNotFoundException;
use ZipStream\Option\Archive;
use ZipStream\ZipStream;
class ClientController extends Controller
{
/**
* @param Request $request
* @param Response $response
* @param int $id
*
* @return Response
*/
public function getShareXConfig(Request $request, Response $response, int $id): Response
{
$user = make(UserRepository::class)->get($request, $id, true);
if (!$user->token) {
$this->session->alert(lang('no_upload_token'), 'danger');
return redirect($response, $request->getHeaderLine('Referer'));
}
$json = [
'DestinationType' => 'ImageUploader, TextUploader, FileUploader',
'RequestURL' => route('upload'),
'FileFormName' => 'upload',
'Arguments' => [
'file' => '$filename$',
'text' => '$input$',
'token' => $user->token,
],
'URL' => '$json:url$',
'ThumbnailURL' => '$json:url$/raw',
'DeletionURL' => '$json:url$/delete/'.$user->token,
];
return json($response, $json, 200, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)
->withHeader('Content-Disposition', 'attachment;filename="'.$user->username.'-ShareX.sxcu"');
}
/**
* @param Request $request
* @param string|null $token
* @return Response
* @throws \ZipStream\Exception\FileNotFoundException
* @throws \ZipStream\Exception\FileNotReadableException
* @throws \ZipStream\Exception\OverflowException
* @throws HttpNotFoundException
*/
public function getScreenCloudConfig(Request $request, string $token): Response
{
$user = $this->database->query('SELECT * FROM `users` WHERE `token` = ? LIMIT 1', $token)->fetch();
if (!$user) {
throw new HttpNotFoundException($request);
}
$config = [
'token' => $token,
'host' => route('root'),
];
ob_end_clean();
$options = new Archive();
$options->setSendHttpHeaders(true);
$zip = new ZipStream($user->username.'-screencloud.zip', $options);
$zip->addFileFromPath('main.py', BASE_DIR.'resources/uploaders/screencloud/main.py');
$zip->addFileFromPath('icon.png', BASE_DIR.'static/images/favicon-32x32.png');
$zip->addFileFromPath('metadata.xml', BASE_DIR.'resources/uploaders/screencloud/metadata.xml');
$zip->addFileFromPath('settings.ui', BASE_DIR.'resources/uploaders/screencloud/settings.ui');
$zip->addFile('config.json', json_encode($config, JSON_UNESCAPED_SLASHES));
$zip->finish();
exit(0);
}
/**
* @param Request $request
* @param Response $response
* @param int $id
*
* @return Response
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public function getBashScript(Request $request, Response $response, int $id): Response
{
$user = make(UserRepository::class)->get($request, $id, true);
if (!$user->token) {
$this->session->alert(lang('no_upload_token'), 'danger');
return redirect($response, $request->getHeaderLine('Referer'));
}
return view()->render(
$response->withHeader('Content-Disposition', 'attachment;filename="xbackbone_uploader_'.$user->username.'.sh"'),
'scripts/xbackbone_uploader.sh.twig',
[
'username' => $user->username,
'upload_url' => route('upload'),
'token' => $user->token,
]
);
}
/**
* @param Request $request
* @param Response $response
* @param int $id
*
* @return Response
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public function getKDEScript(Request $request, Response $response, int $id): Response
{
$user = make(UserRepository::class)->get($request, $id, true);
if (!$user->token) {
$this->session->alert(lang('no_upload_token'), 'danger');
return redirect($response, $request->getHeaderLine('Referer'));
}
return view()->render(
$response->withHeader('Content-Disposition', 'attachment;filename="xbackbone_uploader_'.$user->username.'.sh"'),
'scripts/xbackbone_kde_uploader.sh.twig',
[
'username' => $user->username,
'upload_url' => route('upload'),
'token' => $user->token,
]
);
}
}

View file

@ -2,89 +2,153 @@
namespace App\Controllers;
use App\Exceptions\AuthenticationException;
use App\Exceptions\UnauthorizedException;
use App\Web\Session;
use Flight;
use App\Database\DB;
use League\Flysystem\Adapter\Local;
use App\Database\Repositories\UserRepository;
use App\Web\Lang;
use App\Web\Session;
use App\Web\ValidationHelper;
use App\Web\View;
use DI\Container;
use DI\DependencyException;
use DI\NotFoundException;
use Exception;
use League\Flysystem\Filesystem;
use Monolog\Logger;
use Psr\Http\Message\ServerRequestInterface as Request;
/**
* @property Session session
* @property View view
* @property DB database
* @property Logger|null logger
* @property Filesystem|null storage
* @property Lang lang
* @property array config
*/
abstract class Controller
{
/** @var Container */
protected $container;
/**
* Check if the current user is logged in
* @throws AuthenticationException
*/
protected function checkLogin(): void
{
if (!Session::get('logged', false)) {
Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
throw new AuthenticationException();
}
public function __construct(Container $container)
{
$this->container = $container;
}
if (!DB::query('SELECT `id`, `active` FROM `users` WHERE `id` = ? LIMIT 1', [Session::get('user_id')])->fetch()->active) {
Session::alert('Your account is not active anymore.', 'danger');
Session::set('logged', false);
Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
throw new AuthenticationException();
}
/**
* @param $name
*
* @return mixed|null
* @throws NotFoundException
*
* @throws DependencyException
*/
public function __get($name)
{
if ($this->container->has($name)) {
return $this->container->get($name);
}
}
return null;
}
/**
* Check if the current user is an admin
* @throws AuthenticationException
* @throws UnauthorizedException
*/
protected function checkAdmin(): void
{
$this->checkLogin();
/**
* @param $key
* @param null $default
* @return object
*/
protected function getSetting($key, $default = null)
{
return $this->database->query('SELECT `value` FROM `settings` WHERE `key` = '.$this->database->getPdo()->quote($key))->fetch()->value ?? $default;
}
if (!DB::query('SELECT `id`, `is_admin` FROM `users` WHERE `id` = ? LIMIT 1', [Session::get('user_id')])->fetch()->is_admin) {
Session::alert('Your account is not admin anymore.', 'danger');
Session::set('admin', false);
Session::set('redirectTo', (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
throw new UnauthorizedException();
}
}
/**
* @param $current
* @param $max
*/
protected function setSessionQuotaInfo($current, $max)
{
$this->session->set('current_disk_quota', humanFileSize($current));
if ($this->getSetting('quota_enabled', 'off') === 'on') {
if ($max < 0) {
$this->session->set('max_disk_quota', '∞')->set('percent_disk_quota', null);
} else {
$this->session->set('max_disk_quota', humanFileSize($max))->set('percent_disk_quota', round(($current * 100) / $max));
}
} else {
$this->session->set('max_disk_quota', null)->set('percent_disk_quota', null);
}
}
/**
* @param Request $request
* @param $userId
* @param $fileSize
* @param bool $dec
* @return bool
*/
protected function updateUserQuota(Request $request, $userId, $fileSize, $dec = false)
{
$user = make(UserRepository::class)->get($request, $userId);
/**
* Generate a human readable file size
* @param $size
* @param int $precision
* @return string
*/
protected function humanFilesize($size, $precision = 2): string
{
for ($i = 0; ($size / 1024) > 0.9; $i++, $size /= 1024) {
}
return round($size, $precision) . ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i];
}
if ($dec) {
$tot = max($user->current_disk_quota - $fileSize, 0);
} else {
$tot = $user->current_disk_quota + $fileSize;
/**
* Get a filesystem instance
* @return Filesystem
*/
protected function getStorage(): Filesystem
{
return new Filesystem(new Local(Flight::get('config')['storage_dir']));
}
if ($this->getSetting('quota_enabled') === 'on' && $user->max_disk_quota > 0 && $user->max_disk_quota < $tot) {
return false;
}
}
/**
* Set http2 header for a resource if is supported
* @param string $url
* @param string $as
*/
protected function http2push(string $url, string $as = 'image'): void
{
if (Flight::request()->scheme === 'HTTP/2.0') {
$headers = isset(Flight::response()->headers()['Link']) ? Flight::response()->headers()['Link'] : [];
$headers[] = "<${url}>; rel=preload; as=${as}";
Flight::response()->header('Link', $headers);
}
}
}
$this->database->query('UPDATE `users` SET `current_disk_quota`=? WHERE `id` = ?', [
$tot,
$user->id,
]);
return true;
}
/**
* @param $userId
* @throws Exception
*/
protected function refreshRememberCookie($userId)
{
$selector = bin2hex(random_bytes(8));
$token = bin2hex(random_bytes(32));
$expire = time() + 604800; // a week
$this->database->query('UPDATE `users` SET `remember_selector`=?, `remember_token`=?, `remember_expire`=? WHERE `id`=?', [
$selector,
password_hash($token, PASSWORD_DEFAULT),
date('Y-m-d\TH:i:s', $expire),
$userId,
]);
// Workaround for php <= 7.3
if (PHP_VERSION_ID < 70300) {
setcookie('remember', "{$selector}:{$token}", $expire, '; SameSite=Strict', '', isSecure(), true);
} else {
setcookie('remember', "{$selector}:{$token}", [
'expires' => $expire,
'httponly' => true,
'samesite' => 'Strict',
'secure' => isSecure(),
]);
}
}
/**
* @param Request $request
* @return ValidationHelper
*/
public function getUserCreateValidator(Request $request)
{
return make(ValidationHelper::class)
->alertIf(empty(param($request, 'username')), 'username_required')
->alertIf(!filter_var(param($request, 'email'), FILTER_VALIDATE_EMAIL), 'email_required')
->alertIf($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ?', param($request, 'email'))->fetch()->count != 0, 'email_taken')
->alertIf($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ?', param($request, 'username'))->fetch()->count != 0, 'username_taken');
}
}

View file

@ -2,89 +2,96 @@
namespace App\Controllers;
use App\Database\DB;
use App\Traits\SingletonController;
use App\Web\Session;
use Flight;
use League\Flysystem\FileNotFoundException;
use App\Database\Repositories\MediaRepository;
use App\Database\Repositories\TagRepository;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class DashboardController extends Controller
{
use SingletonController;
/**
* @param Request $request
* @param Response $response
*
* @return Response
*/
public function redirects(Request $request, Response $response): Response
{
if (param($request, 'afterInstall') !== null && !is_dir(BASE_DIR.'install')) {
$this->session->alert(lang('installed'), 'success');
}
const PER_PAGE = 21;
const PER_PAGE_ADMIN = 50;
return redirect($response, route('home'));
}
public function redirects(): void
{
$this->checkLogin();
Flight::redirect('/home');
}
/**
* @param Request $request
* @param Response $response
* @param int|null $page
*
* @return Response
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*
* @throws \Twig\Error\LoaderError
*/
public function home(Request $request, Response $response, int $page = 0): Response
{
$page = max(0, --$page);
public function home($page = 1): void
{
$this->checkLogin();
switch (param($request, 'sort', 'time')) {
case 'size':
$order = MediaRepository::ORDER_SIZE;
break;
case 'name':
$order = MediaRepository::ORDER_NAME;
break;
default:
case 'time':
$order = MediaRepository::ORDER_TIME;
break;
}
$page = max(0, --$page);
$isAdmin = (bool) $this->session->get('admin', false);
if (Session::get('admin', false)) {
$medias = DB::query('SELECT `uploads`.*, `users`.`user_code`, `users`.`username` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id` ORDER BY `timestamp` DESC LIMIT ? OFFSET ?', [self::PER_PAGE_ADMIN, $page * self::PER_PAGE_ADMIN])->fetchAll();
$pages = DB::query('SELECT COUNT(*) AS `count` FROM `uploads`')->fetch()->count / self::PER_PAGE_ADMIN;
} else {
$medias = DB::query('SELECT `uploads`.*,`users`.`user_code`, `users`.`username` FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_id` = ? ORDER BY `timestamp` DESC LIMIT ? OFFSET ?', [Session::get('user_id'), self::PER_PAGE, $page * self::PER_PAGE])->fetchAll();
$pages = DB::query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `user_id` = ?', Session::get('user_id'))->fetch()->count / self::PER_PAGE;
}
/** @var MediaRepository $query */
$query = make(MediaRepository::class, ['isAdmin' => $isAdmin])
->orderBy($order, param($request, 'order', 'DESC'))
->withUserId($this->session->get('user_id'))
->search(param($request, 'search', null))
->filterByTag(param($request, 'tag'))
->run($page);
$filesystem = $this->getStorage();
$tags = make(TagRepository::class, [
'isAdmin' => $isAdmin,
'userId' => $this->session->get('user_id')
])->all();
foreach ($medias as $media) {
$extension = pathinfo($media->filename, PATHINFO_EXTENSION);
try {
$mime = $filesystem->getMimetype($media->storage_path);
$size = $filesystem->getSize($media->storage_path);
} catch (FileNotFoundException $e) {
$mime = null;
}
$media->mimetype = $mime;
$media->extension = $extension;
$media->size = $this->humanFilesize($size);
}
return view()->render(
$response,
($this->session->get('gallery_view', $isAdmin)) ? 'dashboard/list.twig' : 'dashboard/grid.twig',
[
'medias' => $query->getMedia(),
'next' => $page < floor($query->getPages()),
'previous' => $page >= 1,
'current_page' => ++$page,
'copy_raw' => $this->session->get('copy_raw', false),
'tags' => $tags,
]
);
}
Flight::render(
Session::get('admin', false) ? 'dashboard/admin.twig' : 'dashboard/home.twig',
[
'medias' => $medias,
'next' => $page < floor($pages),
'previous' => $page >= 1,
'current_page' => ++$page,
]
);
}
/**
* @param Response $response
*
* @return Response
*/
public function switchView(Response $response): Response
{
$isAdmin = (bool) $this->session->get('admin', false);
public function system()
{
$this->checkAdmin();
$this->session->set('gallery_view', !$this->session->get('gallery_view', $isAdmin));
$usersCount = DB::query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count;
$mediasCount = DB::query('SELECT COUNT(*) AS `count` FROM `uploads`')->fetch()->count;
$orphanFilesCount = DB::query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `user_id` IS NULL')->fetch()->count;
$medias = DB::query('SELECT `users`.`user_code`, `uploads`.`code`, `uploads`.`storage_path` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id`')->fetchAll();
$totalSize = 0;
$filesystem = $this->getStorage();
foreach ($medias as $media) {
$totalSize += $filesystem->getSize($media->storage_path);
}
Flight::render('dashboard/system.twig', [
'usersCount' => $usersCount,
'mediasCount' => $mediasCount,
'orphanFilesCount' => $orphanFilesCount,
'totalSize' => $this->humanFilesize($totalSize),
'max_filesize' => ini_get('post_max_size') . '/' . ini_get('upload_max_filesize'),
]);
}
}
return redirect($response, route('home'));
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace App\Controllers;
use App\Database\Repositories\UserRepository;
use League\Flysystem\FileNotFoundException;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use ZipStream\Option\Archive;
use ZipStream\ZipStream;
class ExportController extends Controller
{
/**
* @param Request $request
* @param Response $response
* @param int|null $id
* @return Response
* @throws \ZipStream\Exception\OverflowException
*/
public function downloadData(Request $request, Response $response, int $id): Response
{
$user = make(UserRepository::class)->get($request, $id, true);
$medias = $this->database->query('SELECT `uploads`.`filename`, `uploads`.`storage_path` FROM `uploads` WHERE `user_id` = ?', $user->id);
$this->logger->info("User $user->id, $user->username, exporting data...");
set_time_limit(0);
ob_end_clean();
$options = new Archive();
$options->setSendHttpHeaders(true);
$zip = new ZipStream($user->username.'-'.time().'-export.zip', $options);
$filesystem = $this->storage;
foreach ($medias as $media) {
try {
$zip->addFileFromStream($media->filename, $filesystem->readStream($media->storage_path));
} catch (FileNotFoundException $e) {
$this->logger->error('Cannot export file', ['exception' => $e]);
}
}
$zip->finish();
exit(0);
}
}

View file

@ -1,68 +0,0 @@
<?php
namespace App\Controllers;
use App\Database\DB;
use App\Traits\SingletonController;
use App\Web\Log;
use App\Web\Session;
use Flight;
class LoginController extends Controller
{
use SingletonController;
public function show(): void
{
if (Session::get('logged', false)) {
Flight::redirect('/home');
return;
}
Flight::render('auth/login.twig');
}
public function login(): void
{
$form = Flight::request()->data;
$result = DB::query('SELECT `id`,`username`, `password`,`is_admin`, `active` FROM `users` WHERE `username` = ? LIMIT 1', $form->username)->fetch();
if (!$result || !password_verify($form->password, $result->password)) {
Flight::redirect('login');
Session::alert('Wrong credentials', 'danger');
return;
}
if (!$result->active) {
Flight::redirect('login');
Session::alert('Your account is disabled.', 'danger');
return;
}
Session::set('logged', true);
Session::set('user_id', $result->id);
Session::set('username', $result->username);
Session::set('admin', $result->is_admin);
Session::alert("Welcome, $result->username!", 'info');
Log::info("User $result->username logged in.");
if (Session::has('redirectTo')) {
Flight::redirect(Session::get('redirectTo'));
return;
}
Flight::redirect('/home');
}
public function logout(): void
{
$this->checkLogin();
Session::clear();
Session::set('logged', false);
Session::alert('Goodbye!', 'warning');
Flight::redirect('/login');
}
}

View file

@ -0,0 +1,549 @@
<?php
namespace App\Controllers;
use App\Database\Repositories\UserRepository;
use App\Web\UA;
use Intervention\Image\Constraint;
use Intervention\Image\ImageManagerStatic as Image;
use League\Flysystem\FileNotFoundException;
use League\Flysystem\Filesystem;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Exception\HttpBadRequestException;
use Slim\Exception\HttpNotFoundException;
use Slim\Exception\HttpUnauthorizedException;
use Slim\Psr7\Stream;
class MediaController extends Controller
{
/**
* @param Request $request
* @param Response $response
* @param string $userCode
* @param string $mediaCode
* @param string|null $token
*
* @return Response
* @throws HttpNotFoundException
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
* @throws FileNotFoundException
*
*/
public function show(
Request $request,
Response $response,
string $userCode,
string $mediaCode,
string $token = null
): Response {
$media = $this->getMedia($userCode, $mediaCode, true);
if (!$media || (!$media->published && $this->session->get('user_id') !== $media->user_id && !$this->session->get(
'admin',
false
))) {
throw new HttpNotFoundException($request);
}
$filesystem = $this->storage;
$userAgent = $request->getHeaderLine('User-Agent');
$mime = $filesystem->getMimetype($media->storage_path);
try {
$media->mimetype = $mime;
$media->extension = pathinfo($media->filename, PATHINFO_EXTENSION);
$size = $filesystem->getSize($media->storage_path);
$type = explode('/', $media->mimetype)[0];
if ($type === 'image' && !isDisplayableImage($media->mimetype)) {
$type = 'application';
$media->mimetype = 'application/octet-stream';
}
if ($type === 'text') {
if ($size <= (500 * 1024)) { // less than 500 KB
$media->text = $filesystem->read($media->storage_path);
} else {
$type = 'application';
$media->mimetype = 'application/octet-stream';
}
}
$media->size = humanFileSize($size);
} catch (FileNotFoundException $e) {
throw new HttpNotFoundException($request);
}
if (
UA::isBot($userAgent) &&
!(
// embed if enabled
(UA::embedsLinks($userAgent) &&
isEmbeddable($mime) &&
$this->getSetting('image_embeds') === 'on') ||
// if the file is too large to be displayed as non embedded
(UA::embedsLinks($userAgent) &&
isEmbeddable($mime) &&
$size >= (8 * 1024 * 1024))
)
) {
return $this->streamMedia($request, $response, $filesystem, $media);
}
return view()->render($response, 'upload/public.twig', [
'delete_token' => $token,
'media' => $media,
'type' => $type,
'url' => urlFor(glue($userCode, $mediaCode)),
'copy_raw' => $this->session->get('copy_raw', false),
]);
}
/**
* @param Request $request
* @param Response $response
* @param int $id
*
* @return Response
* @throws HttpNotFoundException
*
* @throws FileNotFoundException
*/
public function getRawById(Request $request, Response $response, int $id): Response
{
$media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch();
if (!$media) {
throw new HttpNotFoundException($request);
}
return $this->streamMedia($request, $response, $this->storage, $media);
}
/**
* @param Request $request
* @param Response $response
* @param string $userCode
* @param string $mediaCode
* @param string|null $ext
*
* @return Response
* @throws HttpBadRequestException
* @throws HttpNotFoundException
*
* @throws FileNotFoundException
*/
public function getRaw(
Request $request,
Response $response,
string $userCode,
string $mediaCode,
?string $ext = null
): Response {
$media = $this->getMedia($userCode, $mediaCode, false);
if (!$media || (!$media->published && $this->session->get('user_id') !== $media->user_id && !$this->session->get(
'admin',
false
))) {
throw new HttpNotFoundException($request);
}
if ($ext !== null && pathinfo($media->filename, PATHINFO_EXTENSION) !== $ext) {
throw new HttpBadRequestException($request);
}
if (must_be_escaped($this->storage->getMimetype($media->storage_path))) {
$response = $this->streamMedia($request, $response, $this->storage, $media);
return $response->withHeader('Content-Type', 'text/plain');
}
return $this->streamMedia($request, $response, $this->storage, $media);
}
/**
* @param Request $request
* @param Response $response
* @param string $userCode
* @param string $mediaCode
*
* @return Response
* @throws HttpNotFoundException
*
* @throws FileNotFoundException
*/
public function download(Request $request, Response $response, string $userCode, string $mediaCode): Response
{
$media = $this->getMedia($userCode, $mediaCode, false);
if (!$media || (!$media->published && $this->session->get('user_id') !== $media->user_id && !$this->session->get(
'admin',
false
))) {
throw new HttpNotFoundException($request);
}
return $this->streamMedia($request, $response, $this->storage, $media, 'attachment');
}
/**
* @param Request $request
* @param Response $response
* @param string $vanity
* @param string $id
*
* @return Response
* @throws HttpNotFoundException
* @throws HttpBadRequestException
*/
public function createVanity(Request $request, Response $response, int $id): Response
{
$media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch();
$vanity = param($request, 'vanity');
$vanity = preg_replace('/[^a-z0-9]+/', '-', strtolower($vanity));
//handle collisions
$collision = $this->database->query('SELECT * FROM `uploads` WHERE `code` = ? AND `id` != ? LIMIT 1', [$vanity, $id])->fetch();
if (!$media) {
throw new HttpNotFoundException($request);
}
if ($vanity === '' || $collision) {
throw new HttpBadRequestException($request);
}
$this->database->query('UPDATE `uploads` SET `code` = ? WHERE `id` = ?', [$vanity, $media->id]);
$media->code = $vanity;
$response->getBody()->write(json_encode($media));
$this->logger->info('User '.$this->session->get('username').' created a vanity link for media '.$media->id);
return $response;
}
/**
* @param Request $request
* @param Response $response
* @param int $id
*
* @return Response
* @throws HttpNotFoundException
*
*/
public function togglePublish(Request $request, Response $response, int $id): Response
{
if ($this->session->get('admin')) {
$media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch();
} else {
$media = $this->database->query(
'SELECT * FROM `uploads` WHERE `id` = ? AND `user_id` = ? LIMIT 1',
[$id, $this->session->get('user_id')]
)->fetch();
}
if (!$media) {
throw new HttpNotFoundException($request);
}
$this->database->query(
'UPDATE `uploads` SET `published`=? WHERE `id`=?',
[$media->published ? 0 : 1, $media->id]
);
return $response;
}
/**
* @param Request $request
* @param Response $response
* @param int $id
*
* @return Response
* @throws HttpNotFoundException
* @throws HttpUnauthorizedException
*/
public function delete(Request $request, Response $response, int $id): Response
{
$media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch();
if (!$media) {
throw new HttpNotFoundException($request);
}
if (!$this->session->get('admin', false) && $media->user_id !== $this->session->get('user_id')) {
throw new HttpUnauthorizedException($request);
}
$this->deleteMedia($request, $media->storage_path, $id, $media->user_id);
$this->logger->info('User '.$this->session->get('username').' deleted a media.', [$id]);
if ($media->user_id === $this->session->get('user_id')) {
$user = make(UserRepository::class)->get($request, $media->user_id, true);
$this->setSessionQuotaInfo($user->current_disk_quota, $user->max_disk_quota);
}
if ($request->getMethod() === 'GET') {
return redirect($response, route('home'));
}
return $response;
}
/**
* @param Request $request
* @param Response $response
* @param string $userCode
* @param string $mediaCode
* @param string $token
*
* @return Response
* @throws HttpUnauthorizedException
*
* @throws HttpNotFoundException
*/
public function deleteByToken(
Request $request,
Response $response,
string $userCode,
string $mediaCode,
string $token
): Response {
$media = $this->getMedia($userCode, $mediaCode, false);
if (!$media) {
throw new HttpNotFoundException($request);
}
$user = $this->database->query('SELECT `id`, `active` FROM `users` WHERE `token` = ? LIMIT 1', $token)->fetch();
if (!$user) {
$this->session->alert(lang('token_not_found'), 'danger');
return redirect($response, $request->getHeaderLine('Referer'));
}
if (!$user->active) {
$this->session->alert(lang('account_disabled'), 'danger');
return redirect($response, $request->getHeaderLine('Referer'));
}
if ($this->session->get('admin', false) || $user->id === $media->user_id) {
$this->deleteMedia($request, $media->storage_path, $media->mediaId, $user->id);
$this->logger->info('User '.$user->username.' deleted a media via token.', [$media->mediaId]);
} else {
throw new HttpUnauthorizedException($request);
}
return redirect($response, route('home'));
}
/**
* @param Request $request
* @param string $storagePath
* @param int $id
*
* @param int $userId
* @return void
* @throws HttpNotFoundException
*/
protected function deleteMedia(Request $request, string $storagePath, int $id, int $userId)
{
try {
$size = $this->storage->getSize($storagePath);
$this->storage->delete($storagePath);
$this->updateUserQuota($request, $userId, $size, true);
} catch (FileNotFoundException $e) {
throw new HttpNotFoundException($request);
} finally {
$this->database->query('DELETE FROM `uploads` WHERE `id` = ?', $id);
$this->database->query('DELETE FROM `tags` WHERE `tags`.`id` NOT IN (SELECT `uploads_tags`.`tag_id` FROM `uploads_tags`)');
}
}
/**
* @param $userCode
* @param $mediaCode
*
* @param bool $withTags
* @return mixed
*/
protected function getMedia($userCode, $mediaCode, $withTags = false)
{
$mediaCode = pathinfo($mediaCode)['filename'];
$media = $this->database->query(
'SELECT `uploads`.*, `users`.*, `users`.`id` AS `userId`, `uploads`.`id` AS `mediaId` FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_code` = ? AND `uploads`.`code` = ? LIMIT 1',
[
$userCode,
$mediaCode,
]
)->fetch();
if (!$withTags || !$media) {
return $media;
}
$media->tags = [];
foreach ($this->database->query(
'SELECT `tags`.`id`, `tags`.`name` FROM `uploads_tags` INNER JOIN `tags` ON `uploads_tags`.`tag_id` = `tags`.`id` WHERE `uploads_tags`.`upload_id` = ?',
$media->mediaId
) as $tag) {
$media->tags[$tag->id] = $tag->name;
}
return $media;
}
/**
* @param Request $request
* @param Response $response
* @param Filesystem $storage
* @param $media
* @param string $disposition
*
* @return Response
* @throws FileNotFoundException
*
*/
protected function streamMedia(
Request $request,
Response $response,
Filesystem $storage,
$media,
string $disposition = 'inline'
): Response {
set_time_limit(0);
$this->session->close();
$mime = $storage->getMimetype($media->storage_path);
if ((param($request, 'width') !== null || param($request, 'height') !== null) && explode(
'/',
$mime
)[0] === 'image') {
return $this->makeThumbnail(
$storage,
$media,
param($request, 'width'),
param($request, 'height'),
$disposition
);
}
$stream = new Stream($storage->readStream($media->storage_path));
if (!in_array(explode('/', $mime)[0], ['image', 'video', 'audio']) || $disposition === 'attachment') {
return $response->withHeader('Content-Type', $mime)
->withHeader('Content-Disposition', $disposition.'; filename="'.$media->filename.'"')
->withHeader('Content-Length', $stream->getSize())
->withBody($stream);
}
if (isset($request->getServerParams()['HTTP_RANGE'])) {
return $this->handlePartialRequest(
$response,
$stream,
$request->getServerParams()['HTTP_RANGE'],
$disposition,
$media,
$mime
);
}
return $response->withHeader('Content-Type', $mime)
->withHeader('Content-Length', $stream->getSize())
->withHeader('Accept-Ranges', 'bytes')
->withBody($stream);
}
/**
* @param Filesystem $storage
* @param $media
* @param null $width
* @param null $height
* @param string $disposition
*
* @return Response
* @throws FileNotFoundException
*
*/
protected function makeThumbnail(
Filesystem $storage,
$media,
$width = null,
$height = null,
string $disposition = 'inline'
) {
return Image::make($storage->readStream($media->storage_path))
->resize($width, $height, function (Constraint $constraint) {
$constraint->aspectRatio();
})
->resizeCanvas($width, $height, 'center')
->psrResponse('png')
->withHeader(
'Content-Disposition',
$disposition.';filename="scaled-'.pathinfo($media->filename, PATHINFO_FILENAME).'.png"'
);
}
/**
* @param Response $response
* @param Stream $stream
* @param string $range
* @param string $disposition
* @param $media
* @param $mime
*
* @return Response
*/
protected function handlePartialRequest(
Response $response,
Stream $stream,
string $range,
string $disposition,
$media,
$mime
) {
$end = $stream->getSize() - 1;
[, $range] = explode('=', $range, 2);
if (strpos($range, ',') !== false) {
return $response->withHeader('Content-Type', $mime)
->withHeader('Content-Disposition', $disposition.'; filename="'.$media->filename.'"')
->withHeader('Content-Length', $stream->getSize())
->withHeader('Accept-Ranges', 'bytes')
->withHeader('Content-Range', "0,{$stream->getSize()}")
->withStatus(416)
->withBody($stream);
}
if ($range === '-') {
$start = $stream->getSize() - (int) substr($range, 1);
} else {
$range = explode('-', $range);
$start = (int) $range[0];
$end = (isset($range[1]) && is_numeric($range[1])) ? (int) $range[1] : $stream->getSize();
}
if ($end > $stream->getSize() - 1) {
$end = $stream->getSize() - 1;
}
$stream->seek($start);
header("Content-Type: $mime");
header('Content-Length: '.($end - $start + 1));
header('Accept-Ranges: bytes');
header("Content-Range: bytes $start-$end/{$stream->getSize()}");
http_response_code(206);
ob_end_clean();
fpassthru($stream->detach());
exit(0);
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace App\Controllers;
use App\Database\Repositories\UserRepository;
use App\Web\ValidationHelper;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class ProfileController extends Controller
{
/**
* @param Request $request
* @param Response $response
*
* @return Response
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public function profile(Request $request, Response $response): Response
{
$user = make(UserRepository::class)->get($request, $this->session->get('user_id'), true);
return view()->render($response, 'user/edit.twig', [
'profile' => true,
'user' => $user,
]);
}
/**
* @param Request $request
* @param Response $response
* @param int $id
*
* @return Response
*/
public function profileEdit(Request $request, Response $response, int $id): Response
{
$user = make(UserRepository::class)->get($request, $id, true);
/** @var ValidationHelper $validator */
$validator = make(ValidationHelper::class)
->alertIf(!filter_var(param($request, 'email'), FILTER_VALIDATE_EMAIL), 'email_required')
->alertIf($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ? AND `email` <> ?', [param($request, 'email'), $user->email])->fetch()->count != 0, 'email_taken');
if ($validator->fails()) {
return redirect($response, route('profile'));
}
if (param($request, 'password') !== null && !empty(param($request, 'password'))) {
$this->database->query('UPDATE `users` SET `email`=?, `password`=?, `hide_uploads`=?, `copy_raw`=? WHERE `id` = ?', [
param($request, 'email'),
password_hash(param($request, 'password'), PASSWORD_DEFAULT),
param($request, 'hide_uploads') !== null ? 1 : 0,
param($request, 'copy_raw') !== null ? 1 : 0,
$user->id,
]);
} else {
$this->database->query('UPDATE `users` SET `email`=?, `hide_uploads`=?, `copy_raw`=? WHERE `id` = ?', [
param($request, 'email'),
param($request, 'hide_uploads') !== null ? 1 : 0,
param($request, 'copy_raw') !== null ? 1 : 0,
$user->id,
]);
}
$this->session->set('copy_raw', param($request, 'copy_raw') !== null ? 1 : 0)->alert(lang('profile_updated'), 'success');
$this->logger->info('User '.$this->session->get('username')." updated profile of $user->id.");
return redirect($response, route('profile'));
}
}

View file

@ -0,0 +1,115 @@
<?php
namespace App\Controllers;
use App\Database\Repositories\UserRepository;
use App\Web\Theme;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Exception\HttpBadRequestException;
use Slim\Exception\HttpInternalServerErrorException;
class SettingController extends Controller
{
/**
* @param Request $request
* @param Response $response
*
* @return Response
* @throws HttpInternalServerErrorException
*/
public function saveSettings(Request $request, Response $response): Response
{
if (!preg_match('/[0-9]+[K|M|G|T]/i', param($request, 'default_user_quota', '1G'))) {
$this->session->alert(lang('invalid_quota', 'danger'));
return redirect($response, route('system'));
}
if (param($request, 'recaptcha_enabled', 'off') === 'on' && (empty(param($request, 'recaptcha_site_key')) || empty(param($request, 'recaptcha_secret_key')))) {
$this->session->alert(lang('recaptcha_keys_required', 'danger'));
return redirect($response, route('system'));
}
// registrations
$this->updateSetting('register_enabled', param($request, 'register_enabled', 'off'));
$this->updateSetting('auto_tagging', param($request, 'auto_tagging', 'off'));
// quota
$this->updateSetting('quota_enabled', param($request, 'quota_enabled', 'off'));
$this->updateSetting('default_user_quota', stringToBytes(param($request, 'default_user_quota', '1G')));
$user = make(UserRepository::class)->get($request, $this->session->get('user_id'));
$this->setSessionQuotaInfo($user->current_disk_quota, $user->max_disk_quota);
$this->updateSetting('custom_head', param($request, 'custom_head'));
$this->updateSetting('recaptcha_enabled', param($request, 'recaptcha_enabled', 'off'));
$this->updateSetting('recaptcha_site_key', param($request, 'recaptcha_site_key'));
$this->updateSetting('recaptcha_secret_key', param($request, 'recaptcha_secret_key'));
$this->updateSetting('image_embeds', param($request, 'image_embeds'));
$this->applyTheme($request);
$this->applyLang($request);
$this->logger->info("User $user->username updated the system settings.");
$this->session->alert(lang('settings_saved'));
return redirect($response, route('system'));
}
/**
* @param Request $request
*/
public function applyLang(Request $request)
{
if (param($request, 'lang') !== 'auto') {
$this->updateSetting('lang', param($request, 'lang'));
} else {
$this->database->query('DELETE FROM `settings` WHERE `key` = \'lang\'');
}
}
/**
* @param Request $request
* @throws HttpInternalServerErrorException
*/
public function applyTheme(Request $request)
{
$css = param($request, 'css');
if ($css === null) {
return;
}
if (!is_writable(BASE_DIR.'static/bootstrap/css/bootstrap.min.css')) {
$this->session->alert(lang('cannot_write_file'), 'danger');
throw new HttpInternalServerErrorException($request);
}
make(Theme::class)->applyTheme($css);
// if is default, remove setting
if ($css !== Theme::default()) {
$this->updateSetting('css', $css);
} else {
$this->database->query('DELETE FROM `settings` WHERE `key` = \'css\'');
}
}
/**
* @param $key
* @param null $value
*/
private function updateSetting($key, $value = null)
{
if (!$this->database->query('SELECT `value` FROM `settings` WHERE `key` = '.$this->database->getPdo()->quote($key))->fetch()) {
$this->database->query(
'INSERT INTO `settings`(`key`, `value`) VALUES ('.$this->database->getPdo()->quote($key).', ?)',
$value
);
} else {
$this->database->query(
'UPDATE `settings` SET `value`=? WHERE `key` = '.$this->database->getPdo()->quote($key),
$value
);
}
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace App\Controllers;
use App\Database\Repositories\TagRepository;
use App\Web\ValidationHelper;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Exception\HttpBadRequestException;
use Slim\Exception\HttpNotFoundException;
class TagController extends Controller
{
/**
* @param Request $request
* @param Response $response
* @return Response
* @throws HttpBadRequestException
*/
public function addTag(Request $request, Response $response): Response
{
$validator = $this->validateTag($request)->failIf(empty(param($request, 'tag')));
if ($validator->fails()) {
throw new HttpBadRequestException($request);
}
[$id, $limit] = make(TagRepository::class)->addTag(param($request, 'tag'), param($request, 'mediaId'));
$this->logger->info("Tag added $id.");
return json($response, [
'limitReached' => $limit,
'tagId' => $id,
'href' => queryParams(['tag' => $id]),
]);
}
/**
* @param Request $request
* @param Response $response
* @return Response
* @throws HttpBadRequestException
* @throws HttpNotFoundException
*/
public function removeTag(Request $request, Response $response): Response
{
$validator = $this->validateTag($request);
if ($validator->fails()) {
throw new HttpBadRequestException($request);
}
$result = make(TagRepository::class)->removeTag(param($request, 'tagId'), param($request, 'mediaId'));
if ($result === null) {
throw new HttpNotFoundException($request);
}
$this->logger->info("Tag removed ".param($request, 'tagId').', from media '.param($request, 'mediaId'));
return json($response, [
'deleted' => $result,
]);
}
/**
* @param Request $request
* @return ValidationHelper
*/
protected function validateTag(Request $request)
{
return make(ValidationHelper::class)
->failIf(empty(param($request, 'mediaId')))
->failIf($this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `id` = ?', param($request, 'mediaId'))->fetch()->count == 0)
->failIf(!$this->session->get('admin', false) && $this->database->query('SELECT `user_id` FROM `uploads` WHERE `id` = ? LIMIT 1', param($request, 'mediaId'))->fetch()->user_id != $this->session->get('user_id'));
}
}

View file

@ -0,0 +1,194 @@
<?php
namespace App\Controllers;
use App\Web\Session;
use Monolog\Logger;
use Parsedown;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use RuntimeException;
use ZipArchive;
use function glob_recursive;
use function redirect;
use function removeDirectory;
use function route;
use function urlFor;
class UpgradeController extends Controller
{
const GITHUB_SOURCE_API = 'https://api.github.com/repos/SergiX44/XBackBone/releases';
/**
* @param Response $response
*
* @param Logger $logger
* @param Session $session
* @return Response
*/
public function upgrade(Response $response, Logger $logger, Session $session): Response
{
if (!extension_loaded('zip')) {
$session->alert(lang('zip_ext_not_loaded'), 'danger');
return redirect($response, route('system'));
}
if (!is_writable(BASE_DIR)) {
$session->alert(lang('path_not_writable', BASE_DIR), 'warning');
return redirect($response, route('system'));
}
try {
$json = $this->getApiJson();
} catch (RuntimeException $e) {
$session->alert($e->getMessage(), 'danger');
return redirect($response, route('system'));
}
if (version_compare($json[0]->tag_name, PLATFORM_VERSION, '<=')) {
$session->alert(lang('already_latest_version'), 'warning');
return redirect($response, route('system'));
}
$tmpFile = sys_get_temp_dir().DIRECTORY_SEPARATOR.'xbackbone_update.zip';
if (file_put_contents($tmpFile, file_get_contents($json[0]->assets[0]->browser_download_url)) === false) {
$session->alert(lang('cannot_retrieve_file'), 'danger');
return redirect($response, route('system'));
}
if (filesize($tmpFile) !== $json[0]->assets[0]->size) {
$session->alert(lang('file_size_no_match'), 'danger');
return redirect($response, route('system'));
}
$logger->info('System update started.');
$config = require BASE_DIR.'config.php';
$config['maintenance'] = true;
file_put_contents(BASE_DIR.'config.php', '<?php'.PHP_EOL.'return '.var_export($config, true).';');
$currentFiles = array_merge(
glob_recursive(BASE_DIR.'app/*'),
glob_recursive(BASE_DIR.'bin/*'),
glob_recursive(BASE_DIR.'bootstrap/*'),
glob_recursive(BASE_DIR.'resources/templates/*'),
glob_recursive(BASE_DIR.'resources/lang/*'),
glob_recursive(BASE_DIR.'resources/schemas/*'),
glob_recursive(BASE_DIR.'static/*')
);
removeDirectory(BASE_DIR.'vendor/');
$updateZip = new ZipArchive();
$updateZip->open($tmpFile);
for ($i = 0; $i < $updateZip->numFiles; $i++) {
$nameIndex = $updateZip->getNameIndex($i);
$updateZip->extractTo(BASE_DIR, $nameIndex);
if (($key = array_search(rtrim(BASE_DIR.$nameIndex, '/'), $currentFiles)) !== false) {
unset($currentFiles[$key]);
}
}
$updateZip->close();
unlink($tmpFile);
foreach ($currentFiles as $extraneous) {
if (is_dir($extraneous)) {
removeDirectory($extraneous);
} else {
unlink($extraneous);
}
}
$logger->info('System update completed.');
return redirect($response, urlFor('/install'));
}
/**
* @param Request $request
* @param Response $response
*
* @return Response
*/
public function checkForUpdates(Request $request, Response $response): Response
{
$jsonResponse = [
'status' => 'OK',
'message' => lang('already_latest_version'),
'upgrade' => false,
];
$acceptPrerelease = param($request, 'prerelease', 'false') === 'true';
try {
$json = $this->getApiJson();
foreach ($json as $release) {
if (
$release->prerelease === $acceptPrerelease &&
version_compare($release->tag_name, PLATFORM_VERSION, '>') &&
version_compare($release->tag_name, '4.0.0', '<')
) {
$jsonResponse['message'] = lang('new_version_available', [$release->tag_name]);
$jsonResponse['upgrade'] = true;
break;
}
if (version_compare($release->tag_name, PLATFORM_VERSION, '<=')) {
break;
}
}
} catch (RuntimeException $e) {
$jsonResponse['status'] = 'ERROR';
$jsonResponse['message'] = $e->getMessage();
}
return json($response, $jsonResponse);
}
/**
* @param Request $request
* @param Response $response
* @return Response
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public function changelog(Request $request, Response $response): Response
{
return view()->render($response, 'dashboard/changelog.twig', [
'content' => Parsedown::instance()->text(file_get_contents('CHANGELOG.md')),
]);
}
protected function getApiJson()
{
$opts = [
'http' => [
'method' => 'GET',
'header' => [
'User-Agent: XBackBone-App',
'Accept: application/vnd.github.v3+json',
],
],
];
$data = @file_get_contents(self::GITHUB_SOURCE_API, false, stream_context_create($opts));
if ($data === false) {
throw new RuntimeException('Cannot contact the Github API. Try again.');
}
return json_decode($data);
}
}

View file

@ -2,247 +2,236 @@
namespace App\Controllers;
use App\Database\DB;
use App\Exceptions\NotFoundException;
use App\Traits\SingletonController;
use App\Web\Log;
use App\Web\Session;
use Flight;
use League\Flysystem\FileExistsException;
use League\Flysystem\FileNotFoundException;
use League\Flysystem\Filesystem;
use App\Database\Repositories\TagRepository;
use App\Database\Repositories\UserRepository;
use App\Exceptions\ValidationException;
use Exception;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\UploadedFileInterface;
class UploadController extends Controller
{
use SingletonController;
private $json = [
'message' => null,
'version' => PLATFORM_VERSION,
];
public function upload(): void
{
$requestData = Flight::request()->data;
/**
* @param Response $response
*
* @return Response
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public function uploadWebPage(Response $response): Response
{
$maxFileSize = min(stringToBytes(ini_get('post_max_size')), stringToBytes(ini_get('upload_max_filesize')));
$response = [
'message' => null,
];
return view()->render($response, 'upload/web.twig', [
'max_file_size' => humanFileSize($maxFileSize),
]);
}
if (!isset($requestData->token)) {
$response['message'] = 'Token not specified.';
Flight::json($response, 400);
return;
}
/**
* @param Request $request
* @param Response $response
* @return Response
* @throws Exception
*/
public function uploadWeb(Request $request, Response $response): Response
{
try {
$file = $this->validateFile($request, $response);
$user = DB::query('SELECT * FROM `users` WHERE `token` = ? LIMIT 1', $requestData->token)->fetch();
$user = make(UserRepository::class)->get($request, $this->session->get('user_id'));
if (!$user) {
$response['message'] = 'Token specified not found.';
Flight::json($response, 404);
return;
}
$this->validateUser($request, $response, $file, $user);
} catch (ValidationException $e) {
return $e->response();
}
if (!$user->active) {
$response['message'] = 'Account disabled.';
Flight::json($response, 401);
return;
}
if (!$this->updateUserQuota($request, $user->id, $file->getSize())) {
$this->json['message'] = 'User disk quota exceeded.';
do {
$code = uniqid();
} while (DB::query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0);
return json($response, $this->json, 507);
}
$file = Flight::request()->files->current();
$fileInfo = pathinfo($file['name']);
$storagePath = "$user->user_code/$code.$fileInfo[extension]";
try {
$response = $this->saveMedia($response, $file, $user);
$this->setSessionQuotaInfo($user->current_disk_quota + $file->getSize(), $user->max_disk_quota);
} catch (Exception $e) {
$this->updateUserQuota($request, $user->id, $file->getSize(), true);
throw $e;
}
$stream = fopen($file['tmp_name'], 'r+');
return $response;
}
$filesystem = $this->getStorage();
try {
$filesystem->writeStream($storagePath, $stream);
} catch (FileExistsException $e) {
Flight::halt(500);
return;
} finally {
fclose($stream);
}
/**
* @param Request $request
* @param Response $response
*
* @return Response
* @throws Exception
*/
public function uploadEndpoint(Request $request, Response $response): Response
{
if ($this->config['maintenance']) {
$this->json['message'] = 'Endpoint under maintenance.';
DB::query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`) VALUES (?, ?, ?, ?)', [
$user->id,
$code,
$file['name'],
$storagePath
]);
return json($response, $this->json, 503);
}
$base_url = Flight::get('config')['base_url'];
try {
$file = $this->validateFile($request, $response);
$response['message'] = 'OK.';
$response['url'] = "$base_url/$user->user_code/$code.$fileInfo[extension]";
Flight::json($response, 201);
if (param($request, 'token') === null) {
$this->json['message'] = 'Token not specified.';
Log::info("User $user->username uploaded new media.", [DB::raw()->lastInsertId()]);
}
return json($response, $this->json, 400);
}
public function show($userCode, $mediaCode): void
{
$media = $this->getMedia($userCode, $mediaCode);
$user = $this->database->query('SELECT * FROM `users` WHERE `token` = ? LIMIT 1', param($request, 'token'))->fetch();
if (!$media || !$media->published && Session::get('user_id') !== $media->user_id && !Session::get('admin', false)) {
Flight::error(new NotFoundException());
return;
}
$this->validateUser($request, $response, $file, $user);
} catch (ValidationException $e) {
return $e->response();
}
$filesystem = $this->getStorage();
if (!$this->updateUserQuota($request, $user->id, $file->getSize())) {
$this->json['message'] = 'User disk quota exceeded.';
if (stristr(Flight::request()->user_agent, 'TelegramBot') ||
stristr(Flight::request()->user_agent, 'facebookexternalhit/') ||
stristr(Flight::request()->user_agent, 'Facebot')) {
$this->streamMedia($filesystem, $media);
} else {
return json($response, $this->json, 507);
}
try {
$mime = $filesystem->getMimetype($media->storage_path);
try {
$response = $this->saveMedia($response, $file, $user);
} catch (Exception $e) {
$this->updateUserQuota($request, $user->id, $file->getSize(), true);
throw $e;
}
return $response;
}
$type = explode('/', $mime)[0];
if ($type === 'text') {
$media->text = $filesystem->read($media->storage_path);
} elseif (in_array($type, ['image', 'video'])) {
$this->http2push(Flight::get('config')['base_url'] . "/$userCode/$mediaCode/raw");
}
/**
* @param Request $request
* @param Response $response
* @return UploadedFileInterface
* @throws ValidationException
*/
protected function validateFile(Request $request, Response $response)
{
$iniValue = ini_get('post_max_size');
$maxPostSize = $iniValue === '0' ? INF : stringToBytes($iniValue);
if ($request->getServerParams()['CONTENT_LENGTH'] > $maxPostSize) {
$this->json['message'] = 'File too large (post_max_size too low?).';
} catch (FileNotFoundException $e) {
Flight::error($e);
return;
}
throw new ValidationException(json($response, $this->json, 400));
}
Flight::render('upload/public.twig', [
'media' => $media,
'type' => $mime,
'extension' => pathinfo($media->filename, PATHINFO_EXTENSION)
]);
}
}
$file = array_values($request->getUploadedFiles());
/** @var UploadedFileInterface|null $file */
$file = $file[0] ?? null;
public function getRawById($id): void
{
$this->checkAdmin();
if ($file === null) {
$this->json['message'] = 'Request without file attached.';
$media = DB::query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch();
throw new ValidationException(json($response, $this->json, 400));
}
if (!$media) {
Flight::error(new NotFoundException());
return;
}
if ($file->getError() === UPLOAD_ERR_INI_SIZE) {
$this->json['message'] = 'File too large (upload_max_filesize too low?).';
$this->streamMedia($this->getStorage(), $media);
}
throw new ValidationException(json($response, $this->json, 400));
}
public function showRaw($userCode, $mediaCode): void
{
$media = $this->getMedia($userCode, $mediaCode);
return $file;
}
if (!$media || !$media->published && Session::get('user_id') !== $media->user_id && !Session::get('admin', false)) {
Flight::error(new NotFoundException());
return;
}
/**
* @param Request $request
* @param Response $response
* @param UploadedFileInterface $file
* @param $user
* @return void
* @throws ValidationException
*/
protected function validateUser(Request $request, Response $response, UploadedFileInterface $file, $user)
{
if (!$user) {
$this->json['message'] = 'Token specified not found.';
$this->streamMedia($this->getStorage(), $media);
}
throw new ValidationException(json($response, $this->json, 404));
}
if (!$user->active) {
$this->json['message'] = 'Account disabled.';
throw new ValidationException(json($response, $this->json, 401));
}
}
public function download($userCode, $mediaCode): void
{
$media = $this->getMedia($userCode, $mediaCode);
/**
* @param Response $response
* @param UploadedFileInterface $file
* @param $user
* @return Response
* @throws \League\Flysystem\FileExistsException
* @throws \League\Flysystem\FileNotFoundException
*/
protected function saveMedia(Response $response, UploadedFileInterface $file, $user)
{
do {
$code = humanRandomString();
} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `uploads` WHERE `code` = ?', $code)->fetch()->count > 0);
if (!$media || !$media->published && Session::get('user_id') !== $media->user_id && !Session::get('admin', false)) {
Flight::error(new NotFoundException());
return;
}
$fileInfo = pathinfo($file->getClientFilename());
$storagePath = "$user->user_code/$code.$fileInfo[extension]";
$this->streamMedia($this->getStorage(), $media, 'attachment');
}
$this->storage->writeStream($storagePath, $file->getStream()->detach());
public function togglePublish($id): void
{
$this->checkLogin();
$this->database->query('INSERT INTO `uploads`(`user_id`, `code`, `filename`, `storage_path`, `published`) VALUES (?, ?, ?, ?, ?)', [
$user->id,
$code,
$file->getClientFilename(),
$storagePath,
$user->hide_uploads == '1' ? 0 : 1,
]);
$mediaId = $this->database->getPdo()->lastInsertId();
if (Session::get('admin')) {
$media = DB::query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch();
} else {
$media = DB::query('SELECT * FROM `uploads` WHERE `id` = ? AND `user_id` = ? LIMIT 1', [$id, Session::get('user_id')])->fetch();
}
if ($this->getSetting('auto_tagging') === 'on') {
$this->autoTag($mediaId, $storagePath);
}
if (!$media) {
Flight::halt(404);
return;
}
$this->json['message'] = 'OK';
$this->json['url'] = urlFor("/{$user->user_code}/{$code}.{$fileInfo['extension']}");
$this->json['raw_url'] = urlFor("/{$user->user_code}/{$code}/raw.{$fileInfo['extension']}");
DB::query('UPDATE `uploads` SET `published`=? WHERE `id`=?', [!$media->published, $media->id]);
}
$this->logger->info("User $user->username uploaded new media.", [$mediaId]);
public function delete($id): void
{
$this->checkLogin();
return json($response, $this->json, 201);
}
$media = DB::query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch();
/**
* @param $mediaId
* @param $storagePath
* @throws \League\Flysystem\FileNotFoundException
*/
protected function autoTag($mediaId, $storagePath)
{
$mime = $this->storage->getMimetype($storagePath);
if (Session::get('admin', false) || $media->user_id === Session::get('user_id')) {
[$type, $subtype] = explode('/', $mime);
$filesystem = $this->getStorage();
try {
$filesystem->delete($media->storage_path);
} catch (FileNotFoundException $e) {
Flight::halt(404);
return;
} finally {
DB::query('DELETE FROM `uploads` WHERE `id` = ?', $id);
Log::info('User ' . Session::get('username') . " deleted media $id");
}
} else {
Flight::halt(403);
}
}
/** @var TagRepository $query */
$query = make(TagRepository::class);
$query->addTag($type, $mediaId);
protected function getMedia($userCode, $mediaCode)
{
$mediaCode = pathinfo($mediaCode)['filename'];
$media = DB::query('SELECT * FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_code` = ? AND `uploads`.`code` = ? LIMIT 1', [
$userCode,
$mediaCode
])->fetch();
return $media;
}
protected function streamMedia(Filesystem $storage, $media, string $disposition = 'inline'): void
{
try {
$mime = $storage->getMimetype($media->storage_path);
$query = Flight::request()->query;
if ($query['width'] !== null && explode('/', $mime)[0] === 'image') {
Flight::response()->header('Content-Type', 'image/png');
Flight::response()->header('Content-Disposition', $disposition . ';filename="scaled-' . pathinfo($media->filename)['filename'] . '.png"');
Flight::response()->sendHeaders();
ob_clean();
$image = imagecreatefromstring($storage->read($media->storage_path));
$scaled = imagescale($image, $query['width'], $query['height'] !== null ? $query['height'] : -1);
imagedestroy($image);
imagepng($scaled, null, 9);
imagedestroy($scaled);
} else {
Flight::response()->header('Content-Type', $mime);
Flight::response()->header('Content-Disposition', $disposition . ';filename="' . $media->filename . '"');
Flight::response()->header('Content-Length', $storage->getSize($media->storage_path));
Flight::response()->sendHeaders();
ob_end_clean();
fpassthru($storage->readStream($media->storage_path));
}
} catch (FileNotFoundException $e) {
Flight::error($e);
}
}
}
if ($type === 'application' || $subtype === 'gif') {
$query->addTag($subtype, $mediaId);
}
}
}

View file

@ -2,336 +2,316 @@
namespace App\Controllers;
use App\Database\DB;
use App\Exceptions\NotFoundException;
use App\Exceptions\UnauthorizedException;
use App\Traits\SingletonController;
use App\Web\Log;
use App\Web\Session;
use Flight;
use App\Database\Repositories\UserRepository;
use App\Web\Mail;
use App\Web\ValidationHelper;
use League\Flysystem\FileNotFoundException;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class UserController extends Controller
{
use SingletonController;
const PER_PAGE = 15;
public function index($page = 1): void
{
$this->checkAdmin();
$page = max(0, --$page);
$users = DB::query('SELECT * FROM `users` LIMIT ? OFFSET ?', [self::PER_PAGE, $page * self::PER_PAGE])->fetchAll();
$pages = DB::query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count / self::PER_PAGE;
Flight::render('user/index.twig', [
'users' => $users,
'next' => $page < floor($pages),
'previous' => $page >= 1,
'current_page' => ++$page,
]);
}
public function create(): void
{
$this->checkAdmin();
Flight::render('user/create.twig');
}
public function store(): void
{
$this->checkAdmin();
$form = Flight::request()->data;
if (!isset($form->email) || empty($form->email)) {
Session::alert('The email is required.', 'danger');
Flight::redirectBack();
return;
}
if (!isset($form->username) || empty($form->username)) {
Session::alert('The username is required.', 'danger');
Flight::redirectBack();
return;
}
if (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ?', $form->username)->fetch()->count > 0) {
Session::alert('The username already taken.', 'danger');
Flight::redirectBack();
return;
}
if (!isset($form->password) || empty($form->password)) {
Session::alert('The password is required.', 'danger');
Flight::redirectBack();
return;
}
do {
$userCode = substr(md5(microtime()), rand(0, 26), 5);
} while (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `user_code` = ?', $userCode)->fetch()->count > 0);
$token = $this->generateNewToken();
DB::query('INSERT INTO `users`(`email`, `username`, `password`, `is_admin`, `active`, `user_code`, `token`) VALUES (?, ?, ?, ?, ?, ?, ?)', [
$form->email,
$form->username,
password_hash($form->password, PASSWORD_DEFAULT),
isset($form->is_admin),
isset($form->is_active),
$userCode,
$token
]);
Session::alert("User '$form->username' created!", 'success');
Log::info('User ' . Session::get('username') . ' created a new user.', [array_diff($form->getData(), ['password'])]);
Flight::redirect('/users');
}
public function edit($id): void
{
$this->checkAdmin();
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
if (!$user) {
Flight::error(new NotFoundException());
return;
}
Flight::render('user/edit.twig', [
'user' => $user
]);
}
public function update($id): void
{
$this->checkAdmin();
$form = Flight::request()->data;
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
if (!$user) {
Flight::error(new NotFoundException());
return;
}
if (!isset($form->email) || empty($form->email)) {
Session::alert('The email is required.', 'danger');
Flight::redirectBack();
return;
}
if (!isset($form->username) || empty($form->username)) {
Session::alert('The username is required.', 'danger');
Flight::redirectBack();
return;
}
if (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ? AND `username` <> ?', [$form->username, $user->username])->fetch()->count > 0) {
Session::alert('The username already taken.', 'danger');
Flight::redirectBack();
return;
}
if ($user->id === Session::get('user_id') && !isset($form->is_admin)) {
Session::alert('You cannot demote yourself.', 'danger');
Flight::redirectBack();
return;
}
if (isset($form->password) && !empty($form->password)) {
DB::query('UPDATE `users` SET `email`=?, `username`=?, `password`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [
$form->email,
$form->username,
password_hash($form->password, PASSWORD_DEFAULT),
isset($form->is_admin),
isset($form->is_active),
$user->id
]);
} else {
DB::query('UPDATE `users` SET `email`=?, `username`=?, `is_admin`=?, `active`=? WHERE `id` = ?', [
$form->email,
$form->username,
isset($form->is_admin),
isset($form->is_active),
$user->id
]);
}
Session::alert("User '$form->username' updated!", 'success');
Log::info('User ' . Session::get('username') . " updated $user->id.", [$user, array_diff($form->getData(), ['password'])]);
Flight::redirect('/users');
}
public function delete($id): void
{
$this->checkAdmin();
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
if (!$user) {
Flight::error(new NotFoundException());
return;
}
if ($user->id === Session::get('user_id')) {
Session::alert('You cannot delete yourself.', 'danger');
Flight::redirectBack();
return;
}
DB::query('DELETE FROM `users` WHERE `id` = ?', $user->id);
Session::alert('User deleted.', 'success');
Log::info('User ' . Session::get('username') . " deleted $user->id.");
Flight::redirect('/users');
}
public function profile(): void
{
$this->checkLogin();
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', Session::get('user_id'))->fetch();
if (!$user) {
Flight::error(new NotFoundException());
return;
}
if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) {
Flight::error(new UnauthorizedException());
return;
}
Flight::render('user/profile.twig', [
'user' => $user
]);
}
public function profileEdit($id): void
{
$this->checkLogin();
$form = Flight::request()->data;
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
if (!$user) {
Flight::error(new NotFoundException());
return;
}
if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) {
Flight::error(new UnauthorizedException());
return;
}
if (!isset($form->email) || empty($form->email)) {
Session::alert('The email is required.', 'danger');
Flight::redirectBack();
return;
}
if (isset($form->password) && !empty($form->password)) {
DB::query('UPDATE `users` SET `email`=?, `password`=? WHERE `id` = ?', [
$form->email,
password_hash($form->password, PASSWORD_DEFAULT),
$user->id
]);
} else {
DB::query('UPDATE `users` SET `email`=? WHERE `id` = ?', [
$form->email,
$user->id
]);
}
Session::alert('Profile updated successfully!', 'success');
Log::info('User ' . Session::get('username') . " updated profile of $user->id.");
Flight::redirectBack();
}
public function refreshToken($id): void
{
$this->checkLogin();
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
if (!$user) {
Flight::halt(404);
return;
}
if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) {
Flight::halt(403);
return;
}
$token = $this->generateNewToken();
DB::query('UPDATE `users` SET `token`=? WHERE `id` = ?', [
$token,
$user->id
]);
Log::info('User ' . Session::get('username') . " refreshed token of user $user->id.");
echo $token;
}
public function getShareXconfigFile($id): void
{
$this->checkLogin();
$user = DB::query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
if (!$user) {
Flight::halt(404);
return;
}
if ($user->id !== Session::get('user_id') && !Session::get('admin', false)) {
Flight::halt(403);
return;
}
$base_url = Flight::get('config')['base_url'];
$json = [
'DestinationType' => 'ImageUploader, TextUploader, FileUploader',
'RequestURL' => "$base_url/upload",
'FileFormName' => 'upload',
'Arguments' => [
'file' => '$filename$',
'text' => '$input$',
'token' => $user->token,
],
'URL' => '$json:url$',
'ThumbnailURL' => '$json:url$/raw',
];
Flight::response()->header('Content-Type', 'application/json');
Flight::response()->header('Content-Disposition', 'attachment;filename="' . $user->username . '-ShareX.sxcu"');
Flight::response()->sendHeaders();
echo json_encode($json, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
}
protected function generateNewToken(): string
{
do {
$token = 'token_' . md5(uniqid('', true));
} while (DB::query('SELECT COUNT(*) AS `count` FROM `users` WHERE `token` = ?', $token)->fetch()->count > 0);
return $token;
}
}
const PER_PAGE = 15;
/**
* @param Response $response
* @param int|null $page
*
* @return Response
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*
* @throws \Twig\Error\LoaderError
*/
public function index(Response $response, int $page = 0): Response
{
$page = max(0, --$page);
$users = $this->database->query('SELECT * FROM `users` LIMIT ? OFFSET ?', [self::PER_PAGE, $page * self::PER_PAGE])->fetchAll();
$pages = $this->database->query('SELECT COUNT(*) AS `count` FROM `users`')->fetch()->count / self::PER_PAGE;
return view()->render(
$response,
'user/index.twig',
[
'users' => $users,
'next' => $page < floor($pages),
'previous' => $page >= 1,
'current_page' => ++$page,
'quota_enabled' => $this->getSetting('quota_enabled'),
]
);
}
/**
* @param Response $response
*
* @return Response
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*
* @throws \Twig\Error\LoaderError
*/
public function create(Response $response): Response
{
return view()->render($response, 'user/create.twig', [
'default_user_quota' => humanFileSize($this->getSetting('default_user_quota'), 0, true),
'quota_enabled' => $this->getSetting('quota_enabled', 'off'),
]);
}
/**
* @param Request $request
* @param Response $response
*
* @return Response
* @throws \Exception
*/
public function store(Request $request, Response $response): Response
{
$maxUserQuota = -1;
$validator = $this->getUserCreateValidator($request)
->callIf($this->getSetting('quota_enabled') === 'on', function ($session) use (&$maxUserQuota, &$request) {
$maxUserQuota = param($request, 'max_user_quota', humanFileSize($this->getSetting('default_user_quota'), 0, true));
if (!preg_match('/(^[0-9]+[B|K|M|G|T]$)|(^\-1$)/i', $maxUserQuota)) {
$session->alert(lang('invalid_quota', 'danger'));
return false;
}
if ($maxUserQuota !== '-1') {
$maxUserQuota = stringToBytes($maxUserQuota);
}
return true;
});
if ($validator->fails()) {
return redirect($response, route('user.create'));
}
make(UserRepository::class)->create(
param($request, 'email'),
param($request, 'username'),
param($request, 'password'),
param($request, 'is_admin') !== null ? 1 : 0,
param($request, 'is_active') !== null ? 1 : 0,
$maxUserQuota,
false,
param($request, 'hide_uploads') !== null ? 1 : 0,
param($request, 'copy_raw') !== null ? 1 : 0
);
if (param($request, 'send_notification') !== null) {
$resetToken = null;
if (empty(param($request, 'password'))) {
$resetToken = bin2hex(random_bytes(16));
$this->database->query('UPDATE `users` SET `reset_token`=? WHERE `id` = ?', [
$resetToken,
$this->database->getPdo()->lastInsertId(),
]);
}
$this->sendCreateNotification($request, $resetToken);
}
$this->session->alert(lang('user_created', [param($request, 'username')]), 'success');
$this->logger->info('User '.$this->session->get('username').' created a new user.', [array_diff_key($request->getParsedBody(), array_flip(['password']))]);
return redirect($response, route('user.index'));
}
/**
* @param Request $request
* @param Response $response
* @param int $id
*
* @return Response
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public function edit(Request $request, Response $response, int $id): Response
{
$user = make(UserRepository::class)->get($request, $id);
return view()->render($response, 'user/edit.twig', [
'profile' => false,
'user' => $user,
'quota_enabled' => $this->getSetting('quota_enabled', 'off'),
'max_disk_quota' => $user->max_disk_quota > 0 ? humanFileSize($user->max_disk_quota, 0, true) : -1,
]);
}
/**
* @param Request $request
* @param Response $response
* @param int $id
*
* @return Response
*/
public function update(Request $request, Response $response, int $id): Response
{
$user = make(UserRepository::class)->get($request, $id);
$user->max_disk_quota = -1;
/** @var ValidationHelper $validator */
$validator = make(ValidationHelper::class)
->alertIf(!filter_var(param($request, 'email'), FILTER_VALIDATE_EMAIL), 'email_required')
->alertIf(empty(param($request, 'username')), 'username_required')
->alertIf($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ? AND `email` <> ?', [param($request, 'email'), $user->email])->fetch()->count != 0, 'email_taken')
->alertIf($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ? AND `username` <> ?', [param($request, 'username'), $user->username])->fetch()->count != 0, 'username_taken')
->alertIf($user->id === $this->session->get('user_id') && param($request, 'is_admin') === null, 'cannot_demote')
->callIf($this->getSetting('quota_enabled') === 'on', function ($session) use (&$user, &$request) {
$maxUserQuota = param($request, 'max_user_quota', humanFileSize($this->getSetting('default_user_quota'), 0, true));
if (!preg_match('/(^[0-9]+[B|K|M|G|T]$)|(^\-1$)/i', $maxUserQuota)) {
$session->alert(lang('invalid_quota', 'danger'));
return false;
}
if ($maxUserQuota !== '-1') {
$user->max_disk_quota = stringToBytes($maxUserQuota);
}
return true;
});
if ($validator->fails()) {
return redirect($response, route('user.edit', ['id' => $id]));
}
make(UserRepository::class)->update(
$user->id,
param($request, 'email'),
param($request, 'username'),
param($request, 'password'),
param($request, 'is_admin') !== null ? 1 : 0,
param($request, 'is_active') !== null ? 1 : 0,
$user->max_disk_quota,
param($request, 'ldap') !== null ? 1 : 0,
param($request, 'hide_uploads') !== null ? 1 : 0,
param($request, 'copy_raw') !== null ? 1 : 0
);
if ($user->id === $this->session->get('user_id')) {
$this->setSessionQuotaInfo($user->current_disk_quota, $user->max_disk_quota);
}
$this->session->alert(lang('user_updated', [param($request, 'username')]), 'success');
$this->logger->info('User '.$this->session->get('username')." updated $user->id.", [
array_diff_key((array) $user, array_flip(['password'])),
array_diff_key($request->getParsedBody(), array_flip(['password'])),
]);
return redirect($response, route('user.index'));
}
/**
* @param Request $request
* @param Response $response
* @param int $id
*
* @return Response
*/
public function delete(Request $request, Response $response, int $id): Response
{
$user = make(UserRepository::class)->get($request, $id);
if ($user->id === $this->session->get('user_id')) {
$this->session->alert(lang('cannot_delete'), 'danger');
return redirect($response, route('user.index'));
}
$this->database->query('DELETE FROM `users` WHERE `id` = ?', $user->id);
$this->session->alert(lang('user_deleted'), 'success');
$this->logger->info('User '.$this->session->get('username')." deleted $user->id.");
return redirect($response, route('user.index'));
}
/**
* @param Request $request
* @param Response $response
* @param int $id
* @return Response
*/
public function clearUserMedia(Request $request, Response $response, int $id): Response
{
$user = make(UserRepository::class)->get($request, $id, true);
$medias = $this->database->query('SELECT * FROM `uploads` WHERE `user_id` = ?', $user->id);
foreach ($medias as $media) {
try {
$this->storage->delete($media->storage_path);
} catch (FileNotFoundException $e) {
}
}
$this->database->query('DELETE FROM `uploads` WHERE `user_id` = ?', $user->id);
$this->database->query('UPDATE `users` SET `current_disk_quota`=? WHERE `id` = ?', [
0,
$user->id,
]);
$this->session->alert(lang('account_media_deleted'), 'success');
return redirect($response, route('user.edit', ['id' => $id]));
}
/**
* @param Request $request
* @param Response $response
* @param int $id
*
* @return Response
*/
public function refreshToken(Request $request, Response $response, int $id): Response
{
$query = make(UserRepository::class);
$user = $query->get($request, $id, true);
$this->logger->info('User '.$this->session->get('username')." refreshed token of user $user->id.");
$response->getBody()->write($query->refreshToken($user->id));
return $response;
}
/**
* @param $request
* @param null $resetToken
*/
private function sendCreateNotification($request, $resetToken = null)
{
if ($resetToken === null && !empty(param($request, 'password'))) {
$message = lang('mail.new_account_text_with_pw', [
param($request, 'username'),
$this->config['app_name'],
$this->config['base_url'],
$this->config['base_url'],
param($request, 'username'),
param($request, 'password'),
route('login.show'),
route('login.show'),
]);
} else {
$message = lang('mail.new_account_text_with_reset', [
param($request, 'username'),
$this->config['app_name'],
$this->config['base_url'],
$this->config['base_url'],
route('recover.password', ['resetToken' => $resetToken]),
route('recover.password', ['resetToken' => $resetToken]),
]);
}
Mail::make()
->from(platform_mail(), $this->config['app_name'])
->to(param($request, 'email'))
->subject(lang('mail.new_account', [$this->config['app_name']]))
->message($message)
->send();
}
}

View file

@ -2,125 +2,64 @@
namespace App\Database;
use PDO;
class DB
{
/** @var DB */
protected static $instance;
/** @var DB */
protected static $instance;
/** @var PDO */
protected $pdo;
/** @var string */
private static $password;
/** @var string */
protected $currentDriver;
/** @var string */
private static $username;
public function __construct(string $dsn, string $username = null, string $password = null)
{
$this->pdo = new PDO($dsn, $username, $password);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
/** @var PDO */
protected $pdo;
$this->currentDriver = $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
if ($this->currentDriver === 'sqlite') {
$this->pdo->exec('PRAGMA foreign_keys = ON');
}
}
/** @var string */
protected static $dsn = 'database.db';
public function query(string $query, $parameters = [])
{
if (!is_array($parameters)) {
$parameters = [$parameters];
}
$query = $this->pdo->prepare($query);
/** @var string */
protected $currentDriver;
foreach ($parameters as $index => $parameter) {
$query->bindValue($index + 1, $parameter, is_int($parameter) ? PDO::PARAM_INT : PDO::PARAM_STR);
}
public function __construct(string $dsn, $username, $password)
{
$query->execute();
$this->pdo = new PDO($dsn, $username, $password);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
return $query;
}
$this->currentDriver = $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
if ($this->currentDriver === 'sqlite') {
$this->pdo->exec('PRAGMA foreign_keys = ON');
}
}
/**
* Get the PDO instance.
*
* @return PDO
*/
public function getPdo(): PDO
{
return $this->pdo;
}
public function doQuery(string $query, $parameters = [])
{
if (!is_array($parameters)) {
$parameters = [$parameters];
}
$query = $this->pdo->prepare($query);
$query->execute($parameters);
return $query;
}
/**
* Get the PDO instance
* @return PDO
*/
public function getPdo(): PDO
{
return $this->pdo;
}
/**
* Get the current PDO driver
* @return string
*/
public function getCurrentDriver(): string
{
return $this->currentDriver;
}
/**
* Perform a query
* @param string $query
* @param array $parameters
* @return bool|\PDOStatement|string
*/
public static function query(string $query, $parameters = [])
{
if (self::$instance === null) {
self::$instance = new self(self::$dsn, self::$username, self::$password);
}
return self::$instance->doQuery($query, $parameters);
}
/**
* Static method to get the current driver name
* @return string
*/
public static function driver(): string
{
if (self::$instance === null) {
self::$instance = new self(self::$dsn, self::$username, self::$password);
}
return self::$instance->getCurrentDriver();
}
/**
* Get directly the PDO instance
* @return PDO
*/
public static function raw(): PDO
{
if (self::$instance === null) {
self::$instance = new self(self::$dsn, self::$username, self::$password);
}
return self::$instance->getPdo();
}
/**
* Set the PDO connection string
* @param string $dsn
* @param string|null $username
* @param string|null $password
*/
public static function setDsn(string $dsn, string $username = null, string $password = null): void
{
self::$dsn = $dsn;
self::$username = $username;
self::$password = $password;
}
}
/**
* Get the current PDO driver.
*
* @return string
*/
public function getCurrentDriver(): string
{
return $this->currentDriver;
}
}

111
app/Database/Migrator.php Normal file
View file

@ -0,0 +1,111 @@
<?php
namespace App\Database;
use League\Flysystem\FileNotFoundException;
use League\Flysystem\Filesystem;
use PDOException;
class Migrator
{
/**
* @var DB
*/
private $db;
/**
* @var string
*/
private $schemaPath;
/**
* Migrator constructor.
*
* @param DB $db
* @param string|null $schemaPath
*/
public function __construct(DB $db, ?string $schemaPath)
{
$this->db = $db;
$this->schemaPath = $schemaPath;
}
public function migrate(): void
{
$this->db->getPdo()->exec(file_get_contents($this->schemaPath.DIRECTORY_SEPARATOR.'migrations.sql'));
$files = glob($this->schemaPath.'/'.$this->db->getCurrentDriver().'/*.sql');
$names = array_map(static function ($path) {
return basename($path);
}, $files);
$in = str_repeat('?, ', count($names) - 1).'?';
$inMigrationsTable = $this->db->query("SELECT * FROM `migrations` WHERE `name` IN ($in)", $names)->fetchAll();
foreach ($files as $file) {
$continue = false;
$exists = false;
foreach ($inMigrationsTable as $migration) {
if (basename($file) === $migration->name && $migration->migrated) {
$continue = true;
break;
}
if (basename($file) === $migration->name && !$migration->migrated) {
$exists = true;
break;
}
}
if ($continue) {
continue;
}
$sql = file_get_contents($file);
try {
$this->db->getPdo()->exec($sql);
if (!$exists) {
$this->db->query('INSERT INTO `migrations` VALUES (?,?)', [basename($file), 1]);
} else {
$this->db->query('UPDATE `migrations` SET `migrated`=? WHERE `name`=?', [1, basename($file)]);
}
} catch (PDOException $exception) {
if (!$exists) {
$this->db->query('INSERT INTO `migrations` VALUES (?,?)', [basename($file), 0]);
}
throw $exception;
}
}
}
/**
* @param Filesystem $filesystem
*/
public function reSyncQuotas(Filesystem $filesystem)
{
$uploads = $this->db->query('SELECT `id`,`user_id`, `storage_path` FROM `uploads`')->fetchAll();
$usersQuotas = [];
foreach ($uploads as $upload) {
if (!array_key_exists($upload->user_id, $usersQuotas)) {
$usersQuotas[$upload->user_id] = 0;
}
try {
$usersQuotas[$upload->user_id] += $filesystem->getSize($upload->storage_path);
} catch (FileNotFoundException $e) {
$this->db->query('DELETE FROM `uploads` WHERE `id` = ?', $upload->id);
}
}
foreach ($usersQuotas as $userId => $quota) {
$this->db->query('UPDATE `users` SET `current_disk_quota`=? WHERE `id` = ?', [
$quota,
$userId,
]);
}
}
}

View file

@ -0,0 +1,387 @@
<?php
namespace App\Database\Repositories;
use App\Database\DB;
use League\Flysystem\FileNotFoundException;
use League\Flysystem\Filesystem;
use League\Flysystem\Plugin\ListWith;
class MediaRepository
{
public const PER_PAGE = 21;
public const PER_PAGE_ADMIN = 27;
public const ORDER_TIME = 0;
public const ORDER_NAME = 1;
public const ORDER_SIZE = 2;
/** @var DB */
protected $db;
/** @var bool */
protected $isAdmin;
protected $userId;
/** @var int */
protected $orderBy;
/** @var string */
protected $orderMode;
/** @var string */
protected $text;
/** @var Filesystem */
protected $storage;
private $pages;
private $media;
/**
* @var int
*/
private $tagId;
/**
* MediaQuery constructor.
*
* @param DB $db
* @param bool $isAdmin
* @param Filesystem $storage
*/
public function __construct(DB $db, Filesystem $storage, bool $isAdmin)
{
$this->db = $db;
$this->isAdmin = $isAdmin;
$this->storage = $storage;
}
/**
* @param DB $db
* @param bool $isAdmin
* @param Filesystem $storage
* @return MediaRepository
*/
public static function make(DB $db, Filesystem $storage, bool $isAdmin)
{
return new self($db, $storage, $isAdmin);
}
/**
* @param $id
*
* @return $this
*/
public function withUserId($id): MediaRepository
{
$this->userId = $id;
return $this;
}
/**
* @param string|null $type
* @param string $mode
*
* @return $this
*/
public function orderBy(string $type = null, $mode = 'ASC'): MediaRepository
{
$this->orderBy = $type ?? self::ORDER_TIME;
$this->orderMode = (strtoupper($mode) === 'ASC') ? 'ASC' : 'DESC';
return $this;
}
/**
* @param string|null $text
*
* @return $this
*/
public function search(?string $text): MediaRepository
{
$this->text = $text;
return $this;
}
/**
* @param $tagId
* @return $this
*/
public function filterByTag($tagId): MediaRepository
{
if ($tagId !== null) {
$this->tagId = (int) $tagId;
}
return $this;
}
/**
* @param int $page
* @return $this
*/
public function run(int $page): MediaRepository
{
if ($this->orderBy == self::ORDER_SIZE) {
$this->runWithFileSort($page);
} else {
$this->runWithDbSort($page);
}
return $this;
}
/**
* @param int $page
* @return $this
*/
public function runWithDbSort(int $page): MediaRepository
{
$params = [];
if ($this->isAdmin) {
[$queryMedia, $queryPages] = $this->buildAdminQueries();
$constPage = self::PER_PAGE_ADMIN;
} else {
[$queryMedia, $queryPages] = $this->buildUserQueries();
$params[] = $this->userId;
$constPage = self::PER_PAGE;
}
if ($this->text !== null) {
$params[] = '%'.htmlentities($this->text).'%';
}
$queryMedia .= $this->buildOrderBy().' LIMIT ? OFFSET ?';
$this->media = $this->db->query($queryMedia, array_merge($params, [$constPage, $page * $constPage]))->fetchAll();
$this->pages = $this->db->query($queryPages, $params)->fetch()->count / $constPage;
$tags = $this->getTags(array_column($this->media, 'id'));
foreach ($this->media as $media) {
try {
$media->size = humanFileSize($this->storage->getSize($media->storage_path));
$media->mimetype = $this->storage->getMimetype($media->storage_path);
} catch (FileNotFoundException $e) {
$media->size = null;
$media->mimetype = null;
}
$media->extension = pathinfo($media->filename, PATHINFO_EXTENSION);
if (array_key_exists($media->id, $tags)) {
$media->tags = $tags[$media->id];
} else {
$media->tags = [];
}
}
return $this;
}
/**
* @param int $page
* @return $this
*/
public function runWithFileSort(int $page): MediaRepository
{
$this->storage->addPlugin(new ListWith());
if ($this->isAdmin) {
$files = $this->storage->listWith(['size', 'mimetype'], '/', true);
$offset = $page * self::PER_PAGE_ADMIN;
$limit = self::PER_PAGE_ADMIN;
} else {
$userCode = $this->db->query('SELECT `user_code` FROM `users` WHERE `id` = ?', $this->userId)->fetch()->user_code;
$files = $this->storage->listWith(['size', 'mimetype'], $userCode);
$offset = $page * self::PER_PAGE;
$limit = self::PER_PAGE;
}
$files = array_filter($files, function ($file) {
return $file['type'] !== 'dir';
});
array_multisort(array_column($files, 'size'), $this->buildOrderBy(), SORT_NUMERIC, $files);
$params = [];
$queryPagesParams = [];
if ($this->text !== null) {
if ($this->isAdmin) {
[$queryMedia, $queryPages] = $this->buildAdminQueries();
} else {
[$queryMedia, $queryPages] = $this->buildUserQueries();
$params[] = $this->userId;
}
$params[] = '%'.htmlentities($this->text).'%';
$queryPagesParams = $params;
$paths = array_column($files, 'path');
} elseif ($this->tagId !== null) {
if ($this->isAdmin) {
[, $queryPages] = $this->buildAdminQueries();
} else {
[, $queryPages] = $this->buildUserQueries();
$queryPagesParams[] = $this->userId;
}
$paths = array_column($files, 'path');
$ids = $this->getMediaIdsByTagId($this->tagId);
$queryMedia = 'SELECT `uploads`.*, `users`.`user_code`, `users`.`username` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `uploads`.`storage_path` IN ("'.implode('","', $paths).'") AND `uploads`.`id` IN ('.implode(',', $ids).')';
} else {
if ($this->isAdmin) {
[, $queryPages] = $this->buildAdminQueries();
} else {
[, $queryPages] = $this->buildUserQueries();
$queryPagesParams[] = $this->userId;
}
$files = array_slice($files, $offset, $limit, true);
$paths = array_column($files, 'path');
$queryMedia = 'SELECT `uploads`.*, `users`.`user_code`, `users`.`username` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `uploads`.`storage_path` IN ("'.implode('","', $paths).'")';
}
$medias = $this->db->query($queryMedia, $params)->fetchAll();
$paths = array_flip($paths);
foreach ($medias as $media) {
$paths[$media->storage_path] = $media;
}
$tags = $this->getTags(array_column($medias, 'id'));
$this->media = [];
foreach ($files as $file) {
$media = $paths[$file['path']];
if (is_object($media)) {
$media->size = humanFileSize($file['size']);
$media->extension = $file['extension'];
$media->mimetype = $file['mimetype'];
$this->media[] = $media;
if (array_key_exists($media->id, $tags)) {
$media->tags = $tags[$media->id];
} else {
$media->tags = [];
}
}
}
$this->pages = $this->db->query($queryPages, $queryPagesParams)->fetch()->count / $limit;
if ($this->text !== null || $this->tagId !== null) {
$this->media = array_slice($this->media, $offset, $limit, true);
}
return $this;
}
protected function buildAdminQueries()
{
$queryPages = 'SELECT COUNT(*) AS `count` FROM `uploads`';
$queryMedia = 'SELECT `uploads`.*, `users`.`user_code`, `users`.`username` FROM `uploads` LEFT JOIN `users` ON `uploads`.`user_id` = `users`.`id`';
if ($this->text !== null || $this->tagId !== null) {
$queryMedia .= ' WHERE';
$queryPages .= ' WHERE';
}
if ($this->text !== null) {
$queryMedia .= ' `uploads`.`filename` LIKE ?';
$queryPages .= ' `filename` LIKE ?';
}
if ($this->tagId !== null) {
if ($this->text !== null) {
$queryMedia .= ' AND';
$queryPages .= ' AND';
}
$ids = $this->getMediaIdsByTagId($this->tagId);
$queryMedia .= ' `uploads`.`id` IN ('.implode(',', $ids).')';
$queryPages .= ' `uploads`.`id` IN ('.implode(',', $ids).')';
}
return [$queryMedia, $queryPages];
}
protected function buildUserQueries()
{
$queryPages = 'SELECT COUNT(*) AS `count` FROM `uploads` WHERE `user_id` = ?';
$queryMedia = 'SELECT `uploads`.*,`users`.`user_code`, `users`.`username` FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_id` = ?';
if ($this->text !== null) {
$queryMedia .= ' AND `uploads`.`filename` LIKE ? ';
$queryPages .= ' AND `filename` LIKE ?';
}
if ($this->tagId !== null) {
$ids = $this->getMediaIdsByTagId($this->tagId);
$queryMedia .= ' AND `uploads`.`id` IN ('.implode(',', $ids).')';
$queryPages .= ' AND `uploads`.`id` IN ('.implode(',', $ids).')';
}
return [$queryMedia, $queryPages];
}
protected function buildOrderBy()
{
switch ($this->orderBy) {
case self::ORDER_NAME:
return ' ORDER BY `filename` '.$this->orderMode;
case self::ORDER_TIME:
return ' ORDER BY `timestamp` '.$this->orderMode;
case self::ORDER_SIZE:
return ($this->orderMode === 'ASC') ? SORT_ASC : SORT_DESC;
default:
return '';
}
}
/**
* @param array $mediaIds
* @return array
*/
protected function getTags(array $mediaIds)
{
$allTags = $this->db->query('SELECT `uploads_tags`.`upload_id`,`tags`.`id`, `tags`.`name` FROM `uploads_tags` INNER JOIN `tags` ON `uploads_tags`.`tag_id` = `tags`.`id` WHERE `uploads_tags`.`upload_id` IN ("'.implode('","', $mediaIds).'") ORDER BY `tags`.`timestamp`')->fetchAll();
$tags = [];
foreach ($allTags as $tag) {
$tags[$tag->upload_id][$tag->id] = $tag->name;
}
return $tags;
}
/**
* @param $tagId
* @return array
*/
protected function getMediaIdsByTagId($tagId)
{
$mediaIds = $this->db->query('SELECT `upload_id` FROM `uploads_tags` WHERE `tag_id` = ?', $tagId)->fetchAll();
$ids = [-1];
foreach ($mediaIds as $pivot) {
$ids[] = $pivot->upload_id;
}
return $ids;
}
/**
* @return mixed
*/
public function getMedia()
{
return $this->media;
}
/**
* @return mixed
*/
public function getPages()
{
return $this->pages;
}
}

View file

@ -0,0 +1,106 @@
<?php
namespace App\Database\Repositories;
use App\Database\DB;
use PDO;
class TagRepository
{
public const PER_MEDIA_LIMIT = 10;
/**
* @var DB
*/
private $db;
/**
* @var null|bool
*/
private $isAdmin;
/**
* @var null|int|string
*/
private $userId;
public function __construct(DB $db, $isAdmin = null, $userId = null)
{
$this->db = $db;
$this->isAdmin = $isAdmin;
$this->userId = $userId;
}
/**
* @return array
*/
public function all()
{
if ($this->isAdmin) {
return $this->db->query('SELECT * FROM `tags` ORDER BY `name`')->fetchAll();
}
return $this->db->query('SELECT DISTINCT `tags`.* FROM `tags` INNER JOIN `uploads_tags` ON `tags`.`id` = `uploads_tags`.`tag_id` INNER JOIN `uploads` ON `uploads`.`id` = `uploads_tags`.`upload_id` WHERE `uploads`.`user_id` = ? ORDER BY `tags`.`name`', $this->userId)->fetchAll();
}
/**
* @param string $tagName
* @param $mediaId
* @return array [id, limit]
*/
public function addTag(string $tagName, $mediaId)
{
$tag = $this->db->query('SELECT * FROM `tags` WHERE `name` = ? LIMIT 1', $tagName)->fetch();
$connectedIds = $this->db->query('SELECT `tag_id` FROM `uploads_tags` WHERE `upload_id` = ?', $mediaId)->fetchAll(PDO::FETCH_COLUMN, 0);
if (!$tag && count($connectedIds) < self::PER_MEDIA_LIMIT) {
$this->db->query('INSERT INTO `tags`(`name`) VALUES (?)', strtolower($tagName));
$tagId = $this->db->getPdo()->lastInsertId();
$this->db->query('INSERT INTO `uploads_tags`(`upload_id`, `tag_id`) VALUES (?, ?)', [
$mediaId,
$tagId,
]);
return [$tagId, false];
}
if (count($connectedIds) >= self::PER_MEDIA_LIMIT || in_array($tag->id, $connectedIds)) {
return [null, true];
}
$this->db->query('INSERT INTO `uploads_tags`(`upload_id`, `tag_id`) VALUES (?, ?)', [
$mediaId,
$tag->id,
]);
return [$tag->id, false];
}
/**
* @param $tagId
* @param $mediaId
* @return bool
*/
public function removeTag($tagId, $mediaId)
{
$tag = $this->db->query('SELECT * FROM `tags` WHERE `id` = ? LIMIT 1', $tagId)->fetch();
if ($tag) {
$this->db->query('DELETE FROM `uploads_tags` WHERE `upload_id` = ? AND `tag_id` = ?', [
$mediaId,
$tag->id,
]);
if ($this->db->query('SELECT COUNT(*) AS `count` FROM `uploads_tags` WHERE `tag_id` = ?', $tag->id)->fetch()->count == 0) {
$this->db->query('DELETE FROM `tags` WHERE `id` = ? ', $tag->id);
return true;
}
return false;
}
return null;
}
}

View file

@ -0,0 +1,181 @@
<?php
namespace App\Database\Repositories;
use App\Database\DB;
use App\Web\Session;
use InvalidArgumentException;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Exception\HttpNotFoundException;
use Slim\Exception\HttpUnauthorizedException;
class UserRepository
{
/**
* @var DB
*/
private $database;
/**
* @var Session
*/
private $session;
/**
* UserQuery constructor.
* @param DB $db
* @param Session|null $session
*/
public function __construct(DB $db, ?Session $session)
{
$this->database = $db;
$this->session = $session;
}
/**
* @param DB $db
* @param Session|null $session
* @return UserRepository
*/
public static function make(DB $db, Session $session = null)
{
return new self($db, $session);
}
/**
* @param Request $request
* @param $id
* @param bool $authorize
* @return mixed
* @throws HttpNotFoundException
* @throws HttpUnauthorizedException
*/
public function get(Request $request, $id, $authorize = false)
{
$user = $this->database->query('SELECT * FROM `users` WHERE `id` = ? LIMIT 1', $id)->fetch();
if (!$user) {
throw new HttpNotFoundException($request);
}
if ($authorize) {
if ($this->session === null) {
throw new InvalidArgumentException('The session is null.');
}
if ($user->id !== $this->session->get('user_id') && !$this->session->get('admin', false)) {
throw new HttpUnauthorizedException($request);
}
}
return $user;
}
/**
* @param string $email
* @param string $username
* @param string|null $password
* @param int $isAdmin
* @param int $isActive
* @param int $maxUserQuota
* @param string|null $activateToken
* @param int $ldap
* @param int $hideUploads
* @param int $copyRaw
* @return bool|\PDOStatement|string
*/
public function create(string $email, string $username, string $password = null, int $isAdmin = 0, int $isActive = 0, int $maxUserQuota = -1, string $activateToken = null, int $ldap = 0, int $hideUploads = 0, int $copyRaw = 0)
{
do {
$userCode = humanRandomString(5);
} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `user_code` = ?', $userCode)->fetch()->count > 0);
$token = $this->generateUserUploadToken();
return $this->database->query('INSERT INTO `users`(`email`, `username`, `password`, `is_admin`, `active`, `user_code`, `token`, `max_disk_quota`, `activate_token`, `ldap`, `hide_uploads`, `copy_raw`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [
$email,
$username,
$password !== null ? password_hash($password, PASSWORD_DEFAULT) : null,
$isAdmin,
$isActive,
$userCode,
$token,
$maxUserQuota,
$activateToken,
$ldap,
$hideUploads,
$copyRaw,
]);
}
/**
* @param $id
* @param string $email
* @param string $username
* @param string|null $password
* @param int $isAdmin
* @param int $isActive
* @param int $maxUserQuota
* @param int $ldap
* @param int $hideUploads
* @param int $copyRaw
* @return bool|\PDOStatement|string
*/
public function update($id, string $email, string $username, string $password = null, int $isAdmin = 0, int $isActive = 0, int $maxUserQuota = -1, int $ldap = 0, int $hideUploads = 0, int $copyRaw = 0)
{
if (!empty($password)) {
return $this->database->query('UPDATE `users` SET `email`=?, `username`=?, `password`=?, `is_admin`=?, `active`=?, `max_disk_quota`=?, `ldap`=?, `hide_uploads`=?, `copy_raw`=? WHERE `id` = ?', [
$email,
$username,
password_hash($password, PASSWORD_DEFAULT),
$isAdmin,
$isActive,
$maxUserQuota,
$ldap,
$hideUploads,
$copyRaw,
$id,
]);
}
return $this->database->query('UPDATE `users` SET `email`=?, `username`=?, `is_admin`=?, `active`=?, `max_disk_quota`=?, `ldap`=?, `hide_uploads`=?, `copy_raw`=? WHERE `id` = ?', [
$email,
$username,
$isAdmin,
$isActive,
$maxUserQuota,
$ldap,
$hideUploads,
$copyRaw,
$id,
]);
}
/**
* @param $id
* @return string
*/
public function refreshToken($id)
{
$token = $this->generateUserUploadToken();
$this->database->query('UPDATE `users` SET `token`=? WHERE `id` = ?', [
$token,
$id,
]);
return $token;
}
/**
* @return string
*/
protected function generateUserUploadToken(): string
{
do {
$token = 'token_'.md5(uniqid('', true));
} while ($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `token` = ?', $token)->fetch()->count > 0);
return $token;
}
}

View file

@ -1,15 +0,0 @@
<?php
namespace App\Exceptions;
use Exception;
use Throwable;
class AuthenticationException extends Exception
{
public function __construct(string $message = 'Not Authorized', int $code = 401, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace App\Exceptions\Handlers;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Handlers\ErrorHandler;
use Throwable;
class AppErrorHandler extends ErrorHandler
{
protected function logError(string $error): void
{
resolve('logger')->error($error);
}
public function __invoke(ServerRequestInterface $request, Throwable $exception, bool $displayErrorDetails, bool $logErrors, bool $logErrorDetails): ResponseInterface
{
$response = parent::__invoke($request, $exception, $displayErrorDetails, $logErrors, $logErrorDetails);
if ($response->getStatusCode() !== 404) {
$this->writeToErrorLog();
}
return $response;
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace App\Exceptions\Handlers\Renderers;
use App\Exceptions\UnderMaintenanceException;
use Slim\Exception\HttpBadRequestException;
use Slim\Exception\HttpForbiddenException;
use Slim\Exception\HttpMethodNotAllowedException;
use Slim\Exception\HttpNotFoundException;
use Slim\Exception\HttpUnauthorizedException;
use Slim\Interfaces\ErrorRendererInterface;
use Throwable;
class HtmlErrorRenderer implements ErrorRendererInterface
{
/**
* @param Throwable $exception
* @param bool $displayErrorDetails
*
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*
* @return string
*/
public function __invoke(Throwable $exception, bool $displayErrorDetails): string
{
if ($exception instanceof UnderMaintenanceException) {
return view()->string('errors/maintenance.twig');
}
if ($exception instanceof HttpUnauthorizedException || $exception instanceof HttpForbiddenException) {
return view()->string('errors/403.twig');
}
if ($exception instanceof HttpMethodNotAllowedException) {
return view()->string('errors/405.twig');
}
if ($exception instanceof HttpNotFoundException) {
return view()->string('errors/404.twig');
}
if ($exception instanceof HttpBadRequestException) {
return view()->string('errors/400.twig');
}
return view()->string('errors/500.twig', ['exception' => $displayErrorDetails ? $exception : null]);
}
}

View file

@ -1,15 +0,0 @@
<?php
namespace App\Exceptions;
use Exception;
use Throwable;
class NotFoundException extends Exception
{
public function __construct(string $message = 'Not Found', int $code = 404, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View file

@ -1,15 +0,0 @@
<?php
namespace App\Exceptions;
use Exception;
use Throwable;
class UnauthorizedException extends Exception
{
public function __construct(string $message = 'Forbidden', int $code = 403, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace App\Exceptions;
use Slim\Exception\HttpSpecializedException;
class UnderMaintenanceException extends HttpSpecializedException
{
protected $code = 503;
protected $message = 'Platform Under Maintenance.';
protected $title = '503 Service Unavailable';
protected $description = 'We\'ll be back very soon! :)';
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Exceptions;
use Exception;
use Psr\Http\Message\ResponseInterface as Response;
use Throwable;
class ValidationException extends Exception
{
/**
* @var Response
*/
private $response;
public function __construct(Response $response, $message = "", Throwable $previous = null)
{
parent::__construct($message, $response->getStatusCode(), $previous);
$this->response = $response;
}
/**
* @return Response
*/
public function response(): Response
{
return $this->response;
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace App\Factories;
use App\Web\View;
use Psr\Container\ContainerInterface as Container;
use Slim\Factory\ServerRequestCreatorFactory;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\TwigFunction;
class ViewFactory
{
public static function createAppInstance(Container $container)
{
$config = $container->get('config');
$loader = new FilesystemLoader(BASE_DIR.'resources/templates');
$twig = new Environment($loader, [
'cache' => BASE_DIR.'resources/cache/twig',
'autoescape' => 'html',
'debug' => $config['debug'],
'auto_reload' => $config['debug'],
]);
$request = ServerRequestCreatorFactory::determineServerRequestCreator()->createServerRequestFromGlobals();
$twig->addGlobal('config', $config);
$twig->addGlobal('request', $request);
$twig->addGlobal('session', $container->get('session'));
$twig->addGlobal('current_lang', $container->get('lang')->getLang());
$twig->addGlobal('maxUploadSize', stringToBytes(ini_get('post_max_size')));
$twig->addGlobal('PLATFORM_VERSION', PLATFORM_VERSION);
$twig->addFunction(new TwigFunction('route', 'route'));
$twig->addFunction(new TwigFunction('lang', 'lang'));
$twig->addFunction(new TwigFunction('urlFor', 'urlFor'));
$twig->addFunction(new TwigFunction('asset', 'asset'));
$twig->addFunction(new TwigFunction('mime2font', 'mime2font'));
$twig->addFunction(new TwigFunction('queryParams', 'queryParams'));
$twig->addFunction(new TwigFunction('isDisplayableImage', 'isDisplayableImage'));
$twig->addFunction(new TwigFunction('inPath', 'inPath'));
$twig->addFunction(new TwigFunction('humanFileSize', 'humanFileSize'));
$twig->addFunction(new TwigFunction('param', 'param'));
$twig->addFunction(new TwigFunction('glue', 'glue'));
return new View($twig);
}
public static function createInstallerInstance(Container $container)
{
$config = $container->get('config');
$loader = new FilesystemLoader([BASE_DIR.'install/templates', BASE_DIR.'resources/templates']);
$twig = new Environment($loader, [
'cache' => false,
'autoescape' => 'html',
'debug' => $config['debug'],
'auto_reload' => $config['debug'],
]);
$request = ServerRequestCreatorFactory::determineServerRequestCreator()->createServerRequestFromGlobals();
$twig->addGlobal('config', $config);
$twig->addGlobal('request', $request);
$twig->addGlobal('session', $container->get('session'));
$twig->addGlobal('PLATFORM_VERSION', PLATFORM_VERSION);
return new View($twig);
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Exception\HttpUnauthorizedException;
class AdminMiddleware extends Middleware
{
/**
* @param Request $request
* @param RequestHandler $handler
*
* @throws HttpUnauthorizedException
*
* @return ResponseInterface
*/
public function __invoke(Request $request, RequestHandler $handler): ResponseInterface
{
if (!$this->database->query('SELECT `id`, `is_admin` FROM `users` WHERE `id` = ? LIMIT 1', [$this->session->get('user_id')])->fetch()->is_admin) {
$this->session->set('admin', false);
throw new HttpUnauthorizedException($request);
}
return $handler->handle($request);
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Psr7\Factory\ResponseFactory;
class AuthMiddleware extends Middleware
{
/**
* @param Request $request
* @param RequestHandler $handler
*
* @return ResponseInterface
*/
public function __invoke(Request $request, RequestHandler $handler): ResponseInterface
{
if (!$this->session->get('logged', false)) {
$this->session->set('redirectTo', (string) $request->getUri()->getPath());
return redirect((new ResponseFactory())->createResponse(), route('login.show'));
}
if (!$this->database->query('SELECT `id`, `active` FROM `users` WHERE `id` = ? LIMIT 1', [$this->session->get('user_id')])->fetch()->active) {
$this->session->alert(lang('account_disabled'), 'danger');
$this->session->set('logged', false);
return redirect((new ResponseFactory())->createResponse(), route('login.show'));
}
return $handler->handle($request);
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Middleware;
use App\Exceptions\UnderMaintenanceException;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class CheckForMaintenanceMiddleware extends Middleware
{
/**
* @param Request $request
* @param RequestHandler $handler
*
* @throws UnderMaintenanceException
*
* @return Response
*/
public function __invoke(Request $request, RequestHandler $handler): Response
{
if ($this->config['maintenance'] && !$this->database->query('SELECT `id`, `is_admin` FROM `users` WHERE `id` = ? LIMIT 1', [$this->session->get('user_id')])->fetch()->is_admin) {
throw new UnderMaintenanceException($request);
}
return $handler->handle($request);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class InjectMiddleware extends Middleware
{
/**
* @param Request $request
* @param RequestHandler $handler
*
* @return Response
*/
public function __invoke(Request $request, RequestHandler $handler)
{
$this->view->getTwig()->addGlobal('customHead', $this->getSetting('custom_head'));
return $handler->handle($request);
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class LangMiddleware extends Middleware
{
/**
* @param Request $request
* @param RequestHandler $handler
*
* @return Response
*/
public function __invoke(Request $request, RequestHandler $handler)
{
$forcedLang = $this->getSetting('lang');
if ($forcedLang !== null) {
$this->lang::setLang($forcedLang);
$request = $request->withAttribute('forced_lang', $forcedLang);
}
return $handler->handle($request);
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace App\Middleware;
use App\Controllers\Controller;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
abstract class Middleware extends Controller
{
/**
* @param Request $request
* @param RequestHandler $handler
*
* @return Response
*/
abstract public function __invoke(Request $request, RequestHandler $handler);
}

View file

@ -0,0 +1,41 @@
<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class RememberMiddleware extends Middleware
{
/**
* @param Request $request
* @param RequestHandler $handler
*
* @return Response
* @throws \Exception
*/
public function __invoke(Request $request, RequestHandler $handler)
{
if (!$this->session->get('logged', false) && !empty($request->getCookieParams()['remember'])) {
[$selector, $token] = explode(':', $request->getCookieParams()['remember']);
$user = $this->database->query(
'SELECT `id`, `username`,`is_admin`, `active`, `remember_token`, `current_disk_quota`, `max_disk_quota`, `copy_raw` FROM `users` WHERE `remember_selector` = ? AND `remember_expire` > ? LIMIT 1',
[$selector, date('Y-m-d\TH:i:s', time())]
)->fetch();
if ($user && password_verify($token, $user->remember_token) && $user->active) {
$this->session->set('logged', true)
->set('user_id', $user->id)
->set('username', $user->username)
->set('admin', $user->is_admin)
->set('copy_raw', $user->copy_raw);
$this->setSessionQuotaInfo($user->current_disk_quota, $user->max_disk_quota);
$this->refreshRememberCookie($user->id);
}
}
return $handler->handle($request);
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace App\Traits;
use App\Controllers\Controller;
trait SingletonController
{
protected static $instance;
/**
* Return the controller instance
* @return Controller
*/
public static function instance(): Controller
{
if (static::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
}

159
app/Web/Lang.php Normal file
View file

@ -0,0 +1,159 @@
<?php
namespace App\Web;
class Lang
{
const DEFAULT_LANG = 'en';
const LANG_PATH = __DIR__.'../../resources/lang/';
/** @var string */
protected static $langPath = self::LANG_PATH;
/** @var string */
protected static $lang;
/** @var Lang */
protected static $instance;
/** @var array */
protected $cache = [];
/**
* @return Lang
*/
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* @param string $lang
* @param string $langPath
*
* @return Lang
*/
public static function build($lang = self::DEFAULT_LANG, $langPath = null): self
{
self::$lang = $lang;
if ($langPath !== null) {
self::$langPath = $langPath;
}
self::$instance = new self();
return self::$instance;
}
/**
* Recognize the current language from the request.
*
* @return bool|string
*/
public static function recognize()
{
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
return locale_accept_from_http($_SERVER['HTTP_ACCEPT_LANGUAGE']);
}
return self::DEFAULT_LANG;
}
/**
* @return string
*/
public static function getLang(): string
{
return self::$lang;
}
/**
* @param $lang
*/
public static function setLang($lang)
{
self::$lang = $lang;
}
/**
* @return array
*/
public static function getList()
{
$languages = [];
$default = count(include self::$langPath.self::DEFAULT_LANG.'.lang.php') - 1;
foreach (glob(self::$langPath.'*.lang.php') as $file) {
$dict = include $file;
if (!is_array($dict) || !isset($dict['lang'])) {
continue;
}
$count = count($dict) - 1;
$percent = min(round(($count / $default) * 100), 100);
$languages[str_replace('.lang.php', '', basename($file))] = "[{$percent}%] ".$dict['lang'];
}
return $languages;
}
/**
* @param $key
* @param array $args
*
* @return string
*/
public function get($key, $args = []): string
{
return $this->getString($key, self::$lang, $args);
}
/**
* @param $key
* @param $lang
* @param $args
*
* @return string
*/
private function getString($key, $lang, $args): string
{
$redLang = strtolower(substr($lang, 0, 2));
if (array_key_exists($lang, $this->cache)) {
$transDict = $this->cache[$lang];
} else {
if (file_exists(self::$langPath.$lang.'.lang.php')) {
$transDict = include self::$langPath.$lang.'.lang.php';
$this->cache[$lang] = $transDict;
} else {
if (file_exists(self::$langPath.$redLang.'.lang.php')) {
$transDict = include self::$langPath.$redLang.'.lang.php';
$this->cache[$lang] = $transDict;
} else {
$transDict = [];
}
}
}
if (array_key_exists($key, $transDict)) {
$string = @vsprintf($transDict[$key], $args);
if ($string !== false) {
return $string;
}
}
if ($lang !== self::DEFAULT_LANG) {
return $this->getString($key, self::DEFAULT_LANG, $args);
}
return $key;
}
}

View file

@ -1,68 +0,0 @@
<?php
namespace App\Web;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
class Log
{
/** @var Logger */
protected $logger;
/** @var Log */
protected static $instance;
/**
* @return Log
* @throws \Exception
*/
public static function instance(): Log
{
if (static::$instance === null) {
static::$instance = new static();
}
return static::$instance;
}
/**
* Log constructor.
* @throws \Exception
*/
public function __construct()
{
$this->logger = new Logger(self::class);
$streamHandler = new RotatingFileHandler(__DIR__ . '/../../logs/log.txt', 10, Logger::DEBUG);
$streamHandler->setFormatter(new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n", "Y-m-d H:i:s", true));
$this->logger->pushHandler($streamHandler);
}
public static function debug(string $message, array $context = [])
{
self::instance()->logger->debug($message, $context);
}
public static function info(string $message, array $context = [])
{
self::instance()->logger->info($message, $context);
}
public static function warning(string $message, array $context = [])
{
self::instance()->logger->warning($message, $context);
}
public static function error(string $message, array $context = [])
{
self::instance()->logger->error($message, $context);
}
public static function critical(string $message, array $context = [])
{
self::instance()->logger->critical($message, $context);
}
}

152
app/Web/Mail.php Normal file
View file

@ -0,0 +1,152 @@
<?php
namespace App\Web;
use InvalidArgumentException;
class Mail
{
/**
* @var bool
*/
private static $testing = false;
protected $fromMail = 'no-reply@example.com';
protected $fromName;
protected $to;
protected $subject;
protected $message;
protected $additionalHeaders = '';
protected $headers = '';
/**
* @return Mail
*/
public static function make()
{
return new self();
}
/**
* This will skip the email send
*/
public static function fake()
{
self::$testing = true;
}
/**
* @param $mail
* @param $name
* @return $this
*/
public function from(string $mail, string $name)
{
$this->fromMail = $mail;
$this->fromName = $name;
return $this;
}
/**
* @param $mail
* @return $this
*/
public function to(string $mail)
{
if (!filter_var($mail, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Mail not valid.');
}
$this->to = "<$mail>";
return $this;
}
/**
* @param $text
* @return $this
*/
public function subject(string $text)
{
$this->subject = $text;
return $this;
}
/**
* @param $text
* @return $this
*/
public function message(string $text)
{
$this->message = $text;
return $this;
}
/**
* @param $header
* @return $this
*/
public function addHeader(string $header)
{
$this->additionalHeaders .= "$header\r\n";
return $this;
}
/**
* @param $header
* @return $this
*/
protected function addRequiredHeader(string $header)
{
$this->headers .= "$header\r\n";
return $this;
}
/**
* Set headers before send
*/
protected function setHeaders()
{
if ($this->fromName === null) {
$this->addRequiredHeader("From: $this->fromMail");
} else {
$this->addRequiredHeader("From: $this->fromName <$this->fromMail>");
}
$this->addRequiredHeader('X-Mailer: PHP/'.phpversion())
->addRequiredHeader('MIME-Version: 1.0')
->addRequiredHeader('Content-Type: text/html; charset=utf-8');
}
/**
* @return int
*/
public function send()
{
if ($this->to === null) {
throw new InvalidArgumentException('Target email cannot be null.');
}
if ($this->subject === null) {
throw new InvalidArgumentException('Subject cannot be null.');
}
if ($this->message === null) {
throw new InvalidArgumentException('Message cannot be null.');
}
$this->setHeaders();
$this->headers .= $this->additionalHeaders;
$message = html_entity_decode($this->message);
if (self::$testing) {
return 1;
}
return (int) mail($this->to, $this->subject, "<html><body>$message</body></html>", $this->headers);
}
}

View file

@ -2,106 +2,160 @@
namespace App\Web;
use Exception;
class Session
{
/**
* Session constructor.
*
* @param string $name
* @param string $path
*
* @throws Exception
*/
public function __construct(string $name, $path = '')
{
if (session_status() === PHP_SESSION_NONE) {
if (!is_writable($path) && $path !== '') {
throw new Exception("The given path '{$path}' is not writable.");
}
/**
* Start a session if is not already started in the current context
*/
public static function init(): void
{
if (session_status() === PHP_SESSION_NONE) {
session_start([
'name' => 'xbackbone_session',
'save_path' => 'resources/sessions'
]);
}
}
// Workaround for php <= 7.3
if (PHP_VERSION_ID < 70300) {
$params = session_get_cookie_params();
session_set_cookie_params(
$params['lifetime'],
$params['path'].'; SameSite=Strict',
$params['domain'],
isSecure(),
$params['httponly']
);
}
/**
* Destroy the current session
* @return bool
*/
public static function destroy(): bool
{
return session_destroy();
}
$started = @session_start([
'name' => $name,
'save_path' => $path,
'cookie_httponly' => true,
'gc_probability' => 25,
'cookie_samesite' => 'Strict', // works only for php >= 7.3
'cookie_secure' => isSecure(),
]);
/**
* Clear all session stored values
*/
public static function clear(): void
{
self::init();
$_SESSION = [];
}
if (!$started) {
throw new Exception("Cannot start the HTTP session. The session path '{$path}' is not writable.");
}
}
}
/**
* Check if session has a stored key
* @param $key
* @return bool
*/
public static function has($key): bool
{
self::init();
return isset($_SESSION[$key]);
}
/**
* @return string
*/
public function getId()
{
return session_id();
}
/**
* Get the content of the current session
* @return array
*/
public static function all(): array
{
self::init();
return $_SESSION;
}
/**
* Destroy the current session.
*
* @return bool
*/
public function destroy(): bool
{
return session_destroy();
}
/**
* Returned a value given a key
* @param $key
* @param null $default
* @return mixed
*/
public static function get($key, $default = null)
{
self::init();
return self::has($key) ? $_SESSION[$key] : $default;
}
/**
* Clear all session stored values.
*/
public function clear(): Session
{
$_SESSION = [];
return $this;
}
/**
* Add a key-value pair to the session
* @param $key
* @param $value
*/
public static function set($key, $value): void
{
self::init();
$_SESSION[$key] = $value;
}
/**
* Check if session has a stored key.
*
* @param $key
*
* @return bool
*/
public function has($key): bool
{
return isset($_SESSION[$key]);
}
/**
* Set a flash alert
* @param $message
* @param string $type
*/
public static function alert($message, string $type = 'info'): void
{
self::init();
$_SESSION['_flash'] = [$type => $message];
}
/**
* Get the content of the current session.
*
* @return array
*/
public function all(): array
{
return $_SESSION;
}
/**
* Returned a value given a key.
*
* @param $key
* @param null $default
*
* @return mixed
*/
public function get($key, $default = null)
{
return self::has($key) ? $_SESSION[$key] : $default;
}
/**
* Retrieve flash alerts
* @return array
*/
public static function getAlert()
{
$flash = self::get('_flash');
self::set('_flash', []);
return $flash;
}
/**
* Add a key-value pair to the session.
*
* @param $key
* @param $value
* @return Session
*/
public function set($key, $value): Session
{
$_SESSION[$key] = $value;
return $this;
}
}
/**
* Set a flash alert.
*
* @param $message
* @param string $type
* @return Session
*/
public function alert($message, string $type = 'info'): Session
{
$_SESSION['_flash'][] = [$type => $message];
return $this;
}
/**
* Closes the current session
*
* @return bool|void
*/
public function close()
{
return session_write_close();
}
/**
* Retrieve flash alerts.
*
* @return array
*/
public function getAlert(): ?array
{
$flash = self::get('_flash');
self::set('_flash', []);
return $flash;
}
}

67
app/Web/Theme.php Normal file
View file

@ -0,0 +1,67 @@
<?php
namespace App\Web;
class Theme
{
public const DEFAULT_THEME_URL = 'https://bootswatch.com/4/_vendor/bootstrap/dist/css/bootstrap.min.css';
/**
* @return array
*/
public function availableThemes(): array
{
$apiJson = json_decode(file_get_contents('https://bootswatch.com/api/4.json'));
$default = [];
$default['Default - Bootstrap 4 default theme'] = self::DEFAULT_THEME_URL;
$bootswatch = [];
foreach ($apiJson->themes as $theme) {
$bootswatch["{$theme->name} - {$theme->description}"] = $theme->cssMin;
}
$apiJson = json_decode(file_get_contents('https://theme-park.dev/themes.json'));
$base = $apiJson->applications->xbackbone->base_css;
$themepark = [];
foreach ($apiJson->themes as $name => $urls) {
$themepark[$name] = "{$base},{$urls->url}";
}
return [
'default' => $default,
'bootswatch.com' => $bootswatch,
'theme-park.dev' => $themepark
];
}
/**
* @param string $input
* @return bool
*/
public function applyTheme(string $input): bool
{
[$vendor, $css] = explode('|', $input, 2);
if ($vendor === 'theme-park.dev') {
[$base, $theme] = explode(',', $css);
$data = file_get_contents(self::DEFAULT_THEME_URL).file_get_contents($base).file_get_contents($theme);
} else {
$data = file_get_contents($css ?? self::DEFAULT_THEME_URL);
}
return (bool) file_put_contents(
BASE_DIR.'static/bootstrap/css/bootstrap.min.css',
$data
);
}
/**
* @return string
*/
public static function default(): string
{
return 'default|'.self::DEFAULT_THEME_URL;
}
}

55
app/Web/UA.php Normal file
View file

@ -0,0 +1,55 @@
<?php
namespace App\Web;
class UA
{
/**
* bot user agent => perform link embed
* @var string[]
*/
private static $bots = [
'TelegramBot' => false,
'facebookexternalhit/' => false,
'Facebot' => false,
'curl/' => false,
'wget/' => false,
'WhatsApp/' => false,
'Slack' => false,
'Twitterbot/' => false,
'discord' => true,
// discord image bot
'Mozilla/5.0 (Macintosh; Intel Mac OS X 11.6; rv:92.0) Gecko/20100101 Firefox/92.0' => true,
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0' => true,
];
/**
* @param string $userAgent
* @return bool
*/
public static function isBot(string $userAgent): bool
{
foreach (self::$bots as $bot => $embedsLink) {
if (stripos($userAgent, $bot) !== false) {
return true;
}
}
return false;
}
/**
* @param string $userAgent
* @return false|string
*/
public static function embedsLinks(string $userAgent)
{
foreach (self::$bots as $bot => $embedsLink) {
if (stripos($userAgent, $bot) !== false) {
return $embedsLink;
}
}
return false;
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace App\Web;
class ValidationHelper
{
/**
* @var Session
*/
protected $session;
/**
* @var bool
*/
protected $failed;
/**
* Validator constructor.
* @param Session $session
*/
public function __construct(Session $session)
{
$this->session = $session;
$this->failed = false;
}
public function alertIf(bool $condition, string $alert, string $type = 'danger')
{
if (!$this->failed && $condition) {
$this->failed = true;
$this->session->alert(lang($alert), $type);
}
return $this;
}
public function failIf(bool $condition)
{
if (!$this->failed && $condition) {
$this->failed = true;
}
return $this;
}
public function callIf(bool $condition, callable $closure)
{
if (!$this->failed && $condition) {
do {
$result = $closure($this->session);
if (is_callable($result)) {
$closure = $result;
}
} while (!is_bool($result));
$this->failed = !$result;
}
return $this;
}
public function fails()
{
return $this->failed;
}
}

69
app/Web/View.php Normal file
View file

@ -0,0 +1,69 @@
<?php
namespace App\Web;
use Psr\Http\Message\ResponseInterface as Response;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
class View
{
/**
* @var Environment
*/
private $twig;
/**
* View constructor.
*
* @param Environment $twig
*/
public function __construct(Environment $twig)
{
$this->twig = $twig;
}
/**
* @param Response $response
* @param string $view
* @param array|null $parameters
*
* @throws LoaderError
* @throws RuntimeError
* @throws SyntaxError
*
* @return Response
*/
public function render(Response $response, string $view, ?array $parameters = [])
{
$body = $this->twig->render($view, $parameters);
$response->getBody()->write($body);
return $response;
}
/**
* @param string $view
* @param array|null $parameters
*
* @throws LoaderError
* @throws RuntimeError
* @throws SyntaxError
*
* @return string
*/
public function string(string $view, ?array $parameters = [])
{
return $this->twig->render($view, $parameters);
}
/**
* @return Environment
*/
public function getTwig(): Environment
{
return $this->twig;
}
}

530
app/helpers.php Normal file
View file

@ -0,0 +1,530 @@
<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\ServerRequestCreatorFactory;
if (!defined('HUMAN_RANDOM_CHARS')) {
define('HUMAN_RANDOM_CHARS', 'bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZaeiouAEIOU');
}
if (!function_exists('humanFileSize')) {
/**
* Generate a human readable file size.
*
* @param $size
* @param int $precision
*
* @param bool $iniMode
* @return string
*/
function humanFileSize($size, $precision = 2, $iniMode = false): string
{
for ($i = 0; ($size / 1024) > 0.9; $i++, $size /= 1024) {
}
if ($iniMode) {
return round($size, $precision).['B', 'K', 'M', 'G', 'T'][$i];
}
return round($size, $precision).' '.['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i];
}
}
if (!function_exists('humanRandomString')) {
/**
* @param int $length
*
* @return string
*/
function humanRandomString(int $length = 10): string
{
$result = '';
$numberOffset = round($length * 0.2);
for ($x = 0; $x < $length - $numberOffset; $x++) {
$result .= ($x % 2) ? HUMAN_RANDOM_CHARS[rand(42, 51)] : HUMAN_RANDOM_CHARS[rand(0, 41)];
}
for ($x = 0; $x < $numberOffset; $x++) {
$result .= rand(0, 9);
}
return $result;
}
}
if (!function_exists('isDisplayableImage')) {
/**
* @param string $mime
*
* @return bool
*/
function isDisplayableImage(?string $mime): bool
{
return in_array($mime, [
'image/apng',
'image/bmp',
'image/gif',
'image/x-icon',
'image/jpeg',
'image/png',
'image/tiff',
'image/webp',
]);
}
}
if (!function_exists('isEmbeddable')) {
/**
* @param ?string $mime
*
* @return bool
*/
function isEmbeddable(?string $mime): bool
{
return in_array($mime, [
'image/apng',
'image/bmp',
'image/gif',
'image/x-icon',
'image/jpeg',
'image/png',
'image/tiff',
'image/webp',
'video/mp4',
'video/ogg',
'video/webm',
]);
}
}
if (!function_exists('stringToBytes')) {
/**
* @param $str
*
* @return float
*/
function stringToBytes(string $str): float
{
$val = trim($str);
if (is_numeric($val)) {
return (float) $val;
}
$last = strtolower($val[strlen($val) - 1]);
$val = substr($val, 0, -1);
$val = (float) $val;
switch ($last) {
case 't':
$val *= 1024;
// no break
case 'g':
$val *= 1024;
// no break
case 'm':
$val *= 1024;
// no break
case 'k':
$val *= 1024;
}
return $val;
}
}
if (!function_exists('removeDirectory')) {
/**
* Remove a directory and it's content.
*
* @param $path
*/
function removeDirectory($path)
{
cleanDirectory($path, true);
rmdir($path);
}
}
if (!function_exists('cleanDirectory')) {
/**
* Removes all directory contents.
*
* @param $path
* @param bool $all
*/
function cleanDirectory($path, $all = false)
{
$directoryIterator = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
$iteratorIterator = new RecursiveIteratorIterator($directoryIterator, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($iteratorIterator as $file) {
if ($all || $file->getFilename() !== '.gitkeep') {
$file->isDir() ? rmdir($file) : unlink($file);
}
}
}
}
if (!function_exists('resolve')) {
/**
* Resolve a service from de DI container.
*
* @param string $service
*
* @return mixed
*/
function resolve(string $service)
{
global $app;
return $app->getContainer()->get($service);
}
}
if (!function_exists('make')) {
/**
* Resolve a service from de DI container.
*
* @param string $class
* @param array $params
* @return mixed
*/
function make(string $class, array $params = [])
{
global $app;
return $app->getContainer()->make($class, $params);
}
}
if (!function_exists('view')) {
/**
* Render a view to the response body.
*
* @return \App\Web\View
*/
function view()
{
return resolve('view');
}
}
if (!function_exists('redirect')) {
/**
* Set the redirect response.
*
* @param Response $response
* @param string $url
* @param int $status
*
* @return Response
*/
function redirect(Response $response, string $url, $status = 302)
{
return $response
->withHeader('Location', $url)
->withStatus($status);
}
}
if (!function_exists('asset')) {
/**
* Get the asset link with timestamp.
*
* @param string $path
*
* @return string
*/
function asset(string $path): string
{
return urlFor($path, '?'.filemtime(realpath(BASE_DIR.$path)));
}
}
if (!function_exists('urlFor')) {
/**
* Generate the app url given a path.
*
* @param string $path
* @param string $append
*
* @return string
*/
function urlFor(string $path = '', string $append = ''): string
{
$baseUrl = resolve('config')['base_url'];
return $baseUrl.$path.$append;
}
}
if (!function_exists('route')) {
/**
* Generate the app url given a path.
*
* @param string $path
* @param array $args
* @param string $append
*
* @return string
*/
function route(string $path, array $args = [], string $append = ''): string
{
global $app;
$uri = $app->getRouteCollector()->getRouteParser()->relativeUrlFor($path, $args);
return urlFor($uri, $append);
}
}
if (!function_exists('param')) {
/**
* Get a parameter from the request.
*
* @param Request $request
* @param string $name
* @param null $default
*
* @return mixed
*/
function param(Request $request, string $name, $default = null)
{
if ($request->getMethod() === 'GET') {
$params = $request->getQueryParams();
} else {
$params = $request->getParsedBody();
}
return $params[$name] ?? $default;
}
}
if (!function_exists('json')) {
/**
* Return a json response.
*
* @param Response $response
* @param $data
* @param int $status
* @param int $options
*
* @return Response
*/
function json(Response $response, $data, int $status = 200, $options = 0): Response
{
$response->getBody()->write(json_encode($data, $options));
return $response
->withStatus($status)
->withHeader('Content-Type', 'application/json');
}
}
if (!function_exists('lang')) {
/**
* @param string $key
* @param array $args
*
* @return string
*/
function lang(string $key, $args = []): string
{
return resolve('lang')->get($key, $args);
}
}
if (!function_exists('mime2font')) {
/**
* Convert get the icon from the file mimetype.
*
* @param $mime
*
* @return mixed|string
*/
function mime2font($mime)
{
$classes = [
'image' => 'fa-file-image',
'audio' => 'fa-file-audio',
'video' => 'fa-file-video',
'application/pdf' => 'fa-file-pdf',
'application/msword' => 'fa-file-word',
'application/vnd.ms-word' => 'fa-file-word',
'application/vnd.oasis.opendocument.text' => 'fa-file-word',
'application/vnd.openxmlformats-officedocument.wordprocessingml' => 'fa-file-word',
'application/vnd.ms-excel' => 'fa-file-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml' => 'fa-file-excel',
'application/vnd.oasis.opendocument.spreadsheet' => 'fa-file-excel',
'application/vnd.ms-powerpoint' => 'fa-file-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml' => 'fa-file-powerpoint',
'application/vnd.oasis.opendocument.presentation' => 'fa-file-powerpoint',
'text/plain' => 'fa-file-alt',
'text/html' => 'fa-file-code',
'text/x-php' => 'fa-file-code',
'application/json' => 'fa-file-code',
'application/gzip' => 'fa-file-archive',
'application/zip' => 'fa-file-archive',
'application/octet-stream' => 'fa-file-alt',
];
foreach ($classes as $fullMime => $class) {
if (strpos($mime, $fullMime) === 0) {
return $class;
}
}
return 'fa-file';
}
}
if (!function_exists('dd')) {
/**
* Dumps all the given vars and halt the execution.
*/
function dd()
{
array_map(function ($x) {
echo '<pre>';
print_r($x);
echo '</pre>';
}, func_get_args());
die();
}
}
if (!function_exists('queryParams')) {
/**
* Get the query parameters of the current request.
*
* @param array $replace
*
* @return string
*/
function queryParams(array $replace = [])
{
$request = ServerRequestCreatorFactory::determineServerRequestCreator()->createServerRequestFromGlobals();
$params = array_replace_recursive($request->getQueryParams(), $replace);
return !empty($params) ? '?'.http_build_query($params) : '';
}
}
if (!function_exists('inPath')) {
/**
* Check if uri start with a path.
*
* @param string $uri
* @param string $path
*
* @return bool
*/
function inPath(string $uri, string $path): bool
{
$path = parse_url(urlFor($path), PHP_URL_PATH);
return substr($uri, 0, strlen($path)) === $path;
}
}
if (!function_exists('glob_recursive')) {
/**
* Does not support flag GLOB_BRACE.
*
* @param $pattern
* @param int $flags
*
* @return array|false
*/
function glob_recursive($pattern, $flags = 0)
{
$files = glob($pattern, $flags);
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
$files = array_merge($files, glob_recursive($dir.'/'.basename($pattern), $flags));
}
return $files;
}
}
if (!function_exists('dsnFromConfig')) {
/**
* Return the database DSN from config.
*
* @param array $config
*
* @return string
*/
function dsnFromConfig(array $config): string
{
$dsn = $config['db']['dsn'];
if ($config['db']['connection'] === 'sqlite') {
if (getcwd() !== BASE_DIR) { // if in installer, change the working dir to the app dir
chdir(BASE_DIR);
}
if (file_exists($config['db']['dsn'])) {
$dsn = realpath($config['db']['dsn']);
}
}
return $config['db']['connection'].':'.$dsn;
}
}
if (!function_exists('platform_mail')) {
/**
* Return the system no-reply mail.
*
* @param string $mailbox
* @return string
*/
function platform_mail($mailbox = 'no-reply'): string
{
return $mailbox.'@'.str_ireplace('www.', '', parse_url(resolve('config')['base_url'], PHP_URL_HOST));
}
}
if (!function_exists('must_be_escaped')) {
/**
* Return the system no-reply mail.
*
* @param $mime
* @return bool
*/
function must_be_escaped($mime): bool
{
$mimes = [
'text/htm',
'image/svg',
];
foreach ($mimes as $m) {
if (stripos($mime, $m) !== false) {
return true;
}
}
return false;
}
}
if (!function_exists('isSecure')) {
/**
* @return bool
*/
function isSecure(): bool
{
return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|| (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] === 443);
}
}
if (!function_exists('glue')) {
/**
* @param mixed ...$pieces
* @return string
*/
function glue(...$pieces): string
{
return '/'.implode('/', $pieces);
}
}

112
app/routes.php Normal file → Executable file
View file

@ -1,29 +1,93 @@
<?php
Flight::route('GET /login', [\App\Controllers\LoginController::instance(), 'show']);
Flight::route('POST /login', [\App\Controllers\LoginController::instance(), 'login']);
Flight::route('GET|POST /logout', [\App\Controllers\LoginController::instance(), 'logout']);
use App\Controllers\AdminController;
use App\Controllers\Auth\LoginController;
use App\Controllers\Auth\PasswordRecoveryController;
use App\Controllers\Auth\RegisterController;
use App\Controllers\ClientController;
use App\Controllers\DashboardController;
use App\Controllers\ExportController;
use App\Controllers\MediaController;
use App\Controllers\ProfileController;
use App\Controllers\SettingController;
use App\Controllers\TagController;
use App\Controllers\UpgradeController;
use App\Controllers\UploadController;
use App\Controllers\UserController;
use App\Middleware\AdminMiddleware;
use App\Middleware\AuthMiddleware;
use App\Middleware\CheckForMaintenanceMiddleware;
use Slim\Routing\RouteCollectorProxy;
Flight::route('GET /', [\App\Controllers\DashboardController::instance(), 'redirects']);
Flight::route('GET /home(/page/@page)', [\App\Controllers\DashboardController::instance(), 'home']);
Flight::route('GET /system', [\App\Controllers\DashboardController::instance(), 'system']);
global $app;
$app->group('', function (RouteCollectorProxy $group) {
$group->get('/home[/page/{page}]', [DashboardController::class, 'home'])->setName('home');
$group->get('/upload', [UploadController::class, 'uploadWebPage'])->setName('upload.web.show');
$group->post('/upload/web', [UploadController::class, 'uploadWeb'])->setName('upload.web');
$group->get('/home/switchView', [DashboardController::class, 'switchView'])->setName('switchView');
Flight::route('GET /users(/page/@page)', [\App\Controllers\UserController::instance(), 'index']);
Flight::route('GET /user/add', [\App\Controllers\UserController::instance(), 'create']);
Flight::route('POST /user/add', [\App\Controllers\UserController::instance(), 'store']);
Flight::route('GET /user/@id/edit', [\App\Controllers\UserController::instance(), 'edit']);
Flight::route('POST /user/@id', [\App\Controllers\UserController::instance(), 'update']);
Flight::route('GET /user/@id/delete', [\App\Controllers\UserController::instance(), 'delete']);
Flight::route('GET /profile', [\App\Controllers\UserController::instance(), 'profile']);
Flight::route('POST /profile/@id/edit', [\App\Controllers\UserController::instance(), 'profileEdit']);
Flight::route('POST /user/@id/refreshToken', [\App\Controllers\UserController::instance(), 'refreshToken']);
Flight::route('GET /user/@id/config/sharex', [\App\Controllers\UserController::instance(), 'getShareXconfigFile']);
$group->group('', function (RouteCollectorProxy $group) {
$group->get('/system/deleteOrphanFiles', [AdminController::class, 'deleteOrphanFiles'])->setName('system.deleteOrphanFiles');
$group->get('/system/recalculateUserQuota', [AdminController::class, 'recalculateUserQuota'])->setName('system.recalculateUserQuota');
Flight::route('POST /upload', [\App\Controllers\UploadController::instance(), 'upload']);
Flight::route('POST /upload/@id/publish', [\App\Controllers\UploadController::instance(), 'togglePublish']);
Flight::route('POST /upload/@id/unpublish', [\App\Controllers\UploadController::instance(), 'togglePublish']);
Flight::route('GET /upload/@id/raw', [\App\Controllers\UploadController::instance(), 'getRawById']);
Flight::route('POST /upload/@id/delete', [\App\Controllers\UploadController::instance(), 'delete']);
Flight::route('GET /@userCode/@filename', [\App\Controllers\UploadController::instance(), 'show']);
Flight::route('GET /@userCode/@filename/raw', [\App\Controllers\UploadController::instance(), 'showRaw']);
Flight::route('GET /@userCode/@filename/download', [\App\Controllers\UploadController::instance(), 'download']);
$group->get('/system/themes', [AdminController::class, 'getThemes'])->setName('theme');
$group->post('/system/settings/save', [SettingController::class, 'saveSettings'])->setName('settings.save');
$group->post('/system/upgrade', [UpgradeController::class, 'upgrade'])->setName('system.upgrade');
$group->get('/system/checkForUpdates', [UpgradeController::class, 'checkForUpdates'])->setName('system.checkForUpdates');
$group->get('/system/changelog', [UpgradeController::class, 'changelog'])->setName('system.changelog');
$group->get('/system', [AdminController::class, 'system'])->setName('system');
$group->get('/users[/page/{page}]', [UserController::class, 'index'])->setName('user.index');
})->add(AdminMiddleware::class);
$group->group('/user', function (RouteCollectorProxy $group) {
$group->get('/create', [UserController::class, 'create'])->setName('user.create');
$group->post('/create', [UserController::class, 'store'])->setName('user.store');
$group->get('/{id}/edit', [UserController::class, 'edit'])->setName('user.edit');
$group->post('/{id}', [UserController::class, 'update'])->setName('user.update');
$group->get('/{id}/delete', [UserController::class, 'delete'])->setName('user.delete');
$group->get('/{id}/clear', [UserController::class, 'clearUserMedia'])->setName('user.clear');
})->add(AdminMiddleware::class);
$group->get('/profile', [ProfileController::class, 'profile'])->setName('profile');
$group->post('/profile/{id}', [ProfileController::class, 'profileEdit'])->setName('profile.update');
$group->post('/user/{id}/refreshToken', [UserController::class, 'refreshToken'])->setName('refreshToken');
$group->get('/user/{id}/config/sharex', [ClientController::class, 'getShareXConfig'])->setName('config.sharex');
$group->get('/user/{id}/config/script', [ClientController::class, 'getBashScript'])->setName('config.script');
$group->get('/user/{id}/config/kde_script', [ClientController::class, 'getKDEScript'])->setName('kde_config.script');
$group->get('/user/{id}/export', [ExportController::class, 'downloadData'])->setName('export.data');
$group->post('/upload/{id}/publish', [MediaController::class, 'togglePublish'])->setName('upload.publish');
$group->post('/upload/{id}/unpublish', [MediaController::class, 'togglePublish'])->setName('upload.unpublish');
$group->post('/upload/{id}/vanity', [MediaController::class, 'createVanity'])->setName('upload.vanity');
$group->get('/upload/{id}/raw', [MediaController::class, 'getRawById'])->add(AdminMiddleware::class)->setName('upload.raw');
$group->map(['GET', 'POST'], '/upload/{id}/delete', [MediaController::class, 'delete'])->setName('upload.delete');
$group->post('/tag/add', [TagController::class, 'addTag'])->setName('tag.add');
$group->post('/tag/remove', [TagController::class, 'removeTag'])->setName('tag.remove');
})->add(App\Middleware\CheckForMaintenanceMiddleware::class)->add(AuthMiddleware::class);
$app->get('/', [DashboardController::class, 'redirects'])->setName('root');
$app->get('/register', [RegisterController::class, 'registerForm'])->setName('register.show');
$app->post('/register', [RegisterController::class, 'register'])->setName('register');
$app->get('/activate/{activateToken}', [RegisterController::class, 'activateUser'])->setName('activate');
$app->get('/recover', [PasswordRecoveryController::class, 'recover'])->setName('recover');
$app->post('/recover/mail', [PasswordRecoveryController::class, 'recoverMail'])->setName('recover.mail');
$app->get('/recover/password/{resetToken}', [PasswordRecoveryController::class, 'recoverPasswordForm'])->setName('recover.password.view');
$app->post('/recover/password/{resetToken}', [PasswordRecoveryController::class, 'recoverPassword'])->setName('recover.password');
$app->get('/login', [LoginController::class, 'show'])->setName('login.show');
$app->post('/login', [LoginController::class, 'login'])->setName('login');
$app->map(['GET', 'POST'], '/logout', [LoginController::class, 'logout'])->setName('logout');
$app->post('/upload', [UploadController::class, 'uploadEndpoint'])->setName('upload');
$app->get('/user/{token}/config/screencloud', [ClientController::class, 'getScreenCloudConfig'])->setName('config.screencloud')->add(CheckForMaintenanceMiddleware::class);
$app->get('/{userCode}/{mediaCode}', [MediaController::class, 'show'])->setName('public');
$app->get('/{userCode}/{mediaCode}/delete/{token}', [MediaController::class, 'show'])->setName('public.delete.show')->add(CheckForMaintenanceMiddleware::class);
$app->post('/{userCode}/{mediaCode}/delete/{token}', [MediaController::class, 'deleteByToken'])->setName('public.delete')->add(CheckForMaintenanceMiddleware::class);
$app->get('/{userCode}/{mediaCode}/raw[.{ext}]', [MediaController::class, 'getRaw'])->setName('public.raw');
$app->get('/{userCode}/{mediaCode}/download', [MediaController::class, 'download'])->setName('public.download');

View file

@ -1,35 +1,24 @@
#!/usr/bin/env php
<?php
require 'vendor/autoload.php';
if (php_sapi_name() !== 'cli') {
((PHP_MAJOR_VERSION >= 7 && PHP_MINOR_VERSION >= 3) || PHP_MAJOR_VERSION > 7) ?: die('Sorry, PHP 7.3 or above is required to run XBackBone.');
if (PHP_SAPI !== 'cli') {
die();
}
function cleanDir($path)
{
$directoryIterator = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
$iteratorIterator = new RecursiveIteratorIterator($directoryIterator, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($iteratorIterator as $file) {
if ($file->getFilename() !== '.gitkeep') {
$file->isDir() ? rmdir($file) : unlink($file);
}
}
}
require __DIR__ . '/../vendor/autoload.php';
$action = isset($argv[1]) ? $argv[1] : 'all';
switch ($action) {
case 'cache':
cleanDir('resources/cache');
cleanDirectory(__DIR__ . '/../resources/cache');
break;
case 'sessions':
cleanDir('resources/sessions');
cleanDirectory(__DIR__ . '/../resources/sessions');
break;
case 'all':
cleanDir('resources/cache');
cleanDir('resources/sessions');
cleanDirectory(__DIR__ . '/../resources/cache');
cleanDirectory(__DIR__ . '/../resources/sessions');
break;
case 'help':
default:

View file

@ -1,89 +1,45 @@
#!/usr/bin/env php
<?php
use App\Database\DB;
require 'vendor/autoload.php';
if (php_sapi_name() !== 'cli') {
die();
((PHP_MAJOR_VERSION >= 7 && PHP_MINOR_VERSION >= 3) || PHP_MAJOR_VERSION > 7) ?: die('Sorry, PHP 7.3 or above is required to run XBackBone.');
if (PHP_SAPI !== 'cli') {
die();
}
$config = include 'config.php';
use App\Database\Migrator;
use DI\ContainerBuilder;
require __DIR__.'/../vendor/autoload.php';
define('BASE_DIR', realpath(__DIR__.'/../').DIRECTORY_SEPARATOR);
$config = include __DIR__.'/../config.php';
if (!$config) {
die('config.php not found. Please create a new one.');
die('config.php not found. Please create a new one.');
}
DB::setDsn($config['db']['connection'] . ':' . $config['db']['dsn'], $config['db']['username'], $config['db']['password']);
chdir(BASE_DIR);
$firstMigrate = false;
if (!file_exists($config['db']['dsn']) && DB::driver() === 'sqlite') {
touch($config['db']['dsn']);
$firstMigrate = true;
}
$builder = new ContainerBuilder();
$builder->addDefinitions(BASE_DIR.'bootstrap/container.php');
try {
DB::query('SELECT 1 FROM `migrations` LIMIT 1');
} catch (PDOException $exception) {
$firstMigrate = true;
}
$container = $builder->build();
$container->set('config', $config);
echo 'Connected.' . PHP_EOL;
$db = $container->get('database');
if ($firstMigrate) {
echo 'Creating migrations table...' . PHP_EOL;
DB::raw()->exec(file_get_contents('resources/schemas/migrations.sql'));
}
$files = glob('resources/schemas/' . DB::driver() . '/*.sql');
$names = array_map(function ($path) {
return basename($path);
}, $files);
$in = str_repeat('?, ', count($names) - 1) . '?';
$inMigrationsTable = DB::query("SELECT * FROM `migrations` WHERE `name` IN ($in)", $names)->fetchAll();
foreach ($files as $file) {
$continue = false;
$exists = false;
foreach ($inMigrationsTable as $migration) {
if (basename($file) === $migration->name && $migration->migrated) {
$continue = true;
break;
} elseif (basename($file) === $migration->name && !$migration->migrated) {
$exists = true;
break;
}
}
if ($continue) continue;
$sql = file_get_contents($file);
try {
DB::raw()->exec($sql);
if (!$exists) {
DB::query('INSERT INTO `migrations` VALUES (?,?)', [basename($file), 1]);
} else {
DB::query('UPDATE `migrations` SET `migrated`=? WHERE `name`=?', [1, basename($file)]);
}
echo "Migrated '$file'" . PHP_EOL;
} catch (PDOException $exception) {
if (!$exists) {
DB::query('INSERT INTO `migrations` VALUES (?,?)', [basename($file), 0]);
}
echo "Error migrating '$file' (" . $exception->getMessage() . ')' . PHP_EOL;
echo $exception->getTraceAsString() . PHP_EOL;
}
}
$migrator = new Migrator($db, 'resources/schemas');
$migrator->migrate();
$migrator->reSyncQuotas($container->get('storage'));
if (isset($argv[1]) && $argv[1] === '--install') {
DB::query("INSERT INTO `users` (`email`, `username`, `password`, `is_admin`, `user_code`) VALUES ('admin@example.com', 'admin', ?, 1, ?)", [password_hash('admin', PASSWORD_DEFAULT), substr(md5(microtime()), rand(0, 26), 5)]);
$db->query("INSERT INTO `users` (`email`, `username`, `password`, `is_admin`, `user_code`) VALUES ('admin@example.com', 'admin', ?, 1, ?)", [password_hash('admin', PASSWORD_DEFAULT), humanRandomString(5)]);
}
echo 'Done.' . PHP_EOL;
if (file_exists(__DIR__.'/../install') && (!isset($config['debug']) || !$config['debug'])) {
removeDirectory(__DIR__.'/../install');
}
echo 'If you are upgrading from a previous version, please run a "php bin' . DIRECTORY_SEPARATOR . 'clean".'.PHP_EOL;
echo 'Done.'.PHP_EOL;
exit(0);

View file

@ -1,29 +0,0 @@
#!/usr/bin/env php
<?php
require 'vendor/autoload.php';
if (php_sapi_name() !== 'cli') {
die();
}
$json = json_decode(file_get_contents('https://bootswatch.com/api/4.json'));
if (!isset($argv[1])) {
echo 'Usage: php bin\\theme <theme-name>' . PHP_EOL;
echo 'Here a list of available Bootswatch themes:' . PHP_EOL;
}
foreach ($json->themes as $theme) {
if (isset($argv[1]) && strtolower($argv[1]) === strtolower($theme->name)) {
file_put_contents('static/bootstrap/css/bootstrap.min.css', file_get_contents($theme->cssMin));
echo "Installed theme {$theme->name}." . PHP_EOL;
break;
}
if (!isset($argv[1])) {
echo " - {$theme->name} ({$theme->description})[Preview: {$theme->preview}]" . PHP_EOL;
}
}
exit(0);

View file

@ -1,83 +1,121 @@
<?php
use App\Database\DB;
use App\Exceptions\Handlers\AppErrorHandler;
use App\Exceptions\Handlers\Renderers\HtmlErrorRenderer;
use App\Factories\ViewFactory;
use App\Middleware\InjectMiddleware;
use App\Middleware\LangMiddleware;
use App\Middleware\RememberMiddleware;
use App\Web\Session;
use App\Web\View;
use DI\Bridge\Slim\Bridge;
use DI\ContainerBuilder;
use Psr\Container\ContainerInterface as Container;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use function DI\factory;
use function DI\get;
if (!file_exists(CONFIG_FILE) && is_dir(BASE_DIR.'install/')) {
header('Location: ./install/');
exit();
}
if (!file_exists(CONFIG_FILE) && !is_dir(BASE_DIR.'install/')) {
exit('Cannot find the config file.');
}
// Load the config
$config = array_replace_recursive([
'app_name' => 'XBackBone',
'base_url' => isset($_SERVER['HTTPS']) ? 'https://' . $_SERVER['HTTP_HOST'] : 'http://' . $_SERVER['HTTP_HOST'],
'storage_dir' => 'storage',
'debug' => false,
'db' => [
'connection' => 'sqlite',
'dsn' => 'resources/database/xbackbone.db',
'username' => null,
'password' => null,
],
], require 'config.php');
'app_name' => 'XBackBone',
'base_url' => isSecure() ? 'https://'.$_SERVER['HTTP_HOST'] : 'http://'.$_SERVER['HTTP_HOST'],
'debug' => false,
'maintenance' => false,
'db' => [
'connection' => 'sqlite',
'dsn' => BASE_DIR.implode(DIRECTORY_SEPARATOR, ['resources', 'database', 'xbackbone.db']),
'username' => null,
'password' => null,
],
'storage' => [
'driver' => 'local',
'path' => realpath(__DIR__.'/').DIRECTORY_SEPARATOR.'storage',
],
'ldap' => [
'enabled' => false,
'host' => null,
'port' => null,
'base_domain' => null,
'user_domain' => null,
],
], require CONFIG_FILE);
// Set flight parameters
Flight::set('flight.base_url', $config['base_url']);
Flight::set('flight.log_errors', false);
Flight::set('config', $config);
$builder = new ContainerBuilder();
// Set the database dsn
DB::setDsn($config['db']['connection'] . ':' . $config['db']['dsn'], $config['db']['username'], $config['db']['password']);
if (!$config['debug']) {
$builder->enableCompilation(BASE_DIR.'/resources/cache/di');
$builder->writeProxiesToFile(true, BASE_DIR.'/resources/cache/di');
}
// Register the Twig instance
Flight::register('view', 'Twig_Environment',
[new Twig_Loader_Filesystem('resources/templates'),
[
'cache' => 'resources/cache',
'autoescape' => 'html',
'debug' => $config['debug'],
'auto_reload' => $config['debug'],
]
]
);
$builder->addDefinitions([
Session::class => factory(function () {
return new Session('xbackbone_session', BASE_DIR.'resources/sessions');
}),
'session' => get(Session::class),
View::class => factory(function (Container $container) {
return ViewFactory::createAppInstance($container);
}),
'view' => get(View::class),
]);
// Redirect back helper
Flight::register('redirectBack', function () {
Flight::redirect(Flight::request()->referrer);
$builder->addDefinitions(__DIR__.'/container.php');
global $app;
$app = Bridge::create($builder->build());
$app->getContainer()->set('config', $config);
$app->setBasePath(parse_url($config['base_url'], PHP_URL_PATH) ?: '');
if (!$config['debug']) {
$app->getRouteCollector()->setCacheFile(BASE_DIR.'resources/cache/routes.cache.php');
}
$app->add(InjectMiddleware::class);
$app->add(LangMiddleware::class);
$app->add(RememberMiddleware::class);
// Permanently redirect paths with a trailing slash to their non-trailing counterpart
$app->add(function (Request $request, RequestHandler $handler) use (&$app, &$config) {
$uri = $request->getUri();
$path = $uri->getPath();
if ($path !== $app->getBasePath().'/' && substr($path, -1) === '/') {
// permanently redirect paths with a trailing slash
// to their non-trailing counterpart
$uri = $uri->withPath(substr($path, 0, -1));
if ($request->getMethod() === 'GET') {
return $app->getResponseFactory()
->createResponse(301)
->withHeader('Location', (string) $uri);
} else {
$request = $request->withUri($uri);
}
}
return $handler->handle($request);
});
// Map the render call to the Twig view instance
Flight::map('render', function (string $template, array $data = []) use (&$config) {
Flight::view()->addGlobal('config', $config);
Flight::view()->addGlobal('request', Flight::request());
Flight::view()->addGlobal('alerts', App\Web\Session::getAlert());
Flight::view()->addGlobal('session', App\Web\Session::all());
Flight::view()->addGlobal('PLATFORM_VERSION', PLATFORM_VERSION);
Flight::view()->display($template, $data);
});
$app->addRoutingMiddleware();
// The application error handler
Flight::map('error', function (Exception $exception) {
if ($exception instanceof \App\Exceptions\AuthenticationException) {
Flight::redirect('/login');
return;
}
// Configure the error handler
$errorHandler = new AppErrorHandler($app->getCallableResolver(), $app->getResponseFactory());
$errorHandler->registerErrorRenderer('text/html', HtmlErrorRenderer::class);
if ($exception instanceof \App\Exceptions\UnauthorizedException) {
Flight::response()->status(403);
Flight::render('errors/403.twig');
return;
}
if ($exception instanceof \App\Exceptions\NotFoundException) {
Flight::response()->status(404);
Flight::render('errors/404.twig');
return;
}
Flight::response()->status(500);
\App\Web\Log::critical('Fatal error during app execution', [$exception->getTraceAsString()]);
Flight::render('errors/500.twig', ['exception' => $exception]);
});
Flight::map('notFound', function () {
Flight::render('errors/404.twig');
});
// Add Error Middleware
$errorMiddleware = $app->addErrorMiddleware($config['debug'], false, true);
$errorMiddleware->setDefaultErrorHandler($errorHandler);
// Load the application routes
require 'app/routes.php';
require BASE_DIR.'app/routes.php';
return $app;

113
bootstrap/container.php Normal file
View file

@ -0,0 +1,113 @@
<?php
use App\Database\DB;
use App\Web\Lang;
use Aws\S3\S3Client;
use League\Flysystem\Cached\CachedAdapter;
use League\Flysystem\Cached\Storage\Adapter;
use function DI\factory;
use function DI\get;
use Google\Cloud\Storage\StorageClient;
use League\Flysystem\Adapter\Ftp as FtpAdapter;
use League\Flysystem\Adapter\Local;
use League\Flysystem\AwsS3v3\AwsS3Adapter;
use League\Flysystem\AzureBlobStorage\AzureBlobStorageAdapter;
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
use League\Flysystem\Filesystem;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use Psr\Container\ContainerInterface as Container;
use Spatie\Dropbox\Client as DropboxClient;
use Spatie\FlysystemDropbox\DropboxAdapter;
use Superbalist\Flysystem\GoogleStorage\GoogleStorageAdapter;
return [
Logger::class => factory(function () {
$logger = new Logger('app');
$streamHandler = new RotatingFileHandler(BASE_DIR.'logs/log.txt', 10, Logger::DEBUG);
$lineFormatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n", 'Y-m-d H:i:s');
$lineFormatter->includeStacktraces(true);
$streamHandler->setFormatter($lineFormatter);
$logger->pushHandler($streamHandler);
return $logger;
}),
'logger' => get(Logger::class),
DB::class => factory(function (Container $container) {
$config = $container->get('config');
return new DB(dsnFromConfig($config), $config['db']['username'], $config['db']['password']);
}),
'database' => get(DB::class),
Filesystem::class => factory(function (Container $container) {
$config = $container->get('config');
$driver = $config['storage']['driver'];
if ($driver === 'local') {
return new Filesystem(new Local($config['storage']['path']));
} elseif ($driver === 's3') {
$client = new S3Client([
'credentials' => [
'key' => $config['storage']['key'],
'secret' => $config['storage']['secret'],
],
'region' => $config['storage']['region'],
'endpoint' => $config['storage']['endpoint'],
'version' => 'latest',
'use_path_style_endpoint' => $config['storage']['use_path_style_endpoint'] ?? false,
'@http' => ['stream' => true],
]);
$adapter = new AwsS3Adapter($client, $config['storage']['bucket'], $config['storage']['path']);
} elseif ($driver === 'dropbox') {
$client = new DropboxClient($config['storage']['token']);
$adapter = new DropboxAdapter($client);
} elseif ($driver === 'ftp') {
$adapter = new FtpAdapter([
'host' => $config['storage']['host'],
'username' => $config['storage']['username'],
'password' => $config['storage']['password'],
'port' => $config['storage']['port'],
'root' => $config['storage']['path'],
'passive' => $config['storage']['passive'],
'ssl' => $config['storage']['ssl'],
'timeout' => 30,
]);
} elseif ($driver === 'google-cloud') {
$client = new StorageClient([
'projectId' => $config['storage']['project_id'],
'keyFilePath' => $config['storage']['key_path'],
]);
$adapter = new GoogleStorageAdapter($client, $client->bucket($config['storage']['bucket']));
} elseif ($driver === 'azure') {
$client = BlobRestProxy::createBlobService(
sprintf(
'DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s;',
$config['storage']['account_name'],
$config['storage']['account_key']
)
);
$adapter = new AzureBlobStorageAdapter($client, $config['storage']['container_name']);
} else {
throw new InvalidArgumentException('The driver specified is not supported.');
}
$cache = new Adapter(new Local(BASE_DIR.'resources/cache/fs'), 'file', 300); // 5min
return new Filesystem(new CachedAdapter($adapter, $cache));
}),
'storage' => get(Filesystem::class),
Lang::class => factory(function () {
return Lang::build(Lang::recognize(), BASE_DIR.'resources/lang/');
}),
'lang' => get(Lang::class),
];

View file

@ -1,28 +1,59 @@
{
"name": "sergix44/xbackbone",
"version": "1.3",
"license": "AGPL-3.0-only",
"version": "3.7.0",
"description": "A lightweight ShareX PHP backend",
"type": "project",
"require": {
"mikecao/flight": "^1.3",
"league/flysystem": "^1.0.45",
"twig/twig": "~2.0",
"monolog/monolog": "^1.23",
"php": ">=7.1",
"ext-json": "*",
"php": ">=7.3",
"ext-filter": "*",
"ext-gd": "*",
"ext-pdo": "*"
"ext-intl": "*",
"ext-json": "*",
"ext-pdo": "*",
"ext-zip": "*",
"erusev/parsedown": "^1.7",
"intervention/image": "^2.6",
"league/flysystem": "^1.1.4",
"league/flysystem-aws-s3-v3": "^1.0",
"league/flysystem-cached-adapter": "^1.1",
"maennchen/zipstream-php": "^2.0",
"monolog/monolog": "^1.23",
"php-di/slim-bridge": "^3.0",
"sapphirecat/slim4-http-interop-adapter": "^1.0",
"slim/psr7": "^1.5",
"slim/slim": "^4.0",
"spatie/flysystem-dropbox": "^1.0",
"superbalist/flysystem-google-storage": "^7.2",
"twig/twig": "^2.14"
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"platform": {
"php": "7.3.33"
}
},
"autoload": {
"files": [
"app/helpers.php"
],
"psr-4": {
"App\\": "app/"
}
},
"extra": {
"violinist": {
"allow_updates_beyond_constraint": 1,
"blacklist": [],
"update_with_dependencies": 1
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"require-dev": {
"roave/security-advisories": "dev-latest",
"phpstan/phpstan": "^0.11.5",
"phpunit/phpunit": "^9.0",
"symfony/dom-crawler": "^4.4"
}
}

6936
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,15 @@
<?php
return [
'base_url' => 'http://localhost',
'storage_dir' => 'storage',
'db' => [
'connection' => 'sqlite',
'dsn' => 'resources/database/xbackbone.db',
'username' => null,
'password' => null,
]
'base_url' => 'https://localhost', // no trailing slash
'db' => [
'connection' => 'sqlite',
'dsn' => realpath(__DIR__).'/resources/database/xbackbone.db',
'username' => null,
'password' => null,
],
'storage' => [
'driver' => 'local',
'path' => realpath(__DIR__).'/storage',
],
];

1
docs/CNAME Normal file
View file

@ -0,0 +1 @@
xbackbone.app

8
docs/_config.yml Normal file
View file

@ -0,0 +1,8 @@
remote_theme: pmarsceill/just-the-docs
logo: "img/xbackbone.png"
title: "XBackBone"
aux_links:
"Star me on GitHub":
- "//github.com/SergiX44/XBackBone"
footer_content: "Copyright &copy; 2020 Sergio Brighenti. Distributed under <a href=\"https://github.com/SergiX44/XBackBone/blob/master/LICENSE\">AGPL v3.0 license.</a>"
ga_tracking: UA-55470316-7

View file

@ -0,0 +1 @@
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-1835087833720665" crossorigin="anonymous"></script>

1
docs/ads.txt Normal file
View file

@ -0,0 +1 @@
google.com, pub-1835087833720665, DIRECT, f08c47fec0942fa0

31
docs/basic_usage.md Normal file
View file

@ -0,0 +1,31 @@
---
layout: default
title: Basic Usage
nav_order: 6
---
# Basic Usage
## Users
Some functions that every user that use XBackBone should know:
+ You can hide/publish every upload, once it's hidden, it's visible only by yourself.
+ You can download your ShareX configuration file from your profile page.
+ You can upload files directly from the upload page.
+ You can change you upload token anytime.
+ You can export all your uploads in a single zip files in your profile page.
+ (`v3.1`+) With the right click on the uploaded media, you can select them, and then remove them in bulk.
+ (`v3.1`+) You can add additional tag to your uploads, clicking on the **+** button on the media.
+ (`v3.1`+) You can add you can delete tags, by right-click on them.
+ (`v3.1`+) You can choose in your profile options if hide the uploads by default.
+ (`v3.1`+) You can choose in your profile options if always copy the raw url (from the web interface).
## Administrator
In addition, from the system page, and administrator can:
+ Perform maintenance actions.
+ Change theme.
+ Force system languages.
+ Enabled recaptcha.
+ Enable user disk quota.
+ ... and more.
In the users page, it can add/remove users, and edit per user configurations.

451
docs/changelog.md Normal file
View file

@ -0,0 +1,451 @@
---
layout: default
title: Changelog
nav_order: 9
---
# Changelog
## [3.7.0] - 2024-01-14
### Added
- Added support for vanity urls.
- Added KDE integration for linux script.
### Changed
- Updated translations.
- File preview is now clickable to open the file.
### Fixed
- Fixes for LDAP authentication.
## [3.6.3] - 2023-05-27
### Fixed
- Fix LDAP for php >= 8.1
## [3.6.2] - 2023-05-24
### Changed
- Support for PHP 8.2
### Removed
- Azure blob storage driver
## [3.6.1] - 2022-11-27
### Changed
- Upgraded dependencies
- Updated translations
### Fixed
- Fixed error in export data (#499)
- Fixed issues with reverse proxies (#495)
- Fixed duplicated twitter tags (#496)
## [3.6.0] - 2022-06-20
### Changed
- Improved embedding on discord of large videos.
- Releases are now compressed for faster downloads
- Updated translations
### Fixed
- Fixed deprecation notices on php >= 8
- Fixed embed UA for Discord.
- Fixed error with post_max_size = 0
### Removed
- Support for php 7.2
## [3.5.1] - 2021-10-22
### Changed
- Fixed embed UA for Discord.
- Updated translations.
## [3.5.0] - 2021-09-05
### Added
- Support for theme-park.dev themes.
- Updated translations.
### Fixed
- Wrong css when reapplying the default theme.
### Removed
- Dropped theme cli command.
## [3.4.1] - 2021-08-11
### Added
- Toggle to disable embeds.
### Changed
- Raw url copying now contains also the file extension.
## [3.4.0] - 2021-08-01
### Added
- Added image support for OG for Discord only.
### Changed
- Updated translations.
- Dropped support for PHP 7.1
### Fixed
- Fixed possible XSS and CSRF attacks.
## [3.3.5] - 2021-04-25
### Fixed
- Removed OG integration for discord.
### Changed
- Updated translations.
## [3.3.4] - 2021-03-07
### Added
- Login failed logging.
- User identifier option for LDAP configurations.
### Fixed
- Fixed open graph meta tags for Discord.
- Fixed custom html tags are not displayed back in the admin setting.
- Fixed python plugin for newer version of Screencloud.
- Fixed accented chars in email subject.
- Fixed error on PHP 8.
## [3.3.3] - 2020-11-13
### Fixed
- Fixed issue with responsive menu on mobile.
## [3.3.2] - 2020-11-12
### Fixed
- Fixed switch not works for the first time for normal users.
## [3.3.1] - 2020-11-12
### Fixed
- Formatting error on the check for updates.
- Fixed default view for normal users.
## [3.3.0] - 2020-11-12
### Added
- Enabled PHP 8 support.
- Added Screencloud client support (https://screencloud.net).
- OpenGraph image tag (issue #269).
- Start adding unit tests.
### Changed
- The list mode is now available also for non-admin accounts (issue #226).
### Fixed
- Linux script strange response code in headless mode.
### Removed
- Dropped Telegram share button.
## [3.2.0] - 2020-09-05
### Added
- Added support to use Azure Blob Storage account as storage location.
- Support for other S3-compatible storage endpoint.
- Line number when showing text files.
### Fixed
- S3 driver file streaming not working properly.
- Fixed Slack image preview.
## [3.1.4] - 2020-04-13
### Changed
- Now the migrate command resync the system quota for each user.
### Fixed
- Fixed error with the migrate command.
## [3.1.3] - 2020-04-13
### Changed
- Added changelog page.
- Updated translations.
## [3.1.2] - 2020-04-12
### Changed
- Improved installer storage checks.
### Fixed
- Fixed upload table lost when updating very old instances.
## [3.1.1] - 2020-04-11
### Fixed
- Fixed error during a fresh installation with sqlite.
## [3.1] - 2020-04-10
### Added
- Added tagging system (add, delete, search of tagged files).
- Added basic media auto-tagging on upload.
- Added registration system.
- Added password recovery system.
- Added ability to export all media of an account.
- Added ability to choose between default and raw url on copy.
- Added hide by default option.
- Added user disk quota.
- Added reCAPTCHA login protection.
- Added bulk delete.
- Added account clean function.
- Added user disk quota system.
- Added notification option on account create.
- Added LDAP authentication.
### Changed
- The theme is now re-applied after every system update.
- Updated system settings page.
- Updated translations.
- Improved grid layout.
### Fixed
- Fixed bug html files raws are rendered in a browser.
- Fixes and improvements.
## [3.0.2] - 2019-12-04
### Changed
- Updated translations.
### Fixed
- Fixed error with migrate command.
## [3.0.1] - 2019-11-25
### Changed
- Small installer update.
### Fixed
- Fixed error with older mysql versions.
- Fixed config is compiled with the di container.
## [3.0] - 2019-11-23
### Added
- Added web upload.
- Added ability to add custom HTML in \<head\> tag.
- Added ability to show a preview of PDF files.
- Added remember me functionality.
- Added delete button on the preview page if the user is logged in.
- New project icon (by [@SerenaItalia](https://www.deviantart.com/serenaitalia)).
- The linux script can be used on headless systems.
- Raw URL now accept file extensions.
- Implemented SameSite XSS protection.
### Changed
- Upgraded from Slim3 to Slim 4.
- Replaced videojs player with Plyr.
- Improved installer.
- Improved thumbnail generation.
- Small fixes and improvements.
## [2.6.6] - 2019-10-23
### Added
- Ability to choose between releases and prereleases with the web updater.
### Changed
- Updated translations.
## [2.6.5] - 2019-09-17
### Changed
- Changed color to some buttons to address visibility with some themes.
### Fixed
- Fixed error after orphaned files removal #74.
- Fixed update password not correctly removed from log files (#74).
## [2.6.4] - 2019-09-15
### Added
- Filter on displayable images.
### Changed
- The generated random strings are now more human readable.
### Fixed
- Fixed during upload error on php compiled for 32 bit.
- Fixed icons on the installer page.
## [2.6.3] - 2019-09-14
### Fixed
- Fixed #67.
- Fixed bad preload statement.
- Fixed wrong redirect after install in subdirs.
## [2.6.2] - 2019-09-06
### Added
- Added method for cache busting when updating/change theme.
- Added russian translation from [Weblate](https://hosted.weblate.org/projects/xbackbone/xbackbone/).
### Changed
- Changed background default color.
- Use the Font Awesome web font for better performances.
## [2.6.1] - 2019-09-04
### Added
- Added alert if required extensions are not loaded.
### Changed
- Improved shell commands.
- Updated translations.
### Fixed
- Fixed bad redirects on the web installer (#62).
- Fixed login page with dark themes.
## [2.6] - 2019-08-20
### Added
- Added support to use AWS S3, Google Cloud Storage, Dropbox and FTP(s) accounts as storage location.
- Added german and norwegian translations from [Weblate](https://hosted.weblate.org/projects/xbackbone/xbackbone/).
- Added ability to force system language.
### Changed
- Improved lang detection.
### Fixed
- Fixed missing icon.
## [2.5.3] - 2019-05-12
### Changed
- Improved exception stacktrace logging.
### Fixed
- Fixed bad css loading on Firefox (#35).
- Fixed wrong style for publish/unpublish button.
## [2.5.2] - 2019-05-09
### Added
- Added preloading for some resources to improve performances.
- Added check for block execution on EOL and unsupported PHP versions.
### Changed
- Improved session handling.
- Other minor improvements.
### Fixed
- Fixed telegram share not working.
- Fix for big text file now are not rendered in the browser.
## [2.5.1] - 2019-04-10
### Changed
- Improved HTTP partial content implementation for large files.
### Fixed
- Fixed bad redirect if the theme folder is not writable. (#27)
## [2.5] - 2019-02-10
### Added
- Added partial content implementation (stream seeking on chromium based browsers).
- **[BETA]** Added self update feature.
- Added project favicon.
### Changed
- Updated project license to [AGPL v3.0](https://choosealicense.com/licenses/agpl-3.0/) (now releases ships with the new license).
- Improved video.js alignment with large videos.
- Optimized output zip release size.
- Templates cleanup and optimizations.
- Improved error handling.
## [2.4.1] - 2019-01-24
### Fixed
- Fixed error message when the file is too large. (#15)
- Fixed button alignment.
## [2.4] - 2019-01-22
### Added
- Added function to remove orphaned files.
- Multiple uploads sorting methods.
- Switch between tab and gallery mode using an admin account.
- Search in uploads.
### Changed
- Updated js dependencies.
- Internal refactoring and improvements
## [2.3.1] - 2018-12-09
### Added
- Added checks during the installation wizard.
- cURL and Wget can now directly download the file.
### Fixed
- Fixed english language.
- Fixed forced background with dark themes.
## [2.3] - 2018-11-30
### Added
- Added overlay on user gallery images.
- Added linux script to allow uploads from linux screenshot tools.
- Enable audio player with video.js.
- Font Awesome icon match the single file mime-type.
### Changed
- Improved image scaling in user gallery.
- Video and audio now starts with volume at 50%.
- Minor layout fixes.
### Fixed
- Fixed IT translation.
## [2.2] - 2018-11-20
### Added
- Added multi-language support.
### Fixed
- Improved routing.
- Minor improvements and bug fixes.
- Fixed HTTP/2 push is resetting the current session.
## [2.1] - 2018-11-20
### Added
- Added video.js support.
- Allow e-mail login.
- Support for ShareX deletion URL.
### Changed
- Improved theme style.
- Improved page redirecting.
### Fixed
- Fixed HTTP/2 push preload.
## [2.0] - 2018-11-13
### Added
- Added install wizard (using the CLI is no longer required).
- Added used space indicator per user.
- Allow discord bot to display the preview.
- Theme switcher on the web UI.
- MySQL support.
### Changed
- Migrated from Flight to Slim 3 framework.
- Improvements under the hood.
## [1.3] - 2018-10-14
### Added
- Added command to switch between bootswatch.com themes.
- Added popover to write the telegram message when sharing.
- Allow Facebook bots to display the preview.
### Changed
- Packaging improvements.
- Updated some dependencies.
## [1.2] - 2018-05-01
### Added
- Added auto config generator for ShareX.
- Show upload file size on the dashboard.
### Changed
- Previews are now scaled for better page load.
### Removed
- Removed HTTP2 push from the dashboard to improve loading time.
### Fixed
- Fixed insert for admin user (running `php bin\migrate --install`).
## [1.1] - 2018-04-28
### Added
- Added logging.
- Added share to Telegram.
### Changed
- Improved migrate system.
- Updated Bootstrap theme.
### Fixed
- Fixed back to top when click delete or publish/unpublish.
- Login redirect back to the requested page.
## [1.0] - 2018-04-28
### Added
- Initial version.

48
docs/clients.md Normal file
View file

@ -0,0 +1,48 @@
---
layout: default
title: Client Configuration
nav_order: 5
---
# Clients Configuration
## ShareX (Windows)
Once you are logged in, just go in your profile settings and download the ShareX config file for your account.
## Screencloud (Windows, Mac and Linux)
Once you are logged in, go in your profile account and click on the Screencloud button.
Now open Screencloud, open "Preferences" > "Online Services" tab > click "More Services" > and "Install from URL"
and paste the URL copied from XBackBone, and all should work out-of-the-box.
If for whatever reason you need to change the instance url or the token, just edit the settings of the XBackBone plugin.
## MagicCap (Mac and Linux)
MagicCap supports the same file format used by ShareX.
Just download the ShareX config file from your profile, and then on MagicCap open the Preferences > Uploader settings and choose ShareX.
Set the path to the file you have downloaded, and you are good to go!
## uPic (Mac)
This tool does not support plugins or custom configuration, but you can configure it manually:
In preferences, you should add "Custom" host and configure it as follows:
- **API URL:** Your instance upload url, like `http://example.com/upload`
- **Request method:** POST
- **File field:** file
- **URL Path:** ["url"]
- In "Other fields", in the body section, you should add the field `token`, with your upload token.
- In "Other fields", in the headers section, you should add the field `Content-Type`, with the value `application/x-www-form-urlencoded`.
## Bash Script (Linux, Mac, WSL)
XBackBone can generate a script that allows you to share an item from any tool, even headless servers:
+ Login into your account
+ Navigate to your profile and download the Linux script for your account.
+ Place the script where you want (ex. in your user home: `/home/<username>`).
+ Add execution permissions (`chmod +x xbackbone_uploader_XXX.sh`)
+ Run the script for the first time to create the desktop entry: `./xbackbone_uploader_XXX.sh -desktop-entry`.
Now, to upload a media, just use the right click on the file > "Open with ..." > search XBackBone Uploader (XXX) in the app list.
You can use this feature in combination with tools like [Flameshot](https://github.com/lupoDharkael/flameshot), just use the "Open with ..." button once you have done the screenshot.
The script requires `xclip`, `curl`, and `notify-send` on a desktop distribution.
*Note: XXX is the username of your XBackBone account.*

20
docs/common_issues.md Normal file
View file

@ -0,0 +1,20 @@
---
layout: default
title: Common Issues
nav_order: 7
---
# Common Issues
### Error 404 after installation
If you have Apache web server, check if it's reading the file `.htaccess` and the module `mod_rewrite` is enabled.
<hr>
### [Discord, Telegram, ...] is not showing the image/video preview of the link.
If you use Cloudflare, check if the setting that blocks access to bots is active. If enabled, the bots of the respective platforms will not be able to access to download the preview.
<hr>
### How to increase the upload max file size?
Increase the `post_max_size` and `upload_max_filesize` in your `php.ini`.

214
docs/configuration.md Normal file
View file

@ -0,0 +1,214 @@
---
layout: default
title: Configuration
nav_order: 3
---
# Configuration
## Web Server
*Apache need the `mod_rewrite` extension to make XBackBone work properly*.
If you do not use Apache, or the Apache `.htaccess` is not enabled, set your web server so that the `static/` folder is the only one accessible from the outside, otherwise even private uploads and logs will be accessible!
If you are using NGINX, you can find an example configuration [`nginx.conf`](https://github.com/SergiX44/XBackBone/blob/master/nginx.conf) in the project repository.
## Maintenance Mode
Maintenance mode is automatically enabled during an upgrade using the upgrade manager. You can activate it manually by editing the `config.php`, and adding this line:
```php
return array(
...
'maintenance' => true,
);
```
## Database support
Currently, is supported `MySQL/MariaDB` and `SQLite3`.
For big installations, `MySQL/MariaDB` is recommended.
Example config:
```php
return array(
...,
'db' => array (
'connection' => 'mysql', // sqlite or mysql
'dsn' => 'host=localhost;port=3306;dbname=xbackbone', // the path to db, if sqlite
'username' => 'xbackbone', // null, if sqlite
'password' => 's3cr3t', // null, if sqlite
),
);
```
## LDAP Authentication
Since the release 3.1, the LDAP integration can be configured.
Edit the `config.php`, and add the following lines:
This configuration requires anonymous LDAP access
```php
return array(
...
'ldap' => array(
'enabled' => true, // enable it
'host' => 'ad.example.com', // set the ldap host
'port' => 389, // ldap port
'base_domain' => 'dc=example,dc=com', // the base_dn string
'user_domain' => 'ou=Users', // the user dn string
'rdn_attribute' => 'uid=', // the attribute to identify the user
),
);
```
The following configuration snippet enables authenticated LDAP user lookups
```php
return array(
...
'ldap' => array(
'enabled' => true, // enable it
'schema' => 'ldap', // use 'ldap' or 'ldaps' Default is 'ldap'
'host' => 'ad.example.com', // set the ldap host
'port' => 389, // ldap port
'base_domain' => 'dc=example,dc=com', // the base_dn string
'search_filter' => '(&(objectClass=user)(sAMAccountName=????))', // ???? is replaced with user provided username
'rdn_attribute' => 'sAMAccountName', // the attribute to use as username
'service_account_dn' => 'cn=xbackbone,cn=Users,dc=example,dc=com', // LDAP Service Account Full DN
'service_account_password' => 'examplepassword',
),
);
```
Enabling LDAP over TLS. Make sure to update port number. Merge with your current LDAP configuration.
```php
return array(
...
'ldap' => array(
'schema' => 'ldaps', //defaults to 'ldap'
'port' => 636,
),
);
```
Enabling StartTLS upgrade. Merge with your current LDAP configuration.
```php
return array(
...
'ldap' => array(
...
'useStartTLS' => true, //defaults to false
),
);
```
The 'schema' => 'ldaps' and 'useStartTLS'=> true configuration directives are mutually exclusive. Do no use them together.
By activating this function, it will not be possible for users logging in via LDAP to reset the password from the application (for obvious reasons), and it will also be possible to bring existing users under LDAP authentication.
## Storage drivers
XBackBone supports these storage drivers (with some configuration examples):
+ Local Storage (default)
```php
return array(
...
'storage' => array (
'driver' => 'local',
'path' => '/path/to/storage/folder',
),
);
```
+ Amazon S3
```php
return array(
...
'storage' => array (
'driver' => 's3',
'key' => 'the-key',
'secret' => 'the-secret',
'region' => 'the-region',
'bucket' => 'bucket-name',
'path' => 'optional/path/prefix',
),
);
```
For any filesystem S3-compatible, it's possible to specify an `endpoint` (for i.e. Minio)
```php
return array(
...
'storage' => array (
'driver' => 's3',
'endpoint' => 'my-custom-endpoint',
'key' => 'the-key',
'secret' => 'the-secret',
'region' => 'the-region',
'bucket' => 'bucket-name',
'path' => 'optional/path/prefix',
),
);
```
+ Dropbox
```php
return array(
...
'storage' => array (
'driver' => 'dropbox',
'token' => 'the-token',
),
);
```
+ FTP(s)
```php
return array(
...
'storage' => array (
'driver' => 'ftp',
'host' => 'ftp.example.com',
'port' => 21,
'username' => 'the-username',
'password' => 'the-password',
'path' => 'the/prefix/path/',
'passive' => true/false,
'ssl' => true/false,
),
);
```
+ Google Cloud Storage
```php
return array(
...
'storage' => array (
'driver' => 'google-cloud',
'project_id' => 'the-project-id',
'key_path' => 'the-key-path',
'bucket' => 'bucket-name',
),
);
```
## Changing themes
XBackBone supports all [bootswatch.com](https://bootswatch.com/) themes.
From the web UI:
+ Navigate to the web interface as admin -> System Menu -> Choose a theme from the dropdown.
From the CLI:
+ Run the command `php bin/theme` to see the available themes.
+ Use the same command with the argument name (`php bin/theme <THEME-NAME>`) to choose a theme.
+ If you want to revert back to the original bootstrap theme, run the command `php bin/theme default`.
*Clear the browser cache once you have applied.*
## Change app install name
Add to the `config.php` file an array element like this:
```php
return array(
'app_name' => 'This line will overwrite "XBackBone"',
...
);
```

BIN
docs/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
docs/img/xbackbone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

57
docs/index.md Normal file
View file

@ -0,0 +1,57 @@
---
layout: default
title: Home
nav_order: 1
---
<p class="text-center">
<img src="img/xbackbone.png" width="400px">
</p>
XBackBone is a simple and lightweight PHP file manager that support the instant sharing tool ShareX and *NIX systems. It supports uploading and displaying images, GIF, video, code, formatted text, pdf, and file downloading and uploading. Also have a web UI with multi user management, media gallery and search support.
{: .fs-5 .fw-300 }
<p class="text-center">
<a href="https://github.com/SergiX44/XBackBone/releases/latest" class="btn btn-green">Download</a>
<a href="https://github.com/SergiX44/XBackBone" class="btn btn-blue">GitHub</a>
<a href="sponsor.html" class="btn btn-purple" style="background-color: #e7af06; background-image: linear-gradient(#f7d12e, #e7af06);">Sponsor</a>
</p>
## Main Features
+ Multiple clients supported: ShareX, Screencloud, uPic, ...
+ Config generator for ShareX, Screencloud.
+ Low memory footprint.
+ Multiple backends support: Local storage, AWS S3, Google Cloud, Azure Blob Storage, Dropbox, FTP(s).
+ Web file upload.
+ Code uploads syntax highlighting.
+ Video and audio uploads webplayer.
+ PDF viewer.
+ Files preview page.
+ Bootswatch themes support.
+ Responsive theme for mobile use.
+ Multi language support.
+ User management, multi user features, roles and disk quota.
+ Public and private uploads.
+ Logging system.
+ Share to Telegram.
+ Linux supported via a per-user custom generated script (server and desktop).
+ Direct downloads using curl or wget commands.
+ Direct images links support on Discord, Telegram, Facebook, etc.
+ System updates without FTP or CLI.
+ Easy web installer.
+ LDAP authentication.
+ Registration system.
+ Automatic uploads tagging system.
+ Tag uploads with custom tags for categorization.
+ ... and more.
### Demo GIF
![img](https://i.imgur.com/iV8Rirn.gif)
## Translations
You can help translating the project on [Weblate](https://hosted.weblate.org/projects/xbackbone/xbackbone/).
<a href="https://hosted.weblate.org/engage/xbackbone/?utm_source=widget">
<img src="https://hosted.weblate.org/widgets/xbackbone/-/xbackbone/multi-auto.svg" alt="Stato traduzione" />
</a>

68
docs/installation.md Normal file
View file

@ -0,0 +1,68 @@
---
layout: default
title: Installation
nav_order: 2
---
# Installation
### Prerequisites
XBackBone require PHP >= `7.3`, with installed the required extensions:
+ `php-sqlite3` for SQLite.
+ `php-mysql` for MariaDB/MySQL.
+ `php-gd` image manipualtion library.
+ `php-json` json file support.
+ `php-intl` internationalization functions.
+ `php-fileinfo` file related functions.
+ `php-zip` compressed files related functions.
+ (optional) `php-ftp` to use the FTP remote storage driver.
+ (optional) `php-ldap` to use LDAP authentication.
## Web installation
+ Download latest release from GitHub: [Latest Release](https://github.com/SergiX44/XBackBone/releases/latest)
+ Extract the release zip to your document root.
+ Navigate to the webspace root (ex. `http://example.com/xbackbone`, this should auto redirect your browser to the install page `http://example.com/xbackbone/install/`)
+ Follow the instructions.
For futher and advanced configurations, see the [configuration page](configuration.md).
## Manual installation
+ Download latest release from GitHub: [Latest Release](https://github.com/SergiX44/XBackBone/releases/latest)
+ Extract the release zip to your document root.
+ Copy and edit the config file:
```sh
cp config.example.php config.php && nano config.php
```
By default, XBackBone will use Sqlite3 as DB engine, and a `storage` dir in the main directory. You can leave these settings unchanged for a simple personal installation.
You must set the `base_url`, or remove it for get dynamically the url from request (not recommended).
```php
return [
'base_url' => 'https://example.com', // no trailing slash
'storage' => [
'driver' => 'local',
'path' => 'absolute/path/to/storage',
],
'db' => [
'connection' => 'sqlite', // current support for sqlite and mysql
'dsn' => 'absolute/path/to/resources/database/xbackbone.db', // if sqlite should be an absolute path
'username' => null, // username and password not needed for sqlite
'password' => null,
]
];
```
+ Finally, run the migrate script to setup the database
```sh
php bin/migrate --install
```
+ Delete the `/install` directory.
+ Now just login with `admin/admin`, **be sure to change these credentials after your first login**.
For futher and advanced configurations, see the [configuration page](configuration.md).
## Docker deployment
Alternatively, a docker container is available.
[Docker container](https://fleet.linuxserver.io/image?name=linuxserver/xbackbone){: .btn .btn-purple }

21
docs/license.md Normal file
View file

@ -0,0 +1,21 @@
---
layout: default
title: License & Credits
nav_order: 10
---
# License
This software is licensed under the <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>, available in this repository.
As a "copyright notice" it is sufficient to keep the small footer at the bottom of the page, also to help other people to learn about this project!
# Built with
+ Project logo by [@Sere](https://www.deviantart.com/serenaitalia)
+ Slim 3 since `v2.0`, and Slim 4 since `v3.0` (https://www.slimframework.com/) and some great PHP packages (Flysystem, Intervention Image, Twig, etc)
+ FlightPHP, up to `v1.x` (http://flightphp.com/)
+ Bootstrap 4 (https://getbootstrap.com/)
+ Font Awesome 5 (http://fontawesome.com)
+ ClipboardJS (https://clipboardjs.com/)
+ HighlightJS (https://highlightjs.org/)
+ JQuery (https://jquery.com/)
+ Plyr.io (https://plyr.io/)
+ Dropzone.js (https://www.dropzonejs.com/)

22
docs/sponsor.md Normal file
View file

@ -0,0 +1,22 @@
---
layout: default
title: Sponsorship
nav_order: 8
---
# Sponsor this project
You can give your contribution to this project thanks to a small sponsorship, through GitHub Sponsors, or as a single donation on PayPal.
All those who make the recurring donations, the names or logos will be inserted in this page, if they wish.
[<img src="https://www.gravatar.com/avatar/98b8d56f4a193e3f7154f236c16930b2?s=160" alt="SergiX44" height="100">](https://github.com/SergiX44) |
---|
[SergiX44](https://github.com/SergiX44) |
[GitHub Sponsors](https://github.com/sponsors/SergiX44) |
[PayPal for XBackBone](http://bit.ly/XBackBonePaypal) |
{: .text-center}
## Sponsors
+ [@philw95](https://github.com/philw95)

34
docs/upgrading.md Normal file
View file

@ -0,0 +1,34 @@
---
layout: default
title: Upgrading
nav_order: 4
---
# Upgrading
The system updates can be applied via the web interface by an administrator, or manually via CLI.
## Self-update (since v2.5)
+ Navigate to the system page as administrator.
+ Click the check for update button, and finally the upgrade button.
+ Wait until the browser redirect to the install page.
+ Click the update button.
+ Done.
## Manual update
+ Download and extract the release zip to your document root, overwriting any file.
+ Navigate to the `/install` path (es: `http://example.com/` -> `http://example.com/install/`)
+ Click the update button.
+ Done.
## CLI update
If, for whatever reason, the web UI is not accessible, you can upgrade from CLI:
+ Download and extract the release zip to your document root, overwriting any file.
+ Run the command `php\migrate`.
+ Run the command `php\clean`.
+ Done.
### Pre-release channel
From the system page, you can also choose to check from beta/RC releases, these are NOT considered stable enough for every day use, but only for testing purposes, **take a backup before upgrading to these versions**.

View file

@ -1,8 +1,11 @@
<?php
require 'vendor/autoload.php';
((PHP_MAJOR_VERSION >= 7 && PHP_MINOR_VERSION >= 3) || PHP_MAJOR_VERSION > 7) ?: die('Sorry, PHP 7.3 or above is required to run XBackBone.');
require __DIR__.'/vendor/autoload.php';
define('BASE_DIR', realpath(__DIR__).DIRECTORY_SEPARATOR);
define('PLATFORM_VERSION', json_decode(file_get_contents('composer.json'))->version);
define('CONFIG_FILE', BASE_DIR.'config.php');
require 'bootstrap/app.php';
Flight::start();
$app = require __DIR__.'/bootstrap/app.php';
$app->run();

248
install/index.php Normal file
View file

@ -0,0 +1,248 @@
<?php
((PHP_MAJOR_VERSION >= 7 && PHP_MINOR_VERSION >= 3) || PHP_MAJOR_VERSION > 7) ?: die('Sorry, PHP 7.3 or above is required to run XBackBone.');
require __DIR__.'/../vendor/autoload.php';
use App\Database\Migrator;
use App\Factories\ViewFactory;
use App\Web\Session;
use App\Web\View;
use DI\Bridge\Slim\Bridge;
use DI\ContainerBuilder;
use League\Flysystem\FileExistsException;
use Psr\Container\ContainerInterface as Container;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use function DI\factory;
use function DI\get;
use function DI\value;
define('PLATFORM_VERSION', json_decode(file_get_contents(__DIR__.'/../composer.json'))->version);
define('BASE_DIR', realpath(__DIR__.'/../').DIRECTORY_SEPARATOR);
// default config
$config = [
'base_url' => str_replace('/install/', '', (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http')."://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"),
'debug' => true,
'db' => [
'connection' => 'sqlite',
'dsn' => BASE_DIR.implode(DIRECTORY_SEPARATOR, ['resources', 'database', 'xbackbone.db']),
'username' => null,
'password' => null,
],
'storage' => [
'driver' => 'local',
'path' => realpath(__DIR__.'/../').DIRECTORY_SEPARATOR.'storage',
],
];
$installed = false;
if (file_exists(__DIR__.'/../config.php')) {
$installed = true;
$config = array_replace_recursive($config, require __DIR__.'/../config.php');
if (isset($config['storage_dir'])) { // if from older installations with no support of other than local driver
$config['storage']['driver'] = 'local';
$config['storage']['path'] = $config['storage_dir'];
unset($config['storage_dir']);
}
if ($config['storage']['driver'] === 'local' && !is_dir($config['storage']['path'])) { // if installed with local driver, and the storage dir don't exists
$realPath = realpath(BASE_DIR.$config['storage']['path']);
if (is_dir($realPath) && is_writable($realPath)) { // and was a path relative to the upper folder
$config['storage']['path'] = $realPath; // update the config
}
}
}
$builder = new ContainerBuilder();
$builder->addDefinitions([
'config' => value($config),
View::class => factory(function (Container $container) {
return ViewFactory::createInstallerInstance($container);
}),
'view' => get(View::class),
Session::class => factory(function () {
return new Session('xbackbone_session');
}),
'session' => get(Session::class),
]);
$builder->addDefinitions(__DIR__.'/../bootstrap/container.php');
$app = Bridge::create($builder->build());
$app->setBasePath(parse_url($config['base_url'].'/install', PHP_URL_PATH));
$app->addRoutingMiddleware();
$app->get('/', function (Response $response, View $view, Session $session) {
if (!extension_loaded('gd')) {
$session->alert('The required "gd" extension is not loaded.', 'danger');
}
if (!extension_loaded('intl')) {
$session->alert('The required "intl" extension is not loaded.', 'danger');
}
if (!extension_loaded('json')) {
$session->alert('The required "json" extension is not loaded.', 'danger');
}
if (!extension_loaded('fileinfo')) {
$session->alert('The required "fileinfo" extension is not loaded.', 'danger');
}
if (!extension_loaded('zip')) {
$session->alert('The required "zip" extension is not loaded.', 'danger');
}
if (!is_writable(__DIR__.'/../resources/cache')) {
$session->alert('The cache folder is not writable ('.__DIR__.'/../resources/cache'.')', 'danger');
}
if (!is_writable(__DIR__.'/../resources/database')) {
$session->alert('The database folder is not writable ('.__DIR__.'/../resources/database'.')', 'danger');
}
if (!is_writable(__DIR__.'/../resources/sessions')) {
$session->alert('The sessions folder is not writable ('.__DIR__.'/../resources/sessions'.')', 'danger');
}
$installed = file_exists(__DIR__.'/../config.php');
return $view->render($response, 'install.twig', [
'installed' => $installed,
]);
})->setName('install');
$app->post('/', function (Request $request, Response $response, \DI\Container $container, Session $session) use (&$config, &$installed) {
// disable debug in production
unset($config['debug']);
// Check if there is a previous installation, if not, setup the config file
if (!$installed) {
// config file setup
$config['base_url'] = param($request, 'base_url');
$config['storage']['driver'] = param($request, 'storage_driver');
$config['db']['connection'] = param($request, 'connection');
$config['db']['dsn'] = param($request, 'dsn');
$config['db']['username'] = param($request, 'db_user');
$config['db']['password'] = param($request, 'db_password');
// setup storage configuration
switch ($config['storage']['driver']) {
case 's3':
$config['storage']['key'] = param($request, 'storage_key');
$config['storage']['secret'] = param($request, 'storage_secret');
$config['storage']['region'] = param($request, 'storage_region');
$config['storage']['endpoint'] = !empty(param($request, 'storage_endpoint')) ? param($request, 'storage_endpoint') : null;
$config['storage']['bucket'] = param($request, 'storage_bucket');
$config['storage']['path'] = param($request, 'storage_path');
break;
case 'dropbox':
$config['storage']['token'] = param($request, 'storage_token');
break;
case 'ftp':
if (!extension_loaded('ftp')) {
$session->alert('The "ftp" extension is not loaded.', 'danger');
return redirect($response, urlFor('/'));
}
$config['storage']['host'] = param($request, 'storage_host');
$config['storage']['username'] = param($request, 'storage_username');
$config['storage']['password'] = param($request, 'storage_password');
$config['storage']['port'] = param($request, 'storage_port');
$config['storage']['path'] = param($request, 'storage_path');
$config['storage']['passive'] = param($request, 'storage_passive') === '1';
$config['storage']['ssl'] = param($request, 'storage_ssl') === '1';
break;
case 'google-cloud':
$config['storage']['project_id'] = param($request, 'storage_project_id');
$config['storage']['key_path'] = param($request, 'storage_key_path');
$config['storage']['bucket'] = param($request, 'storage_bucket');
break;
case 'azure':
$config['storage']['account_name'] = param($request, 'storage_account_name');
$config['storage']['account_key'] = param($request, 'storage_account_key');
$config['storage']['container_name'] = param($request, 'storage_container_name');
break;
case 'local':
default:
$config['storage']['path'] = param($request, 'storage_path');
break;
}
$container->set('config', value($config));
}
$storage = $container->get('storage');
// check if the storage is valid
$storageTestFile = 'storage_test.txt';
try {
try {
$success = $storage->write($storageTestFile, 'TEST_FILE');
} catch (FileExistsException $fileExistsException) {
$success = $storage->update($storageTestFile, 'TEST_FILE');
}
if (!$success) {
throw new Exception('The storage is not writable.');
}
$storage->readAndDelete($storageTestFile);
} catch (Exception $e) {
$session->alert("Storage setup error: {$e->getMessage()} [{$e->getCode()}]", 'danger');
return redirect($response, urlFor('/install'));
}
// Get the db instance and run migrations
$db = $container->get('database');
try {
$migrator = new Migrator($db, __DIR__.'/../resources/schemas');
$migrator->migrate();
$migrator->reSyncQuotas($storage);
} catch (PDOException $e) {
$session->alert("Cannot connect to the database: {$e->getMessage()} [{$e->getCode()}]", 'danger');
return redirect($response, urlFor('/install'));
}
// if not installed, create the default admin account
if (!$installed) {
$db->query("INSERT INTO `users` (`email`, `username`, `password`, `is_admin`, `user_code`) VALUES (?, 'admin', ?, 1, ?)", [param($request, 'email'), password_hash(param($request, 'password'), PASSWORD_DEFAULT), humanRandomString(5)]);
}
// re-apply the previous theme if is present
$css = $db->query('SELECT `value` FROM `settings` WHERE `key` = \'css\'')->fetch()->value ?? null;
if ($css && strpos($css, '|') !== false) {
$container->make(\App\Web\Theme::class)->applyTheme($css);
}
// if is upgrading and existing installation, put it out maintenance
if ($installed) {
unset($config['maintenance']);
// remove old config from old versions
unset($config['lang']);
unset($config['displayErrorDetails']);
}
// Finally write the config
$ret = file_put_contents(__DIR__.'/../config.php', '<?php'.PHP_EOL.'return '.var_export($config, true).';');
if ($ret === false) {
$session->alert('The config folder is not writable ('.__DIR__.'/../config.php'.')', 'danger');
return redirect($response, '/install');
}
// post install cleanup
cleanDirectory(__DIR__.'/../resources/cache');
cleanDirectory(__DIR__.'/../resources/sessions');
removeDirectory(__DIR__.'/../install');
// Installed successfully, destroy the installer session
$session->destroy();
return redirect($response, urlFor('/?afterInstall=true'));
});
$app->run();

View file

@ -0,0 +1,246 @@
<!doctype html>
<html lang="en">
<head>
<title>XBackBone Installer</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="XBackBone Installer">
<link href="../static/bootstrap/css/bootstrap.min.css?{{ 'now'|date('U') }}" rel="stylesheet">
<link href="../static/fontawesome/css/all.min.css?{{ 'now'|date('U') }}" rel="stylesheet">
<link href="../static/app/app.css?{{ 'now'|date('U') }}" rel="stylesheet">
<script src="../static/jquery/jquery.min.js?{{ 'now'|date('U') }}"></script>
<script src="../static/bootstrap/js/bootstrap.bundle.min.js?{{ 'now'|date('U') }}"></script>
<script src="installer.js?{{ 'now'|date('U') }}"></script>
</head>
<body class="bg-light">
<div class="container">
<div class="mt-4">
{% include 'comp/alert.twig' %}
</div>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card mt-3 shadow-sm">
<div class="card-header">Install XBackBone</div>
<div class="card-body">
<form method="post" onsubmit="$('#modalLoading').modal({backdrop: 'static', keyboard: false})">
{% if not installed %}
<div class="form-group row">
<label for="base_url" class="col-sm-3 col-form-label">Base URL</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="base_url" name="base_url" value="{{ config.base_url }}" autocomplete="off" required>
<small>No trailing slash.</small>
</div>
</div>
<hr>
<div class="form-group row">
<label for="connection" class="col-sm-3 col-form-label">SQL Engine</label>
<div class="col-sm-9">
<select name="connection" id="connection" required class="form-control">
<option value="sqlite" selected>SQLite</option>
<option value="mysql">MySQL</option>
</select>
</div>
</div>
<div class="form-group row">
<label for="dsn" class="col-sm-3 col-form-label">Database Source Name (DSN)</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="dsn" name="dsn" value="{{ config.db.dsn }}" autocomplete="off" required>
</div>
</div>
<div class="form-group row hook-database">
<label for="db_user" class="col-sm-3 col-form-label">Database Username</label>
<div class="col-sm-9">
<input type="text" class="form-control hook-database-input" id="db_user" name="db_user" value="db_user" autocomplete="off">
</div>
</div>
<div class="form-group row hook-database">
<label for="db_password" class="col-sm-3 col-form-label">Database Password</label>
<div class="col-sm-9">
<input type="password" class="form-control hook-database-input" id="db_password" name="db_password" autocomplete="off">
</div>
</div>
<hr>
<div class="form-group row">
<label for="storage_driver" class="col-sm-3 col-form-label">Storage Driver</label>
<div class="col-sm-9">
<select id="storage_driver" name="storage_driver" class="form-control" required>
<option value="local">Local Storage</option>
<option value="ftp">FTP/FTPS</option>
<option value="s3">Amazon S3 (or compatible)</option>
<option value="google-cloud">Google Cloud Storage</option>
<option value="azure">Azure Blob Storage</option>
<option value="dropbox">Dropbox</option>
</select>
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_path" class="col-sm-3 col-form-label">Storage path root</label>
<div class="col-sm-9">
<input type="text" class="form-control hook-storage-input" id="storage_path" name="storage_path" autocomplete="off" data-default-local="{{ config.storage.path }}">
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_key" class="col-sm-3 col-form-label">AWS S3 key</label>
<div class="col-sm-9">
<input type="text" class="form-control hook-storage-input" id="storage_key" name="storage_key" placeholder="your-key" autocomplete="off">
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_secret" class="col-sm-3 col-form-label">AWS S3 secret</label>
<div class="col-sm-9">
<input type="text" class="form-control hook-storage-input" id="storage_secret" name="storage_secret" placeholder="your-secret" autocomplete="off">
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_region" class="col-sm-3 col-form-label">AWS S3 region</label>
<div class="col-sm-9">
<input type="text" class="form-control hook-storage-input" id="storage_region" name="storage_region" placeholder="your-region" autocomplete="off">
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_region" class="col-sm-3 col-form-label">AWS S3 endpoint</label>
<div class="col-sm-9">
<input type="text" class="form-control hook-storage-input" id="storage_endpoint" name="storage_endpoint" placeholder="optional (for S3-compatible services)" autocomplete="off">
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_token" class="col-sm-3 col-form-label">Dropbox token</label>
<div class="col-sm-9">
<input type="text" class="form-control hook-storage-input" id="storage_token" name="storage_token" placeholder="authorization-token" autocomplete="off">
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_host" class="col-sm-3 col-form-label">FTP host</label>
<div class="col-sm-9">
<input type="text" class="form-control hook-storage-input" id="storage_host" name="storage_host" placeholder="127.0.0.1" autocomplete="off">
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_username" class="col-sm-3 col-form-label">FTP username</label>
<div class="col-sm-9">
<input type="text" class="form-control hook-storage-input" id="storage_username" name="storage_username" placeholder="username" autocomplete="off">
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_password" class="col-sm-3 col-form-label">FTP password</label>
<div class="col-sm-9">
<input type="password" class="form-control hook-storage-input" id="storage_password" name="storage_password" autocomplete="off">
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_port" class="col-sm-3 col-form-label">FTP port</label>
<div class="col-sm-9">
<input type="number" min="0" max="65535" class="form-control hook-storage-input" id="storage_port" name="storage_port" placeholder="21" autocomplete="off">
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_passive" class="col-sm-3 col-form-label">FTP passive mode</label>
<div class="col-sm-9">
<select name="storage_passive" id="storage_passive" class="form-control hook-storage-input">
<option value="1" selected>Yes</option>
<option value="0">No</option>
</select>
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_ssl" class="col-sm-3 col-form-label">FTP SSL enabled</label>
<div class="col-sm-9">
<select name="storage_ssl" id="storage_ssl" class="form-control hook-storage-input">
<option value="1">Yes</option>
<option value="0" selected>No</option>
</select>
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_project_id" class="col-sm-3 col-form-label">Google project id</label>
<div class="col-sm-9">
<input type="text" class="form-control hook-storage-input" id="storage_project_id" name="storage_project_id" placeholder="your-project-id" autocomplete="off">
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_key_path" class="col-sm-3 col-form-label">Google key path</label>
<div class="col-sm-9">
<input type="text" class="form-control hook-storage-input" id="storage_key_path" name="storage_key_path" placeholder="/path/to/service-account.json" autocomplete="off">
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_account_name" class="col-sm-3 col-form-label">Azure storage account name</label>
<div class="col-sm-9">
<input type="text" class="form-control hook-storage-input" id="storage_account_name" name="storage_account_name" placeholder="your-storage-account-name" autocomplete="off">
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_account_key" class="col-sm-3 col-form-label">Azure storage account key</label>
<div class="col-sm-9">
<input type="text" class="form-control hook-storage-input" id="storage_account_key" name="storage_account_key" placeholder="Account (Access) Key" autocomplete="off">
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_container_name" class="col-sm-3 col-form-label">Azure storage container name</label>
<div class="col-sm-9">
<input type="text" class="form-control hook-storage-input" id="storage_container_name" name="storage_container_name" placeholder="your-blob-container-name" autocomplete="off">
</div>
</div>
<div class="form-group row hook-storage">
<label for="storage_bucket" class="col-sm-3 col-form-label">Storage bucket</label>
<div class="col-sm-9">
<input type="text" class="form-control hook-storage-input" id="storage_bucket" name="storage_bucket" placeholder="your-bucket-name" autocomplete="off">
</div>
</div>
<hr>
<div class="form-group row">
<label for="email" class="col-sm-3 col-form-label">Admin email</label>
<div class="col-sm-9">
<input type="email" class="form-control" id="email" placeholder="email@example.com" name="email" autocomplete="off" required>
</div>
</div>
<div class="form-group row">
<label for="password" class="col-sm-3 col-form-label">Admin password</label>
<div class="col-sm-9">
<input type="password" class="form-control" id="password" placeholder="Password" name="password" autocomplete="off" required>
</div>
</div>
<div class="form-group row justify-content-md-end">
<div class="col-sm-9">
<button type="submit" class="btn btn-outline-success">
<i class="fas fa-save fa-fw"></i> Install
</button>
</div>
</div>
{% else %}
<div class="form-group row">
<div class="col-sm-12 d-flex justify-content-center">
<button type="submit" class="btn btn-lg btn-outline-primary">
<i class="fas fa-sync fa-fw"></i> Finalize update
</button>
</div>
</div>
{% endif %}
</form>
</div>
</div>
</div>
</div>
</div>
<div class="modal" id="modalLoading" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title">Please wait...</h6>
</div>
<div class="modal-body text-center">
<i class="fas fa-10x fa-spinner fa-pulse"></i>
</div>
</div>
</div>
</div>
</body>
</html>

52
nginx.conf Normal file
View file

@ -0,0 +1,52 @@
autoindex off;
location /app {
return 403;
}
location /bin {
return 403;
}
location /bootstrap {
return 403;
}
location /resources {
return 403;
}
location /storage {
return 403;
}
location /vendor {
return 403;
}
location /logs {
return 403;
}
location CHANGELOG.md {
return 403;
}
index index.html index.htm index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
include fastcgi_params;
}

5332
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,21 +1,26 @@
{
"dependencies": {
"bootstrap": "^4.1.3",
"clipboard": "^2.0.1",
"highlightjs": "^9.10.0",
"jquery": "^3.3.1",
"popper.js": "^1.14.4",
"tooltip.js": "^1.3.0",
"@fortawesome/fontawesome-free": "^5.4.1"
"@fortawesome/fontawesome-free": "^5.15.2",
"bootstrap": "^4.6.1",
"bootstrap4-toggle": "^3.6.1",
"clipboard": "^2.0.10",
"dropzone": "^5.9.3",
"highlightjs": "^9.16.2",
"highlightjs-line-numbers.js": "^2.8.0",
"jquery": "^3.6.0",
"plyr": "^3.6.12",
"popper.js": "^1.16.1",
"tooltip.js": "^1.3.3"
},
"devDependencies": {
"grunt": "^1.0",
"grunt": "^1.5.3",
"grunt-contrib-compress": "^2.0.0",
"grunt-contrib-copy": "^1.0.0",
"grunt-contrib-cssmin": "^2.2.1",
"grunt-contrib-jshint": "^1.1.0",
"grunt-contrib-uglify": "^3.3.0",
"grunt-contrib-cssmin": "^3.0.0",
"grunt-contrib-jshint": "^3.1.1",
"grunt-contrib-uglify": "^4.0.1",
"grunt-contrib-watch": "^1.1.0",
"grunt-zip": "^0.17.1",
"grunt-shell": "^3.0.1",
"load-grunt-tasks": "^4.0.0"
}
}

33
phpunit.xml Normal file
View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php"
colors="true"
backupGlobals="false"
backupStaticAttributes="false"
stopOnFailure="false"
cacheResult="false"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true">
<testsuites>
<testsuite name="Feature">
<directory suffix="Test.php">tests/Feature</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">app</directory>
<exclude>
<file>app/routes.php</file>
</exclude>
</whitelist>
</filter>
<logging>
<log type="coverage-html" target="./coverage"
lowUpperBound="70"
highLowerBound="90"/>
</logging>
<php>
<server name="HTTP_HOST" value="localhost"/>
<server name="HTTPS" value="false"/>
</php>
</phpunit>

165
resources/lang/ar.lang.php Normal file
View file

@ -0,0 +1,165 @@
<?php
return [
'lang' => 'Arabian',
'yes' => 'نعم',
'no' => 'لا',
'send' => 'إرسال',
'no_media' => 'لا توجد أي وسائط.',
'login.username' => 'اسم المستخدم أو البريد الإلكتروني',
'password' => 'كلمة المرور',
'login' => 'تسجيل الدخول',
'username' => 'اسم المستخدم',
'home' => 'الرئيسية',
'users' => 'مستخدمون',
'system' => 'نظام',
'profile' => 'ملف شخصي',
'logout' => 'تسجيل الخروج',
'pager.next' => 'التالي',
'pager.previous' => 'السابق',
'copy_link' => 'نسخ الرابط',
'public.telegram' => 'شارك على تيليجرام',
'public.delete_text' => 'هل أنت متأكد من أنك تريد حذف هذا العنصر؟ لن تتمكن من استعادته',
'preview' => 'استعراض',
'filename' => 'اسم الملف',
'size' => 'حجم',
'public' => 'عام',
'owner' => 'مالك',
'date' => 'تاريخ',
'raw' => 'عرض الخام',
'download' => 'تنزيل',
'upload' => 'رفع',
'delete' => 'حذف',
'publish' => 'نشر',
'hide' => 'إخفاء',
'files' => 'ملفات',
'theme' => 'سمة',
'click_to_load' => 'انقر للتحميل…',
'apply' => 'تطبيق',
'save' => 'حفظ',
'used' => 'مستخدَم',
'system_settings' => 'إعدادات النظام',
'user.create' => 'إنشاء مستخدم',
'user.edit' => 'تعديل مستخدم',
'is_active' => 'نشط؟',
'is_admin' => 'مسؤول؟',
'your_profile' => 'ملفك الشخصي',
'copy' => 'نسخ',
'update' => 'تحديث',
'edit' => 'تعديل',
'user_code' => 'رمز المستخدم',
'active' => 'نشط',
'admin' => 'مسؤول',
'reg_date' => 'تاريخ التسجيل',
'none' => 'بلا',
'open' => 'فتح',
'confirm' => 'تأكيد',
'confirm_string' => 'هل أنت متأكد؟',
'installed' => 'اكتمل التنصيب بنجاح!',
'bad_login' => 'اعتمادات خاطئة.',
'account_disabled' => 'حسابك معطل.',
'welcome' => 'مرحبا، %s!',
'goodbye' => 'وداعا!',
'email_required' => 'عنوان البريد الإلكتروني مطلوب.',
'email_taken' => 'عنوان البريد الإلكتروني قيد الاستخدام بالفعل.',
'username_required' => 'اسم المستخدم مطلوب.',
'username_taken' => 'اسم المستخدم قيد الاستخدام.',
'password_required' => 'كلمة المرور مطلوبة.',
'user_created' => 'تم إنشاء المستخدم "٪s"!',
'user_updated' => 'تم تحديث المستخدم "%s"!',
'profile_updated' => 'تم تحديث الملف الشخصي بنجاح!',
'user_deleted' => 'تم حذف المستخدم.',
'cannot_delete' => 'لا يمكنك حذف نفسك.',
'cannot_write_file' => 'مسار الوجهة غير قابل للكتابة.',
'switch_to' => 'تبديل إلى',
'gallery' => 'معرض',
'table' => 'جدول',
'dotted_search' => 'بحث…',
'order_by' => 'ترتيب حسب…',
'time' => 'وقت',
'name' => 'اسم',
'maintenance' => 'صيانة',
'path_not_writable' => 'مسار الإخراج غير قابل للكتابة.',
'already_latest_version' => 'لديك الإصدار الأخير حاليا.',
'new_version_available' => 'إصدار جديد %s متوفر!',
'cannot_retrieve_file' => 'لا يمكن استلام الملف.',
'file_size_no_match' => 'الملف المنزل لا يطابق الحجم الصحيح.',
'check_for_updates' => 'تحقق من التحديثات',
'upgrade' => 'ترقية',
'updates' => 'تحديثات',
'cancel' => 'إلغاء',
'auto_set' => 'حدد تلقائيا',
'prerelease_channel' => 'قناة ما قبل الإصدار',
'drop_to_upload' => 'انقر أو اسحب ملفاتك هنا للرفع.',
'donation' => 'تبرع',
'remember_me' => 'تذكرني',
'please_wait' => 'انتظر رجاء…',
'dont_close' => 'لا تغلق هذا اللسان حتى الاكتمال.',
'register_enabled' => 'التسجيلات مفعلة',
'hide_by_default' => 'أخف الوسائط افتراضيا',
'settings_saved' => 'تم حفظ إعدادات النظام!',
'export_data' => 'استخراج البيانات',
'password_recovery' => 'استعادة كلمة المرور',
'no_account' => 'لا تملك حسابا؟',
'register' => 'تسجيل',
'register_success' => 'تم إنشاء الحساب، تم إرسال رسالة تأكيد عبر البريد الإلكتروني.',
'mail.activate_account' => '%s - تفعيل الحساب',
'mail.recover_password' => '%s - استعادة كلمة المرور',
'recover_email_sent' => 'إن وجد، سيتم إرسال رسالة إلكترونية لاستعادة كلمة المرور إلى الحساب المحدد.',
'account_activated' => 'تم تفعيل الحساب، الآن يمكنك الدخول!',
'password_repeat' => 'تكرار كلمة المرور',
'password_match' => 'يجب أن تتطابق كلمة المرور وتكراره كلمة المرور.',
'password_restored' => 'استعادة كلمة المرور.',
'used_space' => 'مساحة مستخدمة',
'delete_selected' => 'حذف المحدد',
'delete_all' => 'حذف الكل',
'clear_account' => 'مسح الحساب',
'account_media_deleted' => 'كل الوسائط في الحساب تم حذفها.',
'danger_zone' => 'منطقة خطرة',
'send_notification' => 'إرسال إشعار بالبريد الإلكتروني',
'mail.new_account' => '%s - إنشاء حساب جديد',
'php_info' => 'معلومات الPHP',
'enforce_language' => 'فرض اللغة',
'image_embeds' => 'تضمين الصور',
'token_not_found' => 'لم يتم العثور على الرمز المميز المحدد.',
'clean_orphaned_uploads' => 'تنظيف التحميلات المعزولة',
'show_all_tags' => 'إظهار جميع العلامات',
'cannot_demote' => 'لا يمكنك تخفيض رتبتك.',
'recaptcha_secret_key' => 'المفتاح السري reCAPTCHA',
'recaptcha_failed' => 'فشل reCAPTCHA',
'recaptcha_enabled' => 'تم تفعيل reCAPTCHA',
'default_user_quota' => 'الحصة الافتراضية للمستخدم',
'invalid_quota' => 'قيم غير صالحة كحصة مستخدم افتراضية.',
'copy_url_behavior' => 'نسخ وضع الرابط',
'token' => 'الرمز المميز',
'vanity_url' => 'رابط مخصص',
'orphaned_files' => 'الملفات المعزولة',
'copied' => 'تم النسخ إلى الحافظة!',
'client_config' => 'إعدادات العميل',
'deleted_orphans' => 'تم حذف %d من الملفات المعزولة بنجاح.',
'maintenance_in_progress' => 'المنصة تحت الصيانة، حاول مرة أخرى لاحقاً…',
'default_lang_behavior' => 'سيحاول XBackBone مطابقة لغة المتصفح افتراضيا (الاحتياطي هو الإنجليزية).',
'no_upload_token' => 'ليس لديك رمز مميز شخصي للتحميل. (قم بإنشاء واحد وحاول مرة أخرى.)',
'donate_text' => 'إذا كنت تحب XBackBone، ففكر في التبرع لدعم التطوير!',
'custom_head_html' => 'محتوى رأس HTML مخصص',
'custom_head_html_hint' => 'ستتم إضافة هذا المحتوى عند علامة <head> في كل صفحة.',
'custom_head_set' => 'تم تطبيق رأس HTML المخصص.',
'max_user_quota' => 'الحد الأقصى لحصة المستخدم',
'quota_enabled' => 'تفعيل حصة المستخدم',
'recalculate_user_quota' => 'إعادة حساب حصة المستخدم من القرص',
'mail.activate_text' => 'مرحبًا %s!<br>شكرًا لك على إنشاء حسابك على %s (<a href="%s">%s</a>)، انقر على الرابط التالي لتفعيله:<br><br><a href="%s">%s</a>',
'quota_recalculated' => 'تمت إعادة حساب حصة المستخدم من القرص بنجاح.',
'only_recaptcha_v3' => 'يتم دعم reCAPTCHA v3 فقط.',
'recaptcha_site_key' => 'مفتاح موقع reCAPTCHA',
'ldap_cant_connect' => 'لا يمكن الاتصال بخادم مصادقة LDAP.',
'upload_max_file_size' => 'الحد الأقصى لحجم الملف حاليًا هو %s.',
'zip_ext_not_loaded' => 'لم يتم تحميل الامتداد "zip" المطلوب',
'no_tags' => 'لم تتم إضافة أي علامات',
'changelog' => 'سجل التغيير',
'show_changelog' => 'عرض سجل التغيير',
'auto_tagging' => 'تحميل العلامات تلقائيًا',
'recaptcha_keys_required' => 'جميع مفاتيح reCAPTCHA مطلوبة.',
'user_create_password' => 'إذا تركت فارغة، فقد تحتاج إلى إرسال إعلام إلى عنوان البريد الإلكتروني للمستخدم.',
'mail.recover_text' => 'مرحبًا %s،<br>لقد تم طلب إعادة تعيين كلمة المرور لحسابك. لإكمال الإجراء انقر على الرابط التالي:<br><br><a href="%s">%s</a><br><br>إذا لم تكن أنت من طلب إعادة تعيين كلمة المرور، فما عليك سوى تجاهل هذا البريد الإلكتروني.',
'mail.new_account_text_with_reset' => 'مرحبًا %s!<br>تم إنشاء حساب جديد لك على %s (<a href="%s">%s</a>)، انقر على الرابط التالي لتعيين كلمة مرور وتنشيطها:<br ><br><a href="%s">%s</a>',
'mail.new_account_text_with_pw' => 'مرحبًا %s!<br>تم إنشاء حساب جديد لك على %s (<a href="%s">%s</a>)، باستخدام بيانات الاعتماد التالية:<br><br>اسم المستخدم: %s <br>كلمة المرور: %s<br><br>انقر على الرابط التالي للانتقال إلى صفحة تسجيل الدخول:<br><a href="%s">%s</a>',
];

164
resources/lang/bg.lang.php Normal file
View file

@ -0,0 +1,164 @@
<?php
return [
'lang' => 'Bulgarian',
'enforce_language' => 'Изберете език',
'yes' => 'Да',
'no' => 'Не',
'send' => 'Изпрати',
'no_media' => 'Не бяха намерени файлове.',
'login.username' => 'Потребителско име или е-поща',
'password' => 'Парола',
'login' => 'Влезте',
'username' => 'Потребителско име',
'home' => 'Начална страница',
'users' => 'Потребители',
'system' => 'Система',
'profile' => 'Профил',
'logout' => 'Излезте',
'pager.next' => 'Следващо',
'pager.previous' => 'Предишно',
'copy_link' => 'Копирай линк',
'public.telegram' => 'Сподели по Telegram',
'public.delete_text' => 'Наистина ли искате да изтриете този елемент? Няма да можете да го възстановите',
'preview' => 'Предварителен преглед',
'filename' => 'Файлово име',
'size' => 'Размер',
'public' => 'Публично',
'owner' => 'Собственик',
'date' => 'Дата',
'raw' => 'Покажи оригинал',
'download' => 'Изтегли',
'delete' => 'Изтрий',
'publish' => 'Публикувай',
'hide' => 'Скрий',
'files' => 'Файлове',
'orphaned_files' => 'Загубени файлове',
'theme' => 'Тема',
'click_to_load' => 'Щракнете за зареждане…',
'apply' => 'Приложи',
'save' => 'Запомни',
'used' => 'Използвано',
'system_info' => 'Системна информация',
'user.create' => 'Създай потребител',
'user.edit' => 'Редактирай потребител',
'is_active' => 'Е активен',
'is_admin' => 'Е администратор',
'your_profile' => 'Вашия профил',
'token' => 'Ключ',
'copy' => 'Копирай',
'update' => 'Обнови',
'edit' => 'Редактирай',
'client_config' => 'Конфигурация на клиента',
'user_code' => 'Потребителски код',
'active' => 'Активен',
'admin' => 'Админ',
'reg_date' => 'Дата на регистрация',
'confirm' => 'Потвърждение',
'confirm_string' => 'Сигурни ли сте?',
'installed' => 'Инсталацията завърши успешно!',
'welcome' => 'Добре дошли, %s!',
'goodbye' => 'Довиждане!',
'email_required' => 'Изисква се имейл адрес.',
'email_taken' => 'Имейл адресът вече се използва.',
'username_required' => 'Потребителското име е необходимо.',
'username_taken' => 'Потребителското име вече е заето.',
'password_required' => 'Паролата е задължителна.',
'user_created' => 'Потребител „%s“ бе създаден!',
'user_updated' => 'Потребител „%s“ бе обновен!',
'profile_updated' => 'Профилът бе обновен успешно!',
'user_deleted' => 'Потребителят бе изтрит.',
'cannot_delete' => 'Не можете да изтриете себе си.',
'gallery' => 'Галерия',
'image_embeds' => 'Вграждане на изображения',
'bad_login' => 'Грешни идентификационни данни.',
'account_disabled' => 'Вашият акаунт е деактивиран.',
'copied' => 'Копирано в клипборда!',
'none' => 'Нито един',
'open' => 'Отвори',
'token_not_found' => 'Посоченият токен не е намерен.',
'cannot_demote' => 'Не можете да се понижавате.',
'cannot_write_file' => 'Пътят на дестинацията не е за запис.',
'deleted_orphans' => 'Успешно изтрити %d осиротели файла.',
'switch_to' => 'Преминат към',
'table' => 'Таблица',
'time' => 'Време',
'name' => 'Име',
'maintenance' => 'Поддръжка',
'clean_orphaned_uploads' => 'Изчистване на остарели качвания',
'path_not_writable' => 'Изходният път не е за запис.',
'already_latest_version' => 'Вече имате най-новата версия.',
'new_version_available' => 'Налична е нова версия %s!',
'cannot_retrieve_file' => 'Файлът не може да бъде извлечен.',
'upgrade' => 'Подобряване',
'updates' => 'Надстройки',
'auto_set' => 'Задайте автоматично',
'default_lang_behavior' => 'XBackBone ще се опита да съвпадне с езика на браузъра по подразбиране (резервният вариант е английски).',
'donation' => 'Дарение',
'donate_text' => 'Ако харесвате XBackBone, помислете за дарение в подкрепа на развитието!',
'custom_head_html' => 'Персонализирано съдържание на HTML Head',
'custom_head_html_hint' => 'Това съдържание ще бъде добавено към маркера <head> на всяка страница.',
'custom_head_set' => 'Приложена е персонализирана HTML Head.',
'remember_me' => 'Помни ме',
'please_wait' => 'Моля изчакай…',
'dont_close' => 'Не затваряйте този раздел до приключване.',
'copy_url_behavior' => 'Режим на копиране на URL',
'dotted_search' => 'Търсене…',
'order_by' => 'Подредени по…',
'file_size_no_match' => 'Изтегленият файл не съответства на правилния размер на файла.',
'check_for_updates' => 'Провери за актуализации',
'no_account' => 'Нямате акаунт?',
'default_user_quota' => 'Потребителска квота по подразбиране',
'register' => 'Регистрация',
'max_user_quota' => 'Максимална потребителска квота',
'invalid_quota' => 'Невалидни стойности като потребителска квота по подразбиране.',
'account_activated' => 'Акаунтът е активиран, сега можете да влезете!',
'quota_enabled' => 'Активиране на потребителска квота',
'password_restored' => 'Нулиране на паролата.',
'quota_recalculated' => 'Потребителската квота е преизчислена от диска успешно.',
'delete_selected' => 'Изтрий избраното',
'delete_all' => 'Изтриване на всички',
'account_media_deleted' => 'Всички медии в акаунта са изтрити.',
'danger_zone' => 'Опасна зона',
'recaptcha_failed' => 'reCAPTCHA неуспешно',
'recaptcha_enabled' => 'reCAPTCHA е активирана',
'recaptcha_keys_required' => 'Всички ключове reCAPTCHA са задължителни.',
'recaptcha_site_key' => 'Ключ на сайта reCAPTCHA',
'send_notification' => 'Изпращане на известие по имейл',
'mail.new_account' => '%s - Създаване на нов акаунт',
'user_create_password' => 'Ако се остави празно, може да искате да изпратите известие до имейл адреса на потребителя.',
'ldap_cant_connect' => 'Не може да се свърже към LDAP сървъра за удостоверяване.',
'upload_max_file_size' => 'Максималният размер на файла в момента е %s.',
'no_tags' => 'Няма добавени тагове',
'auto_tagging' => 'Маркиране за автоматично качване',
'zip_ext_not_loaded' => 'Необходимото разширение "zip" не е заредено',
'changelog' => 'Регистър на промените',
'show_changelog' => 'Показване на регистъра на промените',
'register_success' => 'Акаунтът е създаден, изпратен е имейл за потвърждение.',
'recover_email_sent' => 'Ако е налице, имейл за възстановяване е изпратен до посочения акаунт.',
'mail.recover_password' => '%s - Възстановяване на парола',
'php_info' => 'PHP информация',
'system_settings' => 'Системни настройки',
'upload' => 'Качване',
'prerelease_channel' => 'Предварителен канал',
'settings_saved' => 'Системните настройки са запазени!',
'export_data' => 'Експортиране на данни',
'cancel' => 'Отмяна',
'no_upload_token' => 'Нямате личен токен за качване. (Генерирайте един и опитайте отново.)',
'maintenance_in_progress' => 'Платформата е в процес на поддръжка, опитайте отново по-късно…',
'drop_to_upload' => 'Щракнете или пуснете вашите файлове тук, за да ги качите.',
'register_enabled' => 'Регистрациите са активирани',
'hide_by_default' => 'Скриване на медиите по подразбиране',
'password_recovery' => 'Възстановяване на парола',
'mail.activate_text' => 'Здравейте %s!<br>благодаря ви, че създадохте своя акаунт в %s (<a href="%s">%s</a>), щракнете върху следната връзка, за да го активирате:<br><br>< a href="%s">%s</a>',
'mail.activate_account' => '%s - Активиране на акаунт',
'mail.recover_text' => 'Здравейте %s,<br>за вашия акаунт бе поискано нулиране на паролата. За да завършите процедурата, щракнете върху следната връзка:<br><br><a href="%s">%s</a><br><br>Ако не вие сте поискали нулирането на паролата, просто игнорирайте този имейл.',
'password_match' => 'Паролата и повторната парола трябва да са еднакви.',
'recalculate_user_quota' => 'Преизчисляване на потребителската квота от диска',
'password_repeat' => 'Повтори паролата',
'recaptcha_secret_key' => 'Секретен ключ на reCAPTCHA',
'mail.new_account_text_with_reset' => 'Здравейте %s!<br>за вас беше създаден нов акаунт на %s (<a href="%s">%s</a>), щракнете върху следната връзка, за да зададете парола и да я активирате:<br ><br><a href="%s">%s</a>',
'used_space' => 'Използвано пространство',
'clear_account' => 'Изчистване на акаунта',
'only_recaptcha_v3' => 'Поддържа се само reCAPTCHA v3.',
'mail.new_account_text_with_pw' => 'Здравейте %s!<br>за вас бе създаден нов акаунт на %s (<a href="%s">%s</a>) със следните идентификационни данни:<br><br>Потребителско име: %s <br>Парола: %s<br><br>Щракнете върху следната връзка, за да отидете на страницата за вход:<br><a href="%s">%s</a>',
];

163
resources/lang/ca.lang.php Normal file
View file

@ -0,0 +1,163 @@
<?php
return [
'no_media' => 'Sense medis.',
'raw' => 'Mostra raw',
'lang' => 'Anglès',
'enforce_language' => 'Aplicar idioma',
'no' => 'No',
'send' => 'Envia',
'login.username' => 'Nom d\'usuari o Email',
'password' => 'Contrasenya',
'login' => 'Registre',
'username' => 'Nom Usuari',
'home' => 'Casa',
'copy_link' => 'Copiar Link',
'public.telegram' => 'Compartir a Telegram',
'public.delete_text' => 'Segur que vols esborrar l\'element? No el podràs recuperar',
'preview' => 'Vista prèvia',
'filename' => 'Nom Arxiu',
'size' => 'Mida',
'public' => 'Public',
'owner' => 'Propietari',
'date' => 'Data',
'upload' => 'Puja',
'delete' => 'Esborra',
'hide' => 'Oculta',
'files' => 'Arxius',
'orphaned_files' => 'Arxius orfes',
'theme' => 'Tema',
'click_to_load' => 'Click per pujar…',
'apply' => 'Aplicar',
'save' => 'Guardar',
'used' => 'Usat',
'php_info' => 'Informació PHP',
'system_settings' => 'Opcions de Sistema',
'user.create' => 'Crear Usuari',
'user.edit' => 'Editar Usuari',
'is_active' => 'Està Actiu',
'is_admin' => 'és administrador',
'your_profile' => 'el teu perfil',
'token' => 'Token',
'copy' => 'Copiar',
'copied' => 'Copiat al portaretalls!',
'client_config' => 'Configuració Client',
'user_code' => 'Codi Usuari',
'active' => 'Actiu',
'reg_date' => 'Data Registre',
'none' => 'Cap',
'open' => 'Obert',
'confirm' => 'Confirmació',
'confirm_string' => 'Estàs segur?',
'installed' => 'Instal·lació satisfactòria!',
'bad_login' => 'Credencials Errònies.',
'account_disabled' => 'Compte Deshabilitat.',
'goodbye' => 'Adéu!',
'token_not_found' => 'Token no trobat.',
'email_taken' => 'Compte de correu en ús.',
'username_required' => 'Nom usuari Requerit.',
'username_taken' => 'Nom d\'usuari ja en ús.',
'password_required' => 'Contrasenya requerida.',
'user_created' => 'Usuari "%s" creat!',
'user_updated' => 'Usuari "%s" actualitzat!',
'profile_updated' => 'Perfil Actualitzat!',
'user_deleted' => 'Usuari esborrat.',
'cannot_delete' => 'No pots esborrar.-te.',
'cannot_demote' => 'No et pots degradar.',
'cannot_write_file' => 'No es pot escriure al path.',
'deleted_orphans' => '%d Fitxers orfes esborrats.',
'dotted_search' => 'Cerca…',
'order_by' => 'Endreça per.…',
'time' => 'Temps',
'name' => 'Nom',
'maintenance' => 'Manteniment',
'clean_orphaned_uploads' => 'Neteja càrregues orfes',
'path_not_writable' => 'No es pot escriure al path de sortida.',
'already_latest_version' => 'Ja estàs a la darrera versió.',
'new_version_available' => 'Nova versió %s disponible!',
'cannot_retrieve_file' => 'No es pot recuperar el fitxer.',
'file_size_no_match' => 'El fitxer baixat no coincideix amb la mida correcta del fitxer.',
'upgrade' => 'Actualitza',
'maintenance_in_progress' => 'En manteniment, torna-ho a provar més tard…',
'cancel' => 'Cancel·la',
'auto_set' => 'Estableix automàticament',
'default_lang_behavior' => 'XBackBone intentarà fer coincidir l\'idioma del navegador de manera predeterminada (la alternativa és l\'anglès).',
'prerelease_channel' => 'Canal de preestrena',
'drop_to_upload' => 'Feu clic o deixeu anar els vostres fitxers aquí per carregar-los.',
'donation' => 'Donatiu',
'custom_head_html' => 'Contingut de capçalera HTML personalitzat',
'custom_head_set' => 'Capçalera HTML personalitzada aplicada.',
'remember_me' => 'Recorda\'m',
'please_wait' => 'Esperi…',
'dont_close' => 'No tanquis la pestanya.',
'register_enabled' => 'Registres habilitats',
'hide_by_default' => 'Amaga medis per defecte',
'copy_url_behavior' => 'Mode copia URL',
'password_recovery' => 'Recupera password',
'no_account' => 'No tens compte?',
'register' => 'Registre',
'register_success' => 'S\'ha creat el compte, s\'ha enviat un correu electrònic de confirmació.',
'default_user_quota' => 'Quota d\'usuari per defecte',
'max_user_quota' => 'Quota màxima d\'usuari',
'invalid_quota' => 'Valors no vàlids com a quota d\'usuari predeterminada.',
'mail.activate_account' => '%s - Activació del compte',
'mail.recover_password' => '%s - Recuperació de la contrasenya',
'recover_email_sent' => 'Si existeix, s\'enviarà un correu electrònic de recuperació al compte especificat.',
'account_activated' => 'Compte activat, ara pots iniciar sessió!',
'quota_enabled' => 'Activa la quota d\'usuari',
'password_repeat' => 'Repeteix Contrasenya',
'password_match' => 'Les contrasenyes han de coincidir.',
'password_restored' => 'Contrasenya restablerta.',
'recalculate_user_quota' => 'Torneu a calcular la quota d\'usuari del disc',
'quota_recalculated' => 'La quota d\'usuari s\'ha recalculat des del disc correctament.',
'used_space' => 'Espai Utilitzat',
'delete_selected' => 'Esborra seleccionat',
'delete_all' => 'Esborra Tot',
'clear_account' => 'Esborra el compte',
'account_media_deleted' => 'S\'han suprimit tots els mitjans del compte.',
'danger_zone' => 'Zona Perillosa',
'recaptcha_failed' => 'reCAPTCHA Erroni',
'recaptcha_keys_required' => 'Calen totes les claus reCAPTCHA.',
'only_recaptcha_v3' => 'Només s\'admet reCAPTCHA v3.',
'recaptcha_site_key' => 'Clau del lloc reCAPTCHA',
'recaptcha_secret_key' => 'Clau secreta reCAPTCHA',
'send_notification' => 'Envia notificació per e-mail',
'mail.new_account' => '%s - Creació nou Compte',
'users' => 'Usuaris',
'system' => 'Sistema',
'mail.new_account_text_with_pw' => 'Hola, %s!<br>s\'ha creat un compte nou a %s (<a href="%s">%s</a>), amb les credencials següents:<br><br>Nom d\'usuari: %s <br>Contrasenya: %s<br><br>Feu clic a l\'enllaç següent per anar a la pàgina d\'inici de sessió:<br><a href="%s">%s</a>',
'user_create_password' => 'Si es deixa buit, és possible que vulgueu enviar una notificació a l\'adreça de correu electrònic de l\'usuari.',
'ldap_cant_connect' => 'No es pot connectar al servidor d\'autenticació LDAP.',
'upload_max_file_size' => 'La mida màxima del fitxer és actualment %s.',
'no_tags' => 'Sense Etiquetes',
'auto_tagging' => 'Etiquetatge de càrrega automàtica',
'zip_ext_not_loaded' => 'L\'extensió "zip" necessària no està carregada',
'changelog' => 'Codi de canvis',
'show_changelog' => 'Mostra canvis',
'image_embeds' => 'Insereix imatges',
'yes' => 'Sí',
'profile' => 'Perfil',
'pager.previous' => 'Anterior',
'logout' => 'Tancar Sessió',
'pager.next' => 'Següent',
'publish' => 'Publica',
'download' => 'Descarrega',
'update' => 'Actualitza',
'admin' => 'Admin',
'edit' => 'Edita',
'welcome' => 'Benvolgut, %s!',
'email_required' => 'Adreça email requerida.',
'gallery' => 'Galeria',
'switch_to' => 'Canvia a',
'no_upload_token' => 'No tens cap testimoni de càrrega personal. (Genereu-ne un i torneu-ho a provar.)',
'table' => 'Taula',
'check_for_updates' => 'Comprova actualitzacions',
'updates' => 'Actualitzacions',
'donate_text' => 'Si t\'agrada XBackBone, considera una donació per donar suport al desenvolupament!',
'settings_saved' => 'Opcions de sistema gravades!',
'export_data' => 'Exporta dades',
'custom_head_html_hint' => 'Aquest contingut s\'afegirà a l\'etiqueta <head> de cada pàgina.',
'mail.activate_text' => 'Hola, %s!<br>gràcies per crear el vostre compte a %s (<a href="%s">%s</a>), feu clic a l\'enllaç següent per activar-lo:<br><br>< a href="%s">%s</a>',
'mail.recover_text' => 'Hola %s,<br>s\'ha sol·licitat un restabliment de la contrasenya per al vostre compte. Per completar el procediment, feu clic a l\'enllaç següent:<br><br><a href="%s">%s</a><br><br>Si no heu estat vosaltres qui heu sol·licitat la restabliment de la contrasenya, simplement ignora aquest correu electrònic.',
'recaptcha_enabled' => 'reCAPTCHA Habilitat',
'mail.new_account_text_with_reset' => 'Hola, %s!<br>s\'ha creat un compte nou a %s (<a href="%s">%s</a>), feu clic a l\'enllaç següent per establir una contrasenya i activar-la:<br ><br><a href="%s">%s</a>',
];

163
resources/lang/cs.lang.php Normal file
View file

@ -0,0 +1,163 @@
<?php
return [
'prerelease_channel' => 'Kanál předběžného vydání',
'default_lang_behavior' => 'XBackBone se pokusí ve výchozím nastavení použít jazyk prohlížeče (záloha je angličtina).',
'auto_set' => 'Nastavit automaticky',
'cancel' => 'Zrušit',
'maintenance_in_progress' => 'Platforma je v údržbě, zkuste to znovu později…',
'updates' => 'Aktualizace',
'upgrade' => 'Aktualizovat',
'check_for_updates' => 'Zkontrolovat aktualizace',
'file_size_no_match' => 'Stažený soubor nemá správnou velikost.',
'cannot_retrieve_file' => 'Nemohu získat soubor.',
'new_version_available' => 'Je dostupná nová verze %s!',
'already_latest_version' => 'Již máte nejnovější verzi.',
'path_not_writable' => 'Výstupová cesta není zapisovatelná.',
'clean_orphaned_uploads' => 'Vymazat samostatné nahrávky',
'maintenance' => 'Údržba',
'name' => 'Jméno',
'time' => 'Čas',
'order_by' => 'Řadit podle…',
'dotted_search' => 'Hledat…',
'table' => 'Tabulka',
'gallery' => 'Galerie',
'switch_to' => 'Přepnout na',
'deleted_orphans' => 'Úspěšně odstraněno %d samostatných souborů.',
'cannot_write_file' => 'Cílová cesta není zapisovatelná.',
'cannot_demote' => 'Nemůžete sami sobě snížit role.',
'cannot_delete' => 'Nemůžete odstranit sami sebe.',
'user_deleted' => 'Uživatel odstraněn.',
'profile_updated' => 'Profil úspěšně aktualizován!',
'user_updated' => 'Uživatel "%s" aktualizován!',
'user_created' => 'Uživatel "%s" vytvořen!',
'password_required' => 'Je vyžadováno heslo.',
'username_taken' => 'Uživatelské jméno je již zabrané.',
'username_required' => 'Je vyžadováno uživatelské jméno.',
'email_taken' => 'Daná e-mailová adresa se již používá.',
'email_required' => 'Je vyžadována e-mailová adresa.',
'token_not_found' => 'Zadaný token nenalezen.',
'goodbye' => 'Mějte se!',
'welcome' => 'Vítejte, %s!',
'account_disabled' => 'Váš účet je pozastaven.',
'bad_login' => 'Špatné údaje.',
'installed' => 'Instalace úspěšně dokončena!',
'confirm_string' => 'Jste si jisti?',
'confirm' => 'Potvrzení',
'open' => 'Otevřený',
'none' => 'Žádný',
'reg_date' => 'Datum registrace',
'admin' => 'Admin',
'active' => 'Aktivní',
'user_code' => 'Uživatelský kód',
'client_config' => 'Konfigurace klienta',
'edit' => 'Upravit',
'update' => 'Aktualizovat',
'copied' => 'Zkopírováno do schránky!',
'copy' => 'Zkopírovat',
'token' => 'Token',
'your_profile' => 'Váš profil',
'is_admin' => 'Je administrátor',
'is_active' => 'Je aktivní',
'user.edit' => 'Upravit uživatele',
'user.create' => 'Vytvořit uživatele',
'system_settings' => 'Systémová nastavení',
'php_info' => 'Informace o PHP',
'used' => 'Použito',
'save' => 'Uložit',
'apply' => 'Použít',
'click_to_load' => 'Klikněte pro načtení…',
'theme' => 'Téma',
'orphaned_files' => 'Samotné soubory',
'files' => 'Soubory',
'hide' => 'Skrýt',
'publish' => 'Zveřejnit',
'delete' => 'Odstranit',
'upload' => 'Nahrát',
'download' => 'Stáhnout',
'raw' => 'Zobrazit základní',
'date' => 'Datum',
'owner' => 'Majitel',
'public' => 'Veřejné',
'size' => 'Velikost',
'filename' => 'Název souboru',
'preview' => 'Náhled',
'public.delete_text' => 'Opravdu chcete odstranit tuto položku? Nebudete ji moci později obnovit',
'public.telegram' => 'Sdílet na Telegramu',
'copy_link' => 'Zkopírovat odkaz',
'pager.previous' => 'Předchozí',
'pager.next' => 'Další',
'logout' => 'Odhlásit se',
'profile' => 'Profil',
'system' => 'Systém',
'users' => 'Uživatelé',
'home' => 'Domů',
'username' => 'Uživatelské jméno',
'login' => 'Přihlásit se',
'password' => 'Heslo',
'login.username' => 'Uživatelské jméno nebo e-mail',
'no_media' => 'Nenalezena žádná média.',
'send' => 'Poslat',
'no' => 'Ne',
'yes' => 'Ano',
'enforce_language' => 'Vynutit jazyk',
'lang' => 'Čeština',
'show_changelog' => 'Zobrazit seznam změn',
'changelog' => 'Seznam změn',
'zip_ext_not_loaded' => 'Vyžadované "zip" rozšíření není načteno',
'auto_tagging' => 'Automatické štítkování nahrání',
'no_tags' => 'Nebyly přidány žádné štítky',
'upload_max_file_size' => 'Maximální velikost souboru je momentálně %s.',
'ldap_cant_connect' => 'Nemohu se připojit k ověřovacímu serveru LDAP.',
'user_create_password' => 'Pokud bude ponecháno prázdné, budete možná chtít poslat oznámení na uživatelovu e-mailovou adresu.',
'mail.new_account_text_with_pw' => 'Zdravíme, %s!<br>Byl pro vás vytvořen nový účet na %s (<a href="%s">%s</a>) s následujícími údaji:<br><br>Uživatelské jméno: %s<br>Heslo: %s<br><br>Klikněte na následující odkaz pro vstup na přihlašovací stránku:<br><a href="%s">%s</a>',
'mail.new_account_text_with_reset' => 'Zdravíme, %s!<br>Byl pro vás vytvořen nový účet na %s (<a href="%s">%s</a>). Klikněte na následující odkaz pro nastavení hesla a aktivaci účtu:<br><br><a href="%s">%s</a>',
'mail.new_account' => '%s - tvorba nového účtu',
'send_notification' => 'Poslat e-mailové oznámení',
'recaptcha_secret_key' => 'reCAPTCHA Tajný klíč',
'recaptcha_site_key' => 'reCAPTCHA Webový Klíč',
'only_recaptcha_v3' => 'Je podporována pouze reCAPTCHA v3.',
'recaptcha_keys_required' => 'Jsou vyžadovány všechny klíče reCAPTCHA.',
'recaptcha_enabled' => 'reCAPTCHA povolena',
'recaptcha_failed' => 'reCAPTCHA selhala',
'danger_zone' => 'Nebezpečná oblast',
'account_media_deleted' => 'Všechna média na účtu byla odstraněna.',
'clear_account' => 'Promazat účet',
'delete_all' => 'Odstranit vše',
'delete_selected' => 'Odstranit vybrané',
'used_space' => 'Využitý prostor',
'quota_recalculated' => 'Uživatelská kvóta úspěšně přepočítána z disku.',
'recalculate_user_quota' => 'Přepočítat uživatelskou kvótu z disku',
'password_restored' => 'Heslo obnoveno.',
'password_match' => 'Pole Heslo a Heslo znovu se musí shodovat.',
'password_repeat' => 'Heslo znovu',
'quota_enabled' => 'Povolit uživatelskou kvótu',
'account_activated' => 'Účet aktivován, nyní se můžete přihlásit!',
'recover_email_sent' => 'Pokud existuje, byl odeslán obnovovací e-mail na zadaný účet.',
'mail.recover_password' => '%s - obnova hesla',
'mail.recover_text' => 'Zdravíme, %s,<br>u vašeho účtu bylo zažádáno obnovení hesla. Pro dokončení akce klikněte na následující odkaz:<br><br><a href="%s">%s</a><br><br>Pokud jste o obnovení hesla nezažádali vy, jednoduše ignorujte tento e-mail.',
'mail.activate_account' => '%s - aktivace účtu',
'mail.activate_text' => 'Zdravíme, %s!<br>Děkujeme za vytvoření účtu na %s (<a href="%s">%s</a>), klikněte na následující odkaz pro jeho aktivaci:<br><br><a href="%s">%s</a>',
'invalid_quota' => 'Neplatné hodnoty jako výchozí uživatelská kvóta.',
'max_user_quota' => 'Maximální uživatelská kvóta',
'default_user_quota' => 'Výchozí uživatelská kvóta',
'register_success' => 'Účet byl vytvořen a potvrzovací e-mail odeslán.',
'register' => 'Zaregistrovat se',
'no_account' => 'Nemáte účet?',
'password_recovery' => 'Obnovit heslo',
'export_data' => 'Exportovat data',
'settings_saved' => 'Nastavení systému uložena!',
'copy_url_behavior' => 'Režim kopírování URL',
'hide_by_default' => 'Skrýt média ve výchozím nastavení',
'register_enabled' => 'Registrace povoleny',
'dont_close' => 'Nezavírejte tuto kartu před dokončením.',
'please_wait' => 'Čekejte prosím…',
'remember_me' => 'Pamatovat si mě',
'custom_head_set' => 'Vlastní hlavička HTML použita.',
'custom_head_html_hint' => 'Tento obsah bude přidán do tagu <head> na každé stránce.',
'custom_head_html' => 'Vlastní obsah hlavičky HTML',
'donate_text' => 'Pokud se vám líbí XBackBone, zvažte příspěvek na podporu vývoje!',
'donation' => 'Přispět',
'drop_to_upload' => 'Klikněte nebo sem přetáhněte soubory pro nahrání.',
'no_upload_token' => 'Nemáte osobní nahrávací token. (Vygenerujte si jej a zkuste to znovu.)',
'image_embeds' => 'Vložené obrázky',
];

164
resources/lang/da.lang.php Normal file
View file

@ -0,0 +1,164 @@
<?php
return [
'lang' => 'Danish',
'yes' => 'Ja',
'no' => 'Nej',
'send' => 'Send',
'no_media' => 'Intet media fundet.',
'login.username' => 'Brugernavn eller e-mail',
'password' => 'Adgangskode',
'login' => 'Log på',
'username' => 'Brugernavn',
'home' => 'Hjem',
'users' => 'Brugere',
'system' => 'System',
'profile' => 'Profil',
'logout' => 'Log ud',
'pager.next' => 'Næste',
'pager.previous' => 'Forrige',
'copy_link' => 'Kopier link',
'public.telegram' => 'Del på Telegram',
'public.delete_text' => 'Er du sikker på, at du vil slette dette element? Du vil ikke være i stand til at gendanne den',
'preview' => 'Forhåndsvisning',
'filename' => 'Filnavn',
'size' => 'Størrelse',
'public' => 'Offentligt',
'owner' => 'Ejer',
'date' => 'Dato',
'raw' => 'Vis rå',
'download' => 'Hent',
'delete' => 'Slet',
'publish' => 'Offentliggøre',
'hide' => 'Skjul',
'files' => 'Filer',
'orphaned_files' => 'Forældreløse filer',
'theme' => 'Tema',
'click_to_load' => 'Klik for at indlæse…',
'apply' => 'Anvend',
'save' => 'Gem',
'used' => 'Brugt',
'system_info' => 'System information',
'user.create' => 'Ny bruger',
'user.edit' => 'Rediger bruger',
'is_active' => 'Er aktiv',
'is_admin' => 'Er administrator',
'your_profile' => 'Din Profil',
'user_updated' => 'Bruger »%s« er opdateret!',
'user_created' => 'Bruger »%s« oprettet!',
'email_taken' => 'E-mail-adressen er allerede i brug.',
'email_required' => 'E-mail er påkrævet.',
'mail.new_account' => '%s - Oprettelse af ny konto',
'send_notification' => 'Send e-mail notifikation',
'recaptcha_enabled' => 'reCAPTCHA slået til',
'recaptcha_failed' => 'reCAPTCHA fejlede',
'delete_all' => 'Slet alt',
'delete_selected' => 'Slet valgte',
'used_space' => 'Brugt plads',
'password_repeat' => 'Gentag adgangskode',
'account_activated' => 'Konto aktiveret, du kan nu logge ind!',
'register' => 'Tilmeld',
'no_account' => 'Har du ikke en konto?',
'password_recovery' => 'Gendan adgangskode',
'export_data' => 'Eksporter data',
'settings_saved' => 'Systemindstillinger gemt!',
'copy_url_behavior' => 'Kopier URL tilstand',
'hide_by_default' => 'Skjul medie som standard',
'register_enabled' => 'Registreringer slået til',
'please_wait' => 'Vent venligst…',
'remember_me' => 'Husk mig',
'custom_head_html' => 'Tilpasset HTML Head indhold',
'donate_text' => 'Hvis du kan lide XBackBone, kan du overveje en donation for at støtte udviklingen!',
'donation' => 'Donering',
'drop_to_upload' => 'Klik eller slip dine filer her for at uploade.',
'auto_set' => 'Sæt automatisk',
'cancel' => 'Annuller',
'maintenance_in_progress' => 'Platformen er under vedligeholdelse, prøv igen senere…',
'updates' => 'Opdateringer',
'upgrade' => 'Opgrader',
'check_for_updates' => 'Søg efter opdateringer',
'file_size_no_match' => 'Den downloadede fil matcher ikke den korrekte filstørrelse.',
'cannot_retrieve_file' => 'Filen kan ikke hentes.',
'new_version_available' => 'Ny version %s tilgængelig!',
'already_latest_version' => 'Du har allerede den seneste version.',
'maintenance' => 'Vedligeholdelse',
'name' => 'Navn',
'time' => 'Tid',
'order_by' => 'Sorter efter…',
'dotted_search' => 'Søg…',
'table' => 'Tabel',
'gallery' => 'Galleri',
'switch_to' => 'Skift til',
'cannot_write_file' => 'Destinationsstien er ikke skrivbar.',
'cannot_demote' => 'Du kan ikke degradere dig selv.',
'cannot_delete' => 'Du kan ikke slette dig selv.',
'user_deleted' => 'Bruger slettet.',
'profile_updated' => 'Profilen blev opdateret!',
'password_required' => 'Adgangskoden er påkrævet.',
'username_taken' => 'Brugernavnet er allerede i brug.',
'username_required' => 'Brugernavnet er påkrævet.',
'goodbye' => 'Farvel!',
'welcome' => 'Velkommen, %s!',
'account_disabled' => 'Din konto er deaktiveret.',
'bad_login' => 'Forkerte loginoplysninger.',
'installed' => 'Installationen blev gennemført!',
'confirm_string' => 'Er du sikker?',
'confirm' => 'Bekræftelse',
'open' => 'Åbn',
'none' => 'Ingen',
'reg_date' => 'Registreringsdato',
'admin' => 'Admin',
'active' => 'Aktiv',
'user_code' => 'Bruger kode',
'client_config' => 'Klientkonfiguration',
'edit' => 'Rediger',
'update' => 'Opdater',
'copied' => 'Kopieret til udklipsholderen!',
'copy' => 'Kopier',
'token' => 'Nøgle',
'system_settings' => 'Systemindstillinger',
'php_info' => 'PHP Informationer',
'upload' => 'Overfør',
'enforce_language' => 'Håndhæve sproget',
'deleted_orphans' => 'Slettet %d forældreløse filer.',
'default_lang_behavior' => 'XBackBone vil prøve at matche browsersproget som standard (tilbagefaldet er Engelsk).',
'prerelease_channel' => 'Forhåndsudgivelse Kanal',
'token_not_found' => 'Den angivne token blev ikke fundet.',
'clean_orphaned_uploads' => 'Fjern forældreløse overførsler',
'custom_head_set' => 'Brugerdefineret HTML-hoved anvendt.',
'dont_close' => 'Luk ikke denne fane, før den er færdig.',
'default_user_quota' => 'Standard bruger plads',
'max_user_quota' => 'Max bruger plads',
'invalid_quota' => 'Ugyldige værdier som standard bruger plads.',
'mail.activate_account' => '%s - Aktivering af konto',
'mail.recover_password' => '%s - Gendan adgangskode',
'password_restored' => 'Nulstil kodeord.',
'recalculate_user_quota' => 'Genberegn bruger plads fra disk',
'quota_recalculated' => 'Bruger pladsen genberegnet fra disken med succes.',
'account_media_deleted' => 'Alle medier på kontoen er blevet slettet.',
'danger_zone' => 'Farligt område',
'recaptcha_keys_required' => 'Alle reCAPTCHA nøgler er påkrævet.',
'recaptcha_site_key' => 'reCAPTCHA webstedsnøgle',
'recaptcha_secret_key' => 'reCAPTCHA hemmelig nøgle',
'mail.new_account_text_with_reset' => 'Hej %s!<br>en ny konto blev oprettet til dig den %s (<a href="%s">%s</a>), klik på følgende link for at indstille en adgangskode og aktivere den:<br><br><a href="%s">%s</a>',
'user_create_password' => 'Hvis den efterlades tom, vil det være en god idé selv at sende en meddelelse til brugerens e-mailadresse.',
'ldap_cant_connect' => 'Kan ikke oprette forbindelse til LDAP-godkendelsesserveren.',
'upload_max_file_size' => 'Den maksimale filstørrelse er i øjeblikket %s.',
'no_tags' => 'Ingen tags tilføjet',
'auto_tagging' => 'Automatisk upload tagging',
'zip_ext_not_loaded' => 'Den nødvendige "zip"-udvidelse er ikke indlæst',
'changelog' => 'Ændringslog',
'show_changelog' => 'Vis ændringslog',
'no_upload_token' => 'Du har ikke et personligt upload token. (Generer en og prøv igen.)',
'custom_head_html_hint' => 'Dette indhold vil blive tilføjet ved <head> tagget på hver side.',
'register_success' => 'Kontoen er oprettet, en bekræftelses-e-mail er blevet sendt.',
'quota_enabled' => 'Aktiver brugerkvote',
'mail.activate_text' => 'Hej %s!<br>tak fordi du oprettede din konto på %s (<a href="%s">%s</a>), klik på følgende link for at aktivere den:<br><br><a href="%s">%s</a>',
'mail.recover_text' => 'Hej %s,<br>der er blevet anmodet om en nulstilling af adgangskoden til din konto. For at fuldføre proceduren, klik på følgende link:<br><br><a href="%s">%s</a><br><br>Hvis det ikke var dig, der anmodede om nulstilling af adgangskoden, skal du blot ignorere denne e-mail.',
'recover_email_sent' => 'Hvis tilstede, blev der sendt en gendannelses-e-mail til den angivne konto.',
'password_match' => 'Adgangskode og den gentaget adgangskode skal være ens.',
'clear_account' => 'Ryd Konto',
'only_recaptcha_v3' => 'Kun reCAPTCHA v3 understøttes.',
'image_embeds' => 'Integrer billeder',
'mail.new_account_text_with_pw' => 'Hej %s!<br>en ny konto blev oprettet til dig den %s (<a href="%s">%s</a>), med følgende oplysninger:<br><br>Brugernavn: %s<br>Adgangskode: %s<br><br>Klik på følgende link for at gå til login-siden:<br><a href="%s">%s</a>',
'path_not_writable' => 'Outputstien er ikke skrivbar.',
];

170
resources/lang/de.lang.php Normal file
View file

@ -0,0 +1,170 @@
<?php
return [
'lang' => 'Deutsch',
'yes' => 'Ja',
'no' => 'Nein',
'send' => 'Senden',
'no_media' => 'Datei nicht gefunden.',
'login.username' => 'Benutzername oder E-Mail',
'password' => 'Passwort',
'login' => 'Anmelden',
'username' => 'Benutzername',
'home' => 'Startseite',
'users' => 'Benutzer',
'system' => 'System',
'profile' => 'Profil',
'logout' => 'Abmelden',
'pager.next' => 'Nächste',
'pager.previous' => 'Zurück',
'copy_link' => 'Kopiere Link',
'public.telegram' => 'Teile auf Telegram',
'public.delete_text' => 'Möchtest du das wirklich löschen? Es wird dann für immer weg sein',
'preview' => 'Vorschau',
'filename' => 'Dateiname',
'size' => 'Größe aller Dateien',
'public' => 'Öffentlich',
'owner' => 'Besitzer',
'date' => 'Datum',
'raw' => 'zeige RAW',
'download' => 'Herunterladen',
'delete' => 'Löschen',
'publish' => 'Veröffentlichen',
'hide' => 'Verstecken',
'files' => 'Datei(n)',
'orphaned_files' => 'verwaiste Dateien',
'theme' => 'Design',
'click_to_load' => 'Zum Laden klicken …',
'apply' => 'Sichern',
'save' => 'Speichern',
'used' => 'Benutzt',
'system_info' => 'System Informationen',
'user.create' => 'Benutzer erstellen',
'user.edit' => 'Benutzer bearbeiten',
'is_active' => 'ist aktiviert',
'is_admin' => 'ist Administrator',
'your_profile' => 'Dein Profil',
'token' => 'Schlüssel',
'copy' => 'Kopieren',
'update' => 'Aktualisieren',
'edit' => 'Bearbeiten',
'client_config' => 'Benutzereinstellungen',
'user_code' => 'Benutzer-ID',
'active' => 'Aktiv',
'admin' => 'Administrator',
'reg_date' => 'Registrierungsdatum',
'none' => 'keine',
'open' => 'Öffnen',
'confirm' => 'Bestätigen',
'confirm_string' => 'Bist du sicher?',
'installed' => 'Installation erfolgreich abgeschlossen!',
'bad_login' => 'Falsche Anmeldeinformationen.',
'account_disabled' => 'Dein Account ist deaktiviert.',
'welcome' => 'Willkommen, %s!',
'goodbye' => 'Auf Wiedersehen!',
'token_not_found' => 'Das angegebene Schlüssel wurde nicht gefunden.',
'email_required' => 'E-Mail-Adresse erforderlich.',
'email_taken' => 'Die E-Mail-Adresse wird bereits verwendet.',
'username_required' => 'Der Benutzername ist erforderlich.',
'username_taken' => 'Der Benutzername ist bereits vergeben.',
'password_required' => 'Das Passwort ist erforderlich.',
'user_created' => 'Benutzer „%s“ wurde erstellt!',
'user_updated' => 'Benutzer „%s“ wurde aktualisiert!',
'profile_updated' => 'Profil erfolgreich aktualisiert!',
'user_deleted' => 'Benutzer gelöscht.',
'cannot_delete' => 'Du kannst dich nicht selbst löschen.',
'cannot_demote' => 'Du kannst dich nicht selber degradieren.',
'cannot_write_file' => 'Der Ordner hat keine Schreibrechte.',
'switch_to' => 'Wechseln zu',
'gallery' => 'Galerie',
'table' => 'Tabelle',
'dotted_search' => 'Suche …',
'order_by' => 'Sortieren nach …',
'time' => 'Zeit',
'name' => 'Name',
'maintenance' => 'Wartungsarbeiten',
'clean_orphaned_uploads' => 'Leere verwaiste Uploads',
'path_not_writable' => 'Der Speicherort ist nicht beschreibbar.',
'already_latest_version' => 'Du hast bereits die neueste Version.',
'new_version_available' => 'Neue Version %s ist verfügbar!',
'cannot_retrieve_file' => 'Die Datei kann nicht abgerufen werden.',
'file_size_no_match' => 'Die heruntergeladene Datei stimmt mit der richtigen Dateigröße nicht überein.',
'check_for_updates' => 'Auf Aktualisierungen prüfen',
'upgrade' => 'Aktualisierung',
'updates' => 'Aktualisierungen',
'maintenance_in_progress' => 'Plattform wird gewartet, versuchen Sie es später erneut …',
'cancel' => 'Abbruch',
'deleted_orphans' => '%d verwaiste Dateie(n) wurden erfolgreich gelöscht.',
'enforce_language' => 'Sprache erzwingen',
'auto_set' => 'Automatisch einstellen',
'translated_strings' => 'übersetzte Zeichen',
'total_strings' => 'Übersetzt',
'lang_name' => 'Name von der Sprache',
'default_lang_behavior' => 'XBackBone wird versuchen die Sprache deines Browsers herauszufinden (Standardsprache is Englisch).',
'lang_set' => 'Sprache ist jetzt "%s"',
'prerelease_channel' => 'Beta Channel',
'upload' => 'Hochladen',
'no_upload_token' => 'Du hast keinen persönlichen Token. (Erstelle einen und versuche es erneut.)',
'drop_to_upload' => 'Hier klicken oder Dateien hierher ziehen.',
'donation' => 'Spenden',
'donate_text' => 'Wenn dir XBackBone gefällt und du die Entwicklung unterstützen möchtest, spende einen kleinen Beitrag!',
'custom_head_html' => 'Eigenes HTML Head Content',
'custom_head_html_hint' => 'Dieser Inhalt wird auf jeder Seite am Tag <head> hinzugefügt.',
'custom_head_set' => 'Benutzerdefinierter HTML Head wurde angewendet.',
'remember_me' => 'Merken',
'please_wait' => 'Bitte warten …',
'dont_close' => 'Schließe diesen Tab erst wenn fertig.',
'php_info' => 'PHP Informationen',
'system_settings' => 'Systemeinstellungen',
'register_enabled' => 'Registrierungen aktiviert',
'hide_by_default' => 'Medien standardmäßig ausblenden',
'copy_url_behavior' => 'URL kopieren Mode',
'settings_saved' => 'Systemeinstellungen gespeichert!',
'export_data' => 'Daten exportieren',
'password_recovery' => 'Passwort wiederherstellen',
'no_account' => 'Haben Sie kein Konto?',
'register' => 'Registrieren',
'default_user_quota' => 'Standard Speicherplatz',
'invalid_quota' => 'Ungültiger Wert für den Speicherplatz.',
'register_success' => 'Das Konto wurde erstellt, eine Bestätigungs-E-Mail wurde gesendet.',
'mail.activate_account' => '%s Aktivierung des Kontos',
'mail.recover_password' => '%s Zurücksetzung des Passworts',
'recover_email_sent' => 'Falls vorhanden, wurde eine Wiederherstellungs-E-Mail an das angegebene Konto gesendet.',
'account_activated' => 'Konto wurde aktiviert, du kannst dich jetzt anmelden!',
'quota_enabled' => 'Aktiviere Speicherlimit',
'password_repeat' => 'Passwort wiederholen',
'password_match' => 'Das Passwort und das wiederholte Passwort muss das gleiche sein.',
'password_restored' => 'Passwort wurde zurückgesetzt.',
'recalculate_user_quota' => 'Benutzer Speicher neu berechnen',
'quota_recalculated' => 'Die Berechnung vom Benutzer Speicher war erfolgreich.',
'used_space' => 'Belegter Speicherplatz',
'max_user_quota' => 'Max. Benutzerkontingent',
'delete_selected' => 'Ausgewähltes löschen',
'delete_all' => 'Alle löschen',
'clear_account' => 'Konto löschen',
'account_media_deleted' => 'Alle Medien in dem Konto wurden gelöscht.',
'danger_zone' => 'Gefahrenzone',
'recaptcha_failed' => 'reCAPTCHA Fehlgeschlagen',
'recaptcha_enabled' => 'reCAPTCHA Aktiviert',
'recaptcha_keys_required' => 'reCAPTCHA-Schlüssel ist erforderlich.',
'only_recaptcha_v3' => 'Es wird nur reCAPTCHA v3 unterstützt.',
'recaptcha_site_key' => 'reCAPTCHA Websiteschlüssel',
'recaptcha_secret_key' => 'reCAPTCHA geheimen Schlüssel',
'send_notification' => 'E-Mail-Benachrichtigung senden',
'mail.new_account' => '%s Erstellung von Konto',
'user_create_password' => 'Wenn das leer bleibt, wollen Sie vielleicht eine Benachrichtigung an die Benutzer per E-Mail senden.',
'no_tags' => 'Keine Tags hinzugefügt',
'show_all_tags' => 'Alle Tags anzeigen',
'upload_max_file_size' => 'Die maximale Dateigröße beträgt derzeit %s.',
'ldap_cant_connect' => 'Es kann keine Verbindung zum LDAP-Auth-Server hergestellt werden.',
'zip_ext_not_loaded' => 'Die zip-Erweiterung ist erforderlich',
'auto_tagging' => 'Automatische Markierung des Hochladen',
'mail.new_account_text_with_pw' => 'Hallo %s! <br>ein neues Konto wurde für Dich auf %s (<a href="%s">%s</a>) erstellt, mit den folgenden Anmeldeinformationen:<br><br>Benutzername: %s<br>Password: %s<br><br>Klicken Sie auf den folgenden Link, um zur Anmeldeseite zu gelangen:<br><a href="%s">%s</a>',
'mail.new_account_text_with_reset' => 'Hallo %s! <br>ein neues Konto wurde für dich auf %s erstellt (<a href="%s">%s</a>), klicken auf den folgenden Link, um ein Passwort festzulegen und es zu aktivieren:<br><br><a href="%s">%s</a>',
'mail.recover_text' => 'Hallo %s,<br>für Ihr Konto wurde eine Zurücksetzung des Passworts angefordert. Um das Verfahren abzuschließen, klicken Sie auf den folgenden Link:<br><br><a href="%s">%s</a><br><br>Wenn Sie keine Zurücksetzung Ihres Passworts angefordert haben, ignorieren Sie diese E-Mail einfach.',
'mail.activate_text' => 'Hallo %s! <br>Vielen Dank fürs Registrieren auf %s (\'a href="%s">%s</a>), klicken Sie auf den folgenden Link, um es zu aktivieren:<br><br>\'a href="%s">%s</a>',
'show_changelog' => 'Änderungsprotokoll ansehen',
'changelog' => 'Änderungsprotokoll',
'copied' => 'In Zwischenablage kopiert!',
'image_embeds' => 'Bilder einbetten',
'vanity_url' => 'Benutzerdefinierte URL',
];

165
resources/lang/en.lang.php Executable file
View file

@ -0,0 +1,165 @@
<?php
return [
'lang' => 'English',
'enforce_language' => 'Enforce language',
'yes' => 'Yes',
'no' => 'No',
'send' => 'Send',
'no_media' => 'No media found.',
'login.username' => 'Username or E-Mail',
'password' => 'Password',
'login' => 'Login',
'username' => 'Username',
'home' => 'Home',
'users' => 'Users',
'system' => 'System',
'profile' => 'Profile',
'logout' => 'Logout',
'pager.next' => 'Next',
'pager.previous' => 'Previous',
'copy_link' => 'Copy link',
'public.telegram' => 'Share on Telegram',
'public.delete_text' => 'Are you sure you want to delete this item? You will not be able to recover it',
'preview' => 'Preview',
'filename' => 'Filename',
'size' => 'Size',
'public' => 'Public',
'owner' => 'Owner',
'date' => 'Date',
'raw' => 'Show raw',
'download' => 'Download',
'upload' => 'Upload',
'delete' => 'Delete',
'confirm' => 'Confirm',
'vanity_url' => 'Custom URL',
'publish' => 'Publish',
'hide' => 'Hide',
'files' => 'Files',
'orphaned_files' => 'Orphaned Files',
'theme' => 'Theme',
'click_to_load' => 'Click to load…',
'apply' => 'Apply',
'save' => 'Save',
'used' => 'Used',
'php_info' => 'PHP Informations',
'system_settings' => 'System Settings',
'user.create' => 'Create User',
'user.edit' => 'Edit User',
'is_active' => 'Is active',
'is_admin' => 'Is administrator',
'your_profile' => 'Your Profile',
'token' => 'Token',
'copy' => 'Copy',
'copied' => 'Copied to clipboard!',
'update' => 'Update',
'edit' => 'Edit',
'client_config' => 'Client Configuration',
'user_code' => 'User Code',
'active' => 'Active',
'admin' => 'Admin',
'reg_date' => 'Registration Date',
'none' => 'None',
'open' => 'Open',
'confirm_string' => 'Are you sure?',
'installed' => 'Installation completed successfully!',
'bad_login' => 'Wrong credentials.',
'account_disabled' => 'Your account is disabled.',
'welcome' => 'Welcome, %s!',
'goodbye' => 'Goodbye!',
'token_not_found' => 'Token specified not found.',
'email_required' => 'E-mail address required.',
'email_taken' => 'The e-mail address is already in use.',
'username_required' => 'The username is required.',
'username_taken' => 'The username is already taken.',
'password_required' => 'The password is required.',
'user_created' => 'User "%s" created!',
'user_updated' => 'User "%s" updated!',
'profile_updated' => 'Profile updated successfully!',
'user_deleted' => 'User deleted.',
'cannot_delete' => 'You cannot delete yourself.',
'cannot_demote' => 'You cannot demote yourself.',
'cannot_write_file' => 'The destination path is not writable.',
'deleted_orphans' => 'Successfully deleted %d orphaned files.',
'switch_to' => 'Switch to',
'gallery' => 'Gallery',
'table' => 'Table',
'dotted_search' => 'Search…',
'order_by' => 'Order by…',
'time' => 'Time',
'name' => 'Name',
'maintenance' => 'Maintenance',
'clean_orphaned_uploads' => 'Clean Orphaned Uploads',
'path_not_writable' => 'The output path is not writable.',
'already_latest_version' => 'You already have the latest version.',
'new_version_available' => 'New version %s available!',
'cannot_retrieve_file' => 'Cannot retrieve the file.',
'file_size_no_match' => 'The downloaded file doesn\'t match the correct file size.',
'check_for_updates' => 'Check for updates',
'upgrade' => 'Upgrade',
'updates' => 'Updates',
'maintenance_in_progress' => 'Platform under maintenance, try again later…',
'cancel' => 'Cancel',
'auto_set' => 'Set automatically',
'default_lang_behavior' => 'XBackBone will try to match the browser language by default (the fallback is English).',
'prerelease_channel' => 'Prerelease Channel',
'no_upload_token' => 'You don\'t have a personal upload token. (Generate one and try again.)',
'drop_to_upload' => 'Click or drop your files here to upload.',
'donation' => 'Donation',
'donate_text' => 'If you like XBackBone, consider a donation to support development!',
'custom_head_html' => 'Custom HTML Head content',
'custom_head_html_hint' => 'This content will be added at the <head> tag on every page.',
'custom_head_set' => 'Custom HTML head applied.',
'remember_me' => 'Remember me',
'please_wait' => 'Please wait…',
'dont_close' => 'Do not close this tab until completion.',
'register_enabled' => 'Registrations enabled',
'hide_by_default' => 'Hide media by default',
'copy_url_behavior' => 'Copy URL mode',
'settings_saved' => 'System settings saved!',
'export_data' => 'Export data',
'password_recovery' => 'Recover password',
'no_account' => 'Don\'t have an account?',
'register' => 'Register',
'register_success' => 'The account has been created, a confirmation e-mail has been sent.',
'default_user_quota' => 'Default User Quota',
'max_user_quota' => 'Max User Quota',
'invalid_quota' => 'Invalid values as default user quota.',
'mail.activate_text' => 'Hi %s!<br>thank you for creating your account on %s (<a href="%s">%s</a>), click on the following link to activate it:<br><br><a href="%s">%s</a>',
'mail.activate_account' => '%s - Account Activation',
'mail.recover_text' => 'Hi %s,<br>a password reset has been requested for your account. To complete the procedure click on the following link:<br><br><a href="%s">%s</a><br><br>If it wasn\'t you who requested the password reset, simply ignore this e-mail.',
'mail.recover_password' => '%s - Password Recovery',
'recover_email_sent' => 'If present, a recovery e-mail was sent to the specified account.',
'account_activated' => 'Account activated, now you can login!',
'quota_enabled' => 'Enable user quota',
'password_repeat' => 'Repeat Password',
'password_match' => 'Password and repeat password must be the same.',
'password_restored' => 'Password reset.',
'recalculate_user_quota' => 'Recalculate user quota from disk',
'quota_recalculated' => 'User quota recalculated from the disk successfully.',
'used_space' => 'Used Space',
'delete_selected' => 'Delete Selected',
'delete_all' => 'Delete All',
'clear_account' => 'Clear Account',
'account_media_deleted' => 'All media in the account have been deleted.',
'danger_zone' => 'Danger Zone',
'recaptcha_failed' => 'reCAPTCHA Failed',
'recaptcha_enabled' => 'reCAPTCHA Enabled',
'recaptcha_keys_required' => 'All reCAPTCHA keys are required.',
'only_recaptcha_v3' => 'Only reCAPTCHA v3 is supported.',
'recaptcha_site_key' => 'reCAPTCHA Site Key',
'recaptcha_secret_key' => 'reCAPTCHA Secret Key',
'send_notification' => 'Send E-mail Notification',
'mail.new_account' => '%s - New Account Creation',
'mail.new_account_text_with_reset' => 'Hi %s!<br>a new account was created for you on %s (<a href="%s">%s</a>), click on the following link to set a password and activate it:<br><br><a href="%s">%s</a>',
'mail.new_account_text_with_pw' => 'Hi %s!<br>a new account was created for you on %s (<a href="%s">%s</a>), with the following credentials:<br><br>Username: %s<br>Password: %s<br><br>Click on the following link to go to the login page:<br><a href="%s">%s</a>',
'user_create_password' => 'If leaved empty, you might want to send a notification to the user e-mail address.',
'ldap_cant_connect' => 'Can\'t connect to the LDAP auth server.',
'upload_max_file_size' => 'The max file size is currently %s.',
'no_tags' => 'No tags added',
'show_all_tags' => 'Show all tags',
'auto_tagging' => 'Auto upload tagging',
'zip_ext_not_loaded' => 'The required "zip" extension is not loaded',
'changelog' => 'Changelog',
'show_changelog' => 'Show changelog',
'image_embeds' => 'Embed images'
];

170
resources/lang/es.lang.php Normal file
View file

@ -0,0 +1,170 @@
<?php
return [
'lang' => 'Español',
'yes' => 'Sí',
'no' => 'No',
'send' => 'Enviar',
'no_media' => 'Contenido no encontrado.',
'login.username' => 'Nombre de usuario o correo electrónico',
'password' => 'Contraseña',
'login' => 'Iniciar sesión',
'username' => 'Nombre de usuario',
'home' => 'Inicio',
'users' => 'Usuarios',
'system' => 'Sistema',
'profile' => 'Perfil',
'logout' => 'Cerrar sesión',
'pager.next' => 'Siguiente',
'pager.previous' => 'Previo',
'copy_link' => 'Copiar enlace',
'public.telegram' => 'Compartir en Telegram',
'public.delete_text' => '¿Está seguro que desea borrar este elemento? No vas a poder recuperarlo',
'preview' => 'Vista previa',
'filename' => 'Nombre del archivo',
'size' => 'Tamaño',
'public' => 'Público',
'owner' => 'Dueño',
'date' => 'Fecha',
'raw' => 'Mostrar archivo sin formato',
'download' => 'Descargar',
'delete' => 'Borrar',
'publish' => 'Publicar',
'hide' => 'Esconder',
'files' => 'Archivos',
'orphaned_files' => 'Archivos huérfanos',
'theme' => 'Tema',
'click_to_load' => 'Clic para cargar…',
'apply' => 'Aplicar',
'save' => 'Guardar',
'used' => 'Usado',
'system_info' => 'Información del Sistema',
'user.create' => 'Crear Usuario',
'user.edit' => 'Editar Usuario',
'is_active' => 'Es activo',
'is_admin' => 'Es administrador',
'your_profile' => 'Tu Perfil',
'token' => 'Ficha',
'copy' => 'Copiar',
'update' => 'Actualizar',
'edit' => 'Editar',
'client_config' => 'Configuración del Cliente',
'user_code' => 'Código de Usuario',
'active' => 'Activo',
'admin' => 'Administrador',
'reg_date' => 'Fecha de Registración',
'none' => 'Ninguno',
'open' => 'Abrir',
'confirm' => 'Confirmar',
'confirm_string' => '¿Está seguro?',
'installed' => '¡Instalación completa!',
'bad_login' => 'Credenciales incorrectas.',
'account_disabled' => 'Su cuenta está desactivada.',
'welcome' => '¡Bienvenido, %s!',
'goodbye' => '¡Adiós!',
'token_not_found' => 'Ficha indicada No encontrada.',
'email_required' => 'El correo electrónico es requisito.',
'email_taken' => 'El correo electrónico ya está en uso.',
'username_required' => 'El Nombre de Usuario es requisito.',
'username_taken' => 'El Nombre de Usuario ya está en uso.',
'password_required' => 'La Contraseña es requisito.',
'user_created' => '¡Usuario «%s» creado!',
'user_updated' => '¡Usuario «%s» actualizado!',
'profile_updated' => '¡Perfil actualizado con éxito!',
'user_deleted' => 'Usuario borrado.',
'cannot_delete' => 'No puede borrarse a usted mismo.',
'cannot_write_file' => 'La ruta de destino no se puede escribir.',
'deleted_orphans' => 'Borró los archivos huérfanos %d con éxito.',
'switch_to' => 'Cambiar a',
'gallery' => 'Galería',
'table' => 'Tabla',
'dotted_search' => 'Búsqueda…',
'order_by' => 'Ordenar por…',
'time' => 'Tiempo',
'name' => 'Nombre',
'maintenance' => 'Mantenimiento',
'path_not_writable' => 'La ruta de salida no se puede escribir.',
'already_latest_version' => 'Ya tiene la última versión.',
'new_version_available' => '¡Nueva versión %s disponible!',
'cannot_retrieve_file' => 'No se puede recuperar el archivo.',
'file_size_no_match' => 'El archivo descargado no coincide con el tamaño de archivo correcto.',
'check_for_updates' => 'Buscar actualizaciones',
'upgrade' => 'Mejorar',
'updates' => 'Actualizaciones',
'maintenance_in_progress' => 'Plataforma en mantenimiento, inténtelo más tarde…',
'cancel' => 'Cancelar',
'enforce_language' => 'Afirmar lenguaje',
'auto_set' => 'Establecer automáticamente',
'translated_strings' => 'cadenas de texto traducidas',
'total_strings' => 'total de cadenas de texto',
'lang_name' => 'nombre del lenguaje',
'default_lang_behavior' => 'XBackBone tratará de coincidir con el lenguaje predeterminado del navegador (recurrimos al Inglés automáticamente).',
'lang_set' => 'Lenguaje del sistema impuesto a "%s"',
'upload' => 'Subir',
'delete_all' => 'Borrar Todo',
'danger_zone' => 'Zona De Peligro',
'recaptcha_failed' => 'reCAPTCHA Falló',
'php_info' => 'Información PHP',
'system_settings' => 'Configuracion de Systema',
'mail.new_account' => '% s Creación de nueva cuenta',
'send_notification' => 'Enviar notificación por correo electrónico',
'mail.recover_password' => '% s Recuperación de contraseña',
'mail.activate_account' => '%s Activación de la cuenta',
'register_success' => 'La cuenta ha sido creada, se ha enviado un correo electrónico de confirmación.',
'donate_text' => 'Si te gusta XBackBone, considera una donación para apoyar el desarrollo!',
'donation' => 'Donación',
'drop_to_upload' => 'Haz clic o arrastra tus archivos aquí para subirlos.',
'no_upload_token' => 'No tienes una clave personal de carga. (Genera uno e inténtalo de nuevo.)',
'prerelease_channel' => 'Canal de prelanzamiento',
'clean_orphaned_uploads' => 'Limpiar las cargas de los huéspedes',
'cannot_demote' => 'No puedes degradarte.',
'copied' => '¡Copiado en el portapapeles!',
'quota_recalculated' => 'Cuota de usuario recalculada desde el disco con éxito.',
'recalculate_user_quota' => 'Recalcular la cuota de usuarios desde el disco',
'quota_enabled' => 'Activar la cuota de usuarios',
'show_changelog' => 'Mostrar el registro de cambios',
'changelog' => 'Registro de cambios',
'zip_ext_not_loaded' => 'La extensión zip requerida no está cargada',
'auto_tagging' => 'Carga automática de etiquetas',
'no_tags' => 'No se han añadido etiquetas',
'upload_max_file_size' => 'El tamaño máximo del archivo es actualmente %s.',
'ldap_cant_connect' => 'No se puede conectar con el servidor de autentificación LDAP.',
'user_create_password' => 'Si se deja vacío, es posible que desee enviar una notificación a la dirección de correo electrónico del usuario.',
'mail.new_account_text_with_pw' => '¡Hola %s!<br>se ha creado una nueva cuenta para ti en %s (<a href="%s">%s</a>), haz clic en el siguiente enlace para establecer una contraseña y activarla:<br><br><a href="%s">%s</a>',
'mail.new_account_text_with_reset' => '¡Hola, %s!<br>Se creó una nueva cuenta para ti en %s (<a href="%s">%s</a>), haz clic en el siguiente enlace para establecer una contraseña y activarla:<br ><br><a href="%s">%s</a>',
'recaptcha_enabled' => 'reCAPTCHA activado',
'account_media_deleted' => 'Se han eliminado todos los archivos de la cuenta.',
'clear_account' => 'Limpiar cuenta',
'delete_selected' => 'Eliminar seleccionado',
'used_space' => 'Espacio usado',
'password_restored' => 'Restablecimiento de la contraseña.',
'password_match' => 'La contraseña y la contraseña repetida deben ser las mismas.',
'password_repeat' => 'Repite la contraseñá',
'account_activated' => 'Cuenta activada, ¡ahora puedes iniciar sesión!',
'recover_email_sent' => 'Si está presente, se ha enviado un correo electrónico de recuperación a la cuenta especificada.',
'mail.recover_text' => 'Hola %s,<br>se ha solicitado el restablecimiento de la contraseña de su cuenta. Para completar el procedimiento haga clic en el siguiente enlace:<br><br><a href="%s">%s</a><br>Si no fue usted quien solicitó el restablecimiento de la contraseña, simplemente ignore este correo electrónico.',
'mail.activate_text' => 'Hola %s!<br>gracias por crear tu cuenta en %s (<a href="%s">%s</a>), haz clic en el siguiente enlace para activarla:<br><br><a href="%s">%s</a>',
'invalid_quota' => 'Valores no válidos como cuota de usuario por defecto.',
'max_user_quota' => 'Cuota máxima del usuario',
'default_user_quota' => 'Cuota de usuarios por defecto',
'register' => 'Registrarse',
'no_account' => '¿No tienes cuenta?',
'password_recovery' => 'Recuperar contraseña',
'export_data' => 'Exportar datos',
'settings_saved' => '¡Configuración del sistema guardada!',
'copy_url_behavior' => 'Modo copiar URL',
'hide_by_default' => 'Ocultar media por defecto',
'register_enabled' => 'Registros abiertos',
'dont_close' => 'No cierres esta pestaña hasta que se haya completado.',
'please_wait' => 'Por favor, espera…',
'remember_me' => 'Recuerdame',
'custom_head_html_hint' => 'Este contenido se añadirá en la etiqueta <head> de cada página.',
'custom_head_html' => 'Contenido personalizado del encabezado HTML',
'image_embeds' => 'Incrustar imágenes',
'recaptcha_keys_required' => 'Todas las claves reCAPTCHA son requeridas.',
'recaptcha_secret_key' => 'Clave secreta de reCAPTCHA',
'recaptcha_site_key' => 'Clave del sitio reCAPTCHA',
'only_recaptcha_v3' => 'Solo reCAPTCHA v3 está soportado.',
'custom_head_set' => 'Cabecera HTML personalizada aplicada.',
'vanity_url' => 'URL personalizada',
'show_all_tags' => 'Mostrar todas las etiquetas',
];

Some files were not shown because too many files have changed in this diff Show more