Skip to content

Commit

Permalink
Added localization support and CSRF guard
Browse files Browse the repository at this point in the history
  • Loading branch information
foobar1643 committed Jun 11, 2016
1 parent 9dd999c commit 964efb3
Show file tree
Hide file tree
Showing 29 changed files with 852 additions and 159 deletions.
7 changes: 7 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## Contributing translations
##### Extracting gettext strings from .twig files
To extract a translatable text strings from application templates, you can use `cli-tools/template-extractor.php` CLI script.
It will automatically make .php files from application templates and save it to the `translation-cache` directory.
After that, you can import application source code into any editor that works with GNU gettext (for example - [Poedit]).

[Poedit]: <https://poedit.net/>
87 changes: 51 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,53 +1,68 @@
# Filehosting - light and easy filesharing app

### Возможности
* Анонимная загрузка файлов на сервер
* Счетчик общего количества скачиваний для файла
* Страница отображения популярных и последних файлов
* Для загрузившего файл пользователя - возможность удаления файла
* Вывод уменьшеной копии изображений на странице скачивания
* Плеер для аудио и видео файлов на странице скачивания
* Для изображений, видео и аудио файлов вывод дополнительной информации
* Возможность оставлять комментарии к файлам без перезагрузки страницы
* Древовидные комментарии к файлам без перезагрузки страницы
* Полнотекстовый поиск по именам файлов
* Настройка максимального размера загружаемых файлов в конфигурации приложения
* Простой интерфейс для добавления/удаления файлов из консоли администратора
### Features
* Anonymous file upload
* Downloads counter for each file
* For uploader - ability to delete a file from the app
* Thumbnails for images on the download page
* Support for multiple languages
* Player for audio and video files on the download page
* Additional info for images, audio and video files on the download page
* Ability to leave comments for files without reloading the page
* Tree-like comments for files
* Fulltext search in files names
* Ability to set maximum file size for uploaded files through configuration file
* Simple command line interface for administrators, that allows to add/delete files and comments

### Используемые технологии
### Used technologies
1. [Twitter Bootstrap]
2. Микрофреймворк [Slim]
3. Шаблонизатор [Twig]
4. Плеер для видео [video.js]
5. Совместимая с Composer [GetId3]
2. [Slim] micro framework
3. [Twig] template engine
4. [jQuery] javascript library
5. [video.js] video player
6. Composer compatible [GetId3]

### Требования
1. Веб-сервер с поддержкой [PHP] *5.5* и выше.
2. База данных [PostgreSQL].
3. Поисковый движок [Sphinx].
4. Пакетный менеджер [Composer].
### Requirements
1. Web server with [PHP] *5.6* support.
2. [PostgreSQL] database.
3. [Sphinx] search engine.
4. [Composer] packet manager.
5. Cron

### Установка
1. Загрузите файлы приложения на ваш веб-сервер.
2. Настройте ваш веб-сервер таким образом, чтобы корневой директорией являлась папка `public`
3. Установите зависимости приложения с помощью команды `composer install`
4. Настройте конфигурацию путей на своем веб-сервере [как описано здесь].
5. Отредактируйте необходимые параметры для подключения к БД и поисковому движку в файлах `config.ini` и `sphinx.conf`
6. Импортируйте файл `filehosting.sql` в вашу базу данных.
7. Измените права доступа к папкам `storage` и `thumbnails` с помощью команд `chmod 0777 ./storage` и `chmod 0777 public/thumbnails`
8. Отредактируйте файл конфигурации поискового движка Sphinx, или замените его готовым файлом `sphinx.conf`.
9. Инициализируйте поисковые индексы используя команду `indexer --all`
10. Добавьте в свой `crontab` запись для автоматического запуска файла переиндексации `cli-tools/reindex.reindex.sh`.
11. Для использования в режиме &laquo;продакшен&raquo; измените `dispaly_errors` на **0** в файле `php.ini`
### Install
1. Clone the repository using `git clone https://github.com/foobar1643/filehosting.git` command.
2. On your web server set `public` directory as a document root.
3. Install application dependencies using `composer install` command.
4. Configure pathing on your web server [as described here].
5. Set your database credentials in `config.ini` and `sphinx.conf`.
6. Import `filehosting.sql` into your database.
7. Edit your Sphinx configuration file or replace it with already configured `sphinx.conf` file.
8. Initialize search indexes with the `indexer --all` command.
9. In order to enable automatic reindexing add `cli-tools/reindex/reindex.sh` to your crontab.
10. For production usage, change `dispaly_errors` option to **0** in your `php.ini`

### Additional configuration
##### Configuring X-Sendfile
If your server has X-Sendfile module installed and configured, you can enable file downloading with the use of X-Sendfile.
To do that you'll need to set `enableXsendfile` option in `config.ini` to `1`. If you're using Nginx don't forget to set `storage` folder [as internal] in your `nginx.conf`.
Proper file downloading with the use of X-Sendfile guaranteed only for Apache and Nginx servers.
##### Configuring sphinx storage directories
If you want Sphinx to store its logs and indices in a different directory, you can specify the path using
`SPHINX_ROOT` environment variable. Default value is `/var/sphinx/`.

### Contributing
If you want to contribute a translation, please refer to [CONTRIBUTING.md].

[PHP]: <https://secure.php.net/>
[Sphinx]: <http://sphinxsearch.com/>
[PostgreSQL]: <http://www.postgresql.org/>
[Composer]: <https://getcomposer.org/>
[GetId3]: <https://github.com/phansys/GetId3>
[jQuery]: <https://jquery.org/>
[video.js]: <http://videojs.com/>
[Twig]: <http://twig.sensiolabs.org/>
[Slim]: <http://www.slimframework.com/>
[Twitter Bootstrap]: <http://getbootstrap.com/>
[как описано здесь]: <http://www.slimframework.com/docs/start/web-servers.html>
[as described here]: <http://www.slimframework.com/docs/start/web-servers.html>
[as internal]: <https://nginx.org/en/docs/http/ngx_http_core_module.html#internal>
[CONTRIBUTING.md]: <https://github.com/foobar1643/filehosting/blob/master/CONTRIBUTING.md>
11 changes: 9 additions & 2 deletions app/Controller/FileController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
use Dflydev\FigCookies\FigRequestCookies;
use \Filehosting\Entity\File;

class FileController
Expand All @@ -12,6 +13,7 @@ class FileController
private $view;
private $fileHelper;
private $fileMapper;
private $langHelper;
private $authHelper;
private $idHelper;
private $commentHelper;
Expand All @@ -20,6 +22,7 @@ public function __construct(\Slim\Container $c)
{
$this->container = $c;
$this->view = $c->get('view');
$this->langHelper = $c->get('LanguageHelper');
$this->fileHelper = $c->get('FileHelper');
$this->fileMapper = $c->get('FileMapper');
$this->commentHelper = $c->get('CommentHelper');
Expand Down Expand Up @@ -52,7 +55,11 @@ public function __invoke(Request $request, Response $response, $args)
'replyTo' => $replyTo,
'commentErrors' => $commentErrors,
'canManageFile' => $this->authHelper->canManageFile($request, $file),
'comments' => $this->commentHelper->getComments($file->getId())]);
'comments' => $this->commentHelper->getComments($file->getId()),
'messageFormatter' => new \MessageFormatter(\Locale::getDefault(), _("{0, plural, =0{No downloads} one{# download} other{# downloads}}")),
'lang' => $args['lang'],
'showLangMessage' => $this->langHelper->canShowLangMsg($request),
'csrf_token' => FigRequestCookies::get($request, "csrf_token")->getValue()]);
}

public function deleteFile(Request $request, Response $response, $args)
Expand All @@ -62,6 +69,6 @@ public function deleteFile(Request $request, Response $response, $args)
if($this->fileHelper->fileExists($args['id']) && $this->authHelper->canManageFile($request, $file)) {
$this->fileHelper->deleteFile($file);
}
return $response->withRedirect("/");
return $response->withRedirect("/{$args['lang']}/");
}
}
14 changes: 9 additions & 5 deletions app/Controller/SearchController.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ class SearchController

private $view;
private $searchHelper;
private $langHelper;

public function __construct(\Slim\Container $c)
{
$this->view = $c->get('view');
$this->searchHelper = $c->get('SearchHelper');
$this->langHelper = $c->get('LanguageHelper');
}

public function __invoke(Request $request, Response $response, $args)
Expand All @@ -32,7 +34,7 @@ public function __invoke(Request $request, Response $response, $args)
$error = true;
} else {
$query = $params["query"];
$pager = new PaginationHelper(self::RESULTS_PER_PAGE, "/search?query={$query}");
$pager = new PaginationHelper(self::RESULTS_PER_PAGE, "/{$args['lang']}/search/?query={$query}");
if(isset($params["page"])) {
$page = $pager->checkPage($params["page"]);
}
Expand All @@ -43,10 +45,12 @@ public function __invoke(Request $request, Response $response, $args)
}
return $this->view->render($response, 'search.twig', [
'error' => $error,
"query" => $query,
"page" => intval($page),
"pager" => $pager,
"files" => $searchResults["results"]]
'query' => $query,
'page' => intval($page),
'pager' => $pager,
'files' => $searchResults["results"],
'lang' => $args['lang'],
'showLangMessage' => $this->langHelper->canShowLangMsg($request)]
);
}
}
7 changes: 5 additions & 2 deletions app/Controller/UploadController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ class UploadController
private $config;
private $validator;
private $authHelper;
private $langHelper;

public function __construct(\Slim\Container $c)
{
$this->view = $c->get('view');
$this->config = $c->get('config');
$this->fileHelper = $c->get('FileHelper');
$this->langHelper = $c->get('LanguageHelper');
$this->authHelper = $c->get('AuthHelper');
$this->validator = $c->get('Validation');
}
Expand All @@ -39,11 +41,12 @@ public function __invoke(Request $request, Response $response, $args)
->setUploader('Anonymous')
->setAuthToken($this->authHelper->getUserToken($request));
$file = $this->fileHelper->uploadFile($file, $uploadedFiles["uploaded-file"]);
return $response->withRedirect("/file/{$file->getId()}");
return $response->withRedirect("/{$args['lang']}/file/{$file->getId()}/");
}
}
return $this->view->render($response, 'upload.twig',
['sizeLimit' => $this->config->getValue('app', 'sizeLimit'),
'errors' => $errors]);
'errors' => $errors, 'lang' => $args['lang'],
'showLangMessage' => $this->langHelper->canShowLangMsg($request)]);
}
}
3 changes: 2 additions & 1 deletion app/Entity/Comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ public function getDatePosted()
{
$dateTime = new \DateTime();
$dateTime->setTimestamp(strtotime($this->date_posted));
return $dateTime->format("m/d/Y, g:i A");
$dateFormater = new \IntlDateFormatter(\Locale::getDefault(), \IntlDateFormatter::SHORT, \IntlDateFormatter::MEDIUM);
return $dateFormater->format($dateTime);
}

public function setDatePosted($datePosted)
Expand Down
3 changes: 2 additions & 1 deletion app/Entity/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ public function getUploadDate()
{
$dateTime = new \DateTime();
$dateTime->setTimestamp(strtotime($this->upload_date));
return $dateTime->format("m/d/Y, g:i A");
$dateFormater = new \IntlDateFormatter(\Locale::getDefault(), \IntlDateFormatter::SHORT, \IntlDateFormatter::MEDIUM);
return $dateFormater->format($dateTime);
}

public function setUploadDate($uploadDate)
Expand Down
88 changes: 88 additions & 0 deletions app/Helper/LanguageHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

namespace Filehosting\Helper;

use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
use Dflydev\FigCookies\SetCookie;
use Dflydev\FigCookies\FigResponseCookies;
use Dflydev\FigCookies\FigRequestCookies;

class LanguageHelper
{
const PATH_TO_LOCALES = "/var/www/filehosting/locale";
const AVAILABLE_LANGUAGES = ["en", "ru"];

public function getUserLocale(Request $request)
{
$serverParams = $request->getServerParams();
$httpLanguage = isset($serverParams['HTTP_ACCEPT_LANGUAGE']) ? $serverParams['HTTP_ACCEPT_LANGUAGE'] : NULL;
return $this->composeFromLanguage($httpLanguage);
}

public function getAppLocale(Request $request)
{
$requestTarget = $request->getRequestTarget();
$language = preg_split("/\//", $requestTarget);
$urlLanguage = isset($language[1]) ? $language[1] : NULL;
return $this->composeFromLanguage($urlLanguage);
}

public function composeFromLanguage($language)
{
$parsedLocale = \Locale::parseLocale($language);
$split = explode(",", $parsedLocale['language']);
$composedLocale = array(
'language'=> $split[0],
'region' => isset($parsedLocale['region']) ? $parsedLocale['region'] : strtoupper($split[0])
);
return \Locale::composeLocale($composedLocale);
}

public function getLanguageDisplayName($language)
{
$locale = $this->composeFromLanguage($language);
$displayLanguage = \Locale::getDisplayLanguage($locale, $locale);
return mb_strtoupper(mb_substr($displayLanguage, 0, 1)) . mb_substr($displayLanguage, 1);
}

public function getAvailableLanguages()
{
return self::AVAILABLE_LANGUAGES;
}

public function languageAvailable($locale)
{
$parsedLocale = \Locale::parseLocale($locale);
if(in_array($parsedLocale['language'], self::AVAILABLE_LANGUAGES)) {
return true;
}
return false;
}

public function getLangMsgViews(Request $request)
{
return intval(FigRequestCookies::get($request, 'langChangeShown')->getValue());
}

public function setLangMsgViews($views, Request $request, Response $response)
{
$dateTime = new \DateTime("now");
$dateTime->add(new \DateInterval("PT3H")); // 3 hours
$response = FigResponseCookies::set($response,
SetCookie::create('langChangeShown')->withValue($views)
->withExpires($dateTime->format(\DateTime::COOKIE))->withPath('/'));
return $response;
}

public function canShowLangMsg(Request $request)
{
$msgShown = $this->getLangMsgViews($request);
$appLocale = $this->getAppLocale($request);
$userLocale = $this->getUserLocale($request);
if($userLocale != $appLocale && $this->languageAvailable($userLocale) && $msgShown < 10) {
return true;
}
return false;
}
}
14 changes: 7 additions & 7 deletions app/Helper/UploadHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,25 @@ public static function parseCode($code)
{
switch($code) {
case UPLOAD_ERR_INI_SIZE:
return "Размер файла превышает максимально допустимый.";
return _("File size is exceeding the maximum.");
break;
case UPLOAD_ERR_FORM_SIZE:
return "Размер файла превышает максимально допустимый.";
return _("File size is exceeding the maximum.");
break;
case UPLOAD_ERR_PARTIAL:
return "Ошибка при загрузке файла.";
return _("Error while uploading file.");
break;
case UPLOAD_ERR_NO_FILE:
return "Заполните все поля и попробуйте еще раз.";
return _("Attach a file and try again.");
break;
case UPLOAD_ERR_NO_TMP_DIR:
return "На сервере отсутствует папка для временных файлов. Обратитесь к администратору или попробуйте еще раз.";
return _("Can't access temporary folder. Contact the server administrator or try again.");
break;
case UPLOAD_ERR_CANT_WRITE:
return "Ошибка при записи файла. Обратитесь к администратору или попробуйте еще раз.";
return _("An error occurred while writing the file. Contact the server administrator or try again.");
break;
default:
return "Неизвестная ошибка с кодом: {$code}.";
return _("Unknown error with code: {$code}.");
break;
}
}
Expand Down
Loading

0 comments on commit 964efb3

Please sign in to comment.