├── .gitignore ├── composer.phar ├── web ├── robots.txt ├── favicon.ico ├── js │ ├── ZeroClipboard.swf │ ├── npm.js │ ├── js.cookie.min.js │ ├── script.min.js │ ├── js.cookie.js │ ├── script.js │ ├── jquery.countdown.min.js │ ├── jquery.zclip.min.js │ ├── hideShowPassword.min.js │ └── bootstrap.min.js ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── .htaccess ├── index.php └── css │ ├── style.min.css │ ├── update.sh │ ├── style.css │ ├── bootstrap-theme.min.css │ └── bootstrap-theme.css ├── log └── .gitignore ├── vendor ├── autoload.php └── .gitignore ├── .openshift └── action_hooks │ └── deploy ├── src └── App │ ├── views │ ├── jslocale.twig │ ├── view_link.twig │ ├── view_password.twig │ ├── layout.twig │ └── index.twig │ ├── Silex │ └── Application.php │ ├── Factory │ └── CredentialsFactory.php │ ├── Entity │ ├── CredentialsRepository.php │ └── Credentials.php │ ├── Tests │ ├── Factory │ │ └── CredentialFactoryTest.php │ ├── Service │ │ ├── I18nServiceTest.php │ │ └── CredentialsServiceTest.php │ └── Controller │ │ └── DefaultControllerTest.php │ ├── Model │ └── CryptAES.php │ ├── Service │ ├── AbstractCryptService.php │ ├── I18nService.php │ └── CredentialsService.php │ └── Controller │ └── DefaultController.php ├── app ├── config.php.travisci ├── config.php.openshift ├── routing.php ├── locales │ ├── de.yml │ ├── eo.yml │ ├── en.yml │ └── es.yml ├── config.php.sample ├── console └── app.php ├── .travis.yml ├── composer.json ├── phpunit.xml ├── LICENSE ├── RoboFile.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | cache 2 | app/config.php 3 | -------------------------------------------------------------------------------- /composer.phar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichaelThessel/pwx/HEAD/composer.phar -------------------------------------------------------------------------------- /web/robots.txt: -------------------------------------------------------------------------------- 1 | # Allow crawling of all content 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichaelThessel/pwx/HEAD/web/favicon.ico -------------------------------------------------------------------------------- /log/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /web/js/ZeroClipboard.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichaelThessel/pwx/HEAD/web/js/ZeroClipboard.swf -------------------------------------------------------------------------------- /web/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichaelThessel/pwx/HEAD/web/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /web/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichaelThessel/pwx/HEAD/web/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /web/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichaelThessel/pwx/HEAD/web/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /web/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichaelThessel/pwx/HEAD/web/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /web/.htaccess: -------------------------------------------------------------------------------- 1 | Options +FollowSymLinks 2 | Options -MultiViews 3 | 4 | RewriteEngine On 5 | RewriteCond %{REQUEST_FILENAME} !-f 6 | RewriteRule ^ index.php [L] 7 | -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | run(); 6 | } else { 7 | echo 'Failed to initialize application.'; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /web/css/style.min.css: -------------------------------------------------------------------------------- 1 | html{position:relative;min-height:100%}body{margin-bottom:2em}footer{position:absolute;bottom:0;width:100%}h1{font-size:2.5em;margin-bottom:1em}a,a:hover,a:visited{text-decoration:none}.panel-body button{float:right}.insert-password{margin-top:0.5em;font-size:0.8em} -------------------------------------------------------------------------------- /.openshift/action_hooks/deploy: -------------------------------------------------------------------------------- 1 | cd $OPENSHIFT_REPO_DIR 2 | 3 | echo "Creating configuration file" 4 | cp app/config.php.openshift app/config.php 5 | 6 | echo "Installing dependencies" 7 | php composer.phar install 8 | 9 | echo "Updating database" 10 | app/console orm:schema-tool:update --force 11 | -------------------------------------------------------------------------------- /vendor/.gitignore: -------------------------------------------------------------------------------- 1 | aptoma 2 | bin 3 | codegyre 4 | composer 5 | dflydev 6 | doctrine 7 | henrikbjorn 8 | ircmaxell 9 | monolog 10 | natxet 11 | paragonie 12 | patchwork 13 | phpdocumentor 14 | phpseclib 15 | phpspec 16 | phpunit 17 | pimple 18 | psr 19 | sebastian 20 | silex 21 | symfony 22 | twig 23 | webmozart 24 | -------------------------------------------------------------------------------- /src/App/views/jslocale.twig: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /app/config.php.travisci: -------------------------------------------------------------------------------- 1 | "bootstrap.${theme}.min.css" 11 | 12 | # Wait 2s between updates to prevent request limiting 13 | sleep 2 14 | done 15 | -------------------------------------------------------------------------------- /app/config.php.openshift: -------------------------------------------------------------------------------- 1 | registerTwig($this); 18 | $this->registerLogger($this); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /web/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /app/routing.php: -------------------------------------------------------------------------------- 1 | get('/', 'app.default_controller:indexAction'); 4 | $app->post('/', 'app.default_controller:indexPostAction'); 5 | $app->get('/link/{hash}', 'app.default_controller:viewLinkAction'); 6 | $app->get('/pw/{hash}', 'app.default_controller:viewPasswordAction'); 7 | $app->post('/delete', 'app.default_controller:deleteAction'); 8 | 9 | // Api 10 | $app->post('/api', 'app.default_controller:apiPostAction'); 11 | $app->get('/api/{hash}', 'app.default_controller:apiViewAction'); 12 | $app->delete('/api/{hash}', 'app.default_controller:apiDeleteAction'); 13 | -------------------------------------------------------------------------------- /web/css/style.css: -------------------------------------------------------------------------------- 1 | /* Page */ 2 | html { 3 | position: relative; 4 | min-height: 100%; 5 | } 6 | 7 | body { 8 | margin-bottom: 2em; 9 | } 10 | 11 | footer { 12 | position: absolute; 13 | bottom: 0; 14 | width: 100%; 15 | } 16 | 17 | h1 { 18 | font-size: 2.5em; 19 | margin-bottom: 1em; 20 | } 21 | 22 | 23 | a, a:hover, a:visited { 24 | text-decoration: none; 25 | } 26 | 27 | /* Share link */ 28 | .panel-body button { 29 | float: right; 30 | } 31 | 32 | /* Link creation form */ 33 | .insert-password { 34 | margin-top: 0.5em; 35 | font-size: 0.8em; 36 | } 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 7.0 7 | - hhvm 8 | 9 | before_install: 10 | # Install composer dependencies 11 | - composer self-update 12 | - composer update 13 | 14 | # Add the TravisCI config file 15 | - cp app/config.php.travisci app/config.php 16 | 17 | # Set up the database 18 | - mysql -e 'CREATE DATABASE IF NOT EXISTS `pwx`;' -u root 19 | - mysql -e 'GRANT ALL ON `pwx`.* TO `pwx`@`localhost` IDENTIFIED BY "pwx";' -u root 20 | - app/console orm:schema-tool:create 21 | 22 | script: 23 | # Run phpunit 24 | - vendor/bin/phpunit 25 | -------------------------------------------------------------------------------- /src/App/Factory/CredentialsFactory.php: -------------------------------------------------------------------------------- 1 | createKey(); 20 | $credentials->setHash(substr(md5($key['privatekey']), 0, 10)); 21 | 22 | return $credentials; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/App/Entity/CredentialsRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('c'); 20 | $qb ->delete() 21 | ->where($qb->expr()->lt('c.expires', '?1')) 22 | ->setParameter(1, time()) 23 | ->getQuery() 24 | ->execute() 25 | ; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/App/views/view_link.twig: -------------------------------------------------------------------------------- 1 | {% extends 'layout.twig' %} 2 | {% block title %}{{ 'app.name'|trans }} - {{ 'view_link.view'|trans }}{% endblock %} 3 | {% set url = (app.request.scheme ~ '://' ~ app.request.host ~ app.request.baseUrl ~ '/pw/' ~ hash) %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |
{{ 'view_link.share'|trans }}
10 |
11 | {{ url }} 12 |
13 |
14 |
15 |
16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "michaelthessel/pwx", 3 | "description": "Online password exchange service", 4 | "license": "MIT", 5 | "require": { 6 | "silex/silex": "~1.2", 7 | "aptoma/silex-extras": "~1.2", 8 | "symfony/config": "~2.7", 9 | "symfony/symfony": "~2.7", 10 | "symfony/twig-bridge": "~2.3", 11 | "symfony/translation": "~2.7", 12 | "symfony/yaml": "~2.7", 13 | "phpunit/phpunit": "4.8.*", 14 | "dflydev/doctrine-orm-service-provider": "1.0.*", 15 | "codegyre/robo": "^0.5.4", 16 | "patchwork/jsqueeze": "^2.0", 17 | "natxet/CssMin": "^3.0", 18 | "phpseclib/phpseclib": "^2.0" 19 | }, 20 | "autoload": { 21 | "psr-0": { 22 | "App": "src/" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | src/*/Tests 12 | 13 | 14 | 15 | 16 | 17 | src 18 | 19 | src/*Bundle/Resources 20 | src/*Bundle/Tests 21 | src/*/*Bundle/Resources 22 | src/*/*Bundle/Tests 23 | src/*/Bundle/*Bundle/Resources 24 | src/*/Bundle/*Bundle/Tests 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Michael Thessel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /app/locales/de.yml: -------------------------------------------------------------------------------- 1 | app.poweredby: Powered by 2 | app.name: Passwort Austausch 3 | 4 | app.meta.description: Internet Passwort Austausch Dienst 5 | app.meta.keywords: Passwort, Austausch, Login, Zugangsdaten, Übermittlung, Email 6 | 7 | app.data.username: Nutzername 8 | app.data.password: Passwort 9 | app.data.comment: Kommentar 10 | app.data.expires: Gültig für 11 | app.data.onetimeview: Nur einmal sichtbar 12 | app.data.seconds: Sekunden 13 | app.data.minutes: Minuten 14 | app.data.hour: Stunde 15 | app.data.hours: Stunden 16 | app.data.day: Tag 17 | app.data.days: Tage 18 | app.data.week: Woche 19 | app.data.month: Monat 20 | 21 | app.language: Sprache 22 | 23 | index.generate: Generieren 24 | index.insert_password_start: Zufälliges 25 | index.insert_password_end: Zeichen langes Passwort einfügen 26 | index.show_password: Passwort anzeigen 27 | index.hide_password: Passwort nicht anzeigen 28 | index.generate_link: Link generieren 29 | 30 | view_link.view: Link Anzeigen 31 | view_link.share: Diesen Link versenden 32 | 33 | view_password.view: Passwort Anzeigen 34 | view_password.expires: Dieser Eintrag erlischt in 35 | view_password.delete: Löschen 36 | view_password.expired: Dieser Eintrag ist erloschen! 37 | -------------------------------------------------------------------------------- /app/locales/eo.yml: -------------------------------------------------------------------------------- 1 | app.poweredby: Funkcianta per 2 | app.name: Pasvort-interŝanĝo 3 | 4 | app.meta.description: Interreta pasvort-interŝanĝa servo 5 | app.meta.keywords: Pasvorto, Interŝanĝo, Interrete, Akreditilo, Transdoni, Retpoŝto 6 | 7 | app.data.username: Salutnomo 8 | app.data.password: Pasvorto 9 | app.data.comment: Noto 10 | app.data.expires: Valida por 11 | app.data.onetimeview: Forigi la rekordon la unuan fojon ĝi estas montrata 12 | app.data.seconds: sekundoj 13 | app.data.minutes: minutoj 14 | app.data.hour: horo 15 | app.data.hours: horoj 16 | app.data.day: tago 17 | app.data.days: tagoj 18 | app.data.week: semajno 19 | app.data.month: monato 20 | 21 | app.language: Lingvo 22 | 23 | index.generate: Krei 24 | index.insert_password_start: Krei pasvorton el 25 | index.insert_password_end: hazardaj signoj 26 | index.show_password: Montri pasvorton 27 | index.hide_password: Kaŝi pasvorton 28 | index.generate_link: Krei ligilon 29 | 30 | view_link.view: Vidi ligilon 31 | view_link.share: Kundividi ĉi tiun ligilon 32 | 33 | view_password.view: Vidi pasvorton 34 | view_password.expires: Ĉi tiu rikordo malaperos post 35 | view_password.delete: Malaperigi 36 | view_password.expired: La petita rikordo eksvalidiĝis! 37 | -------------------------------------------------------------------------------- /src/App/Tests/Factory/CredentialFactoryTest.php: -------------------------------------------------------------------------------- 1 | app = require __DIR__ . '/../../../../app/app.php'; 18 | $this->credentialsFactory = $this->app['credentials_factory']; 19 | } 20 | 21 | /** 22 | * Test retrieval of instance 23 | * 24 | * @return void 25 | */ 26 | public function testInstance() 27 | { 28 | $instance = $this->credentialsFactory->getInstance(); 29 | 30 | $this->assertInstanceOf('App\Entity\Credentials', $instance); 31 | } 32 | 33 | /** 34 | * Test if hash is initialized 35 | * 36 | * @return void 37 | */ 38 | public function testHashIsInitialized() 39 | { 40 | $instance = $this->credentialsFactory->getInstance(); 41 | 42 | $this->assertNotEmpty($instance->getHash()); 43 | $this->assertSame(10, strlen($instance->getHash())); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/config.php.sample: -------------------------------------------------------------------------------- 1 | config['secret']) || $this->config['secret']) { 22 | $this->cipher = new AES(AES::MODE_ECB); 23 | $this->cipher->setKey($this->config['secret']); 24 | return true; 25 | } 26 | 27 | return false; 28 | } 29 | 30 | /** 31 | * Decrypt value 32 | * 33 | * @param string $crypt Value to decrypt 34 | * @return string Decrypted value 35 | */ 36 | public function decrypt($crypt) 37 | { 38 | if (!$this->cipher && !$this->setCipher()) return $crypt; 39 | 40 | return $this->cipher->decrypt($crypt); 41 | } 42 | 43 | /** 44 | * Encrypt value 45 | * 46 | * @param string $crypt Value to encrypt 47 | * @return string Encrypted value 48 | */ 49 | public function encrypt($crypt) 50 | { 51 | if (!$this->cipher && !$this->setCipher()) return $crypt; 52 | 53 | return $this->cipher->encrypt($crypt); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /RoboFile.php: -------------------------------------------------------------------------------- 1 | taskMinify('web/js/script.js') 16 | ->run(); 17 | 18 | // Minify CSS 19 | $this->taskMinify('web/css/style.css') 20 | ->run(); 21 | 22 | // Concat JS 23 | $this->taskConcat(array( 24 | 'web/js/jquery.min.js', 25 | 'web/js/bootstrap.min.js', 26 | 'web/js/jquery.countdown.min.js', 27 | 'web/js/jquery.zclip.min.js', 28 | 'web/js/js.cookie.min.js', 29 | 'web/js/hideShowPassword.min.js', 30 | 'web/js/script.min.js', 31 | )) 32 | ->to('web/js/scripts.min.js') 33 | ->run(); 34 | } 35 | 36 | /** 37 | * Watch CSS and JS assets for changes and compile minfied assets on change 38 | */ 39 | public function watch() 40 | { 41 | $this->taskWatch() 42 | ->monitor( 43 | array('web/js/script.js', 'web/css/style.css'), 44 | function() { 45 | $this->build(); 46 | } 47 | ) 48 | ->run(); 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /web/js/js.cookie.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JavaScript Cookie v2.0.3 3 | * https://github.com/js-cookie/js-cookie 4 | * 5 | * Copyright 2006, 2015 Klaus Hartl & Fagner Brack 6 | * Released under the MIT license 7 | */ 8 | (function(a){if(typeof define==="function"&&define.amd){define(a)}else{if(typeof exports==="object"){module.exports=a()}else{var c=window.Cookies;var b=window.Cookies=a();b.noConflict=function(){window.Cookies=c;return b}}}}(function(){function b(){var f=0;var c={};for(;f1){k=b({path:"/"},c.defaults,k);if(typeof k.expires==="number"){var h=new Date();h.setMilliseconds(h.getMilliseconds()+k.expires*86400000);k.expires=h}try{r=JSON.stringify(n);if(/^[\{\[]/.test(r)){n=r}}catch(m){}n=encodeURIComponent(String(n));n=n.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent);o=encodeURIComponent(String(o));o=o.replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent);o=o.replace(/[\(\)]/g,escape);return(document.cookie=[o,"=",n,k.expires&&"; expires="+k.expires.toUTCString(),k.path&&"; path="+k.path,k.domain&&"; domain="+k.domain,k.secure?"; secure":""].join(""))}if(!o){r={}}var q=document.cookie?document.cookie.split("; "):[];var p=/(%[0-9A-Z]{2})+/g;var l=0;for(;laddCommands(array( 12 | new \Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand, 13 | new \Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand, 14 | new \Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand, 15 | new \Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand, 16 | new \Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand, 17 | new \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand, 18 | new \Doctrine\ORM\Tools\Console\Command\ConvertDoctrine1SchemaCommand, 19 | new \Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand, 20 | new \Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand, 21 | new \Doctrine\ORM\Tools\Console\Command\GenerateEntitiesCommand, 22 | new \Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand, 23 | new \Doctrine\ORM\Tools\Console\Command\GenerateRepositoriesCommand, 24 | new \Doctrine\ORM\Tools\Console\Command\InfoCommand, 25 | new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand, 26 | new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand, 27 | new \Doctrine\DBAL\Tools\Console\Command\ImportCommand, 28 | new \Doctrine\DBAL\Tools\Console\Command\ReservedWordsCommand, 29 | new \Doctrine\DBAL\Tools\Console\Command\RunSqlCommand 30 | )); 31 | 32 | /* 33 | * Doctrine CLI 34 | */ 35 | $console->setHelperSet(new Symfony\Component\Console\Helper\HelperSet(array( 36 | 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($app["db"]), 37 | 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($app["orm.em"]) 38 | ))); 39 | 40 | 41 | Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($console); 42 | 43 | $console->run(); -------------------------------------------------------------------------------- /src/App/Service/AbstractCryptService.php: -------------------------------------------------------------------------------- 1 | isEncrypted()) && $item->isEncrypted()) return; 29 | 30 | $this->cryptProperties('encrypt', $item); 31 | $item->setEncrypted(true); 32 | } 33 | 34 | /** 35 | * Decrypt properties that have been flagged in $cryptedProperties 36 | * 37 | * @param mixed $item Item to decrypt 38 | * @return void 39 | */ 40 | public function decryptProperties($item) 41 | { 42 | if (!is_null($item->isEncrypted()) && !$item->isEncrypted()) return; 43 | 44 | $this->cryptProperties('decrypt', $item); 45 | $item->setEncrypted(false); 46 | } 47 | 48 | /** 49 | * En/DecryptProperties 50 | * 51 | * @param mixed $mode encrypt/decrypt 52 | * @param mixed $item Item to encrypt/decrypt 53 | * @return void 54 | */ 55 | protected function cryptProperties($mode, $item) 56 | { 57 | if (!in_array($mode, array('encrypt', 'decrypt'))) { 58 | throw new \InvalidArgumentException('Invalid mode specified'); 59 | } 60 | 61 | if (!isset($this->cryptedProperties) || empty($this->cryptedProperties)) return; 62 | 63 | foreach ($this->cryptedProperties as $cryptedProperty) { 64 | $get = 'get' . ucfirst($cryptedProperty); 65 | $set = 'set' . ucfirst($cryptedProperty); 66 | $item->$set($this->$mode($item->$get())); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /web/js/script.min.js: -------------------------------------------------------------------------------- 1 | ;var Pwx={init:function(){this.initLanguageSelector();this.initClipboard();this.initCountdown();this.initPasswordGenerator();this.initShowPassword()},initLanguageSelector:function(){$('#language-selector li a').click(function(a){a.preventDefault();if($(this).closest('li').hasClass('active')){return};Cookies.set('locale',$(this).data('locale'),{expires:365});window.location.href=window.location.href.replace(/locale=[a-zA-Z]{2}&?/,'').replace(/[?&#]$/,'')})},initClipboard:function(){if(!this.hasFlash())return;$('[data-clipboard]').each(function(){var a=$('').addClass('btn btn-default').html($('').addClass('glyphicon glyphicon-copy'));$(this).append(a);a.zclip({path:'/js/ZeroClipboard.swf',copy:$(this).data('clipboard'),afterCopy:function(){}})})},initCountdown:function(){$('#expires-countdown').countdown($('#expires-countdown').data('expires'),this.countdownCallback)},initPasswordGenerator:function(){$('.password-length').click($.proxy(function(a){var t=parseInt($(a.target).text());$('#password').val(this.generatePassword(t));a.stopPropagation()},this))},initShowPassword:function(){$('[data-show-password]').click(function(a){var t=$(this).data('show-password');if(t){$(this).data('show-password',!1);$(this).html($(this).data('show-password-text-show'))} 2 | else{$(this).data('show-password',!0);$(this).html($(this).data('show-password-text-hide'))};$('#password').togglePassword();a.stopPropagation()})},countdownCallback:function(a){$(this).html(a.strftime('%D '+LOCALE.app.data.days+' %H '+LOCALE.app.data.hours+' %M '+LOCALE.app.data.minutes+' %S '+LOCALE.app.data.seconds))},hasFlash:function(){var a=!1;try{var t=new ActiveXObject('ShockwaveFlash.ShockwaveFlash');if(t){a=!0}}catch(s){if(navigator.mimeTypes&&navigator.mimeTypes['application/x-shockwave-flash']!==undefined&&navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin){a=!0}};return a},generatePassword:function(a){var s,o,t='abcdefghijklmnopqrstuvwxyz';t+='ABCDEFGHIJKLMNOPQSTUVWXYZ';t+='0123456789';t+='!@#$%^&*()_+-=[]{};"\'<>,./?\\';a=a||24;o='';for(s=0;s 6 |
7 | {% if credentials %} 8 | {% if credentials.userName %} 9 |
10 |
{{ 'app.data.username'|trans }}
11 |
12 | {{ credentials.userName }} 13 |
14 |
15 | {% endif %} 16 |
17 |
{{ 'app.data.password'|trans }}
18 |
19 | {{ credentials.password }} 20 |
21 |
22 | {% if credentials.comment %} 23 |
24 |
{{ 'app.data.comment'|trans }}
25 |
26 | {{ credentials.comment | nl2br }} 27 |
28 |
29 | {% endif %} 30 |
31 |
{{ 'view_password.expires'|trans }}
32 |
33 |
34 |
35 |
36 | 37 | {% if credentials.oneTimeView != true %} 38 |
39 | 40 | 41 |
42 | {% endif %} 43 | 44 | {% else %} 45 | 46 | {% endif %} 47 |
48 | 49 | {% endblock %} 50 | -------------------------------------------------------------------------------- /src/App/Service/I18nService.php: -------------------------------------------------------------------------------- 1 | validLocales; 31 | } 32 | 33 | /** 34 | * Get the current locale from request, cookie or config 35 | * 36 | * @param string $configLocale Configuration locale setting 37 | * @throws \Exception 38 | * @return string Locale 39 | */ 40 | public function getLocale($configLocale = 'en') 41 | { 42 | // Using superglobals here as the Request object is not available at this point 43 | if (isset($_GET['locale']) && $this->isValidLocale($_GET['locale'])) { 44 | return $_GET['locale']; 45 | } elseif (isset($_COOKIE['locale']) && $this->isValidLocale($_COOKIE['locale'])) { 46 | return $_COOKIE['locale']; 47 | } elseif ($this->isValidLocale($configLocale)) { 48 | return $configLocale; 49 | } 50 | 51 | throw new \Exception('Invalid locale set in config'); 52 | } 53 | 54 | /** 55 | * Save the locale in a cookie if the request contains a locale query string 56 | * 57 | * @param Request $request 58 | * @param Response $response 59 | * @return void 60 | */ 61 | public function setLocaleCookie(Request $request, Response $response) 62 | { 63 | $locale = $request->get('locale'); 64 | if ($this->isValidLocale($locale)) { 65 | $response->headers->setCookie(new Cookie('locale', $locale, time() + 365 * 86400, '/', null, false, false)); 66 | } 67 | } 68 | 69 | /** 70 | * Test whether or not a locale string is valid 71 | * 72 | * @param mixed $locale Locale to test 73 | * @return bool Whether or not the locale is valid 74 | */ 75 | protected function isValidLocale($locale) 76 | { 77 | return in_array($locale, $this->validLocales); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/App/views/layout.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | PWX - {% block title %}No Title{% endblock %} 9 | {% block head %}{% endblock %} 10 | 11 | 12 | 13 | 14 | {% include 'jslocale.twig' %} 15 | 16 | 17 | 18 | 19 | 38 | 39 |
40 |
41 | 44 |
45 | {% block content %}{% endblock %} 46 |
47 |
48 |
49 | {{ 'app.poweredby'|trans }} pwx. 50 |
51 |
52 | {% block closing_scripts %}{% endblock %} 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/App/Tests/Service/I18nServiceTest.php: -------------------------------------------------------------------------------- 1 | app = require __DIR__.'/../../../../app/app.php'; 18 | $this->i18nService = $this->app['i18n_service']; 19 | } 20 | 21 | /** 22 | * Test retrieval of valid locales 23 | * 24 | * @return void 25 | */ 26 | public function testGetValidLocales() 27 | { 28 | $validLocales = $this->i18nService->getValidLocales(); 29 | 30 | $this->assertTrue(is_array($validLocales)); 31 | $this->assertNotEmpty($validLocales); 32 | } 33 | 34 | /** 35 | * Test setting the locale by cookie 36 | * 37 | * @return void 38 | */ 39 | public function testSetLocaleByCookie() 40 | { 41 | $_COOKIE['locale'] = $this->getRandomLocale(); 42 | 43 | $locale = $this->i18nService->getLocale(); 44 | 45 | $this->assertSame($_COOKIE['locale'], $locale); 46 | 47 | unset($_COOKIE['locale']); 48 | } 49 | 50 | /** 51 | * Test setting the locale by get param 52 | * 53 | * @return void 54 | */ 55 | public function testSetLocaleByGet() 56 | { 57 | 58 | $_GET['locale'] = $this->getRandomLocale(); 59 | 60 | $locale = $this->i18nService->getLocale(); 61 | 62 | $this->assertSame($_GET['locale'], $locale); 63 | 64 | unset($_GET['locale']); 65 | } 66 | 67 | /** 68 | * Test that GET takes precedence before COOKIE 69 | * 70 | * @return void 71 | */ 72 | public function testGetCookiePrecedence() 73 | { 74 | 75 | $locales = $this->i18nService->getValidLocales(); 76 | 77 | // Make sure we got enough locales to test with 78 | if (count($locales) < 2) return; 79 | 80 | $_GET['locale'] = $locales[0]; 81 | $_COOKIE['locale'] = $locales[1]; 82 | 83 | $locale = $this->i18nService->getLocale(); 84 | 85 | $this->assertSame($_GET['locale'], $locale); 86 | 87 | unset($_GET['locale']); 88 | unset($_COOKIE['locale']); 89 | } 90 | 91 | /** 92 | * Tests if an invalid config locale will throw an exception 93 | * 94 | * @expectedException \Exception 95 | * 96 | * @return void 97 | */ 98 | public function testInvalidConfigLocale() 99 | { 100 | $this->i18nService->getLocale('invalidlocale'); 101 | } 102 | 103 | /** 104 | * Returns a random valid locale 105 | */ 106 | protected function getRandomLocale() 107 | { 108 | $validLocales = $this->i18nService->getValidLocales(); 109 | $locale = rand(0, count($validLocales) - 1); 110 | return $validLocales[$locale]; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/App/views/index.twig: -------------------------------------------------------------------------------- 1 | {% extends 'layout.twig' %} 2 | {% block title %}{{ 'app.name'|trans }} - {{ 'index.generate'|trans }}{% endblock %} 3 | 4 | {% block content %} 5 |
6 |
7 |
8 | {# 9 | Fake fields are a workaround for Chrome autofill getting the wrong fields. 10 | See: http://stackoverflow.com/questions/15738259/disabling-chrome-autofill/15917221#15917221 11 | #} 12 | 13 | 14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 |
22 |
23 | {{ 'index.insert_password_start'|trans }} 24 | {% for i in 6..36 %} 25 | {% if i is divisible by(6) %} 26 | {{i}}{% if i != 36 %},{% endif %} 27 | {% endif %} 28 | {% endfor %} 29 | {{ 'index.insert_password_end'|trans }} 30 |
31 | 34 |
35 |
36 |
37 | 38 | 39 |
40 |
41 | 42 | 49 |
50 |
51 |
52 | 55 |
56 |
57 | 58 |
59 |
60 |
61 | {% endblock %} 62 | -------------------------------------------------------------------------------- /src/App/Tests/Service/CredentialsServiceTest.php: -------------------------------------------------------------------------------- 1 | 'userName', 15 | 'password' => 'password', 16 | 'comment' => 'comment', 17 | 'expires' => 3600, 18 | 'oneTimeView' => false 19 | ); 20 | 21 | public function setUp() 22 | { 23 | $this->app = require __DIR__.'/../../../../app/app.php'; 24 | $this->credentialsService = $this->app['credentials_service']; 25 | } 26 | 27 | /** 28 | * Test saving of credentials 29 | */ 30 | public function testSave() 31 | { 32 | $credentials = $this->credentialsService->save($this->credentials); 33 | 34 | $this->assertSame($credentials->getUserName(), $this->credentials['userName']); 35 | $this->assertSame($credentials->getPassword(), $this->credentials['password']); 36 | $this->assertSame($credentials->getcomment(), $this->credentials['comment']); 37 | 38 | // Test that expires is within 4s of what its supposed to be, to avoid timing issues when testing 39 | $expires = $this->credentials['expires'] + time(); 40 | $this->assertTrue( 41 | $credentials->getExpires() - 2 <= $expires && 42 | $credentials->getExpires() + 2 >= $expires 43 | ); 44 | 45 | $this->credentialsService->delete($credentials->getHash()); 46 | } 47 | 48 | /** 49 | * Test find credentials 50 | */ 51 | public function testFind() 52 | { 53 | $credentials = $this->credentialsService->save($this->credentials); 54 | $credentials = $this->credentialsService->find($credentials->getHash()); 55 | 56 | $this->assertSame($credentials->getUserName(), $this->credentials['userName']); 57 | $this->assertSame($credentials->getPassword(), $this->credentials['password']); 58 | $this->assertSame($credentials->getcomment(), $this->credentials['comment']); 59 | 60 | $this->credentialsService->delete($credentials->getHash()); 61 | } 62 | 63 | /** 64 | * Test find non-existing credentials 65 | */ 66 | public function testFindNoResult() 67 | { 68 | $this->assertNull($this->credentialsService->find('test')); 69 | } 70 | 71 | /** 72 | * Test delete credentials 73 | */ 74 | public function testDelete() 75 | { 76 | $credentials = $this->credentialsService->save($this->credentials); 77 | $hash = $credentials->getHash(); 78 | 79 | $this->assertTrue(is_object($this->credentialsService->find($hash))); 80 | $credentials = $this->credentialsService->delete($hash); 81 | $this->assertTrue(is_null($this->credentialsService->find($hash))); 82 | } 83 | 84 | /** 85 | * Test expires limitation 86 | */ 87 | public function testLimitExpires() 88 | { 89 | $testCredentials = $this->credentials; 90 | $testCredentials['expires'] = 0; 91 | $credentials = $this->credentialsService->save($testCredentials); 92 | 93 | $this->assertNotSame($credentials->getExpires(), $testCredentials['expires'] + time()); 94 | 95 | $this->credentialsService->delete($credentials->getHash()); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/App/Service/CredentialsService.php: -------------------------------------------------------------------------------- 1 | em = $em; 43 | $this->credentialsFactory = $credentialsFactory; 44 | $this->credentialsRepository = $credentialsRepository; 45 | $this->config = $config; 46 | } 47 | 48 | /** 49 | * Save credentials 50 | * 51 | * @param array $args Credentials to save 52 | * @return \App\Entity\Credentials 53 | */ 54 | public function save($args) 55 | { 56 | // Limit expiry times 57 | if ($args['expires'] < 60 * 60 || $args['expires'] > 60 * 60 * 24 * 30) { 58 | $args['expires'] = 60 * 60; 59 | } 60 | $args['expires'] = time() + $args['expires']; 61 | 62 | $credentials = $this->credentialsFactory->getInstance(); 63 | 64 | $credentials->setUserName($args['userName']); 65 | $credentials->setPassword($args['password']); 66 | $credentials->setComment($args['comment']); 67 | $credentials->setExpires($args['expires']); 68 | $credentials->setOneTimeView($args['oneTimeView']); 69 | 70 | $this->encryptProperties($credentials); 71 | 72 | $this->em->persist($credentials); 73 | $this->em->flush(); 74 | 75 | $this->decryptProperties($credentials); 76 | 77 | return $credentials; 78 | } 79 | 80 | /** 81 | * Find credentials by hash 82 | * 83 | * @param string $hash Hash to find 84 | * @return \App\Entity\Credentials 85 | */ 86 | public function find($hash) 87 | { 88 | /** @var Credentials $credentials */ 89 | $credentials = $this->credentialsRepository->find($hash); 90 | if ($credentials) $this->decryptProperties($credentials); 91 | 92 | return $credentials; 93 | } 94 | 95 | /** 96 | * Delete credentials by hash 97 | * 98 | * @param string $hash Hash to delete 99 | * @return void 100 | */ 101 | public function delete($hash) 102 | { 103 | if ($hash) { 104 | $credentials = $this->credentialsRepository->find($hash); 105 | if ($credentials) { 106 | $this->em->remove($credentials); 107 | $this->em->flush(); 108 | } 109 | } 110 | } 111 | 112 | /** 113 | * Delete expired credentials 114 | * 115 | * @return void 116 | */ 117 | public function deleteExpired() 118 | { 119 | $this->credentialsRepository->deleteExpired(); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/app.php: -------------------------------------------------------------------------------- 1 | true, 15 | 'locale' => 'en', 16 | 'baseUrl' => '', 17 | 'theme' => 'yeti', 18 | 'requireHttps' => false, 19 | 'timer.start' => $startTime, 20 | 'monolog.name' => 'pwx', 21 | 'monolog.level' => \Monolog\Logger::DEBUG, 22 | 'monolog.logfile' => __DIR__ . '/../log/app.log', 23 | 'twig.path' => __DIR__ . '/../src/App/views', 24 | 'twig.options' => array( 25 | 'cache' => __DIR__ . '/../cache/twig', 26 | ), 27 | 'orm.em.options' => array( 28 | 'mappings' => array( 29 | array( 30 | 'type' => 'annotation', 31 | 'namespace' => 'App\Entity', 32 | 'path' => __DIR__. '/../src/App/Entity', 33 | 'use_simple_annotation_reader' => false, 34 | ), 35 | ), 36 | ), 37 | ); 38 | 39 | if (file_exists(__DIR__ . '/config.php')) { 40 | include __DIR__ . '/config.php'; 41 | } 42 | 43 | // Initialize Silex 44 | $app = new App\Silex\Application($config); 45 | 46 | // Register ControllerServiceProvider service 47 | $app->register(new Silex\Provider\ServiceControllerServiceProvider()); 48 | 49 | // Register DoctrineServiceProvider service 50 | $app->register(new Silex\Provider\DoctrineServiceProvider(), $config); 51 | 52 | // Register DoctrineOrmServiceProvider service 53 | $app->register(new Dflydev\Silex\Provider\DoctrineOrm\DoctrineOrmServiceProvider(), $config); 54 | 55 | // Register register I18n service 56 | $app['i18n_service'] = $app->share(function () { return new App\Service\I18nService(); }); 57 | 58 | // Register translation service 59 | $app->register(new Silex\Provider\TranslationServiceProvider(), array( 60 | 'locale_fallbacks' => array('en'), 61 | 'locale' => $app['i18n_service']->getLocale($config['locale']), 62 | )); 63 | 64 | // Register the yaml translations 65 | $app['translator'] = $app->share($app->extend('translator', function(Silex\Translator $translator, $app) { 66 | $translator->addLoader('yaml', new Symfony\Component\Translation\Loader\YamlFileLoader()); 67 | 68 | foreach ($app['i18n_service']->getValidLocales() as $locale) { 69 | $translator->addResource('yaml', __DIR__ . '/locales/' . $locale . '.yml', $locale); 70 | } 71 | 72 | return $translator; 73 | })); 74 | 75 | // Register Credentials factory 76 | $app['credentials_factory'] = $app->share( 77 | function () { 78 | return new App\Factory\CredentialsFactory(); 79 | } 80 | ); 81 | 82 | // Register Credentials service 83 | $app['credentials_service'] = $app->share( 84 | function () use ($app, $config) { 85 | return new App\Service\CredentialsService( 86 | $app['orm.em'], 87 | $app['credentials_factory'], 88 | $app['orm.em']->getRepository('App\Entity\Credentials'), 89 | $config 90 | ); 91 | } 92 | ); 93 | 94 | // Register default controller 95 | $app['app.default_controller'] = $app->share( 96 | function () use ($app) { 97 | return new App\Controller\DefaultController( 98 | $app, 99 | $app['twig'], 100 | $app['credentials_service'] 101 | ); 102 | } 103 | ); 104 | 105 | // Force to use SSL 106 | if ($config['requireHttps']) { 107 | $app['controllers']->requireHttps(); 108 | } 109 | 110 | // After middleware 111 | $app->after(function (Request $request, Response $response) use ($app) { 112 | // Set the locale cookie 113 | $app['i18n_service']->setLocaleCookie($request, $response); 114 | }); 115 | 116 | // Map routes to controllers 117 | include __DIR__ . '/routing.php'; 118 | 119 | return $app; 120 | -------------------------------------------------------------------------------- /web/js/js.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * JavaScript Cookie v2.0.3 3 | * https://github.com/js-cookie/js-cookie 4 | * 5 | * Copyright 2006, 2015 Klaus Hartl & Fagner Brack 6 | * Released under the MIT license 7 | */ 8 | (function (factory) { 9 | if (typeof define === 'function' && define.amd) { 10 | define(factory); 11 | } else if (typeof exports === 'object') { 12 | module.exports = factory(); 13 | } else { 14 | var _OldCookies = window.Cookies; 15 | var api = window.Cookies = factory(); 16 | api.noConflict = function () { 17 | window.Cookies = _OldCookies; 18 | return api; 19 | }; 20 | } 21 | }(function () { 22 | function extend () { 23 | var i = 0; 24 | var result = {}; 25 | for (; i < arguments.length; i++) { 26 | var attributes = arguments[ i ]; 27 | for (var key in attributes) { 28 | result[key] = attributes[key]; 29 | } 30 | } 31 | return result; 32 | } 33 | 34 | function init (converter) { 35 | function api (key, value, attributes) { 36 | var result; 37 | 38 | // Write 39 | 40 | if (arguments.length > 1) { 41 | attributes = extend({ 42 | path: '/' 43 | }, api.defaults, attributes); 44 | 45 | if (typeof attributes.expires === 'number') { 46 | var expires = new Date(); 47 | expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); 48 | attributes.expires = expires; 49 | } 50 | 51 | try { 52 | result = JSON.stringify(value); 53 | if (/^[\{\[]/.test(result)) { 54 | value = result; 55 | } 56 | } catch (e) {} 57 | 58 | value = encodeURIComponent(String(value)); 59 | value = value.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); 60 | 61 | key = encodeURIComponent(String(key)); 62 | key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); 63 | key = key.replace(/[\(\)]/g, escape); 64 | 65 | return (document.cookie = [ 66 | key, '=', value, 67 | attributes.expires && '; expires=' + attributes.expires.toUTCString(), // use expires attribute, max-age is not supported by IE 68 | attributes.path && '; path=' + attributes.path, 69 | attributes.domain && '; domain=' + attributes.domain, 70 | attributes.secure ? '; secure' : '' 71 | ].join('')); 72 | } 73 | 74 | // Read 75 | 76 | if (!key) { 77 | result = {}; 78 | } 79 | 80 | // To prevent the for loop in the first place assign an empty array 81 | // in case there are no cookies at all. Also prevents odd result when 82 | // calling "get()" 83 | var cookies = document.cookie ? document.cookie.split('; ') : []; 84 | var rdecode = /(%[0-9A-Z]{2})+/g; 85 | var i = 0; 86 | 87 | for (; i < cookies.length; i++) { 88 | var parts = cookies[i].split('='); 89 | var name = parts[0].replace(rdecode, decodeURIComponent); 90 | var cookie = parts.slice(1).join('='); 91 | 92 | if (cookie.charAt(0) === '"') { 93 | cookie = cookie.slice(1, -1); 94 | } 95 | 96 | try { 97 | cookie = converter && converter(cookie, name) || cookie.replace(rdecode, decodeURIComponent); 98 | 99 | if (this.json) { 100 | try { 101 | cookie = JSON.parse(cookie); 102 | } catch (e) {} 103 | } 104 | 105 | if (key === name) { 106 | result = cookie; 107 | break; 108 | } 109 | 110 | if (!key) { 111 | result[name] = cookie; 112 | } 113 | } catch (e) {} 114 | } 115 | 116 | return result; 117 | } 118 | 119 | api.get = api.set = api; 120 | api.getJSON = function () { 121 | return api.apply({ 122 | json: true 123 | }, [].slice.call(arguments)); 124 | }; 125 | api.defaults = {}; 126 | 127 | api.remove = function (key, attributes) { 128 | api(key, '', extend(attributes, { 129 | expires: -1 130 | })); 131 | }; 132 | 133 | api.withConverter = init; 134 | 135 | return api; 136 | } 137 | 138 | return init(); 139 | })); 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/MichaelThessel/pwx.svg)](https://travis-ci.org/MichaelThessel/pwx) 2 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/0b168ab7-9e4e-4b31-bbf6-e05a52360209/mini.png)](https://insight.sensiolabs.com/projects/0b168ab7-9e4e-4b31-bbf6-e05a52360209) 3 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/MichaelThessel/pwx?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 4 | 5 | About 6 | ===== 7 | 8 | PWX allows you to set up your own password exchange service to share passwords 9 | via a temporary link. 10 | 11 | For more information please check out my [blog](http://michaelthessel.com/tag/pwx/) 12 | 13 | Example 14 | ============ 15 | 16 | https://pwx.michaelthessel.com 17 | 18 | Installation 19 | ============ 20 | 21 | Download the current stable release [here](https://github.com/MichaelThessel/pwx/archive/v1.0.zip) or clone the repository. 22 | 23 | Install dependencies: 24 | ``` 25 | # php composer.phar install 26 | ``` 27 | 28 | Create configuration file and adjust according to your environment: 29 | ``` 30 | # cp app/config.php.sample app/config.php 31 | ``` 32 | 33 | Create the database schema: 34 | ``` 35 | # app/console orm:schema-tool:create 36 | ``` 37 | Web server configuration 38 | ======================== 39 | 40 | The [Silex Documentation](http://silex.sensiolabs.org/doc/web_servers.html) has example configurations for Apache, nginx, IIS & Lighttpd. 41 | 42 | Update 43 | ====== 44 | 45 | Download the latest source code. 46 | 47 | If you are updating to a newer version follow these steps. 48 | 49 | Update dependencies: 50 | ``` 51 | # php composer.phar update 52 | ``` 53 | 54 | Update the database schema: 55 | ``` 56 | # app/console orm:schema-tool:update --force 57 | ``` 58 | 59 | Translations 60 | ============ 61 | 62 | Currently: 63 | 64 | * English (en) 65 | * Spanish (es) 66 | * Esperanto (eo) 67 | * and German (de) 68 | 69 | are supported by PWX. Please set locale according to your requirements in the 70 | configuration. I'm happy to accept pull requests for additional translations. 71 | 72 | You can use a GET parameter when linking to PWX. I.e. 73 | 74 | https://example.com?locale=es 75 | 76 | This allows for one instance of PWX dynamically being accessed in different 77 | languages. 78 | 79 | Themes 80 | ====== 81 | 82 | PWX supports all [Bootswatch](https://bootswatch.com/) themes. You can adjust 83 | the appearance of the application to your liking by a simple config switch. 84 | 85 | Developers 86 | ========== 87 | 88 | PWX uses the [Robo](http://robo.li) task runner. After making changes to the JS 89 | or CSS files run: 90 | 91 | ``` 92 | # vendor/bin/robo build 93 | ``` 94 | 95 | to minify and concatenate the files. 96 | 97 | Alternatively you can run: 98 | 99 | ``` 100 | # vendor/bin/robo watch 101 | ``` 102 | 103 | while developing which will automatically generate the minified and 104 | concatenated assets when style or script files are changed. 105 | 106 | To run the integrated test suite please run: 107 | ``` 108 | # vendor/bin/phpunit 109 | ``` 110 | API 111 | === 112 | 113 | PWX offers a simple API with the following endpoints: 114 | 115 | **Save an entry** 116 | 117 | ``` 118 | Request Methoud: POST 119 | End Point: https://example.com/api 120 | Params: 121 | userName: string (default: "") 122 | password: string (reqired) 123 | comment: string (default: "") 124 | expires: int (time in s from when the entry will expire, min: 1h, max: 30days, default: 3600) 125 | oneTimeView: bool (whether or not the entry will be deleted after viewing it once, default: false) 126 | Response: 127 | hash string (id of entry) 128 | ``` 129 | 130 | **Retrieve an entry** 131 | 132 | ``` 133 | Request Methoud: GET 134 | End Point: https://example.com/api/[hash] 135 | Params: 136 | n/a 137 | Response: 138 | hash: string 139 | userName: string 140 | password: string 141 | comment: string 142 | ``` 143 | 144 | **Delete an entry** 145 | 146 | ``` 147 | Request Methoud: DELETE 148 | End Point: https://example.com/api/[hash] 149 | Params: 150 | n/a 151 | Response: 152 | n/a 153 | ``` 154 | 155 | Warning 156 | ======= 157 | 158 | You will be responsible to secure your environment. The author of this software 159 | takes no responsiblity for any damage as a result of using this software. 160 | -------------------------------------------------------------------------------- /web/js/script.js: -------------------------------------------------------------------------------- 1 | var Pwx = { 2 | 3 | // Initialize app 4 | init: function() { 5 | this.initLanguageSelector(); 6 | this.initClipboard(); 7 | this.initCountdown(); 8 | this.initPasswordGenerator(); 9 | this.initShowPassword(); 10 | }, 11 | 12 | // Init language selector 13 | initLanguageSelector: function() { 14 | $('#language-selector li a').click(function(e) { 15 | e.preventDefault(); 16 | if ($(this).closest('li').hasClass('active')) { return; } 17 | Cookies.set('locale', $(this).data('locale'), { expires: 365 }); 18 | window.location.href = window.location.href 19 | .replace(/locale=[a-zA-Z]{2}&?/, '') 20 | .replace(/[?&#]$/, ''); 21 | }); 22 | }, 23 | 24 | // Enable zclip on all elements with the data-clipboard attribute 25 | initClipboard: function() { 26 | if (!this.hasFlash()) return; 27 | 28 | $('[data-clipboard]').each(function() { 29 | var button = $('').addClass('btn btn-default').html($('').addClass('glyphicon glyphicon-copy')); 30 | $(this).append(button); 31 | button.zclip({ 32 | path: '/js/ZeroClipboard.swf', 33 | copy: $(this).data('clipboard'), 34 | afterCopy: function() {} 35 | }); 36 | }); 37 | }, 38 | 39 | // Init countdown 40 | initCountdown: function() { 41 | $('#expires-countdown').countdown($('#expires-countdown').data('expires'), this.countdownCallback); 42 | }, 43 | 44 | // Click event handler for password generation 45 | initPasswordGenerator: function() { 46 | $('.password-length').click($.proxy(function(e) { 47 | var length = parseInt($(e.target).text()); 48 | $('#password').val(this.generatePassword(length)); 49 | 50 | e.stopPropagation(); 51 | }, this)); 52 | }, 53 | 54 | // Init show/hide password link 55 | initShowPassword: function() { 56 | $('[data-show-password]').click(function(e) { 57 | var toggle = $(this).data('show-password'); 58 | 59 | if (toggle) { 60 | $(this).data('show-password', false); 61 | $(this).html($(this).data('show-password-text-show')); 62 | } else { 63 | $(this).data('show-password', true); 64 | $(this).html($(this).data('show-password-text-hide')); 65 | } 66 | 67 | $('#password').togglePassword(); 68 | 69 | e.stopPropagation(); 70 | }); 71 | }, 72 | 73 | // Callback function for the countdown timer 74 | countdownCallback: function(e) { 75 | $(this).html(e.strftime('%D ' + LOCALE.app.data.days + ' ' + '%H ' + LOCALE.app.data.hours + ' ' + '%M ' + LOCALE.app.data.minutes + ' ' + '%S ' + LOCALE.app.data.seconds)); 76 | }, 77 | 78 | // Detect if flash is enabled (http://stackoverflow.com/a/20095467) 79 | hasFlash: function() { 80 | var hasFlash = false; 81 | try { 82 | var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash'); 83 | if (fo) { 84 | hasFlash = true; 85 | } 86 | } catch (e) { 87 | if ( 88 | navigator.mimeTypes && 89 | navigator.mimeTypes['application/x-shockwave-flash'] !== undefined && 90 | navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin 91 | ) { 92 | hasFlash = true; 93 | } 94 | } 95 | 96 | return hasFlash; 97 | }, 98 | 99 | // Generate random password string 100 | generatePassword: function(length) { 101 | var i, password, 102 | possibleChars = 'abcdefghijklmnopqrstuvwxyz'; 103 | possibleChars += 'ABCDEFGHIJKLMNOPQSTUVWXYZ'; 104 | possibleChars += '0123456789'; 105 | possibleChars += '!@#$%^&*()_+-=[]{};"\'<>,./?\\'; 106 | 107 | length = length || 24; 108 | 109 | password = ''; 110 | for (i = 0; i < length; i++) { 111 | password += possibleChars.charAt(Math.floor(Math.random() * possibleChars.length)); 112 | } 113 | 114 | return password; 115 | } 116 | }; 117 | 118 | $(document).ready(function() { 119 | Pwx.init(); 120 | }); 121 | -------------------------------------------------------------------------------- /web/js/jquery.countdown.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * The Final Countdown for jQuery v2.0.4 (http://hilios.github.io/jQuery.countdown/) 3 | * Copyright (c) 2014 Edson Hilios 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | * this software and associated documentation files (the "Software"), to deal in 7 | * the Software without restriction, including without limitation the rights to 8 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | * the Software, and to permit persons to whom the Software is furnished to do so, 10 | * subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | !function(a){"use strict";"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){"use strict";function b(a){if(a instanceof Date)return a;if(String(a).match(g))return String(a).match(/^[0-9]*$/)&&(a=Number(a)),String(a).match(/\-/)&&(a=String(a).replace(/\-/g,"/")),new Date(a);throw new Error("Couldn't cast `"+a+"` to a date object.")}function c(a){return function(b){var c=b.match(/%(-|!)?[A-Z]{1}(:[^;]+;)?/gi);if(c)for(var e=0,f=c.length;f>e;++e){var g=c[e].match(/%(-|!)?([a-zA-Z]{1})(:[^;]+;)?/),i=new RegExp(g[0]),j=g[1]||"",k=g[3]||"",l=null;g=g[2],h.hasOwnProperty(g)&&(l=h[g],l=Number(a[l])),null!==l&&("!"===j&&(l=d(k,l)),""===j&&10>l&&(l="0"+l.toString()),b=b.replace(i,l.toString()))}return b=b.replace(/%%/,"%")}}function d(a,b){var c="s",d="";return a&&(a=a.replace(/(:|;|\s)/gi,"").split(/\,/),1===a.length?c=a[0]:(d=a[0],c=a[1])),1===Math.abs(b)?d:c}var e=100,f=[],g=[];g.push(/^[0-9]*$/.source),g.push(/([0-9]{1,2}\/){2}[0-9]{4}( [0-9]{1,2}(:[0-9]{2}){2})?/.source),g.push(/[0-9]{4}([\/\-][0-9]{1,2}){2}( [0-9]{1,2}(:[0-9]{2}){2})?/.source),g=new RegExp(g.join("|"));var h={Y:"years",m:"months",w:"weeks",d:"days",D:"totalDays",H:"hours",M:"minutes",S:"seconds"},i=function(b,c,d){this.el=b,this.$el=a(b),this.interval=null,this.offset={},this.instanceNumber=f.length,f.push(this),this.$el.data("countdown-instance",this.instanceNumber),d&&(this.$el.on("update.countdown",d),this.$el.on("stoped.countdown",d),this.$el.on("finish.countdown",d)),this.setFinalDate(c),this.start()};a.extend(i.prototype,{start:function(){null!==this.interval&&clearInterval(this.interval);var a=this;this.update(),this.interval=setInterval(function(){a.update.call(a)},e)},stop:function(){clearInterval(this.interval),this.interval=null,this.dispatchEvent("stoped")},pause:function(){this.stop.call(this)},resume:function(){this.start.call(this)},remove:function(){this.stop(),f[this.instanceNumber]=null,delete this.$el.data().countdownInstance},setFinalDate:function(a){this.finalDate=b(a)},update:function(){return 0===this.$el.closest("html").length?void this.remove():(this.totalSecsLeft=this.finalDate.getTime()-(new Date).getTime(),this.totalSecsLeft=Math.ceil(this.totalSecsLeft/1e3),this.totalSecsLeft=this.totalSecsLeft<0?0:this.totalSecsLeft,this.offset={seconds:this.totalSecsLeft%60,minutes:Math.floor(this.totalSecsLeft/60)%60,hours:Math.floor(this.totalSecsLeft/60/60)%24,days:Math.floor(this.totalSecsLeft/60/60/24)%7,totalDays:Math.floor(this.totalSecsLeft/60/60/24),weeks:Math.floor(this.totalSecsLeft/60/60/24/7),months:Math.floor(this.totalSecsLeft/60/60/24/30),years:Math.floor(this.totalSecsLeft/60/60/24/365)},void(0===this.totalSecsLeft?(this.stop(),this.dispatchEvent("finish")):this.dispatchEvent("update")))},dispatchEvent:function(b){var d=a.Event(b+".countdown");d.finalDate=this.finalDate,d.offset=a.extend({},this.offset),d.strftime=c(this.offset),this.$el.trigger(d)}}),a.fn.countdown=function(){var b=Array.prototype.slice.call(arguments,0);return this.each(function(){var c=a(this).data("countdown-instance");if(void 0!==c){var d=f[c],e=b[0];i.prototype.hasOwnProperty(e)?d[e].apply(d,b.slice(1)):null===String(e).match(/^[$A-Z_][0-9A-Z_$]*$/i)?(d.setFinalDate.call(d,e),d.start()):a.error("Method %s does not exist on jQuery.countdown".replace(/\%s/gi,e))}else new i(this,b[0],b[1])})}}); -------------------------------------------------------------------------------- /src/App/Entity/Credentials.php: -------------------------------------------------------------------------------- 1 | hash = $hash; 64 | } 65 | 66 | /** 67 | * Get hash 68 | * 69 | * @return string 70 | */ 71 | public function getHash() 72 | { 73 | return $this->hash; 74 | } 75 | 76 | /** 77 | * Set userName 78 | * 79 | * @param string $userName 80 | * @return void 81 | */ 82 | public function setUserName($userName) 83 | { 84 | $this->userName = $userName; 85 | } 86 | 87 | /** 88 | * Get userName 89 | * 90 | * @return string 91 | */ 92 | public function getUserName() 93 | { 94 | return $this->userName; 95 | } 96 | 97 | /** 98 | * Set password 99 | * 100 | * @param string $password 101 | * @return void 102 | */ 103 | public function setPassword($password) 104 | { 105 | $this->password = $password; 106 | } 107 | 108 | /** 109 | * Get password 110 | * 111 | * @return string 112 | */ 113 | public function getPassword() 114 | { 115 | return $this->password; 116 | } 117 | 118 | /** 119 | * Set comment 120 | * 121 | * @param string $comment 122 | * @return void 123 | */ 124 | public function setComment($comment) 125 | { 126 | $this->comment = $comment; 127 | } 128 | 129 | /** 130 | * Get comment 131 | * 132 | * @return string 133 | */ 134 | public function getComment() 135 | { 136 | return $this->comment; 137 | } 138 | 139 | /** 140 | * Set expires 141 | * 142 | * @param integer $expires 143 | * @return void 144 | */ 145 | public function setExpires($expires) 146 | { 147 | $this->expires = $expires; 148 | } 149 | 150 | /** 151 | * Get expires 152 | * 153 | * @return integer 154 | */ 155 | public function getExpires() 156 | { 157 | return $this->expires; 158 | } 159 | 160 | /** 161 | * Get encryption state 162 | * 163 | * @return bool Encryption state 164 | */ 165 | public function isEncrypted() 166 | { 167 | return $this->isEncrypted; 168 | } 169 | 170 | /** 171 | * Set encryption state 172 | * 173 | * @param bool $isEncrypted Whether or not the entity is encrypted 174 | * @return void 175 | */ 176 | public function setEncrypted($isEncrypted) 177 | { 178 | $this->isEncrypted = $isEncrypted; 179 | } 180 | 181 | /** 182 | * @return boolean 183 | */ 184 | public function getOneTimeView() 185 | { 186 | return $this->oneTimeView; 187 | } 188 | 189 | /** 190 | * @param boolean $oneTimeView 191 | */ 192 | public function setOneTimeView($oneTimeView) 193 | { 194 | $this->oneTimeView = $oneTimeView; 195 | } 196 | 197 | /** 198 | * @return array 199 | */ 200 | public function jsonSerialize() 201 | { 202 | $data = array( 203 | 'hash' => $this->hash, 204 | 'userName' => $this->userName, 205 | 'password' => $this->password, 206 | 'comment' => $this->comment 207 | ); 208 | return $data; 209 | } 210 | 211 | } 212 | 213 | -------------------------------------------------------------------------------- /src/App/Controller/DefaultController.php: -------------------------------------------------------------------------------- 1 | app = $app; 30 | $this->twig = $twig; 31 | $this->credentialsService = $credentialsService; 32 | 33 | // Clean the credentials table on every request (workaround to not have to add cronjobs) 34 | $this->credentialsService->deleteExpired(); 35 | } 36 | 37 | /** 38 | * Create a new entry 39 | * 40 | * @return mixed Rendered Twig template 41 | */ 42 | public function indexAction() 43 | { 44 | return $this->twig->render('index.twig'); 45 | } 46 | 47 | /** 48 | * Submit new data 49 | * 50 | * @param Request $request 51 | * @return mixed Rendered Twig template 52 | */ 53 | public function indexPostAction(Request $request) 54 | { 55 | // Exit if we got no password 56 | $password = $request->get('password'); 57 | if (empty($password)) { 58 | return $this->app->redirect('/'); 59 | } 60 | 61 | $credentials = $this->saveCredentials($request); 62 | 63 | return $this->app->redirect($this->app['baseUrl'] . '/link/' . $credentials->getHash()); 64 | } 65 | 66 | /** 67 | * Api call to submit new data 68 | * 69 | * @param Request $request 70 | * @return string JSON Response 71 | */ 72 | public function apiPostAction(Request $request) 73 | { 74 | // Exit if we got no password 75 | $password = $request->get('password'); 76 | if (empty($password)) { 77 | $error = array('message' => 'The password cannot be empty'); 78 | return $this->app->json($error, Response::HTTP_BAD_REQUEST); 79 | } 80 | 81 | $credentials = $this->saveCredentials($request); 82 | 83 | $link = array('hash' => $credentials->getHash()); 84 | return $this->app->json($link); 85 | } 86 | 87 | /** 88 | * View the share link 89 | * 90 | * @param mixed $hash Hash that identifies the entry 91 | * @return mixed Rendered Twig template 92 | */ 93 | public function viewLinkAction($hash) 94 | { 95 | return $this->twig->render('view_link.twig', array( 96 | 'hash' => $hash, 97 | )); 98 | } 99 | 100 | /** 101 | * View an entry 102 | * 103 | * @param mixed $hash Hash that identifies the entry 104 | * @return mixed Rendered Twig template 105 | */ 106 | public function viewPasswordAction($hash) 107 | { 108 | $credentials = $this->getCredentials($hash); 109 | 110 | return $this->twig->render('view_password.twig', array( 111 | 'credentials' => $credentials, 112 | )); 113 | } 114 | 115 | /** 116 | * Api call to view an entry 117 | * 118 | * @param mixed $hash Hash that identifies the entry 119 | * @return string JSON Response 120 | */ 121 | public function apiViewAction($hash) 122 | { 123 | $credentials = $this->getCredentials($hash); 124 | 125 | if (!$credentials) { 126 | return $this->app->json( 127 | array('message' => 'This recored has expired'), 128 | Response::HTTP_GONE 129 | ); 130 | } 131 | 132 | return $this->app->json($credentials); 133 | } 134 | 135 | /** 136 | * Delete an entry 137 | * 138 | * @param Request $request 139 | * @return mixed Rendered Twig template 140 | */ 141 | public function deleteAction(Request $request) 142 | { 143 | $this->deleteCredentials($request->get('hash')); 144 | 145 | return $this->app->redirect($this->app['baseUrl'] . '/'); 146 | } 147 | 148 | /** 149 | * Api call to delete an entry 150 | * 151 | * @param $hash 152 | * @return string JSON Response 153 | */ 154 | public function apiDeleteAction($hash) 155 | { 156 | $this->deleteCredentials($hash); 157 | 158 | return $this->app->json('', Response::HTTP_NO_CONTENT); 159 | } 160 | 161 | /** 162 | * Extract credentialParameter from Post-Request and save it 163 | * 164 | * @param Request $request 165 | * @return \App\Entity\Credentials 166 | */ 167 | protected function saveCredentials(Request $request) 168 | { 169 | return $this->credentialsService->save(array( 170 | 'userName' => $request->get('userName'), 171 | 'password' => $request->get('password'), 172 | 'comment' => $request->get('comment'), 173 | 'expires' => $request->get('expires', 60 * 60), 174 | 'oneTimeView' => (int) $request->get('oneTimeView') == 1, 175 | )); 176 | } 177 | 178 | /** 179 | * Get credentials by hash from the credentialService 180 | * 181 | * @param $hash 182 | * @return \App\Entity\Credentials 183 | */ 184 | protected function getCredentials($hash) 185 | { 186 | $credentials = $this->credentialsService->find($hash); 187 | 188 | if ($credentials && $credentials->getOneTimeView()) { 189 | $this->credentialsService->delete($credentials->getHash()); 190 | } 191 | 192 | return $credentials; 193 | } 194 | 195 | /** 196 | * Delete credentials by hash in the credentialService 197 | * 198 | * @param $hash 199 | */ 200 | protected function deleteCredentials($hash) 201 | { 202 | $this->credentialsService->delete($hash); 203 | } 204 | 205 | } 206 | -------------------------------------------------------------------------------- /web/js/jquery.zclip.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * zClip :: jQuery ZeroClipboard v1.1.1 3 | * http://steamdev.com/zclip 4 | * 5 | * Copyright 2011, SteamDev 6 | * Released under the MIT license. 7 | * http://www.opensource.org/licenses/mit-license.php 8 | * 9 | * Date: Wed Jun 01, 2011 10 | */ 11 | 12 | (function(a){a.fn.zclip=function(c){if(typeof c=="object"&&!c.length){var b=a.extend({path:"ZeroClipboard.swf",copy:null,beforeCopy:null,afterCopy:null,clickAfter:true,setHandCursor:true,setCSSEffects:true},c);return this.each(function(){var e=a(this);if(e.is(":visible")&&(typeof b.copy=="string"||a.isFunction(b.copy))){ZeroClipboard.setMoviePath(b.path);var d=new ZeroClipboard.Client();if(a.isFunction(b.copy)){e.bind("zClip_copy",b.copy)}if(a.isFunction(b.beforeCopy)){e.bind("zClip_beforeCopy",b.beforeCopy)}if(a.isFunction(b.afterCopy)){e.bind("zClip_afterCopy",b.afterCopy)}d.setHandCursor(b.setHandCursor);d.setCSSEffects(b.setCSSEffects);d.addEventListener("mouseOver",function(f){e.trigger("mouseenter")});d.addEventListener("mouseOut",function(f){e.trigger("mouseleave")});d.addEventListener("mouseDown",function(f){e.trigger("mousedown");if(!a.isFunction(b.copy)){d.setText(b.copy)}else{d.setText(e.triggerHandler("zClip_copy"))}if(a.isFunction(b.beforeCopy)){e.trigger("zClip_beforeCopy")}});d.addEventListener("complete",function(f,g){if(a.isFunction(b.afterCopy)){e.trigger("zClip_afterCopy")}else{if(g.length>500){g=g.substr(0,500)+"...\n\n("+(g.length-500)+" characters not shown)"}e.removeClass("hover");alert("Copied text to clipboard:\n\n "+g)}if(b.clickAfter){e.trigger("click")}});d.glue(e[0],e.parent()[0]);a(window).bind("load resize",function(){d.reposition()})}})}else{if(typeof c=="string"){return this.each(function(){var f=a(this);c=c.toLowerCase();var e=f.data("zclipId");var d=a("#"+e+".zclip");if(c=="remove"){d.remove();f.removeClass("active hover")}else{if(c=="hide"){d.hide();f.removeClass("active hover")}else{if(c=="show"){d.show()}}}})}}}})(jQuery);var ZeroClipboard={version:"1.0.7",clients:{},moviePath:"ZeroClipboard.swf",nextId:1,$:function(a){if(typeof(a)=="string"){a=document.getElementById(a)}if(!a.addClass){a.hide=function(){this.style.display="none"};a.show=function(){this.style.display=""};a.addClass=function(b){this.removeClass(b);this.className+=" "+b};a.removeClass=function(d){var e=this.className.split(/\s+/);var b=-1;for(var c=0;c-1){e.splice(b,1);this.className=e.join(" ")}return this};a.hasClass=function(b){return !!this.className.match(new RegExp("\\s*"+b+"\\s*"))}}return a},setMoviePath:function(a){this.moviePath=a},dispatch:function(d,b,c){var a=this.clients[d];if(a){a.receiveEvent(b,c)}},register:function(b,a){this.clients[b]=a},getDOMObjectPosition:function(c,a){var b={left:0,top:0,width:c.width?c.width:c.offsetWidth,height:c.height?c.height:c.offsetHeight};if(c&&(c!=a)){b.left+=c.offsetLeft;b.top+=c.offsetTop}return b},Client:function(a){this.handlers={};this.id=ZeroClipboard.nextId++;this.movieId="ZeroClipboardMovie_"+this.id;ZeroClipboard.register(this.id,this);if(a){this.glue(a)}}};ZeroClipboard.Client.prototype={id:0,ready:false,movie:null,clipText:"",handCursorEnabled:true,cssEffects:true,handlers:null,glue:function(d,b,e){this.domElement=ZeroClipboard.$(d);var f=99;if(this.domElement.style.zIndex){f=parseInt(this.domElement.style.zIndex,10)+1}if(typeof(b)=="string"){b=ZeroClipboard.$(b)}else{if(typeof(b)=="undefined"){b=document.getElementsByTagName("body")[0]}}var c=ZeroClipboard.getDOMObjectPosition(this.domElement,b);this.div=document.createElement("div");this.div.className="zclip";this.div.id="zclip-"+this.movieId;$(this.domElement).data("zclipId","zclip-"+this.movieId);var a=this.div.style;a.position="absolute";a.left=""+c.left+"px";a.top=""+c.top+"px";a.width=""+c.width+"px";a.height=""+c.height+"px";a.zIndex=f;if(typeof(e)=="object"){for(addedStyle in e){a[addedStyle]=e[addedStyle]}}b.appendChild(this.div);this.div.innerHTML=this.getHTML(c.width,c.height)},getHTML:function(d,a){var c="";var b="id="+this.id+"&width="+d+"&height="+a;if(navigator.userAgent.match(/MSIE/)){var e=location.href.match(/^https/i)?"https://":"http://";c+=''}else{c+=''}return c},hide:function(){if(this.div){this.div.style.left="-2000px"}},show:function(){this.reposition()},destroy:function(){if(this.domElement&&this.div){this.hide();this.div.innerHTML="";var a=document.getElementsByTagName("body")[0];try{a.removeChild(this.div)}catch(b){}this.domElement=null;this.div=null}},reposition:function(c){if(c){this.domElement=ZeroClipboard.$(c);if(!this.domElement){this.hide()}}if(this.domElement&&this.div){var b=ZeroClipboard.getDOMObjectPosition(this.domElement);var a=this.div.style;a.left=""+b.left+"px";a.top=""+b.top+"px"}},setText:function(a){this.clipText=a;if(this.ready){this.movie.setText(a)}},addEventListener:function(a,b){a=a.toString().toLowerCase().replace(/^on/,"");if(!this.handlers[a]){this.handlers[a]=[]}this.handlers[a].push(b)},setHandCursor:function(a){this.handCursorEnabled=a;if(this.ready){this.movie.setHandCursor(a)}},setCSSEffects:function(a){this.cssEffects=!!a},receiveEvent:function(d,f){d=d.toString().toLowerCase().replace(/^on/,"");switch(d){case"load":this.movie=document.getElementById(this.movieId);if(!this.movie){var c=this;setTimeout(function(){c.receiveEvent("load",null)},1);return}if(!this.ready&&navigator.userAgent.match(/Firefox/)&&navigator.userAgent.match(/Windows/)){var c=this;setTimeout(function(){c.receiveEvent("load",null)},100);this.ready=true;return}this.ready=true;try{this.movie.setText(this.clipText)}catch(h){}try{this.movie.setHandCursor(this.handCursorEnabled)}catch(h){}break;case"mouseover":if(this.domElement&&this.cssEffects){this.domElement.addClass("hover");if(this.recoverActive){this.domElement.addClass("active")}}break;case"mouseout":if(this.domElement&&this.cssEffects){this.recoverActive=false;if(this.domElement.hasClass("active")){this.domElement.removeClass("active");this.recoverActive=true}this.domElement.removeClass("hover")}break;case"mousedown":if(this.domElement&&this.cssEffects){this.domElement.addClass("active")}break;case"mouseup":if(this.domElement&&this.cssEffects){this.domElement.removeClass("active");this.recoverActive=false}break}if(this.handlers[d]){for(var b=0,a=this.handlers[d].length;b',className:"hideShowPassword-toggle",touchSupport:typeof Modernizr==="undefined"?false:Modernizr.touch,attachToEvent:"click",attachToTouchEvent:"touchstart mousedown",attachToKeyEvent:"keyup",attachToKeyCodes:true,styles:{position:"absolute"},touchStyles:{pointerEvents:"none"},position:"infer",verticalAlign:"middle",offset:0,attr:{role:"button","aria-label":"Show Password",tabIndex:0}},wrapper:{element:"
",className:"hideShowPassword-wrapper",enforceWidth:true,styles:{position:"relative"},inheritStyles:["display","verticalAlign","marginTop","marginRight","marginBottom","marginLeft"],innerElementStyles:{marginTop:0,marginRight:0,marginBottom:0,marginLeft:0}},states:{shown:{className:"hideShowPassword-shown",changeEvent:"passwordShown",props:{type:"text"},toggle:{className:"hideShowPassword-toggle-hide",content:"Hide",attr:{"aria-pressed":"true"}}},hidden:{className:"hideShowPassword-hidden",changeEvent:"passwordHidden",props:{type:"password"},toggle:{className:"hideShowPassword-toggle-show",content:"Show",attr:{"aria-pressed":"false"}}}}};function HideShowPassword(element,options){this.element=$(element);this.wrapperElement=$();this.toggleElement=$();this.init(options)}HideShowPassword.prototype={init:function(options){if(this.update(options,defaults)){this.element.addClass(this.options.className);if(this.options.innerToggle){this.wrapElement(this.options.wrapper);this.initToggle(this.options.toggle);if(typeof this.options.innerToggle==="string"){this.toggleElement.hide();this.element.one(this.options.innerToggle,$.proxy(function(){this.toggleElement.show()},this))}}this.element.trigger(this.options.initEvent,[this])}},update:function(options,base){this.options=this.prepareOptions(options,base);if(this.updateElement()){this.element.trigger(this.options.changeEvent,[this]).trigger(this.state().changeEvent,[this])}return this.options.enable},toggle:function(showVal){showVal=showVal||"toggle";return this.update({show:showVal})},prepareOptions:function(options,base){var keyCodes=[],testElement;base=base||this.options;options=$.extend(true,{},base,options);if(options.enable){if(options.show==="toggle"){options.show=this.isType("hidden",options.states)}else if(options.show==="infer"){options.show=this.isType("shown",options.states)}if(options.toggle.position==="infer"){options.toggle.position=this.element.css("text-direction")==="rtl"?"left":"right"}if(!$.isArray(options.toggle.attachToKeyCodes)){if(options.toggle.attachToKeyCodes===true){testElement=$(options.toggle.element);switch(testElement.prop("tagName").toLowerCase()){case"button":case"input":break;case"a":if(testElement.filter("[href]").length){keyCodes.push(SPACE);break}default:keyCodes.push(SPACE,ENTER);break}}options.toggle.attachToKeyCodes=keyCodes}}return options},updateElement:function(){if(!this.options.enable||this.isType())return false;this.element.prop($.extend({},this.options.props,this.state().props)).addClass(this.state().className).removeClass(this.otherState().className);this.updateToggle();return true},isType:function(comparison,states){states=states||this.options.states;comparison=comparison||this.state(undef,undef,states).props.type;if(states[comparison]){comparison=states[comparison].props.type}return this.element.prop("type")===comparison},state:function(key,invert,states){states=states||this.options.states;if(key===undef){key=this.options.show}if(typeof key==="boolean"){key=key?"shown":"hidden"}if(invert){key=key==="shown"?"hidden":"shown"}return states[key]},otherState:function(key){return this.state(key,true)},wrapElement:function(options){var enforceWidth=options.enforceWidth,targetWidth;if(!this.wrapperElement.length){targetWidth=this.element.outerWidth();$.each(options.inheritStyles,$.proxy(function(index,prop){options.styles[prop]=this.element.css(prop)},this));this.element.css(options.innerElementStyles).wrap($(options.element).addClass(options.className).css(options.styles));this.wrapperElement=this.element.parent();if(enforceWidth===true){enforceWidth=this.wrapperElement.outerWidth()===targetWidth?false:targetWidth}if(enforceWidth!==false){this.wrapperElement.css("width",enforceWidth)}}return this.wrapperElement},initToggle:function(options){if(!this.toggleElement.length){this.toggleElement=$(options.element).attr(options.attr).addClass(options.className).css(options.styles).appendTo(this.wrapperElement);this.updateToggle();this.positionToggle(options.position,options.verticalAlign,options.offset);if(options.touchSupport){this.toggleElement.css(options.touchStyles);this.element.on(options.attachToTouchEvent,$.proxy(this.toggleTouchEvent,this))}else{this.toggleElement.on(options.attachToEvent,$.proxy(this.toggleEvent,this))}if(options.attachToKeyCodes.length){this.toggleElement.on(options.attachToKeyEvent,$.proxy(this.toggleKeyEvent,this))}}return this.toggleElement},positionToggle:function(position,verticalAlign,offset){var styles={};styles[position]=offset;switch(verticalAlign){case"top":case"bottom":styles[verticalAlign]=offset;break;case"middle":styles["top"]="50%";styles["marginTop"]=this.toggleElement.outerHeight()/-2;break}return this.toggleElement.css(styles)},updateToggle:function(state,otherState){var paddingProp,targetPadding;if(this.toggleElement.length){paddingProp="padding-"+this.options.toggle.position;state=state||this.state().toggle;otherState=otherState||this.otherState().toggle;this.toggleElement.attr(state.attr).addClass(state.className).removeClass(otherState.className).html(state.content);targetPadding=this.toggleElement.outerWidth()+this.options.toggle.offset*2;if(this.element.css(paddingProp)!==targetPadding){this.element.css(paddingProp,targetPadding)}}return this.toggleElement},toggleEvent:function(event){event.preventDefault();this.toggle()},toggleKeyEvent:function(event){$.each(this.options.toggle.attachToKeyCodes,$.proxy(function(index,keyCode){if(event.which===keyCode){this.toggleEvent(event);return false}},this))},toggleTouchEvent:function(event){var toggleX=this.toggleElement.offset().left,eventX,lesser,greater;if(toggleX){eventX=event.pageX||event.originalEvent.pageX;if(this.options.toggle.position==="left"){toggleX+=this.toggleElement.outerWidth();lesser=eventX;greater=toggleX}else{lesser=toggleX;greater=eventX}if(greater>=lesser){this.toggleEvent(event)}}}};$.fn.hideShowPassword=function(){var options={};$.each(arguments,function(index,value){var newOptions={};if(typeof value==="object"){newOptions=value}else if(shorthandArgs[index]){newOptions[shorthandArgs[index]]=value}else{return false}$.extend(true,options,newOptions)});return this.each(function(){var $this=$(this),data=$this.data(dataKey);if(data){data.update(options)}else{$this.data(dataKey,new HideShowPassword(this,options))}})};$.each({show:true,hide:false,toggle:"toggle"},function(verb,showVal){$.fn[verb+"Password"]=function(innerToggle,options){return this.hideShowPassword(showVal,innerToggle,options)}})},this); -------------------------------------------------------------------------------- /src/App/Tests/Controller/DefaultControllerTest.php: -------------------------------------------------------------------------------- 1 | 'nameOfUser', 16 | 'password' => 'passwordOfUser', 17 | 'comment' => 'commentOfUser', 18 | 'expires' => 3600, 19 | 'oneTimeView' => false 20 | ); 21 | 22 | public function createApplication() 23 | { 24 | $this->app = require __DIR__.'/../../../../app/app.php'; 25 | 26 | $this->credentialsService = $this->app['credentials_service']; 27 | 28 | unset($this->app['exception_handler']); 29 | 30 | return $this->app; 31 | } 32 | 33 | /** 34 | * Test HTTP/S redirects 35 | * 36 | * @return void 37 | */ 38 | public function testHttpRequestRedirectsToHttps() 39 | { 40 | $client = $this->createClient(); 41 | $client->request('GET', '/'); 42 | $this->assertSame($client->getResponse()->isRedirect(), $this->app['requireHttps']); 43 | } 44 | 45 | /** 46 | * Test credential creation 47 | * 48 | * @return void 49 | */ 50 | public function testIndex() 51 | { 52 | $client = $this->createClient(); 53 | $crawler = $client->request('GET', '/', array(), array(), array('HTTPS' => true)); 54 | 55 | $this->assertTrue($client->getResponse()->isOk()); 56 | 57 | // Check if all form elements are available 58 | $this->assertEquals(1, $crawler->filter('input[name="userName"]')->count()); 59 | $this->assertEquals(1, $crawler->filter('input[name="password"]')->count()); 60 | $this->assertEquals(1, $crawler->filter('textarea[name="comment"]')->count()); 61 | $this->assertEquals(1, $crawler->filter('select[name="expires"]')->count()); 62 | $this->assertEquals(1, $crawler->filter('input[name="oneTimeView"]')->count()); 63 | $this->assertEquals(1, $crawler->filter('button[type="submit"]')->count()); 64 | } 65 | 66 | /** 67 | * Test redirect after credential creation 68 | */ 69 | public function testSaveCredentialsAndRedirectToSharePage() 70 | { 71 | // Load index page 72 | $client = $this->createClient(); 73 | $crawler = $client->request('GET', '/', array(), array(), array('HTTPS' => true)); 74 | 75 | // Save credentials 76 | $form = $crawler->filter('#submitCredentialsForm')->form(); 77 | $form->setValues($this->credentials); 78 | 79 | $client->submit($form); 80 | 81 | // Follow redirect to share link page 82 | $crawler = $client->followRedirect(); 83 | 84 | // Test URI and Link-Url 85 | $this->assertTrue($client->getResponse()->isOk()); 86 | 87 | $link = $crawler->filter('#passwordlink')->link()->getUri(); 88 | $hash = array(); 89 | $this->assertEquals(1, preg_match('/pw\/(.*)$/', $link, $hash)); 90 | 91 | $this->credentialsService->delete($hash[1]); 92 | } 93 | 94 | /** 95 | * Test credential viewing 96 | */ 97 | public function testViewCredentials() 98 | { 99 | $credentials = $this->credentialsService->save($this->credentials); 100 | 101 | $client = $this->createClient(); 102 | $crawler = $client->request('GET', '/pw/' . $credentials->getHash(), array(), array(), array('HTTPS' => true)); 103 | 104 | $this->assertTrue($client->getResponse()->isOk()); 105 | $this->assertEquals($this->credentials['userName'], $crawler->filter('#userName > span')->text()); 106 | $this->assertEquals($this->credentials['password'], $crawler->filter('#password > span')->text()); 107 | $this->assertEquals($this->credentials['comment'], trim($crawler->filter('#comment')->text())); 108 | 109 | $this->credentialsService->delete($credentials->getHash()); 110 | } 111 | 112 | /** 113 | * Test credential deletion 114 | */ 115 | public function testDeleteCredentials() 116 | { 117 | $credentials = $this->credentialsService->save($this->credentials); 118 | 119 | $client = $this->createClient(); 120 | $crawler = $client->request('GET', '/pw/' . $credentials->getHash(), array(), array(), array('HTTPS' => true)); 121 | 122 | // Delete credential 123 | $form = $crawler->filter('#deleteCredentialsForm')->form(); 124 | $client->submit($form); 125 | $client->followRedirect(); 126 | $this->assertTrue($client->getResponse()->isOk()); 127 | 128 | // Test if redirects to '/' 129 | $this->assertEquals('/', $client->getRequest()->getRequestUri()); 130 | 131 | // Visit the link page again and see of the entry is deleted 132 | $client = $this->createClient(); 133 | $crawler = $client->request('GET', '/pw/' . $credentials->getHash(), array(), array(), array('HTTPS' => true)); 134 | $this->assertEquals(1, $crawler->filter('#credentialsExpired')->count()); 135 | } 136 | 137 | /** 138 | * Test oneTimeView Off 139 | */ 140 | public function testOneTimeViewOff() 141 | { 142 | $credentials = $this->credentialsService->save($this->credentials); 143 | 144 | // Access resource first time 145 | $client = $this->createClient(); 146 | $client->request('GET', '/pw/' . $credentials->getHash(), array(), array(), array('HTTPS' => true)); 147 | $this->assertTrue($client->getResponse()->isOk()); 148 | 149 | // Visit the link page again and see of the entry is deleted 150 | $client = $this->createClient(); 151 | $crawler = $client->request('GET', '/pw/' . $credentials->getHash(), array(), array(), array('HTTPS' => true)); 152 | $this->assertTrue($client->getResponse()->isOk()); 153 | $this->assertEquals(1, $crawler->filter('#deleteCredentialsForm')->count()); 154 | } 155 | 156 | /** 157 | * Test oneTimeView On 158 | */ 159 | public function testOneTimeViewOn() 160 | { 161 | $this->credentials['oneTimeView'] = true; 162 | $credentials = $this->credentialsService->save($this->credentials); 163 | 164 | // Access resource first time 165 | $client = $this->createClient(); 166 | $crawler = $client->request('GET', '/pw/' . $credentials->getHash(), array(), array(), array('HTTPS' => true)); 167 | $this->assertTrue($client->getResponse()->isOk()); 168 | $this->assertEquals(0, $crawler->filter('#credentialsExpired')->count()); 169 | 170 | // Visit the link page again and see of the entry is deleted 171 | $client = $this->createClient(); 172 | $crawler = $client->request('GET', '/pw/' . $credentials->getHash(), array(), array(), array('HTTPS' => true)); 173 | $this->assertTrue($client->getResponse()->isOk()); 174 | $this->assertEquals(1, $crawler->filter('#credentialsExpired')->count()); 175 | $this->assertEquals(0, $crawler->filter('#deleteCredentialsForm')->count()); 176 | } 177 | 178 | /** 179 | * Test API, submit credentials and get link to password-page 180 | */ 181 | public function testApiPostCredentialsAndGetLinkToPasswordPage() 182 | { 183 | // Load index page 184 | $client = $this->createClient(); 185 | $client->request( 186 | 'POST', 187 | '/api', 188 | $this->credentials, 189 | array(), 190 | array('HTTPS' => true) 191 | ); 192 | 193 | $this->assertTrue($client->getResponse()->isOk()); 194 | $response = json_decode($client->getResponse()->getContent(), true); 195 | $this->assertSame(200, $client->getResponse()->getStatusCode()); 196 | $this->assertTrue(is_array($response)); 197 | $this->assertTrue(array_key_exists('hash', $response)); 198 | 199 | $this->credentialsService->delete($response['hash']); 200 | } 201 | 202 | /** 203 | * Test API, submit empty credentials 204 | */ 205 | public function testApiPostCredentialsEmpty() 206 | { 207 | // Load index page 208 | $client = $this->createClient(); 209 | $client->request( 210 | 'POST', 211 | '/api', 212 | array(), 213 | array(), 214 | array('HTTPS' => true) 215 | ); 216 | 217 | $response = json_decode($client->getResponse()->getContent(), true); 218 | $this->assertSame(400, $client->getResponse()->getStatusCode()); 219 | $this->assertTrue(is_array($response)); 220 | $this->assertTrue(array_key_exists('message', $response)); 221 | } 222 | 223 | /** 224 | * Test API, show credentials by hash 225 | */ 226 | public function testApiGetCredentials() 227 | { 228 | $credentials = $this->credentialsService->save($this->credentials); 229 | 230 | $client = $this->createClient(); 231 | $client->request('GET', '/api/' . $credentials->getHash(), array(), array(), array('HTTPS' => true)); 232 | $response = json_decode($client->getResponse()->getContent(), true); 233 | 234 | $this->assertTrue($client->getResponse()->isOk()); 235 | $this->assertEquals($this->credentials['userName'], $response['userName']); 236 | $this->assertEquals($this->credentials['password'], $response['password']); 237 | $this->assertEquals($this->credentials['comment'], $response['comment']); 238 | 239 | $this->credentialsService->delete($credentials->getHash()); 240 | } 241 | 242 | /** 243 | * Test API, show credentials by hash 244 | */ 245 | public function testApiGetCredentialsInvalid() 246 | { 247 | 248 | $client = $this->createClient(); 249 | $client->request('GET', '/api/invalid', array(), array(), array('HTTPS' => true)); 250 | 251 | $response = json_decode($client->getResponse()->getContent(), true); 252 | $this->assertSame(410, $client->getResponse()->getStatusCode()); 253 | $this->assertTrue(is_array($response)); 254 | $this->assertTrue(array_key_exists('message', $response)); 255 | } 256 | 257 | /** 258 | * Test API, delete credentials by hash 259 | */ 260 | public function testApiDeleteCredentials() 261 | { 262 | $credentials = $this->credentialsService->save($this->credentials); 263 | 264 | $client = $this->createClient(); 265 | $client->request('GET', '/api/' . $credentials->getHash(), array(), array(), array('HTTPS' => true)); 266 | $this->assertTrue($client->getResponse()->isOk()); 267 | 268 | // Delete credential 269 | // Response is empty with statusCode 204 270 | $client->request('DELETE', '/api/' . $credentials->getHash(), array(), array(), array('HTTPS' => true)); 271 | $this->assertEquals(204, $client->getResponse()->getStatusCode()); 272 | 273 | // Visit the link page again and see of the entry is deleted 274 | $client->request('GET', '/api/' . $credentials->getHash(), array(), array(), array('HTTPS' => true)); 275 | $this->assertEquals(410, $client->getResponse()->getStatusCode()); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /web/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.5 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /web/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.5 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | .btn-default, 7 | .btn-primary, 8 | .btn-success, 9 | .btn-info, 10 | .btn-warning, 11 | .btn-danger { 12 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); 13 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 14 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 15 | } 16 | .btn-default:active, 17 | .btn-primary:active, 18 | .btn-success:active, 19 | .btn-info:active, 20 | .btn-warning:active, 21 | .btn-danger:active, 22 | .btn-default.active, 23 | .btn-primary.active, 24 | .btn-success.active, 25 | .btn-info.active, 26 | .btn-warning.active, 27 | .btn-danger.active { 28 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 29 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 30 | } 31 | .btn-default.disabled, 32 | .btn-primary.disabled, 33 | .btn-success.disabled, 34 | .btn-info.disabled, 35 | .btn-warning.disabled, 36 | .btn-danger.disabled, 37 | .btn-default[disabled], 38 | .btn-primary[disabled], 39 | .btn-success[disabled], 40 | .btn-info[disabled], 41 | .btn-warning[disabled], 42 | .btn-danger[disabled], 43 | fieldset[disabled] .btn-default, 44 | fieldset[disabled] .btn-primary, 45 | fieldset[disabled] .btn-success, 46 | fieldset[disabled] .btn-info, 47 | fieldset[disabled] .btn-warning, 48 | fieldset[disabled] .btn-danger { 49 | -webkit-box-shadow: none; 50 | box-shadow: none; 51 | } 52 | .btn-default .badge, 53 | .btn-primary .badge, 54 | .btn-success .badge, 55 | .btn-info .badge, 56 | .btn-warning .badge, 57 | .btn-danger .badge { 58 | text-shadow: none; 59 | } 60 | .btn:active, 61 | .btn.active { 62 | background-image: none; 63 | } 64 | .btn-default { 65 | text-shadow: 0 1px 0 #fff; 66 | background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); 67 | background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%); 68 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0)); 69 | background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); 70 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 71 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 72 | background-repeat: repeat-x; 73 | border-color: #dbdbdb; 74 | border-color: #ccc; 75 | } 76 | .btn-default:hover, 77 | .btn-default:focus { 78 | background-color: #e0e0e0; 79 | background-position: 0 -15px; 80 | } 81 | .btn-default:active, 82 | .btn-default.active { 83 | background-color: #e0e0e0; 84 | border-color: #dbdbdb; 85 | } 86 | .btn-default.disabled, 87 | .btn-default[disabled], 88 | fieldset[disabled] .btn-default, 89 | .btn-default.disabled:hover, 90 | .btn-default[disabled]:hover, 91 | fieldset[disabled] .btn-default:hover, 92 | .btn-default.disabled:focus, 93 | .btn-default[disabled]:focus, 94 | fieldset[disabled] .btn-default:focus, 95 | .btn-default.disabled.focus, 96 | .btn-default[disabled].focus, 97 | fieldset[disabled] .btn-default.focus, 98 | .btn-default.disabled:active, 99 | .btn-default[disabled]:active, 100 | fieldset[disabled] .btn-default:active, 101 | .btn-default.disabled.active, 102 | .btn-default[disabled].active, 103 | fieldset[disabled] .btn-default.active { 104 | background-color: #e0e0e0; 105 | background-image: none; 106 | } 107 | .btn-primary { 108 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); 109 | background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); 110 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); 111 | background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); 112 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); 113 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 114 | background-repeat: repeat-x; 115 | border-color: #245580; 116 | } 117 | .btn-primary:hover, 118 | .btn-primary:focus { 119 | background-color: #265a88; 120 | background-position: 0 -15px; 121 | } 122 | .btn-primary:active, 123 | .btn-primary.active { 124 | background-color: #265a88; 125 | border-color: #245580; 126 | } 127 | .btn-primary.disabled, 128 | .btn-primary[disabled], 129 | fieldset[disabled] .btn-primary, 130 | .btn-primary.disabled:hover, 131 | .btn-primary[disabled]:hover, 132 | fieldset[disabled] .btn-primary:hover, 133 | .btn-primary.disabled:focus, 134 | .btn-primary[disabled]:focus, 135 | fieldset[disabled] .btn-primary:focus, 136 | .btn-primary.disabled.focus, 137 | .btn-primary[disabled].focus, 138 | fieldset[disabled] .btn-primary.focus, 139 | .btn-primary.disabled:active, 140 | .btn-primary[disabled]:active, 141 | fieldset[disabled] .btn-primary:active, 142 | .btn-primary.disabled.active, 143 | .btn-primary[disabled].active, 144 | fieldset[disabled] .btn-primary.active { 145 | background-color: #265a88; 146 | background-image: none; 147 | } 148 | .btn-success { 149 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 150 | background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); 151 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); 152 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 153 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 154 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 155 | background-repeat: repeat-x; 156 | border-color: #3e8f3e; 157 | } 158 | .btn-success:hover, 159 | .btn-success:focus { 160 | background-color: #419641; 161 | background-position: 0 -15px; 162 | } 163 | .btn-success:active, 164 | .btn-success.active { 165 | background-color: #419641; 166 | border-color: #3e8f3e; 167 | } 168 | .btn-success.disabled, 169 | .btn-success[disabled], 170 | fieldset[disabled] .btn-success, 171 | .btn-success.disabled:hover, 172 | .btn-success[disabled]:hover, 173 | fieldset[disabled] .btn-success:hover, 174 | .btn-success.disabled:focus, 175 | .btn-success[disabled]:focus, 176 | fieldset[disabled] .btn-success:focus, 177 | .btn-success.disabled.focus, 178 | .btn-success[disabled].focus, 179 | fieldset[disabled] .btn-success.focus, 180 | .btn-success.disabled:active, 181 | .btn-success[disabled]:active, 182 | fieldset[disabled] .btn-success:active, 183 | .btn-success.disabled.active, 184 | .btn-success[disabled].active, 185 | fieldset[disabled] .btn-success.active { 186 | background-color: #419641; 187 | background-image: none; 188 | } 189 | .btn-info { 190 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 191 | background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 192 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); 193 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 194 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 195 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 196 | background-repeat: repeat-x; 197 | border-color: #28a4c9; 198 | } 199 | .btn-info:hover, 200 | .btn-info:focus { 201 | background-color: #2aabd2; 202 | background-position: 0 -15px; 203 | } 204 | .btn-info:active, 205 | .btn-info.active { 206 | background-color: #2aabd2; 207 | border-color: #28a4c9; 208 | } 209 | .btn-info.disabled, 210 | .btn-info[disabled], 211 | fieldset[disabled] .btn-info, 212 | .btn-info.disabled:hover, 213 | .btn-info[disabled]:hover, 214 | fieldset[disabled] .btn-info:hover, 215 | .btn-info.disabled:focus, 216 | .btn-info[disabled]:focus, 217 | fieldset[disabled] .btn-info:focus, 218 | .btn-info.disabled.focus, 219 | .btn-info[disabled].focus, 220 | fieldset[disabled] .btn-info.focus, 221 | .btn-info.disabled:active, 222 | .btn-info[disabled]:active, 223 | fieldset[disabled] .btn-info:active, 224 | .btn-info.disabled.active, 225 | .btn-info[disabled].active, 226 | fieldset[disabled] .btn-info.active { 227 | background-color: #2aabd2; 228 | background-image: none; 229 | } 230 | .btn-warning { 231 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 232 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 233 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); 234 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 235 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 236 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 237 | background-repeat: repeat-x; 238 | border-color: #e38d13; 239 | } 240 | .btn-warning:hover, 241 | .btn-warning:focus { 242 | background-color: #eb9316; 243 | background-position: 0 -15px; 244 | } 245 | .btn-warning:active, 246 | .btn-warning.active { 247 | background-color: #eb9316; 248 | border-color: #e38d13; 249 | } 250 | .btn-warning.disabled, 251 | .btn-warning[disabled], 252 | fieldset[disabled] .btn-warning, 253 | .btn-warning.disabled:hover, 254 | .btn-warning[disabled]:hover, 255 | fieldset[disabled] .btn-warning:hover, 256 | .btn-warning.disabled:focus, 257 | .btn-warning[disabled]:focus, 258 | fieldset[disabled] .btn-warning:focus, 259 | .btn-warning.disabled.focus, 260 | .btn-warning[disabled].focus, 261 | fieldset[disabled] .btn-warning.focus, 262 | .btn-warning.disabled:active, 263 | .btn-warning[disabled]:active, 264 | fieldset[disabled] .btn-warning:active, 265 | .btn-warning.disabled.active, 266 | .btn-warning[disabled].active, 267 | fieldset[disabled] .btn-warning.active { 268 | background-color: #eb9316; 269 | background-image: none; 270 | } 271 | .btn-danger { 272 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 273 | background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 274 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); 275 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 276 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 277 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 278 | background-repeat: repeat-x; 279 | border-color: #b92c28; 280 | } 281 | .btn-danger:hover, 282 | .btn-danger:focus { 283 | background-color: #c12e2a; 284 | background-position: 0 -15px; 285 | } 286 | .btn-danger:active, 287 | .btn-danger.active { 288 | background-color: #c12e2a; 289 | border-color: #b92c28; 290 | } 291 | .btn-danger.disabled, 292 | .btn-danger[disabled], 293 | fieldset[disabled] .btn-danger, 294 | .btn-danger.disabled:hover, 295 | .btn-danger[disabled]:hover, 296 | fieldset[disabled] .btn-danger:hover, 297 | .btn-danger.disabled:focus, 298 | .btn-danger[disabled]:focus, 299 | fieldset[disabled] .btn-danger:focus, 300 | .btn-danger.disabled.focus, 301 | .btn-danger[disabled].focus, 302 | fieldset[disabled] .btn-danger.focus, 303 | .btn-danger.disabled:active, 304 | .btn-danger[disabled]:active, 305 | fieldset[disabled] .btn-danger:active, 306 | .btn-danger.disabled.active, 307 | .btn-danger[disabled].active, 308 | fieldset[disabled] .btn-danger.active { 309 | background-color: #c12e2a; 310 | background-image: none; 311 | } 312 | .thumbnail, 313 | .img-thumbnail { 314 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 315 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 316 | } 317 | .dropdown-menu > li > a:hover, 318 | .dropdown-menu > li > a:focus { 319 | background-color: #e8e8e8; 320 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 321 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 322 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 323 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 324 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 325 | background-repeat: repeat-x; 326 | } 327 | .dropdown-menu > .active > a, 328 | .dropdown-menu > .active > a:hover, 329 | .dropdown-menu > .active > a:focus { 330 | background-color: #2e6da4; 331 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 332 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 333 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 334 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 335 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 336 | background-repeat: repeat-x; 337 | } 338 | .navbar-default { 339 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); 340 | background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%); 341 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8)); 342 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); 343 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 344 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 345 | background-repeat: repeat-x; 346 | border-radius: 4px; 347 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 348 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 349 | } 350 | .navbar-default .navbar-nav > .open > a, 351 | .navbar-default .navbar-nav > .active > a { 352 | background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 353 | background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 354 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); 355 | background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); 356 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); 357 | background-repeat: repeat-x; 358 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 359 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 360 | } 361 | .navbar-brand, 362 | .navbar-nav > li > a { 363 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25); 364 | } 365 | .navbar-inverse { 366 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); 367 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); 368 | background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); 369 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); 370 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 371 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 372 | background-repeat: repeat-x; 373 | border-radius: 4px; 374 | } 375 | .navbar-inverse .navbar-nav > .open > a, 376 | .navbar-inverse .navbar-nav > .active > a { 377 | background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); 378 | background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); 379 | background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); 380 | background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); 381 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); 382 | background-repeat: repeat-x; 383 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 384 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 385 | } 386 | .navbar-inverse .navbar-brand, 387 | .navbar-inverse .navbar-nav > li > a { 388 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); 389 | } 390 | .navbar-static-top, 391 | .navbar-fixed-top, 392 | .navbar-fixed-bottom { 393 | border-radius: 0; 394 | } 395 | @media (max-width: 767px) { 396 | .navbar .navbar-nav .open .dropdown-menu > .active > a, 397 | .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, 398 | .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { 399 | color: #fff; 400 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 401 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 402 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 403 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 404 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 405 | background-repeat: repeat-x; 406 | } 407 | } 408 | .alert { 409 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2); 410 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 411 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 412 | } 413 | .alert-success { 414 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 415 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 416 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); 417 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 418 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 419 | background-repeat: repeat-x; 420 | border-color: #b2dba1; 421 | } 422 | .alert-info { 423 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 424 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 425 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); 426 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 427 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 428 | background-repeat: repeat-x; 429 | border-color: #9acfea; 430 | } 431 | .alert-warning { 432 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 433 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 434 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); 435 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 436 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 437 | background-repeat: repeat-x; 438 | border-color: #f5e79e; 439 | } 440 | .alert-danger { 441 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 442 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 443 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); 444 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 445 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 446 | background-repeat: repeat-x; 447 | border-color: #dca7a7; 448 | } 449 | .progress { 450 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 451 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 452 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); 453 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 454 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 455 | background-repeat: repeat-x; 456 | } 457 | .progress-bar { 458 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); 459 | background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); 460 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); 461 | background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); 462 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); 463 | background-repeat: repeat-x; 464 | } 465 | .progress-bar-success { 466 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 467 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); 468 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); 469 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 470 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 471 | background-repeat: repeat-x; 472 | } 473 | .progress-bar-info { 474 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 475 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 476 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); 477 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 478 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 479 | background-repeat: repeat-x; 480 | } 481 | .progress-bar-warning { 482 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 483 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 484 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); 485 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 486 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 487 | background-repeat: repeat-x; 488 | } 489 | .progress-bar-danger { 490 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 491 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); 492 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); 493 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 494 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 495 | background-repeat: repeat-x; 496 | } 497 | .progress-bar-striped { 498 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 499 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 500 | background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 501 | } 502 | .list-group { 503 | border-radius: 4px; 504 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 505 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 506 | } 507 | .list-group-item.active, 508 | .list-group-item.active:hover, 509 | .list-group-item.active:focus { 510 | text-shadow: 0 -1px 0 #286090; 511 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); 512 | background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); 513 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); 514 | background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); 515 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); 516 | background-repeat: repeat-x; 517 | border-color: #2b669a; 518 | } 519 | .list-group-item.active .badge, 520 | .list-group-item.active:hover .badge, 521 | .list-group-item.active:focus .badge { 522 | text-shadow: none; 523 | } 524 | .panel { 525 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 526 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 527 | } 528 | .panel-default > .panel-heading { 529 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 530 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 531 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 532 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 533 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 534 | background-repeat: repeat-x; 535 | } 536 | .panel-primary > .panel-heading { 537 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 538 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 539 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 540 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 541 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 542 | background-repeat: repeat-x; 543 | } 544 | .panel-success > .panel-heading { 545 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 546 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 547 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); 548 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 549 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 550 | background-repeat: repeat-x; 551 | } 552 | .panel-info > .panel-heading { 553 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 554 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 555 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); 556 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 557 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 558 | background-repeat: repeat-x; 559 | } 560 | .panel-warning > .panel-heading { 561 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 562 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 563 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); 564 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 565 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 566 | background-repeat: repeat-x; 567 | } 568 | .panel-danger > .panel-heading { 569 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 570 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 571 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); 572 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 573 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 574 | background-repeat: repeat-x; 575 | } 576 | .well { 577 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 578 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 579 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); 580 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 581 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 582 | background-repeat: repeat-x; 583 | border-color: #dcdcdc; 584 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 585 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 586 | } 587 | /*# sourceMappingURL=bootstrap-theme.css.map */ 588 | -------------------------------------------------------------------------------- /web/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.5 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.5",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.5",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.5",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.5",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.5",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.5",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.5",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.5",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); --------------------------------------------------------------------------------