1
0
Fork 0
forked from deblan/gist

Compare commits

...

47 commits

Author SHA1 Message Date
Showfom 3d1a6f8158 Added Simplified Chinese Translation
Added Simplified Chinese Translation
2017-06-03 16:59:28 +02:00
Simon Vieille 3fcdae9270 Default configuration values 2017-04-24 01:23:08 +02:00
Simon Vieille 2f1d57d36b Merge branch 'dev-master' 2017-04-24 01:19:15 +02:00
Simon Vieille 0b6c4d7ac1 Fix theme name typo 2017-04-24 01:18:21 +02:00
Simon Vieille 4a035ba7ed Mage conf file renamed 2017-04-24 01:16:22 +02:00
Simon Vieille 706823c04a Config file ignored 2017-04-24 01:15:04 +02:00
Simon Vieille 87bd9e9db5 Documentation 2017-04-24 01:11:54 +02:00
Simon Vieille 48eacc5cb8 New configuration file 2017-04-24 01:11:39 +02:00
Simon Vieille df86035225 Default and light themes 2017-04-24 01:11:09 +02:00
Simon Vieille 43db48a715 Merge branch 'dev-master' 2017-04-23 16:59:29 +02:00
Simon Vieille c7a03c4bf9 Indent 2017-04-23 16:57:27 +02:00
Simon Vieille 61b071feb8 Merge branch 'dev-master' 2017-04-23 16:31:11 +02:00
Simon Vieille 36f4fb98ff Title filter in account page 2017-04-23 16:30:26 +02:00
Simon Vieille 9dfbde6730 Title filter in account page 2017-04-23 16:29:41 +02:00
Simon Vieille 74b23a59b5 README updated to explain upgrade (issue #9) 2017-02-16 10:51:39 +01:00
Simon Vieille 2702a1d987 README updated to explain upgrade (issue #9) 2017-02-16 10:50:21 +01:00
Simon Vieille 221e28832f README updated to explain upgrade (issue #9) 2017-02-15 16:18:29 +01:00
Simon Vieille ab47699731 Fix issue #9 2017-02-15 16:13:34 +01:00
Simon Vieille eb1f4f1df3 README: upgrade 2016-12-23 11:23:58 +01:00
Simon Vieille 0e02add6e6 Adding of field that contains the number of commits 2016-12-23 11:17:56 +01:00
Simon Vieille df3035f2f1 Form to change the password 2016-12-23 10:28:09 +01:00
Simon Vieille a79b443b36 DE translations 2016-12-21 22:55:06 +01:00
Simon Vieille 82790998d4 DE translations 2016-12-21 22:52:23 +01:00
Simon Vieille 63ba878e80 Fix typo 2016-11-17 00:13:09 +01:00
Simon Vieille b0f74d8dc8 PHP Documentation 2016-11-13 00:44:23 +01:00
Simon Vieille aa73578efe Gitignore 2016-11-13 00:43:42 +01:00
Simon Vieille 3f29ab2574 gitignore 2016-10-29 16:12:08 +02:00
Simon Vieille 22cf9ab3e5 gitignore 2016-10-29 16:10:22 +02:00
Simon Vieille b7b93460b1 Contributors 2016-10-03 23:35:26 +02:00
Simon Vieille f27f74e0e9 Webserver documentation 2016-10-03 23:32:01 +02:00
Simon Vieille 7bba890025 Webserver documentation 2016-10-03 23:29:49 +02:00
Simon Vieille 3e297ec85f German disabled 2016-10-03 19:23:29 +02:00
Simon Vieille 41fefddc16 Issue fixed 2016-10-03 19:11:26 +02:00
Simon Vieille 67c674c52e Spanish translations 2016-10-03 19:10:34 +02:00
Simon Vieille 724fee29bb Contributors 2016-10-03 17:41:01 +02:00
Simon Vieille be31903aaa README 2016-10-02 20:31:08 +02:00
Simon Vieille 12e6efc471 README 2016-09-28 09:18:33 +02:00
Simon Vieille 2f65088c5e README 2016-09-28 00:49:50 +02:00
Simon Vieille 1b961403ad README 2016-09-28 00:43:54 +02:00
Simon Vieille 841211a63a README 2016-09-28 00:25:47 +02:00
Simon Vieille 1006e079f6 Indent 2016-09-26 17:42:39 +02:00
Simon Vieille da7bb48893 Cipher: translation removed 2016-09-26 17:41:57 +02:00
Simon Vieille 0529ec16d0 [security] XSS injection patch 2016-09-26 01:41:08 +02:00
Simon Vieille 2ddfea60cc Cipher: embeded links updated 2016-09-24 16:42:13 +02:00
Simon Vieille f25e427dcc Cipher alert removed 2016-09-24 16:10:32 +02:00
Simon Vieille 96f199adaa Fix issue #1 2016-09-24 16:07:35 +02:00
Simon Vieille 6ab827bf94 Ciphered GIST cloning 2016-09-24 14:43:15 +02:00
67 changed files with 1928 additions and 558 deletions

22
.gitignore vendored
View file

@ -1,9 +1,13 @@
.mage/config/environment/*.yml /.mage/config/environment/*.yml
.mage/logs/*.log /.mage/logs/*.log
composer.lock /composer.lock
vendor/ /vendor/
tags /propel.yml
*.swp /src/Gist/Model/Base/
propel.yml /src/Gist/Model/Map/
src/Gist/Model/Base/ /web/components/
src/Gist/Model/Map/ /app/propel/
/app/config/config.yml
/app/config/propel/
/data/
/trans/

View file

@ -8,6 +8,7 @@ deployment:
- "*.svn" - "*.svn"
- "*.git" - "*.git"
- "*.swp" - "*.swp"
- "app/config/config.yml"
- "app/config/propel/" - "app/config/propel/"
- "app/propel/" - "app/propel/"
- "data/git" - "data/git"

View file

@ -31,6 +31,7 @@ update:
@echo "-----------------------------------" @echo "-----------------------------------"
@echo @echo
sh -c 'test -d app && $(GIT) add app && $(GIT) commit -m "Configuration"'
$(GIT) pull origin master $(GIT) pull origin master
${MKDIR} -p data/git ${MKDIR} -p data/git
$(COMPOSER) update $(COMPOSER) update

287
README.md
View file

@ -3,16 +3,18 @@ Table of Contents
* [GIST](#gist) * [GIST](#gist)
* [Requirements](#requirements) * [Requirements](#requirements)
* [Installation](#installation)
* [Git](#git) * [Git](#git)
* [Composer](#composer) * [Composer](#composer)
* [Bower](#bower) * [Bower](#bower)
* [Upgrade](#upgrade) * [Installation](#installation)
* [Upgrade](#upgrade)
* [Configuration](#configuration)
* [Makefile](#makefile) * [Makefile](#makefile)
* [API](#api) * [API](#api)
* [Console](#console) * [Console](#console)
* [Configuration](#configuration)
* [Deployment](#deployment) * [Deployment](#deployment)
* [Contributors](#contributors)
GIST GIST
==== ====
@ -34,20 +36,6 @@ Requirements
* Composer (php) * Composer (php)
* Bower (node) * Bower (node)
Installation
------------
$ git clone https://gitnet.fr/deblan/gist
$ cd gist
$ make
$ mv propel-dist.yaml propel.yaml
$ # EDIT propel.yaml (dsn)
$ make propel
Edit `app/bootstrap.php.d/70-security.php` and modify the valye of `$app['token']` with a strong secret phrase.
Screencast: https://asciinema.org/a/19814
### Git ### Git
Git can maybe be downloaded from your system's repositories. Git can maybe be downloaded from your system's repositories.
@ -60,57 +48,131 @@ Git can maybe be downloaded from your system's repositories.
Composer can maybe be downloaded from your system's repositories. Composer can maybe be downloaded from your system's repositories.
Else, follow the next instructions: Else, follow the next instructions:
#### Download # With cURL
$ curl -sS https://getcomposer.org/installer | php
# With cURL # With Wget
curl -sS https://getcomposer.org/installer | php $ wget -O - -q https://getcomposer.org/installer | php
# With Wget $ chmod +x composer.phar
wget -O - -q https://getcomposer.org/installer | php
You can now use it with `php composer.phar [arguments]`. # For a local installation and if the envvar PATH contains "$HOME/bin/"
$ mv composer.phar ~/bin/composer
#### Executable # For a global installation
$ sudo mv composer.phar /usr/local/bin/composer
mv composer.phar composer
chmod +x composer
Use it with `./composer [arguments]`.
#### Install
Assuming `~/bin` exists ans is in `$PATH`.
mv composer ~/bin
#### Dependencies Installation (from `composer.lock`)
composer install
#### Dependencies Update (will change `composer.lock`)
composer update
### Bower ### Bower
$ sudo apt-get install npm
$ sudo npm install -g bower
#### Install Installation
------------
npm install -g bower $ cd /path/to/www/
$ git clone https://gitnet.fr/deblan/gist
$ cd gist
$ make
$ cp propel-dist.yaml propel.yaml
#### Dependencies Installation (from `bower.json`) Edit `propel.yaml`. **Use spaces instead of tabulations**.
bower install **MySQL**
#### Dependencies Update propel:
database:
connections:
default:
adapter: mysql
# http://www.php.net/manual/en/ref.pdo-mysql.connection.php
dsn: "mysql:host=DATABASE_SERVER;dbname=DATABASE_NAME"
user: DATEBASE_USER
password: DATEBASE_PASSWORD
settings:
charset: utf8
queries:
utf8: "SET NAMES utf8 COLLATE utf8_unicode_ci, COLLATION_CONNECTION = utf8_unicode_ci, COLLATION_DATABASE = utf8_unicode_ci, COLLATION_SERVER = utf8_unicode_ci"
bower install [...]
**SQLITE**
propel:
database:
connections:
default:
adapter: sqlite
# http://www.php.net/manual/en/ref.pdo-sqlite.connection.php
dsn: "sqlite:/PATH/TO/gist.sqlite"
user: ~
password: ~
[...]
Then `$ make propel`.
**Versions >= 1.4.4 only**: `$ cp app/config/config.yml.dist app/config/config.yml`
See the [configuration section](#configuration) for more information about configuration.
---
The web server must have permission to write into `data`.
$ sudo chown -R www-data:www-data data
Your webserver must be configured to serve `web/` as document root. If you use nginx, all virtual paths must be rooted with `web/index.php` or `web/app_dev.php` ([documentation](https://www.nginx.com/resources/wiki/start/topics/recipes/symfony/)). If you use apache, you must enable the `rewrite` module and restart:
$ sudo a2enmod rewrite
$ sudo service apache2 restart
`app_dev.php` is the development router. Access is granted for an IP range defined in the same file.
Upgrade
-------
If your version is less than v1.4.2, run: `test -d app && git add app && git commit -m "Configuration"`.
### Upgrade
$ make update $ make update
$ make propel $ make propel
If you upgrade to v1.4.1, run: `app/console migrate:to:v1.4.1`.
If you upgrade to v1.4.4 or more, the configuration is moved to a `app/config/config.yml`: `$ cp app/config/config.yml.dist app/config/config.yml` and see the [configuration section](#configuration) for more information.
Configuration
-------------
### Version < 1.4.4
Edit `app/bootstrap.php.d/70-security.php`.
* `$app['token']`: the securty token (a strong passphrase).
* `$app['enable_registration']`: defines if the registration is allowed (`true` or `false`)
* `$app['enable_login']`: defines if the login is allowed (`true` or `false`)
* `$app['login_required_to_edit_gist']`: defines if the user must be logged to create or clone a Gist (`true` or `false`)
* `$app['login_required_to_view_gist']`: defines if the user must be logged to view a Gist (`true` or `false`)
* `$app['login_required_to_view_gist']`: defines if the user must be logged to view an embeded Gist (`true` or `false`)
If you install Gist on your server, you have to modify the `base_uri` of the API.
Edit `app/bootstrap.php.d/60-api.php` and replace `https://gist.deblan.org/`.
### Version >= 1.4.4
Edit `app/config/config.yml`.
* `security.token`: the securty token (a strong passphrase)
* `security.enable_registration`: defines if the registration is allowed (`true` or `false`)
* `security.enable_login`: defines if the login is allowed (`true` or `false`)
* `security.login_required_to_edit_gist`: defines if the user must be logged to create or clone a Gist (`true` or `false`)
* `security.login_required_to_view_gist`: defines if the user must be logged to view a Gist (`true` or `false`)
* `security.login_required_to_view_gist`: defines if the user must be logged to view an embeded Gist (`true` or `false`)
* `api.base_uri`: The url of your instance.
* `data.path`: the path where the files are saved.
* `git.path`: The path of `git`.
* `theme.name`: the name of the theme (`dark` or `light`)
Makefile Makefile
-------- --------
@ -130,15 +192,18 @@ API
**POST** /{locale}/api/create **POST** /{locale}/api/create
Params: Params:
* ```form[title]```: String (required, can be empty) * `form[title]`: String (required, can be empty)
* ```form[type]```: String (required) * `form[type]`: String (required)
Values: html, css, javascript, php, sql, xml, yaml, perl, c, asp, python, bash, actionscript3, text Values: html, css, javascript, php, sql, xml, yaml, perl, c, asp, python, bash, actionscript3, text
* ```form[content]```: String (required) * `form[content]`: String (required)
#### Responses: **Responses:**
* Code ```200```: A json which contains gist's information. Example: * Code `405`: Method Not Allowed
```javascript * Code `400`: Bad Request
* Code `200`: A json which contains gist's information. Example:
```javascript
{ {
"url": "https:\/\/gist.deblan.org\/en\/view\/55abcfa7771e0\/f4afbf72967dd95e3461490dcaa310d728d6a97d", "url": "https:\/\/gist.deblan.org\/en\/view\/55abcfa7771e0\/f4afbf72967dd95e3461490dcaa310d728d6a97d",
"gist": { "gist": {
@ -151,22 +216,23 @@ Params:
"UpdatedAt": "2015-07-19T16:26:15Z" "UpdatedAt": "2015-07-19T16:26:15Z"
} }
} }
``` ```
* Code ```405```: Method Not Allowed
* Code ```400```: Bad Request
### Update an existing Gist ### Update an existing gist
**POST** /{locale}/api/update/{id} **POST** /{locale}/api/update/{id}
Params: Params:
* ```{id}```: Gist Id (required) * `{id}`: Gist Id (required)
* ```form[content]```: String (required) * `form[content]`: String (required)
#### Responses: **Responses:**
* Code ```200```: A json which contains gist's information. Example: * Code `405`: Method Not Allowed
```javascript * Code `400`: Bad Request
* Code `200`: A json which contains gist's information. Example:
```javascript
{ {
"url": "https:\/\/gist.deblan.org\/en\/view\/55abcfa7771e0\/abcgi72967dd95e3461490dcaa310d728d6adef", "url": "https:\/\/gist.deblan.org\/en\/view\/55abcfa7771e0\/abcgi72967dd95e3461490dcaa310d728d6adef",
"gist": { "gist": {
@ -179,90 +245,49 @@ Params:
"UpdatedAt": "2015-07-19T16:30:15Z" "UpdatedAt": "2015-07-19T16:30:15Z"
} }
} }
``` ```
* Code ```405```: Method Not Allowed
* Code ```400```: Bad Request
Console Console
------- -------
### Create and update gists: * **Create a gist**: `$ app/console --help create`
* **Update a gist**: `$ app/console --help update`
``` * **Create user**: `app/console --help user:create`
$ app/console --help create * **Show stats**: `$ app/console --help stats`
$ app/console --help update
```
### Create user
```
$ app/console --help user:create
```
### Show stats
```
$ app/console --help stats
```
Configuration
-------------
### API
#### Personal instance
If you install Gist on your server, you have to modify the ```base_uri``` of the API.
Edit ```app/bootstrap.php.d/60-api.php``` and replace ```https://gist.deblan.org/```.
### Authentication
#### Disabling login
Edit `app/bootstrap.php.d/70-security.php` and modify the value of `$app['enable_login']` with `false`.
#### Disabling registration
Edit `app/bootstrap.php.d/70-security.php` and modify the value of `$app['enable_registration']` with `false`.
#### Force registration/login
##### Login required to edit a gist
Edit `app/bootstrap.php.d/70-security.php` and modify the value of `$app['login_required_to_edit_gist']` with `true`.
##### Login required to view a gist
Edit `app/bootstrap.php.d/70-security.php` and modify the value of `$app['login_required_to_view_gist']` with `true`.
##### Login required to view an embeded gist
Edit `app/bootstrap.php.d/70-security.php` and modify the value of `$app['login_required_to_view_embeded_gist']` with `true`.
### Debug
`app_dev.php` is the development router. Access is granted for an IP range defined in the same file.
Deployment Deployment
---------- ----------
Gist uses [Magallanes](http://magephp.com/) to manage deployment. Gist uses [Magallanes](http://magephp.com/) to manage deployment.
### Global installation **Global installation**
$ composer global require andres-montanez/magallanes $ composer global require andres-montanez/magallanes
# if the envvar PATH contains "$HOME/bin/" # if the envvar PATH contains "$HOME/bin/"
$ ln -s ~/.composer/vendor/bin/mage ~/bin/mage $ ln -s ~/.composer/vendor/bin/mage ~/bin/mage
### Local installation **Local installation**
$ composer require andres-montanez/magallanes $ composer require andres-montanez/magallanes
There is an example of the configuration of an environment in `.mage/config/environment/prod.yml-dist`. There is an example of the configuration of an environment in `.mage/config/environment/prod.yml.dist`.
# global installation # global installation
$ mage deploy to:prod $ mage deploy to:prod
# local installation # local installation
$ ./vendor/andres-montanez/magallanes/bin/mage deploy to:prod $ ./vendor/andres-montanez/magallanes/bin/mage deploy to:prod
Contributors
------------
**Developers**
* Simon Vieille <contact@deblan.fr>
**Translators**
* Simon Vieille <contact@deblan.fr>
* Marion Sanchez
* Marjorie Da Silva
* Mélanie Chanat

View file

@ -1,9 +1,14 @@
<?php <?php
use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\FileLocator;
use Symfony\Component\Yaml\Yaml;
$app['config.locator.path'] = $app['root_path'].'/app/config/'; $app['config.locator.path'] = $app['root_path'].'/app/config/';
$app['config.locator'] = function ($app) { $app['config.locator'] = function ($app) {
return new FileLocator($app['config.locator.path']); return new FileLocator($app['config.locator.path']);
}; };
$app['settings'] = $app->share(function ($app) {
return Yaml::parse($app['config.locator']->locate('config.yml'));
});

View file

@ -11,7 +11,7 @@ use Symfony\Component\HttpFoundation\Cookie;
$app->register(new TranslationServiceProvider(), array( $app->register(new TranslationServiceProvider(), array(
'locale' => 'en', 'locale' => 'en',
'locale_fallback' => 'en', 'locale_fallback' => 'en',
'locales' => array('en', 'fr'), 'locales' => array('en', 'fr', 'es', 'de'),
)); ));
$app['translator'] = $app->extend('translator', function ($translator, $app) { $app['translator'] = $app->extend('translator', function ($translator, $app) {

View file

@ -3,10 +3,16 @@
use GitWrapper\GitWrapper; use GitWrapper\GitWrapper;
use Gist\Service\Gist; use Gist\Service\Gist;
$app['gist_path'] = $app['root_path'].'/data/git'; $dataPath = $app['settings']['data']['path'];
if ($dataPath[0] !== '/') {
$app['gist_path'] = $app['root_path'].$dataPath;
} else {
$app['gist_path'] = $dataPath;
}
$app['git_wrapper'] = $app->share(function ($app) { $app['git_wrapper'] = $app->share(function ($app) {
return new GitWrapper('/usr/bin/git'); return new GitWrapper($app['settings']['git']['path']);
}); });
$app['git_working_copy'] = $app->share(function ($app) { $app['git_working_copy'] = $app->share(function ($app) {

View file

@ -3,5 +3,5 @@
use Gist\Api\Client; use Gist\Api\Client;
$app['api_client'] = $app->share(function ($app) { $app['api_client'] = $app->share(function ($app) {
return new Client(['base_uri' => 'https://gist.deblan.org/']); return new Client(['base_uri' => $app['settings']['api']['base_uri']]);
}); });

View file

@ -6,20 +6,14 @@ use Silex\Provider\RememberMeServiceProvider;
use Gist\Service\SaltGenerator; use Gist\Service\SaltGenerator;
use Gist\Security\AuthenticationProvider; use Gist\Security\AuthenticationProvider;
use Gist\Security\AuthenticationListener; use Gist\Security\AuthenticationListener;
use Gist\Security\AuthenticationEntryPoint;
use Gist\Security\LogoutSuccessHandler; use Gist\Security\LogoutSuccessHandler;
use Silex\Provider\SessionServiceProvider; use Silex\Provider\SessionServiceProvider;
use Symfony\Component\Security\Http\HttpUtils;
$app['enable_registration'] = true; $securitySettings = $app['settings']['security'];
$app['enable_login'] = true;
$app['login_required_to_edit_gist'] = false;
$app['login_required_to_view_gist'] = false;
$app['login_required_to_view_embeded_gist'] = false;
$app['token'] = 'ThisTokenIsNotSoSecretChangeIt'; $app['token'] = $securitySettings['token'];
$app['salt_generator'] = $app->share(function($app) { $app['salt_generator'] = $app->share(function ($app) {
return new SaltGenerator(); return new SaltGenerator();
}); });
@ -34,10 +28,10 @@ $app['security.authentication_listener.factory.form'] = $app->protect(function (
$app['security.authentication_provider.'.$name.'.form'] = $app->share(function ($app) { $app['security.authentication_provider.'.$name.'.form'] = $app->share(function ($app) {
return new AuthenticationProvider($app['user.provider']); return new AuthenticationProvider($app['user.provider']);
}); });
$app['security.authentication_listener.'.$name.'.form'] = $app->share(function ($app) use ($name) { $app['security.authentication_listener.'.$name.'.form'] = $app->share(function ($app) use ($name) {
return new AuthenticationListener( return new AuthenticationListener(
$app['security.token_storage'], $app['security.token_storage'],
$app['security.authentication_provider.'.$name.'.form'] $app['security.authentication_provider.'.$name.'.form']
); );
}); });
@ -46,7 +40,7 @@ $app['security.authentication_listener.factory.form'] = $app->protect(function (
'security.authentication_provider.'.$name.'.form', 'security.authentication_provider.'.$name.'.form',
'security.authentication_listener.'.$name.'.form', 'security.authentication_listener.'.$name.'.form',
null, null,
'pre_auth' 'pre_auth',
]; ];
}); });
@ -76,32 +70,31 @@ $firewall = [
], ],
'security.access_rules' => [ 'security.access_rules' => [
['^/[a-z]{2}/my.*$', 'ROLE_USER'], ['^/[a-z]{2}/my.*$', 'ROLE_USER'],
] ],
]; ];
if ($app['login_required_to_edit_gist'] || $app['login_required_to_view_gist'] || $app['login_required_to_view_embeded_gist']) { if ($securitySettings['login_required_to_edit_gist'] || $securitySettings['login_required_to_view_gist'] || $securitySettings['login_required_to_view_embeded_gist']) {
$securityRegexp = '^/[a-z]{2}';
$exceptedUriPattern = ['login', 'register']; $exceptedUriPattern = ['login', 'register'];
if ($app['login_required_to_view_gist'] === true) { if ($securitySettings['login_required_to_view_gist'] === true) {
$firewall['security.access_rules'][] = ['^/[a-z]{2}/view.*$', 'ROLE_USER']; $firewall['security.access_rules'][] = ['^/[a-z]{2}/view.*$', 'ROLE_USER'];
$firewall['security.access_rules'][] = ['^/[a-z]{2}/revs.*$', 'ROLE_USER']; $firewall['security.access_rules'][] = ['^/[a-z]{2}/revs.*$', 'ROLE_USER'];
} else { } else {
$exceptedUriPattern[] = 'view'; $exceptedUriPattern[] = 'view';
$exceptedUriPattern[] = 'revs'; $exceptedUriPattern[] = 'revs';
} }
if ($app['login_required_to_view_embeded_gist'] === true) { if ($securitySettings['login_required_to_view_embeded_gist'] === true) {
$firewall['security.access_rules'][] = ['^/[a-z]{2}/embed.*$', 'ROLE_USER']; $firewall['security.access_rules'][] = ['^/[a-z]{2}/embed.*$', 'ROLE_USER'];
} else { } else {
$exceptedUriPattern[] = 'embed'; $exceptedUriPattern[] = 'embed';
} }
if ($app['login_required_to_edit_gist'] === true) { if ($securitySettings['login_required_to_edit_gist'] === true) {
$firewall['security.access_rules'][] = ['^/[a-z]{2}/(?!('.implode('|', $exceptedUriPattern).')).*$', 'ROLE_USER']; $firewall['security.access_rules'][] = ['^/[a-z]{2}/(?!('.implode('|', $exceptedUriPattern).')).*$', 'ROLE_USER'];
} }
} }
$app->register(new SecurityServiceProvider(), $firewall); $app->register(new SecurityServiceProvider(), $firewall);
$app->register(new SessionServiceProvider()); $app->register(new SessionServiceProvider());
$app->register(new RememberMeServiceProvider()); $app->register(new RememberMeServiceProvider());

View file

@ -0,0 +1,15 @@
security:
token: ThisTokenIsNotSoSecretChangeIt
enable_registration: true
enable_login: true
login_required_to_edit_gist: false
login_required_to_view_gist: false
login_required_to_view_embeded_gist: false
api:
base_url: 'https://gist.deblan.org/'
data:
path: data/git
git:
path: /usr/bin/git
theme:
name: dark

View file

@ -5,6 +5,7 @@ use Gist\Command\CreateCommand;
use Gist\Command\UpdateCommand; use Gist\Command\UpdateCommand;
use Gist\Command\StatsCommand; use Gist\Command\StatsCommand;
use Gist\Command\UserCreateCommand; use Gist\Command\UserCreateCommand;
use Gist\Command\Migration\UpgradeTo1p4p1Command;
$app = require __DIR__.'/bootstrap.php'; $app = require __DIR__.'/bootstrap.php';
@ -12,5 +13,6 @@ $app['console']->add(new CreateCommand());
$app['console']->add(new UpdateCommand()); $app['console']->add(new UpdateCommand());
$app['console']->add(new StatsCommand()); $app['console']->add(new StatsCommand());
$app['console']->add(new UserCreateCommand()); $app['console']->add(new UserCreateCommand());
$app['console']->add(new UpgradeTo1p4p1Command());
$app['console']->run(); $app['console']->run();

102
app/locales/cn.yml Normal file
View file

@ -0,0 +1,102 @@
app:
title: '#!GIST'
title_prefix: '#!GIST - '
menu:
home:
title: '首页'
about:
title: '关于'
my:
login:
title: '登陆'
logout:
title: '注销'
register:
title: '注册'
my:
title: '账号'
my:
title: '我的 Gist'
nothing: '这家伙很懒,暂时没有内容'
gist:
untitled: '未命名'
404:
title: '哇!'
message: '这个 gist 未找到...'
action:
view: '查看'
history: '历史版本'
raw: '源文件'
download: '下载'
clone: '克隆'
embed: '引用:'
add: '新建'
date:
format: 'Y-m-d h:i:s'
footer:
text: '<p>Powered by <a href="https://gitnet.fr/deblan/gist">GIST</a>, it''s open source :) - <a href="https://gitnet.fr/deblan/gist#api">API</a></p>'
login:
login:
title: '登陆'
invalid: '用户名或密码错误'
form:
username:
placeholder: '用户名'
password:
placeholder: '密码'
remember_me:
label: '记住我'
register:
title: '注册新账号'
already_exists: '改用户名已被使用'
registred: '恭喜您,您的账号已经注册成功!'
form:
username:
placeholder: '用户名'
current_password:
placeholder: '当前密码'
password:
placeholder: '新密码'
form:
error:
password: '密码错误'
not_blank: '该选项不可为空'
title:
placeholder: '标题'
cipher:
label: '加密: %value%'
choice:
anyway: '随意'
yes: '是'
no: '否'
submit: '提交'
filter: '过滤器'
confirm: '确认此次操作?'
success:
password: '密码已更新'
gist: 'Gist 已移除'
type:
label: '语言: %value%'
choice:
all: 'All'
html: 'HTML'
xml: 'XML'
css: 'CSS'
javascript: 'JAVASCRIPT'
php: 'PHP'
sql: 'SQL'
yaml: 'YAML'
perl: 'PERL'
c: 'C/C++'
asp: 'ASP'
python: 'PYTHON'
bash: 'BASH'
actionscript3: 'ACTION SCRIPT'
text: 'TEXT'

102
app/locales/de.yml Normal file
View file

@ -0,0 +1,102 @@
app:
title: '#!GIST'
title_prefix: '#!GIST - '
menu:
home:
title: 'Home'
about:
title: 'Über uns'
my:
login:
title: 'Anmelden'
logout:
title: 'Abmelden'
register:
title: 'Konto öffnen'
my:
title: 'Mein Konto'
my:
title: 'Meine Gists'
nothing: 'Nichts zu finden (momentan)!'
gist:
untitled: 'Ohne Titel'
404:
title: 'Tja...'
message: 'Dieser Gist existiert nicht.'
action:
view: 'Anzeigen'
history: 'Commit(s)'
raw: 'RAW'
download: 'Herunterladen'
clone: 'Klonen'
embed: 'Einfügen:'
add: 'Hinzufügen'
date:
format: 'Y-m-d h:i:s'
footer:
text: '<p>Powered by <a href="https://gitnet.fr/deblan/gist">GIST</a>, it''s open source :) - <a href="https://gitnet.fr/deblan/gist#api">API</a></p>'
login:
login:
title: 'Login'
invalid: 'Anmeldung festgeschlagen.'
form:
username:
placeholder: 'Benutzername'
password:
placeholder: 'Passwort'
remember_me:
label: 'Remember me'
register:
title: 'New account'
already_exists: 'This username is already registred!'
registred: 'Congratulations, your account is created!'
form:
username:
placeholder: 'Username'
current_password:
placeholder: 'Aktuelles Passwort'
password:
placeholder: 'Password'
form:
error:
password: 'Ungültiges Passwort.'
not_blank: 'Dieser Wert darf nicht leer sein.'
title:
placeholder: 'Titel'
cipher:
label: 'Cipher: %value%'
choice:
anyway: 'Anyway'
yes: 'Yes'
no: 'No'
submit: 'Senden'
filter: 'Filter'
confirm: 'Bestätigen Sie?'
success:
password: 'Passwort aktualisiert.'
gist: 'Gist removed.'
type:
label: 'Language: %value%'
choice:
all: 'All'
html: 'HTML'
xml: 'XML'
css: 'CSS'
javascript: 'JAVASCRIPT'
php: 'PHP'
sql: 'SQL'
yaml: 'YAML'
perl: 'PERL'
c: 'C/C++'
asp: 'ASP'
python: 'PYTHON'
bash: 'BASH'
actionscript3: 'ACTION SCRIPT'
text: 'TEXT'

View file

@ -59,16 +59,18 @@ login:
form: form:
username: username:
placeholder: 'Username' placeholder: 'Username'
current_password:
placeholder: 'Current password'
password: password:
placeholder: 'Password' placeholder: 'Password'
form: form:
error: error:
password: 'Invalid password.'
not_blank: 'This value should not be blank bro!' not_blank: 'This value should not be blank bro!'
title: title:
placeholder: 'Title' placeholder: 'Title'
cipher: cipher:
alert: 'By enabling cipher, fork will be not possible.'
label: 'Cipher: %value%' label: 'Cipher: %value%'
choice: choice:
anyway: 'Anyway' anyway: 'Anyway'
@ -76,7 +78,9 @@ form:
no: 'No' no: 'No'
submit: 'Send' submit: 'Send'
filter: 'Filter' filter: 'Filter'
confirm: 'Do you confirm?'
success: success:
password: 'Password updated.'
gist: 'Gist removed.' gist: 'Gist removed.'
type: type:
label: 'Language: %value%' label: 'Language: %value%'

102
app/locales/es.yml Normal file
View file

@ -0,0 +1,102 @@
app:
title: '#!GIST'
title_prefix: '#!GIST - '
menu:
home:
title: 'Inicio'
about:
title: 'A propósito'
my:
login:
title: 'Conexión'
logout:
title: 'Desconexión'
register:
title: 'Apuntarse'
my:
title: 'Mi cuenta'
my:
title: 'Mis Gists'
nothing: 'Nada por ahora.'
gist:
untitled: 'Sin título'
404:
title: '¡Uy!'
message: "Este gist no existe."
action:
view: 'Visualizar'
history: 'Confirmacion(es)'
raw: 'RAW'
download: 'Descargar'
clone: 'Clonar'
embed: 'Insertar : '
add: 'Añadir'
date:
format: 'd/m/Y H\hi s\s'
footer:
text: '<p>Impulsado por <a href="https://gitnet.fr/deblan/gist">GIST</a>, es libre :) - <a href="https://gitnet.fr/deblan/gist#api">API</a></p>'
login:
login:
title: 'Identificación'
invalid: 'Nombre de usuario o contraseña incorrecta'
form:
username:
placeholder: 'Nombre de usuario'
password:
placeholder: 'Contraseña'
remember_me:
label: 'Recordar mis datos'
register:
title: 'Nueva cuenta'
already_exists: 'Este nombre de usuario ya está en uso'
registred: '¡ Enhorabuena, su cuenta fue creada !'
form:
username:
placeholder: 'Nombre de usuario'
current_password:
placeholder: 'Contraseña actual'
password:
placeholder: 'Contraseña'
form:
error:
password: 'Contraseña no válida.'
not_blank: 'Se necesita introducir este dato.'
title:
placeholder: 'Título'
cipher:
label: 'Cifrar : %value%'
choice:
anyway: 'Da igual'
yes: 'Sí'
no: 'No'
submit: 'Enviar'
filter: 'Filtrar'
confirm: '¿Confirmas?'
success:
password: 'Contraseña cambiada.'
gist: 'Su Gist ha sido eliminado.'
type:
label: 'Lenguaje : %value%'
choice:
all: 'Todos'
html: 'HTML'
xml: 'XML'
css: 'CSS'
javascript: 'JAVASCRIPT'
php: 'PHP'
sql: 'SQL'
yaml: 'YAML'
perl: 'PERL'
c: 'C/C++'
asp: 'ASP'
python: 'PYTHON'
bash: 'BASH'
actionscript3: 'ACTION SCRIPT'
text: 'TEXTE'

View file

@ -59,17 +59,18 @@ login:
form: form:
username: username:
placeholder: 'Nom d''utilisateur' placeholder: 'Nom d''utilisateur'
current_password:
placeholder: 'Mot de passe courrant'
password: password:
placeholder: 'Mot de passe' placeholder: 'Mot de passe'
form: form:
error: error:
password: 'Mot de passe invalide.'
not_blank: 'Vous devez saisir cette donnée.' not_blank: 'Vous devez saisir cette donnée.'
title: title:
placeholder: 'Titre' placeholder: 'Titre'
cipher: cipher:
alert: 'En activant le chiffrement, le fork deviendra impossible.'
label: 'Chiffrer : %value%' label: 'Chiffrer : %value%'
choice: choice:
anyway: 'Peu importe' anyway: 'Peu importe'
@ -77,7 +78,9 @@ form:
no: 'Non' no: 'Non'
submit: 'Envoyer' submit: 'Envoyer'
filter: 'Filtrer' filter: 'Filtrer'
confirm: 'Confirmez-vous ?'
success: success:
password: 'Mot de passe modifié.'
gist: 'Votre Gist a bien a été supprimé.' gist: 'Votre Gist a bien a été supprimé.'
type: type:
label: 'Langage : %value%' label: 'Langage : %value%'

View file

@ -11,11 +11,12 @@
"GIT" "GIT"
], ],
"license": "LGPL", "license": "LGPL",
"homepage": "https://gitlab.deblan.org/deblan/gist.deblan.org", "homepage": "https://gitnet.fr/deblan/gist",
"dependencies": { "dependencies": {
"bootstrap": "3.3.4", "bootstrap": "3.3.4",
"flag-icon-css": "0.7.1", "flag-icon-css": "0.7.1",
"SyntaxHighlighter": "3.0.83", "SyntaxHighlighter": "3.0.83",
"iframe-resizer": "2.8.6" "iframe-resizer": "2.8.6",
"jsdiff": "~2.2.2"
} }
} }

View file

@ -7,7 +7,6 @@ propel:
dsn: "mysql:host=localhost;dbname=gist" dsn: "mysql:host=localhost;dbname=gist"
user: root user: root
password: root password: root
attributes:
settings: settings:
charset: utf8 charset: utf8
queries: queries:

View file

@ -5,14 +5,34 @@ namespace Gist\Api;
use GuzzleHttp\Client as BaseClient; use GuzzleHttp\Client as BaseClient;
/** /**
* Class Client * Class Client.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class Client extends BaseClient class Client extends BaseClient
{ {
/**
* URI of creation
*
* @const string
*/
const CREATE = '/en/api/create'; const CREATE = '/en/api/create';
/**
* URI of updating
*
* @const string
*/
const UPDATE = '/en/api/update/{gist}'; const UPDATE = '/en/api/update/{gist}';
/**
* Creates a gist
*
* @param string $title The title
* @param string $type The type
* @param string $content The content
* @return array
*/
public function create($title, $type, $content) public function create($title, $type, $content)
{ {
$response = $this->post( $response = $this->post(
@ -34,7 +54,15 @@ class Client extends BaseClient
return []; return [];
} }
/**
* Clones and update a gist
*
* @param string $gist Gist's ID
* @param string $content The content
*
* @return array
*/
public function update($gist, $content) public function update($gist, $content)
{ {
$response = $this->post( $response = $this->post(

View file

@ -5,16 +5,23 @@ namespace Gist;
use Silex\Application as SilexApplication; use Silex\Application as SilexApplication;
/** /**
* @deprecated The static version should be avoided, use DI instead. * @deprecated The static version should be avoided, use DI instead
*/ */
class Application extends SilexApplication class Application extends SilexApplication
{ {
/**
* Creates an instance of Application.
*
* @static
*
* @return Application
*/
public static function getInstance() public static function getInstance()
{ {
static $app; static $app;
if (null === $app) { if (null === $app) {
$app = new static; $app = new static();
} }
return $app; return $app;

View file

@ -8,8 +8,16 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
/**
* class CreateCommand.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class CreateCommand extends Command class CreateCommand extends Command
{ {
/**
* {@inheritdoc}
*/
protected function configure() protected function configure()
{ {
$types = implode(', ', $this->getTypes()); $types = implode(', ', $this->getTypes());
@ -45,6 +53,9 @@ EOF
); );
} }
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
//$output->writeln(sprintf('<comment>%s</comment> bar.', 'test')); //$output->writeln(sprintf('<comment>%s</comment> bar.', 'test'));
@ -88,7 +99,7 @@ EOF
return true; return true;
} }
if ($input->getOption('show-id')) { if ($input->getOption('show-id')) {
$output->writeln($gist['gist']['Id']); $output->writeln($gist['gist']['Id']);
@ -98,6 +109,11 @@ EOF
$output->writeln(json_encode($gist)); $output->writeln(json_encode($gist));
} }
/**
* Returns the list of types.
*
* @return array
*/
protected function getTypes() protected function getTypes()
{ {
$types = array( $types = array(

View file

@ -0,0 +1,44 @@
<?php
namespace Gist\Command\Migration;
use Knp\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Gist\Model\GistQuery;
/**
* class UpgradeTo1p4p1Command.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class UpgradeTo1p4p1Command extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('migrate:to:v1.4.1')
->setDescription('Migrates database entries to >= v1.4.1')
->setHelp('The <info>%command.name%</info> migrates database entries to >= v1.4.1');
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$app = $this->getSilexApplication();
$gists = GistQuery::create()
->filterByCommits(0)
->find();
foreach ($gists as $gist) {
$commits = $app['gist']->getNumberOfCommits($gist);
$gist->setCommits($commits);
$gist->save();
}
}
}

View file

@ -10,8 +10,16 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Helper\Table;
use GitWrapper\GitException; use GitWrapper\GitException;
/**
* class StatsCommand;
*
* @author Simon Vieille <simon@deblan.fr>
*/
class StatsCommand extends Command class StatsCommand extends Command
{ {
/**
* {@inheritdoc}
*/
protected function configure() protected function configure()
{ {
$this $this
@ -23,6 +31,9 @@ EOF
); );
} }
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$gistService = $this->getSilexApplication()['gist']; $gistService = $this->getSilexApplication()['gist'];
@ -41,18 +52,13 @@ EOF
$withEncryption[$gist->getType()] = 0; $withEncryption[$gist->getType()] = 0;
$commits[$gist->getType()] = 0; $commits[$gist->getType()] = 0;
} }
if ($gist->getCipher()) { if ($gist->getCipher()) {
$withEncryption[$gist->getType()]++; $withEncryption[$gist->getType()]++;
} }
$languages[$gist->getType()]++; $languages[$gist->getType()]++;
try { $commits[$gist->getType()] += $gist->getCommits();
$count = count($gistService->getHistory($gist));
$commits[$gist->getType()] += $count;
} catch(GitException $e) {
}
} }
$output->writeln(['<comment>Gists statistics</comment>', '']); $output->writeln(['<comment>Gists statistics</comment>', '']);
@ -62,9 +68,9 @@ EOF
->setHeaders(array('Without encryption', 'With encryption', 'Commits', 'Total')) ->setHeaders(array('Without encryption', 'With encryption', 'Commits', 'Total'))
->setRows(array( ->setRows(array(
array( array(
$total - $v = array_sum($withEncryption), $total - $v = array_sum($withEncryption),
$v, $v,
array_sum($commits), array_sum($commits),
$total $total
), ),
)) ))
@ -79,9 +85,9 @@ EOF
$table->setHeaders(array( $table->setHeaders(array(
'Type', 'Type',
'Without encryption', 'Without encryption',
'With encryption', 'With encryption',
'Commits', 'Commits',
'Total', 'Total',
)); ));

View file

@ -8,8 +8,16 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
/**
* class UpdateCommand.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class UpdateCommand extends Command class UpdateCommand extends Command
{ {
/**
* {@inheritdoc}
*/
protected function configure() protected function configure()
{ {
$types = implode(', ', $this->getTypes()); $types = implode(', ', $this->getTypes());
@ -44,6 +52,9 @@ EOF
); );
} }
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
//$output->writeln(sprintf('<comment>%s</comment> bar.', 'test')); //$output->writeln(sprintf('<comment>%s</comment> bar.', 'test'));
@ -80,7 +91,7 @@ EOF
return true; return true;
} }
if ($input->getOption('show-id')) { if ($input->getOption('show-id')) {
$output->writeln($gist['gist']['Id']); $output->writeln($gist['gist']['Id']);
@ -90,6 +101,11 @@ EOF
$output->writeln(json_encode($gist)); $output->writeln(json_encode($gist));
} }
/**
* Returns the list of types.
*
* @return array
*/
protected function getTypes() protected function getTypes()
{ {
$types = array( $types = array(

View file

@ -7,16 +7,27 @@ use Symfony\Component\Console\Output\OutputInterface;
use Knp\Command\Command; use Knp\Command\Command;
use Symfony\Component\Console\Question\Question; use Symfony\Component\Console\Question\Question;
/**
* Class UserCreateCommand.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class UserCreateCommand extends Command class UserCreateCommand extends Command
{ {
/**
* {@inheritdoc}
*/
protected function configure() protected function configure()
{ {
$this $this
->setName('user:create') ->setName('user:create')
->setDescription('Create a user') ->setDescription('Create a user')
->setHelp(""); ->setHelp('');
} }
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$helper = $this->getHelper('question'); $helper = $this->getHelper('question');
@ -28,7 +39,7 @@ class UserCreateCommand extends Command
while (trim($username) === '') { while (trim($username) === '') {
$question = new Question('Username: ', ''); $question = new Question('Username: ', '');
$username = $helper->ask($input, $output, $question); $username = $helper->ask($input, $output, $question);
if ($userProvider->userExists($username)) { if ($userProvider->userExists($username)) {
$output->writeln('<error>This username is already used.</error>'); $output->writeln('<error>This username is already used.</error>');
$username = ''; $username = '';

View file

@ -10,7 +10,8 @@ use Gist\Model\GistQuery;
use Gist\Form\ApiUpdateGistForm; use Gist\Form\ApiUpdateGistForm;
/** /**
* Class ApiController * Class ApiController.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class ApiController extends Controller class ApiController extends Controller

View file

@ -9,23 +9,42 @@ use Gist\Model\GistQuery;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
/** /**
* Class Controller * Class Controller.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class Controller class Controller
{ {
/**
* @var Application
*/
protected $app; protected $app;
/**
* __construct.
*
* @param Application $app
*/
public function __construct(Application $app) public function __construct(Application $app)
{ {
$this->app = $app; $this->app = $app;
} }
/**
* Returns the application.
*
* @return Application
*/
public function getApp() public function getApp()
{ {
return $this->app; return $this->app;
} }
/**
* Returns a 404 response.
*
* @return Response
*/
protected function notFoundResponse() protected function notFoundResponse()
{ {
$app = $this->getApp(); $app = $this->getApp();
@ -38,7 +57,16 @@ class Controller
404 404
); );
} }
/**
* Returns the default options of a gist view.
*
* @param Request $request
* @param string $gist Gist's ID
* @param string $commit The commit ID
*
* @return array
*/
protected function getViewOptions(Request $request, $gist, $commit) protected function getViewOptions(Request $request, $gist, $commit)
{ {
$app = $this->getApp(); $app = $this->getApp();
@ -67,6 +95,13 @@ class Controller
); );
} }
/**
* Returns the content of the gist depending of the commit and its history.
*
* @param Gist $gist
* @param mixed $commit
* @param mixed $history
*/
protected function getContentByCommit(Gist $gist, &$commit, $history) protected function getContentByCommit(Gist $gist, &$commit, $history)
{ {
$app = $this->getApp(); $app = $this->getApp();
@ -90,6 +125,11 @@ class Controller
return $app['gist']->getContent($gist, $commit); return $app['gist']->getContent($gist, $commit);
} }
/**
* Returns the connected user.
*
* @return mixed
*/
public function getUser() public function getUser()
{ {
$app = $this->getApp(); $app = $this->getApp();
@ -110,6 +150,14 @@ class Controller
return $user; return $user;
} }
/**
* Renders a view.
*
* @param string $template
* @param array $params
*
* @return string
*/
public function render($template, array $params = null) public function render($template, array $params = null)
{ {
$app = $this->getApp(); $app = $this->getApp();

View file

@ -10,11 +10,19 @@ use GitWrapper\GitException;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
/** /**
* Class EditController * Class EditController.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class EditController extends Controller class EditController extends Controller
{ {
/**
* Creation page.
*
* @param Request $request
*
* @return string
*/
public function createAction(Request $request) public function createAction(Request $request)
{ {
$app = $this->getApp(); $app = $this->getApp();
@ -44,6 +52,13 @@ class EditController extends Controller
); );
} }
/**
* Cloning page.
*
* @param Request $request
*
* @return string
*/
public function cloneAction(Request $request, $gist, $commit) public function cloneAction(Request $request, $gist, $commit)
{ {
$app = $this->getApp(); $app = $this->getApp();

View file

@ -7,20 +7,26 @@ use Gist\Model\User;
use Gist\Form\UserRegisterForm; use Gist\Form\UserRegisterForm;
use Gist\Form\UserLoginForm; use Gist\Form\UserLoginForm;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\HttpFoundation\RedirectResponse;
/** /**
* Class LoginController * Class LoginController.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class LoginController extends Controller class LoginController extends Controller
{ {
/**
* Registration page.
*
* @param Request $request
*
* @return string
*/
public function registerAction(Request $request) public function registerAction(Request $request)
{ {
$app = $this->getApp(); $app = $this->getApp();
if (false === $app['enable_registration']) { if (false === $app['settings']['enable_registration']) {
return new Response('', 403); return new Response('', 403);
} }
@ -54,18 +60,25 @@ class LoginController extends Controller
return $this->render( return $this->render(
'Login/register.html.twig', 'Login/register.html.twig',
[ [
'form' => $form->createView(), 'form' => $form->createView(),
'error' => isset($error) ? $error : '', 'error' => isset($error) ? $error : '',
'success' => isset($success) ? $success : '', 'success' => isset($success) ? $success : '',
] ]
); );
} }
/**
* Login page.
*
* @param Request $request
*
* @return string
*/
public function loginAction(Request $request) public function loginAction(Request $request)
{ {
$app = $this->getApp(); $app = $this->getApp();
if (false === $app['enable_login']) { if (false === $app['settings']['enable_login']) {
return new Response('', 403); return new Response('', 403);
} }
@ -87,7 +100,7 @@ class LoginController extends Controller
return $this->render( return $this->render(
'Login/login.html.twig', 'Login/login.html.twig',
[ [
'form' => $form->createView(), 'form' => $form->createView(),
'error' => isset($error) ? $error : '', 'error' => isset($error) ? $error : '',
] ]
); );

View file

@ -3,26 +3,36 @@
namespace Gist\Controller; namespace Gist\Controller;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Gist\Model\GistQuery;
use Gist\Form\DeleteGistForm; use Gist\Form\DeleteGistForm;
use Gist\Form\FilterGistForm; use Gist\Form\FilterGistForm;
use Gist\Form\UserPasswordForm;
use Symfony\Component\HttpFoundation\RedirectResponse;
/** /**
* Class MyController * Class MyController.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class MyController extends Controller class MyController extends Controller
{ {
/**
* "My" page.
*
* @param Request $request
* @param int $page
*
* @return string
*/
public function myAction(Request $request, $page) public function myAction(Request $request, $page)
{ {
$page = (int) $page; $page = (int) $page;
$app = $this->getApp(); $app = $this->getApp();
$deleteForm = new DeleteGistForm($app['form.factory'], $app['translator']); $deleteForm = new DeleteGistForm($app['form.factory'], $app['translator']);
$deleteForm = $deleteForm->build()->getForm(); $deleteForm = $deleteForm->build()->getForm();
$options = array( $options = array(
'type' => 'all', 'type' => 'all',
'cipher' => 'anyway', 'cipher' => 'anyway',
); );
@ -35,6 +45,9 @@ class MyController extends Controller
$filterForm = $filterForm->build()->getForm(); $filterForm = $filterForm->build()->getForm();
$passwordForm = new UserPasswordForm($app['form.factory'], $app['translator']);
$passwordForm = $passwordForm->build()->getForm();
if ($request->query->has('filter')) { if ($request->query->has('filter')) {
$filterForm->submit($request); $filterForm->submit($request);
@ -42,13 +55,14 @@ class MyController extends Controller
$options = $filterForm->getData(); $options = $filterForm->getData();
} }
} }
$gists = $this->getUser()->getGistsPager($page, $options); $gists = $this->getUser()->getGistsPager($page, $options);
if ($request->isMethod('post')) { if ($request->isMethod('post')) {
$deleteForm->submit($request); $deleteForm->handleRequest($request);
$passwordForm->handleRequest($request);
if ($deleteForm->isValid()) { if ($deleteForm->isSubmitted() && $deleteForm->isValid()) {
$id = (int) $deleteForm->getData()['id']; $id = (int) $deleteForm->getData()['id'];
foreach ($gists as $gist) { foreach ($gists as $gist) {
@ -59,16 +73,41 @@ class MyController extends Controller
} }
} }
} }
if ($passwordForm->isSubmitted() && $passwordForm->isValid()) {
$currentPassword = $passwordForm->getData()['currentPassword'];
$newPassword = $passwordForm->getData()['newPassword'];
$passwordUpdated = 0;
if ($app['user.provider']->isCurrentUserPassword($this->getUser(), $currentPassword)) {
$app['user.provider']->updateUserPassword(
$this->getUser(),
$newPassword
);
$passwordUpdated = 1;
}
return new RedirectResponse(
$app['url_generator']->generate(
'my',
[
'passwordUpdated' => $passwordUpdated,
]
)
);
}
} }
return $this->render( return $this->render(
'My/my.html.twig', 'My/my.html.twig',
array( array(
'gists' => $gists, 'gists' => $gists,
'page' => $page, 'page' => $page,
'deleteForm' => $deleteForm->createView(), 'deleteForm' => $deleteForm->createView(),
'filterForm' => $filterForm->createView(), 'filterForm' => $filterForm->createView(),
'deleted' => !empty($deleted), 'passwordForm' => $passwordForm->createView(),
'deleted' => !empty($deleted),
) )
); );
} }

View file

@ -9,11 +9,21 @@ use Gist\Model\Gist;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
/** /**
* Class ViewController * Class ViewController.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class ViewController extends Controller class ViewController extends Controller
{ {
/**
* View action.
*
* @param Request $request
* @param string $gist Gist's ID
* @param string $commit The commit
*
* @return string|Response
*/
public function viewAction(Request $request, $gist, $commit) public function viewAction(Request $request, $gist, $commit)
{ {
$app = $this->getApp(); $app = $this->getApp();
@ -27,6 +37,15 @@ class ViewController extends Controller
} }
} }
/**
* Embed action.
*
* @param Request $request
* @param string $gist Gist's ID
* @param string $commit The commit
*
* @return string|Response
*/
public function embedAction(Request $request, $gist, $commit) public function embedAction(Request $request, $gist, $commit)
{ {
$app = $this->getApp(); $app = $this->getApp();
@ -40,6 +59,15 @@ class ViewController extends Controller
} }
} }
/**
* JS embed action.
*
* @param Request $request
* @param string $gist Gist's ID
* @param string $commit The commit
*
* @return string|Response
*/
public function embedJsAction(Request $request, $gist, $commit) public function embedJsAction(Request $request, $gist, $commit)
{ {
$viewOptions = $this->getViewOptions($request, $gist, $commit); $viewOptions = $this->getViewOptions($request, $gist, $commit);
@ -53,6 +81,15 @@ class ViewController extends Controller
); );
} }
/**
* Raw action.
*
* @param Request $request
* @param string $gist Gist's ID
* @param string $commit The commit
*
* @return string|Response
*/
public function rawAction(Request $request, $gist, $commit) public function rawAction(Request $request, $gist, $commit)
{ {
$viewOptions = $this->getViewOptions($request, $gist, $commit); $viewOptions = $this->getViewOptions($request, $gist, $commit);
@ -70,6 +107,15 @@ class ViewController extends Controller
} }
} }
/**
* Download action.
*
* @param Request $request
* @param string $gist Gist's ID
* @param string $commit The commit
*
* @return string|Response
*/
public function downloadAction(Request $request, $gist, $commit) public function downloadAction(Request $request, $gist, $commit)
{ {
$app = $this->getApp(); $app = $this->getApp();
@ -94,6 +140,14 @@ class ViewController extends Controller
} }
} }
/**
* Revisions action.
*
* @param Request $request
* @param string $gist Gist's ID
*
* @return string|Response
*/
public function revisionsAction(Request $request, $gist) public function revisionsAction(Request $request, $gist)
{ {
$app = $this->getApp(); $app = $this->getApp();

View file

@ -3,17 +3,23 @@
namespace Gist; namespace Gist;
use Silex\ControllerResolver as BaseControllerResolver; use Silex\ControllerResolver as BaseControllerResolver;
use Gist\Application;
/** /**
* Class DecoratorControllerResolver * Class DecoratorControllerResolver.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class ControllerResolver extends BaseControllerResolver class ControllerResolver extends BaseControllerResolver
{ {
/**
* Instanciates a controller.
*
* @param string $class
*
* @return Gist\Controller
*/
protected function instantiateController($class) protected function instantiateController($class)
{ {
return new $class($this->app); return new $class($this->app);
} }
} }

View file

@ -6,15 +6,34 @@ use Symfony\Component\Form\FormFactory;
use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\Translator;
/** /**
* Class AbstractForm * Class AbstractForm.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
abstract class AbstractForm abstract class AbstractForm
{ {
/**
* The builder.
*
* @var Symfony\Component\Form\FormBuilder
*/
protected $builder; protected $builder;
/**
* The translator.
*
* @var Translator
*/
protected $translator; protected $translator;
/**
* __construct.
*
* @param FormFactory $formFactory
* @param Translator $translator
* @param mixed $data
* @param array $formFactoryOptions
*/
public function __construct(FormFactory $formFactory, Translator $translator, $data = null, $formFactoryOptions = array()) public function __construct(FormFactory $formFactory, Translator $translator, $data = null, $formFactoryOptions = array())
{ {
$this->translator = $translator; $this->translator = $translator;
@ -22,15 +41,32 @@ abstract class AbstractForm
$this->builder = $formFactory->createNamedBuilder($this->getName(), 'form', $data, $formFactoryOptions); $this->builder = $formFactory->createNamedBuilder($this->getName(), 'form', $data, $formFactoryOptions);
} }
/**
* Returns the form from the builder.
*
* @return Symfony\Component\Form\Form
*/
public function getForm() public function getForm()
{ {
return $this->builder->getForm(); return $this->builder->getForm();
} }
/**
* Returns the form's name.
*
* @return string
*/
public function getName() public function getName()
{ {
return 'form'; return 'form';
} }
/**
* Builds the form.
*
* @param array $options
*
* @return Symfony\Component\Form\FormBuilder
*/
abstract public function build(array $options = array()); abstract public function build(array $options = array());
} }

View file

@ -3,11 +3,15 @@
namespace Gist\Form; namespace Gist\Form;
/** /**
* Class ApiCreateGistForm * Class ApiCreateGistForm.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class ApiCreateGistForm extends CreateGistForm class ApiCreateGistForm extends CreateGistForm
{ {
/**
* {@inheritdoc}
*/
public function build(array $options = array()) public function build(array $options = array())
{ {
parent::build($options); parent::build($options);

View file

@ -3,11 +3,15 @@
namespace Gist\Form; namespace Gist\Form;
/** /**
* Class ApiUpdateGistForm * Class ApiUpdateGistForm.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class ApiUpdateGistForm extends ApiCreateGistForm class ApiUpdateGistForm extends ApiCreateGistForm
{ {
/**
* {@inheritdoc}
*/
public function build(array $options = array()) public function build(array $options = array())
{ {
parent::build($options); parent::build($options);

View file

@ -3,11 +3,15 @@
namespace Gist\Form; namespace Gist\Form;
/** /**
* Class CreateGistForm * Class CreateGistForm.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class CloneGistForm extends CreateGistForm class CloneGistForm extends CreateGistForm
{ {
/**
* {@inheritdoc}
*/
public function build(array $options = array()) public function build(array $options = array())
{ {
parent::build($options); parent::build($options);

View file

@ -5,11 +5,15 @@ namespace Gist\Form;
use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotBlank;
/** /**
* Class CreateGistForm * Class CreateGistForm.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class CreateGistForm extends AbstractForm class CreateGistForm extends AbstractForm
{ {
/**
* {@inheritdoc}
*/
public function build(array $options = array()) public function build(array $options = array())
{ {
$this->builder->add( $this->builder->add(
@ -69,6 +73,11 @@ class CreateGistForm extends AbstractForm
return $this->builder; return $this->builder;
} }
/**
* Returns the types for generating the form.
*
* @return array
*/
protected function getTypes() protected function getTypes()
{ {
$types = array( $types = array(
@ -78,7 +87,7 @@ class CreateGistForm extends AbstractForm
'php' => '', 'php' => '',
'sql' => '', 'sql' => '',
'xml' => '', 'xml' => '',
'yaml'=> '', 'yaml' => '',
'perl' => '', 'perl' => '',
'c' => '', 'c' => '',
'asp' => '', 'asp' => '',

View file

@ -5,11 +5,15 @@ namespace Gist\Form;
use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotBlank;
/** /**
* Class DeleteGistForm * Class DeleteGistForm.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class DeleteGistForm extends AbstractForm class DeleteGistForm extends AbstractForm
{ {
/**
* {@inheritdoc}
*/
public function build(array $options = array()) public function build(array $options = array())
{ {
$this->builder->add( $this->builder->add(
@ -22,12 +26,15 @@ class DeleteGistForm extends AbstractForm
), ),
) )
); );
$this->builder->setMethod('POST'); $this->builder->setMethod('POST');
return $this->builder; return $this->builder;
} }
/**
* {@inheritdoc}
*/
public function getName() public function getName()
{ {
return 'delete'; return 'delete';

View file

@ -5,11 +5,15 @@ namespace Gist\Form;
use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotBlank;
/** /**
* Class CreateGistForm * Class CreateGistForm.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class FilterGistForm extends AbstractForm class FilterGistForm extends AbstractForm
{ {
/**
* {@inheritdoc}
*/
public function build(array $options = array()) public function build(array $options = array())
{ {
$this->builder->add( $this->builder->add(
@ -40,11 +44,28 @@ class FilterGistForm extends AbstractForm
) )
); );
$this->builder->add(
'title',
'text',
array(
'required' => false,
'attr' => array(
'placeholder' => $this->translator->trans('form.title.placeholder'),
'class' => 'form-control',
)
)
);
$this->builder->setMethod('GET'); $this->builder->setMethod('GET');
return $this->builder; return $this->builder;
} }
/**
* Returns the types for generating the form.
*
* @return array
*/
protected function getTypes() protected function getTypes()
{ {
$types = array( $types = array(
@ -55,7 +76,7 @@ class FilterGistForm extends AbstractForm
'php' => '', 'php' => '',
'sql' => '', 'sql' => '',
'xml' => '', 'xml' => '',
'yaml'=> '', 'yaml' => '',
'perl' => '', 'perl' => '',
'c' => '', 'c' => '',
'asp' => '', 'asp' => '',
@ -72,6 +93,9 @@ class FilterGistForm extends AbstractForm
return $types; return $types;
} }
/**
* {@inheritdoc}
*/
public function getName() public function getName()
{ {
return 'filter'; return 'filter';

View file

@ -5,11 +5,15 @@ namespace Gist\Form;
use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotBlank;
/** /**
* Class UserLoginForm * Class UserLoginForm.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class UserLoginForm extends AbstractForm class UserLoginForm extends AbstractForm
{ {
/**
* {@inheritdoc}
*/
public function build(array $options = array()) public function build(array $options = array())
{ {
$this->builder->add( $this->builder->add(
@ -45,7 +49,7 @@ class UserLoginForm extends AbstractForm
), ),
) )
); );
$this->builder->add( $this->builder->add(
'_remember_me', '_remember_me',
'checkbox', 'checkbox',
@ -63,6 +67,9 @@ class UserLoginForm extends AbstractForm
return $this->builder; return $this->builder;
} }
/**
* {@inheritdoc}
*/
public function getName() public function getName()
{ {
return ''; return '';

View file

@ -0,0 +1,65 @@
<?php
namespace Gist\Form;
use Symfony\Component\Validator\Constraints\NotBlank;
/**
* Class UserPasswordForm.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class UserPasswordForm extends AbstractForm
{
/**
* {@inheritdoc}
*/
public function build(array $options = array())
{
$this->builder->add(
'currentPassword',
'password',
array(
'required' => true,
'attr' => array(
'class' => 'form-control',
'placeholder' => $this->translator->trans('login.register.form.current_password.placeholder'),
),
'trim' => false,
'constraints' => array(
new NotBlank(array(
'message' => $this->translator->trans('form.error.not_blank'),
)),
),
)
);
$this->builder->add(
'newPassword',
'password',
array(
'required' => true,
'attr' => array(
'class' => 'form-control',
'placeholder' => $this->translator->trans('login.register.form.password.placeholder'),
),
'trim' => false,
'constraints' => array(
new NotBlank(array(
'message' => $this->translator->trans('form.error.not_blank'),
)),
),
)
);
return $this->builder;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'password';
}
}

View file

@ -5,11 +5,15 @@ namespace Gist\Form;
use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\NotBlank;
/** /**
* Class UserRegisterForm * Class UserRegisterForm.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class UserRegisterForm extends AbstractForm class UserRegisterForm extends AbstractForm
{ {
/**
* {@inheritdoc}
*/
public function build(array $options = array()) public function build(array $options = array())
{ {
$this->builder->add( $this->builder->add(

View file

@ -4,8 +4,20 @@ namespace Gist\Model;
use Gist\Model\Base\Gist as BaseGist; use Gist\Model\Base\Gist as BaseGist;
/**
* Class Gist.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class Gist extends BaseGist class Gist extends BaseGist
{ {
/**
* Hydrates the gist with array data.
*
* @param array $data
*
* @return Gist
*/
public function hydrateWith(array $data) public function hydrateWith(array $data)
{ {
if (isset($data['title'])) { if (isset($data['title'])) {
@ -21,11 +33,21 @@ class Gist extends BaseGist
return $this; return $this;
} }
/**
* Generates a unique filename.
*
* @return string
*/
public function generateFilename() public function generateFilename()
{ {
$this->setFile(uniqid()); $this->setFile(uniqid());
} }
/**
* Returns the type for Geshi.
*
* @return string
*/
public function getGeshiType() public function getGeshiType()
{ {
$data = array( $data = array(
@ -35,11 +57,16 @@ class Gist extends BaseGist
return str_replace(array_keys($data), array_values($data), $this->getType()); return str_replace(array_keys($data), array_values($data), $this->getType());
} }
/**
* Returns the extension depending of the type.
*
* @return string
*/
public function getTypeAsExtension() public function getTypeAsExtension()
{ {
$data = array( $data = array(
'javascript' => 'js', 'javascript' => 'js',
'yaml'=> 'yml', 'yaml' => 'yml',
'perl' => 'pl', 'perl' => 'pl',
'python' => 'py', 'python' => 'py',
'bash' => 'sh', 'bash' => 'sh',
@ -49,4 +76,14 @@ class Gist extends BaseGist
return str_replace(array_keys($data), array_values($data), $this->getType()); return str_replace(array_keys($data), array_values($data), $this->getType());
} }
/*
* Increments the number of commits.
*/
public function commit()
{
$this->setCommits($this->getCommits() + 1);
return $this;
}
} }

View file

@ -5,14 +5,9 @@ namespace Gist\Model;
use Gist\Model\Base\GistQuery as BaseGistQuery; use Gist\Model\Base\GistQuery as BaseGistQuery;
/** /**
* Skeleton subclass for performing query and update operations on the 'gist' table. * Class GistQuery.
*
*
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
* *
* @author Simon Vieille <simon@deblan.fr>
*/ */
class GistQuery extends BaseGistQuery class GistQuery extends BaseGistQuery
{ {

View file

@ -7,17 +7,35 @@ use Symfony\Component\Security\Core\User\UserInterface;
use Propel\Runtime\ActiveQuery\Criteria; use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\Connection\ConnectionInterface; use Propel\Runtime\Connection\ConnectionInterface;
/**
* Class User.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class User extends BaseUser implements UserInterface class User extends BaseUser implements UserInterface
{ {
/**
* Erases credentials.
*
* @return void
*/
public function eraseCredentials() public function eraseCredentials()
{ {
} }
/**
* Returns roles.
*
* @return array
*/
public function getRoles() public function getRoles()
{ {
return explode(',', parent::getRoles()); return explode(',', parent::getRoles());
} }
/**
* {@inheritdoc}
*/
public function getGists(Criteria $criteria = null, ConnectionInterface $con = null) public function getGists(Criteria $criteria = null, ConnectionInterface $con = null)
{ {
if ($criteria === null) { if ($criteria === null) {
@ -27,6 +45,15 @@ class User extends BaseUser implements UserInterface
return parent::getGists($criteria, $con); return parent::getGists($criteria, $con);
} }
/**
* Generates a pager of the user's gists.
*
* @param int $page
* @param array $options
* @param int $maxPerPage
*
* @return Propel\Runtime\Util\PropelModelPager
*/
public function getGistsPager($page, $options = array(), $maxPerPage = 10) public function getGistsPager($page, $options = array(), $maxPerPage = 10)
{ {
$query = GistQuery::create() $query = GistQuery::create()
@ -37,6 +64,10 @@ class User extends BaseUser implements UserInterface
$query->filterByType($options['type']); $query->filterByType($options['type']);
} }
if (!empty($options['title'])) {
$query->filterByTitle('%'.$options['title'].'%', Criteria::LIKE);
}
if (!empty($options['cipher']) && $options['cipher'] !== 'anyway') { if (!empty($options['cipher']) && $options['cipher'] !== 'anyway') {
$bools = array( $bools = array(
'yes' => true, 'yes' => true,

View file

@ -5,14 +5,9 @@ namespace Gist\Model;
use Gist\Model\Base\UserQuery as BaseUserQuery; use Gist\Model\Base\UserQuery as BaseUserQuery;
/** /**
* Skeleton subclass for performing query and update operations on the 'user' table. * Class UserQuery.
*
*
*
* You should add additional methods to this class to meet the
* application requirements. This class will only be generated as
* long as it does not already exist in the output directory.
* *
* @author Simon Vieille <simon@deblan.fr>
*/ */
class UserQuery extends BaseUserQuery class UserQuery extends BaseUserQuery
{ {

View file

@ -7,7 +7,8 @@
<column name="type" type="VARCHAR" size="30" required="true" /> <column name="type" type="VARCHAR" size="30" required="true" />
<column name="file" type="VARCHAR" size="30" required="true" /> <column name="file" type="VARCHAR" size="30" required="true" />
<column name="user_id" type="INTEGER" required="false" /> <column name="user_id" type="INTEGER" required="false" />
<column name="commits" type="INTEGER" required="true" defaultValue="0" />
<foreign-key foreignTable="user" onDelete="setnull" onUpdate="cascade"> <foreign-key foreignTable="user" onDelete="setnull" onUpdate="cascade">
<reference local="user_id" foreign="id"/> <reference local="user_id" foreign="id"/>
</foreign-key> </foreign-key>

View file

@ -17,7 +17,14 @@
</p> </p>
<p> <p>
{{ form_errors(form.content) }} {{ form_errors(form.content) }}
{{ form_widget(form.content) }}
{% set class = 'form-control' %}
{% if gist.cipher %}
{% set class = class ~ ' cipher-editor' %}
{% endif %}
{{ form_widget(form.content, {attr: {class: class}}) }}
</p> </p>
<p> <p>
<input type="submit" class="btn btn-primary" value="{{ 'form.submit'|trans }}"> <input type="submit" class="btn btn-primary" value="{{ 'form.submit'|trans }}">
@ -29,3 +36,13 @@
</form> </form>
</div> </div>
{% endblock %} {% endblock %}
{% block js %}
{% if gist.cipher %}
<script>
var cipherGistClone = true;
</script>
{% endif %}
{{ parent() }}
{% endblock %}

View file

@ -11,10 +11,6 @@
{{ form_widget(form.title) }} {{ form_widget(form.title) }}
</div> </div>
<div class="panel-body"> <div class="panel-body">
<p class="text-primary hide" id="cipher-alert">
<span class="glyphicon glyphicon-info-sign"></span>
{{ 'form.cipher.alert'|trans }}
</p>
<div class="btn-toolbar"> <div class="btn-toolbar">
<div class="btn-group" id="options"> <div class="btn-group" id="options">
<div class="btn-group"> <div class="btn-group">

View file

@ -24,19 +24,19 @@
<p> <p>
{{ form_errors(form._username) }} {{ form_errors(form._username) }}
{{ form_widget(form._username) }} {{ form_widget(form._username) }}
</p> </p>
<p> <p>
{{ form_errors(form._password) }} {{ form_errors(form._password) }}
{{ form_widget(form._password) }} {{ form_widget(form._password) }}
</p> </p>
<p> <p>
{{ form_errors(form._remember_me) }} {{ form_errors(form._remember_me) }}
{{ form_widget(form._remember_me) }} {{ form_widget(form._remember_me) }}
{{ form_label(form._remember_me) }} {{ form_label(form._remember_me) }}
</p> </p>
<p> <p>
<input type="submit" class="btn btn-primary" value="{{ 'form.submit'|trans }}"> <input type="submit" class="btn btn-primary" value="{{ 'form.submit'|trans }}">
</p> </p>

View file

@ -1,52 +1,52 @@
{% extends 'base.html.twig' %} {% extends 'base.html.twig' %}
{% block title %} {% block title %}
{{ 'login.register.title'|trans }} {{ 'login.register.title'|trans }}
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<div class="row"> <div class="row">
{% if error %} {% if error %}
<div class="col-md-12"> <div class="col-md-12">
<div class="alert alert-warning"> <div class="alert alert-warning">
{{ error }} {{ error }}
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if success %}
<div class="col-md-12">
<div class="alert alert-success">
{{ success }}
</div>
</div>
{% else %}
<form action="{{ path('register') }}" method="post" id="main-form">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
{{ 'login.register.title'|trans }}
</div>
<div class="panel-body">
<p>
{{ form_errors(form.username) }}
{{ form_widget(form.username) }}
</p>
<p>
{{ form_errors(form.password) }}
{{ form_widget(form.password) }}
</p>
<p>
<input type="submit" class="btn btn-primary" value="{{ 'form.submit'|trans }}">
</p>
{{ form_rest(form) }} {% if success %}
</div> <div class="col-md-12">
</div> <div class="alert alert-success">
</div> {{ success }}
</form> </div>
{% endif %} </div>
{% else %}
<form action="{{ path('register') }}" method="post" id="main-form">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
{{ 'login.register.title'|trans }}
</div>
<div class="panel-body">
<p>
{{ form_errors(form.username) }}
{{ form_widget(form.username) }}
</p>
<p>
{{ form_errors(form.password) }}
{{ form_widget(form.password) }}
</p>
<p>
<input type="submit" class="btn btn-primary" value="{{ 'form.submit'|trans }}">
</p>
{{ form_rest(form) }}
</div>
</div>
</div>
</form>
{% endif %}
</div> </div>
{% endblock %} {% endblock %}

View file

@ -4,6 +4,20 @@
{% block body %} {% block body %}
<div class="row"> <div class="row">
{% if app.request.query.has('passwordUpdated') %}
<div class="col-md-12">
{% if app.request.query.get('passwordUpdated') %}
<div class="alert alert-success">
<p>{{ 'form.success.password'|trans }}</p>
</div>
{% else %}
<div class="alert alert-warning">
<p>{{ 'form.error.password'|trans }}</p>
</div>
{% endif %}
</div>
{% endif %}
{% if deleted %} {% if deleted %}
<div class="col-md-12"> <div class="col-md-12">
<div class="alert alert-success"> <div class="alert alert-success">
@ -11,17 +25,18 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div class="col-md-12"> <div class="col-md-12">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
{{ 'my.title'|trans }} {{ 'my.title'|trans }}
<div class="pull-right actions"> <div class="pull-right actions">
<a href="{{ path('home', app.request.attributes.get('_route_params')) }}" class="btn btn-success btn-sm"> <a href="{{ path('home', app.request.attributes.get('_route_params')) }}" class="btn btn-success btn-sm">
<span class="glyphicon glyphicon-copy"></span> <span class="glyphicon glyphicon-copy"></span>
{{ 'gist.action.add'|trans }} {{ 'gist.action.add'|trans }}
</a> </a>
</div> </div>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div class="tab-content"> <div class="tab-content">
<div id="form-deletion"> <div id="form-deletion">
@ -30,7 +45,10 @@
{% set params = app.request.attributes.get('_route_params')|merge({page: 1}) %} {% set params = app.request.attributes.get('_route_params')|merge({page: 1}) %}
<form action="{{ path('my', params) }}" method="GET"> <form action="{{ path('my', params) }}" method="GET" class="form-inline">
<div class="form-group pull-left">
{{ form_widget(filterForm.title) }}
</div>
<div class="btn-toolbar"> <div class="btn-toolbar">
<div class="btn-group" id="options"> <div class="btn-group" id="options">
<div class="btn-group"> <div class="btn-group">
@ -42,15 +60,15 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{% for item in filterForm.type.vars.choices %} {% for item in filterForm.type.vars.choices %}
<li> <li>
<input <input
{% if item.value == filterForm.type.vars.value %}checked{% endif %} {% if item.value == filterForm.type.vars.value %}checked{% endif %}
data-id="#type-label" type="radio" class="hide" data-id="#type-label" type="radio" class="hide"
data-title="{{ item.label }}" data-title="{{ item.label }}"
value="{{ item.value }}" value="{{ item.value }}"
name="filter[type]" name="filter[type]"
id="type-{{ loop.index }}" /> id="type-{{ loop.index }}" />
<a href="#"> <a href="#">
<label for="type-{{ loop.index }}"> <label for="type-{{ loop.index }}">
{{ item.label }} {{ item.label }}
</label> </label>
@ -68,15 +86,15 @@
<ul class="dropdown-menu" role="menu"> <ul class="dropdown-menu" role="menu">
{% for item in filterForm.cipher.vars.choices %} {% for item in filterForm.cipher.vars.choices %}
<li> <li>
<input <input
{% if item.value == filterForm.cipher.vars.value %}checked{% endif %} {% if item.value == filterForm.cipher.vars.value %}checked{% endif %}
data-id="#cipher-label" type="radio" class="hide cipher-input" data-id="#cipher-label" type="radio" class="hide cipher-input"
data-title="{{ item.label }}" data-title="{{ item.label }}"
value="{{ item.value }}" value="{{ item.value }}"
name="filter[cipher]" name="filter[cipher]"
id="cipher-{{ loop.index }}" /> id="cipher-{{ loop.index }}" />
<a href="#"> <a href="#">
<label for="cipher-{{ loop.index }}"> <label for="cipher-{{ loop.index }}">
{{ item.label }} {{ item.label }}
</label> </label>
@ -91,9 +109,9 @@
</div> </div>
</form> </form>
{% if gists.nbResults == 0 %} {% if gists.nbResults == 0 %}
{{ 'my.nothing'|trans }} {{ 'my.nothing'|trans }}
{% else %} {% else %}
{% set pager %} {% set pager %}
{% if gists.haveToPaginate %} {% if gists.haveToPaginate %}
{% set params = app.request.attributes.get('_route_params')|merge({filter: app.request.query.get('filter', [])}) %} {% set params = app.request.attributes.get('_route_params')|merge({filter: app.request.query.get('filter', [])}) %}
@ -118,7 +136,7 @@
</span> </span>
</a> </a>
</li> </li>
{% for p in gists.links(10) %} {% for p in gists.links(10) %}
<li {% if p == page %}class="active"{% endif %}> <li {% if p == page %}class="active"{% endif %}>
{% set params = params|merge({page: p}) %} {% set params = params|merge({page: p}) %}
@ -126,7 +144,7 @@
<a href="{{ path('my', params) }}">{{ p }}</a> <a href="{{ path('my', params) }}">{{ p }}</a>
</li> </li>
{% endfor %} {% endfor %}
<li> <li>
{% set params = params|merge({page: gists.nextPage}) %} {% set params = params|merge({page: gists.nextPage}) %}
@ -136,7 +154,7 @@
</span> </span>
</a> </a>
</li> </li>
<li> <li>
{% set params = params|merge({page: gists.lastPage}) %} {% set params = params|merge({page: gists.lastPage}) %}
@ -149,43 +167,69 @@
</ul> </ul>
{% endif %} {% endif %}
{% endset %} {% endset %}
{{ pager }} {{ pager }}
{% for gist in gists %} {% for gist in gists %}
<div class="commit"> <div class="commit">
<p> <p>
<strong>{{ gist.title ? gist.title : 'gist.untitled'|trans }}</strong>, <strong>{{ gist.title ? gist.title : 'gist.untitled'|trans }}</strong>,
{{ gist.createdAt|date('date.format'|trans) }} {{ gist.createdAt|date('date.format'|trans) }}
</p> </p>
<p> <p>
<button class="btn btn-info btn-sm"> <button class="btn btn-info btn-sm">
{{ gist.type }} {{ gist.type }}
</button> </button>
{% if not gist.cipher %}
<a href="{{ path('view', {gist: gist.file}) }}" class="btn btn-warning btn-sm">
{{ 'gist.action.view'|trans }}
</a>
{% else %}
<button class="btn btn-error btn-sm">
<span class="glyphicon glyphicon-lock"></span>
</button>
{% endif %}
{% if not gist.cipher %}
<a href="{{ path('view', {gist: gist.file}) }}" class="btn btn-warning btn-sm">
{{ 'gist.action.view'|trans }}
</a>
{% else %}
<button class="btn btn-error btn-sm">
<span class="glyphicon glyphicon-lock"></span>
</button>
{% endif %}
<button class="btn btn-delete btn-sm" data-id="{{ gist.id }}"> <button class="btn btn-delete btn-sm" data-id="{{ gist.id }}">
<span class="glyphicon btn-delete glyphicon-remove" data-id="{{ gist.id }}"></span> <span class="glyphicon btn-delete glyphicon-remove" data-id="{{ gist.id }}"></span>
</button> </button>
</p> </p>
</div> </div>
{% endfor %} {% endfor %}
{{ pager }} {{ pager }}
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
</div>
</div> <div class="panel panel-default">
<div class="panel-heading">
{{ 'login.login.form.password.placeholder'|trans }}
</div>
<div class="panel-body">
<div class="tab-content">
<form action="{{ path('my', params) }}" method="post">
<p>
{{ form_errors(passwordForm.currentPassword) }}
{{ form_widget(passwordForm.currentPassword) }}
</p>
<p>
{{ form_errors(passwordForm.newPassword) }}
{{ form_widget(passwordForm.newPassword) }}
</p>
<p>
{{ form_rest(passwordForm) }}
<input type="submit" class="btn btn-primary" value="{{ 'form.submit'|trans }}">
</p>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View file

@ -0,0 +1,30 @@
<script type="text/javascript" src="{{ web_path }}components/SyntaxHighlighter/scripts/XRegExp.js"></script> <!-- XRegExp is bundled with the final shCore.js during build -->
<script type="text/javascript" src="{{ web_path }}components/SyntaxHighlighter/scripts/shCore.js"></script>
<script type="text/javascript" src="{{ web_path }}components/SyntaxHighlighter/scripts/shAutoloader.js"></script>
<script type="text/javascript">
SyntaxHighlighter.autoloader(
['applescript', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushAppleScript.js' ],
['actionscript3', 'as3', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushAS3.js' ],
['bash', 'shell', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushBash.js' ],
['coldfusion', 'cf', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushColdFusion.js' ],
['cpp', 'c', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushCpp.js' ],
['c#', 'c-sharp', 'csharp', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushCSharp.js' ],
['css', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushCss.js' ],
['delphi', 'pascal', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushDelphi.js' ],
['diff', 'patch', 'pas', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushDiff.js' ],
['erl', 'erlang', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushErlang.js' ],
['groovy', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushGroovy.js' ],
['java', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushJava.js' ],
['jfx', 'javafx', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushJavaFX.js' ],
['js', 'jscript', 'javascript', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushJScript.js' ],
['perl', 'pl', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushPerl.js' ],
['php', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushPhp.js' ],
['text', 'plain', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushPlain.js' ],
['py', 'python', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushPython.js' ],
['ruby', 'rails', 'ror', 'rb', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushRuby.js' ],
['scala', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushScala.js' ],
['sql', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushSql.js' ],
['vb', 'vbnet', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushVb.js' ],
['xml', 'xhtml', 'xslt', 'html', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushXml.js' ]
);
</script>

View file

@ -50,14 +50,14 @@
<div class="col-md-12" id="embed"> <div class="col-md-12" id="embed">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
{% if not gist.cipher %} <div class="pull-right actions">
<div class="pull-right actions"> <a target="_blank" href="{{ path('view', app.request.attributes.get('_route_params')) }}" class="btn btn-default btn-sm cipher-link">
<a target="_blank" href="{{ path('view', app.request.attributes.get('_route_params')) }}" class="btn btn-default btn-sm"> <span class="btn btn-warning btn-xs">
<span class="btn btn-warning btn-xs"> {{ commit|slice(0, 10) }}
{{ commit|slice(0, 10) }} </span>
</span> </a>
</a>
{% if not gist.cipher %}
<a target="_blank" href="{{ path('raw', app.request.attributes.get('_route_params')) }}" class="btn btn-default btn-sm"> <a target="_blank" href="{{ path('raw', app.request.attributes.get('_route_params')) }}" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-eye-open"></span> <span class="glyphicon glyphicon-eye-open"></span>
{{ 'gist.action.raw'|trans }} {{ 'gist.action.raw'|trans }}
@ -66,12 +66,13 @@
<span class="glyphicon glyphicon-save-file"></span> <span class="glyphicon glyphicon-save-file"></span>
{{ 'gist.action.download'|trans }} {{ 'gist.action.download'|trans }}
</a> </a>
<a target="_blank" href="{{ path('clone', app.request.attributes.get('_route_params')) }}" class="btn btn-success btn-sm"> {% endif %}
<span class="glyphicon glyphicon-copy"></span>
{{ 'gist.action.clone'|trans }} <a target="_blank" href="{{ path('clone', app.request.attributes.get('_route_params')) }}" class="btn btn-success btn-sm cipher-link">
</a> <span class="glyphicon glyphicon-copy"></span>
</div> {{ 'gist.action.clone'|trans }}
{% endif %} </a>
</div>
{{ gist.title ? gist.title : 'gist.untitled'|trans }} {{ gist.title ? gist.title : 'gist.untitled'|trans }}
</div> </div>

View file

@ -11,21 +11,19 @@
{% block body %} {% block body %}
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
{% if not gist.cipher %} <ul class="nav nav-tabs">
<ul class="nav nav-tabs"> <li>
<li> <a href="{{ path('view', {gist: gist.file}) }}" class="cipher-link">
<a href="{{ path('view', {gist: gist.file}) }}"> {{ 'gist.action.view'|trans }}
{{ 'gist.action.view'|trans }} </a>
</a> </li>
</li> <li class="active">
<li class="active"> <a href="{{ path('revisions', {gist: gist.file}) }}" class="cipher-link">
<a href="{{ path('revisions', {gist: gist.file}) }}"> {{ 'gist.action.history'|trans }}
{{ 'gist.action.history'|trans }} <span class="badge">{{ history|length }}</span>
<span class="badge">{{ history|length }}</span> </a>
</a> </li>
</li> </ul>
</ul>
{% endif %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
@ -33,42 +31,41 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div class="tab-content"> <div class="tab-content">
{% if not gist.cipher %} <div id="revisions" class="tab-pane in active">
<div id="revisions" class="tab-pane in active"> {% for commit in history %}
{% for commit in history %} <div class="commit">
<div class="commit"> <p>
<p> <a href="{{ path('view', {gist: gist.file, commit: commit.commit}) }}" class="btn btn-warning btn-sm cipher-link">
<a href="{{ path('view', {gist: gist.file, commit: commit.commit}) }}" class="btn btn-warning btn-sm"> {{ commit.commit|slice(0, 10) }}
{{ commit.commit|slice(0, 10) }} </a>
{% if loop.first %}<span class="btn btn-info btn-sm">init</span>{% endif %}
{% if not loop.first %}
<a href="#diff-{{ loop.index }}" data-target="#diff-{{ loop.index }}" class="btn btn-default btn-sm show-diff">
diff
</a> </a>
{% endif %}
{% if loop.first %}<span class="btn btn-info btn-sm">init</span>{% endif %} </p>
<p>
{% if not loop.first %} {{ commit.date|date('date.format'|trans) }}
<a href="#diff-{{ loop.index }}" data-target="#diff-{{ loop.index }}" class="btn btn-default btn-sm show-diff"> </p>
diff <div>
</a> {% if not loop.first %}
{% endif %} <div class="diff" id="diff-{{ loop.index }}">
</p> {% if not gist.cipher %}
<p>
{{ commit.date|date('date.format'|trans) }}
</p>
<div>
{% if not loop.first %}
<div class="diff" id="diff-{{ loop.index }}">
{{ commit.diff|raw }} {{ commit.diff|raw }}
</div> {% endif %}
{% endif %} </div>
</div> {% endif %}
</div> </div>
</div>
{% if not loop.last %} {% if not loop.last %}
<hr /> <hr />
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@ -78,4 +75,61 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{% if gist.cipher %}
{{ include('View/cipherJs.html.twig') }}
<script src="{{ web_path }}components/jsdiff/diff.min.js"></script>
<script>
var key = getKey();
var decrypt = function(content) {
var decrypted = CryptoJS.AES.decrypt(content, key, {
format: JsonFormatter
});
return decrypted.toString(CryptoJS.enc.Utf8);
}
var commits = [];
{% for commit in history %}
try {
var content = decrypt('{{ commit.content|raw }}');
commits[{{ loop.index - 1 }}] = content;
} catch(e) {
}
{% endfor %}
for (var u = commits.length - 1; u > 0; u--) {
if (commits.hasOwnProperty(u) && commits.hasOwnProperty(u - 1)) {
var previous = commits[u - 1];
var current = commits[u];
var diff = JsDiff.diffLines(previous, current);
var diffContent = [];
for (var v = 0, c = diff.length; v < c; v++) {
var value = diff[v].value;
var sign = diff[v].added ? '+' : '-';
var lines = value.split("\n");
for (var i = 0, l = lines.length; i < l; i++) {
diffContent.push(sign + lines[i]);
}
}
diffContent = diffContent.join("\n");
var $pre = $('<pre>')
.attr('class', 'brush: diff; syntaxhighlighter')
.text(diffContent);
$('#diff-' + (u + 1).toString()).append($pre);
}
}
SyntaxHighlighter.all();
</script>
{% endif %}
{% endblock %} {% endblock %}

View file

@ -16,29 +16,28 @@
{% block body %} {% block body %}
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
{% if not gist.cipher %} <ul class="nav nav-tabs">
<ul class="nav nav-tabs"> <li class="active">
<li class="active"> <a href="{{ path('view', {gist: gist.file}) }}" class="cipher-link">
<a href="{{ path('view', {gist: gist.file}) }}"> {{ 'gist.action.view'|trans }}
{{ 'gist.action.view'|trans }} </a>
</a> </li>
</li> <li>
<li> <a href="{{ path('revisions', {gist: gist.file}) }}" class="cipher-link">
<a href="{{ path('revisions', {gist: gist.file}) }}"> {{ 'gist.action.history'|trans }}
{{ 'gist.action.history'|trans }} <span class="badge">{{ history|length }}</span>
<span class="badge">{{ history|length }}</span> </a>
</a> </li>
</li> </ul>
</ul>
{% endif %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
{% if not gist.cipher %} <div class="pull-right actions">
<div class="pull-right actions"> <span class="btn btn-warning btn-xs">
<span class="btn btn-warning btn-xs"> {{ commit|slice(0, 10) }}
{{ commit|slice(0, 10) }} </span>
</span>
{% if not gist.cipher %}
<a href="{{ path('raw', app.request.attributes.get('_route_params')) }}" class="btn btn-default btn-sm"> <a href="{{ path('raw', app.request.attributes.get('_route_params')) }}" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-eye-open"></span> <span class="glyphicon glyphicon-eye-open"></span>
{{ 'gist.action.raw'|trans }} {{ 'gist.action.raw'|trans }}
@ -47,12 +46,13 @@
<span class="glyphicon glyphicon-save-file"></span> <span class="glyphicon glyphicon-save-file"></span>
{{ 'gist.action.download'|trans }} {{ 'gist.action.download'|trans }}
</a> </a>
<a href="{{ path('clone', app.request.attributes.get('_route_params')) }}" class="btn btn-success btn-sm"> {% endif %}
<span class="glyphicon glyphicon-copy"></span>
{{ 'gist.action.clone'|trans }} <a href="{{ path('clone', app.request.attributes.get('_route_params')) }}" class="btn btn-success btn-sm cipher-link">
</a> <span class="glyphicon glyphicon-copy"></span>
</div> {{ 'gist.action.clone'|trans }}
{% endif %} </a>
</div>
{{ gist.title ? gist.title : 'gist.untitled'|trans }} {{ gist.title ? gist.title : 'gist.untitled'|trans }}
@ -84,35 +84,6 @@
{{ parent() }} {{ parent() }}
{% if gist.cipher %} {% if gist.cipher %}
<script type="text/javascript" src="{{ web_path }}components/SyntaxHighlighter/scripts/XRegExp.js"></script> <!-- XRegExp is bundled with the final shCore.js during build --> {{ include('View/cipherJs.html.twig') }}
<script type="text/javascript" src="{{ web_path }}components/SyntaxHighlighter/scripts/shCore.js"></script>
<script type="text/javascript" src="{{ web_path }}components/SyntaxHighlighter/scripts/shAutoloader.js"></script>
<script type="text/javascript">
SyntaxHighlighter.autoloader(
['applescript', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushAppleScript.js' ],
['actionscript3', 'as3', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushAS3.js' ],
['bash', 'shell', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushBash.js' ],
['coldfusion', 'cf', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushColdFusion.js' ],
['cpp', 'c', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushCpp.js' ],
['c#', 'c-sharp', 'csharp', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushCSharp.js' ],
['css', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushCss.js' ],
['delphi', 'pascal', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushDelphi.js' ],
['diff', 'patch', 'pas', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushDiff.js' ],
['erl', 'erlang', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushErlang.js' ],
['groovy', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushGroovy.js' ],
['java', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushJava.js' ],
['jfx', 'javafx', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushJavaFX.js' ],
['js', 'jscript', 'javascript', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushJScript.js' ],
['perl', 'pl', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushPerl.js' ],
['php', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushPhp.js' ],
['text', 'plain', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushPlain.js' ],
['py', 'python', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushPython.js' ],
['ruby', 'rails', 'ror', 'rb', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushRuby.js' ],
['scala', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushScala.js' ],
['sql', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushSql.js' ],
['vb', 'vbnet', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushVb.js' ],
['xml', 'xhtml', 'xslt', 'html', '{{ web_path }}components/SyntaxHighlighter/scripts/shBrushXml.js' ]
);
</script>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View file

@ -1,11 +1,19 @@
<!DOCTYPE html> <!DOCTYPE html>
{% set theme_settings = app.settings.theme %}
{% set security_dettings = app.settings.security %}
<html xmlns="http://www.w3.org/1999/xhtml"> <html xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
{% block css %} {% block css %}
<link rel="stylesheet" href="{{ web_path }}components/bootstrap/dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="{{ web_path }}components/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="{{ web_path }}components/flag-icon-css/css/flag-icon.min.css" /> <link rel="stylesheet" href="{{ web_path }}components/flag-icon-css/css/flag-icon.min.css" />
<link rel="stylesheet" href="{{ web_path }}app/css/bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="{{ web_path }}app/css/app.css" /> {% if theme_settings.name == 'dark' %}
<link rel="stylesheet" href="{{ web_path }}app/css/bootstrap/bootstrap.min.css" />
{% else %}
<link rel="stylesheet" href="{{ web_path }}components/bootstrap/dist/css/bootstrap-theme.min.css" />
{% endif %}
<link rel="stylesheet" href="{{ web_path }}app/css/themes/{{ theme_settings.name }}.css" />
{% endblock %} {% endblock %}
{% block metas %} {% block metas %}
@ -17,7 +25,7 @@
</head> </head>
<body> <body>
{% block nav %} {% block nav %}
<nav class="navbar navbar-inverse"> <nav class="navbar navbar-{{ theme_settings.name == 'dark' ? 'inverse' : 'default' }}">
<div class="container-fluid"> <div class="container-fluid">
<div class="navbar-header"> <div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#main-menu"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#main-menu">
@ -36,33 +44,33 @@
{{ 'app.menu.home.title'|trans }} {{ 'app.menu.home.title'|trans }}
</a> </a>
</li> </li>
{% if user %}
<li>
<a href="{{ path('my') }}">
{{ 'app.menu.my.my.title'|trans }}
</a>
</li>
<li>
<a href="{{ path('logout', {target_url: path('home')}) }}">
{{ 'app.menu.my.logout.title'|trans }}
</a>
</li>
{% elseif app.enable_login %}
<li>
<a href="{{ path('login') }}">
{{ 'app.menu.my.login.title'|trans }}
</a>
</li>
{% if app.enable_registration %} {% if user %}
<li> <li>
<a href="{{ path('register') }}"> <a href="{{ path('my') }}">
{{ 'app.menu.my.register.title'|trans }} {{ 'app.menu.my.my.title'|trans }}
</a> </a>
</li> </li>
{% endif %} <li>
{% endif %} <a href="{{ path('logout', {target_url: path('home')}) }}">
{{ 'app.menu.my.logout.title'|trans }}
</a>
</li>
{% elseif security_dettings.enable_login %}
<li>
<a href="{{ path('login') }}">
{{ 'app.menu.my.login.title'|trans }}
</a>
</li>
{% if security_dettings.enable_registration %}
<li>
<a href="{{ path('register') }}">
{{ 'app.menu.my.register.title'|trans }}
</a>
</li>
{% endif %}
{% endif %}
<li> <li>
<a href="https://gitnet.fr/deblan/gist/src/master/README.md"> <a href="https://gitnet.fr/deblan/gist/src/master/README.md">
{{ 'app.menu.about.title'|trans }} {{ 'app.menu.about.title'|trans }}
@ -71,12 +79,11 @@
</ul> </ul>
{% block langs %} {% block langs %}
<p class="navbar-text navbar-right"> <p class="navbar-text navbar-right">
<a class="lang btn btn-xs" href="{{ path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')|merge({_locale: 'en'})) }}"> {% for locale, flag in {'en': 'gb', 'fr': 'fr', 'es': 'es', 'de': 'de'} %}
<span class="flag-icon flag-icon-gb"></span> <a class="lang btn btn-xs cipher-link" href="{{ path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')|merge({_locale: locale})) }}">
</a> <span class="flag-icon flag-icon-{{ flag }}"></span>
<a class="lang btn btn-xs" href="{{ path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')|merge({_locale: 'fr'})) }}"> </a>
<span class="flag-icon flag-icon-fr"></span> {% endfor %}
</a>
</p> </p>
{% endblock %} {% endblock %}
</div> </div>
@ -92,9 +99,22 @@
<footer> <footer>
{{ 'footer.text'|trans|raw }} {{ 'footer.text'|trans|raw }}
</footer> </footer>
{% endblock %} {% endblock %}
</div> </div>
{% block js %} {% block js %}
<script>
var trans = function(key) {
var translations = {
'form.confirm': '{{ 'form.confirm'|trans }}',
};
if (translations.hasOwnProperty(key)) {
return translations[key];
}
return key;
}
</script>
<script src="{{ web_path }}components/jquery/dist/jquery.min.js"></script> <script src="{{ web_path }}components/jquery/dist/jquery.min.js"></script>
<script src="{{ web_path }}components/bootstrap/dist/js/bootstrap.min.js"></script> <script src="{{ web_path }}components/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="{{ web_path }}app/js/cipher.js"></script> <script src="{{ web_path }}app/js/cipher.js"></script>

View file

@ -7,36 +7,48 @@ use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Component\HttpFoundation\Request;
/** /**
* Class AuthenticationListener * Class AuthenticationListener.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class AuthenticationListener implements ListenerInterface class AuthenticationListener implements ListenerInterface
{ {
/**
* @var TokenStorageInterface
*/
protected $tokenStorage; protected $tokenStorage;
/**
* @var AuthenticationManagerInterface
*/
protected $authenticationManager; protected $authenticationManager;
/**
* __construct.
*
* @param TokenStorageInterface $tokenStorage
* @param AuthenticationManagerInterface $authenticationManager
*/
public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager) public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager)
{ {
$this->tokenStorage = $tokenStorage; $this->tokenStorage = $tokenStorage;
$this->authenticationManager = $authenticationManager; $this->authenticationManager = $authenticationManager;
} }
/**
* @param GetResponseEvent $event
*/
public function handle(GetResponseEvent $event) public function handle(GetResponseEvent $event)
{ {
$request = $event->getRequest(); $request = $event->getRequest();
$username = $request->get('_username'); $username = $request->get('_username');
$password = $request->get('_password'); $password = $request->get('_password');
if (!empty($username)) { if (!empty($username)) {
$token = new UsernamePasswordToken($username, $password, 'default'); $token = new UsernamePasswordToken($username, $password, 'default');
try { try {
$authToken = $this->authenticationManager->authenticate($token); $authToken = $this->authenticationManager->authenticate($token);
$this->tokenStorage->setToken($token); $this->tokenStorage->setToken($token);
@ -46,7 +58,7 @@ class AuthenticationListener implements ListenerInterface
$this->tokenStorage->setToken(null); $this->tokenStorage->setToken(null);
return; return;
} }
} }
} }
} }

View file

@ -9,18 +9,32 @@ use Gist\Service\UserProvider;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
/** /**
* Class AuthenticationProvider * Class AuthenticationProvider.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class AuthenticationProvider implements AuthenticationProviderInterface class AuthenticationProvider implements AuthenticationProviderInterface
{ {
/**
* @var UserProvider
*/
protected $userProvider; protected $userProvider;
/**
* __construct.
*
* @param UserProvider $userProvider
*/
public function __construct(UserProvider $userProvider) public function __construct(UserProvider $userProvider)
{ {
$this->userProvider = $userProvider; $this->userProvider = $userProvider;
} }
/**
* Authenticates.
*
* @param TokenInterface $token
*/
public function authenticate(TokenInterface $token) public function authenticate(TokenInterface $token)
{ {
$user = $this->userProvider->loadUserByUsername($token->getUser()); $user = $this->userProvider->loadUserByUsername($token->getUser());
@ -29,7 +43,7 @@ class AuthenticationProvider implements AuthenticationProviderInterface
$isValid = $this->userProvider->getEncoder()->isPasswordValid( $isValid = $this->userProvider->getEncoder()->isPasswordValid(
$user->getPassword(), $user->getPassword(),
$token->getCredentials(), $token->getCredentials(),
$user->getSalt() $user->getSalt()
); );
if (!$isValid) { if (!$isValid) {
@ -42,6 +56,13 @@ class AuthenticationProvider implements AuthenticationProviderInterface
throw new AuthenticationException('Authentication failed.'); throw new AuthenticationException('Authentication failed.');
} }
/**
* Returns if the token instance is supported.
*
* @param TokenInterface $token
*
* @return bool
*/
public function supports(TokenInterface $token) public function supports(TokenInterface $token)
{ {
return $token instanceof UsernamePasswordToken; return $token instanceof UsernamePasswordToken;

View file

@ -7,11 +7,17 @@ use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
/** /**
* Class LogoutSuccessHandler * Class LogoutSuccessHandler.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class LogoutSuccessHandler implements LogoutSuccessHandlerInterface class LogoutSuccessHandler implements LogoutSuccessHandlerInterface
{ {
/**
* @param Request $request
*
* @return RedirectResponse
*/
public function onLogoutSuccess(Request $request) public function onLogoutSuccess(Request $request)
{ {
$targetUrl = $request->query->get('target_url') ? $request->query->get('target_url') : '/'; $targetUrl = $request->query->get('target_url') ? $request->query->get('target_url') : '/';
@ -19,4 +25,3 @@ class LogoutSuccessHandler implements LogoutSuccessHandlerInterface
return new RedirectResponse($targetUrl); return new RedirectResponse($targetUrl);
} }
} }

View file

@ -11,19 +11,40 @@ use Gist\Model\GistQuery;
use Gist\Model\User; use Gist\Model\User;
/** /**
* Class Gist * Class Gist.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class Gist class Gist
{ {
/**
* @var string
*/
protected $gistPath; protected $gistPath;
/**
* @var GitWrapper
*/
protected $gitWrapper; protected $gitWrapper;
/**
* @var GitWorkingCopy
*/
protected $gitWorkingCopy; protected $gitWorkingCopy;
/**
* @var GeSHi
*/
protected $geshi; protected $geshi;
/**
* __construct.
*
* @param mixed $gistPath
* @param GitWrapper $gitWrapper
* @param GitWorkingCopy $gitWorkingCopy
* @param GeSHi $geshi
*/
public function __construct($gistPath, GitWrapper $gitWrapper, GitWorkingCopy $gitWorkingCopy, GeSHi $geshi) public function __construct($gistPath, GitWrapper $gitWrapper, GitWorkingCopy $gitWorkingCopy, GeSHi $geshi)
{ {
$this->gistPath = $gistPath; $this->gistPath = $gistPath;
@ -32,11 +53,23 @@ class Gist
$this->geshi = $geshi; $this->geshi = $geshi;
} }
/**
* Returns a collection of gists.
*
* @return Propel\Runtime\Collection\ObjectCollection
*/
public function getGists() public function getGists()
{ {
return GistQuery::create()->find(); return GistQuery::create()->find();
} }
/**
* Returns the history of a Gist.
*
* @param GistModel $gist
*
* @return array
*/
public function getHistory(GistModel $gist) public function getHistory(GistModel $gist)
{ {
$command = GitCommand::getInstance('log', '--format=medium', $gist->getFile()); $command = GitCommand::getInstance('log', '--format=medium', $gist->getFile());
@ -49,7 +82,7 @@ class Gist
$history = []; $history = [];
for ($i = count($commits) - 1; $i >= 0; $i--) { for ($i = count($commits) - 1; $i >= 0; --$i) {
$commit = trim($commits[$i][1]); $commit = trim($commits[$i][1]);
$command = GitCommand::getInstance('show', '--no-color', $commit); $command = GitCommand::getInstance('show', '--no-color', $commit);
@ -65,12 +98,24 @@ class Gist
'diff' => $this->highlight('diff', $diff), 'diff' => $this->highlight('diff', $diff),
); );
if ($gist->isCipher()) {
$data['content'] = $this->getContent($gist, $commit);
}
$history[] = $data; $history[] = $data;
} }
return $history; return $history;
} }
/**
* Returns the content of a gist.
*
* @param GistModel $gist
* @param string $commit
*
* @return string
*/
public function getContent(GistModel $gist, $commit) public function getContent(GistModel $gist, $commit)
{ {
$command = GitCommand::getInstance('cat-file', '-p', $commit.':'.$gist->getFile()); $command = GitCommand::getInstance('cat-file', '-p', $commit.':'.$gist->getFile());
@ -80,6 +125,15 @@ class Gist
return str_replace("\r\n", "\n", $this->gitWrapper->run($command)); return str_replace("\r\n", "\n", $this->gitWrapper->run($command));
} }
/**
* Creates a gist.
*
* @param GistModel $gist
* @param array $data
* @param mixed $user
*
* @return GistModel
*/
public function create(GistModel $gist, array $data, $user = null) public function create(GistModel $gist, array $data, $user = null)
{ {
$gist->hydrateWith($data); $gist->hydrateWith($data);
@ -95,11 +149,19 @@ class Gist
$gist->setUser($user); $gist->setUser($user);
} }
$gist->save(); $gist->commit()->save();
return $gist; return $gist;
} }
/**
* Makes a commit.
*
* @param GistModel $gist
* @param array $data
*
* @return GistModel
*/
public function commit(GistModel $gist, array $data) public function commit(GistModel $gist, array $data)
{ {
file_put_contents($this->gistPath.'/'.$gist->getFile(), $data['content']); file_put_contents($this->gistPath.'/'.$gist->getFile(), $data['content']);
@ -108,9 +170,38 @@ class Gist
->add($gist->getFile()) ->add($gist->getFile())
->commit('Update'); ->commit('Update');
$gist->commit()->save();
return $gist; return $gist;
} }
/*
* Returns the number of commits.
*
* @param GistModel $gist
*
* @return int
*/
public function getNumberOfCommits(GistModel $gist)
{
$command = GitCommand::getInstance('log', '--oneline', '--', $gist->getFile());
$command->setDirectory($this->gistPath);
$command->bypass(false);
$content = trim($this->gitWrapper->run($command));
$content = str_replace("\r\n", "\n", $content);
return count(explode("\n", $content));
}
/**
* Highlight the content.
*
* @param string $type
* @param string $content
*
* @return string
*/
public function highlight($type, $content) public function highlight($type, $content)
{ {
$this->geshi->set_source($content); $this->geshi->set_source($content);

View file

@ -5,11 +5,19 @@ namespace Gist\Service;
use InvalidArgumentException; use InvalidArgumentException;
/** /**
* Class SaltGenerator * Class SaltGenerator.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class SaltGenerator class SaltGenerator
{ {
/**
* Generates a random salt.
*
* @param int $length
*
* @return string
*/
public function generate($length = 32) public function generate($length = 32)
{ {
if (!is_numeric($length)) { if (!is_numeric($length)) {

View file

@ -9,24 +9,43 @@ use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Gist\Service\SaltGenerator;
/** /**
* Class UserProvider * Class UserProvider.
*
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class UserProvider implements UserProviderInterface class UserProvider implements UserProviderInterface
{ {
/**
* @var MessageDigestPasswordEncoder
*/
protected $encoder; protected $encoder;
/**
* @var SaltGenerator
*/
protected $saltGenerator; protected $saltGenerator;
/**
* __construct.
*
* @param MessageDigestPasswordEncoder $encoder
* @param SaltGenerator $saltGenerator
*/
public function __construct(MessageDigestPasswordEncoder $encoder, SaltGenerator $saltGenerator) public function __construct(MessageDigestPasswordEncoder $encoder, SaltGenerator $saltGenerator)
{ {
$this->encoder = $encoder; $this->encoder = $encoder;
$this->saltGenerator = $saltGenerator; $this->saltGenerator = $saltGenerator;
} }
/**
* Setter of encoder.
*
* @param MessageDigestPasswordEncoder $encoder
*
* @return UserProvider
*/
public function setEncoder(MessageDigestPasswordEncoder $encoder) public function setEncoder(MessageDigestPasswordEncoder $encoder)
{ {
$this->encoder = $encoder; $this->encoder = $encoder;
@ -34,11 +53,23 @@ class UserProvider implements UserProviderInterface
return $this; return $this;
} }
/**
* Getter of encoder.
*
* @return MessageDigestPasswordEncoder
*/
public function getEncoder() public function getEncoder()
{ {
return $this->encoder; return $this->encoder;
} }
/**
* Setter of saltGenerator.
*
* @param SaltGenerator $saltGenerator
*
* @return UserProvider
*/
public function setSaltGenerator(SaltGenerator $saltGenerator) public function setSaltGenerator(SaltGenerator $saltGenerator)
{ {
$this->saltGenerator = $saltGenerator; $this->saltGenerator = $saltGenerator;
@ -46,11 +77,23 @@ class UserProvider implements UserProviderInterface
return $this; return $this;
} }
/**
* Getter of saltGenerator.
*
* @return SaltGenerator
*/
public function getSaltGenerator() public function getSaltGenerator()
{ {
return $this->saltGenerator; return $this->saltGenerator;
} }
/**
* Checks if the given username is a user.
*
* @param string $username
*
* @return bool
*/
public function userExists($username) public function userExists($username)
{ {
return UserQuery::create() return UserQuery::create()
@ -58,11 +101,24 @@ class UserProvider implements UserProviderInterface
->count() > 0; ->count() > 0;
} }
/**
* Creates a User.
*
* @return User
*/
public function createUser() public function createUser()
{ {
return new User(); return new User();
} }
/**
* Registers an user.
*
* @param User $user
* @param string $password
*
* @return User
*/
public function registerUser(User $user, $password) public function registerUser(User $user, $password)
{ {
$user->setSalt($this->saltGenerator->generate()); $user->setSalt($this->saltGenerator->generate());
@ -75,6 +131,14 @@ class UserProvider implements UserProviderInterface
return $user; return $user;
} }
/**
* Updates an user.
*
* @param User $user
* @param string $password
*
* @return User
*/
public function updateUserPassword(User $user, $password) public function updateUserPassword(User $user, $password)
{ {
$user $user
@ -84,6 +148,13 @@ class UserProvider implements UserProviderInterface
return $user; return $user;
} }
/**
* Loads a user by his username.
*
* @param string $username
*
* @return User
*/
public function loadUserByUsername($username) public function loadUserByUsername($username)
{ {
$user = UserQuery::create()->findOneByUsername($username); $user = UserQuery::create()->findOneByUsername($username);
@ -95,6 +166,26 @@ class UserProvider implements UserProviderInterface
return $user; return $user;
} }
/*
* Checks if the given password is the current user password.
*
* @param User $user
* @param string $password
*
* @return bool
*/
public function isCurrentUserPassword(User $user, $password)
{
return $this->encoder->encodePassword($password, $user->getSalt()) === $user->getPassword();
}
/**
* Refresh an user.
*
* @param User $user
*
* @return User
*/
public function refreshUser(UserInterface $user) public function refreshUser(UserInterface $user)
{ {
if (!$user instanceof User) { if (!$user instanceof User) {
@ -104,6 +195,13 @@ class UserProvider implements UserProviderInterface
return $this->loadUserByUsername($user->getUsername()); return $this->loadUserByUsername($user->getUsername());
} }
/**
* Checks if the class is supported.
*
* @param string $class
*
* @return bool
*/
public function supportsClass($class) public function supportsClass($class)
{ {
return $class === 'Gist\Model\User'; return $class === 'Gist\Model\User';

View file

@ -8,7 +8,7 @@
# Determine the RewriteBase automatically and set it as environment variable. # Determine the RewriteBase automatically and set it as environment variable.
# If you are using Apache aliases to do mass virtual hosting or installed the # If you are using Apache aliases to do mass virtual hosting or installed the
# project in a subdirectory, the base path will be prepended to allow proper # project in a subdirectory, the base path will be prepended to allow proper
# resolution of the app.php file and to redirect to the correct URI. It will # resolution of the index.php file and to redirect to the correct URI. It will
# work in environments without path prefix as well, providing a safe, one-size # work in environments without path prefix as well, providing a safe, one-size
# fits all solution. But as you do not need it in this case, you can comment # fits all solution. But as you do not need it in this case, you can comment
# the following 2 lines to eliminate the overhead. # the following 2 lines to eliminate the overhead.
@ -16,7 +16,7 @@
RewriteRule ^(.*) - [E=BASE:%1] RewriteRule ^(.*) - [E=BASE:%1]
# Redirect to URI without front controller to prevent duplicate content # Redirect to URI without front controller to prevent duplicate content
# (with and without `/app.php`). Only do this redirect on the initial # (with and without `/index.php`). Only do this redirect on the initial
# rewrite by Apache and not on subsequent cycles. Otherwise we would get an # rewrite by Apache and not on subsequent cycles. Otherwise we would get an
# endless redirect loop (request -> rewrite to front controller -> # endless redirect loop (request -> rewrite to front controller ->
# redirect -> request -> ...). # redirect -> request -> ...).

View file

@ -0,0 +1,78 @@
.navbar {
border-radius: 0;
}
#form_content {
display: block;
width: 100%;
padding: 10px;
}
#languages {
padding-bottom: 5px;
}
#languages .btn-group:first-child {
margin-right: 4px;
}
pre {
background: #222;
border: #222;
color: #ddd;
white-space: pre-wrap;
white-space: -moz-pre-wrap;
white-space: -pre-wrap;
white-space: -o-pre-wrap;
word-wrap: break-word;
}
pre ol {
padding-left: 50px !important;
}
pre li:hover {
background: #444;
}
.panel-heading .actions {
margin-top: -5px;
}
div.diff {
display: none;
}
.de1 {
padding-left: 5px;
padding-right: 5px;
}
.li1 {
background: #333;
}
.re8 {
color: #52F700;
}
.kw3 {
color: #C6C765;
}
#viewer .syntaxhighlighter td {
vertical-align: top !important;
}
#options {
margin-bottom: 17px;
}
.btn-delete {
background: #DE3336;
color: #fff;
}
.btn-error:active, .btn-error:hover, .btn-error:focus {
color: #000;
}

View file

@ -66,36 +66,51 @@ var editorEvents = function() {
$(this).trigger('change'); $(this).trigger('change');
}); });
var key = getKey();
if (key) {
$('.show-diff').each(function() {
var href = $(this).attr('href');
href = href.replace('#', '#key=' + key + '&');
$(this).attr('href', href);
});
}
$('.show-diff').click(function() { $('.show-diff').click(function() {
$($(this).data('target')).toggle(); $($(this).data('target')).toggle();
}); });
if ((document.location.href).indexOf('#diff-') !== -1) { var diffLinkTest1 = (document.location.href).indexOf('#diff-') !== -1;
var diffLinkTest2 = (document.location.href).indexOf('&diff-') !== -1;
if (diffLinkTest1 || diffLinkTest2) {
var anchor = '#' + (document.location.href).toString().split('#')[1]; var anchor = '#' + (document.location.href).toString().split('#')[1];
$('.show-diff[href="' + anchor + '"]').click(); $('.show-diff[href="' + anchor + '"]').click();
document.location.href = anchor;
} }
} }
var myEvents = function() { var myEvents = function() {
$('.btn-delete').click(function() { $('.btn-delete').click(function() {
$('#delete_id').val($(this).data('id')); if (confirm(trans('form.confirm'))) {
$('#form-deletion form').submit(); $('#delete_id').val($(this).data('id'));
$('#form-deletion form').submit();
}
}); });
} }
var mainEditorEvents = function() { var mainEditorEvents = function() {
$('.cipher-input').change(function() {
if ($('.cipher-input:checked').val() === 'yes') {
$('#cipher-alert').removeClass('hide');
} else {
$('#cipher-alert').addClass('hide');
}
});
$('#main-form').submit(function(e) { $('#main-form').submit(function(e) {
if ($('.cipher-input:checked').val() === 'yes') { if ($('.cipher-input:checked').val() === 'yes' || typeof cipherGistClone !== 'undefined') {
var passphrase = randomString(256, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); var key = getKey();
if (key) {
var passphrase = key;
} else {
var passphrase = randomString(256, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
}
var content = $('#form_content').val(); var content = $('#form_content').val();
var encrypted = CryptoJS.AES.encrypt(content, passphrase, { var encrypted = CryptoJS.AES.encrypt(content, passphrase, {
format: JsonFormatter format: JsonFormatter
@ -112,7 +127,7 @@ var getKey = function() {
var parts = url.split('#key='); var parts = url.split('#key=');
if (parts.length === 2) { if (parts.length === 2) {
return parts[1]; return parts[1].split('&')[0];
} }
return null; return null;
@ -123,21 +138,38 @@ var viewerEvents = function() {
$(document).ready(function() { $(document).ready(function() {
var key = getKey(); var key = getKey();
var $cipherEditor = $('.cipher-editor');
var $embedInput = $('#embed-input'); var $embedInput = $('#embed-input');
var to = ' '; var to = ' ';
if (0 !== $render.length && key) { if (key) {
var decrypted = CryptoJS.AES.decrypt($render.html(), key, { $('.cipher-link').each(function() {
format: JsonFormatter var href = $(this).attr('href');
}); href = href + '#key=' + key;
$render.text(decrypted.toString(CryptoJS.enc.Utf8));
SyntaxHighlighter.all();
to = ' data-key="#key=' + key + '" '; $(this).attr('href', href);
$('.lang').each(function() {
$(this).attr('href', $(this).attr('href') + '#key=' + key);
}); });
if (0 !== $render.length || $cipherEditor.length !== 0) {
if ($render.length !== 0) {
var decrypted = CryptoJS.AES.decrypt($render.html(), key, {
format: JsonFormatter
});
$render.text(decrypted.toString(CryptoJS.enc.Utf8));
SyntaxHighlighter.all();
to = ' data-key="#key=' + key + '" ';
} else {
var decrypted = CryptoJS.AES.decrypt($cipherEditor.val(), key, {
format: JsonFormatter
});
$cipherEditor.val(decrypted.toString(CryptoJS.enc.Utf8));
}
}
} }
if ($embedInput.length) { if ($embedInput.length) {