Compare commits

..

No commits in common. "master" and "master" have entirely different histories.

76 changed files with 1354 additions and 2102 deletions

3
.bowerrc Normal file
View file

@ -0,0 +1,3 @@
{
"directory": "web/components/"
}

5
.gitignore vendored
View file

@ -2,13 +2,12 @@
/.mage/logs/*.log
/composer.lock
/vendor/
/propel.yaml
/propel.yml
/src/Gist/Model/Base/
/src/Gist/Model/Map/
/web/components/
/app/propel/
/node_modules/
/app/config/config.yml
/app/config/propel/
/data/
/trans/
/cache/

0
.mage/config/environment/.gitignore vendored Normal file
View file

View file

@ -0,0 +1,28 @@
# prod
# Rename this file to "prod.yml"
deployment:
user: gist
from: ./
to: /var/www/gist/
excludes:
- "*.svn"
- "*.git"
- "*.swp"
- "app/config/config.yml"
- "app/config/propel/"
- "app/propel/"
- "data/git"
- "deploy"
- "tags"
- "propel.yaml"
- "composer.lock"
- "composer.json"
- "bower.json"
- ".bowerrc"
hosts:
- gist.mycompany.com
tasks:
pre-deploy:
on-deploy:
post-deploy:
verbose_logging: true

7
.mage/config/general.yml Normal file
View file

@ -0,0 +1,7 @@
# global settings
name: Gist
email:
notifications: false
logging: true
maxlogs: 30
ssh_needs_tty: false

2
.mage/logs/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

0
.mage/tasks/.gitignore vendored Normal file
View file

View file

@ -1,26 +0,0 @@
## Environment
* GIST version or GIT commit: ...
* PHP Version: ...
* GIT version. ...
* HTTPD (Nginx, Apache2): ...
* DBMS (MySQL, SQLite, PostgreSQL): ...
## Description
...
### Steps to reproduce
1. ...
2. ...
3. ...
#### Observed Results
...
#### Expected Results
...

View file

@ -1,58 +1,54 @@
COMPOSER ?= composer
NPM ?= npm
BOWER ?= bower
GIT ?= git
MKDIR ?= mkdir
PHP ?= php
RM ?= rm
all: update clean-cache
all: update
composer:
@echo "Installing PHP dependencies"
@echo "---------------------------"
@echo
@echo "Installing application's dependencies"
@echo "-------------------------------------"
@echo
${COMPOSER} install
$(COMPOSER) install $(COMPOSER_INSTALL_FLAGS)
bower:
@echo "Installing application's dependencies"
@echo "-------------------------------------"
@echo
npm:
@echo "Installing CSS/JS dependencies"
@echo "------------------------------"
@echo
$(BOWER) install
${NPM} install
${NPM} update
optimize:
@echo "Optimizing Composer's autoloader, can take some time"
@echo "----------------------------------------------------"
@echo
$(COMPOSER) dump-autoload --optimize
update:
@echo "Updating application's dependencies"
@echo "-----------------------------------"
@echo
@echo
${GIT} pull origin master
sh -c 'test -d app && $(GIT) add app && $(GIT) commit -m "Configuration"'
$(GIT) pull origin master
${MKDIR} -p data/git
${MKDIR} -p data/cache
${COMPOSER} update
${NPM} install
${NPM} update
clean-cache:
@echo "Removing cache"
@echo "--------------"
@echo
${RM} -fr cache/*
$(COMPOSER) update
$(BOWER) install
run:
@echo "Run development server"
@echo "----------------------"
@echo
@echo
${PHP} -S 127.0.0.1:8080 -t web
$(PHP) -S 127.0.0.1:8080 -t web
propel:
@echo "Propel migration"
@echo "----------------"
@echo
@echo
./vendor/propel/propel/bin/propel config:convert
./vendor/propel/propel/bin/propel model:build --recursive
./vendor/propel/propel/bin/propel migration:diff --recursive

288
README.md
View file

@ -1,23 +1,293 @@
Table of Contents
=================
* [GIST](#gist)
* [Requirements](#requirements)
* [Git](#git)
* [Composer](#composer)
* [Bower](#bower)
* [Installation](#installation)
* [Upgrade](#upgrade)
* [Configuration](#configuration)
* [Makefile](#makefile)
* [API](#api)
* [Console](#console)
* [Deployment](#deployment)
* [Contributors](#contributors)
GIST
====
GIST is an open-source application to share code.
https://www.deblan.io/post/517/gist-est-dans-la-place
**[Read the wiki for more information](https://gitnet.fr/deblan/gist/wiki/_pages).**
![Gist](https://upload.deblan.org/u/2015-11/565b93a5.png "Gist")
### Editor
![Gist](https://upload.deblan.org/u/2016-06/57655dec.png "Gist")
![Gist](https://upload.deblan.org/u/2018-08/5b7ab7a6.png "Gist editor")
### Result
Requirements
------------
![Gist](https://upload.deblan.org/u/2018-08/5b7ab7d4.png "Gist result")
* PHP >= 5.4
* GIT
* MySQL, PostgreSQL or SQLite
* Composer (php)
* Bower (node)
### Account
### Git
![Gist](https://upload.deblan.org/u/2018-08/5b7aba2d.png "Gist account")
Git can maybe be downloaded from your system's repositories.
### Embeded Gist
$ git config --global user.email "you@example.com"
$ git config --global user.name "Your Name"
![Gist](https://upload.deblan.org/u/2018-08/5b7ab81c.png "Embeded Gist")
### Composer
Composer can maybe be downloaded from your system's repositories.
Else, follow the next instructions:
# With cURL
$ curl -sS https://getcomposer.org/installer | php
# With Wget
$ wget -O - -q https://getcomposer.org/installer | php
$ chmod +x composer.phar
# For a local installation and if the envvar PATH contains "$HOME/bin/"
$ mv composer.phar ~/bin/composer
# For a global installation
$ sudo mv composer.phar /usr/local/bin/composer
### Bower
$ sudo apt-get install npm
$ sudo npm install -g bower
Installation
------------
$ cd /path/to/www/
$ git clone https://gitnet.fr/deblan/gist
$ cd gist
$ make
$ cp propel-dist.yaml propel.yaml
Edit `propel.yaml`. **Use spaces instead of tabulations**.
**MySQL**
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"
[...]
**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"`.
$ make update
$ 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
--------
A Makefile is provided to automate some tasks.
* `make` will install application's dependencies via Composer and Bower,
* `make optimize` will run Composer's autoloader dump script with classmap
* `make update` will update the application
* `make propel` will generate propel's files
* `make run` will run development server on http://127.0.0.1:8080/
API
---
### Create a new gist
**POST** /{locale}/api/create
Params:
* `form[title]`: String (required, can be empty)
* `form[type]`: String (required)
Values: html, css, javascript, php, sql, xml, yaml, perl, c, asp, python, bash, actionscript3, text
* `form[content]`: String (required)
**Responses:**
* Code `405`: Method Not Allowed
* Code `400`: Bad Request
* Code `200`: A json which contains gist's information. Example:
```javascript
{
"url": "https:\/\/gist.deblan.org\/en\/view\/55abcfa7771e0\/f4afbf72967dd95e3461490dcaa310d728d6a97d",
"gist": {
"Id": 66,
"Title": "test prod",
"Cipher": false,
"Type": "javascript",
"File": "55abcfa7771e0",
"CreatedAt": "2015-07-19T16:26:15Z",
"UpdatedAt": "2015-07-19T16:26:15Z"
}
}
```
### Update an existing gist
**POST** /{locale}/api/update/{id}
Params:
* `{id}`: Gist Id (required)
* `form[content]`: String (required)
**Responses:**
* Code `405`: Method Not Allowed
* Code `400`: Bad Request
* Code `200`: A json which contains gist's information. Example:
```javascript
{
"url": "https:\/\/gist.deblan.org\/en\/view\/55abcfa7771e0\/abcgi72967dd95e3461490dcaa310d728d6adef",
"gist": {
"Id": 66,
"Title": "test prod",
"Cipher": false,
"Type": "javascript",
"File": "55abcfa7771e0",
"CreatedAt": "2015-07-19T16:26:15Z",
"UpdatedAt": "2015-07-19T16:30:15Z"
}
}
```
Console
-------
* **Create a gist**: `$ app/console --help create`
* **Update a gist**: `$ app/console --help update`
* **Create user**: `app/console --help user:create`
* **Show stats**: `$ app/console --help stats`
Deployment
----------
Gist uses [Magallanes](http://magephp.com/) to manage deployment.
**Global installation**
$ composer global require andres-montanez/magallanes
# if the envvar PATH contains "$HOME/bin/"
$ ln -s ~/.composer/vendor/bin/mage ~/bin/mage
**Local installation**
$ composer require andres-montanez/magallanes
There is an example of the configuration of an environment in `.mage/config/environment/prod.yml.dist`.
# global installation
$ mage deploy to:prod
# local installation
$ ./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

@ -9,6 +9,14 @@ $app->register(new TwigServiceProvider(), array(
$app->extend('twig', function ($twig, $app) {
$base = str_replace($app['request']->server->get('SCRIPT_NAME'), '', $app['request']->getBaseUrl());
$twig->addGlobal('web_path', $base.'/');
return $twig;
});
$app['geshi'] = $app->share(function ($app) {
$geshi = new GeSHi();
$geshi->enable_classes();
$geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
return $geshi;
});

View file

@ -11,7 +11,7 @@ use Symfony\Component\HttpFoundation\Cookie;
$app->register(new TranslationServiceProvider(), array(
'locale' => 'en',
'locale_fallback' => 'en',
'locales' => array('en', 'fr', 'es', 'de', 'cn', 'pl'),
'locales' => array('en', 'fr', 'es', 'de'),
));
$app['translator'] = $app->extend('translator', function ($translator, $app) {
@ -50,7 +50,7 @@ $app->get('/', function (Request $request) use ($app) {
$foundLocale = $app['translator']->getLocale();
foreach ($app['locales'] as $locale) {
if ($cookieValue === $locale || $accept->has($locale)) {
if ($cookie === $locale || $accept->has($locale)) {
$foundLocale = $locale;
break;
}

View file

@ -6,7 +6,7 @@ use Gist\Service\Gist;
$dataPath = $app['settings']['data']['path'];
if ($dataPath[0] !== '/') {
$app['gist_path'] = $app['root_path'].'/'.$dataPath;
$app['gist_path'] = $app['root_path'].$dataPath;
} else {
$app['gist_path'] = $dataPath;
}
@ -20,5 +20,5 @@ $app['git_working_copy'] = $app->share(function ($app) {
});
$app['gist'] = $app->share(function ($app) {
return new Gist($app['gist_path'], $app['git_wrapper'], $app['git_working_copy']);
return new Gist($app['gist_path'], $app['git_wrapper'], $app['git_working_copy'], $app['geshi']);
});

View file

@ -3,11 +3,5 @@
use Gist\Api\Client;
$app['api_client'] = $app->share(function ($app) {
$client = new Client(['base_uri' => rtrim($app['settings']['api']['base_url'], '/')]);
if (!empty($app['settings']['api']['client']['api_key'])) {
$client->setApiKey($app['settings']['api']['client']['api_key']);
}
return $client;
return new Client(['base_uri' => $app['settings']['api']['base_uri']]);
});

View file

@ -1,8 +0,0 @@
<?php
use Silex\Provider\HttpCacheServiceProvider;
$app->register(new HttpCacheServiceProvider(), array(
'http_cache.cache_dir' => $app['root_path'].'/cache/',
'http_cache.esi' => null,
));

View file

@ -6,11 +6,7 @@ security:
login_required_to_view_gist: false
login_required_to_view_embeded_gist: false
api:
enabled: true
api_key_required: false
base_url: 'https://gist.deblan.org/'
client:
api_key:
data:
path: data/git
git:

View file

@ -1,31 +0,0 @@
propel:
database:
connections:
default:
adapter: sqlite
classname: Propel\Runtime\Connection\ConnectionWrapper
dsn: "sqlite:DATABASE_PATH"
user: ~
password: ~
settings:
charset: utf8
queries:
paths:
projectDir: src/
schemaDir: src/
outputDir: src/
phpDir: src/
phpConfDir: app/config/propel
sqlDir: app/propel/sql
migrationDir: app/propel/migration
runtime:
defaultConnection: default
connections: [default]
generator:
defaultConnection: default
connections: [default]
objectModel:
addClassLevelComment: false

View file

@ -57,18 +57,10 @@ revisions:
path: /revs/{gist}
defaults: {_controller: Gist\Controller\ViewController::revisionsAction, _locale: en}
api_list:
path: /api/list/{apiKey}
defaults: {_controller: Gist\Controller\ApiController::listAction, _locale: en, apiKey: null}
api_create:
path: /api/create/{apiKey}
defaults: {_controller: Gist\Controller\ApiController::createAction, _locale: en, apiKey: null}
path: /api/create
defaults: {_controller: Gist\Controller\ApiController::createAction, _locale: en}
api_update:
path: /api/update/{gist}/{apiKey}
defaults: {_controller: Gist\Controller\ApiController::updateAction, _locale: en, apiKey: null}
api_delete:
path: /api/delete/{gist}/{apiKey}
defaults: {_controller: Gist\Controller\ApiController::deleteAction, _locale: en, apiKey: null}
path: /api/update/{gist}
defaults: {_controller: Gist\Controller\ApiController::updateAction, _locale: en}

View file

@ -2,19 +2,15 @@
<?php
use Gist\Command\CreateCommand;
use Gist\Command\ListCommand;
use Gist\Command\UpdateCommand;
use Gist\Command\StatsCommand;
use Gist\Command\DeleteCommand;
use Gist\Command\UserCreateCommand;
use Gist\Command\Migration\UpgradeTo1p4p1Command;
$app = require __DIR__.'/bootstrap.php';
$app['console']->add(new CreateCommand());
$app['console']->add(new ListCommand());
$app['console']->add(new UpdateCommand());
$app['console']->add(new DeleteCommand());
$app['console']->add(new StatsCommand());
$app['console']->add(new UserCreateCommand());
$app['console']->add(new UpgradeTo1p4p1Command());

View file

@ -20,11 +20,6 @@ app:
my:
title: '我的 Gist'
nothing: '这家伙很懒,暂时没有内容'
api:
title: 'API'
warning: 'Keep it <strong>secret!</strong>'
form:
generate: 'Regenerate'
gist:
untitled: '未命名'
@ -37,14 +32,14 @@ gist:
raw: '源文件'
download: '下载'
clone: '克隆'
embed: '引用'
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/wiki/1.4-API/">API</a></p>'
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:
@ -98,7 +93,6 @@ form:
php: 'PHP'
sql: 'SQL'
yaml: 'YAML'
markdown: 'MARKDOWN'
perl: 'PERL'
c: 'C/C++'
asp: 'ASP'

View file

@ -20,11 +20,6 @@ app:
my:
title: 'Meine Gists'
nothing: 'Nichts zu finden (momentan)!'
api:
title: 'API'
warning: 'Keep it <strong>secret!</strong>'
form:
generate: 'Regenerate'
gist:
untitled: 'Ohne Titel'
@ -37,14 +32,14 @@ gist:
raw: 'RAW'
download: 'Herunterladen'
clone: 'Klonen'
embed: 'Einfügen'
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/wiki/1.4-API/">API</a></p>'
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:
@ -98,7 +93,6 @@ form:
php: 'PHP'
sql: 'SQL'
yaml: 'YAML'
markdown: 'MARKDOWN'
perl: 'PERL'
c: 'C/C++'
asp: 'ASP'

View file

@ -20,12 +20,6 @@ app:
my:
title: 'My gists'
nothing: 'Nothing yet!'
api:
title: 'API'
warning: 'Keep it <strong>secret!</strong>'
form:
generate: 'Regenerate'
gist:
untitled: 'Untitled'
@ -38,14 +32,14 @@ gist:
raw: 'RAW'
download: 'Download'
clone: 'Clone'
embed: 'Embed'
embed: 'Embed:'
add: 'New'
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/wiki/1.4-API/">API</a></p>'
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:
@ -99,7 +93,6 @@ form:
php: 'PHP'
sql: 'SQL'
yaml: 'YAML'
markdown: 'MARKDOWN'
perl: 'PERL'
c: 'C/C++'
asp: 'ASP'

View file

@ -20,11 +20,6 @@ app:
my:
title: 'Mis Gists'
nothing: 'Nada por ahora.'
api:
title: 'API'
warning: 'Keep it <strong>secret!</strong>'
form:
generate: 'Regenerate'
gist:
untitled: 'Sin título'
@ -37,14 +32,14 @@ gist:
raw: 'RAW'
download: 'Descargar'
clone: 'Clonar'
embed: 'Insertar'
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/wiki/1.4-API/">API</a></p>'
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:
@ -98,7 +93,6 @@ form:
php: 'PHP'
sql: 'SQL'
yaml: 'YAML'
markdown: 'MARKDOWN'
perl: 'PERL'
c: 'C/C++'
asp: 'ASP'

View file

@ -20,11 +20,6 @@ app:
my:
title: 'Mes Gists'
nothing: 'Rien pour le moment !'
api:
title: 'API'
warning: 'Gardez-la <strong>secrète !</strong>'
form:
generate: 'Regénérer'
gist:
untitled: 'Sans titre'
@ -37,14 +32,14 @@ gist:
raw: 'RAW'
download: 'Télécharger'
clone: 'Clôner'
embed: 'Insérer'
embed: 'Insérer : '
add: 'Ajouter'
date:
format: 'd/m/Y H\hi s\s'
footer:
text: '<p>Propulsé par <a href="https://gitnet.fr/deblan/gist">GIST</a>, c''est libre :) - <a href="https://gitnet.fr/deblan/gist/wiki/1.4-API/">API</a></p>'
text: '<p>Propulsé par <a href="https://gitnet.fr/deblan/gist">GIST</a>, c''est libre :) - <a href="https://gitnet.fr/deblan/gist#api">API</a></p>'
login:
login:
@ -98,7 +93,6 @@ form:
php: 'PHP'
sql: 'SQL'
yaml: 'YAML'
markdown: 'MARKDOWN'
perl: 'PERL'
c: 'C/C++'
asp: 'ASP'

View file

@ -1,109 +0,0 @@
app:
title: '#!GIST'
title_prefix: '#!GIST - '
menu:
home:
title: 'Home'
about:
title: 'O projekcie'
my:
login:
title: 'Logowanie'
logout:
title: 'Wylogowanie'
register:
title: 'Rejestracja'
my:
title: 'Konta'
my:
title: 'Moje wklejki'
nothing: 'Nie ma jeszcze nic!'
api:
title: 'API'
warning: 'Trzymaj to w <strong>sekrecie!</strong>'
form:
generate: 'Wygeneruj nowy'
gist:
untitled: 'Bez tytułu'
404:
title: 'Oops!'
message: 'Wklejka nie została znaleziona...'
action:
view: 'Podgląd'
history: 'Commit(s)'
raw: 'RAW'
download: 'Pobierz'
clone: 'Sklonuj'
embed: 'Embed'
add: 'Nowa'
date:
format: 'Y-m-d h:i:s'
footer:
text: '<p>Napędzane przez <a href="https://gitnet.fr/deblan/gist">GIST</a>, to jest open source :) - <a href="https://gitnet.fr/deblan/gist/wiki/1.4-API/">API</a></p>'
login:
login:
title: 'Logowanie'
invalid: 'Nie prawidłowy login lub hasło.'
form:
username:
placeholder: 'Login'
password:
placeholder: 'Hasło'
remember_me:
label: 'Pamiętaj'
register:
title: 'Nowe konto'
already_exists: 'Ta nazwa użytkownika jest już zarejestrowana!'
registred: 'Gratulacje, twoje konto zostało utworzone!'
form:
username:
placeholder: 'Login'
current_password:
placeholder: 'Obecne hasło'
password:
placeholder: 'Hasło'
form:
error:
password: 'Nie prawidłowe hasło.'
not_blank: 'To pole nie powinno być puste!'
title:
placeholder: 'Tytuł'
cipher:
label: 'Szyfrowanie: %value%'
choice:
anyway: 'Tak czy inaczej'
yes: 'Tak'
no: 'Nie'
submit: 'Wyślij'
filter: 'Filtr'
confirm: 'Potwierdzasz?'
success:
password: 'Hasło zostało zaktualizowane.'
gist: 'Wklejka została usunięta.'
type:
label: 'Język: %value%'
choice:
all: 'All'
html: 'HTML'
xml: 'XML'
css: 'CSS'
javascript: 'JAVASCRIPT'
php: 'PHP'
sql: 'SQL'
yaml: 'YAML'
markdown: 'MARKDOWN'
perl: 'PERL'
c: 'C/C++'
asp: 'ASP'
python: 'PYTHON'
bash: 'BASH'
actionscript3: 'ACTION SCRIPT'
text: 'TEXT'

22
bower.json Normal file
View file

@ -0,0 +1,22 @@
{
"name": "gist",
"version": "0.0.3",
"authors": [
"Simon Vieille <simon@deblan.fr>"
],
"description": "GIST is an open-source application to share code.",
"main": "web/index.php",
"keywords": [
"Gist",
"GIT"
],
"license": "LGPL",
"homepage": "https://gitnet.fr/deblan/gist",
"dependencies": {
"bootstrap": "3.3.4",
"flag-icon-css": "0.7.1",
"SyntaxHighlighter": "3.0.83",
"iframe-resizer": "2.8.6",
"jsdiff": "~2.2.2"
}
}

View file

@ -1,8 +1,6 @@
{
"name": "deblan/gist",
"license": "GPL-3.0-only",
"type": "project",
"require": {
"geshi/geshi": "dev-master",
"silex/silex": "1.3.x-dev",
"symfony/yaml": "~2.6",
"symfony/twig-bridge": "~2.6",
@ -15,17 +13,6 @@
"guzzlehttp/guzzle": "~6.0",
"symfony/security": "^2.7"
},
"scripts": {
"gist-scripts": [
"Gist\\Composer\\PostInstallHandler::execute"
],
"post-install-cmd": [
"@gist-scripts"
],
"post-update-cmd": [
"@gist-scripts"
]
},
"autoload": {
"psr-0": {
"": "src/"

View file

@ -1,9 +0,0 @@
{
"dependencies": {
"bootstrap": "^3.4.1",
"diff": "^3.5.0",
"flag-icon-css": "^0.8.6",
"iframe-resizer": "^2.8.10",
"jquery": ">=3.4.0"
}
}

View file

@ -4,9 +4,9 @@ propel:
default:
adapter: mysql
classname: Propel\Runtime\Connection\ConnectionWrapper
dsn: "mysql:host=DATABASE_HOST;dbname=DATABASE_NAME"
user: "DATABASE_USERNAME"
password: "DATABASE_PASSWORD"
dsn: "mysql:host=localhost;dbname=gist"
user: root
password: root
settings:
charset: utf8
queries:

View file

@ -12,53 +12,31 @@ use GuzzleHttp\Client as BaseClient;
class Client extends BaseClient
{
/**
* URI of creation.
*
* URI of creation
*
* @const string
*/
const CREATE = '/en/api/create';
/**
* URI of update.
* URI of updating
*
* @const string
*/
const UPDATE = '/en/api/update/{gist}';
/**
* URI of delete.
* Creates a gist
*
* @const string
*/
const DELETE = '/en/api/delete/{gist}';
/**
* URI of list.
*
* @const string
*/
const LIST = '/en/api/list';
/**
* The API key.
*
* @var string|null
*/
protected $apiKey;
/**
* Creates a gist.
*
* @param string $title The title
* @param string $type The type
* @param string $title The title
* @param string $type The type
* @param string $content The content
*
* @return array
*/
public function create($title, $type, $content)
{
$response = $this->post(
$this->mergeApiKey(self::CREATE),
self::CREATE,
array(
'form_params' => array(
'form' => array(
@ -78,9 +56,9 @@ class Client extends BaseClient
}
/**
* Clones and update a gist.
* Clones and update a gist
*
* @param string $gist Gist's ID
* @param string $gist Gist's ID
* @param string $content The content
*
* @return array
@ -88,7 +66,7 @@ class Client extends BaseClient
public function update($gist, $content)
{
$response = $this->post(
str_replace('{gist}', $gist, $this->mergeApiKey(self::UPDATE)),
str_replace('{gist}', $gist, self::UPDATE),
array(
'form_params' => array(
'form' => array(
@ -104,81 +82,4 @@ class Client extends BaseClient
return [];
}
/**
* Deletes a gist.
*
* @param string $gist Gist's ID
*
* @return array
*/
public function delete($gist)
{
$response = $this->post(str_replace('{gist}', $gist, $this->mergeApiKey(self::DELETE)));
if ($response->getStatusCode() === 200) {
return json_decode($response->getBody()->getContents(), true);
}
return [];
}
/**
* Lists the user's gists.
*
* @param string $gist Gist's ID
* @param string $content The content
*
* @return array
*/
public function list()
{
$response = $this->get($this->mergeApiKey(self::LIST));
if ($response->getStatusCode() === 200) {
return json_decode($response->getBody()->getContents(), true);
}
return [];
}
/*
* Merges the API key with the given url.
*
* @param string $url
*
* @return string
*/
public function mergeApiKey($url)
{
if (empty($this->apiKey)) {
return $url;
}
return rtrim($url, '/').'/'.$this->apiKey;
}
/*
* Set the value of "apiKey".
*
* @param string|null $apiKey
*
* @return Client
*/
public function setApiKey($apiKey)
{
$this->apiKey = $apiKey;
return $this;
}
/*
* Get the value of "apiKey".
*
* @return string|null
*/
public function getApiKey()
{
return $this->apiKey;
}
}

View file

@ -7,8 +7,6 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Propel\Runtime\Parser\YamlParser;
use Symfony\Component\Yaml\Yaml;
/**
* class CreateCommand.
@ -29,9 +27,8 @@ class CreateCommand extends Command
->addArgument('input', InputArgument::REQUIRED, 'Input')
->addArgument('type', InputArgument::OPTIONAL, 'Type', 'text')
->addOption('title', 't', InputOption::VALUE_REQUIRED, 'Title of the gist')
->addOption('all', 'a', InputOption::VALUE_NONE, 'Display all the response')
->addOption('id', 'i', InputOption::VALUE_NONE, 'Display only the id of the gist')
->addOption('json', 'j', InputOption::VALUE_NONE, 'Format the response to json')
->addOption('show-url', 'u', InputOption::VALUE_NONE, 'Display only the gist url')
->addOption('show-id', 'i', InputOption::VALUE_NONE, 'Display only the gist Id')
->setHelp(<<<EOF
Provides a client to create a gist using the API.
@ -46,15 +43,12 @@ Arguments:
Options:
<info>--title</info>, <info>-t</info>
Defines a title
<info>--show-id</info>, <info>-i</info>
Display only the Id of the gist
<info>--id</info>, <info>-i</info>
Display only the id of the gist
<info>--all</info>, <info>-a</info>
Display all the response
<info>--json</info>, <info>-j</info>
Format the response to json
<info>--show-url</info>, <info>-u</info>
Display only the url of the gist
EOF
);
}
@ -64,10 +58,11 @@ EOF
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
//$output->writeln(sprintf('<comment>%s</comment> bar.', 'test'));
$file = $input->getArgument('input');
$type = $input->getArgument('type');
$title = $input->getOption('title');
$json = $input->getOption('json');
if ($file === '-') {
$content = file_get_contents('php://stdin');
@ -99,17 +94,19 @@ EOF
$gist = $this->getSilexApplication()['api_client']->create($title, $type, $content);
if ($input->getOption('id')) {
$id = isset($gist['gist']['id']) ? $gist['gist']['id'] : $gist['gist']['Id'];
$result = $json ? json_encode(array('id' => $id)) : $id;
} elseif ($input->getOption('all')) {
$result = $json ? json_encode($gist) : Yaml::dump($gist);
} else {
$url = $gist['url'];
$result = $json ? json_encode(array('url' => $url)) : $url;
if ($input->getOption('show-url')) {
$output->writeln($gist['url']);
return true;
}
$output->writeln($result);
if ($input->getOption('show-id')) {
$output->writeln($gist['gist']['Id']);
return true;
}
$output->writeln(json_encode($gist));
}
/**

View file

@ -1,48 +0,0 @@
<?php
namespace Gist\Command;
use Knp\Command\Command;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
/**
* class DeleteCommand.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class DeleteCommand extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('delete')
->setDescription('Delete a gist using the API')
->addOption('gist', null, InputOption::VALUE_REQUIRED, 'Id or File of the gist')
->setHelp(<<<'EOF'
Provides a client to delete a gist using the API.
Arguments:
none.
Options:
<info>--gist</info>
Defines the Gist to delete by using its Id or its File
EOF
);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$result = $this->getSilexApplication()['api_client']->delete($input->getOption('gist'));
$output->writeln(empty($result['error']) ? 'OK' : '<error>An error occured.</error>');
}
}

View file

@ -1,59 +0,0 @@
<?php
namespace Gist\Command;
use Knp\Command\Command;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Propel\Runtime\Parser\YamlParser;
use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Console\Helper\Table;
use DateTime;
/**
* class ListCommand.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class ListCommand extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('gists')
->setDescription('List gists using the API');
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$gists = $this->getSilexApplication()['api_client']->list();
$rows = [];
foreach ($gists as $gist) {
$rows[] = array(
$gist['id'],
$gist['title'],
$gist['cipher'] ? 'y' : 'n',
$gist['type'],
(new DateTime($gist['createdAt']))->format('Y-m-d H:i:s'),
(new DateTime($gist['updatedAt']))->format('Y-m-d H:i:s'),
$gist['url'],
);
}
$table = new Table($output);
$table
->setHeaders(array('ID', 'Title', 'Cipher', 'Type', 'Created At', 'Updated At', 'url'))
->setRows($rows);
$table->render();
}
}

View file

@ -7,7 +7,6 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Yaml\Yaml;
/**
* class UpdateCommand.
@ -27,9 +26,8 @@ class UpdateCommand extends Command
->setDescription('Update a gist using the API')
->addArgument('input', InputArgument::REQUIRED, 'Input')
->addOption('gist', null, InputOption::VALUE_REQUIRED, 'Id or File of the gist')
->addOption('all', 'a', InputOption::VALUE_NONE, 'Display all the response')
->addOption('id', 'i', InputOption::VALUE_NONE, 'Display only the id of the gist')
->addOption('json', 'j', InputOption::VALUE_NONE, 'Format the response to json')
->addOption('show-url', 'u', InputOption::VALUE_NONE, 'Display only the gist url')
->addOption('show-id', 'i', InputOption::VALUE_NONE, 'Display only the gist Id')
->setHelp(<<<EOF
Provides a client to create a gist using the API.
@ -45,14 +43,11 @@ Options:
<info>--gist</info>
Defines the Gist to update by using its Id or its File
<info>--id</info>, <info>-i</info>
Display only the id of the gist
<info>--show-id</info>, <info>-i</info>
Display only the Id of the gist
<info>--all</info>, <info>-a</info>
Display all the response
<info>--json</info>, <info>-j</info>
Format the response to json
<info>--show-url</info>, <info>-u</info>
Display only the url of the gist
EOF
);
}
@ -62,9 +57,10 @@ EOF
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
//$output->writeln(sprintf('<comment>%s</comment> bar.', 'test'));
$file = $input->getArgument('input');
$gist = $input->getOption('gist');
$json = $input->getOption('json');
if ($file === '-') {
$content = file_get_contents('php://stdin');
@ -90,17 +86,19 @@ EOF
$gist = $this->getSilexApplication()['api_client']->update($gist, $content);
if ($input->getOption('id')) {
$id = isset($gist['gist']['id']) ? $gist['gist']['id'] : $gist['gist']['Id'];
$result = $json ? json_encode(array('id' => $id)) : $id;
} elseif ($input->getOption('all')) {
$result = $json ? json_encode($gist) : Yaml::dump($gist);
} else {
$url = $gist['url'];
$result = $json ? json_encode(array('url' => $url)) : $url;
if ($input->getOption('show-url')) {
$output->writeln($gist['url']);
return true;
}
$output->writeln($result);
if ($input->getOption('show-id')) {
$output->writeln($gist['gist']['Id']);
return true;
}
$output->writeln(json_encode($gist));
}
/**

View file

@ -1,211 +0,0 @@
<?php
namespace Gist\Composer;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Process;
use Composer\Script\Event;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Yaml\Yaml;
/**
* class PostInstallHandler.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class PostInstallHandler
{
public static function execute(Event $event)
{
$helper = new QuestionHelper();
$input = new ArgvInput();
$output = new ConsoleOutput();
$filesystem = new Filesystem();
$output->writeln('+==============================+');
$output->writeln('| GIST Setup |');
$output->writeln('+==============================+');
$output->writeln('');
$output->writeln('1. Database');
$output->writeln('===========');
$configure = true;
if ($filesystem->exists('propel.yaml')) {
$output->writeln('The configuration file exists.');
$question = new ConfirmationQuestion('Your current configuration will not be merged. Do you want to override it? [y/N] ', false);
$configure = $helper->ask($input, $output, $question);
} else {
$configure = true;
}
if ($configure) {
$choices = ['MySQL/MariaDB', 'SQLite', 'other'];
$question = new ChoiceQuestion('Which DBMS? ', $choices, 0);
$dbms = $helper->ask($input, $output, $question);
$substitutions = [];
if ($dbms === 'MySQL/MariaDB') {
$templateName = 'propel.yaml.dist-mysql';
$question = new Question('Host: [127.0.0.1] ', '127.0.0.1');
$substitutions['DATABASE_HOST'] = $helper->ask($input, $output, $question);
$question = new Question('Database (it must exists!): [gist] ', 'gist');
$substitutions['DATABASE_NAME'] = $helper->ask($input, $output, $question);
$question = new Question('Username: [root] ', 'root');
$substitutions['DATABASE_USERNAME'] = $helper->ask($input, $output, $question);
$question = new Question('Password: [] ', '');
$substitutions['DATABASE_PASSWORD'] = $helper->ask($input, $output, $question);
} elseif ($dbms === 'SQLite') {
$defaultPath = getcwd().'/data/gist.sqlite';
$question = new Question("Ok! Where do you want me to save datas? [$defaultPath] ", $defaultPath);
$substitutions['DATABASE_PATH'] = $helper->ask($input, $output, $question);
$templateName = 'propel.yaml.dist-sqlite';
} else {
$output->writeln('See README.md to perform the configuration.');
return;
}
$template = file_get_contents('app/config/'.$templateName);
$content = str_replace(
array_keys($substitutions),
array_values($substitutions),
$template
);
$done = file_put_contents('propel.yaml', $content) !== false;
if ($done) {
$output->writeln('Running migration...');
foreach (['config:convert', 'model:build --recursive', 'migration:diff --recursive', 'migration:migrate --recursive'] as $arg) {
$command = self::getPhp(true).' ./vendor/propel/propel/bin/propel '.$arg;
$process = new Process($command);
$process->run();
if (!$process->isSuccessful()) {
$output->writeln('An error occured while executing:');
$output->writeln($command);
$output->writeln('To perform the configuration. See README.md.');
return;
}
}
$output->writeln('Done!');
} else {
$output->writeln('An error occured. See README.md to perform the configuration.');
}
}
$output->writeln('');
$output->writeln('2. Application');
$output->writeln('==============');
$configure = true;
if ($filesystem->exists('app/config/config.yml')) {
$output->writeln('The configuration file exists.');
$question = new ConfirmationQuestion('Your current configuration will not be merged. Do you want to override it? [y/N] ', false);
$configure = $helper->ask($input, $output, $question);
} else {
$configure = true;
}
if ($configure) {
$output->writeln('');
$output->writeln(' 2.1 Security');
$output->writeln('-------------');
$token = str_shuffle(sha1(microtime().uniqid()));
$question = new ConfirmationQuestion('Registration enabled: [Y/n] ', true);
$enableRegistration = $helper->ask($input, $output, $question);
$question = new ConfirmationQuestion('Login enabled: [Y/n] ', true);
$enableLogin = $helper->ask($input, $output, $question);
$question = new ConfirmationQuestion('Login required to edit a gist: [y/N] ', false);
$loginRequiredToEditGist = $helper->ask($input, $output, $question);
$question = new ConfirmationQuestion('Login required to view a gist: [y/N] ', false);
$loginRequiredToViewGist = $helper->ask($input, $output, $question);
$question = new ConfirmationQuestion('Login required to view an embeded gist: [y/N] ', false);
$loginRequiredToViewEmbededGist = $helper->ask($input, $output, $question);
$output->writeln('');
$output->writeln(' 2.2 API');
$output->writeln('--------');
$question = new ConfirmationQuestion('API enabled: [Y/n] ', true);
$apiEnabled = $helper->ask($input, $output, $question);
if ($apiEnabled) {
$question = new ConfirmationQuestion('API key required: [y/N] ', false);
$apikeyRequired = $helper->ask($input, $output, $question);
} else {
$apikeyRequired = false;
}
$question = new Question('[Client] API base URL: [https://gist.deblan.org/] ', 'https://gist.deblan.org/');
$apiBaseUrl = $helper->ask($input, $output, $question);
$question = new Question('[Client] API key: [] ', '');
$apiClientApiKey = $helper->ask($input, $output, $question);
$configuration = [
'security' => [
'token' => $token,
'enable_registration' => $enableRegistration,
'enable_login' => $enableLogin,
'login_required_to_edit_gist' => $loginRequiredToEditGist,
'login_required_to_view_gist' => $loginRequiredToViewGist,
'login_required_to_view_embeded_gist' => $loginRequiredToViewEmbededGist,
],
'api' => [
'enabled' => $apiEnabled,
'api_key_required' => $apikeyRequired,
'base_url' => $apiBaseUrl,
'client' => [
'api_key' => $apiClientApiKey,
],
],
'data' => [
'path' => 'data/git',
],
'git' => [
'path' => '/usr/bin/git',
],
'theme' => [
'name' => 'dark',
],
];
$content = (new Yaml())->dump($configuration);
$done = file_put_contents('app/config/config.yml', $content);
}
$output->writeln('');
$output->writeln('Configuration finished!');
}
protected static function getPhp($includeArgs = true)
{
$phpFinder = new PhpExecutableFinder();
if (!$phpPath = $phpFinder->find($includeArgs)) {
throw new \RuntimeException('The php executable could not be found, add it to your PATH environment variable and try again');
}
return $phpPath;
}
}

View file

@ -8,9 +8,6 @@ use Symfony\Component\HttpFoundation\JsonResponse;
use Gist\Form\ApiCreateGistForm;
use Gist\Model\GistQuery;
use Gist\Form\ApiUpdateGistForm;
use GitWrapper\GitException;
use Gist\Model\UserQuery;
use Propel\Runtime\ActiveQuery\Criteria;
/**
* Class ApiController.
@ -19,80 +16,10 @@ use Propel\Runtime\ActiveQuery\Criteria;
*/
class ApiController extends Controller
{
/**
* Lists gists.
*
* @param Request $request
* @param string $apiKey
*
* @return JsonResponse
*/
public function listAction(Request $request, $apiKey)
public function createAction(Request $request)
{
$app = $this->getApp();
if (false === $app['settings']['api']['enabled']) {
return new Response('', 403);
}
if (false === $this->isValidApiKey($apiKey, true)) {
return $this->invalidApiKeyResponse();
}
if (false === $request->isMethod('get')) {
return $this->invalidMethodResponse('GET method is required.');
}
$user = $app['user.provider']->loadUserByApiKey($apiKey);
$criteria = GistQuery::create()
->limit(15)
->orderById(Criteria::DESC);
$gists = $user->getGists($criteria);
$data = array();
foreach ($gists as $gist) {
try {
$history = $app['gist']->getHistory($gist);
$value = $gist->toArray();
$value['url'] = $request->getSchemeAndHttpHost().$app['url_generator']->generate(
'view',
array(
'gist' => $gist->getFile(),
'commit' => array_pop($history)['commit'],
)
);
$data[] = $value;
} catch (GitException $e) {
}
}
return new JsonResponse($data);
}
/**
* Creates a gist.
*
* @param Request $request
* @param string $apiKey
*
* @return JsonResponse
*/
public function createAction(Request $request, $apiKey)
{
$app = $this->getApp();
if (false === $app['settings']['api']['enabled']) {
return new Response('', 403);
}
if (false === $this->isValidApiKey($apiKey, (bool) $app['settings']['api']['api_key_required'])) {
return $this->invalidApiKeyResponse();
}
if (false === $request->isMethod('post')) {
return $this->invalidMethodResponse('POST method is required.');
}
@ -109,65 +36,40 @@ class ApiController extends Controller
$form->submit($request);
if ($form->isValid()) {
$user = !empty($apiKey) ? $app['user.provider']->loadUserByApiKey($apiKey) : null;
$gist = $app['gist']->create(new Gist(), $form->getData());
$gist
->setCipher(false)
->setUser($user)
->save();
$gist->setCipher(false)->save();
$history = $app['gist']->getHistory($gist);
$data = $gist->toArray();
$data['url'] = $request->getSchemeAndHttpHost().$app['url_generator']->generate(
'view',
array(
'gist' => $gist->getFile(),
'commit' => array_pop($history)['commit'],
)
);
return new JsonResponse($data);
return new JsonResponse(array(
'url' => $request->getSchemeAndHttpHost().$app['url_generator']->generate(
'view',
array(
'gist' => $gist->getFile(),
'commit' => array_pop($history)['commit'],
)
),
'gist' => $gist->toArray(),
));
}
return $this->invalidRequestResponse('Invalid field(s)');
}
/**
* Updates a gist.
*
* @param Request $request
* @param string $gist
* @param string $apiKey
*
* @return JsonResponse
*/
public function updateAction(Request $request, $gist, $apiKey)
public function updateAction(Request $request, $gist)
{
$app = $this->getApp();
if (false === $app['settings']['api']['enabled']) {
return new Response('', 403);
}
if (false === $this->isValidApiKey($apiKey, (bool) $app['settings']['api']['api_key_required'])) {
return $this->invalidApiKeyResponse();
}
if (false === $request->isMethod('post')) {
return $this->invalidMethodResponse('POST method is required.');
}
$query = GistQuery::create()
->filterByCipher(false);
if (ctype_digit($gist)) {
$query->filterById((int) $gist);
} else {
$query->filterByFile($gist);
}
$gist = $query->findOne();
$gist = GistQuery::create()
->filterByCipher(false)
->filterById((int) $gist)
->_or()
->filterByFile($gist)
->findOne();
if (!$gist) {
return $this->invalidRequestResponse('Invalid Gist');
@ -189,88 +91,21 @@ class ApiController extends Controller
$history = $app['gist']->getHistory($gist);
$data = $gist->toArray();
$data['url'] = $request->getSchemeAndHttpHost().$app['url_generator']->generate(
'view',
array(
'gist' => $gist->getFile(),
'commit' => array_pop($history)['commit'],
)
);
return new JsonResponse($data);
return new JsonResponse(array(
'url' => $request->getSchemeAndHttpHost().$app['url_generator']->generate(
'view',
array(
'gist' => $gist->getFile(),
'commit' => array_pop($history)['commit'],
)
),
'gist' => $gist->toArray(),
));
}
return $this->invalidRequestResponse('Invalid field(s)');
}
/**
* Deletes a gist.
*
* @param Request $request
* @param string $gist
* @param string $apiKey
*
* @return JsonResponse
*/
public function deleteAction(Request $request, $gist, $apiKey)
{
$app = $this->getApp();
if (false === $app['settings']['api']['enabled']) {
return new Response('', 403);
}
if (false === $this->isValidApiKey($apiKey, true)) {
return $this->invalidApiKeyResponse();
}
if (false === $request->isMethod('post')) {
return $this->invalidMethodResponse('POST method is required.');
}
$user = $app['user.provider']->loadUserByApiKey($apiKey);
$gist = GistQuery::create()
->filterById((int) $gist)
->_or()
->filterByFile($gist)
->filterByUser($user)
->findOne();
if (!$gist) {
return $this->invalidRequestResponse('Invalid Gist');
}
$gist->delete();
return new JsonResponse(['error' => false]);
}
/**
* Builds an invalid api key response.
*
* @param mixed $message
*
* @return JsonResponse
*/
protected function invalidApiKeyResponse()
{
$data = [
'error' => ' Unauthorized',
'message' => 'Invalid API KEY',
];
return new JsonResponse($data, 401);
}
/**
* Builds an invalid method response.
*
* @param mixed $message
*
* @return JsonResponse
*/
protected function invalidMethodResponse($message = null)
{
$data = [
@ -281,13 +116,6 @@ class ApiController extends Controller
return new JsonResponse($data, 405);
}
/**
* Builds an invalid request response.
*
* @param mixed $message
*
* @return JsonResponse
*/
protected function invalidRequestResponse($message = null)
{
$data = [
@ -297,24 +125,4 @@ class ApiController extends Controller
return new JsonResponse($data, 400);
}
/**
* Checks if the given api key is valid
* depending of the requirement.
*
* @param mixed $apiKey
* @param mixed $required
*
* @return bool
*/
protected function isValidApiKey($apiKey, $required = false)
{
if (empty($apiKey)) {
return !$required;
}
return UserQuery::create()
->filterByApiKey($apiKey)
->count() === 1;
}
}

View file

@ -13,34 +13,13 @@ use Symfony\Component\HttpFoundation\Response;
*
* @author Simon Vieille <simon@deblan.fr>
*/
abstract class Controller
class Controller
{
/**
* @var Application
*/
protected $app;
/**
* @var array
*/
protected $types = [
'html',
'css',
'javascript',
'php',
'sql',
'xml',
'yaml',
'markdown',
'perl',
'c',
'asp',
'python',
'bash',
'actionscript3',
'text',
];
/**
* __construct.
*
@ -109,10 +88,10 @@ abstract class Controller
return array(
'gist' => $gist,
'type' => $gist->getType(),
'types' => $this->types,
'history' => $history,
'commit' => $commit,
'raw_content' => $content,
'content' => $app['gist']->highlight($gist->getGeshiType(), $content),
);
}
@ -149,17 +128,12 @@ abstract class Controller
/**
* Returns the connected user.
*
* @param Request $request An API request
*
* @return mixed
*/
public function getUser(Request $request = null)
public function getUser()
{
$app = $this->getApp();
if (!empty($request)) {
}
$securityContext = $app['security.token_storage'];
$securityToken = $securityContext->getToken();
@ -201,32 +175,4 @@ abstract class Controller
$params
);
}
/**
* Creates a Response.
*
* @param string $template
* @param array $params
*
* @return Response
*/
public function createResponse($template, array $params = null)
{
$body = $this->render($template, $params);
$response = new Response($body);
if (empty($params['no_cache'])) {
$ttl = 3600 * 24 * 7;
$etag = sha1($response->getContent());
$response->setTtl($ttl);
$response->setClientTtl($ttl);
$response->setExpires(new \DateTime('now +7 days'));
$response->setLastModified(new \DateTime('now'));
$response->setEtag($etag, true);
}
return $response;
}
}

View file

@ -8,8 +8,6 @@ use Gist\Form\CloneGistForm;
use Gist\Model\Gist;
use GitWrapper\GitException;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\Response;
/**
* Class EditController.
@ -23,7 +21,7 @@ class EditController extends Controller
*
* @param Request $request
*
* @return Response
* @return string
*/
public function createAction(Request $request)
{
@ -39,28 +37,17 @@ class EditController extends Controller
if ($request->isMethod('post')) {
$form->submit($request);
$data = $form->getData();
if (empty($form->getData()['content']) && $form->get('file')->getData()) {
$data['content'] = file_get_contents($form->get('file')->getData()->getPathName());
unset($data['file']);
}
if (empty($data['content'])) {
$form->get('content')->addError(new FormError($app['translator']->trans('form.error.not_blank')));
}
if ($form->isValid()) {
$gist = $app['gist']->create(new Gist(), $data, $this->getUser());
$gist = $app['gist']->create(new Gist(), $form->getData(), $this->getUser());
}
}
return $this->createResponse(
return $this->render(
'Edit/index.html.twig',
array(
'gist' => isset($gist) ? $gist : null,
'form' => $form->createView(),
'no_cache' => true,
)
);
}
@ -70,7 +57,7 @@ class EditController extends Controller
*
* @param Request $request
*
* @return Response
* @return string
*/
public function cloneAction(Request $request, $gist, $commit)
{
@ -110,8 +97,7 @@ class EditController extends Controller
}
$viewOptions['form'] = $form->createView();
$viewOptions['no_cache'] = true;
return $this->createResponse('Edit/clone.html.twig', $viewOptions);
return $this->render('Edit/clone.html.twig', $viewOptions);
}
}

View file

@ -20,13 +20,13 @@ class LoginController extends Controller
*
* @param Request $request
*
* @return Response
* @return string
*/
public function registerAction(Request $request)
{
$app = $this->getApp();
if (false === $app['settings']['security']['enable_registration']) {
if (false === $app['settings']['enable_registration']) {
return new Response('', 403);
}
@ -57,13 +57,12 @@ class LoginController extends Controller
}
}
return $this->createResponse(
return $this->render(
'Login/register.html.twig',
[
'form' => $form->createView(),
'error' => isset($error) ? $error : '',
'success' => isset($success) ? $success : '',
'no_cache' => true,
]
);
}
@ -73,13 +72,13 @@ class LoginController extends Controller
*
* @param Request $request
*
* @return Response
* @return string
*/
public function loginAction(Request $request)
{
$app = $this->getApp();
if (false === $app['settings']['security']['enable_login']) {
if (false === $app['settings']['enable_login']) {
return new Response('', 403);
}
@ -98,12 +97,11 @@ class LoginController extends Controller
$error = $app['translator']->trans('login.login.invalid');
}
return $this->createResponse(
return $this->render(
'Login/login.html.twig',
[
'form' => $form->createView(),
'error' => isset($error) ? $error : '',
'no_cache' => true,
]
);
}

View file

@ -7,7 +7,6 @@ use Gist\Form\DeleteGistForm;
use Gist\Form\FilterGistForm;
use Gist\Form\UserPasswordForm;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
/**
* Class MyController.
@ -22,7 +21,7 @@ class MyController extends Controller
* @param Request $request
* @param int $page
*
* @return Response
* @return string
*/
public function myAction(Request $request, $page)
{
@ -59,26 +58,6 @@ class MyController extends Controller
$gists = $this->getUser()->getGistsPager($page, $options);
$apiKey = $this->getUser()->getApiKey();
if (empty($apiKey)) {
$regenerateApiKey = true;
}
// FIXME: CSRF issue!
elseif ($request->request->get('apiKey') === $apiKey && $request->request->has('generateApiKey')) {
$regenerateApiKey = true;
} else {
$regenerateApiKey = false;
}
if ($regenerateApiKey) {
$apiKey = $app['salt_generator']->generate(32, true);
$this->getUser()
->setApiKey($apiKey)
->save();
}
if ($request->isMethod('post')) {
$deleteForm->handleRequest($request);
$passwordForm->handleRequest($request);
@ -120,17 +99,15 @@ class MyController extends Controller
}
}
return $this->createResponse(
return $this->render(
'My/my.html.twig',
array(
'gists' => $gists,
'page' => $page,
'apiKey' => $apiKey,
'deleteForm' => $deleteForm->createView(),
'filterForm' => $filterForm->createView(),
'passwordForm' => $passwordForm->createView(),
'deleted' => !empty($deleted),
'no_cache' => true,
)
);
}

View file

@ -31,18 +31,7 @@ class ViewController extends Controller
$viewOptions = $this->getViewOptions($request, $gist, $commit);
if (is_array($viewOptions)) {
if ($request->query->has('type')) {
$type = $request->query->get('type');
if (in_array($type, $this->types)) {
$viewOptions['gist']->setType($type);
$viewOptions['type_overrided'] = true;
}
}
$viewOptions['no_cache'] = true;
return $this->createResponse('View/view.html.twig', $viewOptions);
return $this->render('View/view.html.twig', $viewOptions);
} else {
return $this->notFoundResponse();
}
@ -64,16 +53,7 @@ class ViewController extends Controller
$viewOptions = $this->getViewOptions($request, $gist, $commit);
if (is_array($viewOptions)) {
if ($request->query->has('type')) {
$type = $request->query->get('type');
if (in_array($type, $this->types)) {
$viewOptions['gist']->setType($type);
$viewOptions['type_overrided'] = true;
}
}
return $this->createResponse('View/embed.html.twig', $viewOptions);
return $app['twig']->render('View/embed.html.twig', $viewOptions);
} else {
return $this->notFoundResponse();
}
@ -86,25 +66,19 @@ class ViewController extends Controller
* @param string $gist Gist's ID
* @param string $commit The commit
*
* @return Response
* @return string|Response
*/
public function embedJsAction(Request $request, $gist, $commit)
{
$viewOptions = $this->getViewOptions($request, $gist, $commit);
if ($request->query->has('type')) {
$type = $request->query->get('type');
if (in_array($type, $this->types)) {
$viewOptions['gist']->setType($type);
$viewOptions['type_overrided'] = true;
}
}
$response = $this->createResponse('View/embedJs.html.twig', $viewOptions);
$response->headers->set('Content-Type', 'text/javascript');
return $response;
return new Response(
$this->render('View/embedJs.html.twig', $viewOptions),
200,
array(
'Content-Type' => 'text/javascript',
)
);
}
/**
@ -114,7 +88,7 @@ class ViewController extends Controller
* @param string $gist Gist's ID
* @param string $commit The commit
*
* @return Response
* @return string|Response
*/
public function rawAction(Request $request, $gist, $commit)
{
@ -140,7 +114,7 @@ class ViewController extends Controller
* @param string $gist Gist's ID
* @param string $commit The commit
*
* @return Response
* @return string|Response
*/
public function downloadAction(Request $request, $gist, $commit)
{
@ -157,10 +131,9 @@ class ViewController extends Controller
200,
array(
'Content-Disposition' => sprintf('filename=%s.%s', $gist->getFile(), $gist->getTypeAsExtension()),
'Content-Length' => mb_strlen($viewOptions['raw_content']),
'Content-Length' => filesize($file),
'Content-Type' => 'application/force-download',
),
false
)
);
} else {
return $this->notFoundResponse($app);
@ -173,7 +146,7 @@ class ViewController extends Controller
* @param Request $request
* @param string $gist Gist's ID
*
* @return Response
* @return string|Response
*/
public function revisionsAction(Request $request, $gist)
{
@ -191,7 +164,7 @@ class ViewController extends Controller
return $this->notFoundResponse();
}
return $this->createResponse(
return $this->render(
'View/revisions.html.twig',
array(
'gist' => $gist,

View file

@ -16,9 +16,7 @@ class ApiCreateGistForm extends CreateGistForm
{
parent::build($options);
$this->builder
->remove('cipher')
->remove('file');
$this->builder->remove('cipher');
return $this->builder;
}

View file

@ -18,7 +18,6 @@ class ApiUpdateGistForm extends ApiCreateGistForm
$this->builder
->remove('title')
->remove('file')
->remove('type');
return $this->builder;

View file

@ -32,25 +32,16 @@ class CreateGistForm extends AbstractForm
'content',
'textarea',
array(
'required' => false,
'required' => true,
'attr' => array(
'class' => 'form-control',
'rows' => 10,
),
'trim' => false,
'constraints' => array(
),
)
);
$this->builder->add(
'file',
'file',
array(
'required' => false,
'attr' => array(
),
'constraints' => array(
new NotBlank(array(
'message' => $this->translator->trans('form.error.not_blank'),
)),
),
)
);
@ -97,7 +88,6 @@ class CreateGistForm extends AbstractForm
'sql' => '',
'xml' => '',
'yaml' => '',
'markdown' => '',
'perl' => '',
'c' => '',
'asp' => '',

View file

@ -3,7 +3,6 @@
namespace Gist\Model;
use Gist\Model\Base\Gist as BaseGist;
use Propel\Runtime\Map\TableMap;
/**
* Class Gist.
@ -45,16 +44,14 @@ class Gist extends BaseGist
}
/**
* Returns the type for highlighting.
* Returns the type for Geshi.
*
* @return string
*/
public function getHighlightType()
public function getGeshiType()
{
$data = array(
'html' => 'xml',
'asp' => 'aspnet',
'actionscript3' => 'actionscript',
);
return str_replace(array_keys($data), array_values($data), $this->getType());
@ -75,7 +72,6 @@ class Gist extends BaseGist
'bash' => 'sh',
'actionscript3' => 'as',
'text' => 'txt',
'markdown' => 'md',
);
return str_replace(array_keys($data), array_values($data), $this->getType());
@ -90,25 +86,4 @@ class Gist extends BaseGist
return $this;
}
/**
* {@inheritdoc}
*/
public function toArray($keyType = TableMap::TYPE_PHPNAME, $includeLazyLoadColumns = true, $alreadyDumpedObjects = array(), $includeForeignObjects = false)
{
$data = parent::toArray(
$keyType,
$includeLazyLoadColumns,
$alreadyDumpedObjects,
$includeForeignObjects
);
foreach ($data as $key => $value) {
$newKey = lcfirst($key);
unset($data[$key]);
$data[$newKey] = $value;
}
return $data;
}
}

View file

@ -54,7 +54,7 @@ class User extends BaseUser implements UserInterface
*
* @return Propel\Runtime\Util\PropelModelPager
*/
public function getGistsPager($page, $options = array(), $maxPerPage = 10)
public function getGistsPager($page, $options = array(), $maxPerPage = 10)
{
$query = GistQuery::create()
->filterByUser($this)
@ -63,11 +63,11 @@ class User extends BaseUser implements UserInterface
if (!empty($options['type']) && $options['type'] !== 'all') {
$query->filterByType($options['type']);
}
if (!empty($options['title'])) {
$query->filterByTitle('%'.$options['title'].'%', Criteria::LIKE);
}
if (!empty($options['cipher']) && $options['cipher'] !== 'anyway') {
$bools = array(
'yes' => true,

View file

@ -22,7 +22,6 @@
<column name="password" type="VARCHAR" size="255" required="true" />
<column name="roles" type="VARCHAR" size="255" required="true" />
<column name="salt" type="VARCHAR" size="64" required="true" />
<column name="api_key" type="VARCHAR" size="32" required="true" />
<behavior name="timestampable"/>
</table>

View file

@ -1 +0,0 @@
../../../web/app/

View file

@ -4,7 +4,7 @@
{% block body %}
<div class="row">
<form action="{{ path('home') }}" method="post" id="main-form" enctype="multipart/form-data">
<form action="{{ path('home') }}" method="post" id="main-form">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
@ -71,10 +71,6 @@
{{ form_errors(form.content) }}
{{ form_widget(form.content) }}
</p>
<p>
{{ form_errors(form.file) }}
{{ form_widget(form.file) }}
</p>
<p>
<input type="submit" class="btn btn-primary" value="{{ 'form.submit'|trans }}">
</p>

View file

@ -205,61 +205,30 @@
</div>
</div>
{% set apiEnabled = app.settings.api.enabled %}
<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>
<div class="row">
<div class="col-md-{{ apiEnabled ? 6 : 12 }}">
<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_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>
<p>
{{ form_rest(passwordForm) }}
<input type="submit" class="btn btn-primary" value="{{ 'form.submit'|trans }}">
</p>
</form>
</div>
</div>
{% if apiEnabled %}
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
{{ 'my.api.title'|trans }}
</div>
<div class="panel-body">
<div class="tab-content">
<p>{{ 'my.api.warning'|trans|raw }}</p>
<form action="{{ path('my', params) }}" method="post">
<div class="row">
<p class="col-md-12">
<input type="text" name="apiKey" id="form-api-key" class="form-control" value="{{ apiKey }}" data-key="{{ apiKey }}">
</p>
<p class="col-md-12">
<input type="submit" name="generateApiKey" value="{{ 'my.api.form.generate'|trans }}" class="btn btn-primary">
</p>
</div>
</form>
</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
</div>

View file

@ -1,17 +1,31 @@
{% extends 'base.html.twig' %}
{% block css %}
{{ parent() }}
<link rel="stylesheet" href="{{ web_path }}app/css/prism.css">
{% if theme_settings.name == 'dark' %}
<link rel="stylesheet" href="{{ web_path }}app/css/prism-okaidia.css">
{% if gist.cipher %}
<link type="text/css" rel="Stylesheet" href="{{ web_path }}components/SyntaxHighlighter/styles/shCoreRDark.css" />
<link type="text/css" rel="Stylesheet" href="{{ web_path }}components/SyntaxHighlighter/styles/shThemeRDark.css" />
{% else %}
<link rel="stylesheet" href="{{ web_path }}app/css/prism-solarizedlight.css">
<link rel="stylesheet" href="{{ web_path }}app/css/geshi/vibrant-ink.css" />
{% endif %}
{{ parent() }}
<style type="text/css">
#embed {
padding: 0;
}
#viewer {
background: #222;
}
pre {
font-size: 14px;
padding: 5px;
margin-bottom: 0;
background: #333;
}
.panel-body {
padding: 0;
}
@ -20,8 +34,9 @@
margin-bottom: 0;
}
.container-fluid {
padding: 0;
pre ol {
padding-left: 0 !important;
list-style: none;
}
</style>
{% endblock %}
@ -31,83 +46,45 @@
{% block nav %}{% endblock %}
{% block body %}
{% set routeParams = app.request.attributes.get('_route_params') %}
{% if type_overrided is defined %}
{% set routeParams = routeParams|merge({type: gist.type}) %}
{% endif %}
<div class="row">
<div class="col-md-12" id="embed">
<div class="panel panel-default">
<div class="panel-heading">
<div class="row">
<div class="col-md-6 col-xs-9">
{{ gist.title ? gist.title : 'gist.untitled'|trans }}
</div>
<div class="col-md-6 col-xs-3">
<div class="visible-sm visible-xs pull-right">
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-menu-hamburger"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li>
<a target="_blank" href="{{ path('view', routeParams) }}" class="cipher-link">
<span class="btn btn-warning btn-xs">
{{ commit|slice(0, 10) }}
</span>
</a>
</li>
{% if not gist.cipher %}
<li>
<a target="_blank" href="{{ path('raw', routeParams) }}">
{{ 'gist.action.raw'|trans }}
</a>
</li>
<li>
<a target="_blank" href="{{ path('download', routeParams) }}">
{{ 'gist.action.download'|trans }}
</a>
</li>
{% endif %}
<li>
<a target="_blank" href="{{ path('clone', routeParams) }}" class="cipher-link">
{{ 'gist.action.clone'|trans }}
</a>
</li>
</ul>
</div>
</div>
<div class="visible-md visible-lg">
<div class="text-right actions">
<a target="_blank" href="{{ path('view', routeParams) }}" class="cipher-link"><span class="btn btn-warning btn-xs">{{ commit|slice(0, 10) }}</span></a>
<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">
<span class="btn btn-warning btn-xs">
{{ commit|slice(0, 10) }}
</span>
</a>
{% if not gist.cipher %}
<a target="_blank" href="{{ path('raw', routeParams) }}" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-eye-open"></span>
{{ 'gist.action.raw'|trans }}
</a>
<a target="_blank" href="{{ path('download', routeParams) }}" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-save-file"></span>
{{ 'gist.action.download'|trans }}
</a>
{% endif %}
{% if not gist.cipher %}
<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>
{{ 'gist.action.raw'|trans }}
</a>
<a target="_blank" href="{{ path('download', app.request.attributes.get('_route_params')) }}" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-save-file"></span>
{{ 'gist.action.download'|trans }}
</a>
{% endif %}
<a target="_blank" href="{{ path('clone', routeParams) }}" class="btn btn-success btn-sm cipher-link">
<span class="glyphicon glyphicon-copy"></span>
{{ 'gist.action.clone'|trans }}
</a>
</div>
</div>
</div>
<a target="_blank" href="{{ path('clone', app.request.attributes.get('_route_params')) }}" class="btn btn-success btn-sm cipher-link">
<span class="glyphicon glyphicon-copy"></span>
{{ 'gist.action.clone'|trans }}
</a>
</div>
{{ gist.title ? gist.title : 'gist.untitled'|trans }}
</div>
<div class="panel-body">
<div class="tab-content">
<div id="view" class="tab-pane active in">
<div id="viewer">
<pre><code {% if gist.cipher %}data-cipher{% endif %} class="line-numbers language-{{ gist.highlightType }}">{{ raw_content }}</code></pre>
{% if gist.cipher %}
<pre class="brush: {{ gist.type }}; syntaxhighlighter">{{ raw_content|raw }}</pre>
{% else %}
{{ content|raw }}
{% endif %}
</div>
</div>
</div>
@ -123,5 +100,37 @@
{{ parent() }}
<script type="text/javascript" src="{{ web_path }}components/iframe-resizer/js/iframeResizer.contentWindow.min.js"></script>
<script src="{{ web_path }}app/js/prism.js" {% if gist.cipher %}data-manual{% endif %}></script>
{% 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 -->
<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 %}
{% endblock %}

View file

@ -1,16 +1,10 @@
{% set routeParams = app.request.attributes.get('_route_params') %}
{% if type_overrided is defined %}
{% set routeParams = routeParams|merge({type: gist.type}) %}
{% endif %}
(function() {
var iFrameResizeLoaded = function() {
var div = document.getElementById('gist-{{ gist.file }}-{{ commit }}');
var iframeId = 'gist-' + (Math.floor(Math.random() * (10000 - 1)) + 1).toString();
var iframe = document.createElement('iframe');
var url = '{{ app.request.getSchemeAndHttpHost() ~ path('embed', routeParams) }}';
var url = '{{ app.request.getSchemeAndHttpHost() ~ path('embed', app.request.attributes.get('_route_params')) }}';
if (div.getAttribute('data-key')) {
url = [url, div.getAttribute('data-key')].join('');
}

View file

@ -1,15 +1,9 @@
{% extends 'base.html.twig' %}
{% block css %}
<link rel="stylesheet" href="{{ web_path }}app/css/geshi/vibrant-ink.css" />
{{ parent() }}
<link rel="stylesheet" href="{{ web_path }}app/css/prism.css">
{% if theme_settings.name == 'dark' %}
<link rel="stylesheet" href="{{ web_path }}app/css/prism-okaidia.css">
{% else %}
<link rel="stylesheet" href="{{ web_path }}app/css/prism-solarizedlight.css">
{% endif %}
{% endblock %}
{% block title %}{{ gist.title ? gist.title : 'gist.untitled'|trans }} - {{ 'gist.action.history'|trans }}{% endblock %}
@ -29,7 +23,7 @@
<span class="badge">{{ history|length }}</span>
</a>
</li>
</ul>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
@ -46,7 +40,7 @@
</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
@ -58,14 +52,17 @@
</p>
<div>
{% if not loop.first %}
<pre><code class="diff language-diff" id="diff-{{ loop.index }}">{% if not gist.cipher %}{{ commit.diff }}{% endif %}</code></pre>
<div class="diff" id="diff-{{ loop.index }}">
{% if not gist.cipher %}
{{ commit.diff|raw }}
{% endif %}
</div>
{% endif %}
</div>
</div>
{% if not loop.last %}
<hr />
<hr />
{% endif %}
{% endfor %}
</div>
@ -79,10 +76,10 @@
{% block js %}
{{ parent() }}
<script src="{{ web_path }}app/js/prism.js" {% if gist.cipher %}data-manual{% endif %}></script>
{% if gist.cipher %}
<script src="{{ web_path }}components/diff/diff.min.js"></script>
{{ include('View/cipherJs.html.twig') }}
<script src="{{ web_path }}components/jsdiff/diff.min.js"></script>
<script>
var key = getKey();
@ -118,19 +115,21 @@
var lines = value.split("\n");
for (var i = 0, l = lines.length; i < l; i++) {
if (lines[i] != '\ No newline at end of file') {
diffContent.push(sign + lines[i]);
}
diffContent.push(sign + lines[i]);
}
}
diffContent = diffContent.join("\n");
$('#diff-' + (u + 1).toString()).text(diffContent);
var $pre = $('<pre>')
.attr('class', 'brush: diff; syntaxhighlighter')
.text(diffContent);
$('#diff-' + (u + 1).toString()).append($pre);
}
}
Prism.highlightAll();
SyntaxHighlighter.all();
</script>
{% endif %}
{% endblock %}

View file

@ -1,26 +1,19 @@
{% extends 'base.html.twig' %}
{% block css %}
{{ parent() }}
<link rel="stylesheet" href="{{ web_path }}app/css/prism.css">
{% if theme_settings.name == 'dark' %}
<link rel="stylesheet" href="{{ web_path }}app/css/prism-okaidia.css">
{% if gist.cipher %}
<link type="text/css" rel="Stylesheet" href="{{ web_path }}components/SyntaxHighlighter/styles/shCoreRDark.css" />
<link type="text/css" rel="Stylesheet" href="{{ web_path }}components/SyntaxHighlighter/styles/shThemeRDark.css" />
{% else %}
<link rel="stylesheet" href="{{ web_path }}app/css/prism-solarizedlight.css">
<link rel="stylesheet" href="{{ web_path }}app/css/geshi/vibrant-ink.css" />
{% endif %}
{{ parent() }}
{% endblock %}
{% block title %}{{ gist.title ? gist.title : 'gist.untitled'|trans }} - {{ commit|slice(0, 10) }}{% endblock %}
{% block body %}
{% set routeParams = app.request.attributes.get('_route_params') %}
{% if type_overrided is defined %}
{% set routeParams = routeParams|merge({type: gist.type}) %}
{% endif %}
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs">
@ -39,116 +32,46 @@
<div class="panel panel-default">
<div class="panel-heading">
<div class="row">
<div class="col-md-6 col-xs-9">
{{ gist.title ? gist.title : 'gist.untitled'|trans }}
</div>
<div class="col-md-6 col-xs-3">
<div class="visible-sm visible-xs pull-right">
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-menu-hamburger"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li>
<a target="_blank" href="{{ path('view', routeParams) }}" class="cipher-link">
<span class="btn btn-warning btn-xs">
{{ commit|slice(0, 10) }}
</span>
</a>
</li>
{% if not gist.cipher %}
<li>
<a href="{{ path('raw', routeParams) }}">
{{ 'gist.action.raw'|trans }}
</a>
</li>
<li>
<a href="{{ path('download', routeParams) }}">
{{ 'gist.action.download'|trans }}
</a>
</li>
{% endif %}
<li>
<a href="{{ path('clone', routeParams) }}" class="cipher-link">
{{ 'gist.action.clone'|trans }}
</a>
</li>
</ul>
</div>
</div>
<div class="visible-md visible-lg">
<div class="text-right actions">
<span class="btn btn-warning btn-xs">
{{ commit|slice(0, 10) }}
</span>
<div class="pull-right actions">
<span class="btn btn-warning btn-xs">
{{ commit|slice(0, 10) }}
</span>
{% if not gist.cipher %}
<a href="{{ path('raw', routeParams) }}" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-eye-open"></span>
{{ 'gist.action.raw'|trans }}
</a>
<a href="{{ path('download', routeParams) }}" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-save-file"></span>
{{ 'gist.action.download'|trans }}
</a>
{% endif %}
{% if not gist.cipher %}
<a href="{{ path('raw', app.request.attributes.get('_route_params')) }}" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-eye-open"></span>
{{ 'gist.action.raw'|trans }}
</a>
<a href="{{ path('download', app.request.attributes.get('_route_params')) }}" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-save-file"></span>
{{ 'gist.action.download'|trans }}
</a>
{% endif %}
<a href="{{ path('clone', routeParams) }}" class="btn btn-success btn-sm cipher-link">
<span class="glyphicon glyphicon-copy"></span>
{{ 'gist.action.clone'|trans }}
</a>
</div>
</div>
</div>
<a href="{{ path('clone', app.request.attributes.get('_route_params')) }}" class="btn btn-success btn-sm cipher-link">
<span class="glyphicon glyphicon-copy"></span>
{{ 'gist.action.clone'|trans }}
</a>
</div>
{{ gist.title ? gist.title : 'gist.untitled'|trans }}
</div>
<div class="panel-body">
<div class="tab-content">
<div id="view" class="tab-pane active in">
<div id="viewer">
<pre><code {% if gist.cipher %}data-cipher{% endif %} class="line-numbers language-{{ gist.highlightType }}">{{ raw_content }}</code></pre>
{% if gist.cipher %}
<pre class="brush: {{ gist.type }}; syntaxhighlighter">{{ raw_content|raw }}</pre>
{% else %}
{{ content|raw }}
{% endif %}
</div>
</div>
<div class="btn-toolbar">
<div class="pull-right">
<div class="btn-group">
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
{{ ('form.type.choice.' ~ gist.type)|trans }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
{% for item in types %}
{% if gist.type != item %}
<li>
{% set params = app.request.attributes.get('_route_params')|merge({type: item}) %}
<a href="{{ path('view', params) }}">
<label for="type-{{ loop.index }}">
{{ ('form.type.choice.' ~ item)|trans }}
</label>
</a>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
{{ 'gist.action.embed'|trans }}
</div>
<div class="panel-body" id="embed">
<div class="tab-content">
<div class="tab-pane active in">
<pre><code class="language-html">{{ '<div%key%id="gist-' ~ gist.file ~ '-' ~ commit ~ '" class="gist-container"></div>
<script src="' ~ app.request.getSchemeAndHttpHost() ~ path('embedjs', routeParams) ~ '" async></script>' }}</code></pre>
<p id="embed" class="pull-right">
{{ 'gist.action.embed'|trans }}
<input type="text" class="form-control" id="embed-input" value="{{ '<div%key%id="gist-' ~ gist.file ~ '-' ~ commit ~ '" class="gist-container"></div><script src="' ~ app.request.getSchemeAndHttpHost() ~ path('embedjs', app.request.attributes.get('_route_params')) ~ '" async></script>' }}" />
</p>
</div>
</div>
</div>
@ -159,6 +82,8 @@
{% block js %}
{{ parent() }}
<script src="{{ web_path }}app/js/prism.js" {% if gist.cipher %}data-manual{% endif %}></script>
{% if gist.cipher %}
{{ include('View/cipherJs.html.twig') }}
{% endif %}
{% endblock %}

View file

@ -1,28 +1,26 @@
<!DOCTYPE html>
{% set theme_settings = app.settings.theme %}
{% set security_settings = app.settings.security %}
{% set security_dettings = app.settings.security %}
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
{% block 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/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="{{ web_path }}components/flag-icon-css/css/flag-icon.min.css" />
{% if theme_settings.name == 'dark' %}
<link rel="stylesheet" href="{{ web_path }}app/css/bootstrap/bootstrap.min.css">
<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">
<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">
<link rel="stylesheet" href="{{ web_path }}app/css/themes/{{ theme_settings.name }}.css" />
{% endblock %}
{% block metas %}
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
{% endblock %}
<link rel="shortcut icon" href="{{ web_path }}favicon.png">
<title>{{ 'app.title_prefix'|trans }}{% block title %}{% endblock %}</title>
</head>
<body>
@ -58,14 +56,14 @@
{{ 'app.menu.my.logout.title'|trans }}
</a>
</li>
{% elseif security_settings.enable_login %}
{% elseif security_dettings.enable_login %}
<li>
<a href="{{ path('login') }}">
{{ 'app.menu.my.login.title'|trans }}
</a>
</li>
{% if security_settings.enable_registration %}
{% if security_dettings.enable_registration %}
<li>
<a href="{{ path('register') }}">
{{ 'app.menu.my.register.title'|trans }}
@ -81,7 +79,7 @@
</ul>
{% block langs %}
<p class="navbar-text navbar-right">
{% for locale, flag in {'en': 'gb', 'fr': 'fr', 'es': 'es', 'de': 'de', 'cn': 'cn', 'pl': 'pl'} %}
{% for locale, flag in {'en': 'gb', 'fr': 'fr', 'es': 'es', 'de': 'de'} %}
<a class="lang btn btn-xs cipher-link" href="{{ path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')|merge({_locale: locale})) }}">
<span class="flag-icon flag-icon-{{ flag }}"></span>
</a>

View file

@ -32,18 +32,25 @@ class Gist
*/
protected $gitWorkingCopy;
/**
* @var GeSHi
*/
protected $geshi;
/**
* __construct.
*
* @param mixed $gistPath
* @param GitWrapper $gitWrapper
* @param GitWorkingCopy $gitWorkingCopy
* @param GeSHi $geshi
*/
public function __construct($gistPath, GitWrapper $gitWrapper, GitWorkingCopy $gitWorkingCopy)
public function __construct($gistPath, GitWrapper $gitWrapper, GitWorkingCopy $gitWorkingCopy, GeSHi $geshi)
{
$this->gistPath = $gistPath;
$this->gitWrapper = $gitWrapper;
$this->gitWorkingCopy = $gitWorkingCopy;
$this->geshi = $geshi;
}
/**
@ -88,7 +95,7 @@ class Gist
$data = array(
'commit' => trim($commits[$i][1]),
'date' => new \DateTime(trim($dates[$i][1])),
'diff' => str_replace('\ No newline at end of file', '', $diff),
'diff' => $this->highlight('diff', $diff),
);
if ($gist->isCipher()) {
@ -186,4 +193,20 @@ class Gist
return count(explode("\n", $content));
}
/**
* Highlight the content.
*
* @param string $type
* @param string $content
*
* @return string
*/
public function highlight($type, $content)
{
$this->geshi->set_source($content);
$this->geshi->set_language($type);
return $this->geshi->parse_code();
}
}

View file

@ -18,30 +18,18 @@ class SaltGenerator
*
* @return string
*/
public function generate($length = 32, $isApiKey = false)
public function generate($length = 32)
{
if (!is_numeric($length)) {
throw new InvalidArgumentException('Paramter length must be a valid integer.');
}
if (function_exists('openssl_random_pseudo_bytes')) {
$string = base64_encode(openssl_random_pseudo_bytes(256));
return substr(base64_encode(openssl_random_pseudo_bytes($length)), 0, $length);
}
if (function_exists('mcrypt_create_iv')) {
$string = base64_encode(mcrypt_create_iv(256, MCRYPT_DEV_URANDOM));
}
if (!empty($string)) {
if (true === $isApiKey) {
$string = str_replace(
array('+', '%', '/', '#', '&'),
'',
$string
);
}
return substr($string, 0, $length);
return substr(base64_encode(mcrypt_create_iv($length, MCRYPT_DEV_URANDOM)), 0, $length);
}
throw new RuntimeException('You must enable openssl or mcrypt modules.');

View file

@ -126,7 +126,6 @@ class UserProvider implements UserProviderInterface
$user
->setRoles('ROLE_USER')
->setPassword($this->encoder->encodePassword($password, $user->getSalt()))
->setApiKey($this->saltGenerator->generate(32, true))
->save();
return $user;
@ -167,20 +166,6 @@ class UserProvider implements UserProviderInterface
return $user;
}
/**
* Loads a user by his api key.
*
* @param string $apiKey
*
* @return User
*/
public function loadUserByApiKey($apiKey)
{
$user = UserQuery::create()->findOneByApiKey($apiKey);
return $user;
}
/*
* Checks if the given password is the current user password.
*

146
web/app/css/geshi/dawn.css Normal file
View file

@ -0,0 +1,146 @@
/* @override http://localhost/mark_story2/site/css/geshi.css */
/**
* GeSHi CSS Inspired by
* TextMate Theme Dawn
*
* Copyright 2008 Mark Story
*
* This work is licensed under the Creative Commons Attribution-Share Alike 2.5 Canada License.
* To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/2.5/ca/
* or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
*
* @copyright Copyright 2008, Mark Story.
* @link http://mark-story.com
* @license http://creativecommons.org/licenses/by-sa/2.5/ca/
*/
/*
* Global geshi styles
**********************/
#main pre {
line-height: 1.5em;
font-size: 10px;
white-space: normal;
padding: 0;
background: #E8EDF4;
border: 1px solid #222;
}
pre ol {
list-style: decimal;
list-style-position: outside;
padding: 0;
margin: 0;
}
#main pre ol li {
margin: 0 0 0 35px;
padding: 0;
color: #333;
clear: none;
}
pre ol li div {
color:#000;
}
/* Line highlights */
.li1 {
background: #E4E8EF;
}
/* comments */
.co1,
.coMULTI {
color:#5A526E;
}
/* methods */
.me1{
color:#000;
}
.me0 {
}
.me2 {
color:#000;
}
/* brackets */
.br0 {
color:#000;
}
/* strings */
.st0 {
color:#0B6125;
}
/* keywords */
.kw1 {
color: #794938;
}
.kw2 {
color:#A71D5D;
font-style: italic;
}
.kw3 {
color:#693A17;
}
/* numbers */
.nu0 {
color:#811F24;
}
/* vars */
.re0 {
color:#434A97;
}
/*
* CSS selectors
*****************/
/* classnames */
[lang=css] .kw2,
.css .kw2 {
color:#C24F24;
}
[lang=css] .kw1,
.css .kw1 {
color:#691C97;
}
[lang=css] .re0,
.css .re0 {
color: #C24F24;
}
.re1 {
color: #C24F24;
}
/* px values */
[lang=css] .re3,
.css .re3 {
color:#84252A;
}
/*
* Python
****************/
[lang=python] ol li div,
.python ol li div {
color: #000;
}
[lang=python] .kw2,
.python .kw2 {
font-style: normal;
}
[lang=python] .kw1 {
color: #A91D5D;
}
/*
* Javascript
****************/
[lang=javascript] .me1,
.javascript .me1 {
color: #794938;
}

View file

@ -0,0 +1,131 @@
/**
* GeSHi CSS Inspired by TextMate
* Theme Mac Classic
*
* Copyright 2008 Mark Story
*
* This work is licensed under the Creative Commons Attribution-Share Alike 2.5 Canada License.
* To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/2.5/ca/
* or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
*
* @copyright Copyright 2008, Mark Story.
* @link http://mark-story.com
* @license http://creativecommons.org/licenses/by-sa/2.5/ca/
*/
/*
* Global geshi styles
**********************/
#main pre {
line-height: 1.5em;
font-size: 10px;
white-space: normal;
padding: 0;
background: #eae9e5;
border: 1px solid #c0c0c0;
}
pre ol {
list-style: decimal;
list-style-position: outside;
padding: 0;
margin: 0;
}
#main pre ol li {
margin: 0 0 0 35px;
padding: 0;
color: #000;
clear: none;
}
pre ol li div {
color:#000;
}
/* php */
pre[lang=php] ol div,
pre.php ol div {
color:#fd1435;
}
/* Line highlights */
.li1 {
background: #f2f2f2;
}
/* Line highlights */
.li1 {
background: #f2f2f2;
}
/* comments */
.co1,
.coMULTI {
color:#5c9dff;
}
/* methods */
.me1{
color:#000;
}
.me0 {
}
.me2 {
color:#000;
}
/* brackets */
.br0 {
color:#222;
}
/* strings */
.st0 {
color:#b701bb;
}
/* keywords */
.kw1 {
color: #fd1435;
}
.kw2 {
color:#6266f5;
}
.kw3 {
color:#505e80;
}
/* numbers */
.nu0 {
color:#0a0aa5;
}
/* vars */
.re0 {
color:#059532;
}
/*
* CSS selectors
*****************/
/* classnames */
[lang=css] .kw2,
.css .kw2 {
color:#0a0aa5;
}
.re1 {
color: #b701bb;
}
/* px values */
[lang=css] .re3,
.css .re3 {
color:#059532;
}
/*
* Javascript
****************/
[lang=javascript] .me1,
.javascript .me1 {
color:#505e80;
}

View file

@ -0,0 +1,123 @@
/* @override http://localhost/mark_story2/site/css/geshi.css */
/**
* GeSHi CSS Inspired by TextMate
* Theme Twilight
*
* Copyright 2008 Mark Story
*
* This work is licensed under the Creative Commons Attribution-Share Alike 2.5 Canada License.
* To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/2.5/ca/
* or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
*
* @filesource
* @copyright Copyright 2008, Mark Story.
* @link http://mark-story.com
* @license http://creativecommons.org/licenses/by-sa/2.5/ca/
*/
/*
* Global geshi styles
**********************/
#main pre {
line-height: 1.5em;
font-size: 10px;
white-space: normal;
padding: 0;
background: #222223;
border: 1px solid #222;
}
pre ol {
list-style: decimal;
list-style-position: outside;
padding: 0;
margin: 0;
}
#main pre ol li {
margin: 0 0 0 35px;
padding: 0;
color: #000;
clear: none;
}
pre ol li div {
color:#f8f8f8;
}
/* Line highlights */
.li1 {
background: #202021;
}
/* comments */
.co1,
.coMULTI {
color:#5F5A60;
}
/* methods */
.me1{
color:#fff;
}
.me0 {
}
.me2 {
color:#000;
}
/* brackets */
.br0 {
color:#fff;
}
/* strings */
.st0 {
color:#8F9657;
}
/* keywords */
.kw1 {
color: #CDA869;
}
.kw2 {
color:#F9EE98;
}
.kw3 {
color:#505e80;
}
/* numbers */
.nu0 {
color:#CF6745;
}
/* vars */
.re0 {
color:#7587A6;
}
/*
* CSS selectors
*****************/
/* classnames */
[lang=css] .kw2,
.css .kw2 {
color:#F9EE7E;
}
.re1 {
color: #96703D;
}
/* px values */
[lang=css] .re3,
.css .re3 {
color:#CA7840;
}
/*
* Javascript
****************/
[lang=javascript] .me1,
.javascript .me1 {
color:#505e80;
}

View file

@ -0,0 +1,148 @@
/* @override http://localhost/mark_story2/site/css/geshi.css */
/**
* GeSHi CSS Inspired by TextMate
* Theme Vibrant Ink
*
* Copyright 2008 Mark Story
*
* This work is licensed under the Creative Commons Attribution-Share Alike 2.5 Canada License.
* To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/2.5/ca/
* or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
*
* @copyright Copyright 2008, Mark Story.
* @link http://mark-story.com
* @license http://creativecommons.org/licenses/by-sa/2.5/ca/
*/
/*
* Global geshi styles
**********************/
#main pre {
line-height: 1.5em;
font-size: 10px;
white-space: normal;
padding: 0;
background: #000;
border: 1px solid #222;
}
pre ol {
list-style: decimal;
list-style-position: outside;
padding: 0;
margin: 0;
}
#main pre ol li {
margin: 0 0 0 35px;
padding: 0;
color: #fff;
clear: none;
}
pre ol li div {
/*color: #F26100;*/
color: #D2C0B4;
}
/* Line highlights */
.li1 {
background: #030303;
}
/* comments */
.co1,
.coMULTI {
color:#7830CC;
}
/* methods */
.me1{
color:#fff;
}
.me0 {
}
.me2 {
color:#000;
}
/* brackets */
.br0 {
color:#fff;
}
/* strings */
.st0 {
color:#52F700;
}
/* keywords */
.kw1 {
color: #C6C765;
}
.kw2 {
color:#FFC500;
}
.kw3 {
color:#505e80;
}
/* numbers */
.nu0 {
color:#319994;
}
/* vars */
.re0 {
color:#fff;
}
/*
* CSS selectors
*****************/
/* classnames */
[lang=css] .kw2,
.css .kw2 {
color:#fff;
}
[lang=css] .kw1,
.css .kw1 {
color:#999966;
}
[lang=css] .re0,
.css .re0 {
color: #F26100;
}
.re1 {
color: #96703D;
}
/* px values */
[lang=css] .re3,
.css .re3 {
color:#fff;
}
/*
* Python
****************/
[lang=python] ol li div,
.python ol li div {
color: #fff;
}
[lang=python] .kw1,
.python .kw1 {
color:#F26100;
}
/*
* Javascript
****************/
[lang=javascript] .me1,
.javascript .me1 {
color:#fff;
}
pre[lang=javascript] ol li div,
.javascript ol li div {
color: #fff;
}

View file

@ -1,121 +0,0 @@
/**
* okaidia theme for JavaScript, CSS and HTML
* Loosely based on Monokai textmate theme by http://www.monokai.nl/
* @author ocodia
*/
code[class*="language-"],
pre[class*="language-"] {
color: #f8f8f2;
background: none;
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
border-radius: 0.3em;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #272822;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #f8f8f2;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.constant,
.token.symbol,
.token.deleted {
color: #f92672;
}
.token.boolean,
.token.number {
color: #ae81ff;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #a6e22e;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
color: #f8f8f2;
}
.token.atrule,
.token.attr-value,
.token.function {
color: #e6db74;
}
.token.keyword {
color: #66d9ef;
}
.token.regex,
.token.important {
color: #fd971f;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View file

@ -1,148 +0,0 @@
/*
Solarized Color Schemes originally by Ethan Schoonover
http://ethanschoonover.com/solarized
Ported for PrismJS by Hector Matos
Website: https://krakendev.io
Twitter Handle: https://twitter.com/allonsykraken)
*/
/*
SOLARIZED HEX
--------- -------
base03 #002b36
base02 #073642
base01 #586e75
base00 #657b83
base0 #839496
base1 #93a1a1
base2 #eee8d5
base3 #fdf6e3
yellow #b58900
orange #cb4b16
red #dc322f
magenta #d33682
violet #6c71c4
blue #268bd2
cyan #2aa198
green #859900
*/
code[class*="language-"],
pre[class*="language-"] {
color: #657b83; /* base00 */
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
background: #073642; /* base02 */
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
background: #073642; /* base02 */
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
border-radius: 0.3em;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background-color: #fdf6e3; /* base3 */
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #93a1a1; /* base1 */
}
.token.punctuation {
color: #586e75; /* base01 */
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #268bd2; /* blue */
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.url,
.token.inserted {
color: #2aa198; /* cyan */
}
.token.entity {
color: #657b83; /* base00 */
background: #eee8d5; /* base2 */
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #859900; /* green */
}
.token.function {
color: #b58900; /* yellow */
}
.token.regex,
.token.important,
.token.variable {
color: #cb4b16; /* orange */
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View file

@ -1,233 +0,0 @@
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+actionscript+c+csharp+bash+cpp+aspnet+css-extras+diff+markup-templating+markdown+perl+php+php-extras+sql+python+yaml&plugins=line-highlight+line-numbers */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
pre[data-line] {
position: relative;
padding: 1em 0 1em 3em;
}
.line-highlight {
position: absolute;
left: 0;
right: 0;
padding: inherit 0;
margin-top: 1em; /* Same as .prisms padding-top */
background: hsla(24, 20%, 50%,.08);
background: linear-gradient(to right, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
pointer-events: none;
line-height: inherit;
white-space: pre;
}
.line-highlight:before,
.line-highlight[data-end]:after {
content: attr(data-start);
position: absolute;
top: .4em;
left: .6em;
min-width: 1em;
padding: 0 .5em;
background-color: hsla(24, 20%, 50%,.4);
color: hsl(24, 20%, 95%);
font: bold 65%/1.5 sans-serif;
text-align: center;
vertical-align: .3em;
border-radius: 999px;
text-shadow: none;
box-shadow: 0 1px white;
}
.line-highlight[data-end]:after {
content: attr(data-end);
top: auto;
bottom: .4em;
}
.line-numbers .line-highlight:before,
.line-numbers .line-highlight:after {
content: none;
}
pre[class*="language-"].line-numbers {
position: relative;
padding-left: 3.8em;
counter-reset: linenumber;
}
pre[class*="language-"].line-numbers > code {
position: relative;
white-space: inherit;
}
.line-numbers .line-numbers-rows {
position: absolute;
pointer-events: none;
top: 0;
font-size: 100%;
left: -3.8em;
width: 3em; /* works for line-numbers below 1000 lines */
letter-spacing: -1px;
border-right: 1px solid #999;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.line-numbers-rows > span {
pointer-events: none;
display: block;
counter-increment: linenumber;
}
.line-numbers-rows > span:before {
content: counter(linenumber);
color: #999;
display: block;
padding-right: 0.8em;
text-align: right;
}

View file

@ -26,24 +26,54 @@ body {
margin-right: 4px;
}
#viewer pre, #viewer code, #revisions pre, #revisions code, #embed pre, #embed code {
background: none !important;
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;
border: 0;
}
pre ol {
padding-left: 50px !important;
}
pre li:hover {
background: #444;
}
.panel-heading .actions {
margin-top: -5px;
}
.diff {
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;
}
@ -56,11 +86,3 @@ body {
.btn-error:active, .btn-error:hover, .btn-error:focus {
color: #000;
}
pre.line-numbers {
padding-left: 50px !important;
}
.line-numbers-rows {
top: -4px !important;
}

View file

@ -16,24 +16,54 @@
margin-right: 4px;
}
pre, code {
background: none !important;
white-space: pre-wrap !important;
white-space: -moz-pre-wrap !important;
white-space: -pre-wrap !important;
white-space: -o-pre-wrap !important;
word-wrap: break-word !important;
border: 0;
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;
}
.diff {
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;
}
@ -46,11 +76,3 @@ pre, code {
.btn-error:active, .btn-error:hover, .btn-error:focus {
color: #000;
}
pre.line-numbers {
padding-left: 50px !important;
}
.line-numbers-rows {
top: -4px !important;
}

View file

@ -98,10 +98,6 @@ var myEvents = function() {
$('#form-deletion form').submit();
}
});
$(document).on('change keyup keydown', '#form-api-key', function() {
$(this).val($(this).data('key'));
});
}
var mainEditorEvents = function() {
@ -138,13 +134,13 @@ var getKey = function() {
}
var viewerEvents = function() {
var $render = $('#viewer code[data-cipher]');
var $render = $('.syntaxhighlighter');
$(document).ready(function() {
var key = getKey();
var $cipherEditor = $('.cipher-editor');
var $embedCode = $('#embed code');
var $embedInput = $('#embed-input');
var to = ' ';
@ -163,8 +159,7 @@ var viewerEvents = function() {
});
$render.text(decrypted.toString(CryptoJS.enc.Utf8));
$render.attr('class', $render.data('class'));
Prism.highlightAll();
SyntaxHighlighter.all();
to = ' data-key="#key=' + key + '" ';
} else {
@ -177,9 +172,8 @@ var viewerEvents = function() {
}
}
if ($embedCode.length) {
$embedCode.html($embedCode.html().replace('%key%', to));
Prism.highlightAll();
if ($embedInput.length) {
$embedInput.val($embedInput.val().replace('%key%', to));
}
});
}

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
../../node_modules/bootstrap

View file

@ -1 +0,0 @@
../../node_modules/diff

View file

@ -1 +0,0 @@
../../node_modules/flag-icon-css

View file

@ -1 +0,0 @@
../../node_modules/iframe-resizer

View file

@ -1 +0,0 @@
../../node_modules/jquery

Binary file not shown.

Before

Width:  |  Height:  |  Size: 570 B

View file

@ -4,4 +4,4 @@ $app = require __DIR__.'/../app/bootstrap.php';
$app['env'] = 'prod';
$app['http_cache']->run();
$app->run();