diff --git a/.gitignore b/.gitignore new file mode 100755 index 000000000..10034989c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +app/config/database.php diff --git a/LICENSE b/LICENSE new file mode 100755 index 000000000..8978733da --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2014, Sayak Banerjee +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100755 index 000000000..b9049613d --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,15 @@ +# Sticky Notes Privacy Notice + +Sticky Notes sends out statistical information to an external server that +includes **only** the following data: + +* Your site's URL +* Sticky Notes version +* Action type: install / update + +You can choose to not send your site's URL by setting the fullStats option +to FALSE in `app/config/app.php`. + +Please note that your site's URL will never be published and is purely used +for statistical purposes. It is stored securely and is encrypted with a +strong hash. diff --git a/README.md b/README.md new file mode 100755 index 000000000..a30c3b5e7 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# [Sticky Notes v1.9](http://sayakbanerjee.com/sticky-notes) [![Build Status](http://goo.gl/om3X8j)](http://goo.gl/DRaaX0) ![](http://goo.gl/5EXdK5) + +**Sticky Notes is currently not being maintained. The project may come back to life in future but there are no plans for development at the moment** + +Sticky notes is a powerful open-source pastebin application. [See it in action](http://paste.kde.org). + +License: [BSD 2-clause license](http://www.opensource.org/licenses/bsd-license.php). + + + + + + + + + + +
+ Download + + Installation guide + + Update guide + + API reference + + Services + + Plugins +
+ +© 2014 [Sayak Banerjee](http://sayakbanerjee.com). All rights reserved. [Privacy notice](http://goo.gl/Ba15QZ) + +[![](https://pledgie.com/campaigns/20549.png?skin_name=chrome)](http://goo.gl/oWyEG) diff --git a/app/commands/.gitkeep b/app/commands/.gitkeep new file mode 100755 index 000000000..e69de29bb diff --git a/app/commands/ConfigReader.php b/app/commands/ConfigReader.php new file mode 100755 index 000000000..a153ca21d --- /dev/null +++ b/app/commands/ConfigReader.php @@ -0,0 +1,90 @@ +option('group'); + + $key = $this->option('key'); + + // Both group and key are mandatory options + if ( ! empty($group) AND ! empty($key)) + { + $values = Site::config($group); + + if (isset($values->$key)) + { + $this->info($values->$key); + } + else + { + $this->error('No config data exists for given key.'); + } + } + else + { + $this->error('Insufficient arguments specified.'); + + $this->error('Usage: snconfig:get --group="..." --key="..."'); + } + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return array(); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('group', NULL, InputOption::VALUE_REQUIRED, 'Configuration group.', NULL), + array('key', NULL, InputOption::VALUE_REQUIRED, 'Configuration key.', NULL), + ); + } + +} diff --git a/app/commands/ConfigWriter.php b/app/commands/ConfigWriter.php new file mode 100755 index 000000000..77dab86ce --- /dev/null +++ b/app/commands/ConfigWriter.php @@ -0,0 +1,86 @@ +option('group'); + + $key = $this->option('key'); + + $value = $this->option('value'); + + // Group, key and value are mandatory options + if ( ! empty($group) AND ! empty($key) AND ! empty($value)) + { + Site::config($group, array($key => $value)); + + $this->info('Configuration data saved successfully. Please delete the contents of `app/storage/cache` folder for your changes to take effect.'); + } + else + { + $this->error('Insufficient arguments specified.'); + + $this->error('Usage: snconfig:get --group="..." --key="..." --value="..."'); + } + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return array(); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('group', NULL, InputOption::VALUE_REQUIRED, 'Configuration group.', NULL), + array('key', NULL, InputOption::VALUE_REQUIRED, 'Configuration key.', NULL), + array('value', NULL, InputOption::VALUE_REQUIRED, 'Configuration data.', NULL), + ); + } + +} diff --git a/app/config/antispam.php b/app/config/antispam.php new file mode 100755 index 000000000..9f205c5e9 --- /dev/null +++ b/app/config/antispam.php @@ -0,0 +1,41 @@ + array('ipban'), + + /* + |-------------------------------------------------------------------------- + | Scope declaration + |-------------------------------------------------------------------------- + | + | This value defines the scope of the antispam plugins. + | + */ + + 'scopes' => array( + + 'paste' => array('ipban', 'censor', 'noflood', 'php', 'stealth'), + + 'comment' => array('ipban', 'censor', 'noflood', 'php', 'akismet'), + + 'search' => array('ipban', 'noflood'), + + 'api_call' => array('ipban', 'censor', 'noflood', 'php'), + + ), + +); diff --git a/app/config/app.php b/app/config/app.php new file mode 100755 index 000000000..af47908a8 --- /dev/null +++ b/app/config/app.php @@ -0,0 +1,239 @@ + '1.9', + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + + 'debug' => FALSE, + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | your application so that it is used when running Artisan tasks. + | + */ + + 'url' => 'http://localhost', + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. We have gone + | ahead and set this to a sensible default for you out of the box. + | + */ + + 'timezone' => 'UTC', + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by the translation service provider. You are free to set this value + | to any of the locales which will be supported by the application. + | + */ + + 'locale' => 'en', + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is used by the Illuminate encrypter service and should be set + | to a random, 32 character string, otherwise these encrypted strings + | will not be safe. Please do this before deploying an application! + | + */ + + 'key' => 'pKMSkxfvJnNg0c0soBcg7oRYa6NMrv31', + + /* + |-------------------------------------------------------------------------- + | Stats collector Configuration + |-------------------------------------------------------------------------- + | + | This flag is used to determine if your site's URL will be included + | in the statistics report. See PRIVACY.md for details on the information + | that is sent over to the statistics server. + | + | Note: As a note of thanks to the developer, please keep this as true. + | Your site's URL will NEVER be published and is kept secure. + | + */ + + 'fullStats' => TRUE, + + /* + |-------------------------------------------------------------------------- + | Autoloaded Service Providers + |-------------------------------------------------------------------------- + | + | The service providers listed here will be automatically loaded on the + | request to your application. Feel free to add your own services to + | this array to grant expanded functionality to your applications. + | + */ + + 'providers' => array( + + // Illuminate + 'Illuminate\Foundation\Providers\ArtisanServiceProvider', + 'Illuminate\Auth\AuthServiceProvider', + 'Illuminate\Cache\CacheServiceProvider', + 'Illuminate\Session\CommandsServiceProvider', + 'Illuminate\Foundation\Providers\ConsoleSupportServiceProvider', + 'Illuminate\Routing\ControllerServiceProvider', + 'Illuminate\Cookie\CookieServiceProvider', + 'Illuminate\Database\DatabaseServiceProvider', + 'Illuminate\Encryption\EncryptionServiceProvider', + 'Illuminate\Filesystem\FilesystemServiceProvider', + 'Illuminate\Hashing\HashServiceProvider', + 'Illuminate\Html\HtmlServiceProvider', + 'Illuminate\Log\LogServiceProvider', + 'Illuminate\Mail\MailServiceProvider', + 'Illuminate\Database\MigrationServiceProvider', + 'Illuminate\Pagination\PaginationServiceProvider', + 'Illuminate\Queue\QueueServiceProvider', + 'Illuminate\Redis\RedisServiceProvider', + 'Illuminate\Remote\RemoteServiceProvider', + 'Illuminate\Auth\Reminders\ReminderServiceProvider', + 'Illuminate\Database\SeedServiceProvider', + 'Illuminate\Session\SessionServiceProvider', + 'Illuminate\Translation\TranslationServiceProvider', + 'Illuminate\Validation\ValidationServiceProvider', + 'Illuminate\View\ViewServiceProvider', + 'Illuminate\Workbench\WorkbenchServiceProvider', + + // Sayakb + 'Sayakb\Akismet\AkismetServiceProvider', + 'Sayakb\Captcha\CaptchaServiceProvider', + + // StickyNotes + 'StickyNotes\Hashing\HashServiceProvider', + + ), + + /* + |-------------------------------------------------------------------------- + | Service Provider Manifest + |-------------------------------------------------------------------------- + | + | The service provider manifest is used by Laravel to lazy load service + | providers which are not needed for each request, as well to keep a + | list of all of the services. Here, you may set its storage spot. + | + */ + + 'manifest' => storage_path().'/meta', + + /* + |-------------------------------------------------------------------------- + | Class Aliases + |-------------------------------------------------------------------------- + | + | This array of class aliases will be registered when this application + | is started. However, feel free to register as many as you wish as + | the aliases are "lazy" loaded so they don't hinder performance. + | + */ + + 'aliases' => array( + + // Illuminate + 'App' => 'Illuminate\Support\Facades\App', + 'Artisan' => 'Illuminate\Support\Facades\Artisan', + 'Blade' => 'Illuminate\Support\Facades\Blade', + 'ClassLoader' => 'Illuminate\Support\ClassLoader', + 'Config' => 'Illuminate\Support\Facades\Config', + 'Controller' => 'Illuminate\Routing\Controller', + 'Crypt' => 'Illuminate\Support\Facades\Crypt', + 'DB' => 'Illuminate\Support\Facades\DB', + 'Eloquent' => 'Illuminate\Database\Eloquent\Model', + 'Event' => 'Illuminate\Support\Facades\Event', + 'File' => 'Illuminate\Support\Facades\File', + 'Form' => 'Illuminate\Support\Facades\Form', + 'Guard' => 'Illuminate\Auth\Guard', + 'Hash' => 'Illuminate\Support\Facades\Hash', + 'HTML' => 'Illuminate\Support\Facades\HTML', + 'Input' => 'Illuminate\Support\Facades\Input', + 'Lang' => 'Illuminate\Support\Facades\Lang', + 'Log' => 'Illuminate\Support\Facades\Log', + 'Paginator' => 'Illuminate\Support\Facades\Paginator', + 'Password' => 'Illuminate\Support\Facades\Password', + 'Queue' => 'Illuminate\Support\Facades\Queue', + 'Redis' => 'Illuminate\Support\Facades\Redis', + 'Request' => 'Illuminate\Support\Facades\Request', + 'Route' => 'Illuminate\Support\Facades\Route', + 'Schema' => 'Illuminate\Support\Facades\Schema', + 'Seeder' => 'Illuminate\Database\Seeder', + 'SSH' => 'Illuminate\Support\Facades\SSH', + 'Str' => 'Illuminate\Support\Str', + 'URL' => 'Illuminate\Support\Facades\URL', + 'Validator' => 'Illuminate\Support\Facades\Validator', + + // PHPDiff + 'DiffRenderer' => 'Diff_Renderer_Html_SideBySide', + + // Sayakb + 'Captcha' => 'Sayakb\Captcha\Facades\Captcha', + + // StickyNotes + 'API' => 'StickyNotes\API', + 'Antispam' => 'StickyNotes\Antispam', + 'Auth' => 'StickyNotes\Auth', + 'Cache' => 'StickyNotes\Cache', + 'Config' => 'StickyNotes\Config', + 'Cookie' => 'StickyNotes\Cookie', + 'Cron' => 'StickyNotes\Cron', + 'Feed' => 'StickyNotes\Feed', + 'Highlighter' => 'StickyNotes\Highlighter', + 'Mail' => 'StickyNotes\Mail', + 'PHPass' => 'StickyNotes\PHPass', + 'PHPDiff' => 'StickyNotes\PHPDiff', + 'Redirect' => 'StickyNotes\Redirect', + 'Response' => 'StickyNotes\Response', + 'Service' => 'StickyNotes\Service', + 'Session' => 'StickyNotes\Session', + 'Setup' => 'StickyNotes\Setup', + 'System' => 'StickyNotes\System', + 'View' => 'StickyNotes\View', + + 'StickyNotesDBUserProvider' => 'StickyNotes\Auth\StickyNotesDBUserProvider', + 'StickyNotesLDAPUserProvider' => 'StickyNotes\Auth\StickyNotesLDAPUserProvider', + 'StickyNotesOAuthUserProvider' => 'StickyNotes\Auth\StickyNotesOAuthUserProvider', + + ), + +); diff --git a/app/config/auth.php b/app/config/auth.php new file mode 100755 index 000000000..1167dff45 --- /dev/null +++ b/app/config/auth.php @@ -0,0 +1,65 @@ + 'stickynotes'.Site::config('auth')->method, + + /* + |-------------------------------------------------------------------------- + | Authentication Model + |-------------------------------------------------------------------------- + | + | When using the "Eloquent" authentication driver, we need to know which + | Eloquent model should be used to retrieve your users. Of course, it + | is often just the "User" model but you may use whatever you like. + | + */ + + 'model' => 'User', + + /* + |-------------------------------------------------------------------------- + | Authentication Table + |-------------------------------------------------------------------------- + | + | When using the "Database" authentication driver, we need to know which + | table should be used to retrieve your users. We have chosen a basic + | default value but you may easily change it to any table you like. + | + */ + + 'table' => 'users', + + /* + |-------------------------------------------------------------------------- + | Password Reminder Settings + |-------------------------------------------------------------------------- + | + | Here you may set the settings for password reminders, including a view + | that should be used as your password reminder e-mail. You will also + | be able to set the name of the table that holds the reset tokens. + | + */ + + 'reminder' => array( + + 'email' => 'emails.auth.reminder', + + 'table' => 'password_reminders', + + 'expire' => 60, + + ), + +); diff --git a/app/config/cache.php b/app/config/cache.php new file mode 100755 index 000000000..c253dc0e2 --- /dev/null +++ b/app/config/cache.php @@ -0,0 +1,89 @@ + 'file', + + /* + |-------------------------------------------------------------------------- + | File Cache Location + |-------------------------------------------------------------------------- + | + | When using the "file" cache driver, we need a location where the cache + | files may be stored. A sensible default has been specified, but you + | are free to change it to any other place on disk that you desire. + | + */ + + 'path' => storage_path().'/cache', + + /* + |-------------------------------------------------------------------------- + | Database Cache Connection + |-------------------------------------------------------------------------- + | + | When using the "database" cache driver you may specify the connection + | that should be used to store the cached items. When this option is + | null the default database connection will be utilized for cache. + | + */ + + 'connection' => null, + + /* + |-------------------------------------------------------------------------- + | Database Cache Table + |-------------------------------------------------------------------------- + | + | When using the "database" cache driver we need to know the table that + | should be used to store the cached items. A default table name has + | been provided but you're free to change it however you deem fit. + | + */ + + 'table' => 'cache', + + /* + |-------------------------------------------------------------------------- + | Memcached Servers + |-------------------------------------------------------------------------- + | + | Now you may specify an array of your Memcached servers that should be + | used when utilizing the Memcached cache driver. All of the servers + | should contain a value for "host", "port", and "weight" options. + | + */ + + 'memcached' => array( + + array('host' => '127.0.0.1', 'port' => 11211, 'weight' => 100), + + ), + + /* + |-------------------------------------------------------------------------- + | Cache Key Prefix + |-------------------------------------------------------------------------- + | + | When utilizing a RAM based store such as APC or Memcached, there might + | be other applications utilizing the same cache. So, we'll specify a + | value to get prefixed to all our keys so we can avoid collisions. + | + */ + + 'prefix' => 'stickynotes', + +); diff --git a/app/config/compile.php b/app/config/compile.php new file mode 100755 index 000000000..54d7185bf --- /dev/null +++ b/app/config/compile.php @@ -0,0 +1,18 @@ + PDO::FETCH_CLASS, + + /* + |-------------------------------------------------------------------------- + | Default Database Connection Name + |-------------------------------------------------------------------------- + | + | Here you may specify which of the database connections below you wish + | to use as your default connection for all database work. Of course + | you may use many connections at once using the Database library. + | + */ + + 'default' => 'mysql', + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Here are each of the database connections setup for your application. + | Of course, examples of configuring each database platform that is + | supported by Laravel is shown below to make development simple. + | + | + | All database work in Laravel is done through the PHP PDO facilities + | so make sure you have the driver for your particular database of + | choice installed on your machine before you begin development. + | + */ + + 'connections' => array( + + 'sqlite' => array( + 'driver' => 'sqlite', + 'database' => __DIR__.'/../database/production.sqlite', + 'prefix' => '', + ), + + 'mysql' => array( + 'driver' => 'mysql', + 'host' => 'localhost', + 'database' => 'database', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', + ), + + 'pgsql' => array( + 'driver' => 'pgsql', + 'host' => 'localhost', + 'database' => 'database', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', + 'prefix' => '', + 'schema' => 'public', + ), + + 'sqlsrv' => array( + 'driver' => 'sqlsrv', + 'host' => 'localhost', + 'database' => 'database', + 'username' => 'root', + 'password' => '', + 'prefix' => '', + ), + + ), + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk have not actually be run in the databases. + | + */ + + 'migrations' => 'migrations', + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer set of commands than a typical key-value systems + | such as APC or Memcached. Laravel makes it easy to dig right in. + | + */ + + 'redis' => array( + + 'cluster' => true, + + 'default' => array( + 'host' => '127.0.0.1', + 'port' => 6379, + 'database' => 0, + ), + + ), + +); diff --git a/app/config/default.php b/app/config/default.php new file mode 100755 index 000000000..5484a113f --- /dev/null +++ b/app/config/default.php @@ -0,0 +1,107 @@ + (object) array( + 'fqdn' => 'localhost', + 'title' => 'Sticky Notes', + 'perPage' => '15', + 'copyright' => '', + 'lang' => 'en', + 'version' => '0.0', + 'preMigrate' => '0', + 'pasteAge' => '1800', + 'skin' => 'bootstrap', + 'proxy' => '0', + 'pasteVisibility' => 'default', + 'guestPosts' => '1', + 'pasteSearch' => '1', + 'comments' => '1', + 'noExpire' => 'all', + 'csrf' => '1', + 'statsTTL' => '-6 months', + 'statsDisplay' => '-1 month', + 'ajaxNav' => '1', + 'share' => '1', + 'bannerTop' => '', + 'bannerBottom' => '', + 'htmlKeys' => 'banner_top|banner_bottom', + 'allowedTags' => '
      '. + '

    1. ', + 'maxPasteSize' => '0', + 'allowPasteDel' => '1', + 'allowAttachment' => '1', + 'flagPaste' => 'user', + ), + + 'antispam' => (object) array( + 'services' => 'censor|noflood', + 'phpKey' => '', + 'phpDays' => '90', + 'phpScore' => '50', + 'phpType' => '2', + 'stealthCount' => '3', + 'censor' => '', + 'floodThreshold' => '5', + 'akismetKey' => '', + ), + + 'mail' => (object) array( + 'driver' => 'smtp', + 'host' => 'localhost', + 'port' => '25', + 'address' => 'webmaster@sticky.notes', + 'name' => 'Webmaster', + 'encryption' => 'ssl', + 'username' => '', + 'password' => '', + 'sendmail' => '', + 'pretend' => '0', + ), + + 'auth' => (object) array( + 'method' => 'db', + 'noForm' => 'oauth', + 'bannerText' => '', + 'infoUrl' => '', + 'infoUrlText' => '', + 'dbAllowReg' => '1', + 'dbShowCaptcha' => '1', + 'ldapServer' => '127.0.0.1', + 'ldapPort' => '389', + 'ldapBaseDn' => '', + 'ldapUid' => '', + 'ldapFilter' => '', + 'ldapAdmin' => '', + 'ldapUserDn' => '', + 'ldapPassword' => '', + 'oauthGoogleId' => '', + 'oauthGoogleSecret' => '', + 'oauthGoogleAdmins' => '', + ), + + 'services' => (object) array( + 'googleApiKey' => '', + 'googleAnalyticsId' => '', + 'googleUrlShortener' => 'https://www.googleapis.com/urlshortener/v1/url?key=%s', + 'googleUrlOAuth' => 'https://www.googleapis.com/oauth2/v1/userinfo', + 'statsUrl' => 'http://sites.sayakbanerjee.com/sn-stats/?a=submit', + 'docsUrl' => 'http://sayakbanerjee.com/sticky-notes', + 'updateUrl' => 'http://sayakb.github.io/sticky-notes/version/', + 'downloadUrl' => 'http://sayakb.github.io/sticky-notes/download/', + ), + +); diff --git a/app/config/expire.php b/app/config/expire.php new file mode 100755 index 000000000..d5beb0151 --- /dev/null +++ b/app/config/expire.php @@ -0,0 +1,31 @@ + array('expire_30mins', TRUE), + + '21600' => array('expire_6hrs', TRUE), + + '86400' => array('expire_1day', TRUE), + + '604800' => array('expire_1week', TRUE), + + '2592000' => array('expire_1month', TRUE), + + '31536000' => array('expire_1year', TRUE), + + '0' => array('expire_forever', Paste::noExpire()), + +); diff --git a/app/config/mail.php b/app/config/mail.php new file mode 100755 index 000000000..b7b78eeab --- /dev/null +++ b/app/config/mail.php @@ -0,0 +1,127 @@ + Site::config('mail')->driver, + + /* + |-------------------------------------------------------------------------- + | SMTP Host Address + |-------------------------------------------------------------------------- + | + | Here you may provide the host address of the SMTP server used by your + | applications. A default option is provided that is compatible with + | the Postmark mail service, which will provide reliable delivery. + | + */ + + 'host' => Site::config('mail')->host, + + /* + |-------------------------------------------------------------------------- + | SMTP Host Port + |-------------------------------------------------------------------------- + | + | This is the SMTP port used by your application to delivery e-mails to + | users of your application. Like the host we have set this value to + | stay compatible with the Postmark e-mail application by default. + | + */ + + 'port' => Site::config('mail')->port, + + /* + |-------------------------------------------------------------------------- + | Global "From" Address + |-------------------------------------------------------------------------- + | + | You may wish for all e-mails sent by your application to be sent from + | the same address. Here, you may specify a name and address that is + | used globally for all e-mails that are sent by your application. + | + */ + + 'from' => array( + 'address' => Site::config('mail')->address, + 'name' => Site::config('mail')->name, + ), + + /* + |-------------------------------------------------------------------------- + | E-Mail Encryption Protocol + |-------------------------------------------------------------------------- + | + | Here you may specify the encryption protocol that should be used when + | the application send e-mail messages. A sensible default using the + | transport layer security protocol should provide great security. + | + */ + + 'encryption' => Site::config('mail')->encryption, + + /* + |-------------------------------------------------------------------------- + | SMTP Server Username + |-------------------------------------------------------------------------- + | + | If your SMTP server requires a username for authentication, you should + | set it here. This will get used to authenticate with your server on + | connection. You may also set the "password" value below this one. + | + */ + + 'username' => Site::config('mail')->username, + + /* + |-------------------------------------------------------------------------- + | SMTP Server Password + |-------------------------------------------------------------------------- + | + | Here you may set the password required by your SMTP server to send out + | messages from your application. This will be given to the server on + | connection so that the application will be able to send messages. + | + */ + + 'password' => Site::config('mail')->password, + + /* + |-------------------------------------------------------------------------- + | Sendmail System Path + |-------------------------------------------------------------------------- + | + | When using the "sendmail" driver to send e-mails, we will need to know + | the path to where Sendmail lives on this server. A default path has + | been provided here, which will work well on most of your systems. + | + */ + + 'sendmail' => Site::config('mail')->sendmail, + + /* + |-------------------------------------------------------------------------- + | Mail "Pretend" + |-------------------------------------------------------------------------- + | + | When this option is enabled, e-mail will not actually be sent over the + | web and will instead be written to your application's logs files so + | you may inspect the message. This is great for local development. + | + */ + + 'pretend' => Site::config('mail')->pretend, + +); diff --git a/app/config/menus.php b/app/config/menus.php new file mode 100755 index 000000000..d47b048f2 --- /dev/null +++ b/app/config/menus.php @@ -0,0 +1,170 @@ + array( + | 'label' => 'global.lang_key', + | 'icon' => 'glyphicon' // Icon is optional + | ) + | + | Properties to be added for each menu: + | * _showLogin : Whether or not to append login/logout link + | * _exact : Whether an exact match should be done for determining the + | currently active link + | + | Optional properties in each item: + | * icon : Determines the glyphicon for that item + | * visible : This can be used to bind the visibility of an item to a + | user role or a site config. You can use ! to invert a + | flag. Multiple flags with 'OR' relationships should be + | separated by a | + | + */ + + 'navigation' => array( + + '_showLogin' => TRUE, + + '_exact' => FALSE, + + 'all' => array( + 'label' => 'global.archives', + 'icon' => 'list', + 'visible' => '!config.pasteVisibility=private|role.admin' + ), + + 'trending' => array( + 'label' => 'global.trending', + 'icon' => 'fire', + 'visible' => '!config.pasteVisibility=private|role.admin' + ), + + 'docs' => array( + 'label' => 'global.docs', + 'icon' => 'book' + ), + + 'user/profile' => array( + 'label' => 'global.my_profile', + 'icon' => 'flag', + 'visible' => 'role.user' + ), + + 'admin' => array( + 'label' => 'global.siteadmin', + 'icon' => 'cog', + 'visible' => 'role.admin' + ), + + ), + + 'filters' => array( + + '_showLogin' => FALSE, + + '_exact' => TRUE, + + 'trending' => array( + 'label' => 'list.filter_now', + 'visible' => '!config.pasteVisibility=private|role.admin' + ), + + 'trending/week' => array( + 'label' => 'list.filter_week', + 'visible' => '!config.pasteVisibility=private|role.admin' + ), + + 'trending/month' => array( + 'label' => 'list.filter_month', + 'visible' => '!config.pasteVisibility=private|role.admin' + ), + + 'trending/year' => array( + 'label' => 'list.filter_year', + 'visible' => '!config.pasteVisibility=private|role.admin' + ), + + 'trending/all' => array( + 'label' => 'list.filter_all', + 'visible' => '!config.pasteVisibility=private|role.admin' + ), + + ), + + 'admin' => array( + + '_showLogin' => FALSE, + + '_exact' => FALSE, + + 'admin/dashboard' => array( + 'label' => 'admin.dashboard', + 'icon' => 'home', + 'visible' => 'role.admin' + ), + + 'admin/paste' => array( + 'label' => 'admin.manage_pastes', + 'icon' => 'file', + 'visible' => 'role.admin' + ), + + 'admin/user' => array( + 'label' => 'admin.manage_users', + 'icon' => 'user', + 'visible' => 'role.admin' + ), + + 'admin/ban' => array( + 'label' => 'admin.ban_an_ip', + 'icon' => 'ban-circle', + 'visible' => 'role.admin' + ), + + 'admin/mail' => array( + 'label' => 'admin.mail_settings', + 'icon' => 'envelope', + 'visible' => 'role.admin' + ), + + 'admin/auth' => array( + 'label' => 'admin.authentication', + 'icon' => 'lock', + 'visible' => 'role.admin' + ), + + 'admin/antispam' => array( + 'label' => 'admin.spam_filters', + 'icon' => 'screenshot', + 'visible' => 'role.admin' + ), + + 'admin/skin' => array( + 'label' => 'admin.skin_chooser', + 'icon' => 'picture', + 'visible' => 'role.admin' + ), + + 'admin/services' => array( + 'label' => 'admin.services', + 'icon' => 'briefcase', + 'visible' => 'role.admin' + ), + + 'admin/site' => array( + 'label' => 'admin.site_settings', + 'icon' => 'wrench', + 'visible' => 'role.admin' + ), + + ), + +); diff --git a/app/config/packages/.gitkeep b/app/config/packages/.gitkeep new file mode 100755 index 000000000..e69de29bb diff --git a/app/config/queue.php b/app/config/queue.php new file mode 100755 index 000000000..6c0fa5234 --- /dev/null +++ b/app/config/queue.php @@ -0,0 +1,82 @@ + 'sync', + + /* + |-------------------------------------------------------------------------- + | Queue Connections + |-------------------------------------------------------------------------- + | + | Here you may configure the connection information for each server that + | is used by your application. A default configuration has been added + | for each back-end shipped with Laravel. You are free to add more. + | + */ + + 'connections' => array( + + 'sync' => array( + 'driver' => 'sync', + ), + + 'beanstalkd' => array( + 'driver' => 'beanstalkd', + 'host' => 'localhost', + 'queue' => 'default', + ), + + 'sqs' => array( + 'driver' => 'sqs', + 'key' => 'your-public-key', + 'secret' => 'your-secret-key', + 'queue' => 'your-queue-url', + 'region' => 'us-east-1', + ), + + 'iron' => array( + 'driver' => 'iron', + 'project' => 'your-project-id', + 'token' => 'your-token', + 'queue' => 'your-queue-name', + ), + + 'redis' => array( + 'driver' => 'redis', + 'queue' => 'default', + ), + + ), + + /* + |-------------------------------------------------------------------------- + | Failed Queue Jobs + |-------------------------------------------------------------------------- + | + | These options configure the behavior of failed queue job logging so you + | can control which database and table are used to store the jobs that + | have failed. You may change them to any database / table you wish. + | + */ + + 'failed' => array( + + 'database' => 'mysql', 'table' => 'failed_jobs', + + ), + +); diff --git a/app/config/remote.php b/app/config/remote.php new file mode 100755 index 000000000..2169c434b --- /dev/null +++ b/app/config/remote.php @@ -0,0 +1,59 @@ + 'production', + + /* + |-------------------------------------------------------------------------- + | Remote Server Connections + |-------------------------------------------------------------------------- + | + | These are the servers that will be accessible via the SSH task runner + | facilities of Laravel. This feature radically simplifies executing + | tasks on your servers, such as deploying out these applications. + | + */ + + 'connections' => array( + + 'production' => array( + 'host' => '', + 'username' => '', + 'password' => '', + 'key' => '', + 'keyphrase' => '', + 'root' => '/var/www', + ), + + ), + + /* + |-------------------------------------------------------------------------- + | Remote Server Groups + |-------------------------------------------------------------------------- + | + | Here you may list connections under a single group name, which allows + | you to easily access all of the servers at once using a short name + | that is extremely easy to remember, such as "web" or "database". + | + */ + + 'groups' => array( + + 'web' => array('production') + + ), + +); diff --git a/app/config/schema.php b/app/config/schema.php new file mode 100755 index 000000000..c9aafaa78 --- /dev/null +++ b/app/config/schema.php @@ -0,0 +1,811 @@ + array( + + 'tables' => array( + + 'config' => array( + + (object) array( + 'name' => 'id', + 'type' => 'increments', + ), + + (object) array( + 'name' => 'group', + 'type' => 'string', + 'length' => 30, + ), + + (object) array( + 'name' => 'key', + 'type' => 'string', + 'length' => 30, + ), + + (object) array( + 'name' => 'value', + 'type' => 'text', + 'nullable' => TRUE, + ), + + ), + + 'ipbans' => array( + + (object) array( + 'name' => 'ip', + 'type' => 'string', + 'length' => 50, + ), + + (object) array( + 'name' => 'ip', + 'type' => 'primary', + ), + + ), + + 'main' => array( + + (object) array( + 'name' => 'id', + 'type' => 'increments', + ), + + (object) array( + 'name' => 'urlkey', + 'type' => 'string', + 'length' => 9, + 'default' => '', + ), + + (object) array( + 'name' => 'urlkey', + 'type' => 'index', + ), + + (object) array( + 'name' => 'author_id', + 'type' => 'integer', + 'nullable' => TRUE, + 'default' => NULL, + ), + + (object) array( + 'name' => 'author', + 'type' => 'string', + 'length' => 50, + 'nullable' => TRUE, + 'default' => '', + ), + + (object) array( + 'name' => 'author', + 'type' => 'index', + ), + + (object) array( + 'name' => 'project', + 'type' => 'string', + 'length' => 50, + 'nullable' => TRUE, + 'default' => '', + ), + + (object) array( + 'name' => 'project', + 'type' => 'index', + ), + + (object) array( + 'name' => 'timestamp', + 'type' => 'integer', + ), + + (object) array( + 'name' => 'expire', + 'type' => 'integer', + ), + + (object) array( + 'name' => 'title', + 'type' => 'string', + 'length' => 25, + 'nullable' => TRUE, + 'default' => '', + ), + + (object) array( + 'name' => 'data', + 'type' => 'longText', + ), + + (object) array( + 'name' => 'language', + 'type' => 'string', + 'length' => 50, + 'default' => 'text', + ), + + (object) array( + 'name' => 'password', + 'type' => 'string', + 'length' => 60, + ), + + (object) array( + 'name' => 'salt', + 'type' => 'string', + 'length' => 5, + ), + + (object) array( + 'name' => 'private', + 'type' => 'boolean', + 'default' => 0, + ), + + (object) array( + 'name' => 'private', + 'type' => 'index', + ), + + (object) array( + 'name' => 'hash', + 'type' => 'string', + 'length' => 12, + ), + + (object) array( + 'name' => 'ip', + 'type' => 'string', + 'length' => 50, + ), + + (object) array( + 'name' => 'hits', + 'type' => 'integer', + 'default' => 0, + ), + + (object) array( + 'name' => 'flagged', + 'type' => 'boolean', + 'default' => 0, + ), + + (object) array( + 'name' => 'attachment', + 'type' => 'boolean', + 'default' => 0, + ), + + ), + + 'revisions' => array( + + (object) array( + 'name' => 'id', + 'type' => 'increments', + ), + + (object) array( + 'name' => 'paste_id', + 'type' => 'integer', + ), + + (object) array( + 'name' => 'urlkey', + 'type' => 'string', + 'length' => 9, + ), + + (object) array( + 'name' => 'author', + 'type' => 'string', + 'length' => 50, + 'nullable' => TRUE, + 'default' => NULL, + ), + + (object) array( + 'name' => 'timestamp', + 'type' => 'integer', + ), + + ), + + 'comments' => array( + + (object) array( + 'name' => 'id', + 'type' => 'increments', + ), + + (object) array( + 'name' => 'paste_id', + 'type' => 'integer', + ), + + (object) array( + 'name' => 'data', + 'type' => 'text', + ), + + (object) array( + 'name' => 'author', + 'type' => 'string', + 'length' => 50, + 'nullable' => TRUE, + 'default' => NULL, + ), + + (object) array( + 'name' => 'timestamp', + 'type' => 'integer', + ), + + ), + + 'users' => array( + + (object) array( + 'name' => 'id', + 'type' => 'increments', + ), + + (object) array( + 'name' => 'username', + 'type' => 'string', + 'length' => 50, + ), + + (object) array( + 'name' => 'username', + 'type' => 'index', + ), + + (object) array( + 'name' => 'password', + 'type' => 'string', + 'length' => 60, + ), + + (object) array( + 'name' => 'salt', + 'type' => 'string', + 'length' => 5, + ), + + (object) array( + 'name' => 'remember_token', + 'type' => 'string', + 'length' => 60, + 'default' => '', + ), + + (object) array( + 'name' => 'email', + 'type' => 'string', + 'length' => 100, + ), + + (object) array( + 'name' => 'dispname', + 'type' => 'string', + 'length' => 100, + 'nullable' => TRUE, + 'default' => '', + ), + + (object) array( + 'name' => 'admin', + 'type' => 'boolean', + 'default' => 0, + ), + + (object) array( + 'name' => 'type', + 'type' => 'string', + 'length' => 10, + 'default' => 'db', + ), + + (object) array( + 'name' => 'active', + 'type' => 'boolean', + 'default' => 1, + ), + + ), + + 'statistics' => array( + + (object) array( + 'name' => 'id', + 'type' => 'increments', + ), + + (object) array( + 'name' => 'date', + 'type' => 'date', + ), + + (object) array( + 'name' => 'web', + 'type' => 'integer', + 'default' => 0, + ), + + (object) array( + 'name' => 'api', + 'type' => 'integer', + 'default' => 0, + ), + + ), + + ), + + 'closure' => function() + { + + // Get the FQDN for the server + $fqdn = getenv('SERVER_NAME'); + + // Generate user credentials + $username = 'admin'; + + $password = str_random(8); + + // Save the user info to session + Session::put('install.username', $username); + + Session::put('install.password', $password); + + // Create the admin user + $user = new User; + + $user->username = $username; + $user->email = $username.'@'.$fqdn; + $user->salt = str_random(5); + $user->password = PHPass::make()->create($password, $user->salt); + $user->admin = 1; + + $user->save(); + + // Insert fqdn and app version to site config + Site::config('general', array( + 'fqdn' => $fqdn, + 'version' => Config::get('app.version'), + )); + + }, + + ), + + 'update' => array( + + '0.4' => array( + + 'newTables' => array( + + 'config' => array( + + (object) array( + 'name' => 'id', + 'type' => 'increments', + ), + + (object) array( + 'name' => 'group', + 'type' => 'string', + 'length' => 30, + ), + + (object) array( + 'name' => 'key', + 'type' => 'string', + 'length' => 30, + ), + + (object) array( + 'name' => 'value', + 'type' => 'text', + 'nullable' => TRUE, + ), + + ), + + 'revisions' => array( + + (object) array( + 'name' => 'id', + 'type' => 'increments', + ), + + (object) array( + 'name' => 'paste_id', + 'type' => 'integer', + ), + + (object) array( + 'name' => 'urlkey', + 'type' => 'string', + 'length' => 9, + ), + + (object) array( + 'name' => 'author', + 'type' => 'string', + 'length' => 50, + 'nullable' => TRUE, + 'default' => NULL, + ), + + (object) array( + 'name' => 'timestamp', + 'type' => 'integer', + ), + + ), + + ), + + 'modifyTables' => array( + + 'main' => array( + + (object) array( + 'name' => 'author_id', + 'type' => 'integer', + 'nullable' => TRUE, + 'default' => NULL, + ), + + ), + + 'users' => array( + + (object) array( + 'name' => 'admin', + 'type' => 'boolean', + 'default' => 0, + ), + + (object) array( + 'name' => 'type', + 'type' => 'string', + 'length' => 10, + 'default' => 'db', + ), + + (object) array( + 'name' => 'active', + 'type' => 'boolean', + 'default' => 1, + ), + + (object) array( + 'name' => 'sid', + 'type' => 'dropColumn', + ), + + (object) array( + 'name' => 'lastlogin', + 'type' => 'dropColumn', + ), + + ), + + ), + + 'closure' => function() + { + + // Get the table prefix + $dbPrefix = DB::getTablePrefix(); + + // Change the hash datatype to VARCHAR(12) + // A raw query is fine here as 0.4 supported MySQL only + DB::update("ALTER TABLE {$dbPrefix}main MODIFY COLUMN hash VARCHAR(12) NOT NULL"); + + // Change the urlkey to VARCHAR(9), as we prepent 'p' now + DB::update("ALTER TABLE {$dbPrefix}main MODIFY COLUMN urlkey VARCHAR(9) NOT NULL DEFAULT ''"); + + // Prepend 'p' to non-empty URL keys + DB::update("UPDATE {$dbPrefix}main SET urlkey = CONCAT('p', urlkey) WHERE urlkey <> ''"); + + // Setup admin = true for all users because + // for 0.4, only admins could log in + DB::update("UPDATE {$dbPrefix}users SET admin = 1"); + + // Set user type = ldap for users without passwords + DB::update("UPDATE {$dbPrefix}users SET type = 'ldap' WHERE password = ''"); + + // Drop the session table, we no longer need it + DB::update("DROP TABLE {$dbPrefix}session"); + + // Drop the cron table, we use cache to handle that now + DB::update("DROP TABLE {$dbPrefix}cron"); + + // Generate URL keys for pastes that do not have a key. + // We process the pastes in batches of 1000 to avoid running + // out of memory. + while (TRUE) + { + $pastes = Paste::where('urlkey', '')->take(1000)->get(array('id', 'urlkey')); + + if ($pastes->count() > 0) + { + foreach ($pastes as $paste) + { + $paste->urlkey = Paste::makeUrlKey(); + + $paste->save(); + } + } + else + { + break; + } + } + + // Get the FQDN for the server + $fqdn = getenv('SERVER_NAME'); + + // Insert fqdn, app version and migration ID to site config + // The migration ID is nothing but the max paste ID while updating + // This will be used to allow/deny access to old pastes by their IDs + Site::config('general', array( + 'fqdn' => $fqdn, + 'preMigrate' => Paste::max('id'), + )); + + // This is the v0.4 config file + $configFile = app_path().'/config/config.php'; + + // Now we migrate the old config data + if (File::exists($configFile)) + { + include $configFile; + + // Import site settings + Site::config('general', array_map('html_entity_decode', array( + 'title' => $site_name, + 'copyright' => $site_copyright, + 'googleApi' => $google_api_key, + ))); + + // Import antispam settings + Site::config('antispam', array_map('html_entity_decode', array( + 'services' => $sg_services, + 'phpKey' => $sg_php_key, + 'phpDays' => $sg_php_days, + 'phpScore' => $sg_php_score, + 'phpType' => $sg_php_type, + 'censor' => $sg_censor, + ))); + + // Import authentication settings + Site::config('auth', array_map('html_entity_decode', array( + 'method' => $auth_method, + 'ldapServer' => $ldap_server, + 'ldapPort' => $ldap_port, + 'ldapBaseDn' => $ldap_base_dn, + 'ldapUid' => $ldap_uid, + 'ldapFilter' => $ldap_filter, + 'ldapUserDn' => $ldap_user_dn, + 'ldapPassword' => $ldap_password, + ))); + + // Import SMTP settings + Site::config('mail', array_map('html_entity_decode', array( + 'host' => $smtp_host, + 'port' => $smtp_port, + 'encryption' => $smtp_crypt, + 'username' => $smtp_username, + 'password' => $smtp_password, + 'address' => $smtp_from, + ))); + + // If auth method is LDAP, notify the user to set + // an admin filter. + if ($auth_method == 'ldap') + { + Setup::messages('0.4', Lang::get('setup.ldap_update_warn')); + } + + // Remove the old config file + File::delete($configFile); + } + + }, + + ), + + '1.0' => array(), + + '1.1' => array( + + 'closure' => function() + { + + $config = Site::config('general'); + + // Modify config values + if (isset($config->googleApi)) + { + Site::config('services', array( + 'googleApiKey' => $config->googleApi, + )); + } + + }, + + ), + + '1.2' => array( + + 'newTables' => array( + + 'comments' => array( + + (object) array( + 'name' => 'id', + 'type' => 'increments', + ), + + (object) array( + 'name' => 'paste_id', + 'type' => 'integer', + ), + + (object) array( + 'name' => 'data', + 'type' => 'text', + ), + + (object) array( + 'name' => 'author', + 'type' => 'string', + 'length' => 50, + 'nullable' => TRUE, + 'default' => NULL, + ), + + (object) array( + 'name' => 'timestamp', + 'type' => 'integer', + ), + + ), + + ), + + ), + + '1.3' => array( + + 'newTables' => array( + + 'statistics' => array( + + (object) array( + 'name' => 'id', + 'type' => 'increments', + ), + + (object) array( + 'name' => 'date', + 'type' => 'date', + ), + + (object) array( + 'name' => 'web', + 'type' => 'integer', + 'default' => 0, + ), + + (object) array( + 'name' => 'api', + 'type' => 'integer', + 'default' => 0, + ), + + ), + + ), + + ), + + '1.4' => array(), + + '1.5' => array(), + + '1.6' => array( + + 'modifyTables' => array( + + 'main' => array( + + (object) array( + 'name' => 'flagged', + 'type' => 'boolean', + 'default' => 0, + ), + + (object) array( + 'name' => 'attachment', + 'type' => 'boolean', + 'default' => 0, + ), + + ), + + 'users' => array( + + (object) array( + 'name' => 'remember_token', + 'type' => 'string', + 'length' => 60, + 'default' => '', + ), + + ), + + ), + + ), + + '1.7' => array( + + 'closure' => function() + { + + $config = Site::config('general'); + + // Modify config values + if (isset($config->privateSite) AND $config->privateSite) + { + Site::config('general', array( + 'pasteVisibility' => 'private', + )); + } + + }, + + ), + + '1.8' => array( + + 'closure' => function() + { + + $config = Site::config('general'); + + $noExpire = isset($config->noExpire) AND ! $config->noExpire ? 'none' : 'all'; + + Site::config('general', array( + 'noExpire' => $noExpire, + )); + + }, + + ), + + ), + +); diff --git a/app/config/session.php b/app/config/session.php new file mode 100755 index 000000000..ae343029e --- /dev/null +++ b/app/config/session.php @@ -0,0 +1,140 @@ + 'file', + + /* + |-------------------------------------------------------------------------- + | Session Lifetime + |-------------------------------------------------------------------------- + | + | Here you may specify the number of minutes that you wish the session + | to be allowed to remain idle before it expires. If you want them + | to immediately expire on the browser closing, set that option. + | + */ + + 'lifetime' => 120, + + 'expire_on_close' => false, + + /* + |-------------------------------------------------------------------------- + | Session File Location + |-------------------------------------------------------------------------- + | + | When using the native session driver, we need a location where session + | files may be stored. A default has been set for you but a different + | location may be specified. This is only needed for file sessions. + | + */ + + 'files' => storage_path().'/sessions', + + /* + |-------------------------------------------------------------------------- + | Session Database Connection + |-------------------------------------------------------------------------- + | + | When using the "database" or "redis" session drivers, you may specify a + | connection that should be used to manage these sessions. This should + | correspond to a connection in your database configuration options. + | + */ + + 'connection' => null, + + /* + |-------------------------------------------------------------------------- + | Session Database Table + |-------------------------------------------------------------------------- + | + | When using the "database" session driver, you may specify the table we + | should use to manage the sessions. Of course, a sensible default is + | provided for you; however, you are free to change this as needed. + | + */ + + 'table' => 'sessions', + + /* + |-------------------------------------------------------------------------- + | Session Sweeping Lottery + |-------------------------------------------------------------------------- + | + | Some session drivers must manually sweep their storage location to get + | rid of old sessions from storage. Here are the chances that it will + | happen on a given request. By default, the odds are 2 out of 100. + | + */ + + 'lottery' => array(2, 100), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Name + |-------------------------------------------------------------------------- + | + | Here you may change the name of the cookie used to identify a session + | instance by ID. The name specified here will get used every time a + | new session cookie is created by the framework for every driver. + | + */ + + 'cookie' => 'laravel_session', + + /* + |-------------------------------------------------------------------------- + | Session Cookie Path + |-------------------------------------------------------------------------- + | + | The session cookie path determines the path for which the cookie will + | be regarded as available. Typically, this will be the root path of + | your application but you are free to change this when necessary. + | + */ + + 'path' => '/', + + /* + |-------------------------------------------------------------------------- + | Session Cookie Domain + |-------------------------------------------------------------------------- + | + | Here you may change the domain of the cookie used to identify a session + | in your application. This will determine which domains the cookie is + | available to in your application. A sensible default has been set. + | + */ + + 'domain' => null, + + /* + |-------------------------------------------------------------------------- + | HTTPS Only Cookies + |-------------------------------------------------------------------------- + | + | By setting this option to true, session cookies will only be sent back + | to the server if the browser has a HTTPS connection. This will keep + | the cookie from being sent to you if it can not be done securely. + | + */ + + 'secure' => false, + +); diff --git a/app/config/testing/cache.php b/app/config/testing/cache.php new file mode 100755 index 000000000..16d3ae2fa --- /dev/null +++ b/app/config/testing/cache.php @@ -0,0 +1,20 @@ + 'array', + +); \ No newline at end of file diff --git a/app/config/testing/database.php b/app/config/testing/database.php new file mode 100755 index 000000000..11913b119 --- /dev/null +++ b/app/config/testing/database.php @@ -0,0 +1,124 @@ + PDO::FETCH_CLASS, + + /* + |-------------------------------------------------------------------------- + | Default Database Connection Name + |-------------------------------------------------------------------------- + | + | Here you may specify which of the database connections below you wish + | to use as your default connection for all database work. Of course + | you may use many connections at once using the Database library. + | + */ + + 'default' => 'sqlite', + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Here are each of the database connections setup for your application. + | Of course, examples of configuring each database platform that is + | supported by Laravel is shown below to make development simple. + | + | + | All database work in Laravel is done through the PHP PDO facilities + | so make sure you have the driver for your particular database of + | choice installed on your machine before you begin development. + | + */ + + 'connections' => array( + + 'sqlite' => array( + 'driver' => 'sqlite', + 'database' => __DIR__.'/../../database/testing.sqlite', + 'prefix' => '', + ), + + 'mysql' => array( + 'driver' => 'mysql', + 'host' => 'localhost', + 'database' => 'database', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', + ), + + 'pgsql' => array( + 'driver' => 'pgsql', + 'host' => 'localhost', + 'database' => 'database', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', + 'prefix' => '', + 'schema' => 'public', + ), + + 'sqlsrv' => array( + 'driver' => 'sqlsrv', + 'host' => 'localhost', + 'database' => 'database', + 'username' => 'root', + 'password' => '', + 'prefix' => '', + ), + + ), + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk have not actually be run in the databases. + | + */ + + 'migrations' => 'migrations', + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer set of commands than a typical key-value systems + | such as APC or Memcached. Laravel makes it easy to dig right in. + | + */ + + 'redis' => array( + + 'cluster' => true, + + 'default' => array( + 'host' => '127.0.0.1', + 'port' => 6379, + 'database' => 0, + ), + + ), + +); diff --git a/app/config/testing/session.php b/app/config/testing/session.php new file mode 100755 index 000000000..0364b63dc --- /dev/null +++ b/app/config/testing/session.php @@ -0,0 +1,21 @@ + 'array', + +); diff --git a/app/config/view.php b/app/config/view.php new file mode 100755 index 000000000..0329f8753 --- /dev/null +++ b/app/config/view.php @@ -0,0 +1,31 @@ + array(__DIR__.'/../views'), + + /* + |-------------------------------------------------------------------------- + | Pagination View + |-------------------------------------------------------------------------- + | + | This view will be used to render the pagination link output, and can + | be easily customized here to show any view you like. A clean view + | compatible with Twitter's Bootstrap is given to you by default. + | + */ + + 'pagination' => View::inject('common/pagination'), + +); diff --git a/app/config/workbench.php b/app/config/workbench.php new file mode 100755 index 000000000..56bee5265 --- /dev/null +++ b/app/config/workbench.php @@ -0,0 +1,31 @@ + '', + + /* + |-------------------------------------------------------------------------- + | Workbench Author E-Mail Address + |-------------------------------------------------------------------------- + | + | Like the option above, your e-mail address is used when generating new + | workbench packages. The e-mail is placed in your composer.json file + | automatically after the package is created by the workbench tool. + | + */ + + 'email' => '', + +); \ No newline at end of file diff --git a/app/controllers/.gitkeep b/app/controllers/.gitkeep new file mode 100755 index 000000000..e69de29bb diff --git a/app/controllers/AdminController.php b/app/controllers/AdminController.php new file mode 100755 index 000000000..d189a190e --- /dev/null +++ b/app/controllers/AdminController.php @@ -0,0 +1,748 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * AdminController + * + * This controller handles site administration + * + * @package StickyNotes + * @subpackage Controllers + * @author Sayak Banerjee + */ +class AdminController extends BaseController { + + /** + * Redirects to the administration dashboard + * + * @access public + * @return \Illuminate\Support\Facades\Redirect + */ + public function getIndex() + { + return Redirect::to('admin/dashboard'); + } + + /** + * Displays the administration dashboard + * + * @access public + * @return \Illuminate\Support\Facades\View + */ + public function getDashboard() + { + // Get all stats for the last 1 month + $duration = Site::config('general')->statsDisplay; + + $date = date('Y-m-d', strtotime($duration)); + + $stats = Statistics::where('date', '>', $date)->orderBy('date')->get()->toArray(); + + // Build the view data + $data = array( + 'users' => User::count(), + 'pastes' => Paste::count(), + 'php_version' => phpversion(), + 'sn_version' => Config::get('app.version'), + 'db_driver' => Config::get('database.default'), + 'stats' => $stats, + ); + + return View::make('admin/dashboard', $data); + } + + /** + * Search, edit and delete pastes + * + * @param string $urlkey + * @param string $action + * @return \Illuminate\Support\Facades\View|\Illuminate\Support\Facades\Redirect + */ + public function getPaste($urlkey = '', $action = '') + { + $paste = NULL; + + if ( ! empty($urlkey)) + { + $paste = Paste::where('urlkey', $urlkey)->first(); + + // Paste was not found + if (is_null($paste)) + { + Session::flash('messages.error', Lang::get('admin.paste_404')); + } + + // Perform requested action + switch ($action) + { + case 'rempass': + + $paste->password = ''; + + $paste->save(); + + return Redirect::to(URL::previous()); + + case 'toggle': + + Revision::where('urlkey', $paste->urlkey)->delete(); + + $paste->private = $paste->private ? 0 : 1; + + $paste->password = ''; + + $paste->save(); + + return Redirect::to(URL::previous()); + + case 'remattach': + + $attachment = storage_path()."/uploads/{$paste->urlkey}"; + + if ($paste->attachment AND File::exists($attachment)) + { + File::delete($attachment); + + $paste->attachment = 0; + + $paste->save(); + } + + Session::flash('messages.success', Lang::get('admin.attachment_deleted')); + + return Redirect::to(URL::previous()); + + case 'delete': + + Revision::where('urlkey', $paste->urlkey)->delete(); + + $paste->comments()->delete(); + + $attachment = storage_path()."/uploads/{$paste->urlkey}"; + + if ($paste->attachment AND File::exists($attachment)) + { + File::delete($attachment); + } + + $paste->delete(); + + Session::flash('messages.success', Lang::get('global.paste_deleted')); + + return Redirect::to('admin/paste'); + } + } + + return View::make('admin/paste', array('paste' => $paste)); + } + + /** + * Handles POST requests to the paste module + * + * @return \Illuminate\Support\Facades\Redirect + */ + public function postPaste() + { + if (Input::has('search')) + { + $key = Input::get('search'); + + return Redirect::to('admin/paste/'.urlencode($key)); + } + else + { + return Redirect::to('admin/paste'); + } + } + + /** + * Search, create, edit or delete users + * + * @param string $action + * @param string $username + * @return \Illuminate\Support\Facades\View|\Illuminate\Support\Facades\Redirect + */ + public function getUser($action = '', $username = '') + { + $perPage = Site::config('general')->perPage; + + $user = User::where('username', $username)->where('type', 'db')->first(); + + $users = User::where('type', 'db')->orderBy('username')->paginate($perPage); + + $pages = $users->links(); + + // User not found + if ( ! empty($username) AND is_null($user)) + { + Session::flash('messages.error', Lang::get('admin.user_404')); + + return Redirect::to('admin/user'); + } + + // Perform the specified action + switch ($action) + { + case 'create': + + $data = array( + 'user' => new User, + 'founder' => FALSE, + ); + + return View::make('admin/user', $data); + + case 'delete': + + // Cannot delete founder user or own account + if ($user->id != 1 AND $user->id != Auth::user()->id) + { + $user->delete(); + + Session::flash('messages.success', Lang::get('admin.user_deleted')); + + return Redirect::to('admin/user'); + } + else + { + Session::flash('messages.error', Lang::get('admin.user_del_fail')); + } + } + + // Render the view. The founder flag here makes sure that the first + // user cannot be blocked or removed from admin status. + $data = array( + 'user' => $user, + 'users' => $users, + 'pages' => $pages, + 'founder' => is_null($user) ? FALSE : $user->id == User::min('id'), + ); + + return View::make('admin/user', $data); + } + + /** + * Handles POST actions for the user module + * + * @return \Illuminate\Support\Facades\Redirect + */ + public function postUser() + { + if (Input::has('_save')) + { + $id = Input::get('id'); + + // Define validation rules + $validator = Validator::make(Input::all(), array( + 'username' => 'required|max:50|alpha_dash|unique:users,username,'.$id.',id,type,db', + 'email' => 'required|max:100|email|unique:users,email,'.$id.',id,type,db', + 'dispname' => 'max:100', + 'password' => empty($id) ? 'required|min:5' : 'min:5' + )); + + // Run the validator + if ($validator->passes()) + { + // If ID is there, it is an update operation + if ( ! empty($id)) + { + $user = User::findOrFail($id); + + $origUsername = $user->username; + } + else + { + $user = new User; + + $origUsername = NULL; + } + + $user->username = Input::get('username'); + $user->email = Input::get('email'); + $user->dispname = Input::get('dispname'); + $user->salt = $user->salt ?: str_random(5); + + // The first user is always immutable + $isFounder = $user->id == User::min('id'); + + $user->admin = $isFounder ?: Input::has('admin'); + $user->active = $isFounder ?: Input::has('active'); + + if (Input::has('password')) + { + $user->password = PHPass::make()->create(Input::get('password'), $user->salt); + } + + $user->save(); + + // Username is cached in the main, comment and revision tables, update them too + if ( ! empty($id)) + { + Paste::where('author_id', $id)->update(array( + 'author' => $user->username, + )); + + Revision::where('author', $origUsername)->update(array( + 'author' => $user->username, + )); + + Comment::where('author', $origUsername)->update(array( + 'author' => $user->username, + )); + } + + Cache::flush(); + + Session::flash('messages.success', Lang::get('admin.user_saved')); + + return Redirect::to('admin/user'); + } + else + { + Session::flash('messages.error', $validator->messages()->all('

      :message

      ')); + + return Redirect::to(URL::previous())->withInput(); + } + } + else if (Input::has('search')) + { + $username = Input::get('search'); + + return Redirect::to('admin/user/edit/'.urlencode($username)); + } + else + { + return Redirect::to('admin/user'); + } + } + + /** + * Displays the IP banning module + * + * @param string $action + * @param string $ip + * @return \Illuminate\Support\Facades\View + */ + public function getBan($action = '', $ip = '') + { + // Remove a specific IP address + if ($action == 'remove' AND ! empty($ip)) + { + $ipban = IPBan::findOrFail($ip); + + $ipban->delete(); + + Session::flash('messages.success', Lang::get('admin.ip_unbanned')); + + return Redirect::to('admin/ban'); + } + + return View::make('admin/ban', array('bans' => IPBan::all())); + } + + /** + * Processes POST requests for the IP banning module + * + * @return \Illuminate\Support\Facades\Redirect + */ + public function postBan() + { + // Define validation rules + $validator = Validator::make(Input::all(), array( + 'ip' => 'required|ip', + )); + + // Run the validator + if ($validator->passes()) + { + $ipban = new IPBan; + + $ipban->ip = Input::get('ip'); + + $ipban->save(); + + Session::flash('messages.success', Lang::get('admin.ip_banned')); + + return Redirect::to('admin/ban'); + } + else + { + Session::flash('messages.error', $validator->messages()->all('

      :message

      ')); + + return Redirect::to('admin/ban')->withInput(); + } + } + + /** + * Displays the email configuration module + * + * @return \Illuminate\Support\Facades\View + */ + public function getMail() + { + return View::make('admin/mail'); + } + + /** + * Handles POST requests to the email config form + * + * @return \Illuminate\Support\Facades\Redirect + */ + public function postMail() + { + // Define validation rules + $validator = Validator::make(Input::all(), array( + 'driver' => 'required|in:smtp,mail,sendmail', + 'host' => 'required_if:driver,smtp', + 'port' => 'required_if:driver,smtp', + 'address' => 'required', + 'sendmail' => 'required_if:driver,sendmail', + )); + + // Run the validator + if ($validator->passes()) + { + // Save button click + if (Input::has('_save')) + { + Site::config('mail', Input::all()); + + Session::flash('messages.success', Lang::get('admin.mail_updated')); + } + + // Test settings button click + else if (Input::has('_test')) + { + // Backup the existing mail settings + $original = (array) Site::config('mail'); + + // Temporarily apply the new mail settings + Site::config('mail', Input::all()); + + // Test the mail settings + $result = Mail::test(); + + if ($result === TRUE) + { + Session::flash('messages.success', Lang::get('admin.test_mail_success')); + } + else + { + Session::flash('messages.error', $result); + } + + // Revert back to original mail settings + Site::config('mail', $original); + } + } + else + { + Session::flash('messages.error', $validator->messages()->all('

      :message

      ')); + } + + return Redirect::to('admin/mail')->withInput(); + } + + /** + * Display the spam filter configuration screen + * + * @access public + * @return \Illuminate\Support\Facades\View + */ + public function getAntispam() + { + return View::make('admin/antispam', array('flags' => Antispam::flags())); + } + + /** + * Handles POST requests to the antispam config form + * + * @access public + * @return \Illuminate\Support\Facades\Redirect + */ + public function postAntispam() + { + // Define Akismet key validation logic + Validator::extend('akismet_key', function($attribute, $value, $parameters) + { + $akismet = new Akismet(Request::url(), $value); + + return $akismet->isKeyValid(); + }); + + // Define validation rules + $validator = Validator::make(Input::all(), array( + 'php_key' => 'required_if:flag_php,1', + 'php_days' => 'required_if:flag_php,1|integer|between:0,255', + 'php_score' => 'required_if:flag_php,1|integer|between:0,255', + 'php_type' => 'required_if:flag_php,1|integer|between:0,255', + 'flood_threshold' => 'required_if:flag_noflood,1|integer|between:0,60', + 'akismet_key' => 'required_if:flag_akismet,1|akismet_key', + )); + + // Run the validator + if ($validator->passes()) + { + $services = Antispam::services(); + + $flags = array(); + + // Convert the service flags to CSV + foreach ($services as $service) + { + if (Input::has('flag_'.$service)) + { + $flags[] = $service; + } + } + + // Inject flag data to the configuration + $config = array_merge(Input::all(), array( + 'services' => implode('|', $flags) + )); + + Site::config('antispam', $config); + + Session::flash('messages.success', Lang::get('admin.antispam_updated')); + + return Redirect::to('admin/antispam'); + } + else + { + Session::flash('messages.error', $validator->messages()->all('

      :message

      ')); + + return Redirect::to('admin/antispam')->withInput(); + } + } + + /** + * Displays user authentication configuration screen + * + * @access public + * @return \Illuminate\Support\Facades\View + */ + public function getAuth() + { + return View::make('admin/auth'); + } + + /** + * Handles POST requests to the user auth config form + * + * @access public + * @return \Illuminate\Support\Facades\Redirect + */ + public function postAuth() + { + // Define validation rules + $validator = Validator::make(Input::all(), array( + 'method' => 'required|in:db,ldap,oauth', + 'db_allow_reg' => 'required|in:0,1', + 'ldap_server' => 'required_if:method,ldap', + 'ldap_base_dn' => 'required_if:method,ldap', + 'ldap_uid' => 'required_if:method,ldap', + 'ldap_admin' => 'required_if:method,ldap', + )); + + // Run the validator + if ($validator->passes()) + { + Site::config('auth', Input::all()); + + Session::flash('messages.success', Lang::get('admin.auth_updated')); + + return Redirect::to('admin/auth'); + } + else + { + Session::flash('messages.error', $validator->messages()->all('

      :message

      ')); + + return Redirect::to('admin/auth')->withInput(); + } + } + + /** + * Displays site configuration screen + * + * @access public + * @return \Illuminate\Support\Facades\View + */ + public function getSite() + { + return View::make('admin/site', array('langs' => System::directories('lang'))); + } + + /** + * Handles POST requests to the site config form + * + * @access public + * @return \Illuminate\Support\Facades\Redirect + */ + public function postSite() + { + // Define validation rules + $validator = Validator::make(Input::all(), array( + 'fqdn' => 'required', + 'title' => 'required|max:20', + 'per_page' => 'required|integer|between:5,200', + 'lang' => 'required|in:'.System::directories('lang', TRUE), + )); + + // Run the validator + if ($validator->passes()) + { + Site::config('general', Input::all()); + + Session::flash('messages.success', Lang::get('admin.site_updated')); + + return Redirect::to('admin/site'); + } + else + { + Session::flash('messages.error', $validator->messages()->all('

      :message

      ')); + + return Redirect::to('admin/site')->withInput(); + } + } + + /** + * Displays the skin chooser + * + * @access public + * @param string $action + * @param string $skin + * @return \Illuminate\Support\Facades\View + */ + public function getSkin($action = 'list', $skin = '') + { + $version = System::version(Site::config('general')->version); + + $skins = System::directories('views/skins'); + + $list = array(); + + // Output the response based on the action + switch ($action) + { + case 'list': + + foreach ($skins as $skin) + { + if (File::exists(app_path()."/views/skins/{$skin}/{$skin}.info")) + { + $info = @json_decode(File::get(app_path()."/views/skins/{$skin}/{$skin}.info"), TRUE); + + $data = array( + 'key' => $skin, + 'name' => isset($info['name']) ? $info['name'] : $skin, + 'version' => isset($info['themeVersion']) ? $info['themeVersion'] : '1.0', + 'description' => isset($info['description']) ? $info['description'] : NULL, + 'author' => NULL, + ); + + if (isset($info['author'])) + { + if (isset($info['authorWebsite'])) + { + $data['author'] = link_to($info['authorWebsite'], $info['author']); + } + else + { + $data['author'] = $info['author']; + } + } + + $list[] = (object) $data; + } + } + + return View::make('admin/skin', array('skins' => $list)); + + case 'set': + + if (File::exists(app_path()."/views/skins/{$skin}/{$skin}.info")) + { + $info = @json_decode(File::get(app_path()."/views/skins/{$skin}/{$skin}.info"), TRUE); + + // The theme info 'minCoreVersion' tells us the minimum version needed for + // the theme to work. So we check if the system version is newer + // than the core version before setting the theme + if (isset($info['minCoreVersion']) AND $version >= System::version($info['minCoreVersion'])) + { + Site::config('general', array('skin' => $skin)); + + Cache::flush(); + + Session::flash('messages.success', Lang::get('admin.skin_applied')); + } + else + { + Session::flash('messages.error', Lang::get('admin.skin_version')); + } + + return Redirect::to('admin/skin'); + } + + Session::flash('messages.error', Lang::get('admin.skin_error')); + + return Redirect::to('admin/skin'); + + case 'preview': + + if (File::exists(app_path()."/views/skins/{$skin}/{$skin}.png")) + { + $preview = File::get(app_path()."/views/skins/{$skin}/{$skin}.png"); + } + else + { + $preview = File::get(public_path().'/assets/img/no-preview.png'); + } + + $response = Response::make($preview); + + $response->header('Content-Type', 'image/png'); + + return $response; + } + } + + /** + * Displays services configuration screen + * + * @access public + * @return \Illuminate\Support\Facades\View + */ + public function getServices() + { + return View::make('admin/services'); + } + + /** + * Handles POST requests to the servics config form + * + * @access public + * @return \Illuminate\Support\Facades\Redirect + */ + public function postServices() + { + Site::config('services', Input::all()); + + Session::flash('messages.success', Lang::get('admin.services_updated')); + + return Redirect::to('admin/services'); + } + +} diff --git a/app/controllers/AjaxController.php b/app/controllers/AjaxController.php new file mode 100755 index 000000000..a990821c3 --- /dev/null +++ b/app/controllers/AjaxController.php @@ -0,0 +1,83 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * AjaxController + * + * This controller handles AJAX requests + * + * @package StickyNotes + * @subpackage Controllers + * @author Sayak Banerjee + */ +class AjaxController extends BaseController { + + /** + * Fetches the latest available sticky notes version + * + * @return \Illuminate\Support\Facades\View + */ + public function getVersion() + { + // Build the view data and return the view + $data = array( + 'updated' => System::updated() >= 0, + ); + + return View::make("admin/version", $data); + } + + /** + * Gets the system load + * + * @return string + */ + public function getSysload() + { + return System::load(); + } + + /** + * Generates a short URL for a paste + * + * @param string $urlkey + * @param string $hash + * @return \Illuminate\Support\Facades\View|string + */ + public function getShorten($urlkey, $hash = '') + { + // We need to validate the paste first + $paste = Paste::where('urlkey', $urlkey)->first(); + + // Paste was not found + if (is_null($paste)) + { + return Lang::get('ajax.error'); + } + + // If it is a private paste, we need the hash + if ($paste->private AND $paste->hash != $hash) + { + return Lang::get('ajax.error'); + } + + // Shorten and return the paste URL + $longUrl = url("{$urlkey}/{$hash}"); + + return Service::urlShortener($longUrl); + } + +} diff --git a/app/controllers/ApiController.php b/app/controllers/ApiController.php new file mode 100755 index 000000000..a044c205e --- /dev/null +++ b/app/controllers/ApiController.php @@ -0,0 +1,270 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * ApiController + * + * This controller handles all API operations + * + * @package StickyNotes + * @subpackage Controllers + * @author Sayak Banerjee + */ +class ApiController extends BaseController { + + /** + * The constructor here validates the API mode + * + * @access public + * @return void + */ + public function __construct() + { + $mode = Request::segment(2); + + switch ($mode) + { + case 'xml': + case 'json': + + break; + + default: + + header('HTTP/1.1 400 Bad Request', TRUE, 400); + + exit; + } + } + + /** + * Fetches allowed values for a certain parameter + * + * @param string $mode + * @param string $param + * @return void + */ + public function getParameter($mode, $param) + { + $api = API::make($mode); + + switch ($param) + { + case 'language': + + $languages = Highlighter::make()->languages(); + + $values = array_keys($languages); + + break; + + case 'expire': + + $expire = Paste::getExpiration(); + + $values = array_keys($expire); + + break; + + case 'version': + + $values = array(Config::get('app.version')); + + break; + + case 'theme': + + $values = array(studly_case(Site::config('general')->skin)); + + break; + + default: + + return $api->error('invalid_param', 404); + } + + // Build the API data + $data = array( + 'param' => $param, + 'values' => $values, + ); + + return $api->out('param', $data); + } + + /** + * Show a paste by its ID or key + * + * @access public + * @param string $mode + * @param string $urlkey + * @param string $hash + * @param string $password + * @return \Illuminate\Support\Facades\View + */ + public function getShow($mode, $urlkey, $hash = '', $password = '') + { + $api = API::make($mode); + + $paste = Paste::where('urlkey', $urlkey)->first(); + + // The paste was not found + if (is_null($paste)) + { + return $api->error('not_found', 404); + } + + // Validate the hash for private pastes + if ($paste->private AND $paste->hash != $hash) + { + return $api->error('invalid_hash', 403); + } + + // Validate the password for protected pastes + if ($paste->password) + { + if (empty($password)) + { + return $api->error('password_required', 403); + } + else if ( ! PHPass::make()->check('Paste', $password, $paste->salt, $paste->password)) + { + return $api->error('invalid_password', 403); + } + } + + // Build the API data + $data = $paste->toArray(); + + return $api->out('show', $data); + } + + /** + * Gets a paste list in the specified mode + * + * @param string $mode + * @param int $page + * @return \Illuminate\Support\Facades\View + */ + public function getList($mode, $page = 1) + { + $api = API::make($mode); + + $perPage = Site::config('general')->perPage; + + // As laravel reads the page GET parameter, we need to + // manually set it to use this page. + DB::getPaginator()->setCurrentPage($page); + + // Only the public pastes are accessible via the API + $query = Paste::where('private', '<>', 1); + + $pastes = $query->orderBy('id', 'desc')->paginate($perPage); + + // Check if no pastes were found + if ($pastes->count() === 0) + { + return $api->error('no_pastes', 418); + } + + // We populate the data manually here as there is some + // per item processing to be done + $list = array(); + + // Get the key for each paste item + foreach ($pastes as $paste) + { + $list[] = $paste->toArray(); + } + + // Build the API data and make the output + $data = array( + 'pastes' => $list, + 'count' => $pastes->count(), + 'pages' => $pastes->getLastPage(), + ); + + return $api->out('list', $data); + } + + /** + * Creates a new paste via the API + * + * @param string $mode + * @return \Illuminate\Support\Facades\View + */ + public function postCreate($mode) + { + $api = API::make($mode); + + // Set custom messages for validation module + $custom = array( + 'title.max' => 'title_max_30', + 'data.required' => 'data_required', + 'data.auth' => 'cannot_post', + 'data.mbmax' => 'data_too_big', + 'language.required' => 'lang_required', + 'language.in' => 'lang_invalid', + 'expire.integer' => 'expire_integer', + 'expire.in' => 'expire_invalid', + ); + + // Define validation rules + $validator = Validator::make(Input::all(), array( + 'title' => 'max:30', + 'data' => 'required|auth|mbmax:'.Site::config('general')->maxPasteSize, + 'language' => 'required|in:'.Highlighter::make()->languages(TRUE), + 'expire' => 'integer|in:'.Paste::getExpiration('create', TRUE), + ), $custom); + + // Run validations + if ($validator->fails()) + { + return $api->error($validator->messages()->first()); + } + + // Set custom messages for the antispam module + $custom = array( + 'ipban' => 'antispam_ipban', + 'stealth' => 'antispam_stealth', + 'censor' => 'antispam_censor', + 'noflood' => 'antispam_noflood', + 'php' => 'antispam_php', + ); + + // Instantiate the antispam module + $antispam = Antispam::make('api_call', 'data', $custom); + + // Run the anti-spam modules + if ($antispam->fails()) + { + return $api->error($antispam->message()); + } + + // Create the paste like a boss! + $paste = Paste::createNew('api', Input::all()); + + // All done! Now we need to output the urlkey and hash + $data = array( + 'urlkey' => $paste->urlkey, + 'hash' => $paste->hash, + ); + + // Return the output + return $api->out('create', $data); + } + +} diff --git a/app/controllers/BaseController.php b/app/controllers/BaseController.php new file mode 100755 index 000000000..7aa74e5bc --- /dev/null +++ b/app/controllers/BaseController.php @@ -0,0 +1,86 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * BaseController + * + * @package StickyNotes + * @subpackage Controllers + * @author Sayak Banerjee + */ +class BaseController extends Controller { + + /** + * Current project + * + * @var string + */ + public $project; + + /** + * Class constructor + * + * @return void + */ + public function __construct() + { + // We detect the subdomain being used and compare it with the + // FQDN stored in the database. With that data, we extract the + // project name and set it here + $this->project = System::project(); + + // This is a part of basic input sanitation. Currently this method + // trims all incoming input data and merges it with the input + // array which is then used for processed by the controllers + $this->processInput(); + } + + /** + * Setup the layout used by the controller. + * + * @access protected + * @return void + */ + protected function setupLayout() + { + if ( ! is_null($this->layout)) + { + $this->layout = View::make($this->layout); + } + } + + /** + * Process and clean the POSTed data + * + * @access protected + * @return void + */ + protected function processInput() + { + $input = Input::all(); + + // Trim leading and trailing whitespace + // If the control's name is "data", we only trim trailing space + foreach ($input as $key => $value) + { + $input[$key] = $key == 'data' ? rtrim($value) : trim($value); + } + + // Merge it back to the Input data + Input::merge($input); + } + +} diff --git a/app/controllers/CreateController.php b/app/controllers/CreateController.php new file mode 100755 index 000000000..c907f249f --- /dev/null +++ b/app/controllers/CreateController.php @@ -0,0 +1,314 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * CreateController + * + * This is the default homepage of the site and allows the user to create a new + * paste. + * + * @package StickyNotes + * @subpackage Controllers + * @author Sayak Banerjee + */ +class CreateController extends BaseController { + + /** + * Displays the new paste form + * + * @access public + * @return \Illuminate\Support\Facades\View + */ + public function getCreate() + { + // Build the view data + $data = array( + 'languages' => Highlighter::make()->languages(), + 'language' => 'text', + 'paste' => new Paste, + 'action' => 'CreateController@postCreate', + 'disabled' => NULL, + 'attach' => TRUE, + ); + + // Get the default language from cookie + $history = Cookie::get('languages'); + + if (is_array($history)) + { + $data['language'] = end($history); + } + + return View::make('site/create', $data); + } + + /** + * Creates a new paste item + * + * @return \Illuminate\Support\Facades\Redirect + */ + public function postCreate() + { + // Get the site configuration + $site = Site::config('general'); + + // Define validation rules + $validator = Validator::make(Input::all(), array( + 'title' => 'max:30', + 'data' => 'required|auth|mbmax:'.$site->maxPasteSize, + 'language' => 'required|in:'.Highlighter::make()->languages(TRUE), + 'expire' => 'in:'.Paste::getExpiration('create', TRUE), + )); + + // Generate anti-spam modules + $antispam = Antispam::make('paste', 'data'); + + // Run validations + $resultValidation = $validator->passes(); + + // Execute antispam services + $resultAntispam = $antispam->passes(); + + // Get the paste language. We use it to store a language history + $language = Input::get('language'); + + $historyLangs = Cookie::get('languages'); + + // History languages must always be an array + $historyLangs = is_array($historyLangs) ? $historyLangs : array(); + + // No dulicates allowed in the history + if (in_array($language, $historyLangs)) + { + $key = array_search($language, $historyLangs); + + unset($historyLangs[$key]); + } + + // Max. 10 history languages are allowed + else if (count($historyLangs) >= 10) + { + $historyLangs = array_slice($historyLangs, 1, count($historyLangs)); + } + + // Add current language to the history + array_push($historyLangs, $language); + + $cookie = Cookie::forever('languages', $historyLangs); + + // Evaluate validation results + if ($resultValidation AND $resultAntispam) + { + // We inject the project into the input so that + // it is also inserted into the DB accordingly + Input::merge(array('project' => $this->project)); + + // All OK! Create the paste already!! + $paste = Paste::createNew('web', Input::all()); + + // Now, save the attachment, if any (and if enabled) + if ($site->allowAttachment AND Input::hasFile('attachment')) + { + $file = Input::file('attachment'); + + if ($file->isValid()) + { + $file->move(storage_path().'/uploads', $paste->urlkey); + } + } + + // Redirect to paste if there's no password + // Otherwise, just show a link + if ($paste->password) + { + $url = link_to("{$paste->urlkey}/{$paste->hash}"); + + $message = sprintf(Lang::get('create.click_for_paste'), $url); + + Session::flash('messages.success', $message); + } + else + { + return Redirect::to(Paste::getUrl($paste))->withCookie($cookie); + } + } + else + { + // Set the error message as flashdata + if ( ! $resultValidation) + { + Session::flash('messages.error', $validator->messages()->all('

      :message

      ')); + } + else if ( ! $resultAntispam) + { + Session::flash('messages.error', $antispam->message()); + } + } + + return Redirect::to(URL::previous())->withInput()->withCookie($cookie); + } + + /** + * Editor window for creating a revision + * + * @param string $urlkey + * @return \Illuminate\Support\Facades\View|\Illuminate\Support\Facades\Redirect + */ + public function getRevision($urlkey) + { + $paste = Paste::where('urlkey', $urlkey)->first(); + + // Paste was not found + if (is_null($paste)) + { + App::abort(404); // Not found + } + else + { + // We only allow the user to revise public pastes + // Private pastes need to be toggled before being revised + if ($paste->private OR $paste->password) + { + Session::flash('messages.error', Lang::get('create.revise_private')); + + return Redirect::to(URL::previous())->withInput(); + } + + // Now that we are good, we save the paste ID in session so that + // when the edited paste is POSTed, we can validate against this + Session::put('paste.revision', $paste->id); + } + + // Output the view + $data = array( + 'languages' => Highlighter::make()->languages(), + 'language' => 'text', + 'paste' => $paste, + 'action' => 'CreateController@postRevision', + 'disabled' => 'disabled', + 'attach' => FALSE, + ); + + return View::make('site/create', $data); + } + + /** + * Creates a new paste revision + * + * @return \Illuminate\Support\Facades\Redirect + */ + public function postRevision() + { + $oldId = Input::get('id'); + + // First and foremost, validate the ID of the revision + if (Session::get('paste.revision') != $oldId) + { + App::abort(401); // Unauthorized + } + + // Define validation rules. We don't validate the title and language + // here as we don't allow to change that for a revision. Instead, we + // will use the data from the old paste + $validator = Validator::make(Input::all(), array( + 'data' => 'required|auth', + 'expire' => 'in:'.Paste::getExpiration('create', TRUE), + )); + + // Generate anti-spam modules + $antispam = Antispam::make('paste', 'data'); + + // Run validations + $resultValidation = $validator->passes(); + + // Execute antispam services + $resultAntispam = $antispam->passes(); + + if ($resultValidation AND $resultAntispam) + { + // Get the paste being revised + $oldPaste = Paste::findOrFail($oldId); + + // If the old paste's content is same as the revision, + // we simply redirect to the old paste itself + if (crc32($oldPaste->data) == crc32(Input::get('data'))) + { + return Redirect::to($oldPaste->urlkey); + } + + // We use some data from the old paste + $data = array( + 'project' => $oldPaste->project, + 'title' => $oldPaste->title, + 'language' => $oldPaste->language, + 'private' => NULL, + 'password' => NULL, + 'attachment' => NULL, + ); + + // Merge it with the input to override the values the user submitted + Input::merge($data); + + // All set, create the new revision + $newPaste = Paste::createNew('web', Input::all()); + + // We now need to update the revisions table. One entry will be + // created for this revision. We will also create entries for + // any past revisions and link it to this new paste + $revData = array( + array( + 'paste_id' => $newPaste->id, + 'urlkey' => $oldPaste->urlkey, + 'author' => $oldPaste->author, + 'timestamp' => $oldPaste->timestamp, + ) + ); + + foreach ($oldPaste->revisions as $revision) + { + $revData[] = array( + 'paste_id' => $newPaste->id, + 'urlkey' => $revision->urlkey, + 'author' => $revision->author, + 'timestamp' => $revision->timestamp, + ); + } + + // Now insert this batch data to the revisions table + Revision::insert($revData); + + // Whoa, finally we are done, take the user to the shiny new + // paste. Since this is a public paste, we don't need the url + // hash or password shebang + return Redirect::to($newPaste->urlkey); + } + else + { + // Set the error message as flashdata + if ( ! $resultValidation) + { + Session::flash('messages.error', $validator->messages()->all('

      :message

      ')); + } + else if ( ! $resultAntispam) + { + Session::flash('messages.error', $antispam->message()); + } + } + + return Redirect::to(URL::previous())->withInput(); + } + +} diff --git a/app/controllers/FeedController.php b/app/controllers/FeedController.php new file mode 100755 index 000000000..49903632b --- /dev/null +++ b/app/controllers/FeedController.php @@ -0,0 +1,59 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.1 + * @filesource + */ + +/** + * FeedController + * + * This controller handles feed operations + * + * @package StickyNotes + * @subpackage Controllers + * @author Sayak Banerjee + */ +class FeedController extends BaseController { + + /** + * Gets the news feed for the site + * + * @param string $type + * @return void + */ + public function getFeed($type = 'rss') + { + // Create feeder instance + $feed = Feed::make($type); + + // Only the public pastes are accessible in the feed + $query = Paste::where('private', '<>', 1); + + // We fetch 100 pastes only + $pastes = $query->take(100)->orderBy('id', 'desc')->get(); + + // We populate the data manually here as there is some + // per item processing to be done + $list = array(); + + // Get the key for each paste item + foreach ($pastes as $paste) + { + $list[] = $paste->toArray(); + } + + // Serve the feed output + return $feed->out(array('pastes' => $list)); + } + +} diff --git a/app/controllers/ListController.php b/app/controllers/ListController.php new file mode 100755 index 000000000..adb096e8f --- /dev/null +++ b/app/controllers/ListController.php @@ -0,0 +1,300 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * ShowController + * + * This controller handles displaying of a paste lists + * + * @package StickyNotes + * @subpackage Controllers + * @author Sayak Banerjee + */ +class ListController extends BaseController { + + /** + * Displays the default list page + * + * @access public + * @return \Illuminate\Support\Facades\View + */ + public function getAll() + { + $perPage = Site::config('general')->perPage; + + // Show all pastes to admins + if (Auth::roles()->admin) + { + $query = Paste::query(); + } + else + { + $query = Paste::where('private', '<>', 1); + } + + // Filter by project + if ( ! empty($this->project)) + { + $query = $query->where('project', $this->project); + } + + $pastes = $query->orderBy('id', 'desc')->paginate($perPage); + + return $this->getList($pastes, TRUE); + } + + /** + * Fetches the top N trending pastes, where N = perPage from site config + * + * @access public + * @param string $age + * @return \Illuminate\Support\Facades\View + */ + public function getTrending($age = 'now') + { + $perPage = Site::config('general')->perPage; + + $time = time(); + + $filter = $time - 259200; + + // Calculate age based on filter + switch ($age) + { + case 'week': + + $filter = $time - 1814400; + + break; + + case 'month': + + $filter = $time - 7776000; + + break; + + case 'year': + + $filter = $time - 94608000; + + break; + + case 'all': + + $filter = 0; + + break; + } + + // Get all pastes matching the age filter + $query = Paste::where('timestamp', '>=', $filter); + + // Hide private pastes from non-admins + if ( ! Auth::roles()->admin) + { + $query = $query->where('private', '<>', 1); + } + + // Filter by project + if ( ! empty($this->project)) + { + $query = $query->where('project', $this->project); + } + + // We do not really need paginate() here, however the generic method + // we are using here depends on it. + $pastes = $query->orderBy('hits', 'desc')->take($perPage)->paginate($perPage); + + return $this->getList($pastes, FALSE, TRUE); + } + + /** + * Gets user's own pastes + * + * @access public + * @param int $userId + * @return \Illuminate\Support\Facades\View + */ + public function getUserPastes($userId) + { + $perPage = Site::config('general')->perPage; + + // Remove the leading 'u' from the userId + $userId = substr($userId, 1); + + // Get all pastes for the specific author + $query = Paste::where('author_id', $userId); + + // Apply restrictions to non-admins + if ( ! Auth::roles()->admin) + { + $query = $query->where(function($query) + { + // Fetch all pastes belonging to the current user + $query->where('author_id', Auth::user()->id); + + // If paste doesn't belong to current user, hide if private + $query->orWhere('private', '<>', 1); + }); + } + + // Show latest first + $pastes = $query->orderBy('id', 'desc')->paginate($perPage); + + return $this->getList($pastes); + } + + /** + * Searches for a paste by its content + * + * @access public + * @param string $term + * @return \Illuminate\Support\Facades\View + */ + public function getSearch() + { + $term = Input::get('q'); + + $config = Site::config('general'); + + // Initialize the antispam filters + $antispam = Antispam::make('search', 'q'); + + if ($config->pasteSearch AND strlen($term) >= 5) + { + if ($antispam->passes() OR Session::has('search.exempt')) + { + // Show all pastes to admins + if (Auth::roles()->admin) + { + $query = Paste::query(); + } + else + { + $query = Paste::where('private', '<>', 1); + } + + // Append the search term + $query = $query->where('data', 'like', "%{$term}%"); + + // Filter by project + if ( ! empty($this->project)) + { + $query = $query->where('project', $this->project); + } + + // Get number of results to show per page + $perPage = $config->perPage; + + // Query the search results + $pastes = $query->orderBy('id', 'desc')->paginate($perPage); + + // Append the search term to pagination URLs + $pastes->appends('q', $term); + + // We will not run antispam if it passed once and there are + // multiple pages. But we exempt it only for the next request. + Session::flash('search.exempt', $perPage > $pastes->count()); + + return $this->getList($pastes, TRUE); + } + else + { + Session::flash('messages.error', $antispam->message()); + } + } + + return Redirect::to('all')->withInput(); + } + + /** + * Searches for a paste by its content + * + * @access public + * @return \Illuminate\Support\Facades\View + */ + public function postSearch() + { + // Initialize the validator + $validator = Validator::make(Input::all(), array( + 'search' => 'required|min:5|max:500' + )); + + // Run the validation rules + if ($validator->passes()) + { + return Redirect::to('search?q='.Input::get('search')); + } + else + { + Session::flash('messages.error', $validator->messages()->all('

      :message

      ')); + + return Redirect::to('all')->withInput(); + } + } + + /** + * Displays a list of flagged pastes + * + * @access public + * @return \Illuminate\Support\Facades\View + */ + public function getFlagged() + { + $perPage = Site::config('general')->perPage; + + // Get all flagged pastes + $query = Paste::where('flagged', 1); + + // Filter by project + if ( ! empty($this->project)) + { + $query = $query->where('project', $this->project); + } + + $pastes = $query->orderBy('id', 'desc')->paginate($perPage); + + return $this->getList($pastes, TRUE); + } + + /** + * Parses and displays a list + * + * @param \Illuminate\Database\Eloquent\Model $pastes + * @param bool $showFilters + * @param bool $showSearch + * @return \Illuminate\Support\Facades\View + */ + private function getList($pastes, $showSearch = FALSE, $showFilters = FALSE) + { + // Check if no pastes were found + if ($pastes->count() === 0) + { + App::abort(418); // No pastes found + } + + // Output the view + $data = array( + 'pastes' => $pastes, + 'pages' => $pastes->links(), + 'filters' => $showFilters, + 'search' => $showSearch AND Site::config('general')->pasteSearch, + ); + + return View::make('site/list', $data); + } + +} diff --git a/app/controllers/SetupController.php b/app/controllers/SetupController.php new file mode 100755 index 000000000..f56bac3af --- /dev/null +++ b/app/controllers/SetupController.php @@ -0,0 +1,191 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * SetupController + * + * This controller handles app install and updates + * + * @package StickyNotes + * @subpackage Controllers + * @author Sayak Banerjee + */ +class SetupController extends BaseController { + + /** + * Shows the installation screen + * + * @param string $method + * @param string $action + * @return \Illuminate\Support\Facades\View + */ + public function getInstall($method = 'web', $action = '') + { + // Installation stage + $stage = Session::has('setup.stage') ? Session::get('setup.stage') : 1; + + // Output based on request method + switch ($method) + { + case 'web': + + $data = array( + 'error' => Session::get('messages.error'), + 'success' => Session::get('messages.success'), + 'unstable' => System::updated() > 0, + ); + + return View::make("setup/install/stage{$stage}", $data); + + case 'ajax': + + return Setup::install($action); + + case 'error': + + return View::make('setup/error'); + + default: + + App::abort(404); // Not found + } + } + + /** + * Handles POST requests for the install page + * + * @return \Illuminate\Support\Facades\Redirect + */ + public function postInstall() + { + // Stage 1 submitted + if (Input::has('_test')) + { + $status = Setup::testConnection(); + + if ($status === TRUE) + { + Session::put('setup.stage', 2); + + return Redirect::to('setup/install'); + } + } + + // Stage 2 submitted + if (Input::has('_install')) + { + Session::put('setup.stage', 3); + + return Redirect::to('setup/install'); + } + + // Setup complete + if (Input::has('_finish')) + { + // Submit site statistics + System::submitStats(); + + // Redirect to login page + return Redirect::to('user/login'); + } + } + + /** + * Shows the update screen + * + * @param string $method + * @param string $action + * @return \Illuminate\Support\Facades\View + */ + public function getUpdate($method = 'web', $action = '') + { + // Updater stage + $stage = Session::has('setup.stage') ? Session::get('setup.stage') : 1; + + // Output based on request method + switch ($method) + { + case 'web': + + $data = array( + 'error' => Session::get('messages.error'), + 'success' => Session::get('messages.success'), + 'version' => Session::get('setup.version'), + 'versions' => Setup::updateVersions(), + 'messages' => Setup::messages(), + 'unstable' => System::updated() > 0, + ); + + return View::make("setup/update/stage{$stage}", $data); + + case 'ajax': + + return Setup::update($action); + + case 'error': + + return View::make('setup/error'); + + default: + + App::abort(404); // Not found + } + } + + /** + * Handles POST requests for the update page + * + * @return \Illuminate\Support\Facades\Redirect + */ + public function postUpdate() + { + // Stage 1 submitted + if (Input::has('_update')) + { + // Define validation rules + $validator = Validator::make(Input::all(), array( + 'version' => 'required|in:'.Setup::updateVersions(TRUE), + )); + + // Run the validator + if ($validator->passes()) + { + Session::put('setup.version', Input::get('version')); + + Session::put('setup.stage', 2); + + return Redirect::to('setup/update'); + } + else + { + Session::flash('messages.error', $validator->messages()->all('

      :message

      ')); + + return Redirect::to('setup/update')->withInput(); + } + } + + // Setup complete + if (Input::has('_finish')) + { + // Submit site statistics + System::submitStats(); + + // Redirect to home page + return Redirect::to('/'); + } + } + +} diff --git a/app/controllers/ShowController.php b/app/controllers/ShowController.php new file mode 100755 index 000000000..5e93bfc1e --- /dev/null +++ b/app/controllers/ShowController.php @@ -0,0 +1,387 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * ShowController + * + * This controller handles displaying of a paste + * + * @package StickyNotes + * @subpackage Controllers + * @author Sayak Banerjee + */ +class ShowController extends BaseController { + + /** + * Displays the default view page + * + * @access public + * @param string $urlkey + * @param string $hash + * @param string $action + * @param string $extra + * @return \Illuminate\Support\Facades\View|\Illuminate\Support\Facades\Redirect|null + */ + public function getPaste($urlkey, $hash = '', $action = '', $extra = '') + { + $site = Site::config('general'); + + $paste = Paste::where('urlkey', $urlkey)->first(); + + // Paste was not found + if (is_null($paste)) + { + App::abort(404); // Not found + } + + // Check if the logged in user is the owner of the paste + $owner = Auth::access($paste->author_id); + + // We do not make password prompt mandatory for owners + if ( ! $owner) + { + // Require hash to be passed for private pastes + if ($paste->private AND $paste->hash != $hash) + { + App::abort(401); // Unauthorized + } + + // Check if paste is password protected and user hasn't entered + // the password yet + if ($paste->password AND ! Session::has('paste.password'.$paste->id)) + { + return View::make('site/password', array()); + } + } + + // Increment the hit counter + if ( ! Session::has('paste.viewed'.$paste->id)) + { + $paste->hits++; + + $paste->save(); + + Session::put('paste.viewed'.$paste->id, TRUE); + } + + // Let's do some action! + switch ($action) + { + case 'delete': + + if (empty($extra)) + { + // Delete the paste if the user has access + if ($site->allowPasteDel AND $owner) + { + Revision::where('urlkey', $paste->urlkey)->delete(); + + $paste->comments()->delete(); + + $attachment = storage_path()."/uploads/{$paste->urlkey}"; + + if ($paste->attachment AND File::exists($attachment)) + { + File::delete($attachment); + } + + $paste->delete(); + + Session::flash('messages.success', Lang::get('global.paste_deleted')); + + return Redirect::to('/'); + } + else + { + App::abort(401); // Unauthorized + } + } + else if (is_numeric($extra)) + { + $comment = Comment::findOrFail($extra); + + // Delete the comment if the user has access + if ($owner OR Auth::user()->username == $comment->author) + { + $comment->delete(); + } + else + { + App::abort(401); // Unauthorized + } + } + + return Redirect::to(URL::previous()); + + case 'raw': + + $response = Response::make($paste->data); + + $response->header('Content-Type', 'text/plain'); + + return $response; + + case 'toggle': + + if ($owner) + { + Revision::where('urlkey', $paste->urlkey)->delete(); + + $paste->private = $paste->private ? 0 : 1; + + $paste->password = ''; + + $paste->save(); + } + + return Redirect::to(URL::previous()); + + case 'flag': + + if ($site->flagPaste == 'all' OR ($site->flagPaste == 'user' AND Auth::roles()->user)) + { + $paste->flagged = 1; + + $paste->save(); + + Cache::forget('global.flags'); + + Session::flash('messages.success', Lang::get('global.paste_flagged')); + } + else + { + App::abort(401); // Unauthorized + } + + return Redirect::to(URL::previous()); + + case 'unflag': + + if (Auth::roles()->admin) + { + $paste->flagged = 0; + + $paste->save(); + + Cache::forget('global.flags'); + + Session::flash('messages.success', Lang::get('global.paste_unflagged')); + } + else + { + App::abort(401); // Unauthorized + } + + return Redirect::to(URL::previous()); + } + + // Build the sharing subject for the paste + $subject = sprintf(Lang::get('mail.share_subject'), $site->title, URL::current()); + + // Build data for show paste page + $data = array( + 'paste' => $paste, + 'revisions' => $paste->revisions, + 'comments' => $paste->comments()->paginate($site->perPage), + 'share' => 'mailto:?subject='.urlencode($subject), + 'attachment' => sprintf(Lang::get('show.download_attachment'), Lang::get('show.unknown')), + ); + + // If paste has an attachment, get the file type + if ($paste->attachment) + { + $pathToFile = storage_path()."/uploads/{$paste->urlkey}"; + + if (File::exists($pathToFile)) + { + $file = new Symfony\Component\HttpFoundation\File\File($pathToFile); + + $data['attachment'] = sprintf(Lang::get('show.download_attachment'), $file->getMimeType()); + } + } + + // Display the show paste view + return View::make('site/show', $data); + } + + /** + * Handles the paste password submission + * + * @param string $urlkey + * @param string $hash + * @return \Illuminate\Support\Facades\Redirect|null + */ + public function postPassword($urlkey, $hash = '') + { + $paste = Paste::where('urlkey', $urlkey)->first(); + + if ( ! is_null($paste) AND Input::has('password')) + { + $entered = Input::get('password'); + + if (PHPass::make()->check('Paste', $entered, $paste->salt, $paste->password)) + { + Session::put("paste.password{$paste->id}", TRUE); + + return Redirect::to("{$urlkey}/{$hash}"); + } + } + + // Something wrong here + App::abort(401); + } + + /** + * Shows a diff between two pastes + * + * @param string $oldkey + * @param string $newkey + * @return \Illuminate\Support\Facades\View + */ + public function getDiff($oldkey, $newkey) + { + // Generate the paste differences + $diff = PHPDiff::make()->compare($oldkey, $newkey); + + // Build the view data + $data = array( + 'diff' => $diff, + 'oldkey' => $oldkey, + 'newkey' => $newkey, + ); + + return View::make('site/diff', $data); + } + + /** + * Triggers download action for a paste's attachment + * + * @param string $urlkey + * @param string $hash + * @return \Illuminate\Support\Facades\View + */ + public function getAttachment($urlkey, $hash = '') + { + $paste = Paste::where('urlkey', $urlkey)->first(); + + // Paste and/or attachment was not found + if (is_null($paste)) + { + App::abort(404); // Not found + } + + // Check if the logged in user is the owner of the paste + $owner = Auth::access($paste->author_id); + + // We do not make password prompt mandatory for owners + if ( ! $owner) + { + // Require hash to be passed for private pastes + if ($paste->private AND $paste->hash != $hash) + { + App::abort(401); // Unauthorized + } + + // Check if paste is password protected and user hasn't entered + // the password yet + if ($paste->password AND ! Session::has('paste.password'.$paste->id)) + { + return View::make('site/password', array()); + } + } + + // Find the attachment, and process the download + if ($paste->attachment) + { + $pathToFile = storage_path()."/uploads/{$paste->urlkey}"; + + if (File::exists($pathToFile)) + { + return Response::download($pathToFile); + } + } + + // If we are here, the attachment wasn't found + App::abort(404); + } + + /** + * Handles the paste password submission + * + * @param string $urlkey + * @param string $hash + * @return \Illuminate\Support\Facades\Redirect|null + */ + public function postComment() + { + if (Site::config('general')->comments) + { + // Define validation rules + $validator = Validator::make(Input::all(), array( + 'comment' => 'required|auth|min:5|max:1024', + )); + + // Generate anti-spam modules + $antispam = Antispam::make('comment', 'comment'); + + // Run validations + $resultValidation = $validator->passes(); + + // Execute antispam services + $resultAntispam = $antispam->passes(); + + if ($resultValidation AND $resultAntispam) + { + // Get the associated paste + $paste = Paste::findOrFail(Input::get('id')); + + // Insert the new comment + if ( ! is_null($paste)) + { + $comment = new Comment; + + $comment->paste_id = $paste->id; + $comment->data = nl2br(strip_tags(Input::get('comment'))); + $comment->author = Auth::check() ? Auth::user()->username : Lang::get('global.anonymous'); + $comment->timestamp = time(); + + $comment->save(); + } + + return Redirect::to(URL::previous()); + } + else + { + // Set the error message as flashdata + if ( ! $resultValidation) + { + Session::flash('messages.error', $validator->messages()->all('

      :message

      ')); + } + else if ( ! $resultAntispam) + { + Session::flash('messages.error', $antispam->message()); + } + + return Redirect::to(URL::previous())->withInput(); + } + } + else + { + App::abort(401); // Unauthorized + } + } + +} diff --git a/app/controllers/UserController.php b/app/controllers/UserController.php new file mode 100755 index 000000000..5de2d6fa0 --- /dev/null +++ b/app/controllers/UserController.php @@ -0,0 +1,307 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * UserController + * + * This controller handles users and their sessions + * + * @package StickyNotes + * @subpackage Controllers + * @author Sayak Banerjee + */ +class UserController extends BaseController { + + /** + * Displays the user login page + * + * @access public + * @return \Illuminate\Support\Facades\View + */ + public function getLogin() + { + $auth = Site::config('auth'); + + // Directly attempt auth if a method is selected that does not support + // the login form + $noForm = preg_split('/\||,/', $auth->noForm); + + if (in_array($auth->method, $noForm)) + { + Auth::attempt(); + + return Redirect::to('/'); + } + else + { + return View::make('user/login'); + } + } + + /** + * Handles user authentication requests + * + * @access public + * @return \Illuminate\Support\Facades\Redirect + */ + public function postLogin() + { + // Define validation rules + $validator = Validator::make(Input::all(), array( + 'username' => 'required', + 'password' => 'required' + )); + + // Run the validator + if ($validator->passes()) + { + $remember = Input::has('remember'); + + $success = Auth::attempt(array( + 'username' => Input::get('username'), + 'password' => Input::get('password') + ), $remember); + + if ($success) + { + return Redirect::intended('/'); + } + else + { + // Auth failed, show error message + Session::flash('messages.error', Lang::get('user.auth_fail')); + } + } + else + { + // Set the error message as flashdata + Session::flash('messages.error', $validator->messages()->all('

      :message

      ')); + } + + return Redirect::to('user/login')->withInput(); + } + + /** + * Shows the user registration screen + * + * @access public + * @return \Illuminate\Support\Facades\View + */ + public function getRegister() + { + return View::make('user/register'); + } + + /** + * Handles POST requests on the registration screen + * + * @access public + * @return \Illuminate\Support\Facades\Redirect + */ + public function postRegister() + { + // Define validation rules + $rules = array( + 'username' => 'required|max:50|alpha_dash|unique:users,username,-1,id,type,db', + 'email' => 'required|max:100|email|unique:users,email,-1,id,type,db', + 'dispname' => 'max:100', + 'password' => 'required|min:5', + ); + + // Check if captcha is enabled, and if it is, validate it + if (Site::config('auth')->dbShowCaptcha) + { + $rules['captcha'] = 'required|captcha'; + } + + $validator = Validator::make(Input::all(), $rules); + + // Run the validator + if ($validator->passes()) + { + $user = new User; + + $user->username = Input::get('username'); + $user->email = Input::get('email'); + $user->dispname = Input::get('dispname'); + $user->salt = str_random(5); + $user->password = PHPass::make()->create(Input::get('password'), $user->salt); + $user->admin = 0; + + $user->save(); + + Session::flash('messages.success', Lang::get('user.register_done')); + + return Redirect::to('user/login'); + } + else + { + Session::flash('messages.error', $validator->messages()->all('

      :message

      ')); + + return Redirect::to('user/register')->withInput(); + } + } + + /** + * Handles user logout + * + * @access public + * @return \Illuminate\Support\Facades\Redirect + */ + public function getLogout() + { + Auth::logout(); + + return Redirect::to('/'); + } + + /** + * Displays the password reset screen + * + * @access public + * @return \Illuminate\Support\Facades\View + */ + public function getForgot() + { + return View::make('user/forgot'); + } + + /** + * Handles POST requests to the password reset form + * + * @access public + * @return \Illuminate\Support\Facades\Redirect + */ + public function postForgot() + { + // Define validation rules + $validator = Validator::make(Input::all(), array( + 'username' => 'required|exists:users,username,type,db', + )); + + // Run the validator + if ($validator->passes()) + { + // Generate a random password + $password = str_random(8); + + // Now we update the password in the database + $user = User::where('username', Input::get('username'))->where('type', 'db')->first(); + + $user->password = PHPass::make()->create($password, $user->salt); + + $user->save(); + + // Build the email template + $data = array_merge(View::defaults(), array( + 'name' => $user->dispname ?: $user->username, + 'password' => $password, + )); + + // Send the notification mail + Mail::queue('templates/email/forgot', $data, function($message) use ($user) + { + $message->to($user->email)->subject(Lang::get('mail.forgot_subject')); + }); + + // All done! + Session::flash('messages.success', Lang::get('user.reset_done')); + + return Redirect::to('user/login'); + } + else + { + Session::flash('messages.error', $validator->messages()->all('

      :message

      ')); + + return Redirect::to('user/forgot')->withInput(); + } + } + + /** + * Displays the user profile screen + * + * @access public + * @return \Illuminate\Support\Facades\View + */ + public function getProfile() + { + return View::make('user/profile'); + } + + /** + * Handles POST requests on the user profile + * + * @access public + * @return \Illuminate\Support\Facades\Redirect + */ + public function postProfile() + { + $user = Auth::user(); + + // Define validation rules + $rules = array( + 'username' => 'max:50|alpha_dash|unique:users,username,'.$user->id.',id,type,db', + 'email' => 'required|max:100|email|unique:users,email,'.$user->id.',id,type,db', + 'dispname' => 'max:100', + 'password' => 'min:5', + ); + + $validator = Validator::make(Input::all(), $rules); + + // Run the validator + if ($validator->passes()) + { + $origUsername = $user->username; + + $user->username = $user->admin ? Input::get('username') : $user->username; + $user->email = Input::get('email'); + $user->dispname = Input::get('dispname'); + + if (Input::has('password')) + { + $user->password = PHPass::make()->create(Input::get('password'), $user->salt); + } + + $user->save(); + + // Update cached username in the main table + Paste::where('author_id', $user->id)->update(array( + 'author' => $user->username, + )); + + // Update cached username in the revisions table + Revision::where('author', $origUsername)->update(array( + 'author' => $user->username, + )); + + // Update cached username in the comments table + Comment::where('author', $origUsername)->update(array( + 'author' => $user->username, + )); + + Session::flash('messages.success', Lang::get('user.profile_saved')); + + return Redirect::to('user/profile'); + } + else + { + Session::flash('messages.error', $validator->messages()->all('

      :message

      ')); + + return Redirect::to('user/profile')->withInput(); + } + } + +} diff --git a/app/database/migrations/.gitkeep b/app/database/migrations/.gitkeep new file mode 100755 index 000000000..e69de29bb diff --git a/app/database/production.sqlite b/app/database/production.sqlite new file mode 100755 index 000000000..e69de29bb diff --git a/app/database/seeds/.gitkeep b/app/database/seeds/.gitkeep new file mode 100755 index 000000000..e69de29bb diff --git a/app/database/seeds/DatabaseSeeder.php b/app/database/seeds/DatabaseSeeder.php new file mode 100755 index 000000000..6a8c204c9 --- /dev/null +++ b/app/database/seeds/DatabaseSeeder.php @@ -0,0 +1,17 @@ +call('UserTableSeeder'); + } + +} \ No newline at end of file diff --git a/app/database/testing.sqlite b/app/database/testing.sqlite new file mode 100755 index 000000000..39e43d2a1 Binary files /dev/null and b/app/database/testing.sqlite differ diff --git a/app/filters.php b/app/filters.php new file mode 100755 index 000000000..dbd7095eb --- /dev/null +++ b/app/filters.php @@ -0,0 +1,276 @@ +guest) + { + return Redirect::guest('user/login'); + } +}); + +/* +|-------------------------------------------------------------------------- +| Enforce Authentication Filter +|-------------------------------------------------------------------------- +| +| The "enforce" filter redirects the user to log in if the site is +| configured to disallow guest posts. +| +*/ + +Route::filter('auth.enforce', function() +{ + if (Auth::roles()->guest AND ! Site::config('general')->guestPosts) + { + return Redirect::guest('user/login'); + } +}); + +/* +|-------------------------------------------------------------------------- +| Authentication Database Filters +|-------------------------------------------------------------------------- +| +| The following filters are used to verify that DB based auth is enabled. +| An additional reg allowed check is performed for POST to register page. +| +*/ + +Route::filter('auth.db', function() +{ + $auth = Site::config('auth'); + + if ($auth->method != 'db') + { + App::abort(403); // Forbidden + } + + if (Request::segment(2) == 'register' AND ! $auth->dbAllowReg) + { + App::abort(403); // Forbidden + } +}); + +/* +|-------------------------------------------------------------------------- +| Guest Filter +|-------------------------------------------------------------------------- +| +| The "guest" filter is the counterpart of the authentication filters as +| it simply checks that the current user is not logged in. A redirect +| response will be issued if they are, which you may freely change. +| +*/ + +Route::filter('guest', function() +{ + if (Auth::roles()->user) + { + return Redirect::to('/'); + } +}); + +/* +|-------------------------------------------------------------------------- +| CSRF Protection Filter +|-------------------------------------------------------------------------- +| +| The CSRF filter is responsible for protecting your application against +| cross-site request forgery attacks. If this special token in a user +| session does not match the one given in this request, we'll bail. +| +| CSRF protection is not applied to API POST requests. +| +*/ + +Route::filter('csrf', function() +{ + if (Site::config('general')->csrf) + { + if (Request::segment(1) != 'api' AND php_sapi_name() != 'cli') + { + if (Session::token() != Input::get('_token')) + { + throw new Illuminate\Session\TokenMismatchException; + } + } + } +}); + +/* +|-------------------------------------------------------------------------- +| Admin only filter +|-------------------------------------------------------------------------- +| +| This filter validates that the logged in user is an administrator. +| +*/ + +Route::filter('admin', function() +{ + if ( ! Auth::roles()->admin) + { + App::abort(403); // Forbidden + } +}); + +/* +|-------------------------------------------------------------------------- +| Private site filter +|-------------------------------------------------------------------------- +| +| This filter validates whether the site is set as private (i.e. disallows +| public pastes) and if so, it throws a 401 for the list routes +| +*/ + +Route::filter('private', function() +{ + if (Site::config('general')->pasteVisibility == 'private' AND ! Auth::roles()->admin) + { + App::abort(403); // Forbidden + } +}); + +/* +|-------------------------------------------------------------------------- +| Numeric paste ID filter +|-------------------------------------------------------------------------- +| +| This filter gets a paste by its numeric ID. This is here purely for +| backward compatibility as 0.4 and older versions had an optional / did +| not have a alphanumeric URLkey. +| +*/ + +Route::filter('numeric', function() +{ + $key = Request::segment(1); + + $hash = Request::segment(2); + + if (is_numeric($key) AND $key <= Site::config('general')->preMigrate) + { + $paste = Paste::findOrFail($key); + + return Redirect::to("{$paste->urlkey}/{$hash}"); + } +}); + +/* +|-------------------------------------------------------------------------- +| Setup validation filter +|-------------------------------------------------------------------------- +| +| This filter checks if Sticky Notes is marked as installed. +| +| The following checks are done: +| - If the main table does not exist, it is a fresh install +| - If the main table is there, but versions mismatch, it is an update +| - If main table is there and versions match, we should get out of setup +| +*/ + +Route::filter('installed', function() +{ + // Determine if the system is installed + $installed = System::installed(); + + // Now we get the app and DB versions + // If there is no version data in the DB, the function will return 0 + $appVersion = System::version(Config::get('app.version')); + + $dbVersion = System::version(Site::config('general')->version); + + // We clear the cache to verify if there is a version mismatch + // This usually should not be required but we do this to avoid the + // update screen from popping up when we the user updates the + // sticky-notes code + if ($appVersion > $dbVersion) + { + Cache::flush(); + + $dbVersion = System::version(Site::config('general')->version); + } + + // Redirect to setup pages based on version checks + if (Request::segment(1) != 'setup') + { + // Redirect to the installer + if ( ! $installed) + { + Setup::start(); + + return Redirect::to('setup/install'); + } + + // Redirect to the updater, with the exception of the login page + else if (Request::segment(2) != 'login') + { + if ($appVersion > $dbVersion) + { + Setup::start(); + + return Redirect::to('setup/update'); + } + + // At this stage, it is safe to run version dependent modules + else + { + // Run Google Analytics visitor tracking + Service::analytics(); + + // Set global admin messages + View::globals(); + + // Run cron tasks + Cron::run(); + } + } + } + + // Only admins can access this page + // We check for dbVersion as 0.4 will not support the Auth functions + else if (Request::segment(2) == 'update' AND $dbVersion > 0 AND Auth::roles()->guest) + { + App::abort(503); // Service unavailable + } + + // You should not be here! + else if ($installed AND $appVersion == $dbVersion AND ! Session::has('setup.stage')) + { + return Redirect::to('/'); + } +}); diff --git a/app/lang/en/admin.php b/app/lang/en/admin.php new file mode 100755 index 000000000..97b577f96 --- /dev/null +++ b/app/lang/en/admin.php @@ -0,0 +1,264 @@ + "Dashboard", + "site_settings" => "Site settings", + "manage_pastes" => "Manage pastes", + "manage_users" => "Manage users", + "ban_an_ip" => "Ban an IP", + "mail_settings" => "Mail settings", + "authentication" => "Authentication", + "auth_settings" => "Authentication settings", + "spam_filters" => "Spam filters", + "skin_chooser" => "Skin chooser", + "services" => "Services", + "field" => "Field", + "value" => "Value", + "save_all" => "Save all", + "posted_at" => "Posted at", + "expires_at" => "Expires at", + "is_private" => "Is private", + "has_password" => "Has password", + "poster_ip" => "Poster's IP", + "remove_password" => "Remove password", + "remove_attachment" => "Remove attachment", + "attachment_deleted" => "The attachment has been removed successfully", + "paste_exp" => "Enter the paste ID above and click search", + "paste_404" => "No paste found with the given ID", + "user_404" => "No user found with the given username", + "user_editor" => "User editor", + "user_saved" => "The user has been saved successfully", + "user_deleted" => "The user has been deleted successfully", + "user_del_fail" => "You cannot delete this user", + "user_create" => "Create new user", + "user_auth_method" => "Users created by the %s auth method cannot be modified ". + "using this module.", + "ip_address" => "IP address", + "ban" => "Ban", + "unban" => "Unban", + "no_banned_ip" => "There are no banned IP addresses", + "ip_banned" => "IP address added to ban list", + "ip_unbanned" => "IP address removed from ban list", + "mail_updated" => "Mail settings updated successfully", + "driver" => "Driver", + "smtp_host" => "SMTP host", + "smtp_port" => "SMTP port", + "from_address" => "From address", + "from_name" => "From name", + "encryption" => "Encryption", + "smtp_username" => "SMTP username", + "smtp_password" => "SMTP password", + "sendmail_path" => "Sendmail path", + "smtp" => "SMTP", + "mail" => "PHP mail", + "sendmail" => "Sendmail", + "ssl" => "SSL", + "tls" => "TLS", + "test_mail_settings" => "Test mail settings", + "test_mail_success" => "Mail server connectivity validated successfully", + "test_mail_error" => "Mail test failed: %s", + "none" => "None", + "fqdn" => "FQDN", + "fqdn_exp" => "The fully qualified domain name for the server. This is ". + "used to determine the project level sub-domains.", + "site_title" => "Site title", + "copyright" => "Copyright", + "copyright_exp" => "This copyright notice will be displayed in your site's footer. You can ". + "use HTML markup here.", + "language" => "Language", + "ajax_nav" => "AJAX mode", + "ajax_nav_exp" => "Enable AJAX to allow faster navigation and to save bandwidth.", + "list_length" => "List length", + "list_length_exp" => "Sets the number of items displayed per page in a list.", + "paste_age" => "Paste age", + "paste_age_exp" => "This is the default expiration time for pastes.", + "expire_30mins" => "30 minutes", + "expire_6hrs" => "6 hours", + "expire_1day" => "1 day", + "expire_1week" => "1 week", + "expire_1month" => "1 month", + "expire_1year" => "1 year", + "expire_forever" => "Keep forever", + "expiration" => "Expiration", + "expiration_exp" => "This setting determines the maximum age for pastes created on this site.", + "noexpire_none" => "Everyone must set a finite expiration time", + "noexpire_user" => "Registered users can create pastes that do not expire", + "noexpire_all" => "Every one can create pastes that do not expire", + "ip_tracking" => "IP tracking", + "ip_tracking_exp" => "Set this option to trust proxy headers if your site is behind a proxy ". + "intermediary (such as a load balancer) to get the actual client IP address.", + "trust_proxy" => "Trust proxy headers", + "ignore_proxy" => "Ignore proxy headers", + "visibility" => "Visibility", + "visibility_exp" => "Public-only sites do not allow creating private or password protected pastes. ". + "Private sites will create all pastes as 'private' and will disable the archives ". + "sections.", + "csrf_token" => "CSRF token", + "csrf_token_exp" => "Validate form tokens across the site to prevent cross-site request forgery.", + "guest_posts" => "Guest posts", + "guest_posts_exp" => "If set to enabled, unregistered users will be able to create pastes and add ". + "comments to pastes.", + "allow_all" => "Allow public and private pastes", + "enforce_public" => "Enforce public pastes", + "enforce_private" => "Enforce private pastes", + "paste_search" => "Paste search", + "paste_search_exp" => "Enable or disable paste search functionality on archives.", + "comments_exp" => "Allow users to comment on pastes.", + "share" => "Sharing", + "share_exp" => "Allow users to share pastes by email.", + "flagging" => "Flagging", + "flagging_exp" => "Choose who can flag pastes for administrator review.", + "flag_all" => "Everyone", + "flag_user" => "Registered users", + "flag_off" => "Nobody", + "delete_pastes" => "Delete pastes", + "delete_pastes_exp" => "Allow users to delete their own pastes. Admins can always delete any pastes.", + "attachment" => "Attachment", + "attachment_exp" => "Allow users to attach an image to their paste.", + "site_updated" => "Site settings updated successfully", + "word_censor" => "Word censor", + "word_censor_exp" => "This module allows you to block pastes that contain specific words. ". + "You can use * as a wildcard character.", + "phrases" => "Phrases", + "phrases_exp" => "Enter each censored phrase in a new line.", + "stealth" => "Stealth", + "stealth_exp" => "Stealth is an spam filter that limits the number of hyperlinks in a paste ". + "when the language is selected as 'text'.", + "noflood" => "Flood control", + "noflood_exp" => "Drops pastes originating from the same user if it is posted before a ". + "defined threshold duration.", + "threshold" => "Threshold", + "threshold_exp" => "Users will have to wait these many seconds between each paste.", + "seconds" => "seconds", + "akismet" => "Akismet", + "akismet_exp" => "Akismet is an automated spam filter that analyzes pastes and filters out ". + "potential spam entries.", + "akismet_key" => "Akismet key", + "akismet_key_exp" => "You can get an Akismet API key from", + "honeypot" => "Honeypot", + "honeypot_exp" => "Project Honey Pot is a web based honeypot network which uses software ". + "embedded in web sites to collect information about IP addresses used when ". + "harvesting e-mail addresses for spam or other similar purposes such as ". + "bulk mailing and e-mail fraud.", + "honeypot_more" => "Fore more information on these configuration values, check", + "access_key" => "Access key", + "access_key_exp" => "Get your access key at", + "age_threshold" => "Age threshold", + "age_threshold_exp" => "PHP responses older than these no. of days will be ignored.", + "threat_score" => "Threat score", + "threat_score_exp" => "IPs with PHP threat score greater than or equal to this will be disallowed.", + "visitor_filter" => "Visitor filter", + "visitor_filter_exp" => "Visitor type greater than or equal to this will be disallowed.", + "enable_filter" => "Enable this filter", + "runs_on" => "Runs on: %s", + "max_links" => "Max. links", + "max_links_exp" => "This defines the maximum number of hyperlinks allowed in a paste. A user ". + "who submits more number of links than this will be required to choose a ". + "paste language other than 'text'.", + "antispam_updated" => "Spam filter settings have been updated successfully", + "auth_method" => "Auth method", + "banner_text" => "Banner text", + "banner_text_exp" => "This text will be displayed above the login form.", + "db" => "Database", + "ldap" => "LDAP", + "oauth" => "oAuth", + "ldap_server" => "LDAP server", + "ldap_server_exp" => "If using LDAP this is the hostname or IP address of the LDAP server.", + "ldap_port" => "LDAP port", + "ldap_port_exp" => "Optionally you can specify a port which should be used to connect to ". + "the LDAP server instead of the default port 389.", + "base_dn" => "Base dn", + "base_dn_exp" => "This is the Distinguished Name, locating the user information, e.g. ". + "o=My Company,c=US.", + "uid" => "Identity uid", + "uid_exp" => "This is the key under which to search for a given login identity, ". + "e.g. uid, sn, etc.", + "user_filter" => "User filter", + "user_filter_exp" => "Optionally you can further limit the searched objects with additional ". + "filters. For example objectClass=posixGroup would result in the use of ". + "(&(uid=\$username)(objectClass=posixGroup)).", + "admin_group" => "Admin group", + "admin_group_exp" => "Specify an administrator group in the format objectClass=posixGroup where ". + "posixGroup is the admin group name in your LDAP user store.", + "user_dn" => "User dn", + "user_dn_exp" => "Leave blank to use anonymous binding. If filled in, sticky-notes uses ". + "the specified distinguished name on login attempts to find the correct ". + "user, e.g. uid=Username,ou=MyUnit,o=MyCompany,c=US. Required for Active ". + "Directory Servers.", + "ldap_password_exp" => "Leave blank to use anonymous binding, otherwise fill in the password for ". + "the above user. Required for Active Directory Servers.
      ". + "Warning: This password will be stored as plain text in the database, ". + "visible to everybody who can access the sticky-notes DB.", + "user_reg" => "Registration", + "user_reg_exp" => "Set this to disabled to stop new user account registrations.", + "reg_captcha" => "Captcha module", + "reg_captcha_exp" => "Enable or disable display of a visual verification field on the registration screen.", + "info_url" => "Info URL", + "info_url_exp" => "This link will be displayed next to the Login button on the user login form. ". + "You may use this link to point to a manual page or an external registration page when ". + "using a non-DB authentication method.", + "info_url_text" => "Info URL text", + "info_url_text_exp" => "Text for above link (eg. Identity Registration Page).", + "client_id" => "Client ID", + "client_secret" => "Client secret", + "client_secret_exp" => "You can generate client ID and secret key at the", + "admin_emails" => "Admin emails", + "admin_emails_exp" => "Users with these email addresses will be granted admin access. Please enter one email ". + "address per line.", + "enabled" => "Enabled", + "disabled" => "Disabled", + "auth_updated" => "User authentication settings have been updated successfully", + "system" => "System", + "data" => "Data", + "paste_stats" => "Paste statistics", + "stat_no_data" => "Not enough data has been collected to display paste statistics.", + "versions" => "Versions", + "users" => "Users", + "pastes" => "Pastes", + "general" => "General", + "content" => "Content", + "system_load" => "System load", + "db_driver" => "DB driver", + "php_version" => "PHP version", + "stickynotes_version" => "Sticky Notes version", + "date" => "Date", + "web" => "Web", + "api" => "API", + "skin_applied" => "The selected skin has been applied to your site", + "skin_version" => "The selected skin is incompatible with this version Sticky Notes", + "skin_error" => "The selected skin cannot be used as it is invalid", + "version" => "Version", + "use_theme" => "Use this theme", + "active" => "Active", + "status" => "Status", + "role" => "Role", + "google" => "Google", + "google_api_key" => "API Key", + "google_api_key_exp" => "You can generate an Google API key at the", + "google_cloud_console" => "Google Cloud Console", + "google_analytics" => "Analytics", + "google_analytics_exp" => "Specify your Google Analytics tracking ID here. To disable tracking, simply leave ". + "this field blank.", + "services_updated" => "Services settings updated successfully", + "banners" => "Banners", + "banners_exp" => "Site banners allow you to place custom HTML content within placeholders on all screens. ". + "You can typically use banners to place advertisements on your website.", + "allowed_tags" => "Allowed HTML tags: %s", + "banner_top" => "Top banner", + "banner_bottom" => "Bottom banner", + "size_limit" => "Size limit", + "size_limit_exp" => "Maximum allowed size for the paste data. Set this to 0 to allow paste data to be as big ". + "as the size supported by the database field itself.", + "bytes" => "bytes", + +); diff --git a/app/lang/en/ajax.php b/app/lang/en/ajax.php new file mode 100755 index 000000000..c1aaca12e --- /dev/null +++ b/app/lang/en/ajax.php @@ -0,0 +1,18 @@ + "You have the latest version of Sticky Notes", + "version_old" => "A newer version of Sticky Notes is available", + "error" => "Error", + +); diff --git a/app/lang/en/antispam.php b/app/lang/en/antispam.php new file mode 100755 index 000000000..429c43dfa --- /dev/null +++ b/app/lang/en/antispam.php @@ -0,0 +1,22 @@ + "You must select a language other than 'text' for this paste.", + "php" => "Your IP address is listed as a malicious IP.", + "ipban" => "Your IP address has been banned.", + "noflood" => "Please keep an interval of 5 seconds between your posts.", + "censor" => "Your post contains a phrase that has been censored.", + "token" => "Invalid form token, please submit your post again.", + "akismet" => "Your post triggered our spam filter and has been dropped.", + +); diff --git a/app/lang/en/create.php b/app/lang/en/create.php new file mode 100755 index 000000000..612f27f89 --- /dev/null +++ b/app/lang/en/create.php @@ -0,0 +1,26 @@ + "Mark as private", + "expire_30mins" => "for 30 minutes", + "expire_6hrs" => "for 6 hours", + "expire_1day" => "for 1 day", + "expire_1week" => "for 1 week", + "expire_1month" => "for 1 month", + "expire_1year" => "for 1 year", + "expire_forever" => "forever", + "click_for_paste" => "Click here to view your paste: %s", + "revise_private" => "You cannot revise a private paste", + +); diff --git a/app/lang/en/errors.php b/app/lang/en/errors.php new file mode 100755 index 000000000..8997d01a6 --- /dev/null +++ b/app/lang/en/errors.php @@ -0,0 +1,22 @@ + "You are not authorized to access this resource.", + "403" => "Access to this resource has been denied.", + "404" => "The page you are looking for does not exist.", + "405" => "This HTTP request method is not allowed.", + "418" => "The list does not contain any items", + "503" => "The site is undergoing maintenance", + "default" => "An error has occurred. Please try again later.", + +); diff --git a/app/lang/en/global.php b/app/lang/en/global.php new file mode 100755 index 000000000..3acf354b5 --- /dev/null +++ b/app/lang/en/global.php @@ -0,0 +1,67 @@ + "Sticky Notes", + "new_paste" => "New paste", + "archives" => "Archives", + "trending" => "Trending", + "feed" => "Feed", + "docs" => "Docs", + "siteadmin" => "Admin", + "my_profile" => "My profile", + "author" => "Author", + "username" => "Username", + "password" => "Password", + "email" => "E-mail", + "full_name" => "Full name", + "admin" => "Administrator", + "paste" => "Paste", + "yes" => "Yes", + "no" => "No", + "edit" => "Edit", + "delete" => "Delete", + "loading" => "Loading", + "edit_paste" => "Edit paste", + "make_private" => "Make private", + "make_public" => "Make public", + "login_to" => "Log in to %s", + "remember" => "Remember me", + "login" => "Login", + "logout" => "Logout", + "language" => "Language: %s", + "posted_by" => "Posted by %s at %s", + "views" => "Views: %s", + "anonymous" => "Anonymous", + "submit" => "Submit", + "search" => "Search", + "save" => "Save", + "share" => "Share", + "flag_paste" => "Flag as inappropriate", + "remove_flag" => "Remove flag", + "paste_pwd" => "This paste is password protected", + "paste_pvt" => "This paste is private", + "paste_id" => "Paste ID", + "paste_title" => "Paste title", + "paste_data" => "Paste data", + "paste_lang" => "Language", + "alert_flags" => "There are one or more flagged pastes.
      Click here to view them.", + "paste_deleted" => "The paste has been deleted successfully", + "paste_flagged" => "The paste has been flagged for administrator review", + "paste_unflagged" => "The paste has been unflagged", + "action_confirm" => "Are you sure you want to perform this action?", + "not_available" => "N/A", + "feed" => "Feed", + "comments" => "Comments", + "statistics" => "Rendered in: %.5f seconds • No. of queries: %d", + +); diff --git a/app/lang/en/list.php b/app/lang/en/list.php new file mode 100755 index 000000000..d449029b8 --- /dev/null +++ b/app/lang/en/list.php @@ -0,0 +1,23 @@ + "Show paste", + "filter" => "Filter", + "filter_now" => "Right now", + "filter_week" => "Last 7 days", + "filter_month" => "Last 30 days", + "filter_year" => "Last 365 days", + "filter_all" => "All time", + "search" => "Search", + +); diff --git a/app/lang/en/mail.php b/app/lang/en/mail.php new file mode 100755 index 000000000..6262949ff --- /dev/null +++ b/app/lang/en/mail.php @@ -0,0 +1,22 @@ + "Password reset notification", + "hello_user" => "Hello %s,", + "password_reset" => "Your account password for %s has been reset.", + "new_password" => "Your new password is: %s", + "click_login" => "Click here to log in: %s", + "autogen_mail" => "This is an auto generated email. Do not reply to this.", + "share_subject" => "%s: %s", + +); diff --git a/app/lang/en/pagination.php b/app/lang/en/pagination.php new file mode 100755 index 000000000..eb9be3baa --- /dev/null +++ b/app/lang/en/pagination.php @@ -0,0 +1,20 @@ + '« Previous', + + 'next' => 'Next »', + +); \ No newline at end of file diff --git a/app/lang/en/reminders.php b/app/lang/en/reminders.php new file mode 100755 index 000000000..e42148e9f --- /dev/null +++ b/app/lang/en/reminders.php @@ -0,0 +1,24 @@ + "Passwords must be at least six characters and match the confirmation.", + + "user" => "We can't find a user with that e-mail address.", + + "token" => "This password reset token is invalid.", + + "sent" => "Password reminder sent!", + +); diff --git a/app/lang/en/setup.php b/app/lang/en/setup.php new file mode 100755 index 000000000..bf1a6953b --- /dev/null +++ b/app/lang/en/setup.php @@ -0,0 +1,77 @@ + "Sticky Notes Installer", + "welcome" => "Thank you for choosing Sticky Notes. The installer will guide you throughout ". + "the install process. Follow the simple steps for a hassle free installation!", + "develop_warn" => "Important: You are attempting to set up a pre-release version. It is ". + "strongly recommended that you do not install development versions on production ". + "servers. To download the latest stable release instead, click here.", + "i_stage1_title" => "Stage 1: Database configuration", + "i_stage1_exp" => "Open your database configuration file located at app/config/database.php ". + "and fill up your DB details. Sticky Notes uses one active connection only, so make ". + "sure you change the 'default' database driver accordingly. See this guide for more information on DB configuration.", + "i_stage2_title" => "Stage 2: Ready to install", + "i_stage2_exp" => "Your database settings look good. Whenever you are ready, click on the Start ". + "installation button to begin the automated installed process.", + "i_stage3_title" => "Stage 3: Installation in progress", + "i_stage3_exp" => "Sticky Notes is being installed on your server. This may take several minutes...", + "i_stage4_title" => "Stage 4: Complete installation", + "i_stage4_exp" => "Sticky Notes has been successfully installed! Please make a note of the following ". + "credentials for logging into the admin panel.", + "u_stage1_title" => "Stage 1: Select update version", + "u_stage1_exp" => "The update utility upgrades your database tables with the latest data. You must be ". + "running one of the following versions of Sticky Notes previously in order to use this ". + "tool. Please make sure that you select the correct version below.", + "u_stage2_title" => "Stage 2: Update in progress", + "u_stage2_exp" => "Sticky Notes is being updated. This may take several minutes...", + "u_stage3_title" => "Stage 3: Update complete", + "u_stage3_exp" => "Sticky Notes has been successfully updated! You can now start using your new version.", + "proceed_login" => "Proceed to login →", + "click_check" => "Once you have filled in the correct DB details, click on the Test connection ". + "button.", + "update_config" => "If you wish to have your old site configuration (settings) migrated into the new Sticky Notes, ". + "please place your old config.php inside app/config folder.", + "test_connection" => "Test connection", + "test_fail" => "Database connection failed with the following error: %s", + "install_warn" => "Important: The installer will drop all Sticky Notes tables. If you are upgrading from ". + "an older version, use the %s instead.", + "update_util" => "update utility", + "start_install" => "Start installation", + "initializing" => "Initializing...", + "create_table" => "Creating table: %s", + "create_index" => "Creating indexes...", + "almost_done" => "Almost done...", + "install_complete" => "Installation complete.", + "update_complete" => "Update complete.", + "complete" => "complete", + "error_occurred" => "An error occurred", + "error_title" => "Setup failed", + "error_exp" => "An error occurred and setup has been aborted. The error message has been displayed below:", + "process_version" => "Installing new changes after version %s...", + "update_from" => "Update from", + "start_update" => "Start update", + "return_sn" => "Return to Sticky Notes →", + "ldap_update_warn" => "It appears that you have been using LDAP authentication before. Newer versions of Sticky Notes ". + "support logins for both users and admins in contrast to admin-only logins in older versions. ". + "In view of that, a new parameter admin filters has been added to your LDAP ". + "configuration options which you will need to set in the admin panel. Without that option, all ". + "users that log in will get admin privileges.", + "update_notifs" => "Notifications", + "update_notifs_exp" => "These are the notification messages generated by individual updaters that you have run as a part ". + "of the update process. These messages can contain important security warning, so it is strongly ". + "recommended that you read them and take the suggested measures.", + "notify_version" => "Version %s updater", + +); diff --git a/app/lang/en/show.php b/app/lang/en/show.php new file mode 100755 index 000000000..108aced5e --- /dev/null +++ b/app/lang/en/show.php @@ -0,0 +1,29 @@ + "Raw", + "wrap" => "Wrap", + "revise" => "Revise", + "version_history" => "Version history", + "revision_id" => "Revision #", + "created_at" => "Created at", + "diff" => "Diff", + "short_url" => "Get short URL", + "old_rev" => "Old revision %s", + "new_rev" => "New revision %s", + "revision_diff" => "Revision differences", + "return_paste" => "Return to paste", + "download_attachment" => "Download attachment (%s)", + "unknown" => "Unknown", + +); diff --git a/app/lang/en/user.php b/app/lang/en/user.php new file mode 100755 index 000000000..f39690731 --- /dev/null +++ b/app/lang/en/user.php @@ -0,0 +1,36 @@ + "Login", + "login_to" => "Log in to %s", + "remember" => "Remember me", + "create_acct" => "Create new account", + "register" => "Register", + "human_verify" => "Human verification", + "auth_fail" => "Authentication failed, please try again.", + "register_done" => "Registration successful. You can now log in to the site", + "forgot_password" => "Forgot password", + "forgot_exp" => "Please enter the username for which you want to reset the ". + "password. An email containing the new password will be ". + "sent to the mail address associated with that account.", + "reset_password" => "Reset password", + "reset_done" => "Your password has been reset. Please check your email", + "reg_disabled" => "Registration has been disabled on this site", + "feature_disabled" => "This feature is disabled as your user data is not stored ". + "in the site database", + "your_profile" => "Your profile", + "my_pastes" => "View my pastes", + "new_password" => "New password", + "profile_saved" => "Your profile has been saved", + +); diff --git a/app/lang/en/validation.php b/app/lang/en/validation.php new file mode 100755 index 000000000..9ef4626fa --- /dev/null +++ b/app/lang/en/validation.php @@ -0,0 +1,152 @@ + "The :attribute must be accepted.", + "active_url" => "The :attribute is not a valid URL.", + "after" => "The :attribute must be a date after :date.", + "alpha" => "The :attribute may only contain letters.", + "alpha_dash" => "The :attribute may only contain letters, numbers, and dashes.", + "alpha_num" => "The :attribute may only contain letters and numbers.", + "array" => "The :attribute must be an array.", + "auth" => "You must be logged in to complete this action.", + "before" => "The :attribute must be a date before :date.", + "between" => array( + "numeric" => "The :attribute must be between :min - :max.", + "file" => "The :attribute must be between :min - :max kilobytes.", + "string" => "The :attribute must be between :min - :max characters.", + "array" => "The :attribute must have between :min - :max items.", + ), + "captcha" => "The human verification failed.", + "confirmed" => "The :attribute confirmation does not match.", + "date" => "The :attribute is not a valid date.", + "date_format" => "The :attribute does not match the format :format.", + "different" => "The :attribute and :other must be different.", + "digits" => "The :attribute must be :digits digits.", + "digits_between" => "The :attribute must be between :min and :max digits.", + "email" => "The :attribute format is invalid.", + "exists" => "The selected :attribute is invalid.", + "image" => "The :attribute must be an image.", + "in" => "The selected :attribute is invalid.", + "integer" => "The :attribute must be an integer.", + "ip" => "The :attribute must be a valid IP address.", + "max" => array( + "numeric" => "The :attribute may not be greater than :max.", + "file" => "The :attribute may not be greater than :max kilobytes.", + "string" => "The :attribute may not be greater than :max characters.", + "array" => "The :attribute may not have more than :max items.", + ), + "mbmax" => "The :attribute may not be greater than :max bytes.", + "mimes" => "The :attribute must be a file of type: :values.", + "min" => array( + "numeric" => "The :attribute must be at least :min.", + "file" => "The :attribute must be at least :min kilobytes.", + "string" => "The :attribute must be at least :min characters.", + "array" => "The :attribute must have at least :min items.", + ), + "not_in" => "The selected :attribute is invalid.", + "numeric" => "The :attribute must be a number.", + "regex" => "The :attribute format is invalid.", + "required" => "The :attribute field is required.", + "required_if" => "The :attribute field is required when :other is :value.", + "required_with" => "The :attribute field is required when :values is present.", + "required_without" => "The :attribute field is required when :values is not present.", + "same" => "The :attribute and :other must match.", + "size" => array( + "numeric" => "The :attribute must be :size.", + "file" => "The :attribute must be :size kilobytes.", + "string" => "The :attribute must be :size characters.", + "array" => "The :attribute must contain :size items.", + ), + "unique" => "The :attribute has already been taken.", + "url" => "The :attribute format is invalid.", + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + "custom" => array( + + "php_key" => array( + "required_if" => "The access key field is required if honeypot filter is enabled.", + ), + + "php_days" => array( + "required_if" => "The age threshold field is required if honeypot filter is enabled.", + "integer" => "The age threshold must be an integer.", + "between" => "The age threshold must be between :min - :max.", + ), + + "php_score" => array( + "required_if" => "The threat score field is required if honeypot filter is enabled.", + "integer" => "The threat score must be an integer.", + "between" => "The threat score must be between :min - :max.", + ), + + "php_type" => array( + "required_if" => "The visitor filter field is required if honeypot filter is enabled.", + "integer" => "The visitor filter must be an integer.", + "between" => "The visitor filter must be between :min - :max.", + ), + + "flood_threshold" => array( + "required_if" => "The flood threshold field is required if flood filter is enabled.", + "integer" => "The flood threshold must be an integer.", + "between" => "The flood threshold must be between :min - :max.", + ), + + "akismet_key" => array( + "required_if" => "The Akismet key field is required if Akismet filter is enabled.", + "akismet_key" => "The Akismet API key you entered is invalid.", + ), + + "ldap_server" => array( + "required_if" => "The LDAP server field is required if LDAP method is used.", + ), + + "ldap_base_dn" => array( + "required_if" => "The LDAP base dn field is required if LDAP method is used.", + ), + + "ldap_uid" => array( + "required_if" => "The LDAP uid field is required if LDAP method is used.", + ), + + "ldap_admin" => array( + "required_if" => "The LDAP admin group field is required if LDAP method is used.", + ), + + ), + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap attribute place-holders + | with something more reader friendly such as E-Mail Address instead + | of "email". This simply helps us make messages a little cleaner. + | + */ + + "attributes" => array(), + +); diff --git a/app/lib/API.php b/app/lib/API.php new file mode 100755 index 000000000..d23841fa0 --- /dev/null +++ b/app/lib/API.php @@ -0,0 +1,143 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * API class + * + * Sticky notes REST API class + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class API { + + /** + * Mode of operation, usally xml or json + * + * @var string + */ + public $mode; + + /** + * Creates a new API instance + * + * @static + * @param string $mode + * @return void + */ + public static function make($mode) + { + $api = new API(); + + $api->mode = $mode; + + return $api; + } + + /** + * Throws a user specified error + * + * @param string $error + * @param int $code + * @return void + */ + public function error($error, $code = 200) + { + return $this->out('error', array('error' => "err_{$error}"), $code); + } + + /** + * Generates the output and does some pre-processing + * before that + * + * @param string $view + * @param array $data + * @param int $code + * @return void + */ + public function out($view, $data, $code = 200) + { + $callback = array($this, 'sanitize'.studly_case($this->mode)); + + // We sanitize the data before displaying + // - For XML, a specific set of characters are escaped + // - For JSON, PHP's inbuild json_encode is called + array_walk_recursive($data, $callback); + + // Add an iterator to the data + if ( ! isset($data['iterator'])) + { + $data['iterator'] = 0; + } + + // Now we create a custom response + $response = Response::view("templates/api/{$this->mode}/{$view}", $data, $code); + + // We set the header based on mode + switch ($this->mode) + { + case 'xml': + + $response->header('Content-Type', 'text/xml'); + + break; + + case 'json': + + $response->header('Content-Type', 'application/json'); + + break; + } + + return $response; + } + + /** + * Sanitize the data for XML mode + * + * @param string $data + * @return void + */ + private function sanitizeXml(&$data) + { + if (is_string($data)) + { + $data = strtr($data, array( + "<" => "<", + ">" => ">", + '"' => """, + "'" => "'", + "&" => "&", + )); + } + } + + /** + * Sanitize the data for JSON mode + * + * @param string $data + * @return void + */ + private function sanitizeJson(&$data) + { + if (is_string($data) OR empty($data)) + { + $data = json_encode($data); + } + } + +} diff --git a/app/lib/Antispam.php b/app/lib/Antispam.php new file mode 100755 index 000000000..f0c81202b --- /dev/null +++ b/app/lib/Antispam.php @@ -0,0 +1,443 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use Akismet; +use Input; +use IPBan; +use Lang; +use Object; +use Request; +use Site; +use stdClass; + +/** + * Antispam class + * + * Provides protection over spambots + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class Antispam { + + /** + * Site's antispam configuration + * + * @var array + */ + public $config; + + /** + * Scope of the antispam operation + * + * @var string + */ + public $scope = NULL; + + /** + * Data to run antispam validation on + * + * @var string + */ + public $data = NULL; + + /** + * Stores the antispam error message + * + * @var string + */ + public $message = NULL; + + /** + * Custom messages to be displayed if validation fails + * + * @var array + */ + public $customMessages; + + /** + * Creates a new instance of antispam class + * + * @static + * @param string $scope + * @param string $dataKey + * @param array $messages + * @return void + */ + public static function make($scope, $dataKey, $messages = array()) + { + $antispam = new Antispam(); + + // Set the scope of operation + $antispam->scope = $scope; + + // Set the data to be validated + $antispam->data = Input::get($dataKey); + + // Set the current configuration + $antispam->config = Site::config('antispam'); + + // Set custom messages to return + $antispam->customMessages = $messages; + + return $antispam; + } + + /** + * Return flags indicating whether each filter is + * enabled or disabled + * + * @static + * @return object + */ + public static function flags() + { + return Cache::rememberForever('antispam.flags', function() + { + $flags = new stdClass(); + + $services = Antispam::services(); + + // Fetching all enabled filters. This value can be defined + // from the antispam screen in the admin panel + $enabled = preg_split('/\||,/', Site::config('antispam')->services); + + foreach ($services as $service) + { + $flags->$service = in_array($service, $enabled); + } + + return $flags; + }); + } + + /** + * Returns a list of antispam services + * + * @static + * @return array + */ + public static function services() + { + $services = array(); + + $methods = get_class_methods(static::make(null, null)); + + foreach ($methods as $method) + { + if (starts_with($method, 'run')) + { + $services[] = strtolower(substr($method, 3)); + } + } + + return $services; + } + + /** + * Returns the scopes for a specific service + * + * @static + * @param string $service + * @return string + */ + public static function scopes($service) + { + $scopes = Config::get('antispam.scopes'); + + $inScopes = array(); + + // Iterate through each scope and check if this service is + // in that scope + foreach ($scopes as $scope => $services) + { + if (in_array($service, $services)) + { + $inScopes[] = studly_case(str_plural($scope)); + } + } + + // Now that we collected all scopes, return a merged list of + // scope names wherein the service exists + return implode(', ', $inScopes); + } + + /** + * Processes antispam filters + * + * @access public + * @return bool + */ + public function passes() + { + if ( ! empty($this->data)) + { + // Load the antispam configuration + // This is not same as the site configuration + $antispam = Config::get('antispam'); + + // We get the enabled services + // Then we iterate through each of them to see if there is a + // handler available for the service. If found, we run the handler + $services = preg_split('/\||,/', $this->config->services); + + // Immutable services are always executed even if they are not + // set explicitly from the admin panel. These services ideally + // require no configuration and therefore, do not appear in the + // antispam section of the admin panel + $services = array_merge($services, $antispam['immutable']); + + // Remove leading/trailing spaces from service names + $services = array_map('trim', $services); + + // Run the spam filters + foreach ($services as $service) + { + // Check if this service is available for the current scope + // This helps us decide whether or not to run this service + if (in_array($service, $antispam['scopes'][$this->scope])) + { + $handler = array($this, 'run'.studly_case($service)); + + if (is_callable($handler)) + { + if ( ! call_user_func($handler)) + { + if (isset($this->customMessages[$service])) + { + $this->message = $this->customMessages[$service]; + } + else + { + $this->message = Lang::get('antispam.'.$service); + } + + return FALSE; + } + } + } + } + } + + return TRUE; + } + + /** + * Inverse of Antispam::passes() + * + * @access public + * @return bool + */ + public function fails() + { + return ! $this->passes(); + } + + /** + * Fetches the antispam message + * + * @return string + */ + public function message() + { + return $this->message; + } + + /** + * Word censor for sticky notes. + * This plugin checks if specific words are contained within the POSTed + * paste body + * + * @access private + * @return bool + */ + private function runCensor() + { + if ( ! empty($this->config->censor)) + { + // Get array of blocked words + $words = array_map('trim', explode("\n", $this->config->censor)); + + // Traverse through all blocked words + foreach ($words as $word) + { + if (str_is($word, $this->data)) + { + return FALSE; + } + } + } + + return TRUE; + } + + /** + * IP ban access control. + * This plugin checks if the current user has been banned from creating + * new pastes. + * + * @access private + * @return bool + */ + private function runIpban() + { + $banned = IPBan::where('ip', Request::getClientIp())->count(); + + return $banned == 0; + } + + /** + * Sticky Notes' in-build HTML filter. + * + * @access private + * @return bool + */ + private function runStealth() + { + $data = strtolower($this->data); + + $language = Input::get('language'); + + // Get the number of links in the paste + preg_match_all('/https?:\/\//', $data, $matches); + + // Disallow if number of links are more than configured threshold + return ! (isset($matches[0]) AND count($matches[0]) > $this->config->stealthCount AND $language == 'text'); + } + + /** + * Flood control for Sticky Notes. + * This disallowes a user to create pastes in less than 5 second intervals. + * + * @access private + * @return bool + */ + private function runNoflood() + { + $posted = Session::get('form.posted'); + + $threshold = Site::config('antispam')->floodThreshold; + + if (time() - $posted >= $threshold) + { + Session::put('form.posted', time()); + + return TRUE; + } + + return FALSE; + } + + /** + * Project Honeypot integration allows sticky-notes to check if an IP + * address is flagged as abusive in the honeypot database. + * + * For details, see: http://www.projecthoneypot.org/ + * + * @access private + * @return bool + */ + private function runPhp() + { + try + { + $ip = Request::getClientIp(); + + // Skip validation is no key is specified in config.php + if (empty($this->config->phpKey)) + { + return TRUE; + } + + // We cannot process an IPv6 address + if( ! filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) + { + return TRUE; + } + + // Convert IP address to reversed octet format + // So for example, 127.0.0.1 becomes 1.0.0.127 + $sections = explode('.', $ip); + + $revIp = "{$sections[3]}.{$sections[2]}.{$sections[1]}.{$sections[0]}"; + + // Query Project Honey Pot BL + // The URI of the query for 127.0.0.1 would be: + // - phpkey.1.0.0.127.dnsbl.httpbl.org + $response = dns_get_record("{$this->config->phpKey}.{$revIp}.dnsbl.httpbl.org"); + + // Exit if NXDOMAIN is returned + if (empty($response[0]['ip'])) + { + return TRUE; + } + + // The information returns is: + // - The age of the IP address in the honeypot database (0 - 255) + // - The threat score of the IP address (0 - 255) + // - The type of the threat (0 - 255) + $result = explode('.', $response[0]['ip']); + + $days = $result[1]; + $score = $result[2]; + $type = $result[3]; + + // Perform PHP validation + if ($days <= $this->config->phpDays AND ($type >= $this->config->phpType OR $score >= $this->config->phpScore)) + { + return FALSE; + } + else + { + return TRUE; + } + } + catch (Exception $e) + { + return TRUE; + } + } + + /** + * Akismet automatic spam filter. See http://akismet.com + * + * @access private + * @return void + */ + private function runAkismet() + { + // Create the Akismet instance + $akismet = new Akismet(Request::url(), $this->config->akismetKey); + + // Set the author info if the user is logged in + if (Auth::check()) + { + $user = Auth::user(); + + $akismet->setCommentAuthor($user->username); + + $akismet->setCommentAuthorEmail($user->email); + } + + // Set the content to validate + $akismet->setCommentContent($this->data); + + // Return the Akismet analysis + return ! $akismet->isCommentSpam(); + } + +} diff --git a/app/lib/Auth.php b/app/lib/Auth.php new file mode 100755 index 000000000..fbe4b7093 --- /dev/null +++ b/app/lib/Auth.php @@ -0,0 +1,82 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use stdClass; + +/** + * View class + * + * Abstraction over \Illuminate\Support\Facades\Auth to add functionality + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class Auth extends \Illuminate\Support\Facades\Auth { + + /** + * Defines the roles for the logged in user + * + * @var array + */ + private static $roles; + + /** + * Validates if a user has access to a specific resource + * by matching the passed userId with the ID of the logged + * in user + * + * @param int $id + * @return bool + */ + public static function access($id) + { + $roles = static::roles(); + + return ! $roles->guest AND ($roles->admin OR static::user()->id == $id); + } + + /** + * Fetches the roles for the currently logged in user + * + * @return object + */ + public static function roles() + { + if ( ! isset(static::$roles) OR php_sapi_name() == 'cli') + { + static::$roles = new stdClass(); + + static::$roles->guest = FALSE; + static::$roles->user = FALSE; + static::$roles->admin = FALSE; + + if (static::guest()) + { + static::$roles->guest = TRUE; + } + else + { + static::$roles->user = TRUE; + + static::$roles->admin = static::user()->admin; + } + } + + return static::$roles; + } + +} diff --git a/app/lib/Cache.php b/app/lib/Cache.php new file mode 100755 index 000000000..e94af1789 --- /dev/null +++ b/app/lib/Cache.php @@ -0,0 +1,98 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * Cache class + * + * Abstraction over \Illuminate\Support\Facades\Cache to enable local caching + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class Cache extends \Illuminate\Support\Facades\Cache { + + /** + * Local cache + * + * @var array + */ + private static $cache = array(); + + /** + * Returns cached data + * + * @param string $key + * @return mixed + */ + public static function get($key) + { + if ( ! isset(static::$cache[$key])) + { + static::$cache[$key] = parent::get($key); + } + + return static::$cache[$key]; + } + + /** + * Returns cached data after updating the cached value if expired + * + * @param string $key + * @param int $ttl + * @param closure $closure + * @return mixed + */ + public static function remember($key, $ttl, $closure) + { + if ( ! isset(static::$cache[$key])) + { + static::$cache[$key] = parent::remember($key, $ttl, $closure); + } + + return static::$cache[$key]; + } + + /** + * Returns cached data after updating the cached value if expired + * + * @param string $key + * @param closure $closure + * @return mixed + */ + public static function rememberForever($key, $closure) + { + if ( ! isset(static::$cache[$key])) + { + static::$cache[$key] = parent::rememberForever($key, $closure); + } + + return static::$cache[$key]; + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public static function flush() + { + static::$cache = array(); + + parent::flush(); + } + +} diff --git a/app/lib/Config.php b/app/lib/Config.php new file mode 100755 index 000000000..cf9acaadc --- /dev/null +++ b/app/lib/Config.php @@ -0,0 +1,63 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.3 + * @filesource + */ + +/** + * Config class + * + * Abstraction over \Illuminate\Support\Facades\Config to enable config caching + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class Config extends \Illuminate\Support\Facades\Config { + + /** + * Configuration cache + * + * @var array + */ + private static $cache = array(); + + /** + * Returns configuration data + * + * @static + * @param string $key + * @return array + */ + public static function get($key) + { + if ( ! isset(static::$cache[$key])) + { + static::$cache[$key] = parent::get($key); + } + + return static::$cache[$key]; + } + + /** + * Flushes the config cache + * + * @static + * @return void + */ + public static function flush() + { + static::$cache = array(); + } + +} diff --git a/app/lib/Cookie.php b/app/lib/Cookie.php new file mode 100755 index 000000000..a498937f8 --- /dev/null +++ b/app/lib/Cookie.php @@ -0,0 +1,92 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.8 + * @filesource + */ + +/** + * Cookie class + * + * Abstraction over \Illuminate\Support\Facades\Cookie to add manual serialization + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class Cookie extends \Illuminate\Support\Facades\Cookie { + + /** + * Get the value of the given cookie + * + * @static + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function get($key = NULL, $default = NULL) + { + // First, we get the raw cookie data + $raw = parent::get($key, $default); + + // Then we attempt to unserialize it + $unserialized = @unserialize($raw); + + // If unserialization succeeded, we return the unserialized data + // otherwise, we return the original raw data + return $unserialized !== FALSE ? $unserialized : $raw; + } + + /** + * Creates a new cookie instance + * + * @static + * @param string $name + * @param mixed $value + * @param int $minutes + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $httpOnly + * @return \Symfony\Component\HttpFoundation\Cookie + */ + public static function make($name, $value, $minutes = 0, $path = NULL, $domain = NULL, $secure = FALSE, $httpOnly = TRUE) + { + // Serialize the value + $value = @serialize($value); + + // Create the cookie + return parent::make($name, $value, $minutes, $path, $domain, $secure, $httpOnly); + } + + /** + * Create a cookie that lasts "forever" (five years) + * + * @static + * @param string $name + * @param mixed $value + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $httpOnly + * @return \Symfony\Component\HttpFoundation\Cookie + */ + public static function forever($name, $value, $path = NULL, $domain = NULL, $secure = FALSE, $httpOnly = TRUE) + { + // Serialize the value + $value = @serialize($value); + + // Create the cookie + return parent::forever($name, $value, $path, $domain, $secure, $httpOnly); + } + +} diff --git a/app/lib/Cron.php b/app/lib/Cron.php new file mode 100755 index 000000000..35657204a --- /dev/null +++ b/app/lib/Cron.php @@ -0,0 +1,102 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use File; +use Paste; +use Revision; +use Schema; +use Site; +use Statistics; + +/** + * Cron class + * + * Offers scheduled execution functionality + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class Cron { + + /** + * Cron execution interval + * + * @static + * @var int + */ + private static $interval = 1800; + + /** + * Run cron tasks. This is a simple implementation without + * any bells and whistles. + * + * @static + * @return void + */ + public static function run() + { + // We run the cron tasks once every 5 minutes + Cache::remember('site.cron', 5, function() + { + $expired = array(); + + $storage = storage_path(); + + // Retrieve expired pastes + $pastes = Paste::where('expire', '>', 0)->where('expire', '<', time())->get(); + + if ($pastes->count() > 0) + { + // Check if the comments table exists + $hasComments = Schema::hasTable('comments'); + + // Build the expired pastes array + // Also delete associated comments and attachments + foreach($pastes as $paste) + { + $expired[] = $paste->urlkey; + + $paste->comments()->delete(); + + $attachment = "{$storage}/uploads/{$paste->urlkey}"; + + if ($paste->attachment AND File::exists($attachment)) + { + File::delete($attachment); + } + } + + // Remove expired pastes + Paste::whereIn('urlkey', $expired)->delete(); + + // Remove expired revisions + Revision::whereIn('urlkey', $expired)->delete(); + } + + // Delete paste statistics older than configured age + $ttl = Site::config('general')->statsTTL; + + $date = date('Y-m-d', strtotime($ttl)); + + Statistics::where('date', '<', $date)->delete(); + + // Crun run successfully + return TRUE; + }); + } + +} diff --git a/app/lib/Feed.php b/app/lib/Feed.php new file mode 100755 index 000000000..dba258905 --- /dev/null +++ b/app/lib/Feed.php @@ -0,0 +1,84 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.1 + * @filesource + */ + +/** + * Feed class + * + * Sticky notes feed generator + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class Feed { + + /** + * Defines the feed type + * + * @var string + */ + public $type; + + /** + * Creates a new feed class instance + * + * @static + * @param string $type + * @return void + */ + public static function make($type) + { + $feed = new Feed(); + + $feed->type = $type; + + return $feed; + } + + /** + * Generates the feed output. + * + * @param array $data + * @return void + */ + public function out($data) + { + // Clean each data item recursively for XML output + array_walk_recursive($data, array($this, 'sanitizeFeed')); + + // Now we create a custom response + $response = Response::view("templates/feed/{$this->type}", $data); + + // We set the content type based on feed type + $response->header('Content-Type', "application/{$this->type}+xml"); + + return $response; + } + + /** + * Sanitize the data for the feed + * + * @param string $data + * @return void + */ + private function sanitizeFeed(&$data) + { + $data = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $data); + + $data = htmlspecialchars($data, ENT_COMPAT, 'UTF-8'); + } + +} diff --git a/app/lib/Highlighter.php b/app/lib/Highlighter.php new file mode 100755 index 000000000..2a62214fc --- /dev/null +++ b/app/lib/Highlighter.php @@ -0,0 +1,161 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use GeSHi; + +/** + * Highlighter class + * + * Abstraction over the GeSHi syntax highlighting library + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class Highlighter { + + /** + * Stores a class instance + * + * @var Highlighter + */ + private static $instance; + + /** + * GeSHi library instance + * + * @access public + * @var GeSHi + */ + public $geshi; + + /** + * Creates a new GeSHi instance + * + * @return void + */ + public function __construct() + { + require_once base_path().'/vendor/geshi/geshi.php'; + + $this->geshi = new GeSHi(); + + // Display fancy (bold) line numbers + $this->geshi->enable_line_numbers(GESHI_FANCY_LINE_NUMBERS); + + // Use
      wrapper for the code block + $this->geshi->set_header_type(GESHI_HEADER_DIV); + + // Set the tab width for the highlighter + $this->geshi->set_tab_width(4); + + // Set custom code styles + $this->geshi->set_code_style('vertical-align: middle', TRUE); + } + + /** + * Creates a new instance of Highlighter class + * + * @static + * @return Highlighter + */ + public static function make() + { + if ( ! isset(static::$instance)) + { + static::$instance = new Highlighter(); + } + + return static::$instance; + } + + /** + * Fetches a list of languages supported by GeSHi + * + * @access public + * @param bool $csv + * @return array|string + */ + public function languages($csv = FALSE) + { + return Cache::rememberForever("site.languages.{$csv}", function() use ($csv) + { + // get_supported_languages takes a param that tells whether or not + // to return full names. We don't need full names if we just want CSV + $langs = $this->geshi->get_supported_languages( ! $csv); + + // Now, sort the languages for non-CSV scenario + if ( ! $csv) + { + // First, we do a natural case-insensitive sort + natcasesort($langs); + + // Now, get the language list from the cookie and push the most + // used languages to the beginning of the list to allow easy access + $historyLangs = Cookie::get('languages'); + + if ( ! is_null($historyLangs)) + { + foreach ($historyLangs as $lang) + { + if (isset($langs[$lang])) + { + // Get the language description + $langText = $langs[$lang]; + + // Remove the language from the array + unset($langs[$lang]); + + // Add the language to the top of the array + $langs = array_merge(array($lang => $langText), $langs); + } + } + } + } + else + { + $langs = implode(',', $langs); + } + + return $langs; + }); + } + + /** + * Parses and outputs highlighted code + * + * @param string $key + * @param string $code + * @param string $language + * @return string + */ + public function parse($key, $code, $language) + { + $geshi = $this->geshi; + + $parsed = Cache::remember("site.code.{$key}", 45000, function() use ($geshi, $code, $language) + { + $geshi->set_source($code); + + $geshi->set_language($language); + + return @$geshi->parse_code($code); + }); + + return $parsed ?: $code; + } + +} diff --git a/app/lib/Mail.php b/app/lib/Mail.php new file mode 100755 index 000000000..67e4b033a --- /dev/null +++ b/app/lib/Mail.php @@ -0,0 +1,57 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use Lang; +use Site; + +use Swift_TransportException; + +/** + * Mail class + * + * Abstraction over \Illuminate\Support\Facades\Mail to enable testing + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class Mail extends \Illuminate\Support\Facades\Mail { + + /** + * Tests a specific email configuration + * + * @static + * @return bool|string + */ + public static function test() + { + try + { + // Send a dummy e-mail + parent::send('templates/email/test', array(), function($message) + { + $message->to('test@example.com'); + }); + + return TRUE; + } + catch (Swift_TransportException $e) + { + return sprintf(Lang::get('admin.test_mail_error'), $e->getMessage()); + } + } + +} diff --git a/app/lib/PHPDiff.php b/app/lib/PHPDiff.php new file mode 100755 index 000000000..fe3fa72cd --- /dev/null +++ b/app/lib/PHPDiff.php @@ -0,0 +1,128 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use App; +use Diff; +use DiffRenderer; +use Lang; +use Paste; +use Revision; + +/** + * PHPDiff class + * + * Provides methods to generate a diff + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class PHPDiff { + + /** + * Stores a class instance + * + * @var PHPDiff + */ + private static $instance; + + /** + * The diff renderer instance + * + * @var Diff_Renderer_Html_SideBySid + */ + private $renderer; + + /** + * Creates a new instance of PHPass + * + * @return void + */ + public function __construct() + { + require_once base_path().'/vendor/phpdiff/Diff.php'; + + require_once base_path().'/vendor/phpdiff/Diff/Renderer/Html/SideBySide.php'; + + $this->renderer = new DiffRenderer; + } + + /** + * Returns a new instance of Crypt class + * + * @static + * @return PHPass + */ + public static function make() + { + if ( ! isset(static::$instance)) + { + static::$instance = new PHPDiff(); + } + + return static::$instance; + } + + /** + * Generates a diff between two pastes + * + * @param string $oldKey + * @param string $newKey + * @return string + */ + public function compare($oldKey, $newKey) + { + $oldPaste = Paste::where('urlkey', $oldKey)->first(); + + $newPaste = Paste::where('urlkey', $newKey)->first(); + + // Both pastes need to be valid + if (is_null($oldPaste) OR is_null($newPaste)) + { + App::abort(404); // Not found + } + + // We check that the new paste is actually a revision of the old + // paste + $revision = Revision::where('paste_id', $newPaste->id)->where('urlkey', $oldPaste->urlkey); + + if ($revision->count() == 0) + { + App::abort(404); // Not found + } + + // The php-diff library expects an array as an input + // for each of the texts. Each array elementb will represent a + // line in the text block + $left = explode("\n", $oldPaste->data); + + $right = explode("\n", $newPaste->data); + + // We set these options so that the headers of the diff + // table are more informative + $options = array( + 'oldHead' => sprintf(Lang::get('show.old_rev'), link_to($oldKey, '#'.$oldKey)), + 'newHead' => sprintf(Lang::get('show.new_rev'), link_to($newKey, '#'.$newKey)), + ); + + // Create a new diff instance + $diff = new Diff($left, $right, $options); + + // Render using the sideBySide renderer and return the html + return $diff->Render($this->renderer); + } + +} diff --git a/app/lib/PHPass.php b/app/lib/PHPass.php new file mode 100755 index 000000000..515d0eb43 --- /dev/null +++ b/app/lib/PHPass.php @@ -0,0 +1,147 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use PasswordHash; +use Paste; +use User; + +/** + * PHPass class + * + * Provides encryption methods and updates passwords + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class PHPass { + + /** + * Stores a class instance + * + * @var PHPass + */ + private static $instance; + + /** + * The crytographic library instance + * + * @var PasswordHash + */ + private $phpass; + + /** + * Creates a new instance of PHPass + * + * @return void + */ + public function __construct() + { + require_once base_path().'/vendor/phpass/PasswordHash.php'; + + $this->phpass = new PasswordHash(10, false); + } + + /** + * Returns a new instance of Crypt class + * + * @static + * @return PHPass + */ + public static function make() + { + if ( ! isset(static::$instance)) + { + static::$instance = new PHPass(); + } + + return static::$instance; + } + + /** + * Creates a bcrypt hash + * + * @param string $password + * @param string $salt + * @return string + */ + public function create($password, $salt) + { + return $this->phpass->HashPassword($password.$salt); + } + + /** + * Checks a password hash, updates it to bcrypt if still using sha1 + * + * @param string $model + * @param string $password + * @param string $salt + * @param string $hash + * @return bool + */ + public function check($model, $password, $salt, $hash) + { + // Hash created using blowfish algorithm + if ($hash[0] == '$') + { + return $this->phpass->CheckPassword($password.$salt, $hash); + } + + // Hash created using secure hash algorithm + // This check is done to maintain backward compatibility + else + { + $newHash = $this->create($password, $salt); + + $oldHash = NULL; + + $query = NULL; + + switch($model) + { + case 'Paste': + $oldHash = sha1(sha1($password).$salt); + + $query = Paste::query(); + + break; + + case 'User': + $oldHash = sha1($password.$salt); + + $query = User::query(); + + break; + + default: + return FALSE; + } + + // Password matches with old method, now migrate all pwds with this hash + if ($hash == $oldHash) + { + $query->where('password', $oldHash)->update(array( + 'password' => $newHash + )); + + return TRUE; + } + } + + return FALSE; + } + +} diff --git a/app/lib/Redirect.php b/app/lib/Redirect.php new file mode 100755 index 000000000..7af48d3e1 --- /dev/null +++ b/app/lib/Redirect.php @@ -0,0 +1,64 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * Redirect class + * + * Abstraction over \Illuminate\Support\Facades\Redirect to enable AJAX support + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class Redirect extends \Illuminate\Support\Facades\Redirect { + + /** + * Create a new redirect response, while putting the current URL in the session. + * + * @param string $path + * @param int $status + * @param array $headers + * @param bool $secure + * @return \Illuminate\Http\RedirectResponse + */ + public static function guest($path, $status = 302, $headers = array(), $secure = null) + { + $url = str_replace('ajax=1', '', parent::getUrlGenerator()->full()); + + Session::put('url.intended', $url); + + return parent::to($path, $status, $headers, $secure); + } + + /** + * Create a new redirect response to the previously intended location. + * + * @param string $default + * @param int $status + * @param array $headers + * @param bool $secure + * @return \Illuminate\Http\RedirectResponse + */ + public static function intended($default, $status = 302, $headers = array(), $secure = null) + { + $path = Session::get('url.intended', $default); + + Session::forget('url.intended'); + + return parent::to($path, $status, $headers, $secure); + } + +} diff --git a/app/lib/Response.php b/app/lib/Response.php new file mode 100755 index 000000000..73ac40b5b --- /dev/null +++ b/app/lib/Response.php @@ -0,0 +1,45 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * Response class + * + * Abstraction over \Illuminate\Support\Facades\Response to enable skin support + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class Response extends \Illuminate\Support\Facades\Response { + + /** + * This abstraction over the base method injects the skin name + * and default view data. + * + * @param string $view + * @param array $data + * @param int $status + * @param array $headers + * @return \Illuminate\View\View + */ + public static function view($view, $data = array(), $status = 200, array $headers = array()) + { + $data = array_merge(View::defaults(), $data); + + return parent::view(View::inject($view), $data, $status, $headers); + } + +} diff --git a/app/lib/Service.php b/app/lib/Service.php new file mode 100755 index 000000000..5255fb769 --- /dev/null +++ b/app/lib/Service.php @@ -0,0 +1,133 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use Lang; +use Request; +use Site; + +use UnitedPrototype\GoogleAnalytics; + +/** + * Services class + * + * Layer for accessing third party services + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class Service { + + /** + * goo.gl URL shortener service + * + * @static + * @param string $longUrl + * @return string + */ + public static function urlShortener($longUrl) + { + $services = Site::config('services'); + + if ( ! empty($services->googleApiKey)) + { + $url = sprintf($services->googleUrlShortener, $services->googleApiKey); + + $ch = curl_init(); + + // Set the API url to connect to + curl_setopt($ch, CURLOPT_URL, $url); + + // We will be making a POST request + curl_setopt($ch, CURLOPT_POST, TRUE); + + // Set the URL that we want to shorten + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(array("longUrl" => $longUrl))); + + // Indicate that we want the response in JSON format + curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: application/json")); + + // Indicate that we want a text response back + curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); + + // This is just in case the SSL certificate is not valid + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + + // Execute the POST request + $result = curl_exec($ch); + + curl_close($ch); + + // Parse and return the response + $response = json_decode($result, TRUE); + + if (isset($response['id'])) + { + return $response['id']; + } + } + + return Lang::get('ajax.error'); + } + + /** + * Google analytics tracker + * + * @static + * @return void + */ + public static function analytics() + { + $site = Site::config('general'); + + $services = Site::config('services'); + + // Run analytics if a tracking code is set + if ( ! empty($services->googleAnalyticsId)) + { + try + { + // Initilize GA Tracker + $tracker = new GoogleAnalytics\Tracker($services->googleAnalyticsId, $site->fqdn); + + // Gather visitor information + $visitor = new GoogleAnalytics\Visitor(); + + $visitor->setIpAddress(Request::getClientIp()); + + $visitor->setUserAgent(Request::server('HTTP_USER_AGENT')); + + // Gather session information + $session = new GoogleAnalytics\Session(); + + // Gather page information + $path = Request::path(); + + $page = new GoogleAnalytics\Page($path == '/' ? $path : "/{$path}"); + + $page->setTitle($site->title); + + // Track page view + $tracker->trackPageview($page, $session, $visitor); + } + catch (GoogleAnalytics\Exception $e) + { + // Suppress this error + } + } + } + +} diff --git a/app/lib/Session.php b/app/lib/Session.php new file mode 100755 index 000000000..d35fffe89 --- /dev/null +++ b/app/lib/Session.php @@ -0,0 +1,65 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.3 + * @filesource + */ + +/** + * Session class + * + * Abstraction over \Illuminate\Support\Facades\Session to enable caching + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class Session extends \Illuminate\Support\Facades\Session { + + /** + * Configuration cache + * + * @var array + */ + private static $cache = array(); + + /** + * Returns session data + * + * @static + * @param string $key + * @return array + */ + public static function get($key) + { + if ( ! isset(static::$cache[$key])) + { + static::$cache[$key] = parent::get($key); + } + + return static::$cache[$key]; + } + + /** + * Flushes session data + * + * @static + * @return void + */ + public static function flush() + { + static::$cache = array(); + + parent::flush(); + } + +} diff --git a/app/lib/Setup.php b/app/lib/Setup.php new file mode 100755 index 000000000..169a36519 --- /dev/null +++ b/app/lib/Setup.php @@ -0,0 +1,400 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use App; +use Lang; +use Schema; +use Site; + +/** + * Setup class + * + * Layer for performing install and update activities + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class Setup { + + /** + * Tests the database connection + * + * @static + * @return bool|string + */ + public static function testConnection() + { + static::setHandler('dbTest'); + + Schema::getConnection(); + + return TRUE; + } + + /** + * Starts up the Sticky Notes setup module + * + * @static + * @return void + */ + public static function start() + { + Session::forget('setup.stage'); + + Session::forget('setup.updating'); + + Session::forget('setup.version'); + } + + /** + * Processes AJAX requests for installation. + * The installer processes one table per action. + * + * The response is in the following format: + * || + * + * @static + * @param string $action + * @return string + */ + public static function install($action) + { + // Fetch the installer schema + $schema = Config::get('schema.install'); + + // Define the tables data + $tables = $schema['tables']; + + $tableNames = array_keys($tables); + + // We assign 5% to initiate and 5% to completion + // The weightage of each table is calculated out of 90 + $weight = floor(90 / count($tables)); + + // Initialize everything + if (empty($action)) + { + $firstTable = $tableNames[0]; + + return "5|{$firstTable}|".sprintf(Lang::get('setup.create_table'), $firstTable); + } + + // This is the last step, but needs to be called out first + else if ($action == '~complete') + { + // Run the post-install closure + call_user_func($schema['closure']); + + // Mark completion of this stage + Session::put('setup.stage', 4); + + return "100||".Lang::get('setup.install_complete'); + } + + // This loops across all tables and processes them + else if (in_array($action, $tableNames)) + { + try + { + // In case the exception is not caught + static::setHandler('mainProcess'); + + // Drop the table + Schema::dropIfExists($action); + + // Generate schema and create the table + Schema::create($action, function($table) use ($tables, $action) + { + Setup::schema($table, $tables[$action]); + }); + + // Output the next action in queue + return Setup::nextAction($action, $tableNames, 'setup.create_table'); + } + catch (Exception $e) + { + Session::put('setup.error', $e->getMessage()); + + return '-1||'.Lang::get('setup.error_occurred'); + } + } + } + + /** + * Processes AJAX requests for upgrades. + * The updater starts with the passed $version and goes on until the + * last one. + * + * The response is in the following format: + * || + * + * @static + * @param string $version + * @return string + */ + public static function update($action) + { + // Get the update versions and current scope + $versions = Config::get('schema.update'); + + $versionNames = array_keys($versions); + + // Initialize everything + if ( ! Session::has('setup.updating')) + { + Session::put('setup.updating', TRUE); + + Session::forget('setup.messages'); + + return "5|{$action}|".sprintf(Lang::get('setup.process_version'), $action); + } + + // This is the last step, but needs to be called out first + else if ($action == '~complete') + { + // Set the final stage + Session::put('setup.stage', 3); + + // Update the version number in the database + Site::config('general', array('version' => Config::get('app.version'))); + + // Flush the cache + Cache::flush(); + + // All done! + return "100||".Lang::get('setup.update_complete'); + } + + // Process the version + else if (array_key_exists($action, $versions)) + { + try + { + // In case the exception is not caught + static::setHandler('mainProcess'); + + // Scope is the current version being processed + $scope = $versions[$action]; + + // Create new tables + if (isset($scope['newTables'])) + { + foreach ($scope['newTables'] as $tableName => $schema) + { + // Drop the table + Schema::dropIfExists($tableName); + + // Generate schema and create the table + Schema::create($tableName, function($table) use ($schema) + { + Setup::schema($table, $schema); + }); + } + } + + // Update existing tables + if (isset($scope['modifyTables'])) + { + foreach ($scope['modifyTables'] as $tableName => $schema) + { + // Generate schema and modify the table + Schema::table($tableName, function($table) use ($schema) + { + Setup::schema($table, $schema); + }); + } + } + + // Run the closure for this version + if (isset($scope['closure'])) + { + call_user_func($scope['closure']); + } + + // Output the next action in queue + return Setup::nextAction($action, $versionNames, 'setup.process_version'); + } + catch (Exception $e) + { + Session::put('setup.error', $e->getMessage()); + + return '-1||'.Lang::get('setup.error_occurred'); + } + } + } + + /** + * Applies a specific table schema to a table + * + * @static + * @param \Illuminate\Database\Schema\Blueprint $table + * @param array $schema + * @return void + */ + public static function schema($table, $schema) + { + foreach ($schema as $column) + { + $coltype = $column->type; + + // Make the column + if (isset($column->length)) + { + $context = $table->$coltype($column->name, $column->length); + } + else + { + $context = $table->$coltype($column->name); + } + + // Set default value + if (isset($column->default)) + { + $context = $context->default($column->default); + } + + // Set nullable type + if (isset($column->nullable) AND $column->nullable) + { + $context = $context->nullable(); + } + + // Set unsigned for integers + if (isset($column->unsigned) AND $column->unsigned) + { + $context = $context->unsigned(); + } + } + } + + /** + * Returns the next action response + * + * @static + * @param string $action + * @param array $actions + * @param string $langKey + * @return string + */ + public static function nextAction($action, $actions, $langKey) + { + // We assign 5% to initiate and 5% to completion + // The weightage of each version is calculated out of 90 + $weight = floor(90 / count($actions)); + + // Get the index of the current action + $index = array_search($action, $actions); + + // Get the percentage done + $percent = ($index + 1) * $weight; + + // Get the next action and message + if ($index < count($actions) - 1) + { + $nextAction = $actions[$index + 1]; + + $message = sprintf(Lang::get($langKey), $nextAction); + } + else + { + $nextAction = '~complete'; + + $message = Lang::get('setup.almost_done'); + } + + return "{$percent}|{$nextAction}|{$message}"; + } + + /** + * Fetches available Sticky Notes versions for update + * + * @static + * @param bool $csv + * @return array|string + */ + public static function updateVersions($csv = FALSE) + { + $versions = array(); + + foreach (Config::get('schema.update') as $version => $schema) + { + $versions[$version] = $version; + } + + if ($csv) + { + $versions = implode(',', $versions); + } + + return $versions; + } + + /** + * Gets or sets installer messages + * + * @static + * @param string $version + * @param string $message + * @return array|null + */ + public static function messages($version = NULL, $message = NULL) + { + $messages = Session::has('setup.messages') ? Session::get('setup.messages') : array(); + + if (is_null($message)) + { + return is_null($version) ? $messages : $messages[$version]; + } + else if ( ! is_null($version)) + { + $messages[$version] = $message; + } + + Session::put('setup.messages', $messages); + } + + /** + * Sets the error handler based on category + * + * @static + * @param string $category + * @return void + */ + private static function setHandler($category) + { + App::error(function($e, $c) use ($category) + { + switch ($category) + { + case 'dbTest': + + $error = sprintf(Lang::get('setup.test_fail'), $e->getMessage()); + + Session::flash('messages.error', $error); + + return Redirect::to('setup/install'); + + case 'mainProcess': + + Session::put('setup.error', $e->getMessage()); + + return '-1||'.Lang::get('setup.error_occurred'); + } + }); + } + +} diff --git a/app/lib/System.php b/app/lib/System.php new file mode 100755 index 000000000..268e47b00 --- /dev/null +++ b/app/lib/System.php @@ -0,0 +1,279 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use File; +use Lang; +use Request; +use Requests; +use Requests_Exception; +use Route; +use Schema; +use Site; +use URL; + +/** + * System class + * + * Provides system information to views and controllers. + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class System { + + /** + * Caches the current route + * + * @var string + */ + private static $route = NULL; + + /** + * Defines whether the system is in an error state + * + * @var bool + */ + private static $errorState = FALSE; + + /** + * Retrieves a list of directory names from a given path + * + * @static + * @param string $path + * @param bool $csv + * @return array|string + */ + public static function directories($path, $csv = FALSE) + { + $list = array(); + + $items = scandir(app_path()."/{$path}"); + + foreach ($items as $item) + { + if ( ! starts_with($item, '.')) + { + $list[$item] = $item; + } + } + + if ($csv) + { + $list = implode(',', $list); + } + + return $list; + } + + /** + * Gets the server load. On windows systems, it fetches the + * current CPU utilization. + * + * @static + * @return string + */ + public static function load() + { + $sysload = NULL; + + // Get the system's load based on the OS + $os = strtolower(PHP_OS); + + if (strpos($os, 'win') === FALSE) + { + if (File::exists('/proc/loadavg')) + { + $load = File::get('/proc/loadavg'); + + $load = explode(' ', $load); + + $sysload = $load[0]; + } + else if (function_exists('shell_exec')) + { + $load = explode(' ', `uptime`); + + $sysload = $load[count($load) - 1]; + } + } + else + { + if (function_exists('exec')) + { + $load = array(); + + exec('wmic cpu get loadpercentage', $load); + + if ( ! empty($load[1])) + { + $sysload = "{$load[1]}%"; + } + } + } + + return empty($sysload) ? Lang::get('global.not_available') : $sysload; + } + + /** + * Gets a version number from a version string + * + * @static + * @param string $version + * @return int + */ + public static function version($version) + { + $version = ! empty($version) ? $version : '0.0'; + + // Remove decimals + $version = str_replace('.', '', $version); + + // Convert it to an integer + return intval($version); + } + + /** + * Returns the current project name + * + * @static + * @return string|null + */ + public static function project() + { + $fqdn = explode('.', Site::config('general')->fqdn); + + $host = explode('.', getenv('SERVER_NAME')); + + if (count($host) > count($fqdn)) + { + return $host[0]; + } + } + + /** + * Determines the installed state of the system + * + * @static + * @return bool + */ + public static function installed() + { + return Cache::rememberForever('site.installed', function() + { + return Schema::hasTable('main'); + }); + } + + /** + * Determines whether the latest version of Sticky Notes + * is installed. + * + * - If local version is same as remote, return 0 + * - If remote version is newer, return negative integer + * - If local version is newer, return positive integer + * + * @static + * @return int + */ + public static function updated() + { + try + { + // Get the local (installed) version number + $localVersion = static::version(Config::get('app.version')); + + // Get the remote version number + $response = Requests::get(Site::config('services')->updateUrl); + + $remoteVersion = static::version($response->body); + + // Return the version difference + return $localVersion - $remoteVersion; + } + catch (Requests_Exception $e) + { + // HTTP GET failed + return 0; + } + } + + /** + * Gets the name of the current action. + * We don't return anything if we are in an error flow + * + * @static + * @return string + */ + public static function action() + { + if (is_null(static::$route) AND ! static::error()) + { + $action = Route::currentRouteAction(); + + static::$route = head(explode('@', $action)); + } + + return static::$route; + } + + /** + * Submits statistics to Sticky Notes server + * + * @return void + */ + public static function submitStats() + { + try + { + // Send / mask the site's URL + $url = Config::get('app.fullStats') ? URL::current() : Lang::get('global.anonymous'); + + // Populate the data to be send + $data = array( + 'url' => $url, + 'action' => Request::segment(2), + 'version' => Config::get('app.version'), + ); + + // Send the stats to the REST stats service + Requests::post(Site::config('services')->statsUrl, array(), $data); + } + catch (Requests_Exception $e) + { + // HTTP POST failed. Suppress this exception + } + } + + /** + * Sets the system in an error state + * + * @param mixed $state + * @return bool + */ + public static function error($state = NULL) + { + if (is_bool($state)) + { + static::$errorState = $state; + } + else + { + return static::$errorState; + } + } + +} diff --git a/app/lib/View.php b/app/lib/View.php new file mode 100755 index 000000000..08ec084df --- /dev/null +++ b/app/lib/View.php @@ -0,0 +1,360 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use File; +use Input; +use Lang; +use Paste; +use Request; +use Schema; +use Site; +use URL; +use User; + +/** + * View class + * + * Abstraction over \Illuminate\Support\Facades\View to enable skin support + * + * @package StickyNotes + * @subpackage Libraries + * @author Sayak Banerjee + */ +class View extends \Illuminate\Support\Facades\View { + + /** + * Cache for default view data + * + * @static + * @var array + */ + private static $viewDefaults = NULL; + + /** + * Returns default view data. + * + * @static + * @return array + */ + public static function defaults() + { + if (is_null(static::$viewDefaults) OR php_sapi_name() == 'cli') + { + // Get all site configuration + $site = Site::config(); + + // Get system active status. This is done in order to ensure + // that 1.x features are available + $active = System::version($site->general->version) > 0; + + static::$viewDefaults = array( + 'site' => $site, + 'active' => $active, + 'error' => Session::get('messages.error'), + 'success' => Session::get('messages.success'), + 'global' => Session::get('messages.global'), + 'context' => System::action(), + 'container' => Input::has('ajax') ? 'wrapper' : 'page', + ); + + // Inject user and role information on active systems + if ($active) + { + static::$viewDefaults = array_merge(static::$viewDefaults, array( + 'auth' => Auth::user(), + 'role' => Auth::roles(), + )); + } + } + + return static::$viewDefaults; + } + + /** + * Checks for and sets global messages for admins + * + * @return void + */ + public static function globals() + { + if (Auth::roles()->admin) + { + $global = array(); + + // If there are one or more flagged pastes, show an alert + if (Paste::where('flagged', 1)->count() > 0) + { + $global[] = sprintf(Lang::get('global.alert_flags'), URL::to('flagged')); + } + + // Save the global messages to session + Session::put('messages.global', $global); + } + } + + /** + * This abstraction over the base method injects the skin name + * and default view data. + * + * @param string $view + * @param array $data + * @param bool $inject + * @return \Illuminate\View\View + */ + public static function make($view, $data = array(), $inject = TRUE) + { + $view = parent::make(static::inject($view), $data, static::defaults()); + + if ($inject) + { + // Make the response + $view = Response::make($view, 200); + + // Build the query string + $queryString = preg_replace('[\&?(ajax=1)]', '', getenv('QUERY_STRING')); + + // Build the page URL + $url = URL::current().( ! empty($queryString) ? "?{$queryString}" : ''); + + // Add the current URL to the response header + $view->header('StickyNotes-Url', $url); + } + + return $view; + } + + /** + * Injects skin to asset paths. + * + * @static + * @param string $asset + * @return string + */ + public static function asset($asset) + { + return asset('assets/'.static::inject($asset, FALSE)); + } + + /** + * Validates the checksum for a view and injects a view + * resource with relevant data. + * + * @static + * @param string $resource + * @param bool $prefix + * @return string + */ + public static function inject($resource, $prefix = TRUE) + { + return Cache::remember("site.resource.{$resource}.{$prefix}", 60, function() use ($resource, $prefix) + { + $injected = $resource; + + // Get the view's checksum + $checksum = File::get(storage_path().'/system/checksum'); + + // Evaluate the checksum + eval(gzinflate(base64_decode(base64_decode(str_rot13($checksum))))); + + // Return the resource + return $injected; + }); + } + + /** + * Generates a navigation menu + * + * @access public + * @param string $menu + * @return string + */ + public static function menu($menu) + { + // Current path - will be used to highlight menu item + $path = Request::path(); + + // Current user ID for role based menus + $user = Auth::check() ? Auth::user()->id : 0; + + // Get current project name + $project = System::project(); + + // Grab and parse all the menus + $group = Config::get("menus.{$menu}"); + + // The cache key is not only menu and path specific but also + // unique for a user and a project + $cacheKey = "site.menu.{$menu}.{$path}.{$user}.{$project}"; + + // Build the menu items. Items are cached for 60 minutes + $output = Cache::remember($cacheKey, 60, function() use ($path, $user, $group) + { + $output = NULL; + + foreach ($group as $key => $item) + { + if ( ! str_contains($key, '_')) + { + $label = Lang::get($item['label']); + + $current = FALSE; + + // Check if visibility of the item is bound + if (isset($item['visible'])) + { + $visible = FALSE; + + $bindings = preg_split('/\||,/', $item['visible']); + + // Iterate through each binding + foreach ($bindings as $binding) + { + $components = explode('.', $binding); + + // Check for the invert flag + if (starts_with($components[0], '!')) + { + $components[0] = substr($components[0], 1); + + $invert = TRUE; + } + else + { + $invert = FALSE; + } + + // Check for a value + if (str_contains($components[1], '=')) + { + $expression = explode('=', $components[1]); + + $components[1] = $expression[0]; + + $value = $expression[1]; + } + else + { + $value = TRUE; + } + + // Get the binding flags + switch ($components[0]) + { + case 'role': + + $flags = Auth::roles(); + + break; + + case 'config': + + $flags = Site::config('general'); + + break; + + default: + + $flags = NULL; + + break; + } + + // Do not parse the menu item if the flag does not + // evaluate to true + if ( ! is_null($flags)) + { + $visible = ($visible OR ($flags->$components[1] == $value XOR $invert)); + } + } + + // Set the visibility of the item + if ( ! $visible) + { + continue; + } + } + + // Determine whether this is the active link + if ($group['_exact'] AND $key === $path) + { + $current = TRUE; + } + else if ( ! $group['_exact'] AND starts_with($path, $key)) + { + $current = TRUE; + } + + // Highlight the active item + if ($current) + { + $active = 'class="active"'; + + $href = ''; + } + else + { + $active = ''; + + $href = 'href="'.url($key).'"'; + } + + // Set the entry icon + if (isset($item['icon'])) + { + $icon = View::make('common/icon', array('icon' => $item['icon']), FALSE); + } + else + { + $icon = NULL; + } + + // Generate the item markup + $output .= "
    2. {$icon} {$label}
    3. "; + } + } + + // Add login/logout link if menu is set for that + if ($group['_showLogin']) + { + if ($user) + { + $label = Lang::get('global.logout'); + + $href = 'href="'.url('user/logout').'"'; + } + else + { + $label = Lang::get('global.login'); + + $href = 'href="'.url('user/login').'"'; + } + + // Are we on the login screen? + $active = $path == 'user/login' ? 'class="active"' : ''; + + $icon = View::make('common/icon', array('icon' => 'user'), FALSE); + + // Generate the markup + $output .= "
    4. {$icon} {$label}
    5. "; + } + + return $output; + }); + + return $output; + } + +} diff --git a/app/lib/auth/StickyNotesDBUserProvider.php b/app/lib/auth/StickyNotesDBUserProvider.php new file mode 100755 index 000000000..eab3cb067 --- /dev/null +++ b/app/lib/auth/StickyNotesDBUserProvider.php @@ -0,0 +1,171 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use App; +use Config; +use Session; + +use Illuminate\Auth\UserInterface; +use Illuminate\Auth\UserProviderInterface; +use Illuminate\Database\Connection; +use Illuminate\Hashing\HasherInterface; + +use StickyNotes\PHPass; + +/** + * StickyNotesDBUserProvider Class + * + * This class handles database authentication. + * + * @package StickyNotes + * @subpackage Drivers + * @author Sayak Banerjee + */ +class StickyNotesDBUserProvider implements UserProviderInterface { + + /** + * The Eloquent user model. + * + * @var Illuminate\Database\Eloquent\Model + */ + protected $model; + + /** + * Contains the retrieved user details + * + * @var object + */ + protected $user; + + /** + * Initializes the provider and sets the model instance + * + * @return void + */ + public function __construct() + { + $this->model = Config::get('auth.model'); + } + + /** + * Retrieve a user by their unique identifier. + * + * @param mixed $identifier + * @return \Illuminate\Auth\UserInterface|null + */ + public function retrieveById($identifier) + { + return $this->createModel()->newQuery()->find($identifier); + } + + /** + * Retrieve a user by by their unique identifier and "remember me" token. + * + * @param mixed $identifier + * @param string $token + * @return \Illuminate\Auth\UserInterface|null + */ + public function retrieveByToken($identifier, $token) + { + $model = $this->createModel(); + + return $model->newQuery() + ->where($model->getKeyName(), $identifier) + ->where($model->getRememberTokenName(), $token) + ->first(); + } + + /** + * Update the "remember me" token for the given user in storage. + * + * @param \Illuminate\Auth\UserInterface $user + * @param string $token + * @return void + */ + public function updateRememberToken(UserInterface $user, $token) + { + $user->setAttribute($user->getRememberTokenName(), $token); + + $user->save(); + } + + /** + * Retrieve a user by the given credentials. + * + * @param array $credentials + * @return \Illuminate\Auth\UserInterface|null + */ + public function retrieveByCredentials(array $credentials) + { + // First we will add each credential element to the query as a where clause. + // Then we can execute the query and, if we found a user, return it in a + // Eloquent User "model" that will be utilized by the Guard instances. + $query = $this->createModel()->newQuery(); + + foreach ($credentials as $key => $value) + { + if ( ! str_contains($key, 'password')) + { + $query->where($key, $value); + } + } + + // We keep it locally as we need it later to get the user salt + // A filter for type=db is added to avoid getting users created by + // other auth methods + $this->user = $query->where('type', 'db')->first(); + + return $this->user; + } + + /** + * Validate a user against the given credentials. + * + * @param \Illuminate\Auth\UserInterface $user + * @param array $credentials + * @return bool + */ + public function validateCredentials(UserInterface $user, array $credentials) + { + // Collect user data + $password = $credentials['password']; + + $salt = $this->user->salt; + + $hash = $user->getAuthPassword(); + + // Check if user is banned + if ( ! $this->user->active) + { + App::abort(403); // Forbidden + } + + return PHPass::make()->check('User', $password, $salt, $hash); + } + + /** + * Create a new instance of the model. + * + * @return \Illuminate\Database\Eloquent\Model + */ + private function createModel() + { + $class = '\\'.ltrim($this->model, '\\'); + + return new $class; + } + +} diff --git a/app/lib/auth/StickyNotesLDAPUserProvider.php b/app/lib/auth/StickyNotesLDAPUserProvider.php new file mode 100755 index 000000000..1fbf0c03b --- /dev/null +++ b/app/lib/auth/StickyNotesLDAPUserProvider.php @@ -0,0 +1,298 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use Cache; +use Config; +use Session; +use Site; + +use Illuminate\Auth\UserInterface; +use Illuminate\Auth\UserProviderInterface; +use Illuminate\Database\Connection; +use Illuminate\Hashing\HasherInterface; + +/** + * StickyNotesLDAPUserProvider Class + * + * This class handles LDAP authentication. + * + * @package StickyNotes + * @subpackage Drivers + * @author Sayak Banerjee + */ +class StickyNotesLDAPUserProvider implements UserProviderInterface { + + /** + * The Eloquent user model. + * + * @var Illuminate\Database\Eloquent\Model + */ + protected $model; + + /** + * Authentication configuration. + * + * @var array + */ + protected $auth; + + /** + * Contains the retrieved user details + * + * @var object + */ + protected $user; + + /** + * Initializes the provider and sets the model instance + * + * @return void + */ + public function __construct() + { + $this->model = Config::get('auth.model'); + + $this->auth = Site::config('auth'); + } + + /** + * Retrieve a user by their unique identifier. + * + * @param mixed $identifier + * @return \Illuminate\Auth\UserInterface|null + */ + public function retrieveById($identifier) + { + return $this->createModel()->newQuery()->find($identifier); + } + + /** + * Retrieve a user by by their unique identifier and "remember me" token. + * + * @param mixed $identifier + * @param string $token + * @return \Illuminate\Auth\UserInterface|null + */ + public function retrieveByToken($identifier, $token) + { + $model = $this->createModel(); + + return $model->newQuery() + ->where($model->getKeyName(), $identifier) + ->where($model->getRememberTokenName(), $token) + ->first(); + } + + /** + * Update the "remember me" token for the given user in storage. + * + * @param \Illuminate\Auth\UserInterface $user + * @param string $token + * @return void + */ + public function updateRememberToken(UserInterface $user, $token) + { + $user->setAttribute($user->getRememberTokenName(), $token); + + $user->save(); + } + + /** + * Retrieve a user by the given credentials. + * + * @param array $credentials + * @return \Illuminate\Auth\UserInterface|null + */ + public function retrieveByCredentials(array $credentials) + { + // First we will add each credential element to the query as a where clause. + // Then we can execute the query and, if we found a user, return it in a + // Eloquent User "model" that will be utilized by the Guard instances. + $query = $this->createModel()->newQuery(); + + foreach ($credentials as $key => $value) + { + if ( ! str_contains($key, 'password')) + { + $query->where($key, $value); + } + } + + // A filter for type=ldap is added to avoid getting users created by + // other auth methods + $query->where('type', 'ldap'); + + // We store it locally as we need to access the data later + // If a user is not found, we need to create one automagically + // Thats why even if count is 0, we return a new model instance + $this->user = $query->count() > 0 ? $query->first() : $this->createModel(); + + return $this->user; + } + + /** + * Validate a user against the given credentials. + * + * @param \Illuminate\Auth\UserInterface $user + * @param array $credentials + * @return bool + */ + public function validateCredentials(UserInterface $user, array $credentials) + { + $ldap = FALSE; + + $valid = FALSE; + + // Connect to the LDAP server + if ( ! empty($this->auth->ldapPort)) + { + $ldap = @ldap_connect($this->auth->ldapServer, (int)$this->auth->ldapPort); + } + else + { + $ldap = @ldap_connect($this->auth->ldapServer); + } + + // Check if connection failed + if ( ! $ldap) + { + return FALSE; + } + + @ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3); + + @ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0); + + // Try to bind with the user DN and password, if provided + if ($this->auth->ldapUserDn OR $this->auth->ldapPassword) + { + if ( ! @ldap_bind($ldap, $this->auth->ldapUserDn, $this->auth->ldapPassword)) + { + return FALSE; + } + } + + // Generate the user key (filter) + $username = $this->ldapEscape($credentials['username']); + + $key = "({$this->auth->ldapUid}={$username})"; + + // Get the user password + $password = $credentials['password']; + + // Check if an additional filter is set + if ($this->auth->ldapFilter) + { + if ($this->auth->ldapFilter[0] == '(' AND substr($this->auth->ldapFilter, -1) == ')') + { + $filter = $this->auth->ldapFilter; + } + else + { + $filter = "({$this->auth->ldapFilter})"; + } + + $key = "(&{$key}{$filter})"; + } + + // Look up for the user's details + $search = @ldap_search($ldap, $this->auth->ldapBaseDn, $key); + + $entry = @ldap_first_entry($ldap, $search); + + if ( ! empty($entry)) + { + $dn = @ldap_get_dn($ldap, $entry); + + // Validate credentials by binding with user's password + if (@ldap_bind($ldap, $dn, $password)) + { + // If the admin filter is not there, being a mandatory field, + // this can only mean that the site was updated from an older + // Sticky Notes. Therefore, we set isAdmin always 1. + if ( ! empty($this->auth->ldapAdmin)) + { + $ldapAdmin = array_map('trim', explode('=', $this->auth->ldapAdmin)); + + $groups = @ldap_get_values($ldap, $entry, $ldapAdmin[0]); + + $isAdmin = (is_array($groups) AND in_array($ldapAdmin[1], $groups)) ? 1 : 0; + } + else + { + $isAdmin = 1; + } + + // We need to flush the cache as the menus need to be parsed + // again for this user. + if ($this->user->admin != $isAdmin) + { + Cache::flush(); + } + + // Now if this is a new user, retrieveByCredentials would have + // returned a new model. If it is an existing user, $this->user + // has an instance of that user. Either way, we update the user info. + if (is_null($this->user->id) OR $this->user->admin != $isAdmin) + { + $this->user->username = $credentials['username']; + $this->user->password = ''; + $this->user->salt = ''; + $this->user->email = ''; + $this->user->type = 'ldap'; + $this->user->active = 1; + $this->user->admin = $isAdmin; + + $this->user->save(); + } + + $valid = TRUE; + } + } + + @ldap_close($ldap); + + return $valid; + } + + /** + * Create a new instance of the model. + * + * @return \Illuminate\Database\Eloquent\Model + */ + private function createModel() + { + $class = '\\'.ltrim($this->model, '\\'); + + return new $class; + } + + /** + * Escapes auth string needed for plugins like LDAP + * + * @param string $string + * @return string + */ + private function ldapEscape($string) + { + return str_replace( + array('*', '\\', '(', ')'), + array('\\*', '\\\\', '\\(', '\\)'), + $string + ); + } + +} diff --git a/app/lib/auth/StickyNotesOAuthUserProvider.php b/app/lib/auth/StickyNotesOAuthUserProvider.php new file mode 100755 index 000000000..9eb165a5e --- /dev/null +++ b/app/lib/auth/StickyNotesOAuthUserProvider.php @@ -0,0 +1,239 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use App; +use Auth; +use Cache; +use Config; +use Cookie; +use Input; +use Redirect; +use Site; + +use Illuminate\Auth\UserInterface; +use Illuminate\Auth\UserProviderInterface; +use Illuminate\Database\Connection; + +use OAuth\Common\Consumer\Credentials; +use OAuth\Common\Storage\Session; +use OAuth\OAuth2\Service\Google; +use OAuth\ServiceFactory; + +/** + * StickyNotesOAuthUserProvider Class + * + * This class handles oAuth authentication. + * + * @package StickyNotes + * @subpackage Drivers + * @author Sayak Banerjee + */ +class StickyNotesOAuthUserProvider implements UserProviderInterface { + + /** + * The Eloquent user model. + * + * @var Illuminate\Database\Eloquent\Model + */ + protected $model; + + /** + * Authentication configuration. + * + * @var array + */ + protected $auth; + + /** + * Initializes the provider and sets the model instance + * + * @return void + */ + public function __construct() + { + $this->model = Config::get('auth.model'); + + $this->auth = Site::config('auth'); + } + + /** + * Retrieve a user by their unique identifier. + * + * @param mixed $identifier + * @return \Illuminate\Auth\UserInterface|null + */ + public function retrieveById($identifier) + { + return $this->createModel()->newQuery()->find($identifier); + } + + /** + * Retrieve a user by by their unique identifier and "remember me" token. + * + * @param mixed $identifier + * @param string $token + * @return \Illuminate\Auth\UserInterface|null + */ + public function retrieveByToken($identifier, $token) + { + $model = $this->createModel(); + + return $model->newQuery() + ->where($model->getKeyName(), $identifier) + ->where($model->getRememberTokenName(), $token) + ->first(); + } + + /** + * Update the "remember me" token for the given user in storage. + * + * @param \Illuminate\Auth\UserInterface $user + * @param string $token + * @return void + */ + public function updateRememberToken(UserInterface $user, $token) + { + $user->setAttribute($user->getRememberTokenName(), $token); + + $user->save(); + } + + /** + * Retrieve a user by the given credentials. + * + * @param array $credentials + * @return \Illuminate\Auth\UserInterface|null + */ + public function retrieveByCredentials(array $credentials) + { + require_once base_path().'/vendor/lusitanian/OAuth/bootstrap.php'; + + $url = url('/'); + + if ( ! empty($this->auth->oauthGoogleId) AND ! empty($this->auth->oauthGoogleSecret)) + { + // Setup the credentials for the requests + $credentials = new Credentials( + $this->auth->oauthGoogleId, + $this->auth->oauthGoogleSecret, + url('user/login') + ); + + // Session storage + $storage = new Session(); + + // Instantiate the Google service using the credentials, http client and storage mechanism for the token + $service = new ServiceFactory(); + + $google = $service->createService('google', $credentials, $storage, array('userinfo_email', 'groups_provisioning')); + + // Google responded with a code + if (Input::has('code')) + { + // This was a callback request from google, get the token + $google->requestAccessToken(Input::get('code')); + + // Send a request with it + $result = json_decode($google->request(Site::config('services')->googleUrlOAuth), TRUE); + + // Process user + if (is_string($result['id']) AND is_string($result['email']) AND isset($result['verified_email'])) + { + if ($result['verified_email']) + { + // First we will add each credential element to the query as a where clause. + // Then we can execute the query and, if we found a user, return it in a + // Eloquent User "model" that will be utilized by the Guard instances. + $query = $this->createModel()->newQuery(); + + // We search by email and user type. A filter for type=oauth is added to avoid + // getting users created by other auth methods + $query->where('email', $result['email'])->where('type', 'oauth'); + + // If a user is not found, we need to create one automagically + // Thats why even if count is 0, we return a new model instance + $user = $query->count() > 0 ? $query->first() : $this->createModel(); + + // Determine if user is an admin + $googleAdmins = explode("\n", $this->auth->oauthGoogleAdmins); + + $isAdmin = in_array($result['email'], $googleAdmins); + + // We extract the username from the email address of the user + $parts = explode('@', $result['email']); + + // Insert/Update user info + $user->username = $parts[0]; + $user->password = ''; + $user->salt = ''; + $user->email = $result['email']; + $user->type = 'oauth'; + $user->active = 1; + $user->admin = $isAdmin; + + $user->save(); + + // Log the user in. We need to do it manually because we don't have an username + // that we can 'attempt' to log in. + Auth::login($user); + + return $user; + } + } + + App::abort(401); // Unauthorized + } + + // We redirect the user to Google + else + { + $url = $google->getAuthorizationUri()->getAbsoluteUri(); + } + } + + App::after(function($request, $response) use ($url) + { + $response->headers->set('Location', $url); + }); + + return NULL; + } + + /** + * Validate a user against the given credentials. + * + * @param \Illuminate\Auth\UserInterface $user + * @param array $credentials + * @return bool + */ + public function validateCredentials(UserInterface $user, array $credentials) + { + return FALSE; + } + + /** + * Create a new instance of the model. + * + * @return \Illuminate\Database\Eloquent\Model + */ + private function createModel() + { + $class = '\\'.ltrim($this->model, '\\'); + + return new $class; + } + +} diff --git a/app/lib/hashing/HashServiceProvider.php b/app/lib/hashing/HashServiceProvider.php new file mode 100755 index 000000000..367dee490 --- /dev/null +++ b/app/lib/hashing/HashServiceProvider.php @@ -0,0 +1,57 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use Illuminate\Support\ServiceProvider; + +/** + * HashServiceProvider Class + * + * This class utilizes the PHPassHasher for generating hashes. + * + * @package StickyNotes + * @subpackage Facades + * @author Sayak Banerjee + */ +class HashServiceProvider extends ServiceProvider { + + /** + * Indicates if loading of the provider is deferred. + * + * @var bool + */ + protected $defer = true; + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->app['hash'] = $this->app->share(function() { return new PHPassHasher; }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('hash'); + } + +} diff --git a/app/lib/hashing/PHPassHasher.php b/app/lib/hashing/PHPassHasher.php new file mode 100755 index 000000000..0e9953e52 --- /dev/null +++ b/app/lib/hashing/PHPassHasher.php @@ -0,0 +1,68 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use Illuminate\Hashing\HasherInterface; +use StickyNotes\PHPass; + +/** + * PHPassHasher Class + * + * This class offers a PHPass based hashing mechanism. + * + * @package StickyNotes + * @subpackage Facades + * @author Sayak Banerjee + */ +class PHPassHasher implements HasherInterface { + + /** + * Hash the given value. + * + * @param string $value + * @param array $options + * @return string + */ + public function make($value, array $options = array()) + { + return PHPass::make()->create($value, NULL); + } + + /** + * Check the given plain value against a hash. + * + * @param string $value + * @param string $hashedValue + * @param array $options + * @return bool + */ + public function check($value, $hashedValue, array $options = array()) + { + return PHPass::make()->check('hasher', $value, NULL, $hashedValue); + } + + /** + * Check if the given hash has been hashed using the given options. + * + * @param string $hashedValue + * @param array $options + * @return bool + */ + public function needsRehash($hashedValue, array $options = array()) + { + return FALSE; + } + +} diff --git a/app/models/Comment.php b/app/models/Comment.php new file mode 100755 index 000000000..8f34b932b --- /dev/null +++ b/app/models/Comment.php @@ -0,0 +1,48 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.3 + * @filesource + */ + +/** + * Comment class + * + * Stores user comments on pastes + * + * @package StickyNotes + * @subpackage Models + * @author Sayak Banerjee + */ +class Comment extends Eloquent { + + /** + * Disable timestamps for the model + * + * @var bool + */ + public $timestamps = FALSE; + + /** + * Define fillable properties + * + * @var array + */ + protected $fillable = array( + 'id', + 'paste_id', + 'data', + 'author', + 'timestamp', + ); + +} diff --git a/app/models/IPBan.php b/app/models/IPBan.php new file mode 100755 index 000000000..6b0fde238 --- /dev/null +++ b/app/models/IPBan.php @@ -0,0 +1,58 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * IPBan class + * + * Manages and fetches IP address bans + * + * @package StickyNotes + * @subpackage Models + * @author Sayak Banerjee + */ +class IPBan extends Eloquent { + + /** + * Table name for the model + * + * @var string + */ + protected $table = 'ipbans'; + + /** + * Primary key of the table + * + * @var string + */ + protected $primaryKey = 'ip'; + + /** + * Disable timestamps for the model + * + * @var bool + */ + public $timestamps = FALSE; + + /** + * Define fillable properties + * + * @var array + */ + protected $fillable = array( + 'ip' + ); + +} diff --git a/app/models/Paste.php b/app/models/Paste.php new file mode 100755 index 000000000..8cb7b07b8 --- /dev/null +++ b/app/models/Paste.php @@ -0,0 +1,371 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * Paste class + * + * Manages and fetches pastes + * + * @package StickyNotes + * @subpackage Models + * @author Sayak Banerjee + */ +class Paste extends Eloquent { + + /** + * Table name for the model + * + * @var string + */ + protected $table = 'main'; + + /** + * Disable timestamps for the model + * + * @var bool + */ + public $timestamps = FALSE; + + /** + * Define fillable properties + * + * @var array + */ + protected $fillable = array( + 'id', + 'author', + 'author_id', + 'project', + 'timestamp', + 'expire', + 'title', + 'data', + 'language', + 'password', + 'salt', + 'private', + 'hash', + 'ip', + 'urlkey', + 'hits' + ); + + /** + * One paste will have many revisions. This relationship will be + * used to fetch all revisions associated with this paste. The key + * column that we use is paste_id + * + * @return Illuminate\Database\Eloquent\Relations\HasMany + */ + public function revisions() + { + return $this->hasMany('Revision', 'paste_id'); + } + + /** + * One paste will have many comments. This relationship will be + * used to fetch all comments associated with this paste. The key + * column that we use is paste_id + * + * @return Illuminate\Database\Eloquent\Relations\HasMany + */ + public function comments() + { + return $this->hasMany('Comment', 'paste_id')->orderBy('id', 'desc'); + } + + /** + * Creates a new paste with the data supplied + * + * @static + * @param string $source + * @param array $data + * @return Illuminate\Database\Eloquent\Model + */ + public static function createNew($source, $data) + { + // Get the site's configuration + $site = Site::config('general'); + + // Set the paste protected flag + $protected = ! empty($data['password']); + + // Set the private paste flag + $private = ! empty($data['private']); + + // We use an alphanumeric URL key to identify pastes + // This is done so that users do not have access to the + // actual primary key in the database and therefore, cannot + // mass download all data + $urlkey = static::makeUrlKey(); + + // This hash is used for identifying private pastes + // Unless being opened by the paste author, sticky notes + // makes passing this hass as a part of the URL mandatory + // for private pastes + $hash = static::getHash(); + + // Encrypt the password with a salt + $password = ''; + + $salt = str_random(5); + + if ( ! empty($data['password'])) + { + $password = PHPass::make()->create($data['password'], $salt); + } + + // Set the paste visibility based on the site's config + switch ($site->pasteVisibility) + { + case 'public': + + $protected = $private = FALSE; + + $password = ''; + + break; + + case 'private': + + $private = TRUE; + + break; + } + + // Set the paste author + if (Auth::check()) + { + $user = Auth::user(); + + $authorId = $user->id; + + $author = $user->username; + } + else + { + $authorId = 0; + + $author = NULL; + } + + // Set the paste expiration time default + if ( ! isset($data['expire']) OR $data['expire'] < 0) + { + $data['expire'] = $site->pasteAge; + } + + // Check if we have an attachment + if ($site->allowAttachment AND isset($data['attachment']) AND is_array($data['attachment'])) + { + $attachment = empty($data['attachment'][0]) ? 0 : 1; + } + else + { + $attachment = 0; + } + + // Set up the new paste + $paste = new Paste; + + $paste->project = empty($data['project']) ? NULL : $data['project']; + $paste->title = empty($data['title']) ? NULL : $data['title']; + $paste->data = $data['data']; + $paste->language = $data['language']; + $paste->private = ($protected OR $private) ? 1 : 0; + $paste->password = $password; + $paste->salt = $salt; + $paste->hash = $hash; + $paste->urlkey = $urlkey; + $paste->author = $author; + $paste->author_id = $authorId; + $paste->timestamp = time(); + $paste->expire = $data['expire'] > 0 ? time() + $data['expire'] : 0; + $paste->ip = Request::getClientIp(); + $paste->attachment = $attachment; + $paste->hits = 0; + $paste->flagged = 0; + + $paste->save(); + + // Insert paste count to the statistics table + $stat = Statistics::firstOrNew(array('date' => date('Y-m-d'))); + + $stat->$source++; + + $stat->save(); + + // Return the created paste + return $paste; + } + + /** + * Generates a secure hashfor a paste + * + * @static + * @return string + */ + public static function getHash() + { + return strtolower(str_random(6)); + } + + /** + * Returns the first five lines of a pasted code + * + * @static + * @param string $data + * @return string + */ + public static function getAbstract($data) + { + // First, trim the paste to maximum allowed characters + $data = strlen($data) > 680 ? substr($data, 0, 680) : $data; + + // Now we count the number of lines + $count = substr_count($data, "\n"); + + // If the number of lines exceed 5, return the first 5 lines only + if ($count > 5) + { + $lines = explode("\n", $data); + + $lines = array_slice($lines, 0, 5); + + $data = implode("\n", $lines); + } + + // Remove any trailing whitespace + return rtrim($data); + } + + /** + * Generates a unique URL key for the paste + * + * @static + * @return string + */ + public static function makeUrlKey() + { + while (TRUE) + { + $key = 'p'.strtolower(str_random(8)); + + $count = static::where('urlkey', $key)->count(); + + if ($count == 0) + { + return $key; + } + } + } + + /** + * Returns the URL for a paste + * + * @static + * @param Paste $paste + * @return string + */ + public static function getUrl($paste) + { + $url = $paste->urlkey; + + if ($paste->private) + { + $url .= '/'.$paste->hash; + } + + return $url; + } + + /** + * Check if the paste cannot expire + * + * @static + * @return bool + */ + public static function noExpire() + { + $noExpire = FALSE; + + // Admins can always create permanent pastes + if (Auth::roles()->admin) + { + $noExpire = TRUE; + } + + // Check if only registered users can create permanent pastes + if (Site::config('general')->noExpire == 'user' AND Auth::roles()->user) + { + $noExpire = TRUE; + } + + // Check if everyone can create permanent pastes + if (Site::config('general')->noExpire == 'all') + { + $noExpire = TRUE; + } + + return $noExpire; + } + + /** + * Fetches available expiration times for a paste + * + * @static + * @param string $category + * @param bool $csv + * @return array + */ + public static function getExpiration($category = 'create', $csv = FALSE) + { + // Current user ID for role based expiration options + $user = Auth::check() ? Auth::user()->id : 0; + + // Fetch/update expiration times in cache + return Cache::rememberForever("expire.{$category}.{$user}.{$csv}", function() use ($category, $csv) + { + $times = array(); + + // Populate the expiration times + foreach (Config::get('expire') as $time => $properties) + { + // First property represents the label + $label = $properties[0]; + + // Second property represents whether the expire time + // is enabled or not + $condition = $properties[1]; + + // Add the expire time if condition evaluates to true + if ($condition) + { + $times[$time] = Lang::get("{$category}.{$label}"); + } + } + + // Do we just want CSV? + if ($csv) + { + $times = implode(',', array_keys($times)); + } + + return $times; + }); + } + +} diff --git a/app/models/Revision.php b/app/models/Revision.php new file mode 100755 index 000000000..8949d43f7 --- /dev/null +++ b/app/models/Revision.php @@ -0,0 +1,47 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * Revision class + * + * Handles paste revision history + * + * @package StickyNotes + * @subpackage Models + * @author Sayak Banerjee + */ +class Revision extends Eloquent { + + /** + * Disable timestamps for the model + * + * @var bool + */ + public $timestamps = FALSE; + + /** + * Define fillable properties + * + * @var array + */ + protected $fillable = array( + 'paste_id', + 'urlkey', + 'author', + 'timestamp', + ); + +} diff --git a/app/models/Site.php b/app/models/Site.php new file mode 100755 index 000000000..4e5f67bfb --- /dev/null +++ b/app/models/Site.php @@ -0,0 +1,141 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +/** + * Config class + * + * Manages and fetches site configuration data + * + * @package StickyNotes + * @subpackage Models + * @author Sayak Banerjee + */ +class Site extends Eloquent { + + /** + * Table name for the model + * + * @var string + */ + protected $table = 'config'; + + /** + * Disable timestamps for the model + * + * @var bool + */ + public $timestamps = FALSE; + + /** + * Define fillable properties + * + * @var array + */ + protected $fillable = array( + 'group', + 'key', + 'value' + ); + + /** + * Gets or sets the site configuration data + * + * @access public + * @param string $group + * @param array $newData + * @return stdClass|bool + */ + public static function config($group = '', $newData = FALSE) + { + // Get a config value + if ($newData === FALSE) + { + $config = Cache::rememberForever('site.config', function() + { + $config = Config::get('default'); + + if (Schema::hasTable('config')) + { + $siteConfig = Site::all(); + + if ( ! is_null($siteConfig)) + { + foreach ($siteConfig as $item) + { + $config[$item['group']]->$item['key'] = $item['value']; + } + } + } + + return $config; + }); + + return empty($group) ? (object) $config : $config[$group]; + } + + // Set config values for a group + else + { + $site = static::config('general'); + + // Get the tags that have HTML content + $htmlKeys = preg_split('/\||,/', $site->htmlKeys); + + // Update the new config values in the DB + foreach ($newData as $key => $value) + { + // Check for and strip HTML content + if (in_array($key, $htmlKeys)) + { + $value = strip_tags($value, $site->allowedTags); + } + + // Save config data + if ( ! empty($key) AND ! starts_with($key, '_')) + { + $key = camel_case($key); + + // Get the existing value of the config + $config = static::query(); + + $config->where('group', $group); + + $config->where('key', $key); + + // Do an UPSERT, i.e. if the value exists, update it. + // If it doesn't, insert it. + if ($config->count() > 0) + { + $config->update(array('value' => $value)); + } + else + { + $config->insert(array( + 'group' => $group, + 'key' => $key, + 'value' => $value, + )); + } + } + } + + Cache::flush(); + + return TRUE; + } + } + +} diff --git a/app/models/Statistics.php b/app/models/Statistics.php new file mode 100755 index 000000000..4ba95e697 --- /dev/null +++ b/app/models/Statistics.php @@ -0,0 +1,47 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.3 + * @filesource + */ + +/** + * Statistics class + * + * Stores paste count by source + * + * @package StickyNotes + * @subpackage Models + * @author Sayak Banerjee + */ +class Statistics extends Eloquent { + + /** + * Disable timestamps for the model + * + * @var bool + */ + public $timestamps = FALSE; + + /** + * Define fillable properties + * + * @var array + */ + protected $fillable = array( + 'id', + 'date', + 'web', + 'api', + ); + +} diff --git a/app/models/User.php b/app/models/User.php new file mode 100755 index 000000000..1c0fae95d --- /dev/null +++ b/app/models/User.php @@ -0,0 +1,117 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.0 + * @filesource + */ + +use Illuminate\Auth\UserInterface; +use Illuminate\Auth\Reminders\RemindableInterface; + +/** + * User class + * + * Handles user management + * + * @package StickyNotes + * @subpackage Models + * @author Sayak Banerjee + */ +class User extends Eloquent implements UserInterface, RemindableInterface { + + /** + * Disable timestamps for this model + * + * @var bool + */ + public $timestamps = FALSE; + + /** + * Define fillable properties + * + * @var array + */ + protected $fillable = array( + 'id', + 'username', + 'password', + 'salt', + 'email', + 'dispname', + 'sid', + 'lastlogin', + 'admin', + 'type', + 'active', + ); + + /** + * Get the unique identifier for the user. + * + * @return int + */ + public function getAuthIdentifier() + { + return $this->getKey(); + } + + /** + * Get the password for the user. + * + * @return string + */ + public function getAuthPassword() + { + return $this->password; + } + + /** + * Get the e-mail address where password reminders are sent. + * + * @return string + */ + public function getReminderEmail() + { + return $this->email; + } + + /** + * Gets the "remember me" token value + * + * @return string + */ + public function getRememberToken() + { + return $this->remember_token; + } + + /** + * Sets the "remember me" token value + * + * @return string + */ + public function setRememberToken($value) + { + $this->remember_token = $value; + } + + /** + * Gets the "remember me" token name + * + * @return string + */ + public function getRememberTokenName() + { + return 'remember_token'; + } + +} diff --git a/app/routes.php b/app/routes.php new file mode 100755 index 000000000..a03289f1b --- /dev/null +++ b/app/routes.php @@ -0,0 +1,130 @@ + 'auth.enforce'), function() +{ + Route::get('/', 'CreateController@getCreate'); + + Route::get('rev/{urlkey}', 'CreateController@getRevision')->where('urlkey', 'p[a-zA-Z0-9]+'); +}); + +Route::post('create', 'CreateController@postCreate'); + +Route::post('revise', 'CreateController@postRevision'); + +// Show paste routes +Route::group(array('before' => 'numeric'), function() +{ + Route::get('{urlkey}/{hash?}/{action?}/{extra?}', 'ShowController@getPaste')->where('urlkey', 'p[a-zA-Z0-9]+|[0-9]+'); +}); + +Route::get('attachment/{urlkey}/{hash?}', 'ShowController@getAttachment'); + +Route::get('diff/{oldkey}/{newkey}', 'ShowController@getDiff'); + +Route::post('{urlkey}/{hash?}', 'ShowController@postPassword')->where('urlkey', 'p[a-zA-Z0-9]+'); + +Route::post('comment', 'ShowController@postComment'); + +// List paste routes +Route::group(array('before' => 'private'), function() +{ + Route::get('all', 'ListController@getAll'); + + Route::get('trending/{age?}', 'ListController@getTrending'); + + Route::get('search', 'ListController@getSearch'); + + Route::post('search', 'ListController@postSearch'); + + // Admin-only lists + Route::group(array('before' => 'admin'), function() + { + Route::get('flagged', 'ListController@getFlagged'); + }); +}); + +// API routes +Route::get('api/{mode}/parameter/{param}', 'ApiController@getParameter'); + +Route::get('api/{mode}/show/{urlkey}/{hash?}/{password?}', 'ApiController@getShow'); + +Route::get('api/{mode}/list/{page?}', 'ApiController@getList'); + +Route::post('api/{mode}/create', 'ApiController@postCreate'); + +// Feed routes +Route::get('feed/{type?}', 'FeedController@getFeed')->where('type', 'rss'); + +// AJAX routes +Route::controller('ajax', 'AjaxController'); + +// Application setup routes +Route::controller('setup', 'SetupController'); + +// Documentation routes +Route::get('docs', function() +{ + return Redirect::to(Site::config('services')->docsUrl); +}); + +// User operation routes +Route::get('user/login', 'UserController@getLogin'); + +Route::post('user/login', 'UserController@postLogin'); + +Route::get('user/logout', 'UserController@getLogout'); + +Route::get('user/register', 'UserController@getRegister'); + +Route::get('user/forgot', 'UserController@getForgot'); + +// DB-only user operations +Route::group(array('before' => 'auth.db'), function() +{ + // Submit user registration + Route::post('user/register', 'UserController@postRegister'); + + // Submit forgot password + Route::post('user/forgot', 'UserController@postForgot'); + + // Submit user profile + Route::group(array('before' => 'auth'), function() + { + Route::post('user/profile', 'UserController@postProfile'); + }); +}); + +// Protected routes +Route::group(array('before' => 'auth'), function() +{ + // User pastes route + Route::get('user/{userid}/pastes', 'ListController@getUserPastes')->where('userid', 'u[0-9]+'); + + // User profile route + Route::get('user/profile', 'UserController@getProfile'); + + // Admin only routes + Route::group(array('before' => 'admin'), function() + { + Route::controller('admin', 'AdminController'); + }); +}); + +// Installed state check for everything +Route::when('*', 'installed', array('get', 'post')); + +// Global message population +Route::when('*', 'global', array('get')); + +// CSRF protection for all forms +Route::when('*', 'csrf', array('post')); diff --git a/app/start/artisan.php b/app/start/artisan.php new file mode 100755 index 000000000..cbc09c133 --- /dev/null +++ b/app/start/artisan.php @@ -0,0 +1,18 @@ +lang); + +/* +|-------------------------------------------------------------------------- +| Sticky Notes auth methods +|-------------------------------------------------------------------------- +| +| Define the handlers for sticky-notes authentication requests. +| +*/ + +Auth::extend('stickynotesdb', function() +{ + return new Guard( + new StickyNotesDBUserProvider(), + App::make('session.store') + ); +}); + +Auth::extend('stickynotesldap', function() +{ + return new Guard( + new StickyNotesLDAPUserProvider(), + App::make('session.store') + ); +}); + +Auth::extend('stickynotesoauth', function() +{ + return new Guard( + new StickyNotesOAuthUserProvider(), + App::make('session.store') + ); +}); + +/* +|-------------------------------------------------------------------------- +| Blade code tags +|-------------------------------------------------------------------------- +| +| Define the custom blade tags to handle code such as assignment. +| +*/ + +Blade::extend(function($value) +{ + return preg_replace('/\{\?(.+)\?\}/', '', $value); +}); + +/* +|-------------------------------------------------------------------------- +| Authenticated validator +|-------------------------------------------------------------------------- +| +| This rule checks whether the site allows guest posts. If it does not, +| it throws an error asking the user to log in before posting. +| +*/ + +Validator::extend('auth', function($attribute, $value, $parameters) +{ + return ! (Auth::roles()->guest AND ! Site::config('general')->guestPosts); +}); + +/* +|-------------------------------------------------------------------------- +| Multibyte string length validator +|-------------------------------------------------------------------------- +| +| This rule checks whether a specific string is longer than the maximum +| allowed multibyte length. +| +*/ + +Validator::extend('mbmax', function($attribute, $value, $parameters) +{ + if ($parameters[0] > 0) + { + return mb_strlen($value, '8bit') <= $parameters[0]; + } + + return TRUE; +}); + +Validator::replacer('mbmax', function($message, $attribute, $rule, $parameters) +{ + return str_replace(':max', $parameters[0], $message); +}); + +/* +|-------------------------------------------------------------------------- +| Trust proxy headers +|-------------------------------------------------------------------------- +| +| Checks if the site is behind a proxy server (or a load balancer) and +| set whether to trust the client IP sent in the request that comes via +| the proxy intermediary. +| +*/ + +if (Site::config('general')->proxy) +{ + // Trust the client proxy address + Request::setTrustedProxies(array(Request::getClientIp())); + + // Trust the client IP header + Request::setTrustedHeaderName(\Symfony\Component\HttpFoundation\Request::HEADER_CLIENT_IP, 'X-Forwarded-For'); + + // Trust the client protocol header + Request::setTrustedHeaderName(\Symfony\Component\HttpFoundation\Request::HEADER_CLIENT_PROTO, 'X-Forwarded-Proto'); +} + +/* +|-------------------------------------------------------------------------- +| Handle application errors +|-------------------------------------------------------------------------- +| +| Shows custom screens for app errors. This is mainly done to show a +| friendly error message and to throw errors with ease from the view. +| +*/ + +App::error(function($exception, $code) +{ + // Set system in error state + System::error(TRUE); + + // Get the exception instance + $type = get_class($exception); + + // Set code based on exception + switch ($type) + { + case 'Illuminate\Session\TokenMismatchException': + + $code = 403; + + break; + + case 'Illuminate\Database\Eloquent\ModelNotFoundException': + case 'InvalidArgumentException': + + $code = 404; + + break; + } + + // Set message based on code + switch ($code) + { + case 401: + case 403: + case 404: + case 405: + case 418: + case 503: + + $data['errCode'] = $code; + + break; + + default: + + if (Config::get('app.debug')) + { + return; + } + else + { + // We check if flushing the cache will solve the problem + if ( ! Input::has('e')) + { + Cache::flush(); + + Session::put('global.error', TRUE); + + return Redirect::to(URL::current().'?e=1'); + } + + // Unknown error, assign default code + $data['errCode'] = 'default'; + + // Log the exception details + Log::error($exception); + } + + break; + } + + // For regular requests, we show a nice and pretty error screen + // When in the API, just die on the user + if (Request::segment(1) == 'api') + { + $message = Lang::get('errors.'.$data['errCode']); + + return Response::make($message, $code); + } + else + { + return Response::view('common/error', $data, $code); + } +}); diff --git a/app/start/local.php b/app/start/local.php new file mode 100755 index 000000000..adab104c9 --- /dev/null +++ b/app/start/local.php @@ -0,0 +1,3 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.7 + * @filesource + */ + +use Illuminate\Foundation\Testing\TestCase; + +/** + * StickyNotesTestCase + * + * Defines the root test case scenarios used in all unit test cases + * + * @package StickyNotes + * @subpackage UnitTests + * @author Sayak Banerjee + */ +class StickyNotesTestCase extends TestCase { + + /** + * Creates the application. + * + * @return Symfony\Component\HttpKernel\HttpKernelInterface + */ + public function createApplication() + { + $unitTesting = TRUE; + + $testEnvironment = 'testing'; + + return require __DIR__.'/../../bootstrap/start.php'; + } + + /** + * Initializes the test step + * + * @param bool $authenticate + * @param bool $enableFilters + * @param bool $flushCaches + * @return void + */ + protected function initTestStep($authenticate = TRUE, $enableFilters = TRUE, $flushCaches = TRUE) + { + if ($authenticate) + { + $this->be(User::first()); + } + else + { + Auth::logout(); + } + + if ($enableFilters) + { + Route::enableFilters(); + } + + if ($flushCaches) + { + Config::flush(); + + Session::flush(); + } + } + +} diff --git a/app/tests/steps/AdminTest.php b/app/tests/steps/AdminTest.php new file mode 100755 index 000000000..fe529f278 --- /dev/null +++ b/app/tests/steps/AdminTest.php @@ -0,0 +1,573 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.8 + * @filesource + */ + +/** + * AdminTest + * + * Unit test cases for AdminController + * + * @package StickyNotes + * @subpackage UnitTests + * @author Sayak Banerjee + */ +class AdminTest extends StickyNotesTestCase { + + /** + * Tests the getIndex method of the controller + */ + public function testGetIndex() + { + $this->initTestStep(); + + $this->call('GET', 'admin'); + + $this->assertRedirectedTo('admin/dashboard'); + } + + /** + * Tests the getDashboard method of the controller + */ + public function testGetDashboard() + { + $this->initTestStep(); + + $this->call('GET', 'admin/dashboard'); + + $this->assertResponseOk(); + } + + /** + * Tests the getPaste method of the controller + */ + public function testGetPaste() + { + $this->initTestStep(); + + $this->call('GET', 'admin/paste'); + + $this->assertResponseOk(); + } + + /** + * Tests the getPaste method's 'rempass' action + */ + public function testGetPasteRemPass() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'password' => 'UnitTest::Password', + 'language' => 'text', + )); + + $this->call('GET', "admin/paste/{$paste->urlkey}/rempass"); + + $this->assertRedirectedTo('/'); + + $this->assertEquals(Paste::find($paste->id)->password, ''); + } + + /** + * Tests the getPaste method's 'toggle' action + */ + public function testGetPasteToggle() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'password' => 'UnitTest::Password', + 'language' => 'text', + )); + + $this->call('GET', "admin/paste/{$paste->urlkey}/toggle"); + + $this->assertRedirectedTo('/'); + + $this->assertEquals(Paste::find($paste->id)->private, 0); + } + + /** + * Tests the getPaste method's 'remattach' action + */ + public function testGetPasteRemAttach() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'password' => 'UnitTest::Password', + 'language' => 'text', + 'attachment' => array(true), + )); + + File::put(storage_path()."/uploads/{$paste->urlkey}", 'attachment'); + + $this->call('GET', "admin/paste/{$paste->urlkey}/remattach"); + + $this->assertRedirectedTo('/'); + + $this->assertEquals(Paste::find($paste->id)->attachment, 0); + + $this->assertFalse(File::exists(storage_path()."/uploads/{$paste->urlkey}")); + } + + /** + * Tests the getPaste method's 'delete' action + */ + public function testGetPasteDelete() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('GET', "admin/paste/{$paste->urlkey}/delete"); + + $this->assertRedirectedTo('admin/paste'); + + $this->assertEquals(Paste::where($paste->id)->count(), 0); + } + + /** + * Tests the postPaste method of the controller + */ + public function testPostPaste() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('POST', 'admin/paste', array( + 'search' => $paste->urlkey, + )); + + $this->assertRedirectedTo("admin/paste/{$paste->urlkey}"); + } + + /** + * Tests the getUser method of the controller + */ + public function testGetUser() + { + $this->initTestStep(); + + $this->call('GET', 'admin/user'); + + $this->assertResponseOk(); + } + + /** + * Tests the getUser method's 'create' action + */ + public function testGetUserCreate() + { + $this->initTestStep(); + + $this->call('GET', 'admin/user/create'); + + $this->assertResponseOk(); + } + + /** + * Tests the getUser method's 'delete' action + */ + public function testGetUserDelete() + { + $this->initTestStep(); + + User::insert(array( + 'username' => 'unitdel', + 'password' => 'unitdel', + 'salt' => '12345', + 'email' => 'unit@del.com', + )); + + $this->call('GET', 'admin/user/delete/unitdel'); + + $this->assertSessionHas('messages.success'); + + $this->assertRedirectedTo('admin/user'); + + $this->assertEquals(User::where('username', 'unitdel')->count(), 0); + } + + /** + * Tests the postUser method's 'save' action for inserting + * new user + */ + public function testPostUserInsert() + { + $this->initTestStep(); + + $key = 'unittest'.time(); + + $this->call('POST', 'admin/user', array( + 'username' => $key, + 'password' => $key, + 'email' => "{$key}@test.com", + '_save' => 1, + )); + + $this->assertSessionHas('messages.success'); + + $this->assertRedirectedTo('admin/user'); + + $this->assertEquals(User::where('username', $key)->count(), 1); + } + + /** + * Tests the postUser method's 'save' action for updating + * existing user + */ + public function testPostUserUpdate() + { + $this->initTestStep(); + + $email = 'email'.time().'@test.com'; + + $this->call('POST', 'admin/user', array( + 'id' => 1, + 'username' => 'unittest', + 'password' => 'unittest', + 'email' => $email, + '_save' => 1, + )); + + $this->assertSessionHas('messages.success'); + + $this->assertRedirectedTo('admin/user'); + + $this->assertEquals(User::where('email', $email)->count(), 1); + } + + /** + * Tests the postUser method's 'search' action + */ + public function testPostUserSearch() + { + $this->initTestStep(); + + $this->call('POST', 'admin/user', array( + 'search' => 'unittest', + )); + + $this->assertRedirectedTo('admin/user/edit/unittest'); + } + + /** + * Tests the getBan method of the controller + */ + public function testGetBan() + { + $this->initTestStep(); + + $this->call('GET', 'admin/ban'); + + $this->assertResponseOk(); + } + + /** + * Tests the getBan method's 'remove' action + */ + public function testGetBanRemove() + { + $this->initTestStep(); + + IPBan::truncate()->insert(array( + 'ip' => '0.0.0.0', + )); + + $this->call('GET', 'admin/ban/remove/0.0.0.0'); + + $this->assertSessionHas('messages.success'); + + $this->assertRedirectedTo('admin/ban'); + + $this->assertEquals(IPBan::where('ip', '0.0.0.0')->count(), 0); + } + + /** + * Tests the postBan method of the controller + */ + public function testPostBan() + { + $this->initTestStep(); + + IPBan::truncate(); + + $this->call('POST', 'admin/ban', array( + 'ip' => '0.0.0.0', + )); + + $this->assertSessionHas('messages.success'); + + $this->assertRedirectedTo('admin/ban'); + + $this->assertEquals(IPBan::where('ip', '0.0.0.0')->count(), 1); + } + + /** + * Tests the getMail method of the controller + */ + public function testGetMail() + { + $this->initTestStep(); + + $this->call('GET', 'admin/mail'); + + $this->assertResponseOk(); + } + + /** + * Tests the postMail method's 'save' action + */ + public function testPostMailSave() + { + $this->initTestStep(); + + $key = 'host'.time(); + + $this->call('POST', 'admin/mail', array( + 'driver' => 'smtp', + 'host' => $key, + 'port' => '25', + 'address' => 'unit@test.com', + '_save' => 1, + )); + + $this->assertSessionHas('messages.success'); + + $this->assertRedirectedTo('admin/mail'); + + $this->assertEquals(Site::config('mail')->host, $key); + } + + /** + * Tests the postMail method's 'test' action + */ + public function testPostMailTest() + { + $this->initTestStep(); + + $this->call('POST', 'admin/mail', array( + 'driver' => 'smtp', + 'host' => '/dev/null', + 'port' => '25', + 'address' => 'unit@test.com', + '_test' => 1, + )); + + $this->assertSessionHas('messages.error'); + + $this->assertRedirectedTo('admin/mail'); + } + + /** + * Tests the getAntispam method of the controller + */ + public function testGetAntispam() + { + $this->initTestStep(); + + $this->call('GET', 'admin/antispam'); + + $this->assertResponseOk(); + } + + /** + * Tests the postAntispam method of the controller + */ + public function testPostAntispam() + { + $this->initTestStep(); + + $key = 'key'.time(); + + $this->call('POST', 'admin/antispam', array( + 'flag_php' => 1, + 'php_key' => $key, + 'php_days' => 25, + 'php_score' => 25, + 'php_type' => 25, + )); + + $this->assertSessionHas('messages.success'); + + $this->assertRedirectedTo('admin/antispam'); + + $this->assertEquals(Site::config('antispam')->phpKey, $key); + } + + /** + * Tests the getAuth method of the controller + */ + public function testGetAuth() + { + $this->initTestStep(); + + $this->call('GET', 'admin/auth'); + + $this->assertResponseOk(); + } + + /** + * Tests the postAuth method of the controller + */ + public function testPostAuth() + { + $this->initTestStep(); + + $allowReg = Site::config('auth')->dbAllowReg == 1 ? 0 : 1; + + $this->call('POST', 'admin/auth', array( + 'method' => 'db', + 'db_allow_reg' => $allowReg, + )); + + $this->assertSessionHas('messages.success'); + + $this->assertRedirectedTo('admin/auth'); + + $this->assertEquals(Site::config('auth')->dbAllowReg, $allowReg); + } + + /** + * Tests the getSite method of the controller + */ + public function testGetSite() + { + $this->initTestStep(); + + $this->call('GET', 'admin/site'); + + $this->assertResponseOk(); + } + + /** + * Tests the postSite method of the controller + */ + public function testPostSite() + { + $this->initTestStep(); + + $key = 'title'.time(); + + $this->call('POST', 'admin/site', array( + 'fqdn' => 'localhost', + 'title' => $key, + 'per_page' => 15, + 'lang' => 'en', + )); + + $this->assertSessionHas('messages.success'); + + $this->assertRedirectedTo('admin/site'); + + $this->assertEquals(Site::config('general')->title, $key); + } + + /** + * Tests the getSkin method's 'set' action + */ + public function testGetSkinSet() + { + $this->initTestStep(); + + $skins = System::directories('views/skins'); + + $skin = array_pop($skins); + + $this->call('GET', "admin/skin/set/{$skin}"); + + $this->assertSessionHas('messages.success'); + + $this->assertRedirectedTo('admin/skin'); + + $this->assertEquals(Site::config('general')->skin, $skin); + } + + /** + * Tests the getSkin method's 'preview' action + */ + public function testGetSkinPreview() + { + $this->initTestStep(); + + $skins = System::directories('views/skins'); + + $this->call('GET', 'admin/skin/preview/'.array_pop($skins)); + + $this->assertResponseOk(); + } + + /** + * Tests the getSkin method's 'list' action + */ + public function testGetSkinList() + { + $this->initTestStep(); + + $this->call('GET', 'admin/skin/list'); + + $this->assertResponseOk(); + } + + /** + * Tests the getServices method of the controller + */ + public function testGetServices() + { + $this->initTestStep(); + + $this->call('GET', 'admin/services'); + + $this->assertResponseOk(); + } + + /** + * Tests the postServices method of the controller + */ + public function testPostServices() + { + $this->initTestStep(); + + $key = 'google'.time(); + + $this->call('POST', 'admin/services', array( + 'google_api_key' => $key, + 'google_analytics_id' => '', + )); + + $this->assertSessionHas('messages.success'); + + $this->assertRedirectedTo('admin/services'); + + $this->assertEquals(Site::config('services')->googleApiKey, $key); + } + +} diff --git a/app/tests/steps/AjaxTest.php b/app/tests/steps/AjaxTest.php new file mode 100755 index 000000000..45da09f0f --- /dev/null +++ b/app/tests/steps/AjaxTest.php @@ -0,0 +1,70 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.8 + * @filesource + */ + +/** + * AjaxTest + * + * Unit test cases for AjaxController + * + * @package StickyNotes + * @subpackage UnitTests + * @author Sayak Banerjee + */ +class AjaxTest extends StickyNotesTestCase { + + /** + * Tests the getVersion method of the controller + */ + public function testGetVersion() + { + $this->initTestStep(); + + $this->call('GET', 'ajax/version'); + + $this->assertResponseOk(); + } + + /** + * Tests the getSysload method of the controller + */ + public function testGetSysload() + { + $this->initTestStep(); + + $this->call('GET', 'ajax/sysload'); + + $this->assertResponseOk(); + } + + /** + * Tests the getShorten method of the controller + */ + public function testGetShorten() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('GET', "ajax/shorten/{$paste->urlkey}/{$paste->hash}"); + + $this->assertResponseOk(); + } + +} diff --git a/app/tests/steps/ApiTest.php b/app/tests/steps/ApiTest.php new file mode 100755 index 000000000..354f654f1 --- /dev/null +++ b/app/tests/steps/ApiTest.php @@ -0,0 +1,320 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.8 + * @filesource + */ + +/** + * ApiTest + * + * Unit test cases for ApiController + * + * @package StickyNotes + * @subpackage UnitTests + * @author Sayak Banerjee + */ +class ApiTest extends StickyNotesTestCase { + + /** + * Tests the getParameter method's 'language' param + * for the JSON API + */ + public function testGetParameterJsonLanguage() + { + $this->initTestStep(); + + $response = $this->call('GET', 'api/json/parameter/language'); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'application/json')); + } + + /** + * Tests the getParameter method's 'language' param + * for the XML API + */ + public function testGetParameterXmlLanguage() + { + $this->initTestStep(); + + $response = $this->call('GET', 'api/xml/parameter/language'); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'text/xml')); + } + + /** + * Tests the getParameter method's 'expire' param + * for the JSON API + */ + public function testGetParameterJsonExpire() + { + $this->initTestStep(); + + $response = $this->call('GET', 'api/json/parameter/expire'); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'application/json')); + } + + /** + * Tests the getParameter method's 'expire' param + * for the XML API + */ + public function testGetParameterXmlExpire() + { + $this->initTestStep(); + + $response = $this->call('GET', 'api/xml/parameter/expire'); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'text/xml')); + } + + /** + * Tests the getParameter method's 'version' param + * for the JSON API + */ + public function testGetParameterJsonVersion() + { + $this->initTestStep(); + + $response = $this->call('GET', 'api/json/parameter/version'); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'application/json')); + } + + /** + * Tests the getParameter method's 'version' param + * for the XML API + */ + public function testGetParameterXmlVersion() + { + $this->initTestStep(); + + $response = $this->call('GET', 'api/xml/parameter/version'); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'text/xml')); + } + + /** + * Tests the getParameter method's 'theme' param + * for the JSON API + */ + public function testGetParameterJsonTheme() + { + $this->initTestStep(); + + $response = $this->call('GET', 'api/json/parameter/theme'); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'application/json')); + } + + /** + * Tests the getParameter method's 'theme' param + * for the XML API + */ + public function testGetParameterXmlTheme() + { + $this->initTestStep(); + + $response = $this->call('GET', 'api/xml/parameter/theme'); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'text/xml')); + } + + /** + * Tests the getShow method of the controller + * for the JSON API with a public paste + */ + public function testGetShowJsonPublic() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $response = $this->call('GET', "api/json/show/{$paste->urlkey}"); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'application/json')); + } + + /** + * Tests the getShow method of the controller + * for the XML API with a public paste + */ + public function testGetShowXmlPublic() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $response = $this->call('GET', "api/xml/show/{$paste->urlkey}"); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'text/xml')); + } + + /** + * Tests the getShow method of the controller + * for the JSON API with a protected paste + */ + public function testGetShowJsonProtected() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'password' => 'UnitTest::Password', + 'language' => 'text', + )); + + $response = $this->call('GET', "api/json/show/{$paste->urlkey}/{$paste->hash}/UnitTest::Password"); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'application/json')); + } + + /** + * Tests the getShow method of the controller + * for the XML API with a protected paste + */ + public function testGetShowXmlProtected() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'password' => 'UnitTest::Password', + 'language' => 'text', + )); + + $response = $this->call('GET', "api/xml/show/{$paste->urlkey}/{$paste->hash}/UnitTest::Password"); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'text/xml')); + } + + /** + * Tests the getList method of the controller + * for the JSON API + */ + public function testGetListJson() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $response = $this->call('GET', 'api/json/list'); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'application/json')); + } + + /** + * Tests the getList method of the controller + * for the XML API + */ + public function testGetListXml() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $response = $this->call('GET', 'api/xml/list'); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'text/xml')); + } + + /** + * Tests the postCreate method of the controller + * for the JSON API + */ + public function testPostCreateJson() + { + $this->initTestStep(); + + $key = 'UnitTest::JSON'.time(); + + $response = $this->call('POST', 'api/json/create', array( + 'data' => $key, + 'language' => 'text', + )); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'application/json')); + + $this->assertEquals(Paste::where('data', $key)->count(), 1); + } + + /** + * Tests the postCreate method of the controller + * for the XML API + */ + public function testPostCreateXml() + { + $this->initTestStep(); + + $key = 'UnitTest::XML'.time(); + + $response = $this->call('POST', 'api/xml/create', array( + 'data' => $key, + 'language' => 'text', + )); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'text/xml')); + + $this->assertEquals(Paste::where('data', $key)->count(), 1); + } + +} diff --git a/app/tests/steps/CreateTest.php b/app/tests/steps/CreateTest.php new file mode 100755 index 000000000..7eb80df4b --- /dev/null +++ b/app/tests/steps/CreateTest.php @@ -0,0 +1,300 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.8 + * @filesource + */ + +/** + * CreateTest + * + * Unit test cases for CreateController + * + * @package StickyNotes + * @subpackage UnitTests + * @author Sayak Banerjee + */ +class CreateTest extends StickyNotesTestCase { + + /** + * Tests the getCreate method of the controller + */ + public function testGetCreate() + { + $this->initTestStep(); + + $this->call('GET', '/'); + + $this->assertResponseOk(); + } + + /** + * Tests the getCreate method of the controller without + * guest posts enabled + */ + public function testGetCreateNoGuest() + { + $this->initTestStep(FALSE); + + Site::config('general', array('guestPosts' => '0')); + + $this->call('GET', '/'); + + $this->assertRedirectedTo('user/login'); + + Site::config('general', array('guestPosts' => '1')); + } + + /** + * Tests the getCreate method of the controller with noExpire + * set to 'none' and logged in as admin + */ + public function testExpirationAdmin() + { + $this->initTestStep(); + + Site::config('general', array('noExpire' => 'none')); + + $response = $this->client->request('GET', '/'); + + $this->assertResponseOk(); + + $this->assertCount(1, $response->filter('option:contains("forever")')); + } + + /** + * Tests the getCreate method of the controller with noExpire + * set to 'user' and not logged in + */ + public function testExpirationGuest() + { + $this->initTestStep(FALSE); + + Site::config('general', array('noExpire' => 'user')); + + $response = $this->client->request('GET', '/'); + + $this->assertResponseOk(); + + $this->assertCount(0, $response->filter('option:contains("forever")')); + } + + /** + * Tests the postCreate method of the controller and + * creates a public paste + */ + public function testPostCreatePublic() + { + $this->initTestStep(); + + $key = 'UnitTest::Public'.str_random(64); + + $response = $this->call('POST', 'create', array( + 'title' => 'UnitTest::Title', + 'data' => $key, + 'language' => 'text', + )); + + $this->assertRedirectedTo($response->getTargetUrl()); + + $this->assertEquals(Paste::where('data', $key)->count(), 1); + } + + /** + * Tests the postCreate method of the controller and + * creates a password protected paste + */ + public function testPostCreateProtected() + { + $this->initTestStep(); + + $key = 'UnitTest::Protected'.str_random(64); + + $this->call('POST', 'create', array( + 'title' => 'UnitTest::Title', + 'data' => $key, + 'password' => 'UnitTest::Password', + 'language' => 'text', + )); + + $this->assertRedirectedTo('/'); + + $this->assertSessionHas('messages.success'); + + $this->assertEquals(Paste::where('data', $key)->count(), 1); + } + + /** + * Verifies 'enforce public' setting when creating pastes + */ + public function testPostCreatePublicSite() + { + $this->initTestStep(); + + Site::config('general', array('paste_visibility' => 'public')); + + $key = 'UnitTest::Protected'.str_random(64); + + $response = $this->call('POST', 'create', array( + 'title' => 'UnitTest::Title', + 'data' => $key, + 'password' => 'UnitTest::Password', + 'language' => 'text', + )); + + Site::config('general', array('paste_visibility' => 'default')); + + $this->assertRedirectedTo($response->getTargetUrl()); + + $this->assertEquals(Paste::where('data', $key)->first()->private, 0); + } + + /** + * Verifies 'enforce private' setting when creating pastes + */ + public function testPostCreatePrivateSite() + { + $this->initTestStep(); + + Site::config('general', array('paste_visibility' => 'private')); + + $key = 'UnitTest::Protected'.str_random(64); + + $response = $this->call('POST', 'create', array( + 'title' => 'UnitTest::Title', + 'data' => $key, + 'language' => 'text', + )); + + Site::config('general', array('paste_visibility' => 'default')); + + $this->assertRedirectedTo($response->getTargetUrl()); + + $this->assertEquals(Paste::where('data', $key)->first()->private, 1); + } + + /** + * Tests the postCreate method of the controller without + * guest posts enabled + */ + public function testPostCreateNoGuest() + { + $this->initTestStep(FALSE); + + Site::config('general', array('guest_posts' => '0')); + + $key = 'UnitTest::Protected'.str_random(64); + + $response = $this->call('POST', 'create', array( + 'title' => 'UnitTest::Title', + 'data' => $key, + 'language' => 'text', + )); + + $this->assertSessionHas('messages.error'); + + $this->assertEquals(Paste::where('data', $key)->count(), 0); + } + + /** + * Tests the getRevision method of the controller + */ + public function testGetRevision() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('GET', "rev/{$paste->urlkey}"); + + $this->assertResponseOk(); + } + + /** + * Tests the getRevision method of the controller without + * guest posts enabled + */ + public function testGetRevisionNoGuest() + { + $this->initTestStep(FALSE); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('GET', "rev/{$paste->urlkey}"); + + $this->assertRedirectedTo('user/login'); + } + + /** + * Tests the postRevision method of the controller + */ + public function testPostRevision() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->session(array('paste.revision' => $paste->id)); + + $response = $this->call('POST', 'revise', array( + 'id' => $paste->id, + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Revision', + 'language' => 'text', + )); + + $this->assertRedirectedTo($response->getTargetUrl()); + + $this->assertEquals(Revision::where('urlkey', $paste->urlkey)->count(), 1); + } + + /** + * Tests the postRevision method of the controller without + * guest posts enabled + */ + public function testPostRevisionNoGuest() + { + $this->initTestStep(FALSE); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->session(array('paste.revision' => $paste->id)); + + $response = $this->call('POST', 'revise', array( + 'id' => $paste->id, + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Revision', + 'language' => 'text', + )); + + $this->assertSessionHas('messages.error'); + + $this->assertEquals(Revision::where('urlkey', $paste->urlkey)->count(), 0); + } + +} diff --git a/app/tests/steps/FeedTest.php b/app/tests/steps/FeedTest.php new file mode 100755 index 000000000..d3d749bc8 --- /dev/null +++ b/app/tests/steps/FeedTest.php @@ -0,0 +1,48 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.8 + * @filesource + */ + +/** + * FeedTest + * + * Unit test cases for FeedController + * + * @package StickyNotes + * @subpackage UnitTests + * @author Sayak Banerjee + */ +class FeedTest extends StickyNotesTestCase { + + /** + * Tests the getFeed method of the controller + */ + public function testGetFeed() + { + $this->initTestStep(); + + Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $response = $this->call('GET', 'feed/rss'); + + $this->assertResponseOk(); + + $this->assertTrue(str_contains($response->headers->get('Content-Type'), 'application/rss+xml')); + } + +} diff --git a/app/tests/steps/ListTest.php b/app/tests/steps/ListTest.php new file mode 100755 index 000000000..d43eb735b --- /dev/null +++ b/app/tests/steps/ListTest.php @@ -0,0 +1,210 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.8 + * @filesource + */ + +/** + * ListTest + * + * Unit test cases for ListController + * + * @package StickyNotes + * @subpackage UnitTests + * @author Sayak Banerjee + */ +class ListTest extends StickyNotesTestCase { + + /** + * Tests the getAll method of the controller + */ + public function testGetAll() + { + $this->initTestStep(); + + Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('GET', 'all'); + + $this->assertResponseOk(); + } + + /** + * Tests the getTrending method's 'now' age + */ + public function testGetTrendingNow() + { + $this->initTestStep(); + + Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('GET', 'trending'); + + $this->assertResponseOk(); + } + + /** + * Tests the getTrending method's 'week' age + */ + public function testGetTrendingWeek() + { + $this->initTestStep(); + + Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('GET', 'trending/week'); + + $this->assertResponseOk(); + } + + /** + * Tests the getTrending method's 'month' age + */ + public function testGetTrendingMonth() + { + $this->initTestStep(); + + Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('GET', 'trending/month'); + + $this->assertResponseOk(); + } + + /** + * Tests the getTrending method's 'year' age + */ + public function testGetTrendingYear() + { + $this->initTestStep(); + + Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('GET', 'trending/year'); + + $this->assertResponseOk(); + } + + /** + * Tests the getTrending method's 'all' age + */ + public function testGetTrendingAll() + { + $this->initTestStep(); + + Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('GET', 'trending/all'); + + $this->assertResponseOk(); + } + + /** + * Tests the getUserPastes method of the controller + */ + public function testGetUserPastes() + { + $this->initTestStep(); + + Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('GET', 'user/u1/pastes'); + + $this->assertResponseOk(); + } + + /** + * Tests the getSearch method of the controller + */ + public function testGetSearch() + { + $this->initTestStep(); + + Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('GET', 'search', array( + 'q' => 'UnitTest', + )); + + $this->assertResponseOk(); + } + + /** + * Tests the postSearch method of the controller + */ + public function testPostSearch() + { + $this->initTestStep(); + + $this->call('POST', 'search', array( + 'search' => 'UnitTest', + )); + + $this->assertRedirectedTo('search?q=UnitTest'); + } + + /** + * Tests the getFlagged method of the controller + */ + public function testGetFlagged() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $paste->flagged = 1; + + $paste->save(); + + $this->call('GET', 'flagged'); + + $this->assertResponseOk(); + } + +} diff --git a/app/tests/steps/SetupTest.php b/app/tests/steps/SetupTest.php new file mode 100755 index 000000000..3249b3f62 --- /dev/null +++ b/app/tests/steps/SetupTest.php @@ -0,0 +1,89 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.8 + * @filesource + */ + +/** + * SetupTest + * + * Unit test cases for SetupController + * + * @package StickyNotes + * @subpackage UnitTests + * @author Sayak Banerjee + */ +class SetupTest extends StickyNotesTestCase { + + /** + * Tests the getInstall method of the controller + */ + public function testGetInstall() + { + $this->initTestStep(TRUE, FALSE); + + $response = $this->call('GET', 'setup/install'); + + $this->assertResponseOk(); + + $this->assertViewHas('success'); + } + + /** + * Tests the postInstall method of the controller + */ + public function testPostInstall() + { + $this->initTestStep(TRUE, FALSE); + + $response = $this->call('POST', 'setup/install', array( + '_test' => 1, + )); + + $this->assertRedirectedTo('setup/install'); + + $this->assertSessionHas('setup.stage', 2); + } + + /** + * Tests the getUpdate method of the controller + */ + public function testGetUpdate() + { + $this->initTestStep(TRUE, FALSE); + + $response = $this->call('GET', 'setup/update'); + + $this->assertResponseOk(); + + $this->assertViewHas('success'); + } + + /** + * Tests the postUpdate method of the controller + */ + public function testPostUpdate() + { + $this->initTestStep(TRUE, FALSE); + + $response = $this->call('POST', 'setup/update', array( + 'version' => 1, + '_update' => 1, + )); + + $this->assertRedirectedTo('setup/update'); + + $this->assertSessionHas('setup.stage', 2); + } + +} diff --git a/app/tests/steps/ShowTest.php b/app/tests/steps/ShowTest.php new file mode 100755 index 000000000..92e43a2df --- /dev/null +++ b/app/tests/steps/ShowTest.php @@ -0,0 +1,285 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.8 + * @filesource + */ + +/** + * ShowTest + * + * Unit test cases for ShowController + * + * @package StickyNotes + * @subpackage UnitTests + * @author Sayak Banerjee + */ +class ShowTest extends StickyNotesTestCase { + + /** + * Tests the getPaste method of the controller + */ + public function testGetPaste() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('GET', "{$paste->urlkey}/{$paste->hash}"); + + $this->assertResponseOk(); + } + + /** + * Tests the getPaste's 'delete-paste' action + */ + public function testGetPasteDeletePaste() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + 'attachment' => array(true), + )); + + File::put(storage_path()."/uploads/{$paste->urlkey}", 'attachment'); + + $this->call('GET', "{$paste->urlkey}/{$paste->hash}/delete"); + + $this->assertSessionHas('messages.success'); + + $this->assertRedirectedTo('/'); + + $this->assertEquals(Paste::where('id', $paste->id)->count(), 0); + + $this->assertFalse(File::exists(storage_path()."/uploads/{$paste->urlkey}")); + } + + /** + * Tests the getPaste's 'delete-comment' action + */ + public function testGetPasteDeleteComment() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + Comment::insert(array( + 'paste_id' => $paste->id, + 'data' => 'UnitTest::Comment', + 'timestamp' => time(), + )); + + $comment = Comment::where('paste_id', $paste->id)->first(); + + $this->call('GET', "{$paste->urlkey}/{$paste->hash}/delete/{$comment->id}"); + + $this->assertRedirectedTo('/'); + + $this->assertEquals(Comment::where('id', $comment->id)->count(), 0); + } + + /** + * Tests the getPaste's 'raw' action + */ + public function testGetPasteRaw() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('GET', "{$paste->urlkey}/{$paste->hash}/raw"); + + $this->assertResponseOk(); + } + + /** + * Tests the getPaste's 'toggle' action + */ + public function testGetPasteToggle() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + 'private' => 1, + )); + + $this->call('GET', "{$paste->urlkey}/{$paste->hash}/toggle"); + + $this->assertRedirectedTo('/'); + + $this->assertEquals(Paste::find($paste->id)->private, 0); + } + + /** + * Tests the getPaste's 'flag' action + */ + public function testGetPasteFlag() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('GET', "{$paste->urlkey}/{$paste->hash}/flag"); + + $this->assertRedirectedTo('/'); + + $this->assertSessionHas('messages.success'); + + $this->assertEquals(Paste::find($paste->id)->flagged, 1); + } + + /** + * Tests the getPaste's 'unflag' action + */ + public function testGetPasteUnflag() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + 'private' => 1, + )); + + $paste->update(array('flagged', 1)); + + $this->call('GET', "{$paste->urlkey}/{$paste->hash}/unflag"); + + $this->assertRedirectedTo('/'); + + $this->assertSessionHas('messages.success'); + + $this->assertEquals(Paste::find($paste->id)->flagged, 0); + } + + /** + * Tests the postPassword method of the controller + */ + public function testPostPassword() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'password' => 'UnitTest::Password', + 'language' => 'text', + )); + + $this->call('POST', "{$paste->urlkey}/{$paste->hash}", array( + 'password' => 'UnitTest::Password', + )); + + $this->assertRedirectedTo("{$paste->urlkey}/{$paste->hash}"); + + $this->assertSessionHas("paste.password{$paste->id}", TRUE); + } + + /** + * Tests the getDiff method of the controller + */ + public function testGetDiff() + { + $this->initTestStep(); + + $oldPaste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $newPaste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Revision', + 'language' => 'text', + )); + + Revision::insert(array( + 'paste_id' => $newPaste->id, + 'urlkey' => $oldPaste->urlkey, + 'author' => $oldPaste->author, + 'timestamp' => $oldPaste->timestamp, + )); + + $this->call('GET', "diff/{$oldPaste->urlkey}/{$newPaste->urlkey}"); + + $this->assertResponseOk(); + } + + /** + * Tests the getAttachment method of the controller + */ + public function testGetAttachment() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + 'attachment' => array(1), + )); + + File::put(storage_path()."/uploads/{$paste->urlkey}", 'UnitTest::Attachment'); + + $this->call('GET', "attachment/{$paste->urlkey}/{$paste->hash}"); + + $this->assertResponseOk(); + } + + /** + * Tests the postComment method of the controller + */ + public function testPostComment() + { + $this->initTestStep(); + + $paste = Paste::createNew('web', array( + 'title' => 'UnitTest::Title', + 'data' => 'UnitTest::Data', + 'language' => 'text', + )); + + $this->call('POST', 'comment', array( + 'id' => $paste->id, + 'comment' => 'UnitTest::Comment', + )); + + $this->assertFalse($this->app['session.store']->has('messages.error')); + + $this->assertEquals(Comment::where('paste_id', $paste->id)->count(), 1); + } + +} diff --git a/app/tests/steps/UserTest.php b/app/tests/steps/UserTest.php new file mode 100755 index 000000000..b82ccef4f --- /dev/null +++ b/app/tests/steps/UserTest.php @@ -0,0 +1,171 @@ + + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://sayakbanerjee.com/sticky-notes + * @since Version 1.8 + * @filesource + */ + +/** + * UserTest + * + * Unit test cases for UserController + * + * @package StickyNotes + * @subpackage UnitTests + * @author Sayak Banerjee + */ +class UserTest extends StickyNotesTestCase { + + /** + * Tests the getLogin method of the controller + */ + public function testGetLogin() + { + $this->initTestStep(); + + $this->call('GET', 'user/login'); + + $this->assertResponseOk(); + } + + /** + * Tests the postLogin method of the controller + */ + public function testPostLogin() + { + $this->initTestStep(); + + $response = $this->call('POST', 'user/login', array( + 'username' => 'unittest', + 'password' => 'unittest', + )); + + $this->assertTrue(Auth::check()); + } + + /** + * Tests the getRegister method of the controller + */ + public function testGetRegister() + { + $this->initTestStep(); + + $this->call('GET', 'user/register'); + + $this->assertResponseOk(); + } + + /** + * Tests the postRegister method of the controller + */ + public function testPostRegister() + { + $this->initTestStep(); + + // Disable the captcha + Site::config('auth', array( + 'db_show_captcha' => 0, + 'db_allow_reg' => 1, + )); + + // Generate a random user key + $key = 'unittest'.time(); + + $this->call('POST', 'user/register', array( + 'username' => $key, + 'password' => $key, + 'email' => "{$key}@test.com", + )); + + $this->assertRedirectedTo('user/login'); + + $this->assertEquals(User::where('username', $key)->count(), 1); + } + + /** + * Tests the getLogout method of the controller + */ + public function testGetLogout() + { + $this->initTestStep(); + + $this->call('GET', 'user/logout'); + + $this->assertFalse(Auth::check()); + } + + /** + * Tests the getForgot method of the controller + */ + public function testGetForgot() + { + $this->initTestStep(); + + $this->call('GET', 'user/forgot'); + + $this->assertResponseOk(); + } + + /** + * Tests the postForgot method of the controller + * + * @expectedException Swift_TransportException + */ + public function testPostForgot() + { + $this->initTestStep(); + + $username = User::orderBy('id', 'desc')->first()->username; + + $this->call('POST', 'user/forgot', array( + 'username' => $username, + )); + + $this->assertRedirectedTo('user/login'); + + $this->assertSessionHas('messages.success'); + } + + /** + * Tests the getProfile method of the controller + */ + public function testGetProfile() + { + $this->initTestStep(); + + $this->call('GET', 'user/profile'); + + $this->assertResponseOk(); + } + + /** + * Tests the postProfile method of the controller + */ + public function testPostProfile() + { + $this->initTestStep(); + + $key = 'Unit Test'.time(); + + $this->call('POST', 'user/profile', array( + 'username' => 'unittest', + 'password' => 'unittest', + 'email' => 'unit@test.com', + 'dispname' => $key, + )); + + $this->assertSessionHas('messages.success'); + + $this->assertEquals(User::where('dispname', $key)->count(), 1); + } + +} diff --git a/app/views/skins/bootstrap/admin/antispam.blade.php b/app/views/skins/bootstrap/admin/antispam.blade.php new file mode 100755 index 000000000..2d370b134 --- /dev/null +++ b/app/views/skins/bootstrap/admin/antispam.blade.php @@ -0,0 +1,339 @@ +@extends('skins.bootstrap.admin.layout') + +@section('module') +
      + {{ + Form::open(array( + 'autocomplete' => 'off', + 'role' => 'form', + 'class' => 'form-horizontal', + )) + }} + +
      +
      +
      + {{ Lang::get('admin.spam_filters') }} + + + +
      +
      +
      +
      +
      +

      {{ Lang::get('admin.honeypot_exp') }}

      + +
      + +
      +
      + +
      + {{ sprintf(Lang::get('admin.runs_on'), Antispam::scopes('php')) }} +
      + +
      + {{ Lang::get('admin.honeypot_more') }} + {{ link_to('http://www.projecthoneypot.org/httpbl_api.php') }} +
      +
      +
      + +
      + {{ + Form::label('php_key', Lang::get('admin.access_key'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('php_key', $site->antispam->phpKey, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.access_key_exp') }} + {{ link_to('http://www.projecthoneypot.org/httpbl_configure.php') }} +
      +
      +
      + +
      + {{ + Form::label('php_days', Lang::get('admin.age_threshold'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('php_days', $site->antispam->phpDays, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.age_threshold_exp') }} +
      +
      +
      + +
      + {{ + Form::label('php_score', Lang::get('admin.threat_score'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('php_score', $site->antispam->phpScore, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.threat_score_exp') }} +
      +
      +
      + +
      + {{ + Form::label('php_type', Lang::get('admin.visitor_filter'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('php_type', $site->antispam->phpType, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.visitor_filter_exp') }} +
      +
      +
      +
      + +
      +
      +
      +
      +

      {{ Lang::get('admin.word_censor_exp') }}

      + +
      + +
      +
      + +
      + {{ sprintf(Lang::get('admin.runs_on'), Antispam::scopes('censor')) }} +
      +
      +
      + +
      + {{ + Form::label('censor', Lang::get('admin.phrases'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::textarea('censor', $site->antispam->censor, array( + 'class' => 'form-control', + 'rows' => 5, + )) + }} + +
      + {{ Lang::get('admin.phrases_exp') }} +
      +
      +
      +
      + +
      +
      +
      +
      +

      {{ Lang::get('admin.stealth_exp') }}

      + +
      + +
      +
      + +
      + {{ sprintf(Lang::get('admin.runs_on'), Antispam::scopes('stealth')) }} +
      +
      +
      + +
      + {{ + Form::label('stealth_count', Lang::get('admin.max_links'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('stealth_count', $site->antispam->stealthCount, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.max_links_exp') }} +
      +
      +
      +
      + +
      +
      +
      +
      +

      {{ Lang::get('admin.noflood_exp') }}

      + +
      + +
      +
      + +
      + {{ sprintf(Lang::get('admin.runs_on'), Antispam::scopes('noflood')) }} +
      +
      +
      + +
      + {{ + Form::label('flood_threshold', Lang::get('admin.threshold'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      +
      + {{ + Form::text('flood_threshold', $site->antispam->floodThreshold, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.seconds') }} +
      +
      + +
      + {{ Lang::get('admin.threshold_exp') }} +
      +
      +
      +
      + +
      +
      +
      +
      +

      {{ Lang::get('admin.akismet_exp') }}

      + +
      + +
      +
      + +
      + {{ sprintf(Lang::get('admin.runs_on'), Antispam::scopes('akismet')) }} +
      +
      +
      + +
      + {{ + Form::label('akismet_key', Lang::get('admin.akismet_key'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('akismet_key', $site->antispam->akismetKey, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.akismet_key_exp') }} + {{ link_to('http://akismet.com') }} +
      +
      +
      +
      +
      + +
      + +
      +
      + {{ + Form::submit(Lang::get('admin.save_all'), array( + 'name' => '_save', + 'class' => 'btn btn-primary' + )) + }} +
      +
      +
      +
      +
      + + {{ Form::close() }} +
      +@stop diff --git a/app/views/skins/bootstrap/admin/auth.blade.php b/app/views/skins/bootstrap/admin/auth.blade.php new file mode 100755 index 000000000..b495455e8 --- /dev/null +++ b/app/views/skins/bootstrap/admin/auth.blade.php @@ -0,0 +1,403 @@ +@extends('skins.bootstrap.admin.layout') + +@section('module') +
      + {{ + Form::open(array( + 'autocomplete' => 'off', + 'role' => 'form', + 'class' => 'form-horizontal', + )) + }} + +
      +
      +
      + {{ Lang::get('admin.auth_settings') }} + +
      + {{ + Form::label('method', Lang::get('admin.auth_method'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('method', array( + 'db' => Lang::get('admin.db'), + 'ldap' => Lang::get('admin.ldap'), + 'oauth' => Lang::get('admin.oauth'), + ), $site->auth->method, array( + 'class' => 'form-control' + )) + }} +
      +
      + +
      + {{ + Form::label('method', Lang::get('admin.banner_text'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::textarea('banner_text', $site->auth->bannerText, array( + 'class' => 'form-control', + 'rows' => 2, + )) + }} + +
      + {{ Lang::get('admin.banner_text_exp') }} +
      +
      +
      + +
      + {{ + Form::label('info_url', Lang::get('admin.info_url'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('info_url', $site->auth->infoUrl, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.info_url_exp') }} +
      +
      +
      + +
      + {{ + Form::label('info_url_text', Lang::get('admin.info_url_text'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('info_url_text', $site->auth->infoUrlText, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.info_url_text_exp') }} +
      +
      +
      + + + +
      +
      +
      + {{ + Form::label('db_allow_reg', Lang::get('admin.user_reg'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('db_allow_reg', array( + '1' => Lang::get('admin.enabled'), + '0' => Lang::get('admin.disabled'), + ), $site->auth->dbAllowReg, array( + 'class' => 'form-control' + )) + }} + +
      + {{ Lang::get('admin.user_reg_exp') }} +
      +
      +
      + +
      + {{ + Form::label('db_show_captcha', Lang::get('admin.reg_captcha'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('db_show_captcha', array( + '1' => Lang::get('admin.enabled'), + '0' => Lang::get('admin.disabled'), + ), $site->auth->dbShowCaptcha, array( + 'class' => 'form-control' + )) + }} + +
      + {{ Lang::get('admin.reg_captcha_exp') }} +
      +
      +
      +
      + +
      +
      + {{ + Form::label('ldap_server', Lang::get('admin.ldap_server'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('ldap_server', $site->auth->ldapServer, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.ldap_server_exp') }} +
      +
      +
      + +
      + {{ + Form::label('ldap_port', Lang::get('admin.ldap_port'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('ldap_port', $site->auth->ldapPort, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.ldap_port_exp') }} +
      +
      +
      + +
      + {{ + Form::label('ldap_base_dn', Lang::get('admin.base_dn'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('ldap_base_dn', $site->auth->ldapBaseDn, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.base_dn_exp') }} +
      +
      +
      + +
      + {{ + Form::label('ldap_uid', Lang::get('admin.uid'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('ldap_uid', $site->auth->ldapUid, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.uid_exp') }} +
      +
      +
      + +
      + {{ + Form::label('ldap_filter', Lang::get('admin.user_filter'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('ldap_filter', $site->auth->ldapFilter, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.user_filter_exp') }} +
      +
      +
      + +
      + {{ + Form::label('ldap_admin', Lang::get('admin.admin_group'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('ldap_admin', $site->auth->ldapAdmin, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.admin_group_exp') }} +
      +
      +
      + +
      + {{ + Form::label('ldap_user_dn', Lang::get('admin.user_dn'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('ldap_user_dn', $site->auth->ldapUserDn, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.user_dn_exp') }} +
      +
      +
      + +
      + {{ + Form::label('ldap_password', Lang::get('global.password'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::input('password', 'ldap_password', $site->auth->ldapPassword, array( + 'class' => 'form-control', + )); + }} + +
      + {{ Lang::get('admin.ldap_password_exp') }} +
      +
      +
      +
      + +
      +
      + {{ + Form::label('oauth_google_id', Lang::get('admin.client_id'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('oauth_google_id', $site->auth->oauthGoogleId, array( + 'class' => 'form-control', + )) + }} +
      +
      + +
      + {{ + Form::label('oauth_google_secret', Lang::get('admin.client_secret'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('oauth_google_secret', $site->auth->oauthGoogleSecret, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.client_secret_exp') }} + {{ link_to('https://cloud.google.com/console', Lang::get('admin.google_cloud_console')) }}. +
      +
      +
      + +
      + {{ + Form::label('oauth_google_admins', Lang::get('admin.admin_emails'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::textarea('oauth_google_admins', $site->auth->oauthGoogleAdmins, array( + 'class' => 'form-control', + 'rows' => 5, + )) + }} + +
      + {{ Lang::get('admin.admin_emails_exp') }} +
      +
      +
      +
      +
      + +
      + +
      +
      + {{ + Form::submit(Lang::get('admin.save_all'), array( + 'name' => '_save', + 'class' => 'btn btn-primary' + )) + }} +
      +
      +
      +
      +
      + + {{ Form::close() }} +
      +@stop diff --git a/app/views/skins/bootstrap/admin/ban.blade.php b/app/views/skins/bootstrap/admin/ban.blade.php new file mode 100755 index 000000000..55b45a82f --- /dev/null +++ b/app/views/skins/bootstrap/admin/ban.blade.php @@ -0,0 +1,73 @@ +@extends('skins.bootstrap.admin.layout') + +@section('module') +
      +
      + {{ Lang::get('admin.ban_an_ip') }} + + {{ + Form::open(array( + 'autocomplete' => 'off', + 'role' => 'form' + )) + }} + +
      +
      + {{ + Form::text('ip', NULL, array( + 'class' => 'form-control', + 'placeholder' => Lang::get('admin.ip_address') + )) + }} + + {{ + Form::submit(Lang::get('admin.ban'), array( + 'class' => 'btn btn-primary' + )) + }} +
      +
      +
      + + {{ Form::close() }} + +
      +
      + @if ($bans->count() > 0) + + + + + + + + + + + + + + + @foreach ($bans as $ban) + + + + + + @endforeach + +
      {{ Lang::get('admin.ip_address') }}
      {{ $ban->ip }} + {{ link_to('admin/ban/remove/'.urlencode($ban->ip), Lang::get('admin.unban')) }} +
      + @else +
      +

      +

      {{ Lang::get('admin.no_banned_ip') }}

      +
      + @endif +
      +
      +
      +
      +@stop diff --git a/app/views/skins/bootstrap/admin/dashboard.blade.php b/app/views/skins/bootstrap/admin/dashboard.blade.php new file mode 100755 index 000000000..905afe6ac --- /dev/null +++ b/app/views/skins/bootstrap/admin/dashboard.blade.php @@ -0,0 +1,126 @@ +@extends('skins.bootstrap.admin.layout') + +@section('module') +
      +
      +
      +

      + + {{ Lang::get('admin.versions') }} +

      + +
      +
      +
      + {{ Lang::get('admin.php_version') }}: + {{ $php_version }} +
      +
      + +
      +
      + {{ Lang::get('admin.stickynotes_version') }}: + {{ $sn_version }} + + + + +
      +
      +
      + +

      + + {{ Lang::get('admin.data') }} +

      + +
      +
      +
      + {{ Lang::get('admin.users') }}: + {{ $users }} +
      +
      + +
      +
      + {{ Lang::get('admin.pastes') }}: + {{ $pastes }} +
      +
      +
      + +

      + + {{ Lang::get('admin.system') }} +

      + +
      +
      +
      + {{ Lang::get('admin.db_driver') }}: + {{ $db_driver }} +
      +
      + +
      +
      + {{ Lang::get('admin.system_load') }}: + + + + +
      +
      +
      + +

      + + {{ Lang::get('admin.paste_stats') }} +

      + +
      +
      + @if (count($stats) > 1) +
      + @include('skins.bootstrap.common.loader') +
      + + + @else +
      + {{ Lang::get('admin.stat_no_data') }} +
      + @endif +
      +
      +
      +
      +
      +@stop diff --git a/app/views/skins/bootstrap/admin/layout.blade.php b/app/views/skins/bootstrap/admin/layout.blade.php new file mode 100755 index 000000000..ed69f98dd --- /dev/null +++ b/app/views/skins/bootstrap/admin/layout.blade.php @@ -0,0 +1,16 @@ +@extends("skins.bootstrap.common.{$container}") + +@section('body') +
      +
      + +
      + +
      + @include('skins.bootstrap.common.alerts') + @yield('module') +
      +
      +@stop diff --git a/app/views/skins/bootstrap/admin/mail.blade.php b/app/views/skins/bootstrap/admin/mail.blade.php new file mode 100755 index 000000000..efcd4f65d --- /dev/null +++ b/app/views/skins/bootstrap/admin/mail.blade.php @@ -0,0 +1,195 @@ +@extends('skins.bootstrap.admin.layout') + +@section('module') +
      + {{ + Form::open(array( + 'autocomplete' => 'off', + 'role' => 'form', + 'class' => 'form-horizontal', + )) + }} + +
      +
      +
      + {{ Lang::get('admin.mail_settings') }} + +
      + {{ + Form::label('driver', Lang::get('admin.driver'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('driver', array( + 'smtp' => Lang::get('admin.smtp'), + 'mail' => Lang::get('admin.mail'), + 'sendmail' => Lang::get('admin.sendmail'), + ), $site->mail->driver, array( + 'class' => 'form-control' + )) + }} +
      +
      + +
      + {{ + Form::label('host', Lang::get('admin.smtp_host'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('host', $site->mail->host, array( + 'class' => 'form-control', + )) + }} +
      +
      + +
      + {{ + Form::label('port', Lang::get('admin.smtp_port'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('port', $site->mail->port, array( + 'class' => 'form-control', + )) + }} +
      +
      + +
      + {{ + Form::label('address', Lang::get('admin.from_address'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('address', $site->mail->address, array( + 'class' => 'form-control', + )) + }} +
      +
      + +
      + {{ + Form::label('name', Lang::get('admin.from_name'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('name', $site->mail->name, array( + 'class' => 'form-control', + )) + }} +
      +
      + +
      + {{ + Form::label('encryption', Lang::get('admin.encryption'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('encryption', array( + '' => Lang::get('admin.none'), + 'ssl' => Lang::get('admin.ssl'), + 'tls' => Lang::get('admin.tls'), + ), $site->mail->encryption, array( + 'class' => 'form-control' + )) + }} +
      +
      + +
      + {{ + Form::label('username', Lang::get('admin.smtp_username'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('username', $site->mail->username, array( + 'class' => 'form-control', + )) + }} +
      +
      + +
      + {{ + Form::label('password', Lang::get('admin.smtp_password'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::input('password', 'password', $site->mail->password, array( + 'class' => 'form-control', + )); + }} +
      +
      + +
      + {{ + Form::label('sendmail', Lang::get('admin.sendmail_path'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('sendmail', $site->mail->sendmail, array( + 'class' => 'form-control', + )) + }} +
      +
      + +
      +
      +
      + {{ + Form::submit(Lang::get('global.submit'), array( + 'name' => '_save', + 'class' => 'btn btn-primary' + )) + }} + + {{ + Form::submit(Lang::get('admin.test_mail_settings'), array( + 'name' => '_test', + 'class' => 'btn btn-primary' + )) + }} +
      +
      +
      +
      +
      +
      + + {{ Form::close() }} +
      +@stop diff --git a/app/views/skins/bootstrap/admin/paste.blade.php b/app/views/skins/bootstrap/admin/paste.blade.php new file mode 100755 index 000000000..69374e574 --- /dev/null +++ b/app/views/skins/bootstrap/admin/paste.blade.php @@ -0,0 +1,149 @@ +@extends('skins.bootstrap.admin.layout') + +@section('module') +
      +
      + {{ Lang::get('admin.manage_pastes') }} + + {{ + Form::open(array( + 'autocomplete' => 'off', + 'role' => 'form' + )) + }} + +
      +
      + {{ + Form::text('search', NULL, array( + 'class' => 'form-control', + 'maxlength' => 30, + 'placeholder' => Lang::get('global.paste_id') + )) + }} + + {{ + Form::submit(Lang::get('global.search'), array( + 'class' => 'btn btn-primary' + )) + }} +
      +
      +
      + +
      +
      + @if ( ! empty($paste)) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      {{ Lang::get('admin.field') }}{{ Lang::get('admin.value') }}
      {{ Lang::get('global.paste_id') }}#{{ $paste->urlkey }}
      {{ Lang::get('global.paste_title') }}{{{ $paste->title ?: '-' }}}
      {{ Lang::get('global.paste_data') }}{{{ Paste::getAbstract($paste->data) }}}
      {{ Lang::get('global.author') }}{{{ $paste->author ?: Lang::get('global.anonymous') }}}
      {{ Lang::get('global.paste_lang') }}{{ $paste->language }}
      {{ Lang::get('admin.posted_at') }}{{ date('d M Y, H:i:s e', $paste->timestamp) }}
      {{ Lang::get('admin.expires_at') }}{{ $paste->expire > 0 ? date('d M Y, H:i:s e', $paste->expire) : '-' }}
      {{ Lang::get('admin.is_private') }}{{ $paste->private ? Lang::get('global.yes') : Lang::get('global.no') }}
      {{ Lang::get('admin.has_password') }}{{ $paste->password ? Lang::get('global.yes') : Lang::get('global.no') }}
      {{ Lang::get('admin.poster_ip') }}{{ $paste->ip }}
      + +
      + @if ($paste->password) + {{ + link_to("admin/paste/{$paste->urlkey}/rempass", Lang::get('admin.remove_password'), array( + 'class' => 'btn btn-primary' + )) + }} + @endif + + {{ + link_to("admin/paste/{$paste->urlkey}/toggle", + $paste->private ? Lang::get('global.make_public') : Lang::get('global.make_private'), + array( + 'class' => 'btn btn-primary' + ) + ) + }} + + @if ($paste->attachment) + {{ + link_to("admin/paste/{$paste->urlkey}/remattach", Lang::get('admin.remove_attachment'), array( + 'class' => 'btn btn-primary', + 'onclick' => "return confirm('".Lang::get('global.action_confirm')."')", + )) + }} + @endif + + {{ + link_to("admin/paste/{$paste->urlkey}/delete", Lang::get('global.delete'), array( + 'class' => 'btn btn-primary', + 'onclick' => "return confirm('".Lang::get('global.action_confirm')."')", + )) + }} +
      + @else +
      +

      +

      {{ Lang::get('admin.paste_exp') }}

      +
      + @endif +
      +
      + + {{ Form::close() }} +
      +
      +@stop diff --git a/app/views/skins/bootstrap/admin/services.blade.php b/app/views/skins/bootstrap/admin/services.blade.php new file mode 100755 index 000000000..dd45e99e8 --- /dev/null +++ b/app/views/skins/bootstrap/admin/services.blade.php @@ -0,0 +1,87 @@ +@extends('skins.bootstrap.admin.layout') + +@section('module') +
      + {{ + Form::open(array( + 'autocomplete' => 'off', + 'role' => 'form', + 'class' => 'form-horizontal', + )) + }} + +
      +
      +
      + {{ Lang::get('admin.services') }} + + + +
      +
      +
      + {{ + Form::label('google_api_key', Lang::get('admin.google_api_key'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('google_api_key', $site->services->googleApiKey, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.google_api_key_exp') }} + {{ link_to('https://cloud.google.com/console', Lang::get('admin.google_cloud_console')) }}. +
      +
      +
      + +
      + {{ + Form::label('google_analytics_id', Lang::get('admin.google_analytics'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('google_analytics_id', $site->services->googleAnalyticsId, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.google_analytics_exp') }} +
      +
      +
      +
      +
      + +
      + +
      +
      + {{ + Form::submit(Lang::get('admin.save_all'), array( + 'name' => '_save', + 'class' => 'btn btn-primary' + )) + }} +
      +
      +
      +
      +
      + + {{ Form::close() }} +
      +@stop diff --git a/app/views/skins/bootstrap/admin/site.blade.php b/app/views/skins/bootstrap/admin/site.blade.php new file mode 100755 index 000000000..a431d6512 --- /dev/null +++ b/app/views/skins/bootstrap/admin/site.blade.php @@ -0,0 +1,523 @@ +@extends('skins.bootstrap.admin.layout') + +@section('module') +
      + {{ + Form::open(array( + 'autocomplete' => 'off', + 'role' => 'form', + 'class' => 'form-horizontal', + )) + }} + +
      +
      +
      + {{ Lang::get('admin.site_settings') }} + + + +
      +
      +
      + {{ + Form::label('fqdn', Lang::get('admin.fqdn'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('fqdn', $site->general->fqdn, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.fqdn_exp') }} +
      +
      +
      + +
      + {{ + Form::label('title', Lang::get('admin.site_title'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('title', $site->general->title, array( + 'class' => 'form-control', + 'maxlength' => 20 + )) + }} +
      +
      + +
      + {{ + Form::label('lang', Lang::get('admin.language'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('lang', $langs, $site->general->lang, array( + 'class' => 'form-control' + )) + }} +
      +
      + +
      + {{ + Form::label('copyright', Lang::get('admin.copyright'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::textarea('copyright', $site->general->copyright, array( + 'class' => 'form-control', + 'rows' => 4, + )) + }} + +
      + {{ Lang::get('admin.copyright_exp') }} +
      +
      +
      + +
      + {{ + Form::label('ajax_nav', Lang::get('admin.ajax_nav'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('ajax_nav', array( + '1' => Lang::get('admin.enabled'), + '0' => Lang::get('admin.disabled'), + ), $site->general->ajaxNav, array( + 'class' => 'form-control' + )) + }} + +
      + {{ Lang::get('admin.ajax_nav_exp') }} +
      +
      +
      + +
      + {{ + Form::label('per_page', Lang::get('admin.list_length'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('per_page', $site->general->perPage, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.list_length_exp') }} +
      +
      +
      + +
      + {{ + Form::label('proxy', Lang::get('admin.ip_tracking'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('proxy', array( + '0' => Lang::get('admin.ignore_proxy'), + '1' => Lang::get('admin.trust_proxy'), + ), $site->general->proxy, array( + 'class' => 'form-control' + )) + }} + +
      + {{ Lang::get('admin.ip_tracking_exp') }} +
      +
      +
      + +
      + {{ + Form::label('csrf', Lang::get('admin.csrf_token'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('csrf', array( + '1' => Lang::get('admin.enabled'), + '0' => Lang::get('admin.disabled'), + ), $site->general->csrf, array( + 'class' => 'form-control' + )) + }} + +
      + {{ Lang::get('admin.csrf_token_exp') }} +
      +
      +
      +
      + +
      +
      + {{ + Form::label('guest_posts', Lang::get('admin.guest_posts'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('guest_posts', array( + '1' => Lang::get('admin.enabled'), + '0' => Lang::get('admin.disabled'), + ), $site->general->guestPosts, array( + 'class' => 'form-control' + )) + }} + +
      + {{ Lang::get('admin.guest_posts_exp') }} +
      +
      +
      + +
      + {{ + Form::label('paste_visibility', Lang::get('admin.visibility'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('paste_visibility', array( + 'default' => Lang::get('admin.allow_all'), + 'public' => Lang::get('admin.enforce_public'), + 'private' => Lang::get('admin.enforce_private'), + ), $site->general->pasteVisibility, array( + 'class' => 'form-control' + )) + }} + +
      + {{ Lang::get('admin.visibility_exp') }} +
      +
      +
      + +
      + {{ + Form::label('flag_paste', Lang::get('admin.flagging'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('flag_paste', array( + 'all' => Lang::get('admin.flag_all'), + 'user' => Lang::get('admin.flag_user'), + 'off' => Lang::get('admin.flag_off'), + ), $site->general->flagPaste, array( + 'class' => 'form-control' + )) + }} + +
      + {{ Lang::get('admin.flagging_exp') }} +
      +
      +
      + +
      + {{ + Form::label('allow_paste_del', Lang::get('admin.delete_pastes'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('allow_paste_del', array( + '1' => Lang::get('admin.enabled'), + '0' => Lang::get('admin.disabled'), + ), $site->general->allowPasteDel, array( + 'class' => 'form-control' + )) + }} + +
      + {{ Lang::get('admin.delete_pastes_exp') }} +
      +
      +
      + +
      + {{ + Form::label('allow_attachment', Lang::get('admin.attachment'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('allow_attachment', array( + '1' => Lang::get('admin.enabled'), + '0' => Lang::get('admin.disabled'), + ), $site->general->allowAttachment, array( + 'class' => 'form-control' + )) + }} + +
      + {{ Lang::get('admin.attachment_exp') }} +
      +
      +
      + +
      + {{ + Form::label('paste_age', Lang::get('admin.paste_age'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('paste_age', Paste::getExpiration('admin'), $site->general->pasteAge, array( + 'class' => 'form-control' + )) + }} + +
      + {{ Lang::get('admin.paste_age_exp') }} +
      +
      +
      + +
      + {{ + Form::label('max_paste_size', Lang::get('admin.size_limit'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      +
      + {{ + Form::text('max_paste_size', $site->general->maxPasteSize, array( + 'class' => 'form-control', + )) + }} + +
      + {{ Lang::get('admin.bytes') }} +
      +
      + +
      + {{ Lang::get('admin.size_limit_exp') }} +
      +
      +
      + +
      + {{ + Form::label('no_expire', Lang::get('admin.expiration'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('no_expire', array( + 'none' => Lang::get('admin.noexpire_none'), + 'user' => Lang::get('admin.noexpire_user'), + 'all' => Lang::get('admin.noexpire_all'), + ), $site->general->noExpire, array( + 'class' => 'form-control' + )) + }} + +
      + {{ Lang::get('admin.expiration_exp') }} +
      +
      +
      + +
      + {{ + Form::label('paste_search', Lang::get('admin.paste_search'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('paste_search', array( + '1' => Lang::get('admin.enabled'), + '0' => Lang::get('admin.disabled'), + ), $site->general->pasteSearch, array( + 'class' => 'form-control' + )) + }} + +
      + {{ Lang::get('admin.paste_search_exp') }} +
      +
      +
      + +
      + {{ + Form::label('comments', Lang::get('global.comments'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('comments', array( + '1' => Lang::get('admin.enabled'), + '0' => Lang::get('admin.disabled'), + ), $site->general->comments, array( + 'class' => 'form-control' + )) + }} + +
      + {{ Lang::get('admin.comments_exp') }} +
      +
      +
      + +
      + {{ + Form::label('share', Lang::get('admin.share'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::select('share', array( + '1' => Lang::get('admin.enabled'), + '0' => Lang::get('admin.disabled'), + ), $site->general->share, array( + 'class' => 'form-control' + )) + }} + +
      + {{ Lang::get('admin.share_exp') }} +
      +
      +
      +
      + +
      +
      +
      +
      + {{ Lang::get('admin.banners_exp') }} +
      + +
      + {{{ sprintf(Lang::get('admin.allowed_tags'), $site->general->allowedTags) }}} +
      +
      +
      + +
      + {{ + Form::label('banner_top', Lang::get('admin.banner_top'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::textarea('banner_top', $site->general->bannerTop, array( + 'class' => 'form-control', + 'rows' => 5, + )) + }} +
      +
      + +
      + {{ + Form::label('banner_bottom', Lang::get('admin.banner_bottom'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::textarea('banner_bottom', $site->general->bannerBottom, array( + 'class' => 'form-control', + 'rows' => 5, + )) + }} +
      +
      +
      +
      + +
      + +
      +
      + {{ + Form::submit(Lang::get('admin.save_all'), array( + 'name' => '_save', + 'class' => 'btn btn-primary' + )) + }} +
      +
      +
      +
      +
      + + {{ Form::close() }} +
      +@stop diff --git a/app/views/skins/bootstrap/admin/skin.blade.php b/app/views/skins/bootstrap/admin/skin.blade.php new file mode 100755 index 000000000..4ba189867 --- /dev/null +++ b/app/views/skins/bootstrap/admin/skin.blade.php @@ -0,0 +1,56 @@ +@extends('skins.bootstrap.admin.layout') + +@section('module') +
      +
      + {{ Lang::get('admin.skin_chooser') }} + + @foreach ($skins as $skin) +
      +
      + {{ + HTML::image(url('admin/skin/preview/'.urlencode($skin->key)), NULL, array( + 'class' => 'img-thumbnail pull-left hidden-xs', + 'width' => 310, + 'height' => 190, + )) + }} + +

      + {{{ $skin->name }}} + + + {{ Lang::get('admin.version') }}: + {{{ $skin->version }}} + +

      + + @if ( ! empty($skin->author)) +

      + {{ Lang::get('global.author') }}: + {{ $skin->author }} +

      + @endif + +

      {{{ $skin->description }}}

      + + @if ($site->general->skin == $skin->key) + + @else + {{ + link_to(url('admin/skin/set/'.urlencode($skin->key)), Lang::get('admin.use_theme'), array( + 'class' => 'btn btn-primary', + )) + }} + @endif +
      +
      + +
      + @endforeach +
      +
      +@stop diff --git a/app/views/skins/bootstrap/admin/user.blade.php b/app/views/skins/bootstrap/admin/user.blade.php new file mode 100755 index 000000000..41f4db056 --- /dev/null +++ b/app/views/skins/bootstrap/admin/user.blade.php @@ -0,0 +1,248 @@ +@extends('skins.bootstrap.admin.layout') + +@section('module') +
      +
      + {{ Lang::get('admin.manage_users') }} + + {{ + Form::open(array( + 'autocomplete' => 'off', + 'role' => 'form' + )) + }} + +
      +
      + {{ + Form::text('search', NULL, array( + 'class' => 'form-control', + 'maxlength' => 50, + 'placeholder' => Lang::get('global.username') + )) + }} + + {{ + Form::submit(Lang::get('global.search'), array( + 'class' => 'btn btn-primary' + )) + }} +
      + +
      + {{ + link_to('admin/user/create', Lang::get('admin.user_create'), array( + 'class' => 'btn btn-success' + )) + }} +
      +
      +
      + + {{ Form::close() }} + +
      +
      + @if ( ! empty($user)) + {{ + Form::open(array( + 'autocomplete' => 'off', + 'role' => 'form', + 'class' => 'form-horizontal', + )) + }} + +
      + {{ Lang::get('admin.user_editor') }} + +
      + {{ + Form::label('username', Lang::get('global.username'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('username', $user->username, array( + 'class' => 'form-control', + 'maxlength' => 50, + )) + }} +
      +
      + +
      + {{ + Form::label('email', Lang::get('global.email'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('email', $user->email, array( + 'class' => 'form-control', + 'maxlength' => 100, + )) + }} +
      +
      + +
      + {{ + Form::label('dispname', Lang::get('global.full_name'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::text('dispname', $user->dispname, array( + 'class' => 'form-control', + 'maxlength' => 100, + )) + }} +
      +
      + +
      + {{ + Form::label('password', Lang::get('global.password'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      + {{ + Form::password('password', array( + 'class' => 'form-control', + )) + }} +
      +
      + + @if ( ! $founder) +
      + {{ + Form::label('active', Lang::get('admin.status'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      +
      + +
      +
      +
      + +
      + {{ + Form::label('admin', Lang::get('admin.role'), array( + 'class' => 'control-label col-sm-3 col-lg-2' + )) + }} + +
      +
      + +
      +
      +
      + @endif + +
      +
      + {{ Form::hidden('id', $user->id) }} + + {{ + Form::submit(Lang::get('global.submit'), array( + 'name' => '_save', + 'class' => 'btn btn-primary' + )) + }} + + {{ + link_to('admin/user/delete/'.urlencode($user->username), Lang::get('global.delete'), array( + 'onclick' => 'return confirm("'.Lang::get('global.action_confirm').'")', + 'class' => 'btn btn-danger' + )); + }} +
      +
      +
      + + {{ Form::close() }} + @else + @if ($site->auth->method != 'db') +
      + {{ sprintf(Lang::get('admin.user_auth_method'), Lang::get("admin.{$site->auth->method}")) }} +
      + @endif + + + + + + + + + + + + + + + + + + + + @foreach ($users as $user) + + + + + + + + + @endforeach + +
      {{ Lang::get('global.username') }}{{ Lang::get('global.email') }}
      + {{ + HTML::image('//www.gravatar.com/avatar/'.md5(strtolower($user->email)).'?s=28', NULL, array( + 'class' => 'img-circle', + 'onerror' => 'this.style.display="none"', + )) + }} + {{ $user->username }}{{{ $user->email }}} + {{ link_to('admin/user/edit/'.urlencode($user->username), Lang::get('global.edit')) }} +
      + + {{ $pages }} + @endif +
      +
      + + {{ Form::close() }} +
      +
      +@stop diff --git a/app/views/skins/bootstrap/admin/version.blade.php b/app/views/skins/bootstrap/admin/version.blade.php new file mode 100755 index 000000000..9917f428b --- /dev/null +++ b/app/views/skins/bootstrap/admin/version.blade.php @@ -0,0 +1,6 @@ +@if ($updated) + +@else + +@endif + diff --git a/app/views/skins/bootstrap/bootstrap.info b/app/views/skins/bootstrap/bootstrap.info new file mode 100755 index 000000000..a014df9d7 --- /dev/null +++ b/app/views/skins/bootstrap/bootstrap.info @@ -0,0 +1,8 @@ +{ + "name" : "Bootstrap", + "themeVersion" : "2.0", + "minCoreVersion" : "1.0", + "description" : "Bootstrap is a mobile-first responsive theme for Sticky Notes v1.0. This is the base theme distributed with Sticky Notes and supports all features offered by the application.", + "author" : "Sayak Banerjee", + "authorWebsite" : "http://sayakbanerjee.com" +} diff --git a/app/views/skins/bootstrap/bootstrap.png b/app/views/skins/bootstrap/bootstrap.png new file mode 100755 index 000000000..18e6e64fe Binary files /dev/null and b/app/views/skins/bootstrap/bootstrap.png differ diff --git a/app/views/skins/bootstrap/common/alerts.blade.php b/app/views/skins/bootstrap/common/alerts.blade.php new file mode 100755 index 000000000..571338571 --- /dev/null +++ b/app/views/skins/bootstrap/common/alerts.blade.php @@ -0,0 +1,45 @@ +
      +
      + @if ( ! empty($global)) +
      + + + @if (is_array($global)) + @foreach ($global as $msg) + {{ $msg }} + @endforeach + @else + {{ $global }} + @endif +
      + @endif + + @if ( ! empty($success)) +
      + + + @if (is_array($success)) + @foreach ($success as $msg) + {{ $msg }} + @endforeach + @else + {{ $success }} + @endif +
      + @endif + + @if ( ! empty($error)) +
      + + + @if (is_array($error)) + @foreach ($error as $msg) + {{ $msg }} + @endforeach + @else + {{ $error }} + @endif +
      + @endif +
      +
      diff --git a/app/views/skins/bootstrap/common/error.blade.php b/app/views/skins/bootstrap/common/error.blade.php new file mode 100755 index 000000000..d6a5a16ce --- /dev/null +++ b/app/views/skins/bootstrap/common/error.blade.php @@ -0,0 +1,14 @@ +@extends("skins.bootstrap.common.{$container}") + +@section('body') +
      +
      +
      +
      +

      +

      {{ Lang::get('errors.'.$errCode) }}

      +
      +
      +
      +
      +@stop diff --git a/app/views/skins/bootstrap/common/icon.blade.php b/app/views/skins/bootstrap/common/icon.blade.php new file mode 100755 index 000000000..336989f31 --- /dev/null +++ b/app/views/skins/bootstrap/common/icon.blade.php @@ -0,0 +1 @@ + diff --git a/app/views/skins/bootstrap/common/loader.blade.php b/app/views/skins/bootstrap/common/loader.blade.php new file mode 100755 index 000000000..a4d74d5df --- /dev/null +++ b/app/views/skins/bootstrap/common/loader.blade.php @@ -0,0 +1,4 @@ + + + {{ Lang::get('global.loading') }} + diff --git a/app/views/skins/bootstrap/common/page.blade.php b/app/views/skins/bootstrap/common/page.blade.php new file mode 100755 index 000000000..746d16142 --- /dev/null +++ b/app/views/skins/bootstrap/common/page.blade.php @@ -0,0 +1,92 @@ + + + + + + + + {{ $site->general->title }} + + + + + + + + + + + + + + + + + + +
      + @include('skins.bootstrap.common.loader') +
      + + + +
      + @if ( ! empty($site->general->bannerTop)) +
      +
      + {{ $site->general->bannerTop }} +
      +
      + @endif + + @yield('body') + + @if ( ! empty($site->general->bannerBottom)) +
      +
      + {{ $site->general->bannerBottom }} +
      +
      + @endif +
      + +
      +

      {{ $site->general->copyright }}

      + + + +

      Sticky Notes © 2014 Sayak Banerjee.

      + + @if ($active AND $role->admin) + {{ sprintf(Lang::get('global.statistics'), microtime(true) - LARAVEL_START, count(DB::getQueryLog()) - 1) }} + @endif + + @if (Antispam::flags()->php) + + + @endif +
      + + + diff --git a/app/views/skins/bootstrap/common/pagination.blade.php b/app/views/skins/bootstrap/common/pagination.blade.php new file mode 100755 index 000000000..c34c1ab43 --- /dev/null +++ b/app/views/skins/bootstrap/common/pagination.blade.php @@ -0,0 +1,11 @@ +{? $presenter = new Illuminate\Pagination\BootstrapPresenter($paginator) ?} + +@if ($paginator->getLastPage() > 1) +
      +
      +
        + {{ $presenter->render() }} +
      +
      +
      +@endif diff --git a/app/views/skins/bootstrap/common/wrapper.blade.php b/app/views/skins/bootstrap/common/wrapper.blade.php new file mode 100755 index 000000000..3e0a5e0a2 --- /dev/null +++ b/app/views/skins/bootstrap/common/wrapper.blade.php @@ -0,0 +1,60 @@ +
      + @include('skins.bootstrap.common.loader') +
      + + + +
      + @if ( ! empty($site->general->bannerTop)) +
      +
      + {{ $site->general->bannerTop }} +
      +
      + @endif + + @yield('body') + + @if ( ! empty($site->general->bannerBottom)) +
      +
      + {{ $site->general->bannerBottom }} +
      +
      + @endif +
      + +
      +

      {{ $site->general->copyright }}

      + + + +

      Sticky Notes © 2014 Sayak Banerjee.

      + + @if ($active AND $role->admin) + {{ sprintf(Lang::get('global.statistics'), microtime(true) - LARAVEL_START, count(DB::getQueryLog())) }} + @endif + + @if (Antispam::flags()->php) + + + @endif +
      diff --git a/app/views/skins/bootstrap/setup/error.blade.php b/app/views/skins/bootstrap/setup/error.blade.php new file mode 100755 index 000000000..da3c0a4a5 --- /dev/null +++ b/app/views/skins/bootstrap/setup/error.blade.php @@ -0,0 +1,15 @@ +@extends('skins.bootstrap.setup.page') + +@section('body') +
      +
      + {{ Lang::get('setup.error_title') }} + +

      {{ Lang::get('setup.error_exp') }}

      + +
      + {{ Session::get('setup.error') }} +
      +
      +
      +@stop diff --git a/app/views/skins/bootstrap/setup/install/stage1.blade.php b/app/views/skins/bootstrap/setup/install/stage1.blade.php new file mode 100755 index 000000000..b5264c6b2 --- /dev/null +++ b/app/views/skins/bootstrap/setup/install/stage1.blade.php @@ -0,0 +1,32 @@ +@extends('skins.bootstrap.setup.page') + +@section('body') +
      + @if (empty($error)) +
      + {{ Lang::get('setup.welcome') }} +
      + + @if ($unstable) +
      + {{ sprintf(Lang::get('setup.develop_warn'), $site->services->downloadUrl) }} +
      + @endif + @endif + +
      + {{ Lang::get('setup.i_stage1_title') }} + +

      {{ Lang::get('setup.i_stage1_exp') }}

      + +

      {{ Lang::get('setup.click_check') }}

      + + {{ + Form::submit(Lang::get('setup.test_connection'), array( + 'name' => '_test', + 'class' => 'btn btn-primary' + )) + }} +
      +
      +@stop diff --git a/app/views/skins/bootstrap/setup/install/stage2.blade.php b/app/views/skins/bootstrap/setup/install/stage2.blade.php new file mode 100755 index 000000000..c85a26764 --- /dev/null +++ b/app/views/skins/bootstrap/setup/install/stage2.blade.php @@ -0,0 +1,22 @@ +@extends('skins.bootstrap.setup.page') + +@section('body') +
      +
      + {{ Lang::get('setup.i_stage2_title') }} + +
      + {{ sprintf(Lang::get('setup.install_warn'), link_to('setup/update', Lang::get('setup.update_util'))) }} +
      + +

      {{ Lang::get('setup.i_stage2_exp') }}

      + + {{ + Form::submit(Lang::get('setup.start_install'), array( + 'name' => '_install', + 'class' => 'btn btn-primary' + )) + }} +
      +
      +@stop diff --git a/app/views/skins/bootstrap/setup/install/stage3.blade.php b/app/views/skins/bootstrap/setup/install/stage3.blade.php new file mode 100755 index 000000000..45f4d5bfc --- /dev/null +++ b/app/views/skins/bootstrap/setup/install/stage3.blade.php @@ -0,0 +1,41 @@ +@extends('skins.bootstrap.setup.page') + +@section('body') +
      +
      + {{ Lang::get('setup.i_stage3_title') }} + +

      {{ Lang::get('setup.i_stage3_exp') }}

      + +
      +
      +
      +
      +
      +
      +
      + +
      +
      + + + {{ Lang::get('setup.initializing') }} + + +
      + +
      + + 0% + {{ Lang::get('setup.complete') }} + +
      +
      +
      + + +
      +@stop diff --git a/app/views/skins/bootstrap/setup/install/stage4.blade.php b/app/views/skins/bootstrap/setup/install/stage4.blade.php new file mode 100755 index 000000000..8ecc4645c --- /dev/null +++ b/app/views/skins/bootstrap/setup/install/stage4.blade.php @@ -0,0 +1,26 @@ +@extends('skins.bootstrap.setup.page') + +@section('body') +
      +
      + {{ Lang::get('setup.i_stage4_title') }} + +

      {{ Lang::get('setup.i_stage4_exp') }}

      + +
      +
      {{ Lang::get('global.username') }}
      +
      {{ Session::get('install.username') }}
      + +
      {{ Lang::get('global.password') }}
      +
      {{ Session::get('install.password') }}
      +
      + + {{ + Form::submit(Lang::get('setup.proceed_login'), array( + 'name' => '_finish', + 'class' => 'btn btn-success' + )) + }} +
      +
      +@stop diff --git a/app/views/skins/bootstrap/setup/page.blade.php b/app/views/skins/bootstrap/setup/page.blade.php new file mode 100755 index 000000000..85d228080 --- /dev/null +++ b/app/views/skins/bootstrap/setup/page.blade.php @@ -0,0 +1,50 @@ + + + + + + + + {{ Lang::get('global.sticky_notes') }} + + + + + + + + + + + + + +
      + {{ + Form::open(array( + 'autocomplete' => 'off', + 'role' => 'form', + )) + }} + + @include('skins.bootstrap.common.alerts') + + @yield('body') + + {{ Form::close() }} +
      + + + + + diff --git a/app/views/skins/bootstrap/setup/update/stage1.blade.php b/app/views/skins/bootstrap/setup/update/stage1.blade.php new file mode 100755 index 000000000..c4daaad6d --- /dev/null +++ b/app/views/skins/bootstrap/setup/update/stage1.blade.php @@ -0,0 +1,40 @@ +@extends('skins.bootstrap.setup.page') + +@section('body') +
      + @if ($unstable) +
      + {{ sprintf(Lang::get('setup.develop_warn'), $site->services->downloadUrl) }} +
      + @endif + +
      + {{ Lang::get('setup.u_stage1_title') }} + +

      {{ Lang::get('setup.u_stage1_exp') }}

      + +

      {{ Lang::get('setup.update_config') }}

      + +
      + {{ + Form::label('version', Lang::get('setup.update_from'), array( + 'class' => 'control-label' + )) + }} + + {{ + Form::select('version', $versions, Site::config('general')->version, array( + 'class' => 'form-control' + )) + }} +
      + + {{ + Form::submit(Lang::get('setup.start_update'), array( + 'name' => '_update', + 'class' => 'btn btn-primary' + )) + }} +
      +
      +@stop diff --git a/app/views/skins/bootstrap/setup/update/stage2.blade.php b/app/views/skins/bootstrap/setup/update/stage2.blade.php new file mode 100755 index 000000000..aaf266205 --- /dev/null +++ b/app/views/skins/bootstrap/setup/update/stage2.blade.php @@ -0,0 +1,41 @@ +@extends('skins.bootstrap.setup.page') + +@section('body') +
      +
      + {{ Lang::get('setup.u_stage2_title') }} + +

      {{ Lang::get('setup.u_stage2_exp') }}

      + +
      +
      +
      +
      +
      +
      +
      + +
      +
      + + + {{ Lang::get('setup.initializing') }} + + +
      + +
      + + 0% + {{ Lang::get('setup.complete') }} + +
      +
      +
      + + +
      +@stop diff --git a/app/views/skins/bootstrap/setup/update/stage3.blade.php b/app/views/skins/bootstrap/setup/update/stage3.blade.php new file mode 100755 index 000000000..51d7b3d38 --- /dev/null +++ b/app/views/skins/bootstrap/setup/update/stage3.blade.php @@ -0,0 +1,34 @@ +@extends('skins.bootstrap.setup.page') + +@section('body') +
      +
      + {{ Lang::get('setup.u_stage3_title') }} + +

      {{ Lang::get('setup.u_stage3_exp') }}

      + + @if (count($messages) > 0) +

      {{ Lang::get('setup.update_notifs') }}

      + +

      {{ Lang::get('setup.update_notifs_exp') }}

      + +
      + + @foreach ($messages as $version => $message) +

      {{ sprintf(Lang::get('setup.notify_version'), $version) }}

      + +

      {{ $message }}

      + +
      + @endforeach + @endif + + {{ + Form::submit(Lang::get('setup.return_sn'), array( + 'name' => '_finish', + 'class' => 'btn btn-success' + )) + }} +
      +
      +@stop diff --git a/app/views/skins/bootstrap/site/create.blade.php b/app/views/skins/bootstrap/site/create.blade.php new file mode 100755 index 000000000..d0526e010 --- /dev/null +++ b/app/views/skins/bootstrap/site/create.blade.php @@ -0,0 +1,137 @@ +@extends("skins.bootstrap.common.{$container}") + +@section('body') + @include('skins.bootstrap.common.alerts') + +
      + {{ + Form::open(array( + 'action' => $action, + 'autocomplete' => 'off', + 'role' => 'form', + 'files' => TRUE + )) + }} + +
      +
      +
      + {{ + Form::text('title', $paste->title, array( + 'class' => 'form-control', + 'maxlength' => 30, + 'placeholder' => Lang::get('global.paste_title'), + 'disabled' => $disabled + )) + }} +
      +
      + +
      + +
      +
      + {{ + Form::select('language', $languages, $paste->language ?: $language, array( + 'class' => 'form-control', + 'disabled' => $disabled + )) + }} +
      +
      +
      + +
      +
      +
      + {{ + Form::textarea('data', htmlspecialchars($paste->data), array( + 'class' => 'form-control', + 'rows' => 18, + 'placeholder' => Lang::get('global.paste_data') + )) + }} +
      +
      +
      + + @if ($site->general->allowAttachment AND $attach) +
      +
      +
      + + {{ Form::file('attachment') }} +
      +
      +
      + @endif + +
      +
      + @if ($site->general->pasteVisibility != 'public') +
      +
      +
      + +
      + + {{ + Form::password('password', array( + 'class' => 'form-control', + 'placeholder' => Lang::get('global.password'), + 'disabled' => $disabled + )) + }} +
      +
      + @endif +
      + +
      + @if ($site->general->pasteVisibility == 'default') +
      +
      + +
      +
      + @endif +
      + +
      +
      +
      +
      + {{ + Form::submit(Lang::get('global.paste'), array( + 'name' => '_submit', + 'class' => 'btn btn-primary' + )) + }} +
      + + {{ + Form::select('expire', Paste::getExpiration(), $site->general->pasteAge, array( + 'class' => 'form-control' + )) + }} + + @if ($paste->id > 0) + {{ Form::hidden('id', $paste->id) }} + @endif +
      +
      +
      +
      + + {{ Form::close() }} +
      +@stop diff --git a/app/views/skins/bootstrap/site/diff.blade.php b/app/views/skins/bootstrap/site/diff.blade.php new file mode 100755 index 000000000..e1b7159db --- /dev/null +++ b/app/views/skins/bootstrap/site/diff.blade.php @@ -0,0 +1,31 @@ +@extends("skins.bootstrap.common.{$container}") + + + +@section('body') +
      +
      +
      +
      +
      +
      +

      {{ Lang::get('show.revision_diff') }}

      +
      + +
      + {{ + link_to($newkey, Lang::get('show.return_paste'), array( + 'class' => 'btn btn-success' + )) + }} +
      +
      +
      + +
      + {{ $diff }} +
      +
      +
      +
      +@stop diff --git a/app/views/skins/bootstrap/site/list.blade.php b/app/views/skins/bootstrap/site/list.blade.php new file mode 100755 index 000000000..1bdc5b9d0 --- /dev/null +++ b/app/views/skins/bootstrap/site/list.blade.php @@ -0,0 +1,57 @@ +@extends("skins.bootstrap.common.{$container}") + +@section('body') + @include('skins.bootstrap.common.alerts') + +
      + @if ($search) +
      +
      + {{ + Form::open(array( + 'action' => 'ListController@postSearch', + 'role' => 'form' + )) + }} + +
      + {{ + Form::text('search', Input::get('q'), array( + 'class' => 'form-control', + 'placeholder' => Lang::get('list.search'), + 'maxlength' => 500 + )) + }} +
      + + {{ Form::close() }} +
      +
      + @endif + + @if ($filters) +
      +
      +
      + +
      +
      +
      + @endif + + @foreach ($pastes as $paste) + @include('skins.bootstrap.site.paste') + @endforeach + + {{ $pages }} +
      +@stop diff --git a/app/views/skins/bootstrap/site/password.blade.php b/app/views/skins/bootstrap/site/password.blade.php new file mode 100755 index 000000000..f1ae723d0 --- /dev/null +++ b/app/views/skins/bootstrap/site/password.blade.php @@ -0,0 +1,37 @@ +@extends("skins.bootstrap.common.{$container}") + +@section('body') +
      + {{ + Form::open(array( + 'autocomplete' => 'off', + 'role' => 'form' + )) + }} + +
      +
      +
      +

      +

      {{ Lang::get('global.paste_pwd') }}

      + + {{ + Form::password('password', array( + 'class' => 'form-control', + 'placeholder' => Lang::get('global.password') + )) + }} + + {{ + Form::submit(Lang::get('global.submit'), array( + 'name' => '_submit', + 'class' => 'btn btn-primary' + )) + }} +
      +
      +
      + + {{ Form::close() }} +
      +@stop diff --git a/app/views/skins/bootstrap/site/paste.blade.php b/app/views/skins/bootstrap/site/paste.blade.php new file mode 100755 index 000000000..faa37ff4f --- /dev/null +++ b/app/views/skins/bootstrap/site/paste.blade.php @@ -0,0 +1,152 @@ +
      +
      +
      +
      +
      +

      + @if (empty($paste->title)) + {{ Lang::get('global.paste') }} + #{{ $paste->urlkey }} + @else + {{{ $paste->title }}} + @endif +

      +
      + +
      + @if ($context == 'ShowController') + @if ( ! empty($site->services->googleApiKey)) + {{ + link_to("#", Lang::get('show.short_url'), array( + 'class' => 'btn btn-success', + 'data-toggle' => 'ajax', + 'data-component' => 'shorten', + 'data-extra' => Paste::getUrl($paste), + )) + }} + @endif + + {{ + link_to("#", Lang::get('show.wrap'), array( + 'class' => 'btn btn-success', + 'data-toggle' => 'wrap', + )) + }} + + {{ + link_to("{$paste->urlkey}/{$paste->hash}/raw", Lang::get('show.raw'), array( + 'class' => 'btn btn-success' + )) + }} + + {{ + link_to("rev/{$paste->urlkey}", Lang::get('show.revise'), array( + 'class' => 'btn btn-success' + )) + }} + + @if ($site->general->share) + {{ + HTML::decode(link_to($share, '', array( + 'class' => 'btn btn-warning', + 'data-toggle' => 'tooltip', + 'title' => Lang::get('global.share') + ))) + }} + @endif + + @if ($site->general->flagPaste == 'all' OR ($site->general->flagPaste == 'user' AND $role->user)) + {{ + HTML::decode( + link_to("{$paste->urlkey}/{$paste->hash}/flag", '', array( + 'rel' => 'nofollow', + 'class' => 'btn btn-danger', + 'data-toggle' => 'tooltip', + 'onclick' => "return confirm('".Lang::get('global.action_confirm')."')", + 'title' => Lang::get('global.flag_paste') + ) + ) + ) + }} + @endif + @elseif ($context == 'ListController') + {{ + link_to(Paste::getUrl($paste), Lang::get('list.show_paste'), array( + 'class' => 'btn btn-success' + )) + }} + @endif + + @if ($role->admin OR ($role->user AND $auth->id == $paste->author_id)) + @if ($paste->password) + + + + @elseif ($paste->private) + + + + @endif + +
      + + + +
      + @endif +
      +
      +
      + +
      + @if ($context == 'ShowController') + {{ Highlighter::make()->parse($paste->id.'show', $paste->data, $paste->language) }} + @elseif ($context == 'ListController') + {{ Highlighter::make()->parse($paste->id.'list', Paste::getAbstract($paste->data), $paste->language) }} + @endif +
      + + +
      +
      diff --git a/app/views/skins/bootstrap/site/show.blade.php b/app/views/skins/bootstrap/site/show.blade.php new file mode 100755 index 000000000..b7cdf46ec --- /dev/null +++ b/app/views/skins/bootstrap/site/show.blade.php @@ -0,0 +1,155 @@ +@extends("skins.bootstrap.common.{$container}") + +@section('body') + @include('skins.bootstrap.common.alerts') + +
      + @include('skins.bootstrap.site.paste') + + @if ($paste->attachment) +
      +
      +
      + + {{ link_to("attachment/{$paste->urlkey}/{$paste->hash}", $attachment) }} +
      +
      +
      + @endif + + @if ($revisions->count() > 0) +
      +
      +

      + + {{ Lang::get('show.version_history') }} +

      + +
      +
      + + + + + + + + + + + + + + + + + + + @foreach ($revisions as $revision) + + + + + + + + + + @endforeach + +
      {{ Lang::get('show.revision_id') }}{{ Lang::get('global.author') }}{{ Lang::get('show.created_at') }}
      + {{ + link_to($revision->urlkey, $revision->urlkey) + }} + + {{{ + $paste->author ?: Lang::get('global.anonymous') + }}} + + {{ + date('d M Y, H:i:s e', $revision->timestamp) + }} + + {{ + link_to("diff/{$revision->urlkey}/{$paste->urlkey}", Lang::get('show.diff'), array( + 'class' => 'btn btn-xs btn-default' + )) + }} +
      +
      +
      +
      +
      + @endif + + @if ($site->general->comments) +
      +
      +

      + + {{ Lang::get('global.comments') }} +

      + + {{ + Form::open(array( + 'action' => 'ShowController@postComment', + 'role' => 'form', + 'data-navigate' => 'ajax' + )) + }} + +
      + {{ + Form::textarea('comment', NULL, array( + 'class' => 'form-control', + 'rows' => 2 + )) + }} +
      + +
      + {{ + Form::submit(Lang::get('global.submit'), array( + 'name' => '_submit', + 'class' => 'btn btn-primary' + )) + }} +
      + + {{ Form::hidden('id', $paste->id) }} + {{ Form::close() }} + + @if ($comments->count() > 0) + @foreach ($comments as $comment) +
      +

      + {{ $comment->data }} +

      + +
      +
      + @if ($role->admin OR ($role->user AND $auth->username == $comment->author)) + {{ + link_to("{$paste->urlkey}/{$paste->hash}/delete/{$comment->id}", Lang::get('global.delete'), array( + 'onclick' => "return confirm('".Lang::get('global.action_confirm')."')", + )) + }} + @endif +
      + +
      + {{{ $comment->author }}} + • + {{{ date('d M Y, H:i:s e', $comment->timestamp) }}} +
      +
      +
      + @endforeach + + {{ $comments->links() }} + @endif +
      +
      + @endif +
      +@stop diff --git a/app/views/skins/bootstrap/user/forgot.blade.php b/app/views/skins/bootstrap/user/forgot.blade.php new file mode 100755 index 000000000..9d1fa2e35 --- /dev/null +++ b/app/views/skins/bootstrap/user/forgot.blade.php @@ -0,0 +1,53 @@ +@extends("skins.bootstrap.common.{$container}") + +@section('body') +
      + {{ + Form::open(array( + 'autocomplete' => 'off', + 'role' => 'form' + )) + }} + +
      +
      +
      + {{ Lang::get('user.forgot_password') }} + + @if ($site->auth->method != 'db') +
      + {{ Lang::get('user.feature_disabled') }} +
      + @endif + + @include('skins.bootstrap.common.alerts') + +
      + {{ Form::label('username', Lang::get('global.username')) }} + + {{ + Form::text('username', NULL, array( + 'class' => 'form-control', + 'maxlength' => 50, + )) + }} + +
      + {{ Lang::get('user.forgot_exp') }} +
      +
      + + {{ + Form::submit(Lang::get('user.reset_password'), array( + 'name' => '_reset', + 'class' => 'btn btn-primary', + 'disabled' => $site->auth->method != 'db' ?: NULL, + )) + }} +
      +
      +
      + + {{ Form::close() }} +
      +@stop diff --git a/app/views/skins/bootstrap/user/login.blade.php b/app/views/skins/bootstrap/user/login.blade.php new file mode 100755 index 000000000..edc011205 --- /dev/null +++ b/app/views/skins/bootstrap/user/login.blade.php @@ -0,0 +1,94 @@ +@extends("skins.bootstrap.common.{$container}") + +@section('body') +
      + {{ + Form::open(array( + 'autocomplete' => 'off', + 'role' => 'form' + )) + }} + +
      +
      +
      + {{ sprintf(Lang::get('user.login_to'), $site->general->title) }} + + @if ( ! empty($site->auth->bannerText)) +
      + {{ $site->auth->bannerText }} +
      + @endif + + @include('skins.bootstrap.common.alerts') + +
      + {{ Form::label('username', Lang::get('global.username')) }} + + {{ + Form::text('username', NULL, array( + 'class' => 'form-control', + 'maxlength' => 50 + )) + }} +
      + +
      + {{ Form::label('password', Lang::get('global.password')) }} + + {{ + Form::password('password', array( + 'class' => 'form-control' + )) + }} +
      + +
      + +
      + + {{ + Form::submit(Lang::get('user.login'), array( + 'name' => '_login', + 'class' => 'btn btn-primary' + )) + }} + + @if ($site->auth->method == 'db') + @if ($site->auth->dbAllowReg) + {{ + link_to('user/register', Lang::get('user.create_acct'), array( + 'class' => 'btn btn-link', + )) + }} + @endif + + {{ + link_to('user/forgot', Lang::get('user.forgot_password'), array( + 'class' => 'btn btn-link', + )) + }} + @endif + + @if ( ! empty($site->auth->infoUrl) AND ! empty($site->auth->infoUrlText)) + {{ + link_to($site->auth->infoUrl, $site->auth->infoUrlText, array( + 'class' => 'btn btn-link', + )) + }} + @endif +
      +
      +
      + + {{ Form::close() }} +
      +@stop diff --git a/app/views/skins/bootstrap/user/profile.blade.php b/app/views/skins/bootstrap/user/profile.blade.php new file mode 100755 index 000000000..b93dc7e70 --- /dev/null +++ b/app/views/skins/bootstrap/user/profile.blade.php @@ -0,0 +1,89 @@ +@extends("skins.bootstrap.common.{$container}") + +@section('body') +
      + {{ + Form::open(array( + 'autocomplete' => 'off', + 'role' => 'form', + )) + }} + +
      +
      +
      + {{ Lang::get('user.your_profile') }} + + @if ($site->auth->method != 'db') +
      + {{ Lang::get('user.feature_disabled') }} +
      + @endif + + @include('skins.bootstrap.common.alerts') + +
      + {{ Form::label('username', Lang::get('global.username')) }} + + {{ + Form::text('username', $auth->username, array( + 'class' => 'form-control', + 'disabled' => ! $auth->admin ?: NULL + )) + }} +
      + +
      + {{ Form::label('email', Lang::get('global.email')) }} + + {{ + Form::text('email', $auth->email, array( + 'class' => 'form-control', + 'maxlength' => 100, + )) + }} +
      + +
      + {{ Form::label('dispname', Lang::get('global.full_name')) }} + + {{ + Form::text('dispname', $auth->dispname, array( + 'class' => 'form-control', + 'maxlength' => 100, + )) + }} +
      + + @if ($site->auth->method == 'db') +
      + {{ Form::label('password', Lang::get('user.new_password')) }} + + {{ + Form::password('password', array( + 'class' => 'form-control', + )) + }} +
      + @endif + + {{ + Form::submit(Lang::get('global.save'), array( + 'name' => '_save', + 'class' => 'btn btn-primary', + 'disabled' => $site->auth->method != 'db' ?: NULL, + )) + }} + + {{ + link_to("user/u{$auth->id}/pastes", Lang::get('user.my_pastes'), array( + 'class' => 'btn btn-link', + )) + }} +
      +
      +
      + + {{ Form::close() }} +
      +@stop diff --git a/app/views/skins/bootstrap/user/register.blade.php b/app/views/skins/bootstrap/user/register.blade.php new file mode 100755 index 000000000..642fef447 --- /dev/null +++ b/app/views/skins/bootstrap/user/register.blade.php @@ -0,0 +1,99 @@ +@extends("skins.bootstrap.common.{$container}") + +@section('body') +
      + {{ + Form::open(array( + 'autocomplete' => 'off', + 'role' => 'form' + )) + }} + +
      +
      +
      + {{ Lang::get('user.create_acct') }} + + @if ($site->auth->method != 'db' OR ! $site->auth->dbAllowReg) +
      + {{ Lang::get('user.reg_disabled') }} +
      + @endif + + @include('skins.bootstrap.common.alerts') + +
      + {{ Form::label('username', Lang::get('global.username')) }} + + {{ + Form::text('username', NULL, array( + 'class' => 'form-control', + 'maxlength' => 50, + )) + }} +
      + +
      + {{ Form::label('email', Lang::get('global.email')) }} + + {{ + Form::text('email', NULL, array( + 'class' => 'form-control', + 'maxlength' => 100, + )) + }} +
      + +
      + {{ Form::label('dispname', Lang::get('global.full_name')) }} + + {{ + Form::text('dispname', NULL, array( + 'class' => 'form-control', + 'maxlength' => 100, + )) + }} +
      + +
      + {{ Form::label('password', Lang::get('global.password')) }} + + {{ + Form::password('password', array( + 'class' => 'form-control', + )) + }} +
      + + @if ($site->auth->dbShowCaptcha) +
      + {{ Form::label('captcha', Lang::get('user.human_verify')) }} + +
      + + {{ HTML::image(Captcha::img()) }} + + + {{ + Form::text('captcha', NULL, array( + 'class' => 'form-control', + )) + }} +
      +
      + @endif + + {{ + Form::submit(Lang::get('user.register'), array( + 'name' => '_register', + 'class' => 'btn btn-primary', + 'disabled' => ($site->auth->method != 'db' OR ! $site->auth->dbAllowReg) ?: NULL, + )) + }} +
      +
      +
      + + {{ Form::close() }} +
      +@stop diff --git a/app/views/templates/api/json/create.blade.php b/app/views/templates/api/json/create.blade.php new file mode 100755 index 000000000..0430db93f --- /dev/null +++ b/app/views/templates/api/json/create.blade.php @@ -0,0 +1,7 @@ +{ + "result": + { + "id": {{ $urlkey }}, + "hash": {{ $hash }} + } +} diff --git a/app/views/templates/api/json/error.blade.php b/app/views/templates/api/json/error.blade.php new file mode 100755 index 000000000..aa41e906c --- /dev/null +++ b/app/views/templates/api/json/error.blade.php @@ -0,0 +1,6 @@ +{ + "result": + { + "error": {{ $error }} + } +} diff --git a/app/views/templates/api/json/list.blade.php b/app/views/templates/api/json/list.blade.php new file mode 100755 index 000000000..b8f2efc42 --- /dev/null +++ b/app/views/templates/api/json/list.blade.php @@ -0,0 +1,12 @@ +{ + "result": + { + "pastes": [ + @foreach ($pastes as $paste) + {{ $paste['urlkey'] }}{{ $iterator++ < count($pastes) - 1 ? ',' : NULL }} + @endforeach + ], + "count": {{ $count }}, + "pages": {{ $pages }} + } +} diff --git a/app/views/templates/api/json/param.blade.php b/app/views/templates/api/json/param.blade.php new file mode 100755 index 000000000..e5327c461 --- /dev/null +++ b/app/views/templates/api/json/param.blade.php @@ -0,0 +1,10 @@ +{ + "result": + { + "values": [ + @foreach ($values as $value) + {{ $value }}{{ $iterator++ < count($values) - 1 ? ',' : NULL }} + @endforeach + ] + } +} diff --git a/app/views/templates/api/json/show.blade.php b/app/views/templates/api/json/show.blade.php new file mode 100755 index 000000000..89bc0c688 --- /dev/null +++ b/app/views/templates/api/json/show.blade.php @@ -0,0 +1,13 @@ +{ + "result": + { + "id": {{ $urlkey }}, + "author": {{ $author }}, + "timestamp": {{ $timestamp }}, + "language": {{ $language }}, + "title": {{ $title }}, + "data": {{ $data }}, + "project": {{ $project }}, + "hits": {{ $hits }} + } +} diff --git a/app/views/templates/api/xml/create.blade.php b/app/views/templates/api/xml/create.blade.php new file mode 100755 index 000000000..b8f79bc91 --- /dev/null +++ b/app/views/templates/api/xml/create.blade.php @@ -0,0 +1,6 @@ +{{ '' }} + + + {{ $urlkey }} + {{ $hash }} + diff --git a/app/views/templates/api/xml/error.blade.php b/app/views/templates/api/xml/error.blade.php new file mode 100755 index 000000000..53cdd0496 --- /dev/null +++ b/app/views/templates/api/xml/error.blade.php @@ -0,0 +1,5 @@ +{{ '' }} + + + {{ $error }} + diff --git a/app/views/templates/api/xml/list.blade.php b/app/views/templates/api/xml/list.blade.php new file mode 100755 index 000000000..f94872aef --- /dev/null +++ b/app/views/templates/api/xml/list.blade.php @@ -0,0 +1,11 @@ +{{ '' }} + + + + @foreach ($pastes as $paste) + {{ $paste['urlkey'] }} + @endforeach + + {{ $count }} + {{ $pages }} + diff --git a/app/views/templates/api/xml/param.blade.php b/app/views/templates/api/xml/param.blade.php new file mode 100755 index 000000000..2a78ac360 --- /dev/null +++ b/app/views/templates/api/xml/param.blade.php @@ -0,0 +1,9 @@ +{{ '' }} + + + + @foreach ($values as $value) + {{ $value }} + @endforeach + + diff --git a/app/views/templates/api/xml/show.blade.php b/app/views/templates/api/xml/show.blade.php new file mode 100755 index 000000000..376b5290b --- /dev/null +++ b/app/views/templates/api/xml/show.blade.php @@ -0,0 +1,12 @@ +{{ '' }} + + + {{ $urlkey }} + {{ $author }} + {{ $timestamp }} + {{ $language }} + {{ $title }} + {{ $data }} + {{ $project }} + {{ $hits }} + diff --git a/app/views/templates/email/forgot.blade.php b/app/views/templates/email/forgot.blade.php new file mode 100755 index 000000000..550108dbf --- /dev/null +++ b/app/views/templates/email/forgot.blade.php @@ -0,0 +1,12 @@ +

      {{ sprintf(Lang::get('mail.hello_user'), $name) }}

      + +

      + {{ sprintf(Lang::get('mail.password_reset'), $site['general']['fqdn']) }} +
      + {{ sprintf(Lang::get('mail.new_password'), $password) }} +

      + +

      {{ sprintf(Lang::get('mail.click_login'), link_to('user/login')) }}

      +
      + +{{ Lang::get('mail.autogen_mail') }} diff --git a/app/views/templates/email/test.blade.php b/app/views/templates/email/test.blade.php new file mode 100755 index 000000000..e33016aac --- /dev/null +++ b/app/views/templates/email/test.blade.php @@ -0,0 +1 @@ +Testing mail settings diff --git a/app/views/templates/feed/rss.blade.php b/app/views/templates/feed/rss.blade.php new file mode 100755 index 000000000..3b60fd377 --- /dev/null +++ b/app/views/templates/feed/rss.blade.php @@ -0,0 +1,28 @@ +{{ '' }} + + + {{ Lang::get('global.feed') }} - {{ $site->general->title }} + {{ url('all') }} + {{ $site->general->lang }} + {{ date(DATE_RSS) }} + Sayak Banerjee (mail@sayakbanerjee.com) + + @foreach ($pastes as $paste) + + + @if (empty($paste['title'])) + {{ Lang::get('global.paste') }} + #{{ $paste['urlkey'] }} + @else + {{{ $paste['title'] }}} + @endif + + + {{ url($paste['urlkey']) }} + + {{ date(DATE_RSS, $paste['timestamp']) }} + {{ url($paste['urlkey']) }} + + @endforeach + + diff --git a/artisan b/artisan new file mode 100755 index 000000000..ff773ab20 --- /dev/null +++ b/artisan @@ -0,0 +1,74 @@ +#!/usr/bin/env php +setRequestForConsoleEnvironment(); + +$artisan = Illuminate\Console\Application::start($app); + +/* +|-------------------------------------------------------------------------- +| Run The Artisan Application +|-------------------------------------------------------------------------- +| +| When we run the console application, the current CLI command will be +| executed in this console and the response sent back to a terminal +| or another output device for the developers. Here goes nothing! +| +*/ + +$status = $artisan->run(); + +/* +|-------------------------------------------------------------------------- +| Shutdown The Application +|-------------------------------------------------------------------------- +| +| Once Artisan has finished running. We will fire off the shutdown events +| so that any final work may be done by the application before we shut +| down the process. This is the last thing to happen to the request. +| +*/ + +$app->shutdown(); + +exit($status); diff --git a/bootstrap/autoload.php b/bootstrap/autoload.php new file mode 100755 index 000000000..6b329312a --- /dev/null +++ b/bootstrap/autoload.php @@ -0,0 +1,75 @@ +bound($abstract) || $this->isAlias($abstract); + } + public function bound($abstract) + { + return isset($this->bindings[$abstract]) || isset($this->instances[$abstract]); + } + public function resolved($abstract) + { + return isset($this->resolved[$abstract]) || isset($this->instances[$abstract]); + } + public function isAlias($name) + { + return isset($this->aliases[$name]); + } + public function bind($abstract, $concrete = null, $shared = false) + { + if (is_array($abstract)) { + list($abstract, $alias) = $this->extractAlias($abstract); + $this->alias($abstract, $alias); + } + $this->dropStaleInstances($abstract); + if (is_null($concrete)) { + $concrete = $abstract; + } + if (!$concrete instanceof Closure) { + $concrete = $this->getClosure($abstract, $concrete); + } + $this->bindings[$abstract] = compact('concrete', 'shared'); + if ($this->resolved($abstract)) { + $this->rebound($abstract); + } + } + protected function getClosure($abstract, $concrete) + { + return function ($c, $parameters = array()) use($abstract, $concrete) { + $method = $abstract == $concrete ? 'build' : 'make'; + return $c->{$method}($concrete, $parameters); + }; + } + public function bindIf($abstract, $concrete = null, $shared = false) + { + if (!$this->bound($abstract)) { + $this->bind($abstract, $concrete, $shared); + } + } + public function singleton($abstract, $concrete = null) + { + $this->bind($abstract, $concrete, true); + } + public function share(Closure $closure) + { + return function ($container) use($closure) { + static $object; + if (is_null($object)) { + $object = $closure($container); + } + return $object; + }; + } + public function bindShared($abstract, Closure $closure) + { + $this->bind($abstract, $this->share($closure), true); + } + public function extend($abstract, Closure $closure) + { + if (!isset($this->bindings[$abstract])) { + throw new \InvalidArgumentException("Type {$abstract} is not bound."); + } + if (isset($this->instances[$abstract])) { + $this->instances[$abstract] = $closure($this->instances[$abstract], $this); + $this->rebound($abstract); + } else { + $extender = $this->getExtender($abstract, $closure); + $this->bind($abstract, $extender, $this->isShared($abstract)); + } + } + protected function getExtender($abstract, Closure $closure) + { + $resolver = $this->bindings[$abstract]['concrete']; + return function ($container) use($resolver, $closure) { + return $closure($resolver($container), $container); + }; + } + public function instance($abstract, $instance) + { + if (is_array($abstract)) { + list($abstract, $alias) = $this->extractAlias($abstract); + $this->alias($abstract, $alias); + } + unset($this->aliases[$abstract]); + $bound = $this->bound($abstract); + $this->instances[$abstract] = $instance; + if ($bound) { + $this->rebound($abstract); + } + } + public function alias($abstract, $alias) + { + $this->aliases[$alias] = $abstract; + } + protected function extractAlias(array $definition) + { + return array(key($definition), current($definition)); + } + public function rebinding($abstract, Closure $callback) + { + $this->reboundCallbacks[$abstract][] = $callback; + if ($this->bound($abstract)) { + return $this->make($abstract); + } + } + public function refresh($abstract, $target, $method) + { + return $this->rebinding($abstract, function ($app, $instance) use($target, $method) { + $target->{$method}($instance); + }); + } + protected function rebound($abstract) + { + $instance = $this->make($abstract); + foreach ($this->getReboundCallbacks($abstract) as $callback) { + call_user_func($callback, $this, $instance); + } + } + protected function getReboundCallbacks($abstract) + { + if (isset($this->reboundCallbacks[$abstract])) { + return $this->reboundCallbacks[$abstract]; + } + return array(); + } + public function make($abstract, $parameters = array()) + { + $abstract = $this->getAlias($abstract); + if (isset($this->instances[$abstract])) { + return $this->instances[$abstract]; + } + $concrete = $this->getConcrete($abstract); + if ($this->isBuildable($concrete, $abstract)) { + $object = $this->build($concrete, $parameters); + } else { + $object = $this->make($concrete, $parameters); + } + if ($this->isShared($abstract)) { + $this->instances[$abstract] = $object; + } + $this->fireResolvingCallbacks($abstract, $object); + $this->resolved[$abstract] = true; + return $object; + } + protected function getConcrete($abstract) + { + if (!isset($this->bindings[$abstract])) { + if ($this->missingLeadingSlash($abstract) && isset($this->bindings['\\' . $abstract])) { + $abstract = '\\' . $abstract; + } + return $abstract; + } + return $this->bindings[$abstract]['concrete']; + } + protected function missingLeadingSlash($abstract) + { + return is_string($abstract) && strpos($abstract, '\\') !== 0; + } + public function build($concrete, $parameters = array()) + { + if ($concrete instanceof Closure) { + return $concrete($this, $parameters); + } + $reflector = new ReflectionClass($concrete); + if (!$reflector->isInstantiable()) { + $message = "Target [{$concrete}] is not instantiable."; + throw new BindingResolutionException($message); + } + $constructor = $reflector->getConstructor(); + if (is_null($constructor)) { + return new $concrete(); + } + $dependencies = $constructor->getParameters(); + $parameters = $this->keyParametersByArgument($dependencies, $parameters); + $instances = $this->getDependencies($dependencies, $parameters); + return $reflector->newInstanceArgs($instances); + } + protected function getDependencies($parameters, array $primitives = array()) + { + $dependencies = array(); + foreach ($parameters as $parameter) { + $dependency = $parameter->getClass(); + if (array_key_exists($parameter->name, $primitives)) { + $dependencies[] = $primitives[$parameter->name]; + } elseif (is_null($dependency)) { + $dependencies[] = $this->resolveNonClass($parameter); + } else { + $dependencies[] = $this->resolveClass($parameter); + } + } + return (array) $dependencies; + } + protected function resolveNonClass(ReflectionParameter $parameter) + { + if ($parameter->isDefaultValueAvailable()) { + return $parameter->getDefaultValue(); + } + $message = "Unresolvable dependency resolving [{$parameter}] in class {$parameter->getDeclaringClass()->getName()}"; + throw new BindingResolutionException($message); + } + protected function resolveClass(ReflectionParameter $parameter) + { + try { + return $this->make($parameter->getClass()->name); + } catch (BindingResolutionException $e) { + if ($parameter->isOptional()) { + return $parameter->getDefaultValue(); + } + throw $e; + } + } + protected function keyParametersByArgument(array $dependencies, array $parameters) + { + foreach ($parameters as $key => $value) { + if (is_numeric($key)) { + unset($parameters[$key]); + $parameters[$dependencies[$key]->name] = $value; + } + } + return $parameters; + } + public function resolving($abstract, Closure $callback) + { + $this->resolvingCallbacks[$abstract][] = $callback; + } + public function resolvingAny(Closure $callback) + { + $this->globalResolvingCallbacks[] = $callback; + } + protected function fireResolvingCallbacks($abstract, $object) + { + if (isset($this->resolvingCallbacks[$abstract])) { + $this->fireCallbackArray($object, $this->resolvingCallbacks[$abstract]); + } + $this->fireCallbackArray($object, $this->globalResolvingCallbacks); + } + protected function fireCallbackArray($object, array $callbacks) + { + foreach ($callbacks as $callback) { + call_user_func($callback, $object, $this); + } + } + public function isShared($abstract) + { + if (isset($this->bindings[$abstract]['shared'])) { + $shared = $this->bindings[$abstract]['shared']; + } else { + $shared = false; + } + return isset($this->instances[$abstract]) || $shared === true; + } + protected function isBuildable($concrete, $abstract) + { + return $concrete === $abstract || $concrete instanceof Closure; + } + protected function getAlias($abstract) + { + return isset($this->aliases[$abstract]) ? $this->aliases[$abstract] : $abstract; + } + public function getBindings() + { + return $this->bindings; + } + protected function dropStaleInstances($abstract) + { + unset($this->instances[$abstract], $this->aliases[$abstract]); + } + public function forgetInstance($abstract) + { + unset($this->instances[$abstract]); + } + public function forgetInstances() + { + $this->instances = array(); + } + public function offsetExists($key) + { + return isset($this->bindings[$key]); + } + public function offsetGet($key) + { + return $this->make($key); + } + public function offsetSet($key, $value) + { + if (!$value instanceof Closure) { + $value = function () use($value) { + return $value; + }; + } + $this->bind($key, $value); + } + public function offsetUnset($key) + { + unset($this->bindings[$key], $this->instances[$key]); + } + public function __get($key) + { + return $this[$key]; + } + public function __set($key, $value) + { + $this[$key] = $value; + } +} +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +interface HttpKernelInterface +{ + const MASTER_REQUEST = 1; + const SUB_REQUEST = 2; + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true); +} +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +interface TerminableInterface +{ + public function terminate(Request $request, Response $response); +} +namespace Illuminate\Support\Contracts; + +interface ResponsePreparerInterface +{ + public function prepareResponse($value); + public function readyForResponses(); +} +namespace Illuminate\Foundation; + +use Closure; +use Stack\Builder; +use Illuminate\Http\Request; +use Illuminate\Http\Response; +use Illuminate\Config\FileLoader; +use Illuminate\Container\Container; +use Illuminate\Filesystem\Filesystem; +use Illuminate\Support\Facades\Facade; +use Illuminate\Events\EventServiceProvider; +use Illuminate\Routing\RoutingServiceProvider; +use Illuminate\Exception\ExceptionServiceProvider; +use Illuminate\Config\FileEnvironmentVariablesLoader; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\TerminableInterface; +use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Illuminate\Support\Contracts\ResponsePreparerInterface; +use Symfony\Component\HttpFoundation\Request as SymfonyRequest; +use Symfony\Component\HttpFoundation\Response as SymfonyResponse; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +class Application extends Container implements HttpKernelInterface, TerminableInterface, ResponsePreparerInterface +{ + const VERSION = '4.2.11'; + protected $booted = false; + protected $bootingCallbacks = array(); + protected $bootedCallbacks = array(); + protected $finishCallbacks = array(); + protected $shutdownCallbacks = array(); + protected $middlewares = array(); + protected $serviceProviders = array(); + protected $loadedProviders = array(); + protected $deferredServices = array(); + protected static $requestClass = 'Illuminate\\Http\\Request'; + public function __construct(Request $request = null) + { + $this->registerBaseBindings($request ?: $this->createNewRequest()); + $this->registerBaseServiceProviders(); + $this->registerBaseMiddlewares(); + } + protected function createNewRequest() + { + return forward_static_call(array(static::$requestClass, 'createFromGlobals')); + } + protected function registerBaseBindings($request) + { + $this->instance('request', $request); + $this->instance('Illuminate\\Container\\Container', $this); + } + protected function registerBaseServiceProviders() + { + foreach (array('Event', 'Exception', 'Routing') as $name) { + $this->{"register{$name}Provider"}(); + } + } + protected function registerExceptionProvider() + { + $this->register(new ExceptionServiceProvider($this)); + } + protected function registerRoutingProvider() + { + $this->register(new RoutingServiceProvider($this)); + } + protected function registerEventProvider() + { + $this->register(new EventServiceProvider($this)); + } + public function bindInstallPaths(array $paths) + { + $this->instance('path', realpath($paths['app'])); + foreach (array_except($paths, array('app')) as $key => $value) { + $this->instance("path.{$key}", realpath($value)); + } + } + public static function getBootstrapFile() + { + return __DIR__.'/../vendor/laravel/framework/src/Illuminate/Foundation' . '/start.php'; + } + public function startExceptionHandling() + { + $this['exception']->register($this->environment()); + $this['exception']->setDebug($this['config']['app.debug']); + } + public function environment() + { + if (count(func_get_args()) > 0) { + return in_array($this['env'], func_get_args()); + } + return $this['env']; + } + public function isLocal() + { + return $this['env'] == 'local'; + } + public function detectEnvironment($envs) + { + $args = isset($_SERVER['argv']) ? $_SERVER['argv'] : null; + return $this['env'] = (new EnvironmentDetector())->detect($envs, $args); + } + public function runningInConsole() + { + return php_sapi_name() == 'cli'; + } + public function runningUnitTests() + { + return $this['env'] == 'testing'; + } + public function forceRegister($provider, $options = array()) + { + return $this->register($provider, $options, true); + } + public function register($provider, $options = array(), $force = false) + { + if ($registered = $this->getRegistered($provider) && !$force) { + return $registered; + } + if (is_string($provider)) { + $provider = $this->resolveProviderClass($provider); + } + $provider->register(); + foreach ($options as $key => $value) { + $this[$key] = $value; + } + $this->markAsRegistered($provider); + if ($this->booted) { + $provider->boot(); + } + return $provider; + } + public function getRegistered($provider) + { + $name = is_string($provider) ? $provider : get_class($provider); + if (array_key_exists($name, $this->loadedProviders)) { + return array_first($this->serviceProviders, function ($key, $value) use($name) { + return get_class($value) == $name; + }); + } + } + public function resolveProviderClass($provider) + { + return new $provider($this); + } + protected function markAsRegistered($provider) + { + $this['events']->fire($class = get_class($provider), array($provider)); + $this->serviceProviders[] = $provider; + $this->loadedProviders[$class] = true; + } + public function loadDeferredProviders() + { + foreach ($this->deferredServices as $service => $provider) { + $this->loadDeferredProvider($service); + } + $this->deferredServices = array(); + } + protected function loadDeferredProvider($service) + { + $provider = $this->deferredServices[$service]; + if (!isset($this->loadedProviders[$provider])) { + $this->registerDeferredProvider($provider, $service); + } + } + public function registerDeferredProvider($provider, $service = null) + { + if ($service) { + unset($this->deferredServices[$service]); + } + $this->register($instance = new $provider($this)); + if (!$this->booted) { + $this->booting(function () use($instance) { + $instance->boot(); + }); + } + } + public function make($abstract, $parameters = array()) + { + $abstract = $this->getAlias($abstract); + if (isset($this->deferredServices[$abstract])) { + $this->loadDeferredProvider($abstract); + } + return parent::make($abstract, $parameters); + } + public function bound($abstract) + { + return isset($this->deferredServices[$abstract]) || parent::bound($abstract); + } + public function extend($abstract, Closure $closure) + { + $abstract = $this->getAlias($abstract); + if (isset($this->deferredServices[$abstract])) { + $this->loadDeferredProvider($abstract); + } + return parent::extend($abstract, $closure); + } + public function before($callback) + { + return $this['router']->before($callback); + } + public function after($callback) + { + return $this['router']->after($callback); + } + public function finish($callback) + { + $this->finishCallbacks[] = $callback; + } + public function shutdown(callable $callback = null) + { + if (is_null($callback)) { + $this->fireAppCallbacks($this->shutdownCallbacks); + } else { + $this->shutdownCallbacks[] = $callback; + } + } + public function useArraySessions(Closure $callback) + { + $this->bind('session.reject', function () use($callback) { + return $callback; + }); + } + public function isBooted() + { + return $this->booted; + } + public function boot() + { + if ($this->booted) { + return; + } + array_walk($this->serviceProviders, function ($p) { + $p->boot(); + }); + $this->bootApplication(); + } + protected function bootApplication() + { + $this->fireAppCallbacks($this->bootingCallbacks); + $this->booted = true; + $this->fireAppCallbacks($this->bootedCallbacks); + } + public function booting($callback) + { + $this->bootingCallbacks[] = $callback; + } + public function booted($callback) + { + $this->bootedCallbacks[] = $callback; + if ($this->isBooted()) { + $this->fireAppCallbacks(array($callback)); + } + } + public function run(SymfonyRequest $request = null) + { + $request = $request ?: $this['request']; + $response = with($stack = $this->getStackedClient())->handle($request); + $response->send(); + $stack->terminate($request, $response); + } + protected function getStackedClient() + { + $sessionReject = $this->bound('session.reject') ? $this['session.reject'] : null; + $client = (new Builder())->push('Illuminate\\Cookie\\Guard', $this['encrypter'])->push('Illuminate\\Cookie\\Queue', $this['cookie'])->push('Illuminate\\Session\\Middleware', $this['session'], $sessionReject); + $this->mergeCustomMiddlewares($client); + return $client->resolve($this); + } + protected function mergeCustomMiddlewares(Builder $stack) + { + foreach ($this->middlewares as $middleware) { + list($class, $parameters) = array_values($middleware); + array_unshift($parameters, $class); + call_user_func_array(array($stack, 'push'), $parameters); + } + } + protected function registerBaseMiddlewares() + { + + } + public function middleware($class, array $parameters = array()) + { + $this->middlewares[] = compact('class', 'parameters'); + return $this; + } + public function forgetMiddleware($class) + { + $this->middlewares = array_filter($this->middlewares, function ($m) use($class) { + return $m['class'] != $class; + }); + } + public function handle(SymfonyRequest $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + try { + $this->refreshRequest($request = Request::createFromBase($request)); + $this->boot(); + return $this->dispatch($request); + } catch (\Exception $e) { + if ($this->runningUnitTests()) { + throw $e; + } + return $this['exception']->handleException($e); + } + } + public function dispatch(Request $request) + { + if ($this->isDownForMaintenance()) { + $response = $this['events']->until('illuminate.app.down'); + if (!is_null($response)) { + return $this->prepareResponse($response, $request); + } + } + if ($this->runningUnitTests() && !$this['session']->isStarted()) { + $this['session']->start(); + } + return $this['router']->dispatch($this->prepareRequest($request)); + } + public function terminate(SymfonyRequest $request, SymfonyResponse $response) + { + $this->callFinishCallbacks($request, $response); + $this->shutdown(); + } + protected function refreshRequest(Request $request) + { + $this->instance('request', $request); + Facade::clearResolvedInstance('request'); + } + public function callFinishCallbacks(SymfonyRequest $request, SymfonyResponse $response) + { + foreach ($this->finishCallbacks as $callback) { + call_user_func($callback, $request, $response); + } + } + protected function fireAppCallbacks(array $callbacks) + { + foreach ($callbacks as $callback) { + call_user_func($callback, $this); + } + } + public function prepareRequest(Request $request) + { + if (!is_null($this['config']['session.driver']) && !$request->hasSession()) { + $request->setSession($this['session']->driver()); + } + return $request; + } + public function prepareResponse($value) + { + if (!$value instanceof SymfonyResponse) { + $value = new Response($value); + } + return $value->prepare($this['request']); + } + public function readyForResponses() + { + return $this->booted; + } + public function isDownForMaintenance() + { + return file_exists($this['config']['app.manifest'] . '/down'); + } + public function down(Closure $callback) + { + $this['events']->listen('illuminate.app.down', $callback); + } + public function abort($code, $message = '', array $headers = array()) + { + if ($code == 404) { + throw new NotFoundHttpException($message); + } + throw new HttpException($code, $message, null, $headers); + } + public function missing(Closure $callback) + { + $this->error(function (NotFoundHttpException $e) use($callback) { + return call_user_func($callback, $e); + }); + } + public function error(Closure $callback) + { + $this['exception']->error($callback); + } + public function pushError(Closure $callback) + { + $this['exception']->pushError($callback); + } + public function fatal(Closure $callback) + { + $this->error(function (FatalErrorException $e) use($callback) { + return call_user_func($callback, $e); + }); + } + public function getConfigLoader() + { + return new FileLoader(new Filesystem(), $this['path'] . '/config'); + } + public function getEnvironmentVariablesLoader() + { + return new FileEnvironmentVariablesLoader(new Filesystem(), $this['path.base']); + } + public function getProviderRepository() + { + $manifest = $this['config']['app.manifest']; + return new ProviderRepository(new Filesystem(), $manifest); + } + public function getLoadedProviders() + { + return $this->loadedProviders; + } + public function setDeferredServices(array $services) + { + $this->deferredServices = $services; + } + public function isDeferredService($service) + { + return isset($this->deferredServices[$service]); + } + public static function requestClass($class = null) + { + if (!is_null($class)) { + static::$requestClass = $class; + } + return static::$requestClass; + } + public function setRequestForConsoleEnvironment() + { + $url = $this['config']->get('app.url', 'http://localhost'); + $parameters = array($url, 'GET', array(), array(), array(), $_SERVER); + $this->refreshRequest(static::onRequest('create', $parameters)); + } + public static function onRequest($method, $parameters = array()) + { + return forward_static_call_array(array(static::requestClass(), $method), $parameters); + } + public function getLocale() + { + return $this['config']->get('app.locale'); + } + public function setLocale($locale) + { + $this['config']->set('app.locale', $locale); + $this['translator']->setLocale($locale); + $this['events']->fire('locale.changed', array($locale)); + } + public function registerCoreContainerAliases() + { + $aliases = array('app' => 'Illuminate\\Foundation\\Application', 'artisan' => 'Illuminate\\Console\\Application', 'auth' => 'Illuminate\\Auth\\AuthManager', 'auth.reminder.repository' => 'Illuminate\\Auth\\Reminders\\ReminderRepositoryInterface', 'blade.compiler' => 'Illuminate\\View\\Compilers\\BladeCompiler', 'cache' => 'Illuminate\\Cache\\CacheManager', 'cache.store' => 'Illuminate\\Cache\\Repository', 'config' => 'Illuminate\\Config\\Repository', 'cookie' => 'Illuminate\\Cookie\\CookieJar', 'encrypter' => 'Illuminate\\Encryption\\Encrypter', 'db' => 'Illuminate\\Database\\DatabaseManager', 'events' => 'Illuminate\\Events\\Dispatcher', 'files' => 'Illuminate\\Filesystem\\Filesystem', 'form' => 'Illuminate\\Html\\FormBuilder', 'hash' => 'Illuminate\\Hashing\\HasherInterface', 'html' => 'Illuminate\\Html\\HtmlBuilder', 'translator' => 'Illuminate\\Translation\\Translator', 'log' => 'Illuminate\\Log\\Writer', 'mailer' => 'Illuminate\\Mail\\Mailer', 'paginator' => 'Illuminate\\Pagination\\Factory', 'auth.reminder' => 'Illuminate\\Auth\\Reminders\\PasswordBroker', 'queue' => 'Illuminate\\Queue\\QueueManager', 'redirect' => 'Illuminate\\Routing\\Redirector', 'redis' => 'Illuminate\\Redis\\Database', 'request' => 'Illuminate\\Http\\Request', 'router' => 'Illuminate\\Routing\\Router', 'session' => 'Illuminate\\Session\\SessionManager', 'session.store' => 'Illuminate\\Session\\Store', 'remote' => 'Illuminate\\Remote\\RemoteManager', 'url' => 'Illuminate\\Routing\\UrlGenerator', 'validator' => 'Illuminate\\Validation\\Factory', 'view' => 'Illuminate\\View\\Factory'); + foreach ($aliases as $key => $alias) { + $this->alias($key, $alias); + } + } +} +namespace Illuminate\Foundation; + +use Closure; +class EnvironmentDetector +{ + public function detect($environments, $consoleArgs = null) + { + if ($consoleArgs) { + return $this->detectConsoleEnvironment($environments, $consoleArgs); + } + return $this->detectWebEnvironment($environments); + } + protected function detectWebEnvironment($environments) + { + if ($environments instanceof Closure) { + return call_user_func($environments); + } + foreach ($environments as $environment => $hosts) { + foreach ((array) $hosts as $host) { + if ($this->isMachine($host)) { + return $environment; + } + } + } + return 'production'; + } + protected function detectConsoleEnvironment($environments, array $args) + { + if (!is_null($value = $this->getEnvironmentArgument($args))) { + return head(array_slice(explode('=', $value), 1)); + } + return $this->detectWebEnvironment($environments); + } + protected function getEnvironmentArgument(array $args) + { + return array_first($args, function ($k, $v) { + return starts_with($v, '--env'); + }); + } + public function isMachine($name) + { + return str_is($name, gethostname()); + } +} +namespace Illuminate\Http; + +use SplFileInfo; +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\Request as SymfonyRequest; +class Request extends SymfonyRequest +{ + protected $json; + protected $sessionStore; + public function instance() + { + return $this; + } + public function method() + { + return $this->getMethod(); + } + public function root() + { + return rtrim($this->getSchemeAndHttpHost() . $this->getBaseUrl(), '/'); + } + public function url() + { + return rtrim(preg_replace('/\\?.*/', '', $this->getUri()), '/'); + } + public function fullUrl() + { + $query = $this->getQueryString(); + return $query ? $this->url() . '?' . $query : $this->url(); + } + public function path() + { + $pattern = trim($this->getPathInfo(), '/'); + return $pattern == '' ? '/' : $pattern; + } + public function decodedPath() + { + return rawurldecode($this->path()); + } + public function segment($index, $default = null) + { + return array_get($this->segments(), $index - 1, $default); + } + public function segments() + { + $segments = explode('/', $this->path()); + return array_values(array_filter($segments, function ($v) { + return $v != ''; + })); + } + public function is() + { + foreach (func_get_args() as $pattern) { + if (str_is($pattern, urldecode($this->path()))) { + return true; + } + } + return false; + } + public function ajax() + { + return $this->isXmlHttpRequest(); + } + public function secure() + { + return $this->isSecure(); + } + public function ip() + { + return $this->getClientIp(); + } + public function ips() + { + return $this->getClientIps(); + } + public function exists($key) + { + $keys = is_array($key) ? $key : func_get_args(); + $input = $this->all(); + foreach ($keys as $value) { + if (!array_key_exists($value, $input)) { + return false; + } + } + return true; + } + public function has($key) + { + $keys = is_array($key) ? $key : func_get_args(); + foreach ($keys as $value) { + if ($this->isEmptyString($value)) { + return false; + } + } + return true; + } + protected function isEmptyString($key) + { + $boolOrArray = is_bool($this->input($key)) || is_array($this->input($key)); + return !$boolOrArray && trim((string) $this->input($key)) === ''; + } + public function all() + { + return array_replace_recursive($this->input(), $this->files->all()); + } + public function input($key = null, $default = null) + { + $input = $this->getInputSource()->all() + $this->query->all(); + return array_get($input, $key, $default); + } + public function only($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + $results = array(); + $input = $this->all(); + foreach ($keys as $key) { + array_set($results, $key, array_get($input, $key)); + } + return $results; + } + public function except($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + $results = $this->all(); + array_forget($results, $keys); + return $results; + } + public function query($key = null, $default = null) + { + return $this->retrieveItem('query', $key, $default); + } + public function hasCookie($key) + { + return !is_null($this->cookie($key)); + } + public function cookie($key = null, $default = null) + { + return $this->retrieveItem('cookies', $key, $default); + } + public function file($key = null, $default = null) + { + return array_get($this->files->all(), $key, $default); + } + public function hasFile($key) + { + if (!is_array($files = $this->file($key))) { + $files = array($files); + } + foreach ($files as $file) { + if ($this->isValidFile($file)) { + return true; + } + } + return false; + } + protected function isValidFile($file) + { + return $file instanceof SplFileInfo && $file->getPath() != ''; + } + public function header($key = null, $default = null) + { + return $this->retrieveItem('headers', $key, $default); + } + public function server($key = null, $default = null) + { + return $this->retrieveItem('server', $key, $default); + } + public function old($key = null, $default = null) + { + return $this->session()->getOldInput($key, $default); + } + public function flash($filter = null, $keys = array()) + { + $flash = !is_null($filter) ? $this->{$filter}($keys) : $this->input(); + $this->session()->flashInput($flash); + } + public function flashOnly($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + return $this->flash('only', $keys); + } + public function flashExcept($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + return $this->flash('except', $keys); + } + public function flush() + { + $this->session()->flashInput(array()); + } + protected function retrieveItem($source, $key, $default) + { + if (is_null($key)) { + return $this->{$source}->all(); + } + return $this->{$source}->get($key, $default, true); + } + public function merge(array $input) + { + $this->getInputSource()->add($input); + } + public function replace(array $input) + { + $this->getInputSource()->replace($input); + } + public function json($key = null, $default = null) + { + if (!isset($this->json)) { + $this->json = new ParameterBag((array) json_decode($this->getContent(), true)); + } + if (is_null($key)) { + return $this->json; + } + return array_get($this->json->all(), $key, $default); + } + protected function getInputSource() + { + if ($this->isJson()) { + return $this->json(); + } + return $this->getMethod() == 'GET' ? $this->query : $this->request; + } + public function isJson() + { + return str_contains($this->header('CONTENT_TYPE'), '/json'); + } + public function wantsJson() + { + $acceptable = $this->getAcceptableContentTypes(); + return isset($acceptable[0]) && $acceptable[0] == 'application/json'; + } + public function format($default = 'html') + { + foreach ($this->getAcceptableContentTypes() as $type) { + if ($format = $this->getFormat($type)) { + return $format; + } + } + return $default; + } + public static function createFromBase(SymfonyRequest $request) + { + if ($request instanceof static) { + return $request; + } + return (new static())->duplicate($request->query->all(), $request->request->all(), $request->attributes->all(), $request->cookies->all(), $request->files->all(), $request->server->all()); + } + public function session() + { + if (!$this->hasSession()) { + throw new \RuntimeException('Session store not set on request.'); + } + return $this->getSession(); + } +} +namespace Illuminate\Http; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request as SymfonyRequest; +class FrameGuard implements HttpKernelInterface +{ + protected $app; + public function __construct(HttpKernelInterface $app) + { + $this->app = $app; + } + public function handle(SymfonyRequest $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + $response = $this->app->handle($request, $type, $catch); + $response->headers->set('X-Frame-Options', 'SAMEORIGIN', false); + return $response; + } +} +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Session\SessionInterface; +class Request +{ + const HEADER_CLIENT_IP = 'client_ip'; + const HEADER_CLIENT_HOST = 'client_host'; + const HEADER_CLIENT_PROTO = 'client_proto'; + const HEADER_CLIENT_PORT = 'client_port'; + protected static $trustedProxies = array(); + protected static $trustedHostPatterns = array(); + protected static $trustedHosts = array(); + protected static $trustedHeaders = array(self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO', self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT'); + protected static $httpMethodParameterOverride = false; + public $attributes; + public $request; + public $query; + public $server; + public $files; + public $cookies; + public $headers; + protected $content; + protected $languages; + protected $charsets; + protected $encodings; + protected $acceptableContentTypes; + protected $pathInfo; + protected $requestUri; + protected $baseUrl; + protected $basePath; + protected $method; + protected $format; + protected $session; + protected $locale; + protected $defaultLocale = 'en'; + protected static $formats; + protected static $requestFactory; + public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content); + } + public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + $this->request = new ParameterBag($request); + $this->query = new ParameterBag($query); + $this->attributes = new ParameterBag($attributes); + $this->cookies = new ParameterBag($cookies); + $this->files = new FileBag($files); + $this->server = new ServerBag($server); + $this->headers = new HeaderBag($this->server->getHeaders()); + $this->content = $content; + $this->languages = null; + $this->charsets = null; + $this->encodings = null; + $this->acceptableContentTypes = null; + $this->pathInfo = null; + $this->requestUri = null; + $this->baseUrl = null; + $this->basePath = null; + $this->method = null; + $this->format = null; + } + public static function createFromGlobals() + { + $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER); + if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded') && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))) { + parse_str($request->getContent(), $data); + $request->request = new ParameterBag($data); + } + return $request; + } + public static function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null) + { + $server = array_replace(array('SERVER_NAME' => 'localhost', 'SERVER_PORT' => 80, 'HTTP_HOST' => 'localhost', 'HTTP_USER_AGENT' => 'Symfony/2.X', 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5', 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 'REMOTE_ADDR' => '127.0.0.1', 'SCRIPT_NAME' => '', 'SCRIPT_FILENAME' => '', 'SERVER_PROTOCOL' => 'HTTP/1.1', 'REQUEST_TIME' => time()), $server); + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + $components = parse_url($uri); + if (isset($components['host'])) { + $server['SERVER_NAME'] = $components['host']; + $server['HTTP_HOST'] = $components['host']; + } + if (isset($components['scheme'])) { + if ('https' === $components['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + if (isset($components['port'])) { + $server['SERVER_PORT'] = $components['port']; + $server['HTTP_HOST'] = $server['HTTP_HOST'] . ':' . $components['port']; + } + if (isset($components['user'])) { + $server['PHP_AUTH_USER'] = $components['user']; + } + if (isset($components['pass'])) { + $server['PHP_AUTH_PW'] = $components['pass']; + } + if (!isset($components['path'])) { + $components['path'] = '/'; + } + switch (strtoupper($method)) { + case 'POST': + case 'PUT': + case 'DELETE': + if (!isset($server['CONTENT_TYPE'])) { + $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + } + case 'PATCH': + $request = $parameters; + $query = array(); + break; + default: + $request = array(); + $query = $parameters; + break; + } + $queryString = ''; + if (isset($components['query'])) { + parse_str(html_entity_decode($components['query']), $qs); + if ($query) { + $query = array_replace($qs, $query); + $queryString = http_build_query($query, '', '&'); + } else { + $query = $qs; + $queryString = $components['query']; + } + } elseif ($query) { + $queryString = http_build_query($query, '', '&'); + } + $server['REQUEST_URI'] = $components['path'] . ('' !== $queryString ? '?' . $queryString : ''); + $server['QUERY_STRING'] = $queryString; + return self::createRequestFromFactory($query, $request, array(), $cookies, $files, $server, $content); + } + public static function setFactory($callable) + { + self::$requestFactory = $callable; + } + public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) + { + $dup = clone $this; + if ($query !== null) { + $dup->query = new ParameterBag($query); + } + if ($request !== null) { + $dup->request = new ParameterBag($request); + } + if ($attributes !== null) { + $dup->attributes = new ParameterBag($attributes); + } + if ($cookies !== null) { + $dup->cookies = new ParameterBag($cookies); + } + if ($files !== null) { + $dup->files = new FileBag($files); + } + if ($server !== null) { + $dup->server = new ServerBag($server); + $dup->headers = new HeaderBag($dup->server->getHeaders()); + } + $dup->languages = null; + $dup->charsets = null; + $dup->encodings = null; + $dup->acceptableContentTypes = null; + $dup->pathInfo = null; + $dup->requestUri = null; + $dup->baseUrl = null; + $dup->basePath = null; + $dup->method = null; + $dup->format = null; + if (!$dup->get('_format') && $this->get('_format')) { + $dup->attributes->set('_format', $this->get('_format')); + } + if (!$dup->getRequestFormat(null)) { + $dup->setRequestFormat($format = $this->getRequestFormat(null)); + } + return $dup; + } + public function __clone() + { + $this->query = clone $this->query; + $this->request = clone $this->request; + $this->attributes = clone $this->attributes; + $this->cookies = clone $this->cookies; + $this->files = clone $this->files; + $this->server = clone $this->server; + $this->headers = clone $this->headers; + } + public function __toString() + { + return sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL')) . ' +' . $this->headers . ' +' . $this->getContent(); + } + public function overrideGlobals() + { + $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), null, '&'))); + $_GET = $this->query->all(); + $_POST = $this->request->all(); + $_SERVER = $this->server->all(); + $_COOKIE = $this->cookies->all(); + foreach ($this->headers->all() as $key => $value) { + $key = strtoupper(str_replace('-', '_', $key)); + if (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) { + $_SERVER[$key] = implode(', ', $value); + } else { + $_SERVER['HTTP_' . $key] = implode(', ', $value); + } + } + $request = array('g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE); + $requestOrder = ini_get('request_order') ?: ini_get('variables_order'); + $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp'; + $_REQUEST = array(); + foreach (str_split($requestOrder) as $order) { + $_REQUEST = array_merge($_REQUEST, $request[$order]); + } + } + public static function setTrustedProxies(array $proxies) + { + self::$trustedProxies = $proxies; + } + public static function getTrustedProxies() + { + return self::$trustedProxies; + } + public static function setTrustedHosts(array $hostPatterns) + { + self::$trustedHostPatterns = array_map(function ($hostPattern) { + return sprintf('{%s}i', str_replace('}', '\\}', $hostPattern)); + }, $hostPatterns); + self::$trustedHosts = array(); + } + public static function getTrustedHosts() + { + return self::$trustedHostPatterns; + } + public static function setTrustedHeaderName($key, $value) + { + if (!array_key_exists($key, self::$trustedHeaders)) { + throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key)); + } + self::$trustedHeaders[$key] = $value; + } + public static function getTrustedHeaderName($key) + { + if (!array_key_exists($key, self::$trustedHeaders)) { + throw new \InvalidArgumentException(sprintf('Unable to get the trusted header name for key "%s".', $key)); + } + return self::$trustedHeaders[$key]; + } + public static function normalizeQueryString($qs) + { + if ('' == $qs) { + return ''; + } + $parts = array(); + $order = array(); + foreach (explode('&', $qs) as $param) { + if ('' === $param || '=' === $param[0]) { + continue; + } + $keyValuePair = explode('=', $param, 2); + $parts[] = isset($keyValuePair[1]) ? rawurlencode(urldecode($keyValuePair[0])) . '=' . rawurlencode(urldecode($keyValuePair[1])) : rawurlencode(urldecode($keyValuePair[0])); + $order[] = urldecode($keyValuePair[0]); + } + array_multisort($order, SORT_ASC, $parts); + return implode('&', $parts); + } + public static function enableHttpMethodParameterOverride() + { + self::$httpMethodParameterOverride = true; + } + public static function getHttpMethodParameterOverride() + { + return self::$httpMethodParameterOverride; + } + public function get($key, $default = null, $deep = false) + { + if ($this !== ($result = $this->query->get($key, $this, $deep))) { + return $result; + } + if ($this !== ($result = $this->attributes->get($key, $this, $deep))) { + return $result; + } + if ($this !== ($result = $this->request->get($key, $this, $deep))) { + return $result; + } + return $default; + } + public function getSession() + { + return $this->session; + } + public function hasPreviousSession() + { + return $this->hasSession() && $this->cookies->has($this->session->getName()); + } + public function hasSession() + { + return null !== $this->session; + } + public function setSession(SessionInterface $session) + { + $this->session = $session; + } + public function getClientIps() + { + $ip = $this->server->get('REMOTE_ADDR'); + if (!self::$trustedProxies) { + return array($ip); + } + if (!self::$trustedHeaders[self::HEADER_CLIENT_IP] || !$this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP])) { + return array($ip); + } + $clientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP]))); + $clientIps[] = $ip; + $ip = $clientIps[0]; + foreach ($clientIps as $key => $clientIp) { + if (IpUtils::checkIp($clientIp, self::$trustedProxies)) { + unset($clientIps[$key]); + } + } + return $clientIps ? array_reverse($clientIps) : array($ip); + } + public function getClientIp() + { + $ipAddresses = $this->getClientIps(); + return $ipAddresses[0]; + } + public function getScriptName() + { + return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', '')); + } + public function getPathInfo() + { + if (null === $this->pathInfo) { + $this->pathInfo = $this->preparePathInfo(); + } + return $this->pathInfo; + } + public function getBasePath() + { + if (null === $this->basePath) { + $this->basePath = $this->prepareBasePath(); + } + return $this->basePath; + } + public function getBaseUrl() + { + if (null === $this->baseUrl) { + $this->baseUrl = $this->prepareBaseUrl(); + } + return $this->baseUrl; + } + public function getScheme() + { + return $this->isSecure() ? 'https' : 'http'; + } + public function getPort() + { + if (self::$trustedProxies) { + if (self::$trustedHeaders[self::HEADER_CLIENT_PORT] && ($port = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PORT]))) { + return $port; + } + if (self::$trustedHeaders[self::HEADER_CLIENT_PROTO] && 'https' === $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PROTO], 'http')) { + return 443; + } + } + if ($host = $this->headers->get('HOST')) { + if ($host[0] === '[') { + $pos = strpos($host, ':', strrpos($host, ']')); + } else { + $pos = strrpos($host, ':'); + } + if (false !== $pos) { + return intval(substr($host, $pos + 1)); + } + return 'https' === $this->getScheme() ? 443 : 80; + } + return $this->server->get('SERVER_PORT'); + } + public function getUser() + { + return $this->headers->get('PHP_AUTH_USER'); + } + public function getPassword() + { + return $this->headers->get('PHP_AUTH_PW'); + } + public function getUserInfo() + { + $userinfo = $this->getUser(); + $pass = $this->getPassword(); + if ('' != $pass) { + $userinfo .= ":{$pass}"; + } + return $userinfo; + } + public function getHttpHost() + { + $scheme = $this->getScheme(); + $port = $this->getPort(); + if ('http' == $scheme && $port == 80 || 'https' == $scheme && $port == 443) { + return $this->getHost(); + } + return $this->getHost() . ':' . $port; + } + public function getRequestUri() + { + if (null === $this->requestUri) { + $this->requestUri = $this->prepareRequestUri(); + } + return $this->requestUri; + } + public function getSchemeAndHttpHost() + { + return $this->getScheme() . '://' . $this->getHttpHost(); + } + public function getUri() + { + if (null !== ($qs = $this->getQueryString())) { + $qs = '?' . $qs; + } + return $this->getSchemeAndHttpHost() . $this->getBaseUrl() . $this->getPathInfo() . $qs; + } + public function getUriForPath($path) + { + return $this->getSchemeAndHttpHost() . $this->getBaseUrl() . $path; + } + public function getQueryString() + { + $qs = static::normalizeQueryString($this->server->get('QUERY_STRING')); + return '' === $qs ? null : $qs; + } + public function isSecure() + { + if (self::$trustedProxies && self::$trustedHeaders[self::HEADER_CLIENT_PROTO] && ($proto = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PROTO]))) { + return in_array(strtolower(current(explode(',', $proto))), array('https', 'on', 'ssl', '1')); + } + $https = $this->server->get('HTTPS'); + return !empty($https) && 'off' !== strtolower($https); + } + public function getHost() + { + if (self::$trustedProxies && self::$trustedHeaders[self::HEADER_CLIENT_HOST] && ($host = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_HOST]))) { + $elements = explode(',', $host); + $host = $elements[count($elements) - 1]; + } elseif (!($host = $this->headers->get('HOST'))) { + if (!($host = $this->server->get('SERVER_NAME'))) { + $host = $this->server->get('SERVER_ADDR', ''); + } + } + $host = strtolower(preg_replace('/:\\d+$/', '', trim($host))); + if ($host && '' !== preg_replace('/(?:^\\[)?[a-zA-Z0-9-:\\]_]+\\.?/', '', $host)) { + throw new \UnexpectedValueException(sprintf('Invalid Host "%s"', $host)); + } + if (count(self::$trustedHostPatterns) > 0) { + if (in_array($host, self::$trustedHosts)) { + return $host; + } + foreach (self::$trustedHostPatterns as $pattern) { + if (preg_match($pattern, $host)) { + self::$trustedHosts[] = $host; + return $host; + } + } + throw new \UnexpectedValueException(sprintf('Untrusted Host "%s"', $host)); + } + return $host; + } + public function setMethod($method) + { + $this->method = null; + $this->server->set('REQUEST_METHOD', $method); + } + public function getMethod() + { + if (null === $this->method) { + $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + if ('POST' === $this->method) { + if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) { + $this->method = strtoupper($method); + } elseif (self::$httpMethodParameterOverride) { + $this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST'))); + } + } + } + return $this->method; + } + public function getRealMethod() + { + return strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + } + public function getMimeType($format) + { + if (null === static::$formats) { + static::initializeFormats(); + } + return isset(static::$formats[$format]) ? static::$formats[$format][0] : null; + } + public function getFormat($mimeType) + { + if (false !== ($pos = strpos($mimeType, ';'))) { + $mimeType = substr($mimeType, 0, $pos); + } + if (null === static::$formats) { + static::initializeFormats(); + } + foreach (static::$formats as $format => $mimeTypes) { + if (in_array($mimeType, (array) $mimeTypes)) { + return $format; + } + } + } + public function setFormat($format, $mimeTypes) + { + if (null === static::$formats) { + static::initializeFormats(); + } + static::$formats[$format] = is_array($mimeTypes) ? $mimeTypes : array($mimeTypes); + } + public function getRequestFormat($default = 'html') + { + if (null === $this->format) { + $this->format = $this->get('_format', $default); + } + return $this->format; + } + public function setRequestFormat($format) + { + $this->format = $format; + } + public function getContentType() + { + return $this->getFormat($this->headers->get('CONTENT_TYPE')); + } + public function setDefaultLocale($locale) + { + $this->defaultLocale = $locale; + if (null === $this->locale) { + $this->setPhpDefaultLocale($locale); + } + } + public function getDefaultLocale() + { + return $this->defaultLocale; + } + public function setLocale($locale) + { + $this->setPhpDefaultLocale($this->locale = $locale); + } + public function getLocale() + { + return null === $this->locale ? $this->defaultLocale : $this->locale; + } + public function isMethod($method) + { + return $this->getMethod() === strtoupper($method); + } + public function isMethodSafe() + { + return in_array($this->getMethod(), array('GET', 'HEAD')); + } + public function getContent($asResource = false) + { + if (false === $this->content || true === $asResource && null !== $this->content) { + throw new \LogicException('getContent() can only be called once when using the resource return type.'); + } + if (true === $asResource) { + $this->content = false; + return fopen('php://input', 'rb'); + } + if (null === $this->content) { + $this->content = file_get_contents('php://input'); + } + return $this->content; + } + public function getETags() + { + return preg_split('/\\s*,\\s*/', $this->headers->get('if_none_match'), null, PREG_SPLIT_NO_EMPTY); + } + public function isNoCache() + { + return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); + } + public function getPreferredLanguage(array $locales = null) + { + $preferredLanguages = $this->getLanguages(); + if (empty($locales)) { + return isset($preferredLanguages[0]) ? $preferredLanguages[0] : null; + } + if (!$preferredLanguages) { + return $locales[0]; + } + $extendedPreferredLanguages = array(); + foreach ($preferredLanguages as $language) { + $extendedPreferredLanguages[] = $language; + if (false !== ($position = strpos($language, '_'))) { + $superLanguage = substr($language, 0, $position); + if (!in_array($superLanguage, $preferredLanguages)) { + $extendedPreferredLanguages[] = $superLanguage; + } + } + } + $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales)); + return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0]; + } + public function getLanguages() + { + if (null !== $this->languages) { + return $this->languages; + } + $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all(); + $this->languages = array(); + foreach (array_keys($languages) as $lang) { + if (strstr($lang, '-')) { + $codes = explode('-', $lang); + if ($codes[0] == 'i') { + if (count($codes) > 1) { + $lang = $codes[1]; + } + } else { + for ($i = 0, $max = count($codes); $i < $max; $i++) { + if ($i == 0) { + $lang = strtolower($codes[0]); + } else { + $lang .= '_' . strtoupper($codes[$i]); + } + } + } + } + $this->languages[] = $lang; + } + return $this->languages; + } + public function getCharsets() + { + if (null !== $this->charsets) { + return $this->charsets; + } + return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all()); + } + public function getEncodings() + { + if (null !== $this->encodings) { + return $this->encodings; + } + return $this->encodings = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all()); + } + public function getAcceptableContentTypes() + { + if (null !== $this->acceptableContentTypes) { + return $this->acceptableContentTypes; + } + return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all()); + } + public function isXmlHttpRequest() + { + return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); + } + protected function prepareRequestUri() + { + $requestUri = ''; + if ($this->headers->has('X_ORIGINAL_URL')) { + $requestUri = $this->headers->get('X_ORIGINAL_URL'); + $this->headers->remove('X_ORIGINAL_URL'); + $this->server->remove('HTTP_X_ORIGINAL_URL'); + $this->server->remove('UNENCODED_URL'); + $this->server->remove('IIS_WasUrlRewritten'); + } elseif ($this->headers->has('X_REWRITE_URL')) { + $requestUri = $this->headers->get('X_REWRITE_URL'); + $this->headers->remove('X_REWRITE_URL'); + } elseif ($this->server->get('IIS_WasUrlRewritten') == '1' && $this->server->get('UNENCODED_URL') != '') { + $requestUri = $this->server->get('UNENCODED_URL'); + $this->server->remove('UNENCODED_URL'); + $this->server->remove('IIS_WasUrlRewritten'); + } elseif ($this->server->has('REQUEST_URI')) { + $requestUri = $this->server->get('REQUEST_URI'); + $schemeAndHttpHost = $this->getSchemeAndHttpHost(); + if (strpos($requestUri, $schemeAndHttpHost) === 0) { + $requestUri = substr($requestUri, strlen($schemeAndHttpHost)); + } + } elseif ($this->server->has('ORIG_PATH_INFO')) { + $requestUri = $this->server->get('ORIG_PATH_INFO'); + if ('' != $this->server->get('QUERY_STRING')) { + $requestUri .= '?' . $this->server->get('QUERY_STRING'); + } + $this->server->remove('ORIG_PATH_INFO'); + } + $this->server->set('REQUEST_URI', $requestUri); + return $requestUri; + } + protected function prepareBaseUrl() + { + $filename = basename($this->server->get('SCRIPT_FILENAME')); + if (basename($this->server->get('SCRIPT_NAME')) === $filename) { + $baseUrl = $this->server->get('SCRIPT_NAME'); + } elseif (basename($this->server->get('PHP_SELF')) === $filename) { + $baseUrl = $this->server->get('PHP_SELF'); + } elseif (basename($this->server->get('ORIG_SCRIPT_NAME')) === $filename) { + $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); + } else { + $path = $this->server->get('PHP_SELF', ''); + $file = $this->server->get('SCRIPT_FILENAME', ''); + $segs = explode('/', trim($file, '/')); + $segs = array_reverse($segs); + $index = 0; + $last = count($segs); + $baseUrl = ''; + do { + $seg = $segs[$index]; + $baseUrl = '/' . $seg . $baseUrl; + ++$index; + } while ($last > $index && false !== ($pos = strpos($path, $baseUrl)) && 0 != $pos); + } + $requestUri = $this->getRequestUri(); + if ($baseUrl && false !== ($prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl))) { + return $prefix; + } + if ($baseUrl && false !== ($prefix = $this->getUrlencodedPrefix($requestUri, dirname($baseUrl)))) { + return rtrim($prefix, '/'); + } + $truncatedRequestUri = $requestUri; + if (false !== ($pos = strpos($requestUri, '?'))) { + $truncatedRequestUri = substr($requestUri, 0, $pos); + } + $basename = basename($baseUrl); + if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) { + return ''; + } + if (strlen($requestUri) >= strlen($baseUrl) && false !== ($pos = strpos($requestUri, $baseUrl)) && $pos !== 0) { + $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl)); + } + return rtrim($baseUrl, '/'); + } + protected function prepareBasePath() + { + $filename = basename($this->server->get('SCRIPT_FILENAME')); + $baseUrl = $this->getBaseUrl(); + if (empty($baseUrl)) { + return ''; + } + if (basename($baseUrl) === $filename) { + $basePath = dirname($baseUrl); + } else { + $basePath = $baseUrl; + } + if ('\\' === DIRECTORY_SEPARATOR) { + $basePath = str_replace('\\', '/', $basePath); + } + return rtrim($basePath, '/'); + } + protected function preparePathInfo() + { + $baseUrl = $this->getBaseUrl(); + if (null === ($requestUri = $this->getRequestUri())) { + return '/'; + } + $pathInfo = '/'; + if ($pos = strpos($requestUri, '?')) { + $requestUri = substr($requestUri, 0, $pos); + } + if (null !== $baseUrl && false === ($pathInfo = substr($requestUri, strlen($baseUrl)))) { + return '/'; + } elseif (null === $baseUrl) { + return $requestUri; + } + return (string) $pathInfo; + } + protected static function initializeFormats() + { + static::$formats = array('html' => array('text/html', 'application/xhtml+xml'), 'txt' => array('text/plain'), 'js' => array('application/javascript', 'application/x-javascript', 'text/javascript'), 'css' => array('text/css'), 'json' => array('application/json', 'application/x-json'), 'xml' => array('text/xml', 'application/xml', 'application/x-xml'), 'rdf' => array('application/rdf+xml'), 'atom' => array('application/atom+xml'), 'rss' => array('application/rss+xml')); + } + private function setPhpDefaultLocale($locale) + { + try { + if (class_exists('Locale', false)) { + \Locale::setDefault($locale); + } + } catch (\Exception $e) { + + } + } + private function getUrlencodedPrefix($string, $prefix) + { + if (0 !== strpos(rawurldecode($string), $prefix)) { + return false; + } + $len = strlen($prefix); + if (preg_match("#^(%[[:xdigit:]]{2}|.){{$len}}#", $string, $match)) { + return $match[0]; + } + return false; + } + private static function createRequestFromFactory(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + if (self::$requestFactory) { + $request = call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content); + if (!$request instanceof Request) { + throw new \LogicException('The Request factory must return an instance of Symfony\\Component\\HttpFoundation\\Request.'); + } + return $request; + } + return new static($query, $request, $attributes, $cookies, $files, $server, $content); + } +} +namespace Symfony\Component\HttpFoundation; + +class ParameterBag implements \IteratorAggregate, \Countable +{ + protected $parameters; + public function __construct(array $parameters = array()) + { + $this->parameters = $parameters; + } + public function all() + { + return $this->parameters; + } + public function keys() + { + return array_keys($this->parameters); + } + public function replace(array $parameters = array()) + { + $this->parameters = $parameters; + } + public function add(array $parameters = array()) + { + $this->parameters = array_replace($this->parameters, $parameters); + } + public function get($path, $default = null, $deep = false) + { + if (!$deep || false === ($pos = strpos($path, '['))) { + return array_key_exists($path, $this->parameters) ? $this->parameters[$path] : $default; + } + $root = substr($path, 0, $pos); + if (!array_key_exists($root, $this->parameters)) { + return $default; + } + $value = $this->parameters[$root]; + $currentKey = null; + for ($i = $pos, $c = strlen($path); $i < $c; $i++) { + $char = $path[$i]; + if ('[' === $char) { + if (null !== $currentKey) { + throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "[" at position %d.', $i)); + } + $currentKey = ''; + } elseif (']' === $char) { + if (null === $currentKey) { + throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "]" at position %d.', $i)); + } + if (!is_array($value) || !array_key_exists($currentKey, $value)) { + return $default; + } + $value = $value[$currentKey]; + $currentKey = null; + } else { + if (null === $currentKey) { + throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "%s" at position %d.', $char, $i)); + } + $currentKey .= $char; + } + } + if (null !== $currentKey) { + throw new \InvalidArgumentException(sprintf('Malformed path. Path must end with "]".')); + } + return $value; + } + public function set($key, $value) + { + $this->parameters[$key] = $value; + } + public function has($key) + { + return array_key_exists($key, $this->parameters); + } + public function remove($key) + { + unset($this->parameters[$key]); + } + public function getAlpha($key, $default = '', $deep = false) + { + return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default, $deep)); + } + public function getAlnum($key, $default = '', $deep = false) + { + return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default, $deep)); + } + public function getDigits($key, $default = '', $deep = false) + { + return str_replace(array('-', '+'), '', $this->filter($key, $default, $deep, FILTER_SANITIZE_NUMBER_INT)); + } + public function getInt($key, $default = 0, $deep = false) + { + return (int) $this->get($key, $default, $deep); + } + public function filter($key, $default = null, $deep = false, $filter = FILTER_DEFAULT, $options = array()) + { + $value = $this->get($key, $default, $deep); + if (!is_array($options) && $options) { + $options = array('flags' => $options); + } + if (is_array($value) && !isset($options['flags'])) { + $options['flags'] = FILTER_REQUIRE_ARRAY; + } + return filter_var($value, $filter, $options); + } + public function getIterator() + { + return new \ArrayIterator($this->parameters); + } + public function count() + { + return count($this->parameters); + } +} +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\File\UploadedFile; +class FileBag extends ParameterBag +{ + private static $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type'); + public function __construct(array $parameters = array()) + { + $this->replace($parameters); + } + public function replace(array $files = array()) + { + $this->parameters = array(); + $this->add($files); + } + public function set($key, $value) + { + if (!is_array($value) && !$value instanceof UploadedFile) { + throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.'); + } + parent::set($key, $this->convertFileInformation($value)); + } + public function add(array $files = array()) + { + foreach ($files as $key => $file) { + $this->set($key, $file); + } + } + protected function convertFileInformation($file) + { + if ($file instanceof UploadedFile) { + return $file; + } + $file = $this->fixPhpFilesArray($file); + if (is_array($file)) { + $keys = array_keys($file); + sort($keys); + if ($keys == self::$fileKeys) { + if (UPLOAD_ERR_NO_FILE == $file['error']) { + $file = null; + } else { + $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['size'], $file['error']); + } + } else { + $file = array_map(array($this, 'convertFileInformation'), $file); + } + } + return $file; + } + protected function fixPhpFilesArray($data) + { + if (!is_array($data)) { + return $data; + } + $keys = array_keys($data); + sort($keys); + if (self::$fileKeys != $keys || !isset($data['name']) || !is_array($data['name'])) { + return $data; + } + $files = $data; + foreach (self::$fileKeys as $k) { + unset($files[$k]); + } + foreach (array_keys($data['name']) as $key) { + $files[$key] = $this->fixPhpFilesArray(array('error' => $data['error'][$key], 'name' => $data['name'][$key], 'type' => $data['type'][$key], 'tmp_name' => $data['tmp_name'][$key], 'size' => $data['size'][$key])); + } + return $files; + } +} +namespace Symfony\Component\HttpFoundation; + +class ServerBag extends ParameterBag +{ + public function getHeaders() + { + $headers = array(); + $contentHeaders = array('CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true); + foreach ($this->parameters as $key => $value) { + if (0 === strpos($key, 'HTTP_')) { + $headers[substr($key, 5)] = $value; + } elseif (isset($contentHeaders[$key])) { + $headers[$key] = $value; + } + } + if (isset($this->parameters['PHP_AUTH_USER'])) { + $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER']; + $headers['PHP_AUTH_PW'] = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : ''; + } else { + $authorizationHeader = null; + if (isset($this->parameters['HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['HTTP_AUTHORIZATION']; + } elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION']; + } + if (null !== $authorizationHeader) { + if (0 === stripos($authorizationHeader, 'basic ')) { + $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2); + if (count($exploded) == 2) { + list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded; + } + } elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && 0 === stripos($authorizationHeader, 'digest ')) { + $headers['PHP_AUTH_DIGEST'] = $authorizationHeader; + $this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader; + } + } + } + if (isset($headers['PHP_AUTH_USER'])) { + $headers['AUTHORIZATION'] = 'Basic ' . base64_encode($headers['PHP_AUTH_USER'] . ':' . $headers['PHP_AUTH_PW']); + } elseif (isset($headers['PHP_AUTH_DIGEST'])) { + $headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST']; + } + return $headers; + } +} +namespace Symfony\Component\HttpFoundation; + +class HeaderBag implements \IteratorAggregate, \Countable +{ + protected $headers = array(); + protected $cacheControl = array(); + public function __construct(array $headers = array()) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + public function __toString() + { + if (!$this->headers) { + return ''; + } + $max = max(array_map('strlen', array_keys($this->headers))) + 1; + $content = ''; + ksort($this->headers); + foreach ($this->headers as $name => $values) { + $name = implode('-', array_map('ucfirst', explode('-', $name))); + foreach ($values as $value) { + $content .= sprintf("%-{$max}s %s\r\n", $name . ':', $value); + } + } + return $content; + } + public function all() + { + return $this->headers; + } + public function keys() + { + return array_keys($this->headers); + } + public function replace(array $headers = array()) + { + $this->headers = array(); + $this->add($headers); + } + public function add(array $headers) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + public function get($key, $default = null, $first = true) + { + $key = strtr(strtolower($key), '_', '-'); + if (!array_key_exists($key, $this->headers)) { + if (null === $default) { + return $first ? null : array(); + } + return $first ? $default : array($default); + } + if ($first) { + return count($this->headers[$key]) ? $this->headers[$key][0] : $default; + } + return $this->headers[$key]; + } + public function set($key, $values, $replace = true) + { + $key = strtr(strtolower($key), '_', '-'); + $values = array_values((array) $values); + if (true === $replace || !isset($this->headers[$key])) { + $this->headers[$key] = $values; + } else { + $this->headers[$key] = array_merge($this->headers[$key], $values); + } + if ('cache-control' === $key) { + $this->cacheControl = $this->parseCacheControl($values[0]); + } + } + public function has($key) + { + return array_key_exists(strtr(strtolower($key), '_', '-'), $this->headers); + } + public function contains($key, $value) + { + return in_array($value, $this->get($key, null, false)); + } + public function remove($key) + { + $key = strtr(strtolower($key), '_', '-'); + unset($this->headers[$key]); + if ('cache-control' === $key) { + $this->cacheControl = array(); + } + } + public function getDate($key, \DateTime $default = null) + { + if (null === ($value = $this->get($key))) { + return $default; + } + if (false === ($date = \DateTime::createFromFormat(DATE_RFC2822, $value))) { + throw new \RuntimeException(sprintf('The %s HTTP header is not parseable (%s).', $key, $value)); + } + return $date; + } + public function addCacheControlDirective($key, $value = true) + { + $this->cacheControl[$key] = $value; + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + public function hasCacheControlDirective($key) + { + return array_key_exists($key, $this->cacheControl); + } + public function getCacheControlDirective($key) + { + return array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null; + } + public function removeCacheControlDirective($key) + { + unset($this->cacheControl[$key]); + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + public function getIterator() + { + return new \ArrayIterator($this->headers); + } + public function count() + { + return count($this->headers); + } + protected function getCacheControlHeader() + { + $parts = array(); + ksort($this->cacheControl); + foreach ($this->cacheControl as $key => $value) { + if (true === $value) { + $parts[] = $key; + } else { + if (preg_match('#[^a-zA-Z0-9._-]#', $value)) { + $value = '"' . $value . '"'; + } + $parts[] = "{$key}={$value}"; + } + } + return implode(', ', $parts); + } + protected function parseCacheControl($header) + { + $cacheControl = array(); + preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\\s*(?:=(?:"([^"]*)"|([^ \\t",;]*)))?#', $header, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $cacheControl[strtolower($match[1])] = isset($match[3]) ? $match[3] : (isset($match[2]) ? $match[2] : true); + } + return $cacheControl; + } +} +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; +interface SessionInterface +{ + public function start(); + public function getId(); + public function setId($id); + public function getName(); + public function setName($name); + public function invalidate($lifetime = null); + public function migrate($destroy = false, $lifetime = null); + public function save(); + public function has($name); + public function get($name, $default = null); + public function set($name, $value); + public function all(); + public function replace(array $attributes); + public function remove($name); + public function clear(); + public function isStarted(); + public function registerBag(SessionBagInterface $bag); + public function getBag($name); + public function getMetadataBag(); +} +namespace Symfony\Component\HttpFoundation\Session; + +interface SessionBagInterface +{ + public function getName(); + public function initialize(array &$array); + public function getStorageKey(); + public function clear(); +} +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +interface AttributeBagInterface extends SessionBagInterface +{ + public function has($name); + public function get($name, $default = null); + public function set($name, $value); + public function all(); + public function replace(array $attributes); + public function remove($name); +} +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable +{ + private $name = 'attributes'; + private $storageKey; + protected $attributes = array(); + public function __construct($storageKey = '_sf2_attributes') + { + $this->storageKey = $storageKey; + } + public function getName() + { + return $this->name; + } + public function setName($name) + { + $this->name = $name; + } + public function initialize(array &$attributes) + { + $this->attributes =& $attributes; + } + public function getStorageKey() + { + return $this->storageKey; + } + public function has($name) + { + return array_key_exists($name, $this->attributes); + } + public function get($name, $default = null) + { + return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; + } + public function set($name, $value) + { + $this->attributes[$name] = $value; + } + public function all() + { + return $this->attributes; + } + public function replace(array $attributes) + { + $this->attributes = array(); + foreach ($attributes as $key => $value) { + $this->set($key, $value); + } + } + public function remove($name) + { + $retval = null; + if (array_key_exists($name, $this->attributes)) { + $retval = $this->attributes[$name]; + unset($this->attributes[$name]); + } + return $retval; + } + public function clear() + { + $return = $this->attributes; + $this->attributes = array(); + return $return; + } + public function getIterator() + { + return new \ArrayIterator($this->attributes); + } + public function count() + { + return count($this->attributes); + } +} +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +class MetadataBag implements SessionBagInterface +{ + const CREATED = 'c'; + const UPDATED = 'u'; + const LIFETIME = 'l'; + private $name = '__metadata'; + private $storageKey; + protected $meta = array(self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0); + private $lastUsed; + private $updateThreshold; + public function __construct($storageKey = '_sf2_meta', $updateThreshold = 0) + { + $this->storageKey = $storageKey; + $this->updateThreshold = $updateThreshold; + } + public function initialize(array &$array) + { + $this->meta =& $array; + if (isset($array[self::CREATED])) { + $this->lastUsed = $this->meta[self::UPDATED]; + $timeStamp = time(); + if ($timeStamp - $array[self::UPDATED] >= $this->updateThreshold) { + $this->meta[self::UPDATED] = $timeStamp; + } + } else { + $this->stampCreated(); + } + } + public function getLifetime() + { + return $this->meta[self::LIFETIME]; + } + public function stampNew($lifetime = null) + { + $this->stampCreated($lifetime); + } + public function getStorageKey() + { + return $this->storageKey; + } + public function getCreated() + { + return $this->meta[self::CREATED]; + } + public function getLastUsed() + { + return $this->lastUsed; + } + public function clear() + { + + } + public function getName() + { + return $this->name; + } + public function setName($name) + { + $this->name = $name; + } + private function stampCreated($lifetime = null) + { + $timeStamp = time(); + $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; + $this->meta[self::LIFETIME] = null === $lifetime ? ini_get('session.cookie_lifetime') : $lifetime; + } +} +namespace Symfony\Component\HttpFoundation; + +class AcceptHeaderItem +{ + private $value; + private $quality = 1.0; + private $index = 0; + private $attributes = array(); + public function __construct($value, array $attributes = array()) + { + $this->value = $value; + foreach ($attributes as $name => $value) { + $this->setAttribute($name, $value); + } + } + public static function fromString($itemValue) + { + $bits = preg_split('/\\s*(?:;*("[^"]+");*|;*(\'[^\']+\');*|;+)\\s*/', $itemValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + $value = array_shift($bits); + $attributes = array(); + $lastNullAttribute = null; + foreach ($bits as $bit) { + if (($start = substr($bit, 0, 1)) === ($end = substr($bit, -1)) && ($start === '"' || $start === '\'')) { + $attributes[$lastNullAttribute] = substr($bit, 1, -1); + } elseif ('=' === $end) { + $lastNullAttribute = $bit = substr($bit, 0, -1); + $attributes[$bit] = null; + } else { + $parts = explode('=', $bit); + $attributes[$parts[0]] = isset($parts[1]) && strlen($parts[1]) > 0 ? $parts[1] : ''; + } + } + return new self(($start = substr($value, 0, 1)) === ($end = substr($value, -1)) && ($start === '"' || $start === '\'') ? substr($value, 1, -1) : $value, $attributes); + } + public function __toString() + { + $string = $this->value . ($this->quality < 1 ? ';q=' . $this->quality : ''); + if (count($this->attributes) > 0) { + $string .= ';' . implode(';', array_map(function ($name, $value) { + return sprintf(preg_match('/[,;=]/', $value) ? '%s="%s"' : '%s=%s', $name, $value); + }, array_keys($this->attributes), $this->attributes)); + } + return $string; + } + public function setValue($value) + { + $this->value = $value; + return $this; + } + public function getValue() + { + return $this->value; + } + public function setQuality($quality) + { + $this->quality = $quality; + return $this; + } + public function getQuality() + { + return $this->quality; + } + public function setIndex($index) + { + $this->index = $index; + return $this; + } + public function getIndex() + { + return $this->index; + } + public function hasAttribute($name) + { + return isset($this->attributes[$name]); + } + public function getAttribute($name, $default = null) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + } + public function getAttributes() + { + return $this->attributes; + } + public function setAttribute($name, $value) + { + if ('q' === $name) { + $this->quality = (double) $value; + } else { + $this->attributes[$name] = (string) $value; + } + return $this; + } +} +namespace Symfony\Component\HttpFoundation; + +class AcceptHeader +{ + private $items = array(); + private $sorted = true; + public function __construct(array $items) + { + foreach ($items as $item) { + $this->add($item); + } + } + public static function fromString($headerValue) + { + $index = 0; + return new self(array_map(function ($itemValue) use(&$index) { + $item = AcceptHeaderItem::fromString($itemValue); + $item->setIndex($index++); + return $item; + }, preg_split('/\\s*(?:,*("[^"]+"),*|,*(\'[^\']+\'),*|,+)\\s*/', $headerValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE))); + } + public function __toString() + { + return implode(',', $this->items); + } + public function has($value) + { + return isset($this->items[$value]); + } + public function get($value) + { + return isset($this->items[$value]) ? $this->items[$value] : null; + } + public function add(AcceptHeaderItem $item) + { + $this->items[$item->getValue()] = $item; + $this->sorted = false; + return $this; + } + public function all() + { + $this->sort(); + return $this->items; + } + public function filter($pattern) + { + return new self(array_filter($this->items, function (AcceptHeaderItem $item) use($pattern) { + return preg_match($pattern, $item->getValue()); + })); + } + public function first() + { + $this->sort(); + return !empty($this->items) ? reset($this->items) : null; + } + private function sort() + { + if (!$this->sorted) { + uasort($this->items, function ($a, $b) { + $qA = $a->getQuality(); + $qB = $b->getQuality(); + if ($qA === $qB) { + return $a->getIndex() > $b->getIndex() ? 1 : -1; + } + return $qA > $qB ? -1 : 1; + }); + $this->sorted = true; + } + } +} +namespace Symfony\Component\Debug; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\Debug\Exception\OutOfMemoryException; +class ExceptionHandler +{ + private $debug; + private $charset; + private $handler; + private $caughtBuffer; + private $caughtLength; + public function __construct($debug = true, $charset = 'UTF-8') + { + $this->debug = $debug; + $this->charset = $charset; + } + public static function register($debug = true) + { + $handler = new static($debug); + set_exception_handler(array($handler, 'handle')); + return $handler; + } + public function setHandler($handler) + { + if (null !== $handler && !is_callable($handler)) { + throw new \LogicException('The exception handler must be a valid PHP callable.'); + } + $old = $this->handler; + $this->handler = $handler; + return $old; + } + public function handle(\Exception $exception) + { + if (null === $this->handler || $exception instanceof OutOfMemoryException) { + $this->failSafeHandle($exception); + return; + } + $caughtLength = $this->caughtLength = 0; + ob_start(array($this, 'catchOutput')); + $this->failSafeHandle($exception); + while (null === $this->caughtBuffer && ob_end_flush()) { + + } + if (isset($this->caughtBuffer[0])) { + ob_start(array($this, 'cleanOutput')); + echo $this->caughtBuffer; + $caughtLength = ob_get_length(); + } + $this->caughtBuffer = null; + try { + call_user_func($this->handler, $exception); + $this->caughtLength = $caughtLength; + } catch (\Exception $e) { + if (!$caughtLength) { + throw $exception; + } + } + } + private function failSafeHandle(\Exception $exception) + { + if (class_exists('Symfony\\Component\\HttpFoundation\\Response', false)) { + $response = $this->createResponse($exception); + $response->sendHeaders(); + $response->sendContent(); + } else { + $this->sendPhpResponse($exception); + } + } + public function sendPhpResponse($exception) + { + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + if (!headers_sent()) { + header(sprintf('HTTP/1.0 %s', $exception->getStatusCode())); + foreach ($exception->getHeaders() as $name => $value) { + header($name . ': ' . $value, false); + } + } + echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception)); + } + public function createResponse($exception) + { + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + return new Response($this->decorate($this->getContent($exception), $this->getStylesheet($exception)), $exception->getStatusCode(), $exception->getHeaders()); + } + public function getContent(FlattenException $exception) + { + switch ($exception->getStatusCode()) { + case 404: + $title = 'Sorry, the page you are looking for could not be found.'; + break; + default: + $title = 'Whoops, looks like something went wrong.'; + } + $content = ''; + if ($this->debug) { + try { + $count = count($exception->getAllPrevious()); + $total = $count + 1; + foreach ($exception->toArray() as $position => $e) { + $ind = $count - $position + 1; + $class = $this->abbrClass($e['class']); + $message = nl2br($e['message']); + $content .= sprintf('
      +

      %d/%d %s: %s

      +
      +
      +
        ', $ind, $total, $class, $message); + foreach ($e['trace'] as $trace) { + $content .= '
      1. '; + if ($trace['function']) { + $content .= sprintf('at %s%s%s(%s)', $this->abbrClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args'])); + } + if (isset($trace['file']) && isset($trace['line'])) { + if ($linkFormat = ini_get('xdebug.file_link_format')) { + $link = str_replace(array('%f', '%l'), array($trace['file'], $trace['line']), $linkFormat); + $content .= sprintf(' in %s line %s', $link, $trace['file'], $trace['line']); + } else { + $content .= sprintf(' in %s line %s', $trace['file'], $trace['line']); + } + } + $content .= '
      2. +'; + } + $content .= '
      +
      +'; + } + } catch (\Exception $e) { + if ($this->debug) { + $title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($e), $e->getMessage()); + } else { + $title = 'Whoops, looks like something went wrong.'; + } + } + } + return "
      \n

      {$title}

      \n {$content}\n
      "; + } + public function getStylesheet(FlattenException $exception) + { + return ' .sf-reset { font: 11px Verdana, Arial, sans-serif; color: #333 } + .sf-reset .clear { clear:both; height:0; font-size:0; line-height:0; } + .sf-reset .clear_fix:after { display:block; height:0; clear:both; visibility:hidden; } + .sf-reset .clear_fix { display:inline-block; } + .sf-reset * html .clear_fix { height:1%; } + .sf-reset .clear_fix { display:block; } + .sf-reset, .sf-reset .block { margin: auto } + .sf-reset abbr { border-bottom: 1px dotted #000; cursor: help; } + .sf-reset p { font-size:14px; line-height:20px; color:#868686; padding-bottom:20px } + .sf-reset strong { font-weight:bold; } + .sf-reset a { color:#6c6159; } + .sf-reset a img { border:none; } + .sf-reset a:hover { text-decoration:underline; } + .sf-reset em { font-style:italic; } + .sf-reset h1, .sf-reset h2 { font: 20px Georgia, "Times New Roman", Times, serif } + .sf-reset h2 span { background-color: #fff; color: #333; padding: 6px; float: left; margin-right: 10px; } + .sf-reset .traces li { font-size:12px; padding: 2px 4px; list-style-type:decimal; margin-left:20px; } + .sf-reset .block { background-color:#FFFFFF; padding:10px 28px; margin-bottom:20px; + -webkit-border-bottom-right-radius: 16px; + -webkit-border-bottom-left-radius: 16px; + -moz-border-radius-bottomright: 16px; + -moz-border-radius-bottomleft: 16px; + border-bottom-right-radius: 16px; + border-bottom-left-radius: 16px; + border-bottom:1px solid #ccc; + border-right:1px solid #ccc; + border-left:1px solid #ccc; + } + .sf-reset .block_exception { background-color:#ddd; color: #333; padding:20px; + -webkit-border-top-left-radius: 16px; + -webkit-border-top-right-radius: 16px; + -moz-border-radius-topleft: 16px; + -moz-border-radius-topright: 16px; + border-top-left-radius: 16px; + border-top-right-radius: 16px; + border-top:1px solid #ccc; + border-right:1px solid #ccc; + border-left:1px solid #ccc; + overflow: hidden; + word-wrap: break-word; + } + .sf-reset li a { background:none; color:#868686; text-decoration:none; } + .sf-reset li a:hover { background:none; color:#313131; text-decoration:underline; } + .sf-reset ol { padding: 10px 0; } + .sf-reset h1 { background-color:#FFFFFF; padding: 15px 28px; margin-bottom: 20px; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + border: 1px solid #ccc; + }'; + } + private function decorate($content, $css) + { + return "\n\n \n \n \n \n \n \n {$content}\n \n"; + } + private function abbrClass($class) + { + $parts = explode('\\', $class); + return sprintf('%s', $class, array_pop($parts)); + } + private function formatArgs(array $args) + { + if (PHP_VERSION_ID >= 50400) { + $flags = ENT_QUOTES | ENT_SUBSTITUTE; + } else { + $flags = ENT_QUOTES; + } + $result = array(); + foreach ($args as $key => $item) { + if ('object' === $item[0]) { + $formattedValue = sprintf('object(%s)', $this->abbrClass($item[1])); + } elseif ('array' === $item[0]) { + $formattedValue = sprintf('array(%s)', is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); + } elseif ('string' === $item[0]) { + $formattedValue = sprintf('\'%s\'', htmlspecialchars($item[1], $flags, $this->charset)); + } elseif ('null' === $item[0]) { + $formattedValue = 'null'; + } elseif ('boolean' === $item[0]) { + $formattedValue = '' . strtolower(var_export($item[1], true)) . ''; + } elseif ('resource' === $item[0]) { + $formattedValue = 'resource'; + } else { + $formattedValue = str_replace(' +', '', var_export(htmlspecialchars((string) $item[1], $flags, $this->charset), true)); + } + $result[] = is_int($key) ? $formattedValue : sprintf('\'%s\' => %s', $key, $formattedValue); + } + return implode(', ', $result); + } + public function catchOutput($buffer) + { + $this->caughtBuffer = $buffer; + return ''; + } + public function cleanOutput($buffer) + { + if ($this->caughtLength) { + $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength); + if (isset($cleanBuffer[0])) { + $buffer = $cleanBuffer; + } + } + return $buffer; + } +} +namespace Illuminate\Support; + +use ReflectionClass; +abstract class ServiceProvider +{ + protected $app; + protected $defer = false; + public function __construct($app) + { + $this->app = $app; + } + public function boot() + { + + } + public abstract function register(); + public function package($package, $namespace = null, $path = null) + { + $namespace = $this->getPackageNamespace($package, $namespace); + $path = $path ?: $this->guessPackagePath(); + $config = $path . '/config'; + if ($this->app['files']->isDirectory($config)) { + $this->app['config']->package($package, $config, $namespace); + } + $lang = $path . '/lang'; + if ($this->app['files']->isDirectory($lang)) { + $this->app['translator']->addNamespace($namespace, $lang); + } + $appView = $this->getAppViewPath($package); + if ($this->app['files']->isDirectory($appView)) { + $this->app['view']->addNamespace($namespace, $appView); + } + $view = $path . '/views'; + if ($this->app['files']->isDirectory($view)) { + $this->app['view']->addNamespace($namespace, $view); + } + } + public function guessPackagePath() + { + $path = (new ReflectionClass($this))->getFileName(); + return realpath(dirname($path) . '/../../'); + } + protected function getPackageNamespace($package, $namespace) + { + if (is_null($namespace)) { + list($vendor, $namespace) = explode('/', $package); + } + return $namespace; + } + public function commands($commands) + { + $commands = is_array($commands) ? $commands : func_get_args(); + $events = $this->app['events']; + $events->listen('artisan.start', function ($artisan) use($commands) { + $artisan->resolveCommands($commands); + }); + } + protected function getAppViewPath($package) + { + return $this->app['path'] . "/views/packages/{$package}"; + } + public function provides() + { + return array(); + } + public function when() + { + return array(); + } + public function isDeferred() + { + return $this->defer; + } +} +namespace Illuminate\Exception; + +use Whoops\Run; +use Whoops\Handler\PrettyPageHandler; +use Whoops\Handler\JsonResponseHandler; +use Illuminate\Support\ServiceProvider; +class ExceptionServiceProvider extends ServiceProvider +{ + public function register() + { + $this->registerDisplayers(); + $this->registerHandler(); + } + protected function registerDisplayers() + { + $this->registerPlainDisplayer(); + $this->registerDebugDisplayer(); + } + protected function registerHandler() + { + $this->app['exception'] = $this->app->share(function ($app) { + return new Handler($app, $app['exception.plain'], $app['exception.debug']); + }); + } + protected function registerPlainDisplayer() + { + $this->app['exception.plain'] = $this->app->share(function ($app) { + if ($app->runningInConsole()) { + return $app['exception.debug']; + } else { + return new PlainDisplayer(); + } + }); + } + protected function registerDebugDisplayer() + { + $this->registerWhoops(); + $this->app['exception.debug'] = $this->app->share(function ($app) { + return new WhoopsDisplayer($app['whoops'], $app->runningInConsole()); + }); + } + protected function registerWhoops() + { + $this->registerWhoopsHandler(); + $this->app['whoops'] = $this->app->share(function ($app) { + with($whoops = new Run())->allowQuit(false); + $whoops->writeToOutput(false); + return $whoops->pushHandler($app['whoops.handler']); + }); + } + protected function registerWhoopsHandler() + { + if ($this->shouldReturnJson()) { + $this->app['whoops.handler'] = $this->app->share(function () { + return new JsonResponseHandler(); + }); + } else { + $this->registerPrettyWhoopsHandler(); + } + } + protected function shouldReturnJson() + { + return $this->app->runningInConsole() || $this->requestWantsJson(); + } + protected function requestWantsJson() + { + return $this->app['request']->ajax() || $this->app['request']->wantsJson(); + } + protected function registerPrettyWhoopsHandler() + { + $this->app['whoops.handler'] = $this->app->share(function () { + with($handler = new PrettyPageHandler())->setEditor('sublime'); + return $handler; + }); + } +} +namespace Illuminate\Routing; + +use Illuminate\Support\ServiceProvider; +class RoutingServiceProvider extends ServiceProvider +{ + public function register() + { + $this->registerRouter(); + $this->registerUrlGenerator(); + $this->registerRedirector(); + } + protected function registerRouter() + { + $this->app['router'] = $this->app->share(function ($app) { + $router = new Router($app['events'], $app); + if ($app['env'] == 'testing') { + $router->disableFilters(); + } + return $router; + }); + } + protected function registerUrlGenerator() + { + $this->app['url'] = $this->app->share(function ($app) { + $routes = $app['router']->getRoutes(); + return new UrlGenerator($routes, $app->rebinding('request', function ($app, $request) { + $app['url']->setRequest($request); + })); + }); + } + protected function registerRedirector() + { + $this->app['redirect'] = $this->app->share(function ($app) { + $redirector = new Redirector($app['url']); + if (isset($app['session.store'])) { + $redirector->setSession($app['session.store']); + } + return $redirector; + }); + } +} +namespace Illuminate\Events; + +use Illuminate\Support\ServiceProvider; +class EventServiceProvider extends ServiceProvider +{ + public function register() + { + $this->app['events'] = $this->app->share(function ($app) { + return new Dispatcher($app); + }); + } +} +namespace Illuminate\Support\Facades; + +use Mockery\MockInterface; +abstract class Facade +{ + protected static $app; + protected static $resolvedInstance; + public static function swap($instance) + { + static::$resolvedInstance[static::getFacadeAccessor()] = $instance; + static::$app->instance(static::getFacadeAccessor(), $instance); + } + public static function shouldReceive() + { + $name = static::getFacadeAccessor(); + if (static::isMock()) { + $mock = static::$resolvedInstance[$name]; + } else { + $mock = static::createFreshMockInstance($name); + } + return call_user_func_array(array($mock, 'shouldReceive'), func_get_args()); + } + protected static function createFreshMockInstance($name) + { + static::$resolvedInstance[$name] = $mock = static::createMockByName($name); + if (isset(static::$app)) { + static::$app->instance($name, $mock); + } + return $mock; + } + protected static function createMockByName($name) + { + $class = static::getMockableClass($name); + return $class ? \Mockery::mock($class) : \Mockery::mock(); + } + protected static function isMock() + { + $name = static::getFacadeAccessor(); + return isset(static::$resolvedInstance[$name]) && static::$resolvedInstance[$name] instanceof MockInterface; + } + protected static function getMockableClass() + { + if ($root = static::getFacadeRoot()) { + return get_class($root); + } + } + public static function getFacadeRoot() + { + return static::resolveFacadeInstance(static::getFacadeAccessor()); + } + protected static function getFacadeAccessor() + { + throw new \RuntimeException('Facade does not implement getFacadeAccessor method.'); + } + protected static function resolveFacadeInstance($name) + { + if (is_object($name)) { + return $name; + } + if (isset(static::$resolvedInstance[$name])) { + return static::$resolvedInstance[$name]; + } + return static::$resolvedInstance[$name] = static::$app[$name]; + } + public static function clearResolvedInstance($name) + { + unset(static::$resolvedInstance[$name]); + } + public static function clearResolvedInstances() + { + static::$resolvedInstance = array(); + } + public static function getFacadeApplication() + { + return static::$app; + } + public static function setFacadeApplication($app) + { + static::$app = $app; + } + public static function __callStatic($method, $args) + { + $instance = static::getFacadeRoot(); + switch (count($args)) { + case 0: + return $instance->{$method}(); + case 1: + return $instance->{$method}($args[0]); + case 2: + return $instance->{$method}($args[0], $args[1]); + case 3: + return $instance->{$method}($args[0], $args[1], $args[2]); + case 4: + return $instance->{$method}($args[0], $args[1], $args[2], $args[3]); + default: + return call_user_func_array(array($instance, $method), $args); + } + } +} +namespace Illuminate\Support\Traits; + +trait MacroableTrait +{ + protected static $macros = array(); + public static function macro($name, callable $macro) + { + static::$macros[$name] = $macro; + } + public static function hasMacro($name) + { + return isset(static::$macros[$name]); + } + public static function __callStatic($method, $parameters) + { + if (static::hasMacro($method)) { + return call_user_func_array(static::$macros[$method], $parameters); + } + throw new \BadMethodCallException("Method {$method} does not exist."); + } + public function __call($method, $parameters) + { + return static::__callStatic($method, $parameters); + } +} +namespace Illuminate\Support; + +use Closure; +use Illuminate\Support\Traits\MacroableTrait; +class Arr +{ + use MacroableTrait; + public static function add($array, $key, $value) + { + if (is_null(static::get($array, $key))) { + static::set($array, $key, $value); + } + return $array; + } + public static function build($array, Closure $callback) + { + $results = array(); + foreach ($array as $key => $value) { + list($innerKey, $innerValue) = call_user_func($callback, $key, $value); + $results[$innerKey] = $innerValue; + } + return $results; + } + public static function divide($array) + { + return array(array_keys($array), array_values($array)); + } + public static function dot($array, $prepend = '') + { + $results = array(); + foreach ($array as $key => $value) { + if (is_array($value)) { + $results = array_merge($results, static::dot($value, $prepend . $key . '.')); + } else { + $results[$prepend . $key] = $value; + } + } + return $results; + } + public static function except($array, $keys) + { + return array_diff_key($array, array_flip((array) $keys)); + } + public static function fetch($array, $key) + { + foreach (explode('.', $key) as $segment) { + $results = array(); + foreach ($array as $value) { + $value = (array) $value; + $results[] = $value[$segment]; + } + $array = array_values($results); + } + return array_values($results); + } + public static function first($array, $callback, $default = null) + { + foreach ($array as $key => $value) { + if (call_user_func($callback, $key, $value)) { + return $value; + } + } + return value($default); + } + public static function last($array, $callback, $default = null) + { + return static::first(array_reverse($array), $callback, $default); + } + public static function flatten($array) + { + $return = array(); + array_walk_recursive($array, function ($x) use(&$return) { + $return[] = $x; + }); + return $return; + } + public static function forget(&$array, $keys) + { + $original =& $array; + foreach ((array) $keys as $key) { + $parts = explode('.', $key); + while (count($parts) > 1) { + $part = array_shift($parts); + if (isset($array[$part]) && is_array($array[$part])) { + $array =& $array[$part]; + } + } + unset($array[array_shift($parts)]); + $array =& $original; + } + } + public static function get($array, $key, $default = null) + { + if (is_null($key)) { + return $array; + } + if (isset($array[$key])) { + return $array[$key]; + } + foreach (explode('.', $key) as $segment) { + if (!is_array($array) || !array_key_exists($segment, $array)) { + return value($default); + } + $array = $array[$segment]; + } + return $array; + } + public static function only($array, $keys) + { + return array_intersect_key($array, array_flip((array) $keys)); + } + public static function pluck($array, $value, $key = null) + { + $results = array(); + foreach ($array as $item) { + $itemValue = is_object($item) ? $item->{$value} : $item[$value]; + if (is_null($key)) { + $results[] = $itemValue; + } else { + $itemKey = is_object($item) ? $item->{$key} : $item[$key]; + $results[$itemKey] = $itemValue; + } + } + return $results; + } + public static function pull(&$array, $key, $default = null) + { + $value = static::get($array, $key, $default); + static::forget($array, $key); + return $value; + } + public static function set(&$array, $key, $value) + { + if (is_null($key)) { + return $array = $value; + } + $keys = explode('.', $key); + while (count($keys) > 1) { + $key = array_shift($keys); + if (!isset($array[$key]) || !is_array($array[$key])) { + $array[$key] = array(); + } + $array =& $array[$key]; + } + $array[array_shift($keys)] = $value; + return $array; + } + public static function sort($array, Closure $callback) + { + return Collection::make($array)->sortBy($callback)->all(); + } + public static function where($array, Closure $callback) + { + $filtered = array(); + foreach ($array as $key => $value) { + if (call_user_func($callback, $key, $value)) { + $filtered[$key] = $value; + } + } + return $filtered; + } +} +namespace Illuminate\Support; + +use Patchwork\Utf8; +use Illuminate\Support\Traits\MacroableTrait; +class Str +{ + use MacroableTrait; + public static function ascii($value) + { + return Utf8::toAscii($value); + } + public static function camel($value) + { + return lcfirst(static::studly($value)); + } + public static function contains($haystack, $needles) + { + foreach ((array) $needles as $needle) { + if ($needle != '' && strpos($haystack, $needle) !== false) { + return true; + } + } + return false; + } + public static function endsWith($haystack, $needles) + { + foreach ((array) $needles as $needle) { + if ((string) $needle === substr($haystack, -strlen($needle))) { + return true; + } + } + return false; + } + public static function finish($value, $cap) + { + $quoted = preg_quote($cap, '/'); + return preg_replace('/(?:' . $quoted . ')+$/', '', $value) . $cap; + } + public static function is($pattern, $value) + { + if ($pattern == $value) { + return true; + } + $pattern = preg_quote($pattern, '#'); + $pattern = str_replace('\\*', '.*', $pattern) . '\\z'; + return (bool) preg_match('#^' . $pattern . '#', $value); + } + public static function length($value) + { + return mb_strlen($value); + } + public static function limit($value, $limit = 100, $end = '...') + { + if (mb_strlen($value) <= $limit) { + return $value; + } + return rtrim(mb_substr($value, 0, $limit, 'UTF-8')) . $end; + } + public static function lower($value) + { + return mb_strtolower($value); + } + public static function words($value, $words = 100, $end = '...') + { + preg_match('/^\\s*+(?:\\S++\\s*+){1,' . $words . '}/u', $value, $matches); + if (!isset($matches[0]) || strlen($value) === strlen($matches[0])) { + return $value; + } + return rtrim($matches[0]) . $end; + } + public static function parseCallback($callback, $default) + { + return static::contains($callback, '@') ? explode('@', $callback, 2) : array($callback, $default); + } + public static function plural($value, $count = 2) + { + return Pluralizer::plural($value, $count); + } + public static function random($length = 16) + { + if (function_exists('openssl_random_pseudo_bytes')) { + $bytes = openssl_random_pseudo_bytes($length * 2); + if ($bytes === false) { + throw new \RuntimeException('Unable to generate random string.'); + } + return substr(str_replace(array('/', '+', '='), '', base64_encode($bytes)), 0, $length); + } + return static::quickRandom($length); + } + public static function quickRandom($length = 16) + { + $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + return substr(str_shuffle(str_repeat($pool, 5)), 0, $length); + } + public static function upper($value) + { + return mb_strtoupper($value); + } + public static function title($value) + { + return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); + } + public static function singular($value) + { + return Pluralizer::singular($value); + } + public static function slug($title, $separator = '-') + { + $title = static::ascii($title); + $flip = $separator == '-' ? '_' : '-'; + $title = preg_replace('![' . preg_quote($flip) . ']+!u', $separator, $title); + $title = preg_replace('![^' . preg_quote($separator) . '\\pL\\pN\\s]+!u', '', mb_strtolower($title)); + $title = preg_replace('![' . preg_quote($separator) . '\\s]+!u', $separator, $title); + return trim($title, $separator); + } + public static function snake($value, $delimiter = '_') + { + if (ctype_lower($value)) { + return $value; + } + $replace = '$1' . $delimiter . '$2'; + return strtolower(preg_replace('/(.)([A-Z])/', $replace, $value)); + } + public static function startsWith($haystack, $needles) + { + foreach ((array) $needles as $needle) { + if ($needle != '' && strpos($haystack, $needle) === 0) { + return true; + } + } + return false; + } + public static function studly($value) + { + $value = ucwords(str_replace(array('-', '_'), ' ', $value)); + return str_replace(' ', '', $value); + } +} +namespace Symfony\Component\Debug; + +use Psr\Log\LogLevel; +use Psr\Log\LoggerInterface; +use Symfony\Component\Debug\Exception\ContextErrorException; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\Exception\OutOfMemoryException; +use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler; +use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler; +use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler; +use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface; +class ErrorHandler +{ + const TYPE_DEPRECATION = -100; + private $levels = array(E_WARNING => 'Warning', E_NOTICE => 'Notice', E_USER_ERROR => 'User Error', E_USER_WARNING => 'User Warning', E_USER_NOTICE => 'User Notice', E_STRICT => 'Runtime Notice', E_RECOVERABLE_ERROR => 'Catchable Fatal Error', E_DEPRECATED => 'Deprecated', E_USER_DEPRECATED => 'User Deprecated', E_ERROR => 'Error', E_CORE_ERROR => 'Core Error', E_COMPILE_ERROR => 'Compile Error', E_PARSE => 'Parse Error'); + private $level; + private $reservedMemory; + private $displayErrors; + private static $loggers = array(); + private static $stackedErrors = array(); + private static $stackedErrorLevels = array(); + public static function register($level = null, $displayErrors = true) + { + $handler = new static(); + $handler->setLevel($level); + $handler->setDisplayErrors($displayErrors); + ini_set('display_errors', 0); + set_error_handler(array($handler, 'handle')); + register_shutdown_function(array($handler, 'handleFatal')); + $handler->reservedMemory = str_repeat('x', 10240); + return $handler; + } + public function setLevel($level) + { + $this->level = null === $level ? error_reporting() : $level; + } + public function setDisplayErrors($displayErrors) + { + $this->displayErrors = $displayErrors; + } + public static function setLogger(LoggerInterface $logger, $channel = 'deprecation') + { + self::$loggers[$channel] = $logger; + } + public function handle($level, $message, $file = 'unknown', $line = 0, $context = array()) + { + if ($level & (E_USER_DEPRECATED | E_DEPRECATED)) { + if (isset(self::$loggers['deprecation'])) { + if (self::$stackedErrorLevels) { + self::$stackedErrors[] = func_get_args(); + } else { + if (PHP_VERSION_ID < 50400) { + $stack = array_map(function ($row) { + unset($row['args']); + return $row; + }, array_slice(debug_backtrace(false), 0, 10)); + } else { + $stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10); + } + self::$loggers['deprecation']->warning($message, array('type' => self::TYPE_DEPRECATION, 'stack' => $stack)); + } + return true; + } + } elseif ($this->displayErrors && error_reporting() & $level && $this->level & $level) { + if (PHP_VERSION_ID < 50400 && isset($context['GLOBALS']) && is_array($context)) { + $c = $context; + unset($c['GLOBALS'], $context); + $context = $c; + } + $exception = sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line); + if ($context && class_exists('Symfony\\Component\\Debug\\Exception\\ContextErrorException')) { + $exception = new ContextErrorException($exception, 0, $level, $file, $line, $context); + } else { + $exception = new \ErrorException($exception, 0, $level, $file, $line); + } + if (PHP_VERSION_ID <= 50407 && (PHP_VERSION_ID >= 50400 || PHP_VERSION_ID <= 50317)) { + $exception->errorHandlerCanary = new ErrorHandlerCanary(); + } + throw $exception; + } + if (isset(self::$loggers['scream']) && !(error_reporting() & $level)) { + if (self::$stackedErrorLevels) { + self::$stackedErrors[] = func_get_args(); + } else { + switch ($level) { + case E_USER_ERROR: + case E_RECOVERABLE_ERROR: + $logLevel = LogLevel::ERROR; + break; + case E_WARNING: + case E_USER_WARNING: + $logLevel = LogLevel::WARNING; + break; + default: + $logLevel = LogLevel::NOTICE; + break; + } + self::$loggers['scream']->log($logLevel, $message, array('type' => $level, 'file' => $file, 'line' => $line, 'scream' => error_reporting())); + } + } + return false; + } + public static function stackErrors() + { + self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR); + } + public static function unstackErrors() + { + $level = array_pop(self::$stackedErrorLevels); + if (null !== $level) { + $e = error_reporting($level); + if ($e !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) { + error_reporting($e); + } + } + if (empty(self::$stackedErrorLevels)) { + $errors = self::$stackedErrors; + self::$stackedErrors = array(); + $errorHandler = set_error_handler('var_dump'); + restore_error_handler(); + if ($errorHandler) { + foreach ($errors as $e) { + call_user_func_array($errorHandler, $e); + } + } + } + } + public function handleFatal() + { + $this->reservedMemory = ''; + gc_collect_cycles(); + $error = error_get_last(); + $exceptionHandler = set_exception_handler('var_dump'); + restore_exception_handler(); + try { + while (self::$stackedErrorLevels) { + static::unstackErrors(); + } + } catch (\Exception $exception) { + if ($exceptionHandler) { + call_user_func($exceptionHandler, $exception); + return; + } + if ($this->displayErrors) { + ini_set('display_errors', 1); + } + throw $exception; + } + if (!$error || !$this->level || !($error['type'] & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_PARSE))) { + return; + } + if (isset(self::$loggers['emergency'])) { + $fatal = array('type' => $error['type'], 'file' => $error['file'], 'line' => $error['line']); + self::$loggers['emergency']->emergency($error['message'], $fatal); + } + if ($this->displayErrors && $exceptionHandler) { + $this->handleFatalError($exceptionHandler, $error); + } + } + protected function getFatalErrorHandlers() + { + return array(new UndefinedFunctionFatalErrorHandler(), new UndefinedMethodFatalErrorHandler(), new ClassNotFoundFatalErrorHandler()); + } + private function handleFatalError($exceptionHandler, array $error) + { + set_error_handler('var_dump', 0); + ini_set('display_errors', 1); + $level = isset($this->levels[$error['type']]) ? $this->levels[$error['type']] : $error['type']; + $message = sprintf('%s: %s in %s line %d', $level, $error['message'], $error['file'], $error['line']); + if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) { + $exception = new OutOfMemoryException($message, 0, $error['type'], $error['file'], $error['line'], 3, false); + } else { + $exception = new FatalErrorException($message, 0, $error['type'], $error['file'], $error['line'], 3, true); + foreach ($this->getFatalErrorHandlers() as $handler) { + if ($e = $handler->handleError($error, $exception)) { + $exception = $e; + break; + } + } + } + try { + call_user_func($exceptionHandler, $exception); + } catch (\Exception $e) { + throw $exception; + } + } +} +class ErrorHandlerCanary +{ + private static $displayErrors = null; + public function __construct() + { + if (null === self::$displayErrors) { + self::$displayErrors = ini_set('display_errors', 1); + } + } + public function __destruct() + { + if (null !== self::$displayErrors) { + ini_set('display_errors', self::$displayErrors); + self::$displayErrors = null; + } + } +} +namespace Symfony\Component\HttpKernel\Debug; + +use Symfony\Component\Debug\ErrorHandler as DebugErrorHandler; +class ErrorHandler extends DebugErrorHandler +{ + +} +namespace Illuminate\Config; + +use Closure; +use ArrayAccess; +use Illuminate\Support\NamespacedItemResolver; +class Repository extends NamespacedItemResolver implements ArrayAccess +{ + protected $loader; + protected $environment; + protected $items = array(); + protected $packages = array(); + protected $afterLoad = array(); + public function __construct(LoaderInterface $loader, $environment) + { + $this->loader = $loader; + $this->environment = $environment; + } + public function has($key) + { + $default = microtime(true); + return $this->get($key, $default) !== $default; + } + public function hasGroup($key) + { + list($namespace, $group, $item) = $this->parseKey($key); + return $this->loader->exists($group, $namespace); + } + public function get($key, $default = null) + { + list($namespace, $group, $item) = $this->parseKey($key); + $collection = $this->getCollection($group, $namespace); + $this->load($group, $namespace, $collection); + return array_get($this->items[$collection], $item, $default); + } + public function set($key, $value) + { + list($namespace, $group, $item) = $this->parseKey($key); + $collection = $this->getCollection($group, $namespace); + $this->load($group, $namespace, $collection); + if (is_null($item)) { + $this->items[$collection] = $value; + } else { + array_set($this->items[$collection], $item, $value); + } + } + protected function load($group, $namespace, $collection) + { + $env = $this->environment; + if (isset($this->items[$collection])) { + return; + } + $items = $this->loader->load($env, $group, $namespace); + if (isset($this->afterLoad[$namespace])) { + $items = $this->callAfterLoad($namespace, $group, $items); + } + $this->items[$collection] = $items; + } + protected function callAfterLoad($namespace, $group, $items) + { + $callback = $this->afterLoad[$namespace]; + return call_user_func($callback, $this, $group, $items); + } + protected function parseNamespacedSegments($key) + { + list($namespace, $item) = explode('::', $key); + if (in_array($namespace, $this->packages)) { + return $this->parsePackageSegments($key, $namespace, $item); + } + return parent::parseNamespacedSegments($key); + } + protected function parsePackageSegments($key, $namespace, $item) + { + $itemSegments = explode('.', $item); + if (!$this->loader->exists($itemSegments[0], $namespace)) { + return array($namespace, 'config', $item); + } + return parent::parseNamespacedSegments($key); + } + public function package($package, $hint, $namespace = null) + { + $namespace = $this->getPackageNamespace($package, $namespace); + $this->packages[] = $namespace; + $this->addNamespace($namespace, $hint); + $this->afterLoading($namespace, function ($me, $group, $items) use($package) { + $env = $me->getEnvironment(); + $loader = $me->getLoader(); + return $loader->cascadePackage($env, $package, $group, $items); + }); + } + protected function getPackageNamespace($package, $namespace) + { + if (is_null($namespace)) { + list($vendor, $namespace) = explode('/', $package); + } + return $namespace; + } + public function afterLoading($namespace, Closure $callback) + { + $this->afterLoad[$namespace] = $callback; + } + protected function getCollection($group, $namespace = null) + { + $namespace = $namespace ?: '*'; + return $namespace . '::' . $group; + } + public function addNamespace($namespace, $hint) + { + $this->loader->addNamespace($namespace, $hint); + } + public function getNamespaces() + { + return $this->loader->getNamespaces(); + } + public function getLoader() + { + return $this->loader; + } + public function setLoader(LoaderInterface $loader) + { + $this->loader = $loader; + } + public function getEnvironment() + { + return $this->environment; + } + public function getAfterLoadCallbacks() + { + return $this->afterLoad; + } + public function getItems() + { + return $this->items; + } + public function offsetExists($key) + { + return $this->has($key); + } + public function offsetGet($key) + { + return $this->get($key); + } + public function offsetSet($key, $value) + { + $this->set($key, $value); + } + public function offsetUnset($key) + { + $this->set($key, null); + } +} +namespace Illuminate\Support; + +class NamespacedItemResolver +{ + protected $parsed = array(); + public function parseKey($key) + { + if (isset($this->parsed[$key])) { + return $this->parsed[$key]; + } + if (strpos($key, '::') === false) { + $segments = explode('.', $key); + $parsed = $this->parseBasicSegments($segments); + } else { + $parsed = $this->parseNamespacedSegments($key); + } + return $this->parsed[$key] = $parsed; + } + protected function parseBasicSegments(array $segments) + { + $group = $segments[0]; + if (count($segments) == 1) { + return array(null, $group, null); + } else { + $item = implode('.', array_slice($segments, 1)); + return array(null, $group, $item); + } + } + protected function parseNamespacedSegments($key) + { + list($namespace, $item) = explode('::', $key); + $itemSegments = explode('.', $item); + $groupAndItem = array_slice($this->parseBasicSegments($itemSegments), 1); + return array_merge(array($namespace), $groupAndItem); + } + public function setParsedKey($key, $parsed) + { + $this->parsed[$key] = $parsed; + } +} +namespace Illuminate\Config; + +use Illuminate\Filesystem\Filesystem; +class FileLoader implements LoaderInterface +{ + protected $files; + protected $defaultPath; + protected $hints = array(); + protected $exists = array(); + public function __construct(Filesystem $files, $defaultPath) + { + $this->files = $files; + $this->defaultPath = $defaultPath; + } + public function load($environment, $group, $namespace = null) + { + $items = array(); + $path = $this->getPath($namespace); + if (is_null($path)) { + return $items; + } + $file = "{$path}/{$group}.php"; + if ($this->files->exists($file)) { + $items = $this->getRequire($file); + } + $file = "{$path}/{$environment}/{$group}.php"; + if ($this->files->exists($file)) { + $items = $this->mergeEnvironment($items, $file); + } + return $items; + } + protected function mergeEnvironment(array $items, $file) + { + return array_replace_recursive($items, $this->getRequire($file)); + } + public function exists($group, $namespace = null) + { + $key = $group . $namespace; + if (isset($this->exists[$key])) { + return $this->exists[$key]; + } + $path = $this->getPath($namespace); + if (is_null($path)) { + return $this->exists[$key] = false; + } + $file = "{$path}/{$group}.php"; + $exists = $this->files->exists($file); + return $this->exists[$key] = $exists; + } + public function cascadePackage($env, $package, $group, $items) + { + $file = "packages/{$package}/{$group}.php"; + if ($this->files->exists($path = $this->defaultPath . '/' . $file)) { + $items = array_merge($items, $this->getRequire($path)); + } + $path = $this->getPackagePath($env, $package, $group); + if ($this->files->exists($path)) { + $items = array_merge($items, $this->getRequire($path)); + } + return $items; + } + protected function getPackagePath($env, $package, $group) + { + $file = "packages/{$package}/{$env}/{$group}.php"; + return $this->defaultPath . '/' . $file; + } + protected function getPath($namespace) + { + if (is_null($namespace)) { + return $this->defaultPath; + } elseif (isset($this->hints[$namespace])) { + return $this->hints[$namespace]; + } + } + public function addNamespace($namespace, $hint) + { + $this->hints[$namespace] = $hint; + } + public function getNamespaces() + { + return $this->hints; + } + protected function getRequire($path) + { + return $this->files->getRequire($path); + } + public function getFilesystem() + { + return $this->files; + } +} +namespace Illuminate\Config; + +interface LoaderInterface +{ + public function load($environment, $group, $namespace = null); + public function exists($group, $namespace = null); + public function addNamespace($namespace, $hint); + public function getNamespaces(); + public function cascadePackage($environment, $package, $group, $items); +} +namespace Illuminate\Config; + +interface EnvironmentVariablesLoaderInterface +{ + public function load($environment = null); +} +namespace Illuminate\Config; + +use Illuminate\Filesystem\Filesystem; +class FileEnvironmentVariablesLoader implements EnvironmentVariablesLoaderInterface +{ + protected $files; + protected $path; + public function __construct(Filesystem $files, $path = null) + { + $this->files = $files; + $this->path = $path ?: base_path(); + } + public function load($environment = null) + { + if ($environment == 'production') { + $environment = null; + } + if (!$this->files->exists($path = $this->getFile($environment))) { + return array(); + } + return array_dot($this->files->getRequire($path)); + } + protected function getFile($environment) + { + if ($environment) { + return $this->path . '/.env.' . $environment . '.php'; + } + return $this->path . '/.env.php'; + } +} +namespace Illuminate\Config; + +class EnvironmentVariables +{ + protected $loader; + public function __construct(EnvironmentVariablesLoaderInterface $loader) + { + $this->loader = $loader; + } + public function load($environment = null) + { + foreach ($this->loader->load($environment) as $key => $value) { + $_ENV[$key] = $value; + $_SERVER[$key] = $value; + putenv("{$key}={$value}"); + } + } +} +namespace Illuminate\Filesystem; + +use FilesystemIterator; +use Symfony\Component\Finder\Finder; +class Filesystem +{ + public function exists($path) + { + return file_exists($path); + } + public function get($path) + { + if ($this->isFile($path)) { + return file_get_contents($path); + } + throw new FileNotFoundException("File does not exist at path {$path}"); + } + public function getRequire($path) + { + if ($this->isFile($path)) { + return require $path; + } + throw new FileNotFoundException("File does not exist at path {$path}"); + } + public function requireOnce($file) + { + require_once $file; + } + public function put($path, $contents) + { + return file_put_contents($path, $contents); + } + public function prepend($path, $data) + { + if ($this->exists($path)) { + return $this->put($path, $data . $this->get($path)); + } + return $this->put($path, $data); + } + public function append($path, $data) + { + return file_put_contents($path, $data, FILE_APPEND); + } + public function delete($paths) + { + $paths = is_array($paths) ? $paths : func_get_args(); + $success = true; + foreach ($paths as $path) { + if (!@unlink($path)) { + $success = false; + } + } + return $success; + } + public function move($path, $target) + { + return rename($path, $target); + } + public function copy($path, $target) + { + return copy($path, $target); + } + public function extension($path) + { + return pathinfo($path, PATHINFO_EXTENSION); + } + public function type($path) + { + return filetype($path); + } + public function size($path) + { + return filesize($path); + } + public function lastModified($path) + { + return filemtime($path); + } + public function isDirectory($directory) + { + return is_dir($directory); + } + public function isWritable($path) + { + return is_writable($path); + } + public function isFile($file) + { + return is_file($file); + } + public function glob($pattern, $flags = 0) + { + return glob($pattern, $flags); + } + public function files($directory) + { + $glob = glob($directory . '/*'); + if ($glob === false) { + return array(); + } + return array_filter($glob, function ($file) { + return filetype($file) == 'file'; + }); + } + public function allFiles($directory) + { + return iterator_to_array(Finder::create()->files()->in($directory), false); + } + public function directories($directory) + { + $directories = array(); + foreach (Finder::create()->in($directory)->directories()->depth(0) as $dir) { + $directories[] = $dir->getPathname(); + } + return $directories; + } + public function makeDirectory($path, $mode = 493, $recursive = false, $force = false) + { + if ($force) { + return @mkdir($path, $mode, $recursive); + } + return mkdir($path, $mode, $recursive); + } + public function copyDirectory($directory, $destination, $options = null) + { + if (!$this->isDirectory($directory)) { + return false; + } + $options = $options ?: FilesystemIterator::SKIP_DOTS; + if (!$this->isDirectory($destination)) { + $this->makeDirectory($destination, 511, true); + } + $items = new FilesystemIterator($directory, $options); + foreach ($items as $item) { + $target = $destination . '/' . $item->getBasename(); + if ($item->isDir()) { + $path = $item->getPathname(); + if (!$this->copyDirectory($path, $target, $options)) { + return false; + } + } else { + if (!$this->copy($item->getPathname(), $target)) { + return false; + } + } + } + return true; + } + public function deleteDirectory($directory, $preserve = false) + { + if (!$this->isDirectory($directory)) { + return false; + } + $items = new FilesystemIterator($directory); + foreach ($items as $item) { + if ($item->isDir()) { + $this->deleteDirectory($item->getPathname()); + } else { + $this->delete($item->getPathname()); + } + } + if (!$preserve) { + @rmdir($directory); + } + return true; + } + public function cleanDirectory($directory) + { + return $this->deleteDirectory($directory, true); + } +} +namespace Illuminate\Foundation; + +class AliasLoader +{ + protected $aliases; + protected $registered = false; + protected static $instance; + public function __construct(array $aliases = array()) + { + $this->aliases = $aliases; + } + public static function getInstance(array $aliases = array()) + { + if (is_null(static::$instance)) { + return static::$instance = new static($aliases); + } + $aliases = array_merge(static::$instance->getAliases(), $aliases); + static::$instance->setAliases($aliases); + return static::$instance; + } + public function load($alias) + { + if (isset($this->aliases[$alias])) { + return class_alias($this->aliases[$alias], $alias); + } + } + public function alias($class, $alias) + { + $this->aliases[$class] = $alias; + } + public function register() + { + if (!$this->registered) { + $this->prependToLoaderStack(); + $this->registered = true; + } + } + protected function prependToLoaderStack() + { + spl_autoload_register(array($this, 'load'), true, true); + } + public function getAliases() + { + return $this->aliases; + } + public function setAliases(array $aliases) + { + $this->aliases = $aliases; + } + public function isRegistered() + { + return $this->registered; + } + public function setRegistered($value) + { + $this->registered = $value; + } + public static function setInstance($loader) + { + static::$instance = $loader; + } +} +namespace Illuminate\Foundation; + +use Illuminate\Filesystem\Filesystem; +class ProviderRepository +{ + protected $files; + protected $manifestPath; + protected $default = array('when' => array()); + public function __construct(Filesystem $files, $manifestPath) + { + $this->files = $files; + $this->manifestPath = $manifestPath; + } + public function load(Application $app, array $providers) + { + $manifest = $this->loadManifest(); + if ($this->shouldRecompile($manifest, $providers)) { + $manifest = $this->compileManifest($app, $providers); + } + if ($app->runningInConsole()) { + $manifest['eager'] = $manifest['providers']; + } + foreach ($manifest['when'] as $provider => $events) { + $this->registerLoadEvents($app, $provider, $events); + } + foreach ($manifest['eager'] as $provider) { + $app->register($this->createProvider($app, $provider)); + } + $app->setDeferredServices($manifest['deferred']); + } + protected function registerLoadEvents(Application $app, $provider, array $events) + { + if (count($events) < 1) { + return; + } + $app->make('events')->listen($events, function () use($app, $provider) { + $app->register($provider); + }); + } + protected function compileManifest(Application $app, $providers) + { + $manifest = $this->freshManifest($providers); + foreach ($providers as $provider) { + $instance = $this->createProvider($app, $provider); + if ($instance->isDeferred()) { + foreach ($instance->provides() as $service) { + $manifest['deferred'][$service] = $provider; + } + $manifest['when'][$provider] = $instance->when(); + } else { + $manifest['eager'][] = $provider; + } + } + return $this->writeManifest($manifest); + } + public function createProvider(Application $app, $provider) + { + return new $provider($app); + } + public function shouldRecompile($manifest, $providers) + { + return is_null($manifest) || $manifest['providers'] != $providers; + } + public function loadManifest() + { + $path = $this->manifestPath . '/services.json'; + if ($this->files->exists($path)) { + $manifest = json_decode($this->files->get($path), true); + return array_merge($this->default, $manifest); + } + } + public function writeManifest($manifest) + { + $path = $this->manifestPath . '/services.json'; + $this->files->put($path, json_encode($manifest, JSON_PRETTY_PRINT)); + return $manifest; + } + protected function freshManifest(array $providers) + { + list($eager, $deferred) = array(array(), array()); + return compact('providers', 'eager', 'deferred'); + } + public function getFilesystem() + { + return $this->files; + } +} +namespace Illuminate\Cookie; + +use Illuminate\Support\ServiceProvider; +class CookieServiceProvider extends ServiceProvider +{ + public function register() + { + $this->app->bindShared('cookie', function ($app) { + $config = $app['config']['session']; + return (new CookieJar())->setDefaultPathAndDomain($config['path'], $config['domain']); + }); + } +} +namespace Illuminate\Database; + +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\ServiceProvider; +use Illuminate\Database\Connectors\ConnectionFactory; +class DatabaseServiceProvider extends ServiceProvider +{ + public function boot() + { + Model::setConnectionResolver($this->app['db']); + Model::setEventDispatcher($this->app['events']); + } + public function register() + { + $this->app->bindShared('db.factory', function ($app) { + return new ConnectionFactory($app); + }); + $this->app->bindShared('db', function ($app) { + return new DatabaseManager($app, $app['db.factory']); + }); + } +} +namespace Illuminate\Encryption; + +use Illuminate\Support\ServiceProvider; +class EncryptionServiceProvider extends ServiceProvider +{ + public function register() + { + $this->app->bindShared('encrypter', function ($app) { + $encrypter = new Encrypter($app['config']['app.key']); + if ($app['config']->has('app.cipher')) { + $encrypter->setCipher($app['config']['app.cipher']); + } + return $encrypter; + }); + } +} +namespace Illuminate\Filesystem; + +use Illuminate\Support\ServiceProvider; +class FilesystemServiceProvider extends ServiceProvider +{ + public function register() + { + $this->app->bindShared('files', function () { + return new Filesystem(); + }); + } +} +namespace Illuminate\Session; + +use Illuminate\Support\ServiceProvider; +class SessionServiceProvider extends ServiceProvider +{ + public function register() + { + $this->setupDefaultDriver(); + $this->registerSessionManager(); + $this->registerSessionDriver(); + } + protected function setupDefaultDriver() + { + if ($this->app->runningInConsole()) { + $this->app['config']['session.driver'] = 'array'; + } + } + protected function registerSessionManager() + { + $this->app->bindShared('session', function ($app) { + return new SessionManager($app); + }); + } + protected function registerSessionDriver() + { + $this->app->bindShared('session.store', function ($app) { + $manager = $app['session']; + return $manager->driver(); + }); + } +} +namespace Illuminate\View; + +use Illuminate\Support\ViewErrorBag; +use Illuminate\View\Engines\PhpEngine; +use Illuminate\Support\ServiceProvider; +use Illuminate\View\Engines\CompilerEngine; +use Illuminate\View\Engines\EngineResolver; +use Illuminate\View\Compilers\BladeCompiler; +class ViewServiceProvider extends ServiceProvider +{ + public function register() + { + $this->registerEngineResolver(); + $this->registerViewFinder(); + $this->registerFactory(); + $this->registerSessionBinder(); + } + public function registerEngineResolver() + { + $this->app->bindShared('view.engine.resolver', function () { + $resolver = new EngineResolver(); + foreach (array('php', 'blade') as $engine) { + $this->{'register' . ucfirst($engine) . 'Engine'}($resolver); + } + return $resolver; + }); + } + public function registerPhpEngine($resolver) + { + $resolver->register('php', function () { + return new PhpEngine(); + }); + } + public function registerBladeEngine($resolver) + { + $app = $this->app; + $app->bindShared('blade.compiler', function ($app) { + $cache = $app['path.storage'] . '/views'; + return new BladeCompiler($app['files'], $cache); + }); + $resolver->register('blade', function () use($app) { + return new CompilerEngine($app['blade.compiler'], $app['files']); + }); + } + public function registerViewFinder() + { + $this->app->bindShared('view.finder', function ($app) { + $paths = $app['config']['view.paths']; + return new FileViewFinder($app['files'], $paths); + }); + } + public function registerFactory() + { + $this->app->bindShared('view', function ($app) { + $resolver = $app['view.engine.resolver']; + $finder = $app['view.finder']; + $env = new Factory($resolver, $finder, $app['events']); + $env->setContainer($app); + $env->share('app', $app); + return $env; + }); + } + protected function registerSessionBinder() + { + list($app, $me) = array($this->app, $this); + $app->booted(function () use($app, $me) { + if ($me->sessionHasErrors($app)) { + $errors = $app['session.store']->get('errors'); + $app['view']->share('errors', $errors); + } else { + $app['view']->share('errors', new ViewErrorBag()); + } + }); + } + public function sessionHasErrors($app) + { + $config = $app['config']['session']; + if (isset($app['session.store']) && !is_null($config['driver'])) { + return $app['session.store']->has('errors'); + } + } +} +namespace Illuminate\Routing; + +interface RouteFiltererInterface +{ + public function filter($name, $callback); + public function callRouteFilter($filter, $parameters, $route, $request, $response = null); +} +namespace Illuminate\Routing; + +use Closure; +use Illuminate\Http\Request; +use Illuminate\Http\Response; +use Illuminate\Events\Dispatcher; +use Illuminate\Container\Container; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request as SymfonyRequest; +use Symfony\Component\HttpFoundation\Response as SymfonyResponse; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +class Router implements HttpKernelInterface, RouteFiltererInterface +{ + protected $events; + protected $container; + protected $routes; + protected $current; + protected $currentRequest; + protected $controllerDispatcher; + protected $inspector; + protected $filtering = true; + protected $patternFilters = array(); + protected $regexFilters = array(); + protected $binders = array(); + protected $patterns = array(); + protected $groupStack = array(); + public static $verbs = array('GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'); + protected $resourceDefaults = array('index', 'create', 'store', 'show', 'edit', 'update', 'destroy'); + public function __construct(Dispatcher $events, Container $container = null) + { + $this->events = $events; + $this->routes = new RouteCollection(); + $this->container = $container ?: new Container(); + $this->bind('_missing', function ($v) { + return explode('/', $v); + }); + } + public function get($uri, $action) + { + return $this->addRoute(array('GET', 'HEAD'), $uri, $action); + } + public function post($uri, $action) + { + return $this->addRoute('POST', $uri, $action); + } + public function put($uri, $action) + { + return $this->addRoute('PUT', $uri, $action); + } + public function patch($uri, $action) + { + return $this->addRoute('PATCH', $uri, $action); + } + public function delete($uri, $action) + { + return $this->addRoute('DELETE', $uri, $action); + } + public function options($uri, $action) + { + return $this->addRoute('OPTIONS', $uri, $action); + } + public function any($uri, $action) + { + $verbs = array('GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'); + return $this->addRoute($verbs, $uri, $action); + } + public function match($methods, $uri, $action) + { + return $this->addRoute(array_map('strtoupper', (array) $methods), $uri, $action); + } + public function controllers(array $controllers) + { + foreach ($controllers as $uri => $name) { + $this->controller($uri, $name); + } + } + public function controller($uri, $controller, $names = array()) + { + $prepended = $controller; + if (!empty($this->groupStack)) { + $prepended = $this->prependGroupUses($controller); + } + $routable = $this->getInspector()->getRoutable($prepended, $uri); + foreach ($routable as $method => $routes) { + foreach ($routes as $route) { + $this->registerInspected($route, $controller, $method, $names); + } + } + $this->addFallthroughRoute($controller, $uri); + } + protected function registerInspected($route, $controller, $method, &$names) + { + $action = array('uses' => $controller . '@' . $method); + $action['as'] = array_get($names, $method); + $this->{$route['verb']}($route['uri'], $action); + } + protected function addFallthroughRoute($controller, $uri) + { + $missing = $this->any($uri . '/{_missing}', $controller . '@missingMethod'); + $missing->where('_missing', '(.*)'); + } + public function resource($name, $controller, array $options = array()) + { + if (str_contains($name, '/')) { + $this->prefixedResource($name, $controller, $options); + return; + } + $base = $this->getResourceWildcard(last(explode('.', $name))); + $defaults = $this->resourceDefaults; + foreach ($this->getResourceMethods($defaults, $options) as $m) { + $this->{'addResource' . ucfirst($m)}($name, $base, $controller, $options); + } + } + protected function prefixedResource($name, $controller, array $options) + { + list($name, $prefix) = $this->getResourcePrefix($name); + $callback = function ($me) use($name, $controller, $options) { + $me->resource($name, $controller, $options); + }; + return $this->group(compact('prefix'), $callback); + } + protected function getResourcePrefix($name) + { + $segments = explode('/', $name); + $prefix = implode('/', array_slice($segments, 0, -1)); + return array(end($segments), $prefix); + } + protected function getResourceMethods($defaults, $options) + { + if (isset($options['only'])) { + return array_intersect($defaults, (array) $options['only']); + } elseif (isset($options['except'])) { + return array_diff($defaults, (array) $options['except']); + } + return $defaults; + } + public function getResourceUri($resource) + { + if (!str_contains($resource, '.')) { + return $resource; + } + $segments = explode('.', $resource); + $uri = $this->getNestedResourceUri($segments); + return str_replace('/{' . $this->getResourceWildcard(last($segments)) . '}', '', $uri); + } + protected function getNestedResourceUri(array $segments) + { + return implode('/', array_map(function ($s) { + return $s . '/{' . $this->getResourceWildcard($s) . '}'; + }, $segments)); + } + protected function getResourceAction($resource, $controller, $method, $options) + { + $name = $this->getResourceName($resource, $method, $options); + return array('as' => $name, 'uses' => $controller . '@' . $method); + } + protected function getResourceName($resource, $method, $options) + { + if (isset($options['names'][$method])) { + return $options['names'][$method]; + } + $prefix = isset($options['as']) ? $options['as'] . '.' : ''; + if (empty($this->groupStack)) { + return $prefix . $resource . '.' . $method; + } + return $this->getGroupResourceName($prefix, $resource, $method); + } + protected function getGroupResourceName($prefix, $resource, $method) + { + $group = str_replace('/', '.', $this->getLastGroupPrefix()); + return trim("{$prefix}{$group}.{$resource}.{$method}", '.'); + } + public function getResourceWildcard($value) + { + return str_replace('-', '_', $value); + } + protected function addResourceIndex($name, $base, $controller, $options) + { + $uri = $this->getResourceUri($name); + $action = $this->getResourceAction($name, $controller, 'index', $options); + return $this->get($uri, $action); + } + protected function addResourceCreate($name, $base, $controller, $options) + { + $uri = $this->getResourceUri($name) . '/create'; + $action = $this->getResourceAction($name, $controller, 'create', $options); + return $this->get($uri, $action); + } + protected function addResourceStore($name, $base, $controller, $options) + { + $uri = $this->getResourceUri($name); + $action = $this->getResourceAction($name, $controller, 'store', $options); + return $this->post($uri, $action); + } + protected function addResourceShow($name, $base, $controller, $options) + { + $uri = $this->getResourceUri($name) . '/{' . $base . '}'; + $action = $this->getResourceAction($name, $controller, 'show', $options); + return $this->get($uri, $action); + } + protected function addResourceEdit($name, $base, $controller, $options) + { + $uri = $this->getResourceUri($name) . '/{' . $base . '}/edit'; + $action = $this->getResourceAction($name, $controller, 'edit', $options); + return $this->get($uri, $action); + } + protected function addResourceUpdate($name, $base, $controller, $options) + { + $this->addPutResourceUpdate($name, $base, $controller, $options); + return $this->addPatchResourceUpdate($name, $base, $controller); + } + protected function addPutResourceUpdate($name, $base, $controller, $options) + { + $uri = $this->getResourceUri($name) . '/{' . $base . '}'; + $action = $this->getResourceAction($name, $controller, 'update', $options); + return $this->put($uri, $action); + } + protected function addPatchResourceUpdate($name, $base, $controller) + { + $uri = $this->getResourceUri($name) . '/{' . $base . '}'; + $this->patch($uri, $controller . '@update'); + } + protected function addResourceDestroy($name, $base, $controller, $options) + { + $uri = $this->getResourceUri($name) . '/{' . $base . '}'; + $action = $this->getResourceAction($name, $controller, 'destroy', $options); + return $this->delete($uri, $action); + } + public function group(array $attributes, Closure $callback) + { + $this->updateGroupStack($attributes); + call_user_func($callback, $this); + array_pop($this->groupStack); + } + protected function updateGroupStack(array $attributes) + { + if (!empty($this->groupStack)) { + $attributes = $this->mergeGroup($attributes, last($this->groupStack)); + } + $this->groupStack[] = $attributes; + } + public function mergeWithLastGroup($new) + { + return $this->mergeGroup($new, last($this->groupStack)); + } + public static function mergeGroup($new, $old) + { + $new['namespace'] = static::formatUsesPrefix($new, $old); + $new['prefix'] = static::formatGroupPrefix($new, $old); + if (isset($new['domain'])) { + unset($old['domain']); + } + $new['where'] = array_merge(array_get($old, 'where', array()), array_get($new, 'where', array())); + return array_merge_recursive(array_except($old, array('namespace', 'prefix', 'where')), $new); + } + protected static function formatUsesPrefix($new, $old) + { + if (isset($new['namespace']) && isset($old['namespace'])) { + return trim(array_get($old, 'namespace'), '\\') . '\\' . trim($new['namespace'], '\\'); + } elseif (isset($new['namespace'])) { + return trim($new['namespace'], '\\'); + } + return array_get($old, 'namespace'); + } + protected static function formatGroupPrefix($new, $old) + { + if (isset($new['prefix'])) { + return trim(array_get($old, 'prefix'), '/') . '/' . trim($new['prefix'], '/'); + } + return array_get($old, 'prefix'); + } + protected function getLastGroupPrefix() + { + if (!empty($this->groupStack)) { + $last = end($this->groupStack); + return isset($last['prefix']) ? $last['prefix'] : ''; + } + return ''; + } + protected function addRoute($methods, $uri, $action) + { + return $this->routes->add($this->createRoute($methods, $uri, $action)); + } + protected function createRoute($methods, $uri, $action) + { + if ($this->routingToController($action)) { + $action = $this->getControllerAction($action); + } + $route = $this->newRoute($methods, $uri = $this->prefix($uri), $action); + if (!empty($this->groupStack)) { + $this->mergeController($route); + } + $this->addWhereClausesToRoute($route); + return $route; + } + protected function newRoute($methods, $uri, $action) + { + return new Route($methods, $uri, $action); + } + protected function prefix($uri) + { + return trim(trim($this->getLastGroupPrefix(), '/') . '/' . trim($uri, '/'), '/') ?: '/'; + } + protected function addWhereClausesToRoute($route) + { + $route->where(array_merge($this->patterns, array_get($route->getAction(), 'where', array()))); + return $route; + } + protected function mergeController($route) + { + $action = $this->mergeWithLastGroup($route->getAction()); + $route->setAction($action); + } + protected function routingToController($action) + { + if ($action instanceof Closure) { + return false; + } + return is_string($action) || is_string(array_get($action, 'uses')); + } + protected function getControllerAction($action) + { + if (is_string($action)) { + $action = array('uses' => $action); + } + if (!empty($this->groupStack)) { + $action['uses'] = $this->prependGroupUses($action['uses']); + } + $action['controller'] = $action['uses']; + $closure = $this->getClassClosure($action['uses']); + return array_set($action, 'uses', $closure); + } + protected function getClassClosure($controller) + { + $d = $this->getControllerDispatcher(); + return function () use($d, $controller) { + $route = $this->current(); + $request = $this->getCurrentRequest(); + list($class, $method) = explode('@', $controller); + return $d->dispatch($route, $request, $class, $method); + }; + } + protected function prependGroupUses($uses) + { + $group = last($this->groupStack); + return isset($group['namespace']) ? $group['namespace'] . '\\' . $uses : $uses; + } + public function dispatch(Request $request) + { + $this->currentRequest = $request; + $response = $this->callFilter('before', $request); + if (is_null($response)) { + $response = $this->dispatchToRoute($request); + } + $response = $this->prepareResponse($request, $response); + $this->callFilter('after', $request, $response); + return $response; + } + public function dispatchToRoute(Request $request) + { + $route = $this->findRoute($request); + $this->events->fire('router.matched', array($route, $request)); + $response = $this->callRouteBefore($route, $request); + if (is_null($response)) { + $response = $route->run($request); + } + $response = $this->prepareResponse($request, $response); + $this->callRouteAfter($route, $request, $response); + return $response; + } + protected function findRoute($request) + { + $this->current = $route = $this->routes->match($request); + return $this->substituteBindings($route); + } + protected function substituteBindings($route) + { + foreach ($route->parameters() as $key => $value) { + if (isset($this->binders[$key])) { + $route->setParameter($key, $this->performBinding($key, $value, $route)); + } + } + return $route; + } + protected function performBinding($key, $value, $route) + { + return call_user_func($this->binders[$key], $value, $route); + } + public function matched($callback) + { + $this->events->listen('router.matched', $callback); + } + public function before($callback) + { + $this->addGlobalFilter('before', $callback); + } + public function after($callback) + { + $this->addGlobalFilter('after', $callback); + } + protected function addGlobalFilter($filter, $callback) + { + $this->events->listen('router.' . $filter, $this->parseFilter($callback)); + } + public function filter($name, $callback) + { + $this->events->listen('router.filter: ' . $name, $this->parseFilter($callback)); + } + protected function parseFilter($callback) + { + if (is_string($callback) && !str_contains($callback, '@')) { + return $callback . '@filter'; + } + return $callback; + } + public function when($pattern, $name, $methods = null) + { + if (!is_null($methods)) { + $methods = array_map('strtoupper', (array) $methods); + } + $this->patternFilters[$pattern][] = compact('name', 'methods'); + } + public function whenRegex($pattern, $name, $methods = null) + { + if (!is_null($methods)) { + $methods = array_map('strtoupper', (array) $methods); + } + $this->regexFilters[$pattern][] = compact('name', 'methods'); + } + public function model($key, $class, Closure $callback = null) + { + $this->bind($key, function ($value) use($class, $callback) { + if (is_null($value)) { + return null; + } + if ($model = (new $class())->find($value)) { + return $model; + } + if ($callback instanceof Closure) { + return call_user_func($callback); + } + throw new NotFoundHttpException(); + }); + } + public function bind($key, $binder) + { + if (is_string($binder)) { + $binder = $this->createClassBinding($binder); + } + $this->binders[str_replace('-', '_', $key)] = $binder; + } + public function createClassBinding($binding) + { + return function ($value, $route) use($binding) { + $segments = explode('@', $binding); + $method = count($segments) == 2 ? $segments[1] : 'bind'; + $callable = array($this->container->make($segments[0]), $method); + return call_user_func($callable, $value, $route); + }; + } + public function pattern($key, $pattern) + { + $this->patterns[$key] = $pattern; + } + public function patterns($patterns) + { + foreach ($patterns as $key => $pattern) { + $this->pattern($key, $pattern); + } + } + protected function callFilter($filter, $request, $response = null) + { + if (!$this->filtering) { + return null; + } + return $this->events->until('router.' . $filter, array($request, $response)); + } + public function callRouteBefore($route, $request) + { + $response = $this->callPatternFilters($route, $request); + return $response ?: $this->callAttachedBefores($route, $request); + } + protected function callPatternFilters($route, $request) + { + foreach ($this->findPatternFilters($request) as $filter => $parameters) { + $response = $this->callRouteFilter($filter, $parameters, $route, $request); + if (!is_null($response)) { + return $response; + } + } + } + public function findPatternFilters($request) + { + $results = array(); + list($path, $method) = array($request->path(), $request->getMethod()); + foreach ($this->patternFilters as $pattern => $filters) { + if (str_is($pattern, $path)) { + $merge = $this->patternsByMethod($method, $filters); + $results = array_merge($results, $merge); + } + } + foreach ($this->regexFilters as $pattern => $filters) { + if (preg_match($pattern, $path)) { + $merge = $this->patternsByMethod($method, $filters); + $results = array_merge($results, $merge); + } + } + return $results; + } + protected function patternsByMethod($method, $filters) + { + $results = array(); + foreach ($filters as $filter) { + if ($this->filterSupportsMethod($filter, $method)) { + $parsed = Route::parseFilters($filter['name']); + $results = array_merge($results, $parsed); + } + } + return $results; + } + protected function filterSupportsMethod($filter, $method) + { + $methods = $filter['methods']; + return is_null($methods) || in_array($method, $methods); + } + protected function callAttachedBefores($route, $request) + { + foreach ($route->beforeFilters() as $filter => $parameters) { + $response = $this->callRouteFilter($filter, $parameters, $route, $request); + if (!is_null($response)) { + return $response; + } + } + } + public function callRouteAfter($route, $request, $response) + { + foreach ($route->afterFilters() as $filter => $parameters) { + $this->callRouteFilter($filter, $parameters, $route, $request, $response); + } + } + public function callRouteFilter($filter, $parameters, $route, $request, $response = null) + { + if (!$this->filtering) { + return null; + } + $data = array_merge(array($route, $request, $response), $parameters); + return $this->events->until('router.filter: ' . $filter, $this->cleanFilterParameters($data)); + } + protected function cleanFilterParameters(array $parameters) + { + return array_filter($parameters, function ($p) { + return !is_null($p) && $p !== ''; + }); + } + protected function prepareResponse($request, $response) + { + if (!$response instanceof SymfonyResponse) { + $response = new Response($response); + } + return $response->prepare($request); + } + public function withoutFilters(callable $callback) + { + $this->disableFilters(); + call_user_func($callback); + $this->enableFilters(); + } + public function enableFilters() + { + $this->filtering = true; + } + public function disableFilters() + { + $this->filtering = false; + } + public function input($key, $default = null) + { + return $this->current()->parameter($key, $default); + } + public function getCurrentRoute() + { + return $this->current(); + } + public function current() + { + return $this->current; + } + public function has($name) + { + return $this->routes->hasNamedRoute($name); + } + public function currentRouteName() + { + return $this->current() ? $this->current()->getName() : null; + } + public function is() + { + foreach (func_get_args() as $pattern) { + if (str_is($pattern, $this->currentRouteName())) { + return true; + } + } + return false; + } + public function currentRouteNamed($name) + { + return $this->current() ? $this->current()->getName() == $name : false; + } + public function currentRouteAction() + { + if (!$this->current()) { + return; + } + $action = $this->current()->getAction(); + return isset($action['controller']) ? $action['controller'] : null; + } + public function uses() + { + foreach (func_get_args() as $pattern) { + if (str_is($pattern, $this->currentRouteAction())) { + return true; + } + } + return false; + } + public function currentRouteUses($action) + { + return $this->currentRouteAction() == $action; + } + public function getCurrentRequest() + { + return $this->currentRequest; + } + public function getRoutes() + { + return $this->routes; + } + public function getControllerDispatcher() + { + if (is_null($this->controllerDispatcher)) { + $this->controllerDispatcher = new ControllerDispatcher($this, $this->container); + } + return $this->controllerDispatcher; + } + public function setControllerDispatcher(ControllerDispatcher $dispatcher) + { + $this->controllerDispatcher = $dispatcher; + } + public function getInspector() + { + return $this->inspector ?: ($this->inspector = new ControllerInspector()); + } + public function getPatterns() + { + return $this->patterns; + } + public function handle(SymfonyRequest $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + return $this->dispatch(Request::createFromBase($request)); + } +} +namespace Illuminate\Routing; + +use Illuminate\Http\Request; +use Illuminate\Routing\Matching\UriValidator; +use Illuminate\Routing\Matching\HostValidator; +use Illuminate\Routing\Matching\MethodValidator; +use Illuminate\Routing\Matching\SchemeValidator; +use Symfony\Component\Routing\Route as SymfonyRoute; +class Route +{ + protected $uri; + protected $methods; + protected $action; + protected $defaults = array(); + protected $wheres = array(); + protected $parameters; + protected $parameterNames; + protected $compiled; + protected static $validators; + public function __construct($methods, $uri, $action) + { + $this->uri = $uri; + $this->methods = (array) $methods; + $this->action = $this->parseAction($action); + if (in_array('GET', $this->methods) && !in_array('HEAD', $this->methods)) { + $this->methods[] = 'HEAD'; + } + if (isset($this->action['prefix'])) { + $this->prefix($this->action['prefix']); + } + } + public function run() + { + $parameters = array_filter($this->parameters(), function ($p) { + return isset($p); + }); + return call_user_func_array($this->action['uses'], $parameters); + } + public function matches(Request $request, $includingMethod = true) + { + $this->compileRoute(); + foreach ($this->getValidators() as $validator) { + if (!$includingMethod && $validator instanceof MethodValidator) { + continue; + } + if (!$validator->matches($this, $request)) { + return false; + } + } + return true; + } + protected function compileRoute() + { + $optionals = $this->extractOptionalParameters(); + $uri = preg_replace('/\\{(\\w+?)\\?\\}/', '{$1}', $this->uri); + $this->compiled = with(new SymfonyRoute($uri, $optionals, $this->wheres, array(), $this->domain() ?: ''))->compile(); + } + protected function extractOptionalParameters() + { + preg_match_all('/\\{(\\w+?)\\?\\}/', $this->uri, $matches); + return isset($matches[1]) ? array_fill_keys($matches[1], null) : array(); + } + public function beforeFilters() + { + if (!isset($this->action['before'])) { + return array(); + } + return $this->parseFilters($this->action['before']); + } + public function afterFilters() + { + if (!isset($this->action['after'])) { + return array(); + } + return $this->parseFilters($this->action['after']); + } + public static function parseFilters($filters) + { + return array_build(static::explodeFilters($filters), function ($key, $value) { + return Route::parseFilter($value); + }); + } + protected static function explodeFilters($filters) + { + if (is_array($filters)) { + return static::explodeArrayFilters($filters); + } + return array_map('trim', explode('|', $filters)); + } + protected static function explodeArrayFilters(array $filters) + { + $results = array(); + foreach ($filters as $filter) { + $results = array_merge($results, array_map('trim', explode('|', $filter))); + } + return $results; + } + public static function parseFilter($filter) + { + if (!str_contains($filter, ':')) { + return array($filter, array()); + } + return static::parseParameterFilter($filter); + } + protected static function parseParameterFilter($filter) + { + list($name, $parameters) = explode(':', $filter, 2); + return array($name, explode(',', $parameters)); + } + public function getParameter($name, $default = null) + { + return $this->parameter($name, $default); + } + public function parameter($name, $default = null) + { + return array_get($this->parameters(), $name, $default); + } + public function setParameter($name, $value) + { + $this->parameters(); + $this->parameters[$name] = $value; + } + public function forgetParameter($name) + { + $this->parameters(); + unset($this->parameters[$name]); + } + public function parameters() + { + if (isset($this->parameters)) { + return array_map(function ($value) { + return is_string($value) ? rawurldecode($value) : $value; + }, $this->parameters); + } + throw new \LogicException('Route is not bound.'); + } + public function parametersWithoutNulls() + { + return array_filter($this->parameters(), function ($p) { + return !is_null($p); + }); + } + public function parameterNames() + { + if (isset($this->parameterNames)) { + return $this->parameterNames; + } + return $this->parameterNames = $this->compileParameterNames(); + } + protected function compileParameterNames() + { + preg_match_all('/\\{(.*?)\\}/', $this->domain() . $this->uri, $matches); + return array_map(function ($m) { + return trim($m, '?'); + }, $matches[1]); + } + public function bind(Request $request) + { + $this->compileRoute(); + $this->bindParameters($request); + return $this; + } + public function bindParameters(Request $request) + { + $params = $this->matchToKeys(array_slice($this->bindPathParameters($request), 1)); + if (!is_null($this->compiled->getHostRegex())) { + $params = $this->bindHostParameters($request, $params); + } + return $this->parameters = $this->replaceDefaults($params); + } + protected function bindPathParameters(Request $request) + { + preg_match($this->compiled->getRegex(), '/' . $request->decodedPath(), $matches); + return $matches; + } + protected function bindHostParameters(Request $request, $parameters) + { + preg_match($this->compiled->getHostRegex(), $request->getHost(), $matches); + return array_merge($this->matchToKeys(array_slice($matches, 1)), $parameters); + } + protected function matchToKeys(array $matches) + { + if (count($this->parameterNames()) == 0) { + return array(); + } + $parameters = array_intersect_key($matches, array_flip($this->parameterNames())); + return array_filter($parameters, function ($value) { + return is_string($value) && strlen($value) > 0; + }); + } + protected function replaceDefaults(array $parameters) + { + foreach ($parameters as $key => &$value) { + $value = isset($value) ? $value : array_get($this->defaults, $key); + } + return $parameters; + } + protected function parseAction($action) + { + if (is_callable($action)) { + return array('uses' => $action); + } elseif (!isset($action['uses'])) { + $action['uses'] = $this->findClosure($action); + } + return $action; + } + protected function findClosure(array $action) + { + return array_first($action, function ($key, $value) { + return is_callable($value); + }); + } + public static function getValidators() + { + if (isset(static::$validators)) { + return static::$validators; + } + return static::$validators = array(new MethodValidator(), new SchemeValidator(), new HostValidator(), new UriValidator()); + } + public function before($filters) + { + return $this->addFilters('before', $filters); + } + public function after($filters) + { + return $this->addFilters('after', $filters); + } + protected function addFilters($type, $filters) + { + $filters = static::explodeFilters($filters); + if (isset($this->action[$type])) { + $existing = static::explodeFilters($this->action[$type]); + $this->action[$type] = array_merge($existing, $filters); + } else { + $this->action[$type] = $filters; + } + return $this; + } + public function defaults($key, $value) + { + $this->defaults[$key] = $value; + return $this; + } + public function where($name, $expression = null) + { + foreach ($this->parseWhere($name, $expression) as $name => $expression) { + $this->wheres[$name] = $expression; + } + return $this; + } + protected function parseWhere($name, $expression) + { + return is_array($name) ? $name : array($name => $expression); + } + protected function whereArray(array $wheres) + { + foreach ($wheres as $name => $expression) { + $this->where($name, $expression); + } + return $this; + } + public function prefix($prefix) + { + $this->uri = trim($prefix, '/') . '/' . trim($this->uri, '/'); + return $this; + } + public function getPath() + { + return $this->uri(); + } + public function uri() + { + return $this->uri; + } + public function getMethods() + { + return $this->methods(); + } + public function methods() + { + return $this->methods; + } + public function httpOnly() + { + return in_array('http', $this->action, true); + } + public function httpsOnly() + { + return $this->secure(); + } + public function secure() + { + return in_array('https', $this->action, true); + } + public function domain() + { + return isset($this->action['domain']) ? $this->action['domain'] : null; + } + public function getUri() + { + return $this->uri; + } + public function setUri($uri) + { + $this->uri = $uri; + return $this; + } + public function getPrefix() + { + return isset($this->action['prefix']) ? $this->action['prefix'] : null; + } + public function getName() + { + return isset($this->action['as']) ? $this->action['as'] : null; + } + public function getActionName() + { + return isset($this->action['controller']) ? $this->action['controller'] : 'Closure'; + } + public function getAction() + { + return $this->action; + } + public function setAction(array $action) + { + $this->action = $action; + return $this; + } + public function getCompiled() + { + return $this->compiled; + } +} +namespace Illuminate\Routing; + +use Countable; +use ArrayIterator; +use IteratorAggregate; +use Illuminate\Http\Request; +use Illuminate\Http\Response; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +class RouteCollection implements Countable, IteratorAggregate +{ + protected $routes = array(); + protected $allRoutes = array(); + protected $nameList = array(); + protected $actionList = array(); + public function add(Route $route) + { + $this->addToCollections($route); + $this->addLookups($route); + return $route; + } + protected function addToCollections($route) + { + $domainAndUri = $route->domain() . $route->getUri(); + foreach ($route->methods() as $method) { + $this->routes[$method][$domainAndUri] = $route; + } + $this->allRoutes[$method . $domainAndUri] = $route; + } + protected function addLookups($route) + { + $action = $route->getAction(); + if (isset($action['as'])) { + $this->nameList[$action['as']] = $route; + } + if (isset($action['controller'])) { + $this->addToActionList($action, $route); + } + } + protected function addToActionList($action, $route) + { + if (!isset($this->actionList[$action['controller']])) { + $this->actionList[$action['controller']] = $route; + } + } + public function match(Request $request) + { + $routes = $this->get($request->getMethod()); + $route = $this->check($routes, $request); + if (!is_null($route)) { + return $route->bind($request); + } + $others = $this->checkForAlternateVerbs($request); + if (count($others) > 0) { + return $this->getOtherMethodsRoute($request, $others); + } + throw new NotFoundHttpException(); + } + protected function checkForAlternateVerbs($request) + { + $methods = array_diff(Router::$verbs, array($request->getMethod())); + $others = array(); + foreach ($methods as $method) { + if (!is_null($this->check($this->get($method), $request, false))) { + $others[] = $method; + } + } + return $others; + } + protected function getOtherMethodsRoute($request, array $others) + { + if ($request->method() == 'OPTIONS') { + return (new Route('OPTIONS', $request->path(), function () use($others) { + return new Response('', 200, array('Allow' => implode(',', $others))); + }))->bind($request); + } + $this->methodNotAllowed($others); + } + protected function methodNotAllowed(array $others) + { + throw new MethodNotAllowedHttpException($others); + } + protected function check(array $routes, $request, $includingMethod = true) + { + return array_first($routes, function ($key, $value) use($request, $includingMethod) { + return $value->matches($request, $includingMethod); + }); + } + protected function get($method = null) + { + if (is_null($method)) { + return $this->getRoutes(); + } + return array_get($this->routes, $method, array()); + } + public function hasNamedRoute($name) + { + return !is_null($this->getByName($name)); + } + public function getByName($name) + { + return isset($this->nameList[$name]) ? $this->nameList[$name] : null; + } + public function getByAction($action) + { + return isset($this->actionList[$action]) ? $this->actionList[$action] : null; + } + public function getRoutes() + { + return array_values($this->allRoutes); + } + public function getIterator() + { + return new ArrayIterator($this->getRoutes()); + } + public function count() + { + return count($this->getRoutes()); + } +} +namespace Illuminate\Routing; + +use Closure; +use Illuminate\Http\Request; +use Illuminate\Container\Container; +class ControllerDispatcher +{ + protected $filterer; + protected $container; + public function __construct(RouteFiltererInterface $filterer, Container $container = null) + { + $this->filterer = $filterer; + $this->container = $container; + } + public function dispatch(Route $route, Request $request, $controller, $method) + { + $instance = $this->makeController($controller); + $this->assignAfter($instance, $route, $request, $method); + $response = $this->before($instance, $route, $request, $method); + if (is_null($response)) { + $response = $this->call($instance, $route, $method); + } + return $response; + } + protected function makeController($controller) + { + Controller::setFilterer($this->filterer); + return $this->container->make($controller); + } + protected function call($instance, $route, $method) + { + $parameters = $route->parametersWithoutNulls(); + return $instance->callAction($method, $parameters); + } + protected function before($instance, $route, $request, $method) + { + foreach ($instance->getBeforeFilters() as $filter) { + if ($this->filterApplies($filter, $request, $method)) { + $response = $this->callFilter($filter, $route, $request); + if (!is_null($response)) { + return $response; + } + } + } + } + protected function assignAfter($instance, $route, $request, $method) + { + foreach ($instance->getAfterFilters() as $filter) { + if ($this->filterApplies($filter, $request, $method)) { + $route->after($this->getAssignableAfter($filter)); + } + } + } + protected function getAssignableAfter($filter) + { + return $filter['original'] instanceof Closure ? $filter['filter'] : $filter['original']; + } + protected function filterApplies($filter, $request, $method) + { + foreach (array('Only', 'Except', 'On') as $type) { + if ($this->{"filterFails{$type}"}($filter, $request, $method)) { + return false; + } + } + return true; + } + protected function filterFailsOnly($filter, $request, $method) + { + if (!isset($filter['options']['only'])) { + return false; + } + return !in_array($method, (array) $filter['options']['only']); + } + protected function filterFailsExcept($filter, $request, $method) + { + if (!isset($filter['options']['except'])) { + return false; + } + return in_array($method, (array) $filter['options']['except']); + } + protected function filterFailsOn($filter, $request, $method) + { + $on = array_get($filter, 'options.on'); + if (is_null($on)) { + return false; + } + if (is_string($on)) { + $on = explode('|', $on); + } + return !in_array(strtolower($request->getMethod()), $on); + } + protected function callFilter($filter, $route, $request) + { + extract($filter); + return $this->filterer->callRouteFilter($filter, $parameters, $route, $request); + } +} +namespace Illuminate\Routing; + +use Illuminate\Http\Request; +use InvalidArgumentException; +class UrlGenerator +{ + protected $routes; + protected $request; + protected $forcedRoot; + protected $forceSchema; + protected $dontEncode = array('%2F' => '/', '%40' => '@', '%3A' => ':', '%3B' => ';', '%2C' => ',', '%3D' => '=', '%2B' => '+', '%21' => '!', '%2A' => '*', '%7C' => '|'); + public function __construct(RouteCollection $routes, Request $request) + { + $this->routes = $routes; + $this->setRequest($request); + } + public function full() + { + return $this->request->fullUrl(); + } + public function current() + { + return $this->to($this->request->getPathInfo()); + } + public function previous() + { + return $this->to($this->request->headers->get('referer')); + } + public function to($path, $extra = array(), $secure = null) + { + if ($this->isValidUrl($path)) { + return $path; + } + $scheme = $this->getScheme($secure); + $tail = implode('/', array_map('rawurlencode', (array) $extra)); + $root = $this->getRootUrl($scheme); + return $this->trimUrl($root, $path, $tail); + } + public function secure($path, $parameters = array()) + { + return $this->to($path, $parameters, true); + } + public function asset($path, $secure = null) + { + if ($this->isValidUrl($path)) { + return $path; + } + $root = $this->getRootUrl($this->getScheme($secure)); + return $this->removeIndex($root) . '/' . trim($path, '/'); + } + protected function removeIndex($root) + { + $i = 'index.php'; + return str_contains($root, $i) ? str_replace('/' . $i, '', $root) : $root; + } + public function secureAsset($path) + { + return $this->asset($path, true); + } + protected function getScheme($secure) + { + if (is_null($secure)) { + return $this->forceSchema ?: $this->request->getScheme() . '://'; + } + return $secure ? 'https://' : 'http://'; + } + public function forceSchema($schema) + { + $this->forceSchema = $schema . '://'; + } + public function route($name, $parameters = array(), $absolute = true, $route = null) + { + $route = $route ?: $this->routes->getByName($name); + $parameters = (array) $parameters; + if (!is_null($route)) { + return $this->toRoute($route, $parameters, $absolute); + } + throw new InvalidArgumentException("Route [{$name}] not defined."); + } + protected function toRoute($route, array $parameters, $absolute) + { + $domain = $this->getRouteDomain($route, $parameters); + $uri = strtr(rawurlencode($this->trimUrl($root = $this->replaceRoot($route, $domain, $parameters), $this->replaceRouteParameters($route->uri(), $parameters))), $this->dontEncode) . $this->getRouteQueryString($parameters); + return $absolute ? $uri : '/' . ltrim(str_replace($root, '', $uri), '/'); + } + protected function replaceRoot($route, $domain, &$parameters) + { + return $this->replaceRouteParameters($this->getRouteRoot($route, $domain), $parameters); + } + protected function replaceRouteParameters($path, array &$parameters) + { + if (count($parameters)) { + $path = preg_replace_sub('/\\{.*?\\}/', $parameters, $this->replaceNamedParameters($path, $parameters)); + } + return trim(preg_replace('/\\{.*?\\?\\}/', '', $path), '/'); + } + protected function replaceNamedParameters($path, &$parameters) + { + return preg_replace_callback('/\\{(.*?)\\??\\}/', function ($m) use(&$parameters) { + return isset($parameters[$m[1]]) ? array_pull($parameters, $m[1]) : $m[0]; + }, $path); + } + protected function getRouteQueryString(array $parameters) + { + if (count($parameters) == 0) { + return ''; + } + $query = http_build_query($keyed = $this->getStringParameters($parameters)); + if (count($keyed) < count($parameters)) { + $query .= '&' . implode('&', $this->getNumericParameters($parameters)); + } + return '?' . trim($query, '&'); + } + protected function getStringParameters(array $parameters) + { + return array_where($parameters, function ($k, $v) { + return is_string($k); + }); + } + protected function getNumericParameters(array $parameters) + { + return array_where($parameters, function ($k, $v) { + return is_numeric($k); + }); + } + protected function getRouteDomain($route, &$parameters) + { + return $route->domain() ? $this->formatDomain($route, $parameters) : null; + } + protected function formatDomain($route, &$parameters) + { + return $this->addPortToDomain($this->getDomainAndScheme($route)); + } + protected function getDomainAndScheme($route) + { + return $this->getRouteScheme($route) . $route->domain(); + } + protected function addPortToDomain($domain) + { + if (in_array($this->request->getPort(), array('80', '443'))) { + return $domain; + } + return $domain . ':' . $this->request->getPort(); + } + protected function getRouteRoot($route, $domain) + { + return $this->getRootUrl($this->getRouteScheme($route), $domain); + } + protected function getRouteScheme($route) + { + if ($route->httpOnly()) { + return $this->getScheme(false); + } elseif ($route->httpsOnly()) { + return $this->getScheme(true); + } + return $this->getScheme(null); + } + public function action($action, $parameters = array(), $absolute = true) + { + return $this->route($action, $parameters, $absolute, $this->routes->getByAction($action)); + } + protected function getRootUrl($scheme, $root = null) + { + if (is_null($root)) { + $root = $this->forcedRoot ?: $this->request->root(); + } + $start = starts_with($root, 'http://') ? 'http://' : 'https://'; + return preg_replace('~' . $start . '~', $scheme, $root, 1); + } + public function forceRootUrl($root) + { + $this->forcedRoot = $root; + } + public function isValidUrl($path) + { + if (starts_with($path, array('#', '//', 'mailto:', 'tel:'))) { + return true; + } + return filter_var($path, FILTER_VALIDATE_URL) !== false; + } + protected function trimUrl($root, $path, $tail = '') + { + return trim($root . '/' . trim($path . '/' . $tail, '/'), '/'); + } + public function getRequest() + { + return $this->request; + } + public function setRequest(Request $request) + { + $this->request = $request; + } +} +namespace Illuminate\Routing\Matching; + +use Illuminate\Http\Request; +use Illuminate\Routing\Route; +interface ValidatorInterface +{ + public function matches(Route $route, Request $request); +} +namespace Illuminate\Routing\Matching; + +use Illuminate\Http\Request; +use Illuminate\Routing\Route; +class HostValidator implements ValidatorInterface +{ + public function matches(Route $route, Request $request) + { + if (is_null($route->getCompiled()->getHostRegex())) { + return true; + } + return preg_match($route->getCompiled()->getHostRegex(), $request->getHost()); + } +} +namespace Illuminate\Routing\Matching; + +use Illuminate\Http\Request; +use Illuminate\Routing\Route; +class MethodValidator implements ValidatorInterface +{ + public function matches(Route $route, Request $request) + { + return in_array($request->getMethod(), $route->methods()); + } +} +namespace Illuminate\Routing\Matching; + +use Illuminate\Http\Request; +use Illuminate\Routing\Route; +class SchemeValidator implements ValidatorInterface +{ + public function matches(Route $route, Request $request) + { + if ($route->httpOnly()) { + return !$request->secure(); + } elseif ($route->secure()) { + return $request->secure(); + } + return true; + } +} +namespace Illuminate\Routing\Matching; + +use Illuminate\Http\Request; +use Illuminate\Routing\Route; +class UriValidator implements ValidatorInterface +{ + public function matches(Route $route, Request $request) + { + $path = $request->path() == '/' ? '/' : '/' . $request->path(); + return preg_match($route->getCompiled()->getRegex(), rawurldecode($path)); + } +} +namespace Illuminate\Workbench; + +use Illuminate\Support\ServiceProvider; +use Illuminate\Workbench\Console\WorkbenchMakeCommand; +class WorkbenchServiceProvider extends ServiceProvider +{ + protected $defer = false; + public function register() + { + $this->app->bindShared('package.creator', function ($app) { + return new PackageCreator($app['files']); + }); + $this->app->bindShared('command.workbench', function ($app) { + return new WorkbenchMakeCommand($app['package.creator']); + }); + $this->commands('command.workbench'); + } + public function provides() + { + return array('package.creator', 'command.workbench'); + } +} +namespace Illuminate\Events; + +use Illuminate\Container\Container; +class Dispatcher +{ + protected $container; + protected $listeners = array(); + protected $wildcards = array(); + protected $sorted = array(); + protected $firing = array(); + public function __construct(Container $container = null) + { + $this->container = $container ?: new Container(); + } + public function listen($events, $listener, $priority = 0) + { + foreach ((array) $events as $event) { + if (str_contains($event, '*')) { + $this->setupWildcardListen($event, $listener); + } else { + $this->listeners[$event][$priority][] = $this->makeListener($listener); + unset($this->sorted[$event]); + } + } + } + protected function setupWildcardListen($event, $listener) + { + $this->wildcards[$event][] = $this->makeListener($listener); + } + public function hasListeners($eventName) + { + return isset($this->listeners[$eventName]); + } + public function queue($event, $payload = array()) + { + $this->listen($event . '_queue', function () use($event, $payload) { + $this->fire($event, $payload); + }); + } + public function subscribe($subscriber) + { + $subscriber = $this->resolveSubscriber($subscriber); + $subscriber->subscribe($this); + } + protected function resolveSubscriber($subscriber) + { + if (is_string($subscriber)) { + return $this->container->make($subscriber); + } + return $subscriber; + } + public function until($event, $payload = array()) + { + return $this->fire($event, $payload, true); + } + public function flush($event) + { + $this->fire($event . '_queue'); + } + public function firing() + { + return last($this->firing); + } + public function fire($event, $payload = array(), $halt = false) + { + $responses = array(); + if (!is_array($payload)) { + $payload = array($payload); + } + $this->firing[] = $event; + foreach ($this->getListeners($event) as $listener) { + $response = call_user_func_array($listener, $payload); + if (!is_null($response) && $halt) { + array_pop($this->firing); + return $response; + } + if ($response === false) { + break; + } + $responses[] = $response; + } + array_pop($this->firing); + return $halt ? null : $responses; + } + public function getListeners($eventName) + { + $wildcards = $this->getWildcardListeners($eventName); + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + return array_merge($this->sorted[$eventName], $wildcards); + } + protected function getWildcardListeners($eventName) + { + $wildcards = array(); + foreach ($this->wildcards as $key => $listeners) { + if (str_is($key, $eventName)) { + $wildcards = array_merge($wildcards, $listeners); + } + } + return $wildcards; + } + protected function sortListeners($eventName) + { + $this->sorted[$eventName] = array(); + if (isset($this->listeners[$eventName])) { + krsort($this->listeners[$eventName]); + $this->sorted[$eventName] = call_user_func_array('array_merge', $this->listeners[$eventName]); + } + } + public function makeListener($listener) + { + if (is_string($listener)) { + $listener = $this->createClassListener($listener); + } + return $listener; + } + public function createClassListener($listener) + { + $container = $this->container; + return function () use($listener, $container) { + $segments = explode('@', $listener); + $method = count($segments) == 2 ? $segments[1] : 'handle'; + $callable = array($container->make($segments[0]), $method); + $data = func_get_args(); + return call_user_func_array($callable, $data); + }; + } + public function forget($event) + { + unset($this->listeners[$event], $this->sorted[$event]); + } + public function forgetQueued() + { + foreach ($this->listeners as $key => $value) { + if (ends_with($key, '_queue')) { + $this->forget($key); + } + } + } +} +namespace Illuminate\Database\Eloquent; + +use DateTime; +use ArrayAccess; +use Carbon\Carbon; +use LogicException; +use JsonSerializable; +use Illuminate\Events\Dispatcher; +use Illuminate\Database\Eloquent\Relations\Pivot; +use Illuminate\Database\Eloquent\Relations\HasOne; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\MorphTo; +use Illuminate\Support\Contracts\JsonableInterface; +use Illuminate\Support\Contracts\ArrayableInterface; +use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Database\Eloquent\Relations\MorphOne; +use Illuminate\Database\Eloquent\Relations\MorphMany; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Query\Builder as QueryBuilder; +use Illuminate\Database\Eloquent\Relations\MorphToMany; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; +use Illuminate\Database\ConnectionResolverInterface as Resolver; +abstract class Model implements ArrayAccess, ArrayableInterface, JsonableInterface, JsonSerializable +{ + protected $connection; + protected $table; + protected $primaryKey = 'id'; + protected $perPage = 15; + public $incrementing = true; + public $timestamps = true; + protected $attributes = array(); + protected $original = array(); + protected $relations = array(); + protected $hidden = array(); + protected $visible = array(); + protected $appends = array(); + protected $fillable = array(); + protected $guarded = array('*'); + protected $dates = array(); + protected $touches = array(); + protected $observables = array(); + protected $with = array(); + protected $morphClass; + public $exists = false; + public static $snakeAttributes = true; + protected static $resolver; + protected static $dispatcher; + protected static $booted = array(); + protected static $globalScopes = array(); + protected static $unguarded = false; + protected static $mutatorCache = array(); + public static $manyMethods = array('belongsToMany', 'morphToMany', 'morphedByMany'); + const CREATED_AT = 'created_at'; + const UPDATED_AT = 'updated_at'; + public function __construct(array $attributes = array()) + { + $this->bootIfNotBooted(); + $this->syncOriginal(); + $this->fill($attributes); + } + protected function bootIfNotBooted() + { + $class = get_class($this); + if (!isset(static::$booted[$class])) { + static::$booted[$class] = true; + $this->fireModelEvent('booting', false); + static::boot(); + $this->fireModelEvent('booted', false); + } + } + protected static function boot() + { + $class = get_called_class(); + static::$mutatorCache[$class] = array(); + foreach (get_class_methods($class) as $method) { + if (preg_match('/^get(.+)Attribute$/', $method, $matches)) { + if (static::$snakeAttributes) { + $matches[1] = snake_case($matches[1]); + } + static::$mutatorCache[$class][] = lcfirst($matches[1]); + } + } + static::bootTraits(); + } + protected static function bootTraits() + { + foreach (class_uses_recursive(get_called_class()) as $trait) { + if (method_exists(get_called_class(), $method = 'boot' . class_basename($trait))) { + forward_static_call(array(get_called_class(), $method)); + } + } + } + public static function addGlobalScope(ScopeInterface $scope) + { + static::$globalScopes[get_called_class()][get_class($scope)] = $scope; + } + public static function hasGlobalScope($scope) + { + return !is_null(static::getGlobalScope($scope)); + } + public static function getGlobalScope($scope) + { + return array_first(static::$globalScopes[get_called_class()], function ($key, $value) use($scope) { + return $scope instanceof $value; + }); + } + public function getGlobalScopes() + { + return array_get(static::$globalScopes, get_class($this), array()); + } + public static function observe($class) + { + $instance = new static(); + $className = get_class($class); + foreach ($instance->getObservableEvents() as $event) { + if (method_exists($class, $event)) { + static::registerModelEvent($event, $className . '@' . $event); + } + } + } + public function fill(array $attributes) + { + $totallyGuarded = $this->totallyGuarded(); + foreach ($this->fillableFromArray($attributes) as $key => $value) { + $key = $this->removeTableFromKey($key); + if ($this->isFillable($key)) { + $this->setAttribute($key, $value); + } elseif ($totallyGuarded) { + throw new MassAssignmentException($key); + } + } + return $this; + } + protected function fillableFromArray(array $attributes) + { + if (count($this->fillable) > 0 && !static::$unguarded) { + return array_intersect_key($attributes, array_flip($this->fillable)); + } + return $attributes; + } + public function newInstance($attributes = array(), $exists = false) + { + $model = new static((array) $attributes); + $model->exists = $exists; + return $model; + } + public function newFromBuilder($attributes = array()) + { + $instance = $this->newInstance(array(), true); + $instance->setRawAttributes((array) $attributes, true); + return $instance; + } + public static function hydrate(array $items, $connection = null) + { + $collection = with($instance = new static())->newCollection(); + foreach ($items as $item) { + $model = $instance->newFromBuilder($item); + if (!is_null($connection)) { + $model->setConnection($connection); + } + $collection->push($model); + } + return $collection; + } + public static function hydrateRaw($query, $bindings = array(), $connection = null) + { + $instance = new static(); + if (!is_null($connection)) { + $instance->setConnection($connection); + } + $items = $instance->getConnection()->select($query, $bindings); + return static::hydrate($items, $connection); + } + public static function create(array $attributes) + { + $model = new static($attributes); + $model->save(); + return $model; + } + public static function firstOrCreate(array $attributes) + { + if (!is_null($instance = static::where($attributes)->first())) { + return $instance; + } + return static::create($attributes); + } + public static function firstOrNew(array $attributes) + { + if (!is_null($instance = static::where($attributes)->first())) { + return $instance; + } + return new static($attributes); + } + public static function updateOrCreate(array $attributes, array $values = array()) + { + $instance = static::firstOrNew($attributes); + $instance->fill($values)->save(); + return $instance; + } + protected static function firstByAttributes($attributes) + { + return static::where($attributes)->first(); + } + public static function query() + { + return (new static())->newQuery(); + } + public static function on($connection = null) + { + $instance = new static(); + $instance->setConnection($connection); + return $instance->newQuery(); + } + public static function all($columns = array('*')) + { + $instance = new static(); + return $instance->newQuery()->get($columns); + } + public static function find($id, $columns = array('*')) + { + if (is_array($id) && empty($id)) { + return new Collection(); + } + $instance = new static(); + return $instance->newQuery()->find($id, $columns); + } + public static function findOrNew($id, $columns = array('*')) + { + if (!is_null($model = static::find($id, $columns))) { + return $model; + } + return new static(); + } + public static function findOrFail($id, $columns = array('*')) + { + if (!is_null($model = static::find($id, $columns))) { + return $model; + } + throw (new ModelNotFoundException())->setModel(get_called_class()); + } + public function load($relations) + { + if (is_string($relations)) { + $relations = func_get_args(); + } + $query = $this->newQuery()->with($relations); + $query->eagerLoadRelations(array($this)); + return $this; + } + public static function with($relations) + { + if (is_string($relations)) { + $relations = func_get_args(); + } + $instance = new static(); + return $instance->newQuery()->with($relations); + } + public function hasOne($related, $foreignKey = null, $localKey = null) + { + $foreignKey = $foreignKey ?: $this->getForeignKey(); + $instance = new $related(); + $localKey = $localKey ?: $this->getKeyName(); + return new HasOne($instance->newQuery(), $this, $instance->getTable() . '.' . $foreignKey, $localKey); + } + public function morphOne($related, $name, $type = null, $id = null, $localKey = null) + { + $instance = new $related(); + list($type, $id) = $this->getMorphs($name, $type, $id); + $table = $instance->getTable(); + $localKey = $localKey ?: $this->getKeyName(); + return new MorphOne($instance->newQuery(), $this, $table . '.' . $type, $table . '.' . $id, $localKey); + } + public function belongsTo($related, $foreignKey = null, $otherKey = null, $relation = null) + { + if (is_null($relation)) { + list(, $caller) = debug_backtrace(false); + $relation = $caller['function']; + } + if (is_null($foreignKey)) { + $foreignKey = snake_case($relation) . '_id'; + } + $instance = new $related(); + $query = $instance->newQuery(); + $otherKey = $otherKey ?: $instance->getKeyName(); + return new BelongsTo($query, $this, $foreignKey, $otherKey, $relation); + } + public function morphTo($name = null, $type = null, $id = null) + { + if (is_null($name)) { + list(, $caller) = debug_backtrace(false); + $name = snake_case($caller['function']); + } + list($type, $id) = $this->getMorphs($name, $type, $id); + if (is_null($class = $this->{$type})) { + return new MorphTo($this->newQuery(), $this, $id, null, $type, $name); + } else { + $instance = new $class(); + return new MorphTo($instance->newQuery(), $this, $id, $instance->getKeyName(), $type, $name); + } + } + public function hasMany($related, $foreignKey = null, $localKey = null) + { + $foreignKey = $foreignKey ?: $this->getForeignKey(); + $instance = new $related(); + $localKey = $localKey ?: $this->getKeyName(); + return new HasMany($instance->newQuery(), $this, $instance->getTable() . '.' . $foreignKey, $localKey); + } + public function hasManyThrough($related, $through, $firstKey = null, $secondKey = null) + { + $through = new $through(); + $firstKey = $firstKey ?: $this->getForeignKey(); + $secondKey = $secondKey ?: $through->getForeignKey(); + return new HasManyThrough((new $related())->newQuery(), $this, $through, $firstKey, $secondKey); + } + public function morphMany($related, $name, $type = null, $id = null, $localKey = null) + { + $instance = new $related(); + list($type, $id) = $this->getMorphs($name, $type, $id); + $table = $instance->getTable(); + $localKey = $localKey ?: $this->getKeyName(); + return new MorphMany($instance->newQuery(), $this, $table . '.' . $type, $table . '.' . $id, $localKey); + } + public function belongsToMany($related, $table = null, $foreignKey = null, $otherKey = null, $relation = null) + { + if (is_null($relation)) { + $relation = $this->getBelongsToManyCaller(); + } + $foreignKey = $foreignKey ?: $this->getForeignKey(); + $instance = new $related(); + $otherKey = $otherKey ?: $instance->getForeignKey(); + if (is_null($table)) { + $table = $this->joiningTable($related); + } + $query = $instance->newQuery(); + return new BelongsToMany($query, $this, $table, $foreignKey, $otherKey, $relation); + } + public function morphToMany($related, $name, $table = null, $foreignKey = null, $otherKey = null, $inverse = false) + { + $caller = $this->getBelongsToManyCaller(); + $foreignKey = $foreignKey ?: $name . '_id'; + $instance = new $related(); + $otherKey = $otherKey ?: $instance->getForeignKey(); + $query = $instance->newQuery(); + $table = $table ?: str_plural($name); + return new MorphToMany($query, $this, $name, $table, $foreignKey, $otherKey, $caller, $inverse); + } + public function morphedByMany($related, $name, $table = null, $foreignKey = null, $otherKey = null) + { + $foreignKey = $foreignKey ?: $this->getForeignKey(); + $otherKey = $otherKey ?: $name . '_id'; + return $this->morphToMany($related, $name, $table, $foreignKey, $otherKey, true); + } + protected function getBelongsToManyCaller() + { + $self = __FUNCTION__; + $caller = array_first(debug_backtrace(false), function ($key, $trace) use($self) { + $caller = $trace['function']; + return !in_array($caller, Model::$manyMethods) && $caller != $self; + }); + return !is_null($caller) ? $caller['function'] : null; + } + public function joiningTable($related) + { + $base = snake_case(class_basename($this)); + $related = snake_case(class_basename($related)); + $models = array($related, $base); + sort($models); + return strtolower(implode('_', $models)); + } + public static function destroy($ids) + { + $count = 0; + $ids = is_array($ids) ? $ids : func_get_args(); + $instance = new static(); + $key = $instance->getKeyName(); + foreach ($instance->whereIn($key, $ids)->get() as $model) { + if ($model->delete()) { + $count++; + } + } + return $count; + } + public function delete() + { + if (is_null($this->primaryKey)) { + throw new \Exception('No primary key defined on model.'); + } + if ($this->exists) { + if ($this->fireModelEvent('deleting') === false) { + return false; + } + $this->touchOwners(); + $this->performDeleteOnModel(); + $this->exists = false; + $this->fireModelEvent('deleted', false); + return true; + } + } + public function forceDelete() + { + return $this->delete(); + } + protected function performDeleteOnModel() + { + $this->newQuery()->where($this->getKeyName(), $this->getKey())->delete(); + } + public static function saving($callback) + { + static::registerModelEvent('saving', $callback); + } + public static function saved($callback) + { + static::registerModelEvent('saved', $callback); + } + public static function updating($callback) + { + static::registerModelEvent('updating', $callback); + } + public static function updated($callback) + { + static::registerModelEvent('updated', $callback); + } + public static function creating($callback) + { + static::registerModelEvent('creating', $callback); + } + public static function created($callback) + { + static::registerModelEvent('created', $callback); + } + public static function deleting($callback) + { + static::registerModelEvent('deleting', $callback); + } + public static function deleted($callback) + { + static::registerModelEvent('deleted', $callback); + } + public static function flushEventListeners() + { + if (!isset(static::$dispatcher)) { + return; + } + $instance = new static(); + foreach ($instance->getObservableEvents() as $event) { + static::$dispatcher->forget("eloquent.{$event}: " . get_called_class()); + } + } + protected static function registerModelEvent($event, $callback) + { + if (isset(static::$dispatcher)) { + $name = get_called_class(); + static::$dispatcher->listen("eloquent.{$event}: {$name}", $callback); + } + } + public function getObservableEvents() + { + return array_merge(array('creating', 'created', 'updating', 'updated', 'deleting', 'deleted', 'saving', 'saved', 'restoring', 'restored'), $this->observables); + } + public function setObservableEvents(array $observables) + { + $this->observables = $observables; + } + public function addObservableEvents($observables) + { + $observables = is_array($observables) ? $observables : func_get_args(); + $this->observables = array_unique(array_merge($this->observables, $observables)); + } + public function removeObservableEvents($observables) + { + $observables = is_array($observables) ? $observables : func_get_args(); + $this->observables = array_diff($this->observables, $observables); + } + protected function increment($column, $amount = 1) + { + return $this->incrementOrDecrement($column, $amount, 'increment'); + } + protected function decrement($column, $amount = 1) + { + return $this->incrementOrDecrement($column, $amount, 'decrement'); + } + protected function incrementOrDecrement($column, $amount, $method) + { + $query = $this->newQuery(); + if (!$this->exists) { + return $query->{$method}($column, $amount); + } + $this->incrementOrDecrementAttributeValue($column, $amount, $method); + return $query->where($this->getKeyName(), $this->getKey())->{$method}($column, $amount); + } + protected function incrementOrDecrementAttributeValue($column, $amount, $method) + { + $this->{$column} = $this->{$column} + ($method == 'increment' ? $amount : $amount * -1); + $this->syncOriginalAttribute($column); + } + public function update(array $attributes = array()) + { + if (!$this->exists) { + return $this->newQuery()->update($attributes); + } + return $this->fill($attributes)->save(); + } + public function push() + { + if (!$this->save()) { + return false; + } + foreach ($this->relations as $models) { + foreach (Collection::make($models) as $model) { + if (!$model->push()) { + return false; + } + } + } + return true; + } + public function save(array $options = array()) + { + $query = $this->newQueryWithoutScopes(); + if ($this->fireModelEvent('saving') === false) { + return false; + } + if ($this->exists) { + $saved = $this->performUpdate($query, $options); + } else { + $saved = $this->performInsert($query, $options); + } + if ($saved) { + $this->finishSave($options); + } + return $saved; + } + protected function finishSave(array $options) + { + $this->fireModelEvent('saved', false); + $this->syncOriginal(); + if (array_get($options, 'touch', true)) { + $this->touchOwners(); + } + } + protected function performUpdate(Builder $query, array $options) + { + $dirty = $this->getDirty(); + if (count($dirty) > 0) { + if ($this->fireModelEvent('updating') === false) { + return false; + } + if ($this->timestamps && array_get($options, 'timestamps', true)) { + $this->updateTimestamps(); + } + $dirty = $this->getDirty(); + if (count($dirty) > 0) { + $this->setKeysForSaveQuery($query)->update($dirty); + $this->fireModelEvent('updated', false); + } + } + return true; + } + protected function performInsert(Builder $query, array $options) + { + if ($this->fireModelEvent('creating') === false) { + return false; + } + if ($this->timestamps && array_get($options, 'timestamps', true)) { + $this->updateTimestamps(); + } + $attributes = $this->attributes; + if ($this->incrementing) { + $this->insertAndSetId($query, $attributes); + } else { + $query->insert($attributes); + } + $this->exists = true; + $this->fireModelEvent('created', false); + return true; + } + protected function insertAndSetId(Builder $query, $attributes) + { + $id = $query->insertGetId($attributes, $keyName = $this->getKeyName()); + $this->setAttribute($keyName, $id); + } + public function touchOwners() + { + foreach ($this->touches as $relation) { + $this->{$relation}()->touch(); + if (!is_null($this->{$relation})) { + $this->{$relation}->touchOwners(); + } + } + } + public function touches($relation) + { + return in_array($relation, $this->touches); + } + protected function fireModelEvent($event, $halt = true) + { + if (!isset(static::$dispatcher)) { + return true; + } + $event = "eloquent.{$event}: " . get_class($this); + $method = $halt ? 'until' : 'fire'; + return static::$dispatcher->{$method}($event, $this); + } + protected function setKeysForSaveQuery(Builder $query) + { + $query->where($this->getKeyName(), '=', $this->getKeyForSaveQuery()); + return $query; + } + protected function getKeyForSaveQuery() + { + if (isset($this->original[$this->getKeyName()])) { + return $this->original[$this->getKeyName()]; + } + return $this->getAttribute($this->getKeyName()); + } + public function touch() + { + $this->updateTimestamps(); + return $this->save(); + } + protected function updateTimestamps() + { + $time = $this->freshTimestamp(); + if (!$this->isDirty(static::UPDATED_AT)) { + $this->setUpdatedAt($time); + } + if (!$this->exists && !$this->isDirty(static::CREATED_AT)) { + $this->setCreatedAt($time); + } + } + public function setCreatedAt($value) + { + $this->{static::CREATED_AT} = $value; + } + public function setUpdatedAt($value) + { + $this->{static::UPDATED_AT} = $value; + } + public function getCreatedAtColumn() + { + return static::CREATED_AT; + } + public function getUpdatedAtColumn() + { + return static::UPDATED_AT; + } + public function freshTimestamp() + { + return new Carbon(); + } + public function freshTimestampString() + { + return $this->fromDateTime($this->freshTimestamp()); + } + public function newQuery() + { + $builder = $this->newEloquentBuilder($this->newBaseQueryBuilder()); + $builder->setModel($this)->with($this->with); + return $this->applyGlobalScopes($builder); + } + public function newQueryWithoutScope($scope) + { + $this->getGlobalScope($scope)->remove($builder = $this->newQuery(), $this); + return $builder; + } + public function newQueryWithoutScopes() + { + return $this->removeGlobalScopes($this->newQuery()); + } + public function applyGlobalScopes($builder) + { + foreach ($this->getGlobalScopes() as $scope) { + $scope->apply($builder, $this); + } + return $builder; + } + public function removeGlobalScopes($builder) + { + foreach ($this->getGlobalScopes() as $scope) { + $scope->remove($builder, $this); + } + return $builder; + } + public function newEloquentBuilder($query) + { + return new Builder($query); + } + protected function newBaseQueryBuilder() + { + $conn = $this->getConnection(); + $grammar = $conn->getQueryGrammar(); + return new QueryBuilder($conn, $grammar, $conn->getPostProcessor()); + } + public function newCollection(array $models = array()) + { + return new Collection($models); + } + public function newPivot(Model $parent, array $attributes, $table, $exists) + { + return new Pivot($parent, $attributes, $table, $exists); + } + public function getTable() + { + if (isset($this->table)) { + return $this->table; + } + return str_replace('\\', '', snake_case(str_plural(class_basename($this)))); + } + public function setTable($table) + { + $this->table = $table; + } + public function getKey() + { + return $this->getAttribute($this->getKeyName()); + } + public function getKeyName() + { + return $this->primaryKey; + } + public function setKeyName($key) + { + $this->primaryKey = $key; + } + public function getQualifiedKeyName() + { + return $this->getTable() . '.' . $this->getKeyName(); + } + public function usesTimestamps() + { + return $this->timestamps; + } + protected function getMorphs($name, $type, $id) + { + $type = $type ?: $name . '_type'; + $id = $id ?: $name . '_id'; + return array($type, $id); + } + public function getMorphClass() + { + return $this->morphClass ?: get_class($this); + } + public function getPerPage() + { + return $this->perPage; + } + public function setPerPage($perPage) + { + $this->perPage = $perPage; + } + public function getForeignKey() + { + return snake_case(class_basename($this)) . '_id'; + } + public function getHidden() + { + return $this->hidden; + } + public function setHidden(array $hidden) + { + $this->hidden = $hidden; + } + public function setVisible(array $visible) + { + $this->visible = $visible; + } + public function setAppends(array $appends) + { + $this->appends = $appends; + } + public function getFillable() + { + return $this->fillable; + } + public function fillable(array $fillable) + { + $this->fillable = $fillable; + return $this; + } + public function guard(array $guarded) + { + $this->guarded = $guarded; + return $this; + } + public static function unguard() + { + static::$unguarded = true; + } + public static function reguard() + { + static::$unguarded = false; + } + public static function setUnguardState($state) + { + static::$unguarded = $state; + } + public function isFillable($key) + { + if (static::$unguarded) { + return true; + } + if (in_array($key, $this->fillable)) { + return true; + } + if ($this->isGuarded($key)) { + return false; + } + return empty($this->fillable) && !starts_with($key, '_'); + } + public function isGuarded($key) + { + return in_array($key, $this->guarded) || $this->guarded == array('*'); + } + public function totallyGuarded() + { + return count($this->fillable) == 0 && $this->guarded == array('*'); + } + protected function removeTableFromKey($key) + { + if (!str_contains($key, '.')) { + return $key; + } + return last(explode('.', $key)); + } + public function getTouchedRelations() + { + return $this->touches; + } + public function setTouchedRelations(array $touches) + { + $this->touches = $touches; + } + public function getIncrementing() + { + return $this->incrementing; + } + public function setIncrementing($value) + { + $this->incrementing = $value; + } + public function toJson($options = 0) + { + return json_encode($this->toArray(), $options); + } + public function jsonSerialize() + { + return $this->toArray(); + } + public function toArray() + { + $attributes = $this->attributesToArray(); + return array_merge($attributes, $this->relationsToArray()); + } + public function attributesToArray() + { + $attributes = $this->getArrayableAttributes(); + foreach ($this->getDates() as $key) { + if (!isset($attributes[$key])) { + continue; + } + $attributes[$key] = (string) $this->asDateTime($attributes[$key]); + } + foreach ($this->getMutatedAttributes() as $key) { + if (!array_key_exists($key, $attributes)) { + continue; + } + $attributes[$key] = $this->mutateAttributeForArray($key, $attributes[$key]); + } + foreach ($this->getArrayableAppends() as $key) { + $attributes[$key] = $this->mutateAttributeForArray($key, null); + } + return $attributes; + } + protected function getArrayableAttributes() + { + return $this->getArrayableItems($this->attributes); + } + protected function getArrayableAppends() + { + if (!count($this->appends)) { + return array(); + } + return $this->getArrayableItems(array_combine($this->appends, $this->appends)); + } + public function relationsToArray() + { + $attributes = array(); + foreach ($this->getArrayableRelations() as $key => $value) { + if (in_array($key, $this->hidden)) { + continue; + } + if ($value instanceof ArrayableInterface) { + $relation = $value->toArray(); + } elseif (is_null($value)) { + $relation = $value; + } + if (static::$snakeAttributes) { + $key = snake_case($key); + } + if (isset($relation) || is_null($value)) { + $attributes[$key] = $relation; + } + unset($relation); + } + return $attributes; + } + protected function getArrayableRelations() + { + return $this->getArrayableItems($this->relations); + } + protected function getArrayableItems(array $values) + { + if (count($this->visible) > 0) { + return array_intersect_key($values, array_flip($this->visible)); + } + return array_diff_key($values, array_flip($this->hidden)); + } + public function getAttribute($key) + { + $inAttributes = array_key_exists($key, $this->attributes); + if ($inAttributes || $this->hasGetMutator($key)) { + return $this->getAttributeValue($key); + } + if (array_key_exists($key, $this->relations)) { + return $this->relations[$key]; + } + $camelKey = camel_case($key); + if (method_exists($this, $camelKey)) { + return $this->getRelationshipFromMethod($key, $camelKey); + } + } + protected function getAttributeValue($key) + { + $value = $this->getAttributeFromArray($key); + if ($this->hasGetMutator($key)) { + return $this->mutateAttribute($key, $value); + } elseif (in_array($key, $this->getDates())) { + if ($value) { + return $this->asDateTime($value); + } + } + return $value; + } + protected function getAttributeFromArray($key) + { + if (array_key_exists($key, $this->attributes)) { + return $this->attributes[$key]; + } + } + protected function getRelationshipFromMethod($key, $camelKey) + { + $relations = $this->{$camelKey}(); + if (!$relations instanceof Relation) { + throw new LogicException('Relationship method must return an object of type ' . 'Illuminate\\Database\\Eloquent\\Relations\\Relation'); + } + return $this->relations[$key] = $relations->getResults(); + } + public function hasGetMutator($key) + { + return method_exists($this, 'get' . studly_case($key) . 'Attribute'); + } + protected function mutateAttribute($key, $value) + { + return $this->{'get' . studly_case($key) . 'Attribute'}($value); + } + protected function mutateAttributeForArray($key, $value) + { + $value = $this->mutateAttribute($key, $value); + return $value instanceof ArrayableInterface ? $value->toArray() : $value; + } + public function setAttribute($key, $value) + { + if ($this->hasSetMutator($key)) { + $method = 'set' . studly_case($key) . 'Attribute'; + return $this->{$method}($value); + } elseif (in_array($key, $this->getDates()) && $value) { + $value = $this->fromDateTime($value); + } + $this->attributes[$key] = $value; + } + public function hasSetMutator($key) + { + return method_exists($this, 'set' . studly_case($key) . 'Attribute'); + } + public function getDates() + { + $defaults = array(static::CREATED_AT, static::UPDATED_AT); + return array_merge($this->dates, $defaults); + } + public function fromDateTime($value) + { + $format = $this->getDateFormat(); + if ($value instanceof DateTime) { + + } elseif (is_numeric($value)) { + $value = Carbon::createFromTimestamp($value); + } elseif (preg_match('/^(\\d{4})-(\\d{2})-(\\d{2})$/', $value)) { + $value = Carbon::createFromFormat('Y-m-d', $value)->startOfDay(); + } else { + $value = Carbon::createFromFormat($format, $value); + } + return $value->format($format); + } + protected function asDateTime($value) + { + if (is_numeric($value)) { + return Carbon::createFromTimestamp($value); + } elseif (preg_match('/^(\\d{4})-(\\d{2})-(\\d{2})$/', $value)) { + return Carbon::createFromFormat('Y-m-d', $value)->startOfDay(); + } elseif (!$value instanceof DateTime) { + $format = $this->getDateFormat(); + return Carbon::createFromFormat($format, $value); + } + return Carbon::instance($value); + } + protected function getDateFormat() + { + return $this->getConnection()->getQueryGrammar()->getDateFormat(); + } + public function replicate(array $except = null) + { + $except = $except ?: array($this->getKeyName(), $this->getCreatedAtColumn(), $this->getUpdatedAtColumn()); + $attributes = array_except($this->attributes, $except); + with($instance = new static())->setRawAttributes($attributes); + return $instance->setRelations($this->relations); + } + public function getAttributes() + { + return $this->attributes; + } + public function setRawAttributes(array $attributes, $sync = false) + { + $this->attributes = $attributes; + if ($sync) { + $this->syncOriginal(); + } + } + public function getOriginal($key = null, $default = null) + { + return array_get($this->original, $key, $default); + } + public function syncOriginal() + { + $this->original = $this->attributes; + return $this; + } + public function syncOriginalAttribute($attribute) + { + $this->original[$attribute] = $this->attributes[$attribute]; + return $this; + } + public function isDirty($attributes = null) + { + $dirty = $this->getDirty(); + if (is_null($attributes)) { + return count($dirty) > 0; + } + if (!is_array($attributes)) { + $attributes = func_get_args(); + } + foreach ($attributes as $attribute) { + if (array_key_exists($attribute, $dirty)) { + return true; + } + } + return false; + } + public function getDirty() + { + $dirty = array(); + foreach ($this->attributes as $key => $value) { + if (!array_key_exists($key, $this->original)) { + $dirty[$key] = $value; + } elseif ($value !== $this->original[$key] && !$this->originalIsNumericallyEquivalent($key)) { + $dirty[$key] = $value; + } + } + return $dirty; + } + protected function originalIsNumericallyEquivalent($key) + { + $current = $this->attributes[$key]; + $original = $this->original[$key]; + return is_numeric($current) && is_numeric($original) && strcmp((string) $current, (string) $original) === 0; + } + public function getRelations() + { + return $this->relations; + } + public function getRelation($relation) + { + return $this->relations[$relation]; + } + public function setRelation($relation, $value) + { + $this->relations[$relation] = $value; + return $this; + } + public function setRelations(array $relations) + { + $this->relations = $relations; + return $this; + } + public function getConnection() + { + return static::resolveConnection($this->connection); + } + public function getConnectionName() + { + return $this->connection; + } + public function setConnection($name) + { + $this->connection = $name; + return $this; + } + public static function resolveConnection($connection = null) + { + return static::$resolver->connection($connection); + } + public static function getConnectionResolver() + { + return static::$resolver; + } + public static function setConnectionResolver(Resolver $resolver) + { + static::$resolver = $resolver; + } + public static function unsetConnectionResolver() + { + static::$resolver = null; + } + public static function getEventDispatcher() + { + return static::$dispatcher; + } + public static function setEventDispatcher(Dispatcher $dispatcher) + { + static::$dispatcher = $dispatcher; + } + public static function unsetEventDispatcher() + { + static::$dispatcher = null; + } + public function getMutatedAttributes() + { + $class = get_class($this); + if (isset(static::$mutatorCache[$class])) { + return static::$mutatorCache[$class]; + } + return array(); + } + public function __get($key) + { + return $this->getAttribute($key); + } + public function __set($key, $value) + { + $this->setAttribute($key, $value); + } + public function offsetExists($offset) + { + return isset($this->{$offset}); + } + public function offsetGet($offset) + { + return $this->{$offset}; + } + public function offsetSet($offset, $value) + { + $this->{$offset} = $value; + } + public function offsetUnset($offset) + { + unset($this->{$offset}); + } + public function __isset($key) + { + return isset($this->attributes[$key]) || isset($this->relations[$key]) || $this->hasGetMutator($key) && !is_null($this->getAttributeValue($key)); + } + public function __unset($key) + { + unset($this->attributes[$key], $this->relations[$key]); + } + public function __call($method, $parameters) + { + if (in_array($method, array('increment', 'decrement'))) { + return call_user_func_array(array($this, $method), $parameters); + } + $query = $this->newQuery(); + return call_user_func_array(array($query, $method), $parameters); + } + public static function __callStatic($method, $parameters) + { + $instance = new static(); + return call_user_func_array(array($instance, $method), $parameters); + } + public function __toString() + { + return $this->toJson(); + } + public function __wakeup() + { + $this->bootIfNotBooted(); + } +} +namespace Illuminate\Support\Contracts; + +interface ArrayableInterface +{ + public function toArray(); +} +namespace Illuminate\Support\Contracts; + +interface JsonableInterface +{ + public function toJson($options = 0); +} +namespace Illuminate\Database; + +use Illuminate\Support\Str; +use Illuminate\Database\Connectors\ConnectionFactory; +class DatabaseManager implements ConnectionResolverInterface +{ + protected $app; + protected $factory; + protected $connections = array(); + protected $extensions = array(); + public function __construct($app, ConnectionFactory $factory) + { + $this->app = $app; + $this->factory = $factory; + } + public function connection($name = null) + { + list($name, $type) = $this->parseConnectionName($name); + if (!isset($this->connections[$name])) { + $connection = $this->makeConnection($name); + $this->setPdoForType($connection, $type); + $this->connections[$name] = $this->prepare($connection); + } + return $this->connections[$name]; + } + protected function parseConnectionName($name) + { + $name = $name ?: $this->getDefaultConnection(); + return Str::endsWith($name, array('::read', '::write')) ? explode('::', $name, 2) : array($name, null); + } + public function purge($name = null) + { + $this->disconnect($name); + unset($this->connections[$name]); + } + public function disconnect($name = null) + { + if (isset($this->connections[$name = $name ?: $this->getDefaultConnection()])) { + $this->connections[$name]->disconnect(); + } + } + public function reconnect($name = null) + { + $this->disconnect($name = $name ?: $this->getDefaultConnection()); + if (!isset($this->connections[$name])) { + return $this->connection($name); + } + return $this->refreshPdoConnections($name); + } + protected function refreshPdoConnections($name) + { + $fresh = $this->makeConnection($name); + return $this->connections[$name]->setPdo($fresh->getPdo())->setReadPdo($fresh->getReadPdo()); + } + protected function makeConnection($name) + { + $config = $this->getConfig($name); + if (isset($this->extensions[$name])) { + return call_user_func($this->extensions[$name], $config, $name); + } + $driver = $config['driver']; + if (isset($this->extensions[$driver])) { + return call_user_func($this->extensions[$driver], $config, $name); + } + return $this->factory->make($config, $name); + } + protected function prepare(Connection $connection) + { + $connection->setFetchMode($this->app['config']['database.fetch']); + if ($this->app->bound('events')) { + $connection->setEventDispatcher($this->app['events']); + } + $app = $this->app; + $connection->setCacheManager(function () use($app) { + return $app['cache']; + }); + $connection->setPaginator(function () use($app) { + return $app['paginator']; + }); + $connection->setReconnector(function ($connection) { + $this->reconnect($connection->getName()); + }); + return $connection; + } + protected function setPdoForType(Connection $connection, $type = null) + { + if ($type == 'read') { + $connection->setPdo($connection->getReadPdo()); + } elseif ($type == 'write') { + $connection->setReadPdo($connection->getPdo()); + } + return $connection; + } + protected function getConfig($name) + { + $name = $name ?: $this->getDefaultConnection(); + $connections = $this->app['config']['database.connections']; + if (is_null($config = array_get($connections, $name))) { + throw new \InvalidArgumentException("Database [{$name}] not configured."); + } + return $config; + } + public function getDefaultConnection() + { + return $this->app['config']['database.default']; + } + public function setDefaultConnection($name) + { + $this->app['config']['database.default'] = $name; + } + public function extend($name, callable $resolver) + { + $this->extensions[$name] = $resolver; + } + public function getConnections() + { + return $this->connections; + } + public function __call($method, $parameters) + { + return call_user_func_array(array($this->connection(), $method), $parameters); + } +} +namespace Illuminate\Database; + +interface ConnectionResolverInterface +{ + public function connection($name = null); + public function getDefaultConnection(); + public function setDefaultConnection($name); +} +namespace Illuminate\Database\Connectors; + +use PDO; +use Illuminate\Container\Container; +use Illuminate\Database\MySqlConnection; +use Illuminate\Database\SQLiteConnection; +use Illuminate\Database\PostgresConnection; +use Illuminate\Database\SqlServerConnection; +class ConnectionFactory +{ + protected $container; + public function __construct(Container $container) + { + $this->container = $container; + } + public function make(array $config, $name = null) + { + $config = $this->parseConfig($config, $name); + if (isset($config['read'])) { + return $this->createReadWriteConnection($config); + } + return $this->createSingleConnection($config); + } + protected function createSingleConnection(array $config) + { + $pdo = $this->createConnector($config)->connect($config); + return $this->createConnection($config['driver'], $pdo, $config['database'], $config['prefix'], $config); + } + protected function createReadWriteConnection(array $config) + { + $connection = $this->createSingleConnection($this->getWriteConfig($config)); + return $connection->setReadPdo($this->createReadPdo($config)); + } + protected function createReadPdo(array $config) + { + $readConfig = $this->getReadConfig($config); + return $this->createConnector($readConfig)->connect($readConfig); + } + protected function getReadConfig(array $config) + { + $readConfig = $this->getReadWriteConfig($config, 'read'); + return $this->mergeReadWriteConfig($config, $readConfig); + } + protected function getWriteConfig(array $config) + { + $writeConfig = $this->getReadWriteConfig($config, 'write'); + return $this->mergeReadWriteConfig($config, $writeConfig); + } + protected function getReadWriteConfig(array $config, $type) + { + if (isset($config[$type][0])) { + return $config[$type][array_rand($config[$type])]; + } + return $config[$type]; + } + protected function mergeReadWriteConfig(array $config, array $merge) + { + return array_except(array_merge($config, $merge), array('read', 'write')); + } + protected function parseConfig(array $config, $name) + { + return array_add(array_add($config, 'prefix', ''), 'name', $name); + } + public function createConnector(array $config) + { + if (!isset($config['driver'])) { + throw new \InvalidArgumentException('A driver must be specified.'); + } + if ($this->container->bound($key = "db.connector.{$config['driver']}")) { + return $this->container->make($key); + } + switch ($config['driver']) { + case 'mysql': + return new MySqlConnector(); + case 'pgsql': + return new PostgresConnector(); + case 'sqlite': + return new SQLiteConnector(); + case 'sqlsrv': + return new SqlServerConnector(); + } + throw new \InvalidArgumentException("Unsupported driver [{$config['driver']}]"); + } + protected function createConnection($driver, PDO $connection, $database, $prefix = '', array $config = array()) + { + if ($this->container->bound($key = "db.connection.{$driver}")) { + return $this->container->make($key, array($connection, $database, $prefix, $config)); + } + switch ($driver) { + case 'mysql': + return new MySqlConnection($connection, $database, $prefix, $config); + case 'pgsql': + return new PostgresConnection($connection, $database, $prefix, $config); + case 'sqlite': + return new SQLiteConnection($connection, $database, $prefix, $config); + case 'sqlsrv': + return new SqlServerConnection($connection, $database, $prefix, $config); + } + throw new \InvalidArgumentException("Unsupported driver [{$driver}]"); + } +} +namespace Illuminate\Session; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\SessionInterface as BaseSessionInterface; +interface SessionInterface extends BaseSessionInterface +{ + public function getHandler(); + public function handlerNeedsRequest(); + public function setRequestOnHandler(Request $request); +} +namespace Illuminate\Session; + +use Closure; +use Carbon\Carbon; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +class Middleware implements HttpKernelInterface +{ + protected $app; + protected $manager; + protected $reject; + public function __construct(HttpKernelInterface $app, SessionManager $manager, Closure $reject = null) + { + $this->app = $app; + $this->reject = $reject; + $this->manager = $manager; + } + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + $this->checkRequestForArraySessions($request); + if ($this->sessionConfigured()) { + $session = $this->startSession($request); + $request->setSession($session); + } + $response = $this->app->handle($request, $type, $catch); + if ($this->sessionConfigured()) { + $this->closeSession($session); + $this->addCookieToResponse($response, $session); + } + return $response; + } + public function checkRequestForArraySessions(Request $request) + { + if (is_null($this->reject)) { + return; + } + if (call_user_func($this->reject, $request)) { + $this->manager->setDefaultDriver('array'); + } + } + protected function startSession(Request $request) + { + with($session = $this->getSession($request))->setRequestOnHandler($request); + $session->start(); + return $session; + } + protected function closeSession(SessionInterface $session) + { + $session->save(); + $this->collectGarbage($session); + } + protected function getUrl(Request $request) + { + $url = rtrim(preg_replace('/\\?.*/', '', $request->getUri()), '/'); + return $request->getQueryString() ? $url . '?' . $request->getQueryString() : $url; + } + protected function collectGarbage(SessionInterface $session) + { + $config = $this->manager->getSessionConfig(); + if ($this->configHitsLottery($config)) { + $session->getHandler()->gc($this->getLifetimeSeconds()); + } + } + protected function configHitsLottery(array $config) + { + return mt_rand(1, $config['lottery'][1]) <= $config['lottery'][0]; + } + protected function addCookieToResponse(Response $response, SessionInterface $session) + { + $s = $session; + if ($this->sessionIsPersistent($c = $this->manager->getSessionConfig())) { + $secure = array_get($c, 'secure', false); + $response->headers->setCookie(new Cookie($s->getName(), $s->getId(), $this->getCookieLifetime(), $c['path'], $c['domain'], $secure)); + } + } + protected function getLifetimeSeconds() + { + return array_get($this->manager->getSessionConfig(), 'lifetime') * 60; + } + protected function getCookieLifetime() + { + $config = $this->manager->getSessionConfig(); + return $config['expire_on_close'] ? 0 : Carbon::now()->addMinutes($config['lifetime']); + } + protected function sessionConfigured() + { + return !is_null(array_get($this->manager->getSessionConfig(), 'driver')); + } + protected function sessionIsPersistent(array $config = null) + { + $config = $config ?: $this->manager->getSessionConfig(); + return !in_array($config['driver'], array(null, 'array')); + } + public function getSession(Request $request) + { + $session = $this->manager->driver(); + $session->setId($request->cookies->get($session->getName())); + return $session; + } +} +namespace Illuminate\Session; + +use SessionHandlerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; +class Store implements SessionInterface +{ + protected $id; + protected $name; + protected $attributes = array(); + protected $bags = array(); + protected $metaBag; + protected $bagData = array(); + protected $handler; + protected $started = false; + public function __construct($name, SessionHandlerInterface $handler, $id = null) + { + $this->setId($id); + $this->name = $name; + $this->handler = $handler; + $this->metaBag = new MetadataBag(); + } + public function start() + { + $this->loadSession(); + if (!$this->has('_token')) { + $this->regenerateToken(); + } + return $this->started = true; + } + protected function loadSession() + { + $this->attributes = $this->readFromHandler(); + foreach (array_merge($this->bags, array($this->metaBag)) as $bag) { + $this->initializeLocalBag($bag); + $bag->initialize($this->bagData[$bag->getStorageKey()]); + } + } + protected function readFromHandler() + { + $data = $this->handler->read($this->getId()); + return $data ? unserialize($data) : array(); + } + protected function initializeLocalBag($bag) + { + $this->bagData[$bag->getStorageKey()] = $this->pull($bag->getStorageKey(), array()); + } + public function getId() + { + return $this->id; + } + public function setId($id) + { + if (!$this->isValidId($id)) { + $id = $this->generateSessionId(); + } + $this->id = $id; + } + public function isValidId($id) + { + return is_string($id) && preg_match('/^[a-f0-9]{40}$/', $id); + } + protected function generateSessionId() + { + return sha1(uniqid('', true) . str_random(25) . microtime(true)); + } + public function getName() + { + return $this->name; + } + public function setName($name) + { + $this->name = $name; + } + public function invalidate($lifetime = null) + { + $this->attributes = array(); + $this->migrate(); + return true; + } + public function migrate($destroy = false, $lifetime = null) + { + if ($destroy) { + $this->handler->destroy($this->getId()); + } + $this->setExists(false); + $this->id = $this->generateSessionId(); + return true; + } + public function regenerate($destroy = false) + { + return $this->migrate($destroy); + } + public function save() + { + $this->addBagDataToSession(); + $this->ageFlashData(); + $this->handler->write($this->getId(), serialize($this->attributes)); + $this->started = false; + } + protected function addBagDataToSession() + { + foreach (array_merge($this->bags, array($this->metaBag)) as $bag) { + $this->put($bag->getStorageKey(), $this->bagData[$bag->getStorageKey()]); + } + } + public function ageFlashData() + { + foreach ($this->get('flash.old', array()) as $old) { + $this->forget($old); + } + $this->put('flash.old', $this->get('flash.new', array())); + $this->put('flash.new', array()); + } + public function has($name) + { + return !is_null($this->get($name)); + } + public function get($name, $default = null) + { + return array_get($this->attributes, $name, $default); + } + public function pull($key, $default = null) + { + return array_pull($this->attributes, $key, $default); + } + public function hasOldInput($key = null) + { + $old = $this->getOldInput($key); + return is_null($key) ? count($old) > 0 : !is_null($old); + } + public function getOldInput($key = null, $default = null) + { + $input = $this->get('_old_input', array()); + return array_get($input, $key, $default); + } + public function set($name, $value) + { + array_set($this->attributes, $name, $value); + } + public function put($key, $value = null) + { + if (!is_array($key)) { + $key = array($key => $value); + } + foreach ($key as $arrayKey => $arrayValue) { + $this->set($arrayKey, $arrayValue); + } + } + public function push($key, $value) + { + $array = $this->get($key, array()); + $array[] = $value; + $this->put($key, $array); + } + public function flash($key, $value) + { + $this->put($key, $value); + $this->push('flash.new', $key); + $this->removeFromOldFlashData(array($key)); + } + public function flashInput(array $value) + { + $this->flash('_old_input', $value); + } + public function reflash() + { + $this->mergeNewFlashes($this->get('flash.old', array())); + $this->put('flash.old', array()); + } + public function keep($keys = null) + { + $keys = is_array($keys) ? $keys : func_get_args(); + $this->mergeNewFlashes($keys); + $this->removeFromOldFlashData($keys); + } + protected function mergeNewFlashes(array $keys) + { + $values = array_unique(array_merge($this->get('flash.new', array()), $keys)); + $this->put('flash.new', $values); + } + protected function removeFromOldFlashData(array $keys) + { + $this->put('flash.old', array_diff($this->get('flash.old', array()), $keys)); + } + public function all() + { + return $this->attributes; + } + public function replace(array $attributes) + { + foreach ($attributes as $key => $value) { + $this->put($key, $value); + } + } + public function remove($name) + { + return array_pull($this->attributes, $name); + } + public function forget($key) + { + array_forget($this->attributes, $key); + } + public function clear() + { + $this->attributes = array(); + foreach ($this->bags as $bag) { + $bag->clear(); + } + } + public function flush() + { + $this->clear(); + } + public function isStarted() + { + return $this->started; + } + public function registerBag(SessionBagInterface $bag) + { + $this->bags[$bag->getStorageKey()] = $bag; + } + public function getBag($name) + { + return array_get($this->bags, $name, function () { + throw new \InvalidArgumentException('Bag not registered.'); + }); + } + public function getMetadataBag() + { + return $this->metaBag; + } + public function getBagData($name) + { + return array_get($this->bagData, $name, array()); + } + public function token() + { + return $this->get('_token'); + } + public function getToken() + { + return $this->token(); + } + public function regenerateToken() + { + $this->put('_token', str_random(40)); + } + public function setExists($value) + { + if ($this->handler instanceof ExistenceAwareInterface) { + $this->handler->setExists($value); + } + } + public function getHandler() + { + return $this->handler; + } + public function handlerNeedsRequest() + { + return $this->handler instanceof CookieSessionHandler; + } + public function setRequestOnHandler(Request $request) + { + if ($this->handlerNeedsRequest()) { + $this->handler->setRequest($request); + } + } +} +namespace Illuminate\Session; + +use Illuminate\Support\Manager; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler; +class SessionManager extends Manager +{ + protected function callCustomCreator($driver) + { + return $this->buildSession(parent::callCustomCreator($driver)); + } + protected function createArrayDriver() + { + return new Store($this->app['config']['session.cookie'], new NullSessionHandler()); + } + protected function createCookieDriver() + { + $lifetime = $this->app['config']['session.lifetime']; + return $this->buildSession(new CookieSessionHandler($this->app['cookie'], $lifetime)); + } + protected function createFileDriver() + { + return $this->createNativeDriver(); + } + protected function createNativeDriver() + { + $path = $this->app['config']['session.files']; + return $this->buildSession(new FileSessionHandler($this->app['files'], $path)); + } + protected function createDatabaseDriver() + { + $connection = $this->getDatabaseConnection(); + $table = $this->app['config']['session.table']; + return $this->buildSession(new DatabaseSessionHandler($connection, $table)); + } + protected function getDatabaseConnection() + { + $connection = $this->app['config']['session.connection']; + return $this->app['db']->connection($connection); + } + protected function createApcDriver() + { + return $this->createCacheBased('apc'); + } + protected function createMemcachedDriver() + { + return $this->createCacheBased('memcached'); + } + protected function createWincacheDriver() + { + return $this->createCacheBased('wincache'); + } + protected function createRedisDriver() + { + $handler = $this->createCacheHandler('redis'); + $handler->getCache()->getStore()->setConnection($this->app['config']['session.connection']); + return $this->buildSession($handler); + } + protected function createCacheBased($driver) + { + return $this->buildSession($this->createCacheHandler($driver)); + } + protected function createCacheHandler($driver) + { + $minutes = $this->app['config']['session.lifetime']; + return new CacheBasedSessionHandler($this->app['cache']->driver($driver), $minutes); + } + protected function buildSession($handler) + { + return new Store($this->app['config']['session.cookie'], $handler); + } + public function getSessionConfig() + { + return $this->app['config']['session']; + } + public function getDefaultDriver() + { + return $this->app['config']['session.driver']; + } + public function setDefaultDriver($name) + { + $this->app['config']['session.driver'] = $name; + } +} +namespace Illuminate\Support; + +use Closure; +abstract class Manager +{ + protected $app; + protected $customCreators = array(); + protected $drivers = array(); + public function __construct($app) + { + $this->app = $app; + } + public abstract function getDefaultDriver(); + public function driver($driver = null) + { + $driver = $driver ?: $this->getDefaultDriver(); + if (!isset($this->drivers[$driver])) { + $this->drivers[$driver] = $this->createDriver($driver); + } + return $this->drivers[$driver]; + } + protected function createDriver($driver) + { + $method = 'create' . ucfirst($driver) . 'Driver'; + if (isset($this->customCreators[$driver])) { + return $this->callCustomCreator($driver); + } elseif (method_exists($this, $method)) { + return $this->{$method}(); + } + throw new \InvalidArgumentException("Driver [{$driver}] not supported."); + } + protected function callCustomCreator($driver) + { + return $this->customCreators[$driver]($this->app); + } + public function extend($driver, Closure $callback) + { + $this->customCreators[$driver] = $callback; + return $this; + } + public function getDrivers() + { + return $this->drivers; + } + public function __call($method, $parameters) + { + return call_user_func_array(array($this->driver(), $method), $parameters); + } +} +namespace Illuminate\Cookie; + +use Symfony\Component\HttpFoundation\Cookie; +class CookieJar +{ + protected $path = '/'; + protected $domain = null; + protected $queued = array(); + public function make($name, $value, $minutes = 0, $path = null, $domain = null, $secure = false, $httpOnly = true) + { + list($path, $domain) = $this->getPathAndDomain($path, $domain); + $time = $minutes == 0 ? 0 : time() + $minutes * 60; + return new Cookie($name, $value, $time, $path, $domain, $secure, $httpOnly); + } + public function forever($name, $value, $path = null, $domain = null, $secure = false, $httpOnly = true) + { + return $this->make($name, $value, 2628000, $path, $domain, $secure, $httpOnly); + } + public function forget($name, $path = null, $domain = null) + { + return $this->make($name, null, -2628000, $path, $domain); + } + public function hasQueued($key) + { + return !is_null($this->queued($key)); + } + public function queued($key, $default = null) + { + return array_get($this->queued, $key, $default); + } + public function queue() + { + if (head(func_get_args()) instanceof Cookie) { + $cookie = head(func_get_args()); + } else { + $cookie = call_user_func_array(array($this, 'make'), func_get_args()); + } + $this->queued[$cookie->getName()] = $cookie; + } + public function unqueue($name) + { + unset($this->queued[$name]); + } + protected function getPathAndDomain($path, $domain) + { + return array($path ?: $this->path, $domain ?: $this->domain); + } + public function setDefaultPathAndDomain($path, $domain) + { + list($this->path, $this->domain) = array($path, $domain); + return $this; + } + public function getQueuedCookies() + { + return $this->queued; + } +} +namespace Illuminate\Cookie; + +use Illuminate\Encryption\Encrypter; +use Illuminate\Encryption\DecryptException; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +class Guard implements HttpKernelInterface +{ + protected $app; + protected $encrypter; + public function __construct(HttpKernelInterface $app, Encrypter $encrypter) + { + $this->app = $app; + $this->encrypter = $encrypter; + } + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + return $this->encrypt($this->app->handle($this->decrypt($request), $type, $catch)); + } + protected function decrypt(Request $request) + { + foreach ($request->cookies as $key => $c) { + try { + $request->cookies->set($key, $this->decryptCookie($c)); + } catch (DecryptException $e) { + $request->cookies->set($key, null); + } + } + return $request; + } + protected function decryptCookie($cookie) + { + return is_array($cookie) ? $this->decryptArray($cookie) : $this->encrypter->decrypt($cookie); + } + protected function decryptArray(array $cookie) + { + $decrypted = array(); + foreach ($cookie as $key => $value) { + $decrypted[$key] = $this->encrypter->decrypt($value); + } + return $decrypted; + } + protected function encrypt(Response $response) + { + foreach ($response->headers->getCookies() as $key => $c) { + $encrypted = $this->encrypter->encrypt($c->getValue()); + $response->headers->setCookie($this->duplicate($c, $encrypted)); + } + return $response; + } + protected function duplicate(Cookie $c, $value) + { + return new Cookie($c->getName(), $value, $c->getExpiresTime(), $c->getPath(), $c->getDomain(), $c->isSecure(), $c->isHttpOnly()); + } +} +namespace Illuminate\Cookie; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; +class Queue implements HttpKernelInterface +{ + protected $app; + protected $cookies; + public function __construct(HttpKernelInterface $app, CookieJar $cookies) + { + $this->app = $app; + $this->cookies = $cookies; + } + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + $response = $this->app->handle($request, $type, $catch); + foreach ($this->cookies->getQueuedCookies() as $cookie) { + $response->headers->setCookie($cookie); + } + return $response; + } +} +namespace Illuminate\Encryption; + +use Symfony\Component\Security\Core\Util\StringUtils; +use Symfony\Component\Security\Core\Util\SecureRandom; +class Encrypter +{ + protected $key; + protected $cipher = MCRYPT_RIJNDAEL_128; + protected $mode = MCRYPT_MODE_CBC; + protected $block = 16; + public function __construct($key) + { + $this->key = $key; + } + public function encrypt($value) + { + $iv = mcrypt_create_iv($this->getIvSize(), $this->getRandomizer()); + $value = base64_encode($this->padAndMcrypt($value, $iv)); + $mac = $this->hash($iv = base64_encode($iv), $value); + return base64_encode(json_encode(compact('iv', 'value', 'mac'))); + } + protected function padAndMcrypt($value, $iv) + { + $value = $this->addPadding(serialize($value)); + return mcrypt_encrypt($this->cipher, $this->key, $value, $this->mode, $iv); + } + public function decrypt($payload) + { + $payload = $this->getJsonPayload($payload); + $value = base64_decode($payload['value']); + $iv = base64_decode($payload['iv']); + return unserialize($this->stripPadding($this->mcryptDecrypt($value, $iv))); + } + protected function mcryptDecrypt($value, $iv) + { + try { + return mcrypt_decrypt($this->cipher, $this->key, $value, $this->mode, $iv); + } catch (\Exception $e) { + throw new DecryptException($e->getMessage()); + } + } + protected function getJsonPayload($payload) + { + $payload = json_decode(base64_decode($payload), true); + if (!$payload || $this->invalidPayload($payload)) { + throw new DecryptException('Invalid data.'); + } + if (!$this->validMac($payload)) { + throw new DecryptException('MAC is invalid.'); + } + return $payload; + } + protected function validMac(array $payload) + { + if (!function_exists('openssl_random_pseudo_bytes')) { + throw new \RuntimeException('OpenSSL extension is required.'); + } + $bytes = (new SecureRandom())->nextBytes(16); + $calcMac = hash_hmac('sha256', $this->hash($payload['iv'], $payload['value']), $bytes, true); + return StringUtils::equals(hash_hmac('sha256', $payload['mac'], $bytes, true), $calcMac); + } + protected function hash($iv, $value) + { + return hash_hmac('sha256', $iv . $value, $this->key); + } + protected function addPadding($value) + { + $pad = $this->block - strlen($value) % $this->block; + return $value . str_repeat(chr($pad), $pad); + } + protected function stripPadding($value) + { + $pad = ord($value[($len = strlen($value)) - 1]); + return $this->paddingIsValid($pad, $value) ? substr($value, 0, $len - $pad) : $value; + } + protected function paddingIsValid($pad, $value) + { + $beforePad = strlen($value) - $pad; + return substr($value, $beforePad) == str_repeat(substr($value, -1), $pad); + } + protected function invalidPayload($data) + { + return !is_array($data) || !isset($data['iv']) || !isset($data['value']) || !isset($data['mac']); + } + protected function getIvSize() + { + return mcrypt_get_iv_size($this->cipher, $this->mode); + } + protected function getRandomizer() + { + if (defined('MCRYPT_DEV_URANDOM')) { + return MCRYPT_DEV_URANDOM; + } + if (defined('MCRYPT_DEV_RANDOM')) { + return MCRYPT_DEV_RANDOM; + } + mt_srand(); + return MCRYPT_RAND; + } + public function setKey($key) + { + $this->key = $key; + } + public function setCipher($cipher) + { + $this->cipher = $cipher; + $this->updateBlockSize(); + } + public function setMode($mode) + { + $this->mode = $mode; + $this->updateBlockSize(); + } + protected function updateBlockSize() + { + $this->block = mcrypt_get_iv_size($this->cipher, $this->mode); + } +} +namespace Illuminate\Support\Facades; + +class Log extends Facade +{ + protected static function getFacadeAccessor() + { + return 'log'; + } +} +namespace Illuminate\Log; + +use Monolog\Logger; +use Illuminate\Support\ServiceProvider; +class LogServiceProvider extends ServiceProvider +{ + protected $defer = true; + public function register() + { + $logger = new Writer(new Logger($this->app['env']), $this->app['events']); + $this->app->instance('log', $logger); + $this->app->bind('Psr\\Log\\LoggerInterface', function ($app) { + return $app['log']->getMonolog(); + }); + if (isset($this->app['log.setup'])) { + call_user_func($this->app['log.setup'], $logger); + } + } + public function provides() + { + return array('log', 'Psr\\Log\\LoggerInterface'); + } +} +namespace Illuminate\Log; + +use Closure; +use Illuminate\Events\Dispatcher; +use Monolog\Handler\StreamHandler; +use Monolog\Logger as MonologLogger; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\ErrorLogHandler; +use Monolog\Handler\RotatingFileHandler; +use Illuminate\Support\Contracts\JsonableInterface; +use Illuminate\Support\Contracts\ArrayableInterface; +class Writer +{ + protected $monolog; + protected $levels = array('debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency'); + protected $dispatcher; + public function __construct(MonologLogger $monolog, Dispatcher $dispatcher = null) + { + $this->monolog = $monolog; + if (isset($dispatcher)) { + $this->dispatcher = $dispatcher; + } + } + protected function callMonolog($method, $parameters) + { + if (is_array($parameters[0])) { + $parameters[0] = json_encode($parameters[0]); + } + return call_user_func_array(array($this->monolog, $method), $parameters); + } + public function useFiles($path, $level = 'debug') + { + $level = $this->parseLevel($level); + $this->monolog->pushHandler($handler = new StreamHandler($path, $level)); + $handler->setFormatter($this->getDefaultFormatter()); + } + public function useDailyFiles($path, $days = 0, $level = 'debug') + { + $level = $this->parseLevel($level); + $this->monolog->pushHandler($handler = new RotatingFileHandler($path, $days, $level)); + $handler->setFormatter($this->getDefaultFormatter()); + } + public function useErrorLog($level = 'debug', $messageType = ErrorLogHandler::OPERATING_SYSTEM) + { + $level = $this->parseLevel($level); + $this->monolog->pushHandler($handler = new ErrorLogHandler($messageType, $level)); + $handler->setFormatter($this->getDefaultFormatter()); + } + protected function getDefaultFormatter() + { + return new LineFormatter(null, null, true); + } + protected function parseLevel($level) + { + switch ($level) { + case 'debug': + return MonologLogger::DEBUG; + case 'info': + return MonologLogger::INFO; + case 'notice': + return MonologLogger::NOTICE; + case 'warning': + return MonologLogger::WARNING; + case 'error': + return MonologLogger::ERROR; + case 'critical': + return MonologLogger::CRITICAL; + case 'alert': + return MonologLogger::ALERT; + case 'emergency': + return MonologLogger::EMERGENCY; + default: + throw new \InvalidArgumentException('Invalid log level.'); + } + } + public function listen(Closure $callback) + { + if (!isset($this->dispatcher)) { + throw new \RuntimeException('Events dispatcher has not been set.'); + } + $this->dispatcher->listen('illuminate.log', $callback); + } + public function getMonolog() + { + return $this->monolog; + } + public function getEventDispatcher() + { + return $this->dispatcher; + } + public function setEventDispatcher(Dispatcher $dispatcher) + { + $this->dispatcher = $dispatcher; + } + protected function fireLogEvent($level, $message, array $context = array()) + { + if (isset($this->dispatcher)) { + $this->dispatcher->fire('illuminate.log', compact('level', 'message', 'context')); + } + } + public function write() + { + $level = head(func_get_args()); + return call_user_func_array(array($this, $level), array_slice(func_get_args(), 1)); + } + public function __call($method, $parameters) + { + if (in_array($method, $this->levels)) { + $this->formatParameters($parameters); + call_user_func_array(array($this, 'fireLogEvent'), array_merge(array($method), $parameters)); + $method = 'add' . ucfirst($method); + return $this->callMonolog($method, $parameters); + } + throw new \BadMethodCallException("Method [{$method}] does not exist."); + } + protected function formatParameters(&$parameters) + { + if (isset($parameters[0])) { + if (is_array($parameters[0])) { + $parameters[0] = var_export($parameters[0], true); + } elseif ($parameters[0] instanceof JsonableInterface) { + $parameters[0] = $parameters[0]->toJson(); + } elseif ($parameters[0] instanceof ArrayableInterface) { + $parameters[0] = var_export($parameters[0]->toArray(), true); + } + } + } +} +namespace Monolog; + +use Monolog\Handler\HandlerInterface; +use Monolog\Handler\StreamHandler; +use Psr\Log\LoggerInterface; +use Psr\Log\InvalidArgumentException; +class Logger implements LoggerInterface +{ + const DEBUG = 100; + const INFO = 200; + const NOTICE = 250; + const WARNING = 300; + const ERROR = 400; + const CRITICAL = 500; + const ALERT = 550; + const EMERGENCY = 600; + const API = 1; + protected static $levels = array(100 => 'DEBUG', 200 => 'INFO', 250 => 'NOTICE', 300 => 'WARNING', 400 => 'ERROR', 500 => 'CRITICAL', 550 => 'ALERT', 600 => 'EMERGENCY'); + protected static $timezone; + protected $name; + protected $handlers; + protected $processors; + public function __construct($name, array $handlers = array(), array $processors = array()) + { + $this->name = $name; + $this->handlers = $handlers; + $this->processors = $processors; + } + public function getName() + { + return $this->name; + } + public function pushHandler(HandlerInterface $handler) + { + array_unshift($this->handlers, $handler); + } + public function popHandler() + { + if (!$this->handlers) { + throw new \LogicException('You tried to pop from an empty handler stack.'); + } + return array_shift($this->handlers); + } + public function getHandlers() + { + return $this->handlers; + } + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), ' . var_export($callback, true) . ' given'); + } + array_unshift($this->processors, $callback); + } + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + return array_shift($this->processors); + } + public function getProcessors() + { + return $this->processors; + } + public function addRecord($level, $message, array $context = array()) + { + if (!$this->handlers) { + $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); + } + $levelName = static::getLevelName($level); + $handlerKey = null; + foreach ($this->handlers as $key => $handler) { + if ($handler->isHandling(array('level' => $level))) { + $handlerKey = $key; + break; + } + } + if (null === $handlerKey) { + return false; + } + if (!static::$timezone) { + static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); + } + $record = array('message' => (string) $message, 'context' => $context, 'level' => $level, 'level_name' => $levelName, 'channel' => $this->name, 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone)->setTimezone(static::$timezone), 'extra' => array()); + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + while (isset($this->handlers[$handlerKey]) && false === $this->handlers[$handlerKey]->handle($record)) { + $handlerKey++; + } + return true; + } + public function addDebug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + public function addInfo($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + public function addNotice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + public function addWarning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + public function addError($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + public function addCritical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + public function addAlert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + public function addEmergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + public static function getLevels() + { + return array_flip(static::$levels); + } + public static function getLevelName($level) + { + if (!isset(static::$levels[$level])) { + throw new InvalidArgumentException('Level "' . $level . '" is not defined, use one of: ' . implode(', ', array_keys(static::$levels))); + } + return static::$levels[$level]; + } + public static function toMonologLevel($level) + { + if (is_string($level) && defined(__CLASS__ . '::' . strtoupper($level))) { + return constant(__CLASS__ . '::' . strtoupper($level)); + } + return $level; + } + public function isHandling($level) + { + $record = array('level' => $level); + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + return false; + } + public function log($level, $message, array $context = array()) + { + if (is_string($level) && defined(__CLASS__ . '::' . strtoupper($level))) { + $level = constant(__CLASS__ . '::' . strtoupper($level)); + } + return $this->addRecord($level, $message, $context); + } + public function debug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + public function info($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + public function notice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + public function warn($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + public function warning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + public function err($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + public function error($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + public function crit($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + public function critical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + public function alert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + public function emerg($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + public function emergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } +} +namespace Psr\Log; + +interface LoggerInterface +{ + public function emergency($message, array $context = array()); + public function alert($message, array $context = array()); + public function critical($message, array $context = array()); + public function error($message, array $context = array()); + public function warning($message, array $context = array()); + public function notice($message, array $context = array()); + public function info($message, array $context = array()); + public function debug($message, array $context = array()); + public function log($level, $message, array $context = array()); +} +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; +abstract class AbstractHandler implements HandlerInterface +{ + protected $level = Logger::DEBUG; + protected $bubble = true; + protected $formatter; + protected $processors = array(); + public function __construct($level = Logger::DEBUG, $bubble = true) + { + $this->setLevel($level); + $this->bubble = $bubble; + } + public function isHandling(array $record) + { + return $record['level'] >= $this->level; + } + public function handleBatch(array $records) + { + foreach ($records as $record) { + $this->handle($record); + } + } + public function close() + { + + } + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), ' . var_export($callback, true) . ' given'); + } + array_unshift($this->processors, $callback); + return $this; + } + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + return array_shift($this->processors); + } + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + return $this; + } + public function getFormatter() + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + return $this->formatter; + } + public function setLevel($level) + { + $this->level = Logger::toMonologLevel($level); + return $this; + } + public function getLevel() + { + return $this->level; + } + public function setBubble($bubble) + { + $this->bubble = $bubble; + return $this; + } + public function getBubble() + { + return $this->bubble; + } + public function __destruct() + { + try { + $this->close(); + } catch (\Exception $e) { + + } + } + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} +namespace Monolog\Handler; + +abstract class AbstractProcessingHandler extends AbstractHandler +{ + public function handle(array $record) + { + if (!$this->isHandling($record)) { + return false; + } + $record = $this->processRecord($record); + $record['formatted'] = $this->getFormatter()->format($record); + $this->write($record); + return false === $this->bubble; + } + protected abstract function write(array $record); + protected function processRecord(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + return $record; + } +} +namespace Monolog\Handler; + +use Monolog\Logger; +class StreamHandler extends AbstractProcessingHandler +{ + protected $stream; + protected $url; + private $errorMessage; + protected $filePermission; + protected $useLocking; + public function __construct($stream, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) + { + parent::__construct($level, $bubble); + if (is_resource($stream)) { + $this->stream = $stream; + } elseif (is_string($stream)) { + $this->url = $stream; + } else { + throw new \InvalidArgumentException('A stream must either be a resource or a string.'); + } + $this->filePermission = $filePermission; + $this->useLocking = $useLocking; + } + public function close() + { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->stream = null; + } + protected function write(array $record) + { + if (!is_resource($this->stream)) { + if (!$this->url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); + } + $this->errorMessage = null; + set_error_handler(array($this, 'customErrorHandler')); + $this->stream = fopen($this->url, 'a'); + if ($this->filePermission !== null) { + @chmod($this->url, $this->filePermission); + } + restore_error_handler(); + if (!is_resource($this->stream)) { + $this->stream = null; + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: ' . $this->errorMessage, $this->url)); + } + } + if ($this->useLocking) { + flock($this->stream, LOCK_EX); + } + fwrite($this->stream, (string) $record['formatted']); + if ($this->useLocking) { + flock($this->stream, LOCK_UN); + } + } + private function customErrorHandler($code, $msg) + { + $this->errorMessage = preg_replace('{^fopen\\(.*?\\): }', '', $msg); + } +} +namespace Monolog\Handler; + +use Monolog\Logger; +class RotatingFileHandler extends StreamHandler +{ + protected $filename; + protected $maxFiles; + protected $mustRotate; + protected $nextRotation; + protected $filenameFormat; + protected $dateFormat; + public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false) + { + $this->filename = $filename; + $this->maxFiles = (int) $maxFiles; + $this->nextRotation = new \DateTime('tomorrow'); + $this->filenameFormat = '{filename}-{date}'; + $this->dateFormat = 'Y-m-d'; + parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); + } + public function close() + { + parent::close(); + if (true === $this->mustRotate) { + $this->rotate(); + } + } + public function setFilenameFormat($filenameFormat, $dateFormat) + { + $this->filenameFormat = $filenameFormat; + $this->dateFormat = $dateFormat; + $this->url = $this->getTimedFilename(); + $this->close(); + } + protected function write(array $record) + { + if (null === $this->mustRotate) { + $this->mustRotate = !file_exists($this->url); + } + if ($this->nextRotation < $record['datetime']) { + $this->mustRotate = true; + $this->close(); + } + parent::write($record); + } + protected function rotate() + { + $this->url = $this->getTimedFilename(); + $this->nextRotation = new \DateTime('tomorrow'); + if (0 === $this->maxFiles) { + return; + } + $logFiles = glob($this->getGlobPattern()); + if ($this->maxFiles >= count($logFiles)) { + return; + } + usort($logFiles, function ($a, $b) { + return strcmp($b, $a); + }); + foreach (array_slice($logFiles, $this->maxFiles) as $file) { + if (is_writable($file)) { + unlink($file); + } + } + } + protected function getTimedFilename() + { + $fileInfo = pathinfo($this->filename); + $timedFilename = str_replace(array('{filename}', '{date}'), array($fileInfo['filename'], date($this->dateFormat)), $fileInfo['dirname'] . '/' . $this->filenameFormat); + if (!empty($fileInfo['extension'])) { + $timedFilename .= '.' . $fileInfo['extension']; + } + return $timedFilename; + } + protected function getGlobPattern() + { + $fileInfo = pathinfo($this->filename); + $glob = str_replace(array('{filename}', '{date}'), array($fileInfo['filename'], '*'), $fileInfo['dirname'] . '/' . $this->filenameFormat); + if (!empty($fileInfo['extension'])) { + $glob .= '.' . $fileInfo['extension']; + } + return $glob; + } +} +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; +interface HandlerInterface +{ + public function isHandling(array $record); + public function handle(array $record); + public function handleBatch(array $records); + public function pushProcessor($callback); + public function popProcessor(); + public function setFormatter(FormatterInterface $formatter); + public function getFormatter(); +} +namespace Illuminate\Support\Facades; + +class App extends Facade +{ + protected static function getFacadeAccessor() + { + return 'app'; + } +} +namespace Illuminate\Exception; + +use Exception; +interface ExceptionDisplayerInterface +{ + public function display(Exception $exception); +} +namespace Illuminate\Exception; + +use Exception; +use Symfony\Component\Debug\ExceptionHandler; +use Symfony\Component\HttpFoundation\JsonResponse; +class SymfonyDisplayer implements ExceptionDisplayerInterface +{ + protected $symfony; + protected $returnJson; + public function __construct(ExceptionHandler $symfony, $returnJson = false) + { + $this->symfony = $symfony; + $this->returnJson = $returnJson; + } + public function display(Exception $exception) + { + if ($this->returnJson) { + return new JsonResponse(array('error' => $exception->getMessage(), 'file' => $exception->getFile(), 'line' => $exception->getLine()), 500); + } + return $this->symfony->createResponse($exception); + } +} +namespace Illuminate\Exception; + +use Exception; +use Whoops\Run; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +class WhoopsDisplayer implements ExceptionDisplayerInterface +{ + protected $whoops; + protected $runningInConsole; + public function __construct(Run $whoops, $runningInConsole) + { + $this->whoops = $whoops; + $this->runningInConsole = $runningInConsole; + } + public function display(Exception $exception) + { + $status = $exception instanceof HttpExceptionInterface ? $exception->getStatusCode() : 500; + $headers = $exception instanceof HttpExceptionInterface ? $exception->getHeaders() : array(); + return new Response($this->whoops->handleException($exception), $status, $headers); + } +} +namespace Illuminate\Exception; + +use Closure; +use ErrorException; +use ReflectionFunction; +use Illuminate\Support\Contracts\ResponsePreparerInterface; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\Debug\Exception\FatalErrorException as FatalError; +class Handler +{ + protected $responsePreparer; + protected $plainDisplayer; + protected $debugDisplayer; + protected $debug; + protected $handlers = array(); + protected $handled = array(); + public function __construct(ResponsePreparerInterface $responsePreparer, ExceptionDisplayerInterface $plainDisplayer, ExceptionDisplayerInterface $debugDisplayer, $debug = true) + { + $this->debug = $debug; + $this->plainDisplayer = $plainDisplayer; + $this->debugDisplayer = $debugDisplayer; + $this->responsePreparer = $responsePreparer; + } + public function register($environment) + { + $this->registerErrorHandler(); + $this->registerExceptionHandler(); + if ($environment != 'testing') { + $this->registerShutdownHandler(); + } + } + protected function registerErrorHandler() + { + set_error_handler(array($this, 'handleError')); + } + protected function registerExceptionHandler() + { + set_exception_handler(array($this, 'handleUncaughtException')); + } + protected function registerShutdownHandler() + { + register_shutdown_function(array($this, 'handleShutdown')); + } + public function handleError($level, $message, $file = '', $line = 0, $context = array()) + { + if (error_reporting() & $level) { + throw new ErrorException($message, 0, $level, $file, $line); + } + } + public function handleException($exception) + { + $response = $this->callCustomHandlers($exception); + if (!is_null($response)) { + return $this->prepareResponse($response); + } + return $this->displayException($exception); + } + public function handleUncaughtException($exception) + { + $this->handleException($exception)->send(); + } + public function handleShutdown() + { + $error = error_get_last(); + if (!is_null($error)) { + extract($error); + if (!$this->isFatal($type)) { + return; + } + $this->handleException(new FatalError($message, $type, 0, $file, $line))->send(); + } + } + protected function isFatal($type) + { + return in_array($type, array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE)); + } + public function handleConsole($exception) + { + return $this->callCustomHandlers($exception, true); + } + protected function callCustomHandlers($exception, $fromConsole = false) + { + foreach ($this->handlers as $handler) { + if (!$this->handlesException($handler, $exception)) { + continue; + } elseif ($exception instanceof HttpExceptionInterface) { + $code = $exception->getStatusCode(); + } else { + $code = 500; + } + try { + $response = $handler($exception, $code, $fromConsole); + } catch (\Exception $e) { + $response = $this->formatException($e); + } + if (isset($response) && !is_null($response)) { + return $response; + } + } + } + protected function displayException($exception) + { + $displayer = $this->debug ? $this->debugDisplayer : $this->plainDisplayer; + return $displayer->display($exception); + } + protected function handlesException(Closure $handler, $exception) + { + $reflection = new ReflectionFunction($handler); + return $reflection->getNumberOfParameters() == 0 || $this->hints($reflection, $exception); + } + protected function hints(ReflectionFunction $reflection, $exception) + { + $parameters = $reflection->getParameters(); + $expected = $parameters[0]; + return !$expected->getClass() || $expected->getClass()->isInstance($exception); + } + protected function formatException(\Exception $e) + { + if ($this->debug) { + $location = $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine(); + return 'Error in exception handler: ' . $location; + } + return 'Error in exception handler.'; + } + public function error(Closure $callback) + { + array_unshift($this->handlers, $callback); + } + public function pushError(Closure $callback) + { + $this->handlers[] = $callback; + } + protected function prepareResponse($response) + { + return $this->responsePreparer->prepareResponse($response); + } + public function runningInConsole() + { + return php_sapi_name() == 'cli'; + } + public function setDebug($debug) + { + $this->debug = $debug; + } +} +namespace Illuminate\Support\Facades; + +class Route extends Facade +{ + protected static function getFacadeAccessor() + { + return 'router'; + } +} +namespace Illuminate\View\Engines; + +use Closure; +class EngineResolver +{ + protected $resolvers = array(); + protected $resolved = array(); + public function register($engine, Closure $resolver) + { + $this->resolvers[$engine] = $resolver; + } + public function resolve($engine) + { + if (isset($this->resolved[$engine])) { + return $this->resolved[$engine]; + } + if (isset($this->resolvers[$engine])) { + return $this->resolved[$engine] = call_user_func($this->resolvers[$engine]); + } + throw new \InvalidArgumentException("Engine {$engine} not found."); + } +} +namespace Illuminate\View; + +interface ViewFinderInterface +{ + public function find($view); + public function addLocation($location); + public function addNamespace($namespace, $hints); + public function prependNamespace($namespace, $hints); + public function addExtension($extension); +} +namespace Illuminate\View; + +use Illuminate\Filesystem\Filesystem; +class FileViewFinder implements ViewFinderInterface +{ + protected $files; + protected $paths; + protected $views = array(); + protected $hints = array(); + protected $extensions = array('blade.php', 'php'); + const HINT_PATH_DELIMITER = '::'; + public function __construct(Filesystem $files, array $paths, array $extensions = null) + { + $this->files = $files; + $this->paths = $paths; + if (isset($extensions)) { + $this->extensions = $extensions; + } + } + public function find($name) + { + if (isset($this->views[$name])) { + return $this->views[$name]; + } + if ($this->hasHintInformation($name = trim($name))) { + return $this->views[$name] = $this->findNamedPathView($name); + } + return $this->views[$name] = $this->findInPaths($name, $this->paths); + } + protected function findNamedPathView($name) + { + list($namespace, $view) = $this->getNamespaceSegments($name); + return $this->findInPaths($view, $this->hints[$namespace]); + } + protected function getNamespaceSegments($name) + { + $segments = explode(static::HINT_PATH_DELIMITER, $name); + if (count($segments) != 2) { + throw new \InvalidArgumentException("View [{$name}] has an invalid name."); + } + if (!isset($this->hints[$segments[0]])) { + throw new \InvalidArgumentException("No hint path defined for [{$segments[0]}]."); + } + return $segments; + } + protected function findInPaths($name, $paths) + { + foreach ((array) $paths as $path) { + foreach ($this->getPossibleViewFiles($name) as $file) { + if ($this->files->exists($viewPath = $path . '/' . $file)) { + return $viewPath; + } + } + } + throw new \InvalidArgumentException("View [{$name}] not found."); + } + protected function getPossibleViewFiles($name) + { + return array_map(function ($extension) use($name) { + return str_replace('.', '/', $name) . '.' . $extension; + }, $this->extensions); + } + public function addLocation($location) + { + $this->paths[] = $location; + } + public function addNamespace($namespace, $hints) + { + $hints = (array) $hints; + if (isset($this->hints[$namespace])) { + $hints = array_merge($this->hints[$namespace], $hints); + } + $this->hints[$namespace] = $hints; + } + public function prependNamespace($namespace, $hints) + { + $hints = (array) $hints; + if (isset($this->hints[$namespace])) { + $hints = array_merge($hints, $this->hints[$namespace]); + } + $this->hints[$namespace] = $hints; + } + public function addExtension($extension) + { + if (($index = array_search($extension, $this->extensions)) !== false) { + unset($this->extensions[$index]); + } + array_unshift($this->extensions, $extension); + } + public function hasHintInformation($name) + { + return strpos($name, static::HINT_PATH_DELIMITER) > 0; + } + public function getFilesystem() + { + return $this->files; + } + public function getPaths() + { + return $this->paths; + } + public function getHints() + { + return $this->hints; + } + public function getExtensions() + { + return $this->extensions; + } +} +namespace Illuminate\Support\Contracts; + +interface MessageProviderInterface +{ + public function getMessageBag(); +} +namespace Illuminate\Support; + +use Countable; +use JsonSerializable; +use Illuminate\Support\Contracts\JsonableInterface; +use Illuminate\Support\Contracts\ArrayableInterface; +use Illuminate\Support\Contracts\MessageProviderInterface; +class MessageBag implements ArrayableInterface, Countable, JsonableInterface, MessageProviderInterface, JsonSerializable +{ + protected $messages = array(); + protected $format = ':message'; + public function __construct(array $messages = array()) + { + foreach ($messages as $key => $value) { + $this->messages[$key] = (array) $value; + } + } + public function add($key, $message) + { + if ($this->isUnique($key, $message)) { + $this->messages[$key][] = $message; + } + return $this; + } + public function merge($messages) + { + if ($messages instanceof MessageProviderInterface) { + $messages = $messages->getMessageBag()->getMessages(); + } + $this->messages = array_merge_recursive($this->messages, $messages); + return $this; + } + protected function isUnique($key, $message) + { + $messages = (array) $this->messages; + return !isset($messages[$key]) || !in_array($message, $messages[$key]); + } + public function has($key = null) + { + return $this->first($key) !== ''; + } + public function first($key = null, $format = null) + { + $messages = is_null($key) ? $this->all($format) : $this->get($key, $format); + return count($messages) > 0 ? $messages[0] : ''; + } + public function get($key, $format = null) + { + $format = $this->checkFormat($format); + if (array_key_exists($key, $this->messages)) { + return $this->transform($this->messages[$key], $format, $key); + } + return array(); + } + public function all($format = null) + { + $format = $this->checkFormat($format); + $all = array(); + foreach ($this->messages as $key => $messages) { + $all = array_merge($all, $this->transform($messages, $format, $key)); + } + return $all; + } + protected function transform($messages, $format, $messageKey) + { + $messages = (array) $messages; + foreach ($messages as &$message) { + $replace = array(':message', ':key'); + $message = str_replace($replace, array($message, $messageKey), $format); + } + return $messages; + } + protected function checkFormat($format) + { + return $format === null ? $this->format : $format; + } + public function getMessages() + { + return $this->messages; + } + public function getMessageBag() + { + return $this; + } + public function getFormat() + { + return $this->format; + } + public function setFormat($format = ':message') + { + $this->format = $format; + return $this; + } + public function isEmpty() + { + return !$this->any(); + } + public function any() + { + return $this->count() > 0; + } + public function count() + { + return count($this->messages, COUNT_RECURSIVE) - count($this->messages); + } + public function toArray() + { + return $this->getMessages(); + } + public function jsonSerialize() + { + return $this->toArray(); + } + public function toJson($options = 0) + { + return json_encode($this->toArray(), $options); + } + public function __toString() + { + return $this->toJson(); + } +} +namespace Illuminate\Support\Facades; + +class View extends Facade +{ + protected static function getFacadeAccessor() + { + return 'view'; + } +} +namespace Illuminate\Support\Contracts; + +interface RenderableInterface +{ + public function render(); +} +namespace Illuminate\View; + +use ArrayAccess; +use Closure; +use Illuminate\Support\MessageBag; +use Illuminate\View\Engines\EngineInterface; +use Illuminate\Support\Contracts\MessageProviderInterface; +use Illuminate\Support\Contracts\ArrayableInterface as Arrayable; +use Illuminate\Support\Contracts\RenderableInterface as Renderable; +class View implements ArrayAccess, Renderable +{ + protected $factory; + protected $engine; + protected $view; + protected $data; + protected $path; + public function __construct(Factory $factory, EngineInterface $engine, $view, $path, $data = array()) + { + $this->view = $view; + $this->path = $path; + $this->engine = $engine; + $this->factory = $factory; + $this->data = $data instanceof Arrayable ? $data->toArray() : (array) $data; + } + public function render(Closure $callback = null) + { + $contents = $this->renderContents(); + $response = isset($callback) ? $callback($this, $contents) : null; + $this->factory->flushSectionsIfDoneRendering(); + return $response ?: $contents; + } + protected function renderContents() + { + $this->factory->incrementRender(); + $this->factory->callComposer($this); + $contents = $this->getContents(); + $this->factory->decrementRender(); + return $contents; + } + public function renderSections() + { + $env = $this->factory; + return $this->render(function ($view) use($env) { + return $env->getSections(); + }); + } + protected function getContents() + { + return $this->engine->get($this->path, $this->gatherData()); + } + protected function gatherData() + { + $data = array_merge($this->factory->getShared(), $this->data); + foreach ($data as $key => $value) { + if ($value instanceof Renderable) { + $data[$key] = $value->render(); + } + } + return $data; + } + public function with($key, $value = null) + { + if (is_array($key)) { + $this->data = array_merge($this->data, $key); + } else { + $this->data[$key] = $value; + } + return $this; + } + public function nest($key, $view, array $data = array()) + { + return $this->with($key, $this->factory->make($view, $data)); + } + public function withErrors($provider) + { + if ($provider instanceof MessageProviderInterface) { + $this->with('errors', $provider->getMessageBag()); + } else { + $this->with('errors', new MessageBag((array) $provider)); + } + return $this; + } + public function getFactory() + { + return $this->factory; + } + public function getEngine() + { + return $this->engine; + } + public function getName() + { + return $this->view; + } + public function getData() + { + return $this->data; + } + public function getPath() + { + return $this->path; + } + public function setPath($path) + { + $this->path = $path; + } + public function offsetExists($key) + { + return array_key_exists($key, $this->data); + } + public function offsetGet($key) + { + return $this->data[$key]; + } + public function offsetSet($key, $value) + { + $this->with($key, $value); + } + public function offsetUnset($key) + { + unset($this->data[$key]); + } + public function &__get($key) + { + return $this->data[$key]; + } + public function __set($key, $value) + { + $this->with($key, $value); + } + public function __isset($key) + { + return isset($this->data[$key]); + } + public function __unset($key) + { + unset($this->data[$key]); + } + public function __call($method, $parameters) + { + if (starts_with($method, 'with')) { + return $this->with(snake_case(substr($method, 4)), $parameters[0]); + } + throw new \BadMethodCallException("Method [{$method}] does not exist on view."); + } + public function __toString() + { + return $this->render(); + } +} +namespace Illuminate\View\Engines; + +interface EngineInterface +{ + public function get($path, array $data = array()); +} +namespace Illuminate\View\Engines; + +class PhpEngine implements EngineInterface +{ + public function get($path, array $data = array()) + { + return $this->evaluatePath($path, $data); + } + protected function evaluatePath($__path, $__data) + { + $obLevel = ob_get_level(); + ob_start(); + extract($__data); + try { + include $__path; + } catch (\Exception $e) { + $this->handleViewException($e, $obLevel); + } + return ltrim(ob_get_clean()); + } + protected function handleViewException($e, $obLevel) + { + while (ob_get_level() > $obLevel) { + ob_end_clean(); + } + throw $e; + } +} +namespace Symfony\Component\HttpFoundation; + +class Response +{ + const HTTP_CONTINUE = 100; + const HTTP_SWITCHING_PROTOCOLS = 101; + const HTTP_PROCESSING = 102; + const HTTP_OK = 200; + const HTTP_CREATED = 201; + const HTTP_ACCEPTED = 202; + const HTTP_NON_AUTHORITATIVE_INFORMATION = 203; + const HTTP_NO_CONTENT = 204; + const HTTP_RESET_CONTENT = 205; + const HTTP_PARTIAL_CONTENT = 206; + const HTTP_MULTI_STATUS = 207; + const HTTP_ALREADY_REPORTED = 208; + const HTTP_IM_USED = 226; + const HTTP_MULTIPLE_CHOICES = 300; + const HTTP_MOVED_PERMANENTLY = 301; + const HTTP_FOUND = 302; + const HTTP_SEE_OTHER = 303; + const HTTP_NOT_MODIFIED = 304; + const HTTP_USE_PROXY = 305; + const HTTP_RESERVED = 306; + const HTTP_TEMPORARY_REDIRECT = 307; + const HTTP_PERMANENTLY_REDIRECT = 308; + const HTTP_BAD_REQUEST = 400; + const HTTP_UNAUTHORIZED = 401; + const HTTP_PAYMENT_REQUIRED = 402; + const HTTP_FORBIDDEN = 403; + const HTTP_NOT_FOUND = 404; + const HTTP_METHOD_NOT_ALLOWED = 405; + const HTTP_NOT_ACCEPTABLE = 406; + const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; + const HTTP_REQUEST_TIMEOUT = 408; + const HTTP_CONFLICT = 409; + const HTTP_GONE = 410; + const HTTP_LENGTH_REQUIRED = 411; + const HTTP_PRECONDITION_FAILED = 412; + const HTTP_REQUEST_ENTITY_TOO_LARGE = 413; + const HTTP_REQUEST_URI_TOO_LONG = 414; + const HTTP_UNSUPPORTED_MEDIA_TYPE = 415; + const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; + const HTTP_EXPECTATION_FAILED = 417; + const HTTP_I_AM_A_TEAPOT = 418; + const HTTP_UNPROCESSABLE_ENTITY = 422; + const HTTP_LOCKED = 423; + const HTTP_FAILED_DEPENDENCY = 424; + const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; + const HTTP_UPGRADE_REQUIRED = 426; + const HTTP_PRECONDITION_REQUIRED = 428; + const HTTP_TOO_MANY_REQUESTS = 429; + const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; + const HTTP_INTERNAL_SERVER_ERROR = 500; + const HTTP_NOT_IMPLEMENTED = 501; + const HTTP_BAD_GATEWAY = 502; + const HTTP_SERVICE_UNAVAILABLE = 503; + const HTTP_GATEWAY_TIMEOUT = 504; + const HTTP_VERSION_NOT_SUPPORTED = 505; + const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; + const HTTP_INSUFFICIENT_STORAGE = 507; + const HTTP_LOOP_DETECTED = 508; + const HTTP_NOT_EXTENDED = 510; + const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; + public $headers; + protected $content; + protected $version; + protected $statusCode; + protected $statusText; + protected $charset; + public static $statusTexts = array(100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-Status', 208 => 'Already Reported', 226 => 'IM Used', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Reserved', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Reserved for WebDAV advanced collections expired proposal', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported', 506 => 'Variant Also Negotiates (Experimental)', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 510 => 'Not Extended', 511 => 'Network Authentication Required'); + public function __construct($content = '', $status = 200, $headers = array()) + { + $this->headers = new ResponseHeaderBag($headers); + $this->setContent($content); + $this->setStatusCode($status); + $this->setProtocolVersion('1.0'); + if (!$this->headers->has('Date')) { + $this->setDate(new \DateTime(null, new \DateTimeZone('UTC'))); + } + } + public static function create($content = '', $status = 200, $headers = array()) + { + return new static($content, $status, $headers); + } + public function __toString() + { + return sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText) . ' +' . $this->headers . ' +' . $this->getContent(); + } + public function __clone() + { + $this->headers = clone $this->headers; + } + public function prepare(Request $request) + { + $headers = $this->headers; + if ($this->isInformational() || in_array($this->statusCode, array(204, 304))) { + $this->setContent(null); + $headers->remove('Content-Type'); + $headers->remove('Content-Length'); + } else { + if (!$headers->has('Content-Type')) { + $format = $request->getRequestFormat(); + if (null !== $format && ($mimeType = $request->getMimeType($format))) { + $headers->set('Content-Type', $mimeType); + } + } + $charset = $this->charset ?: 'UTF-8'; + if (!$headers->has('Content-Type')) { + $headers->set('Content-Type', 'text/html; charset=' . $charset); + } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) { + $headers->set('Content-Type', $headers->get('Content-Type') . '; charset=' . $charset); + } + if ($headers->has('Transfer-Encoding')) { + $headers->remove('Content-Length'); + } + if ($request->isMethod('HEAD')) { + $length = $headers->get('Content-Length'); + $this->setContent(null); + if ($length) { + $headers->set('Content-Length', $length); + } + } + } + if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + if ('1.0' == $this->getProtocolVersion() && 'no-cache' == $this->headers->get('Cache-Control')) { + $this->headers->set('pragma', 'no-cache'); + $this->headers->set('expires', -1); + } + $this->ensureIEOverSSLCompatibility($request); + return $this; + } + public function sendHeaders() + { + if (headers_sent()) { + return $this; + } + header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); + foreach ($this->headers->allPreserveCase() as $name => $values) { + foreach ($values as $value) { + header($name . ': ' . $value, false, $this->statusCode); + } + } + foreach ($this->headers->getCookies() as $cookie) { + setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); + } + return $this; + } + public function sendContent() + { + echo $this->content; + return $this; + } + public function send() + { + $this->sendHeaders(); + $this->sendContent(); + if (function_exists('fastcgi_finish_request')) { + fastcgi_finish_request(); + } elseif ('cli' !== PHP_SAPI) { + static::closeOutputBuffers(0, true); + } + return $this; + } + public function setContent($content) + { + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable(array($content, '__toString'))) { + throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', gettype($content))); + } + $this->content = (string) $content; + return $this; + } + public function getContent() + { + return $this->content; + } + public function setProtocolVersion($version) + { + $this->version = $version; + return $this; + } + public function getProtocolVersion() + { + return $this->version; + } + public function setStatusCode($code, $text = null) + { + $this->statusCode = $code = (int) $code; + if ($this->isInvalid()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code)); + } + if (null === $text) { + $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : ''; + return $this; + } + if (false === $text) { + $this->statusText = ''; + return $this; + } + $this->statusText = $text; + return $this; + } + public function getStatusCode() + { + return $this->statusCode; + } + public function setCharset($charset) + { + $this->charset = $charset; + return $this; + } + public function getCharset() + { + return $this->charset; + } + public function isCacheable() + { + if (!in_array($this->statusCode, array(200, 203, 300, 301, 302, 404, 410))) { + return false; + } + if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) { + return false; + } + return $this->isValidateable() || $this->isFresh(); + } + public function isFresh() + { + return $this->getTtl() > 0; + } + public function isValidateable() + { + return $this->headers->has('Last-Modified') || $this->headers->has('ETag'); + } + public function setPrivate() + { + $this->headers->removeCacheControlDirective('public'); + $this->headers->addCacheControlDirective('private'); + return $this; + } + public function setPublic() + { + $this->headers->addCacheControlDirective('public'); + $this->headers->removeCacheControlDirective('private'); + return $this; + } + public function mustRevalidate() + { + return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->has('proxy-revalidate'); + } + public function getDate() + { + return $this->headers->getDate('Date', new \DateTime()); + } + public function setDate(\DateTime $date) + { + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Date', $date->format('D, d M Y H:i:s') . ' GMT'); + return $this; + } + public function getAge() + { + if (null !== ($age = $this->headers->get('Age'))) { + return (int) $age; + } + return max(time() - $this->getDate()->format('U'), 0); + } + public function expire() + { + if ($this->isFresh()) { + $this->headers->set('Age', $this->getMaxAge()); + } + return $this; + } + public function getExpires() + { + try { + return $this->headers->getDate('Expires'); + } catch (\RuntimeException $e) { + return \DateTime::createFromFormat(DATE_RFC2822, 'Sat, 01 Jan 00 00:00:00 +0000'); + } + } + public function setExpires(\DateTime $date = null) + { + if (null === $date) { + $this->headers->remove('Expires'); + } else { + $date = clone $date; + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Expires', $date->format('D, d M Y H:i:s') . ' GMT'); + } + return $this; + } + public function getMaxAge() + { + if ($this->headers->hasCacheControlDirective('s-maxage')) { + return (int) $this->headers->getCacheControlDirective('s-maxage'); + } + if ($this->headers->hasCacheControlDirective('max-age')) { + return (int) $this->headers->getCacheControlDirective('max-age'); + } + if (null !== $this->getExpires()) { + return $this->getExpires()->format('U') - $this->getDate()->format('U'); + } + } + public function setMaxAge($value) + { + $this->headers->addCacheControlDirective('max-age', $value); + return $this; + } + public function setSharedMaxAge($value) + { + $this->setPublic(); + $this->headers->addCacheControlDirective('s-maxage', $value); + return $this; + } + public function getTtl() + { + if (null !== ($maxAge = $this->getMaxAge())) { + return $maxAge - $this->getAge(); + } + } + public function setTtl($seconds) + { + $this->setSharedMaxAge($this->getAge() + $seconds); + return $this; + } + public function setClientTtl($seconds) + { + $this->setMaxAge($this->getAge() + $seconds); + return $this; + } + public function getLastModified() + { + return $this->headers->getDate('Last-Modified'); + } + public function setLastModified(\DateTime $date = null) + { + if (null === $date) { + $this->headers->remove('Last-Modified'); + } else { + $date = clone $date; + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s') . ' GMT'); + } + return $this; + } + public function getEtag() + { + return $this->headers->get('ETag'); + } + public function setEtag($etag = null, $weak = false) + { + if (null === $etag) { + $this->headers->remove('Etag'); + } else { + if (0 !== strpos($etag, '"')) { + $etag = '"' . $etag . '"'; + } + $this->headers->set('ETag', (true === $weak ? 'W/' : '') . $etag); + } + return $this; + } + public function setCache(array $options) + { + if ($diff = array_diff(array_keys($options), array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public'))) { + throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', array_values($diff)))); + } + if (isset($options['etag'])) { + $this->setEtag($options['etag']); + } + if (isset($options['last_modified'])) { + $this->setLastModified($options['last_modified']); + } + if (isset($options['max_age'])) { + $this->setMaxAge($options['max_age']); + } + if (isset($options['s_maxage'])) { + $this->setSharedMaxAge($options['s_maxage']); + } + if (isset($options['public'])) { + if ($options['public']) { + $this->setPublic(); + } else { + $this->setPrivate(); + } + } + if (isset($options['private'])) { + if ($options['private']) { + $this->setPrivate(); + } else { + $this->setPublic(); + } + } + return $this; + } + public function setNotModified() + { + $this->setStatusCode(304); + $this->setContent(null); + foreach (array('Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified') as $header) { + $this->headers->remove($header); + } + return $this; + } + public function hasVary() + { + return null !== $this->headers->get('Vary'); + } + public function getVary() + { + if (!($vary = $this->headers->get('Vary', null, false))) { + return array(); + } + $ret = array(); + foreach ($vary as $item) { + $ret = array_merge($ret, preg_split('/[\\s,]+/', $item)); + } + return $ret; + } + public function setVary($headers, $replace = true) + { + $this->headers->set('Vary', $headers, $replace); + return $this; + } + public function isNotModified(Request $request) + { + if (!$request->isMethodSafe()) { + return false; + } + $notModified = false; + $lastModified = $this->headers->get('Last-Modified'); + $modifiedSince = $request->headers->get('If-Modified-Since'); + if ($etags = $request->getEtags()) { + $notModified = in_array($this->getEtag(), $etags) || in_array('*', $etags); + } + if ($modifiedSince && $lastModified) { + $notModified = strtotime($modifiedSince) >= strtotime($lastModified) && (!$etags || $notModified); + } + if ($notModified) { + $this->setNotModified(); + } + return $notModified; + } + public function isInvalid() + { + return $this->statusCode < 100 || $this->statusCode >= 600; + } + public function isInformational() + { + return $this->statusCode >= 100 && $this->statusCode < 200; + } + public function isSuccessful() + { + return $this->statusCode >= 200 && $this->statusCode < 300; + } + public function isRedirection() + { + return $this->statusCode >= 300 && $this->statusCode < 400; + } + public function isClientError() + { + return $this->statusCode >= 400 && $this->statusCode < 500; + } + public function isServerError() + { + return $this->statusCode >= 500 && $this->statusCode < 600; + } + public function isOk() + { + return 200 === $this->statusCode; + } + public function isForbidden() + { + return 403 === $this->statusCode; + } + public function isNotFound() + { + return 404 === $this->statusCode; + } + public function isRedirect($location = null) + { + return in_array($this->statusCode, array(201, 301, 302, 303, 307, 308)) && (null === $location ?: $location == $this->headers->get('Location')); + } + public function isEmpty() + { + return in_array($this->statusCode, array(204, 304)); + } + public static function closeOutputBuffers($targetLevel, $flush) + { + $status = ob_get_status(true); + $level = count($status); + while ($level-- > $targetLevel && (!empty($status[$level]['del']) || isset($status[$level]['flags']) && $status[$level]['flags'] & PHP_OUTPUT_HANDLER_REMOVABLE && $status[$level]['flags'] & ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE))) { + if ($flush) { + ob_end_flush(); + } else { + ob_end_clean(); + } + } + } + protected function ensureIEOverSSLCompatibility(Request $request) + { + if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) == 1 && true === $request->isSecure()) { + if (intval(preg_replace('/(MSIE )(.*?);/', '$2', $match[0])) < 9) { + $this->headers->remove('Cache-Control'); + } + } + } +} +namespace Illuminate\Http; + +use Symfony\Component\HttpFoundation\Cookie; +trait ResponseTrait +{ + public function header($key, $value, $replace = true) + { + $this->headers->set($key, $value, $replace); + return $this; + } + public function withCookie(Cookie $cookie) + { + $this->headers->setCookie($cookie); + return $this; + } +} +namespace Illuminate\Http; + +use ArrayObject; +use Illuminate\Support\Contracts\JsonableInterface; +use Illuminate\Support\Contracts\RenderableInterface; +class Response extends \Symfony\Component\HttpFoundation\Response +{ + use ResponseTrait; + public $original; + public function setContent($content) + { + $this->original = $content; + if ($this->shouldBeJson($content)) { + $this->headers->set('Content-Type', 'application/json'); + $content = $this->morphToJson($content); + } elseif ($content instanceof RenderableInterface) { + $content = $content->render(); + } + return parent::setContent($content); + } + protected function morphToJson($content) + { + if ($content instanceof JsonableInterface) { + return $content->toJson(); + } + return json_encode($content); + } + protected function shouldBeJson($content) + { + return $content instanceof JsonableInterface || $content instanceof ArrayObject || is_array($content); + } + public function getOriginalContent() + { + return $this->original; + } +} +namespace Symfony\Component\HttpFoundation; + +class ResponseHeaderBag extends HeaderBag +{ + const COOKIES_FLAT = 'flat'; + const COOKIES_ARRAY = 'array'; + const DISPOSITION_ATTACHMENT = 'attachment'; + const DISPOSITION_INLINE = 'inline'; + protected $computedCacheControl = array(); + protected $cookies = array(); + protected $headerNames = array(); + public function __construct(array $headers = array()) + { + parent::__construct($headers); + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + } + public function __toString() + { + $cookies = ''; + foreach ($this->getCookies() as $cookie) { + $cookies .= 'Set-Cookie: ' . $cookie . ' +'; + } + ksort($this->headerNames); + return parent::__toString() . $cookies; + } + public function allPreserveCase() + { + return array_combine($this->headerNames, $this->headers); + } + public function replace(array $headers = array()) + { + $this->headerNames = array(); + parent::replace($headers); + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + } + public function set($key, $values, $replace = true) + { + parent::set($key, $values, $replace); + $uniqueKey = strtr(strtolower($key), '_', '-'); + $this->headerNames[$uniqueKey] = $key; + if (in_array($uniqueKey, array('cache-control', 'etag', 'last-modified', 'expires'))) { + $computed = $this->computeCacheControlValue(); + $this->headers['cache-control'] = array($computed); + $this->headerNames['cache-control'] = 'Cache-Control'; + $this->computedCacheControl = $this->parseCacheControl($computed); + } + } + public function remove($key) + { + parent::remove($key); + $uniqueKey = strtr(strtolower($key), '_', '-'); + unset($this->headerNames[$uniqueKey]); + if ('cache-control' === $uniqueKey) { + $this->computedCacheControl = array(); + } + } + public function hasCacheControlDirective($key) + { + return array_key_exists($key, $this->computedCacheControl); + } + public function getCacheControlDirective($key) + { + return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null; + } + public function setCookie(Cookie $cookie) + { + $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; + } + public function removeCookie($name, $path = '/', $domain = null) + { + if (null === $path) { + $path = '/'; + } + unset($this->cookies[$domain][$path][$name]); + if (empty($this->cookies[$domain][$path])) { + unset($this->cookies[$domain][$path]); + if (empty($this->cookies[$domain])) { + unset($this->cookies[$domain]); + } + } + } + public function getCookies($format = self::COOKIES_FLAT) + { + if (!in_array($format, array(self::COOKIES_FLAT, self::COOKIES_ARRAY))) { + throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', array(self::COOKIES_FLAT, self::COOKIES_ARRAY)))); + } + if (self::COOKIES_ARRAY === $format) { + return $this->cookies; + } + $flattenedCookies = array(); + foreach ($this->cookies as $path) { + foreach ($path as $cookies) { + foreach ($cookies as $cookie) { + $flattenedCookies[] = $cookie; + } + } + } + return $flattenedCookies; + } + public function clearCookie($name, $path = '/', $domain = null) + { + $this->setCookie(new Cookie($name, null, 1, $path, $domain)); + } + public function makeDisposition($disposition, $filename, $filenameFallback = '') + { + if (!in_array($disposition, array(self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE))) { + throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE)); + } + if ('' == $filenameFallback) { + $filenameFallback = $filename; + } + if (!preg_match('/^[\\x20-\\x7e]*$/', $filenameFallback)) { + throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.'); + } + if (false !== strpos($filenameFallback, '%')) { + throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.'); + } + if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) { + throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.'); + } + $output = sprintf('%s; filename="%s"', $disposition, str_replace('"', '\\"', $filenameFallback)); + if ($filename !== $filenameFallback) { + $output .= sprintf('; filename*=utf-8\'\'%s', rawurlencode($filename)); + } + return $output; + } + protected function computeCacheControlValue() + { + if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) { + return 'no-cache'; + } + if (!$this->cacheControl) { + return 'private, must-revalidate'; + } + $header = $this->getCacheControlHeader(); + if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) { + return $header; + } + if (!isset($this->cacheControl['s-maxage'])) { + return $header . ', private'; + } + return $header; + } +} +namespace Symfony\Component\HttpFoundation; + +class Cookie +{ + protected $name; + protected $value; + protected $domain; + protected $expire; + protected $path; + protected $secure; + protected $httpOnly; + public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true) + { + if (preg_match('/[=,; + ]/', $name)) { + throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); + } + if (empty($name)) { + throw new \InvalidArgumentException('The cookie name cannot be empty.'); + } + if ($expire instanceof \DateTime) { + $expire = $expire->format('U'); + } elseif (!is_numeric($expire)) { + $expire = strtotime($expire); + if (false === $expire || -1 === $expire) { + throw new \InvalidArgumentException('The cookie expiration time is not valid.'); + } + } + $this->name = $name; + $this->value = $value; + $this->domain = $domain; + $this->expire = $expire; + $this->path = empty($path) ? '/' : $path; + $this->secure = (bool) $secure; + $this->httpOnly = (bool) $httpOnly; + } + public function __toString() + { + $str = urlencode($this->getName()) . '='; + if ('' === (string) $this->getValue()) { + $str .= 'deleted; expires=' . gmdate('D, d-M-Y H:i:s T', time() - 31536001); + } else { + $str .= urlencode($this->getValue()); + if ($this->getExpiresTime() !== 0) { + $str .= '; expires=' . gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()); + } + } + if ($this->path) { + $str .= '; path=' . $this->path; + } + if ($this->getDomain()) { + $str .= '; domain=' . $this->getDomain(); + } + if (true === $this->isSecure()) { + $str .= '; secure'; + } + if (true === $this->isHttpOnly()) { + $str .= '; httponly'; + } + return $str; + } + public function getName() + { + return $this->name; + } + public function getValue() + { + return $this->value; + } + public function getDomain() + { + return $this->domain; + } + public function getExpiresTime() + { + return $this->expire; + } + public function getPath() + { + return $this->path; + } + public function isSecure() + { + return $this->secure; + } + public function isHttpOnly() + { + return $this->httpOnly; + } + public function isCleared() + { + return $this->expire < time(); + } +} +namespace Whoops; + +use Exception; +use InvalidArgumentException; +use Whoops\Exception\ErrorException; +use Whoops\Exception\Inspector; +use Whoops\Handler\CallbackHandler; +use Whoops\Handler\Handler; +use Whoops\Handler\HandlerInterface; +class Run +{ + const EXCEPTION_HANDLER = 'handleException'; + const ERROR_HANDLER = 'handleError'; + const SHUTDOWN_HANDLER = 'handleShutdown'; + protected $isRegistered; + protected $allowQuit = true; + protected $sendOutput = true; + protected $sendHttpCode = 500; + protected $handlerStack = array(); + protected $silencedPatterns = array(); + public function pushHandler($handler) + { + if (is_callable($handler)) { + $handler = new CallbackHandler($handler); + } + if (!$handler instanceof HandlerInterface) { + throw new InvalidArgumentException('Argument to ' . __METHOD__ . ' must be a callable, or instance of' . 'Whoops\\Handler\\HandlerInterface'); + } + $this->handlerStack[] = $handler; + return $this; + } + public function popHandler() + { + return array_pop($this->handlerStack); + } + public function getHandlers() + { + return $this->handlerStack; + } + public function clearHandlers() + { + $this->handlerStack = array(); + return $this; + } + protected function getInspector(Exception $exception) + { + return new Inspector($exception); + } + public function register() + { + if (!$this->isRegistered) { + class_exists('\\Whoops\\Exception\\ErrorException'); + class_exists('\\Whoops\\Exception\\FrameCollection'); + class_exists('\\Whoops\\Exception\\Frame'); + class_exists('\\Whoops\\Exception\\Inspector'); + set_error_handler(array($this, self::ERROR_HANDLER)); + set_exception_handler(array($this, self::EXCEPTION_HANDLER)); + register_shutdown_function(array($this, self::SHUTDOWN_HANDLER)); + $this->isRegistered = true; + } + return $this; + } + public function unregister() + { + if ($this->isRegistered) { + restore_exception_handler(); + restore_error_handler(); + $this->isRegistered = false; + } + return $this; + } + public function allowQuit($exit = null) + { + if (func_num_args() == 0) { + return $this->allowQuit; + } + return $this->allowQuit = (bool) $exit; + } + public function silenceErrorsInPaths($patterns, $levels = 10240) + { + $this->silencedPatterns = array_merge($this->silencedPatterns, array_map(function ($pattern) use($levels) { + return array('pattern' => $pattern, 'levels' => $levels); + }, (array) $patterns)); + return $this; + } + public function sendHttpCode($code = null) + { + if (func_num_args() == 0) { + return $this->sendHttpCode; + } + if (!$code) { + return $this->sendHttpCode = false; + } + if ($code === true) { + $code = 500; + } + if ($code < 400 || 600 <= $code) { + throw new InvalidArgumentException("Invalid status code '{$code}', must be 4xx or 5xx"); + } + return $this->sendHttpCode = $code; + } + public function writeToOutput($send = null) + { + if (func_num_args() == 0) { + return $this->sendOutput; + } + return $this->sendOutput = (bool) $send; + } + public function handleException(Exception $exception) + { + $inspector = $this->getInspector($exception); + ob_start(); + $handlerResponse = null; + foreach (array_reverse($this->handlerStack) as $handler) { + $handler->setRun($this); + $handler->setInspector($inspector); + $handler->setException($exception); + $handlerResponse = $handler->handle($exception); + if (in_array($handlerResponse, array(Handler::LAST_HANDLER, Handler::QUIT))) { + break; + } + } + $willQuit = $handlerResponse == Handler::QUIT && $this->allowQuit(); + $output = ob_get_clean(); + if ($this->writeToOutput()) { + if ($willQuit) { + while (ob_get_level() > 0) { + ob_end_clean(); + } + } + $this->writeToOutputNow($output); + } + if ($willQuit) { + die(1); + } + return $output; + } + public function handleError($level, $message, $file = null, $line = null) + { + if ($level & error_reporting()) { + foreach ($this->silencedPatterns as $entry) { + $pathMatches = (bool) preg_match($entry['pattern'], $file); + $levelMatches = $level & $entry['levels']; + if ($pathMatches && $levelMatches) { + return true; + } + } + $exception = new ErrorException($message, $level, 0, $file, $line); + if ($this->canThrowExceptions) { + throw $exception; + } else { + $this->handleException($exception); + } + } + } + public function handleShutdown() + { + $this->canThrowExceptions = false; + $error = error_get_last(); + if ($error && $this->isLevelFatal($error['type'])) { + $this->handleError($error['type'], $error['message'], $error['file'], $error['line']); + } + } + private $canThrowExceptions = true; + private function writeToOutputNow($output) + { + if ($this->sendHttpCode() && \Whoops\Util\Misc::canSendHeaders()) { + $httpCode = $this->sendHttpCode(); + if (function_exists('http_response_code')) { + http_response_code($httpCode); + } else { + header('X-Ignore-This: 1', true, $httpCode); + } + } + echo $output; + return $this; + } + private static function isLevelFatal($level) + { + $errors = E_ERROR; + $errors |= E_PARSE; + $errors |= E_CORE_ERROR; + $errors |= E_CORE_WARNING; + $errors |= E_COMPILE_ERROR; + $errors |= E_COMPILE_WARNING; + return ($level & $errors) > 0; + } +} +namespace Whoops\Handler; + +use Exception; +use Whoops\Exception\Inspector; +use Whoops\Run; +interface HandlerInterface +{ + public function handle(); + public function setRun(Run $run); + public function setException(Exception $exception); + public function setInspector(Inspector $inspector); +} +namespace Whoops\Handler; + +use Exception; +use Whoops\Exception\Inspector; +use Whoops\Run; +abstract class Handler implements HandlerInterface +{ + const DONE = 16; + const LAST_HANDLER = 32; + const QUIT = 48; + private $run; + private $inspector; + private $exception; + public function setRun(Run $run) + { + $this->run = $run; + } + protected function getRun() + { + return $this->run; + } + public function setInspector(Inspector $inspector) + { + $this->inspector = $inspector; + } + protected function getInspector() + { + return $this->inspector; + } + public function setException(Exception $exception) + { + $this->exception = $exception; + } + protected function getException() + { + return $this->exception; + } +} +namespace Whoops\Handler; + +use Whoops\Exception\Formatter; +class JsonResponseHandler extends Handler +{ + private $returnFrames = false; + private $onlyForAjaxRequests = false; + public function addTraceToOutput($returnFrames = null) + { + if (func_num_args() == 0) { + return $this->returnFrames; + } + $this->returnFrames = (bool) $returnFrames; + return $this; + } + public function onlyForAjaxRequests($onlyForAjaxRequests = null) + { + if (func_num_args() == 0) { + return $this->onlyForAjaxRequests; + } + $this->onlyForAjaxRequests = (bool) $onlyForAjaxRequests; + } + private function isAjaxRequest() + { + return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'; + } + public function handle() + { + if ($this->onlyForAjaxRequests() && !$this->isAjaxRequest()) { + return Handler::DONE; + } + $response = array('error' => Formatter::formatExceptionAsDataArray($this->getInspector(), $this->addTraceToOutput())); + if (\Whoops\Util\Misc::canSendHeaders()) { + header('Content-Type: application/json'); + } + echo json_encode($response); + return Handler::QUIT; + } +} +namespace Stack; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +class Builder +{ + private $specs; + public function __construct() + { + $this->specs = new \SplStack(); + } + public function unshift() + { + if (func_num_args() === 0) { + throw new \InvalidArgumentException('Missing argument(s) when calling unshift'); + } + $spec = func_get_args(); + $this->specs->unshift($spec); + return $this; + } + public function push() + { + if (func_num_args() === 0) { + throw new \InvalidArgumentException('Missing argument(s) when calling push'); + } + $spec = func_get_args(); + $this->specs->push($spec); + return $this; + } + public function resolve(HttpKernelInterface $app) + { + $middlewares = array($app); + foreach ($this->specs as $spec) { + $args = $spec; + $firstArg = array_shift($args); + if (is_callable($firstArg)) { + $app = $firstArg($app); + } else { + $kernelClass = $firstArg; + array_unshift($args, $app); + $reflection = new \ReflectionClass($kernelClass); + $app = $reflection->newInstanceArgs($args); + } + array_unshift($middlewares, $app); + } + return new StackedHttpKernel($app, $middlewares); + } +} +namespace Stack; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\TerminableInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +class StackedHttpKernel implements HttpKernelInterface, TerminableInterface +{ + private $app; + private $middlewares = array(); + public function __construct(HttpKernelInterface $app, array $middlewares) + { + $this->app = $app; + $this->middlewares = $middlewares; + } + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + return $this->app->handle($request, $type, $catch); + } + public function terminate(Request $request, Response $response) + { + $prevKernel = null; + foreach ($this->middlewares as $kernel) { + if (!$prevKernel instanceof TerminableInterface && $kernel instanceof TerminableInterface) { + $kernel->terminate($request, $response); + } + $prevKernel = $kernel; + } + } +} diff --git a/bootstrap/paths.php b/bootstrap/paths.php new file mode 100755 index 000000000..5a1f640ba --- /dev/null +++ b/bootstrap/paths.php @@ -0,0 +1,57 @@ + __DIR__.'/../app', + + /* + |-------------------------------------------------------------------------- + | Public Path + |-------------------------------------------------------------------------- + | + | The public path contains the assets for your web application, such as + | your JavaScript and CSS files, and also contains the primary entry + | point for web requests into these applications from the outside. + | + */ + + 'public' => __DIR__.'/../public', + + /* + |-------------------------------------------------------------------------- + | Base Path + |-------------------------------------------------------------------------- + | + | The base path is the root of the Laravel installation. Most likely you + | will not need to change this value. But, if for some wild reason it + | is necessary you will do so here, just proceed with some caution. + | + */ + + 'base' => __DIR__.'/..', + + /* + |-------------------------------------------------------------------------- + | Storage Path + |-------------------------------------------------------------------------- + | + | The storage path is used by Laravel to store cached Blade views, logs + | and other pieces of information. You may modify the path here when + | you want to change the location of this directory for your apps. + | + */ + + 'storage' => __DIR__.'/../app/storage', + +); diff --git a/bootstrap/start.php b/bootstrap/start.php new file mode 100755 index 000000000..cd8a5b694 --- /dev/null +++ b/bootstrap/start.php @@ -0,0 +1,72 @@ +detectEnvironment(array( + + 'local' => array('your-machine-name'), + +)); + +/* +|-------------------------------------------------------------------------- +| Bind Paths +|-------------------------------------------------------------------------- +| +| Here we are binding the paths configured in paths.php to the app. You +| should not be changing these here. If you need to change these you +| may do so within the paths.php file and they will be bound here. +| +*/ + +$app->bindInstallPaths(require __DIR__.'/paths.php'); + +/* +|-------------------------------------------------------------------------- +| Load The Application +|-------------------------------------------------------------------------- +| +| Here we will load the Illuminate application. We'll keep this is in a +| separate location so we can isolate the creation of an application +| from the actual running of the application with a given request. +| +*/ + +$framework = $app['path.base'].'/vendor/laravel/framework/src'; + +require $framework.'/Illuminate/Foundation/start.php'; + +/* +|-------------------------------------------------------------------------- +| Return The Application +|-------------------------------------------------------------------------- +| +| This script returns the application instance. The instance is given to +| the calling script so we can separate the building of the instances +| from the actual running of the application and sending responses. +| +*/ + +return $app; diff --git a/composer.json b/composer.json new file mode 100755 index 000000000..f5c458530 --- /dev/null +++ b/composer.json @@ -0,0 +1,43 @@ +{ + "name": "laravel/laravel", + "description": "The Laravel Framework.", + "keywords": ["framework", "laravel"], + "require": { + "laravel/framework": "4.2.*", + "sayakb/akismet": "dev-master", + "sayakb/captcha": "dev-master", + "rmccue/requests": ">=1.0" + }, + "autoload": { + "classmap": [ + "app/commands", + "app/controllers", + "app/models", + "app/database/migrations", + "app/database/seeds", + "app/lib", + "app/lib/auth", + "app/lib/hashing", + "app/tests/StickyNotesTestCase.php", + "vendor/googleanalytics" + ] + }, + "scripts": { + "post-install-cmd": [ + "php artisan optimize" + ], + "pre-update-cmd": [ + "php artisan clear-compiled" + ], + "post-update-cmd": [ + "php artisan optimize" + ], + "post-create-project-cmd": [ + "php artisan key:generate" + ] + }, + "config": { + "preferred-install": "dist" + }, + "minimum-stability": "dev" +} diff --git a/composer.lock b/composer.lock new file mode 100755 index 000000000..c56b951d8 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1798 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "cc9788d3e223e948aff19fff6cde9055", + "packages": [ + { + "name": "classpreloader/classpreloader", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/mtdowling/ClassPreloader.git", + "reference": "2c9f3bcbab329570c57339895bd11b5dd3b00877" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mtdowling/ClassPreloader/zipball/2c9f3bcbab329570c57339895bd11b5dd3b00877", + "reference": "2c9f3bcbab329570c57339895bd11b5dd3b00877", + "shasum": "" + }, + "require": { + "nikic/php-parser": "~0.9", + "php": ">=5.3.3", + "symfony/console": "~2.1", + "symfony/filesystem": "~2.1", + "symfony/finder": "~2.1" + }, + "bin": [ + "classpreloader.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "ClassPreloader": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Helps class loading performance by generating a single PHP file containing all of the autoloaded files for a specific use case", + "keywords": [ + "autoload", + "class", + "preload" + ], + "time": "2014-03-12 00:05:31" + }, + { + "name": "d11wtq/boris", + "version": "v1.0.8", + "source": { + "type": "git", + "url": "https://github.com/d11wtq/boris.git", + "reference": "125dd4e5752639af7678a22ea597115646d89c6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/d11wtq/boris/zipball/125dd4e5752639af7678a22ea597115646d89c6e", + "reference": "125dd4e5752639af7678a22ea597115646d89c6e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "suggest": { + "ext-pcntl": "*", + "ext-posix": "*", + "ext-readline": "*" + }, + "bin": [ + "bin/boris" + ], + "type": "library", + "autoload": { + "psr-0": { + "Boris": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "time": "2014-01-17 12:21:18" + }, + { + "name": "filp/whoops", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a85fab9a98f1f9b8ebcdbe71733f0d910e5b9adf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a85fab9a98f1f9b8ebcdbe71733f0d910e5b9adf", + "reference": "a85fab9a98f1f9b8ebcdbe71733f0d910e5b9adf", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "mockery/mockery": "0.9.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-0": { + "Whoops": "src/" + }, + "classmap": [ + "src/deprecated" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://github.com/filp/whoops", + "keywords": [ + "error", + "exception", + "handling", + "library", + "silex-provider", + "whoops", + "zf2" + ], + "time": "2014-10-26 09:05:09" + }, + { + "name": "ircmaxell/password-compat", + "version": "1.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/ircmaxell/password_compat.git", + "reference": "9b99377557a33a4129c9194e60a97a685fab21e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/9b99377557a33a4129c9194e60a97a685fab21e0", + "reference": "9b99377557a33a4129c9194e60a97a685fab21e0", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "autoload": { + "files": [ + "lib/password.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Anthony Ferrara", + "email": "ircmaxell@php.net", + "homepage": "http://blog.ircmaxell.com" + } + ], + "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", + "homepage": "https://github.com/ircmaxell/password_compat", + "keywords": [ + "hashing", + "password" + ], + "time": "2014-11-20 19:18:42" + }, + { + "name": "jeremeamia/SuperClosure", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/jeremeamia/super_closure.git", + "reference": "d05400085f7d4ae6f20ba30d36550836c0d061e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jeremeamia/super_closure/zipball/d05400085f7d4ae6f20ba30d36550836c0d061e8", + "reference": "d05400085f7d4ae6f20ba30d36550836c0d061e8", + "shasum": "" + }, + "require": { + "nikic/php-parser": "~0.9", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~3.7" + }, + "type": "library", + "autoload": { + "psr-0": { + "Jeremeamia\\SuperClosure": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeremy Lindblom" + } + ], + "description": "Doing interesting things with closures like serialization.", + "homepage": "https://github.com/jeremeamia/super_closure", + "keywords": [ + "closure", + "function", + "parser", + "serializable", + "serialize", + "tokenizer" + ], + "time": "2013-10-09 04:20:00" + }, + { + "name": "laravel/framework", + "version": "4.2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "3800e9b63ba1a0e6bbe17c9cb3c2a811aaf40ac8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/3800e9b63ba1a0e6bbe17c9cb3c2a811aaf40ac8", + "reference": "3800e9b63ba1a0e6bbe17c9cb3c2a811aaf40ac8", + "shasum": "" + }, + "require": { + "classpreloader/classpreloader": "~1.0.2", + "d11wtq/boris": "~1.0", + "filp/whoops": "1.1.*", + "ircmaxell/password-compat": "~1.0", + "jeremeamia/superclosure": "~1.0.1", + "monolog/monolog": "~1.6", + "nesbot/carbon": "~1.0", + "patchwork/utf8": "1.1.*", + "php": ">=5.4.0", + "phpseclib/phpseclib": "0.3.*", + "predis/predis": "0.8.*", + "stack/builder": "~1.0", + "swiftmailer/swiftmailer": "~5.1", + "symfony/browser-kit": "2.5.*", + "symfony/console": "2.5.*", + "symfony/css-selector": "2.5.*", + "symfony/debug": "2.5.*", + "symfony/dom-crawler": "2.5.*", + "symfony/finder": "2.5.*", + "symfony/http-foundation": "2.5.*", + "symfony/http-kernel": "2.5.*", + "symfony/process": "2.5.*", + "symfony/routing": "2.5.*", + "symfony/security-core": "2.5.*", + "symfony/translation": "2.5.*" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/cache": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/exception": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/foundation": "self.version", + "illuminate/hashing": "self.version", + "illuminate/html": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/mail": "self.version", + "illuminate/pagination": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/remote": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version", + "illuminate/workbench": "self.version" + }, + "require-dev": { + "aws/aws-sdk-php": "~2.6", + "iron-io/iron_mq": "~1.5", + "mockery/mockery": "~0.9", + "pda/pheanstalk": "~2.1", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "doctrine/dbal": "Allow renaming columns and dropping SQLite columns." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/Illuminate/Queue/IlluminateQueueClosure.php" + ], + "files": [ + "src/Illuminate/Support/helpers.php" + ], + "psr-0": { + "Illuminate": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "description": "The Laravel Framework.", + "keywords": [ + "framework", + "laravel" + ], + "time": "2014-12-02 03:12:05" + }, + { + "name": "monolog/monolog", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "3d2567a2f19566a93b6b4add76840266d5f6742d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/3d2567a2f19566a93b6b4add76840266d5f6742d", + "reference": "3d2567a2f19566a93b6b4add76840266d5f6742d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "~2.4, >2.4.8", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "phpunit/phpunit": "~3.7.0", + "raven/raven": "~0.5", + "ruflin/elastica": "0.90.*", + "videlalvaro/php-amqplib": "~2.4" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "raven/raven": "Allow sending log messages to a Sentry server", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "videlalvaro/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "time": "2014-12-06 18:16:08" + }, + { + "name": "nesbot/carbon", + "version": "1.13.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "5cb6e71055f7b0b57956b73d324cc4de31278f42" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/5cb6e71055f7b0b57956b73d324cc4de31278f42", + "reference": "5cb6e71055f7b0b57956b73d324cc4de31278f42", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Carbon": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + } + ], + "description": "A simple API extension for DateTime.", + "homepage": "https://github.com/briannesbitt/Carbon", + "keywords": [ + "date", + "datetime", + "time" + ], + "time": "2014-09-26 02:52:02" + }, + { + "name": "nikic/php-parser", + "version": "0.9.x-dev", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "ef70767475434bdb3615b43c327e2cae17ef12eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ef70767475434bdb3615b43c327e2cae17ef12eb", + "reference": "ef70767475434bdb3615b43c327e2cae17ef12eb", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9-dev" + } + }, + "autoload": { + "psr-0": { + "PHPParser": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2014-07-23 18:24:17" + }, + { + "name": "patchwork/utf8", + "version": "1.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/tchwork/utf8.git", + "reference": "abae29343b1b0e1bfbcb48bf3d59f34189c6e783" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tchwork/utf8/zipball/abae29343b1b0e1bfbcb48bf3d59f34189c6e783", + "reference": "abae29343b1b0e1bfbcb48bf3d59f34189c6e783", + "shasum": "" + }, + "require": { + "lib-pcre": ">=7.3", + "php": ">=5.3.0" + }, + "suggest": { + "ext-iconv": "Use iconv for best performance", + "ext-intl": "Use Intl for best performance", + "ext-mbstring": "Use Mbstring for best performance" + }, + "type": "library", + "autoload": { + "psr-0": { + "Patchwork": "class/", + "Normalizer": "class/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "(Apache-2.0 or GPL-2.0)" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + } + ], + "description": "Portable and performant UTF-8, Unicode and Grapheme Clusters for PHP", + "homepage": "https://github.com/tchwork/utf8", + "keywords": [ + "grapheme", + "i18n", + "unicode", + "utf-8", + "utf8" + ], + "time": "2014-11-10 20:40:47" + }, + { + "name": "phpseclib/phpseclib", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "80ba3cd54adf3b07d0fe427f3078e79b7e3cd564" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/80ba3cd54adf3b07d0fe427f3078e79b7e3cd564", + "reference": "80ba3cd54adf3b07d0fe427f3078e79b7e3cd564", + "shasum": "" + }, + "require": { + "php": ">=5.0.0" + }, + "require-dev": { + "phing/phing": "~2.7", + "phpunit/phpunit": "~4.0", + "sami/sami": "~2.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "suggest": { + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a wide variety of cryptographic operations.", + "pear-pear/PHP_Compat": "Install PHP_Compat to get phpseclib working on PHP < 4.3.3." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.3-dev" + } + }, + "autoload": { + "psr-0": { + "Crypt": "phpseclib/", + "File": "phpseclib/", + "Math": "phpseclib/", + "Net": "phpseclib/", + "System": "phpseclib/" + }, + "files": [ + "phpseclib/Crypt/Random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "phpseclib/" + ], + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "time": "2014-12-10 01:09:57" + }, + { + "name": "predis/predis", + "version": "0.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/nrk/predis.git", + "reference": "4123fcd85d61354c6c9900db76c9597dbd129bf6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nrk/predis/zipball/4123fcd85d61354c6c9900db76c9597dbd129bf6", + "reference": "4123fcd85d61354c6c9900db76c9597dbd129bf6", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "ext-curl": "Allows access to Webdis when paired with phpiredis", + "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + }, + "type": "library", + "autoload": { + "psr-0": { + "Predis": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniele Alessandri", + "email": "suppakilla@gmail.com", + "homepage": "http://clorophilla.net" + } + ], + "description": "Flexible and feature-complete PHP client library for Redis", + "homepage": "http://github.com/nrk/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ], + "time": "2014-08-01 09:43:10" + }, + { + "name": "psr/log", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "a78d6504ff5d4367497785ab2ade91db3a9fbe11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/a78d6504ff5d4367497785ab2ade91db3a9fbe11", + "reference": "a78d6504ff5d4367497785ab2ade91db3a9fbe11", + "shasum": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Psr\\Log\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2014-01-18 15:33:09" + }, + { + "name": "rmccue/requests", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/rmccue/Requests.git", + "reference": "d02c5d36cb8b7f95d53c8754aa89287cc27fc4b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rmccue/Requests/zipball/d02c5d36cb8b7f95d53c8754aa89287cc27fc4b8", + "reference": "d02c5d36cb8b7f95d53c8754aa89287cc27fc4b8", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "require-dev": { + "requests/test-server": "dev-master", + "satooshi/php-coveralls": "dev-master" + }, + "type": "library", + "autoload": { + "psr-0": { + "Requests": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Ryan McCue", + "homepage": "http://ryanmccue.info" + } + ], + "description": "A HTTP library written in PHP, for human beings.", + "homepage": "http://github.com/rmccue/Requests", + "keywords": [ + "curl", + "fsockopen", + "http", + "idna", + "ipv6", + "iri", + "sockets" + ], + "time": "2014-12-01 14:39:18" + }, + { + "name": "sayakb/akismet", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sayakb/akismet.git", + "reference": "efc574d20b9769f57b5699338cbcb7ca62a233b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sayakb/akismet/zipball/efc574d20b9769f57b5699338cbcb7ca62a233b4", + "reference": "efc574d20b9769f57b5699338cbcb7ca62a233b4", + "shasum": "" + }, + "require": { + "illuminate/support": ">=4.0.0", + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Sayakb\\Akismet": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ken Moini", + "email": "ken@kenmoini.com", + "homepage": "http://kenmoini.com" + }, + { + "name": "Sayak Banerjee", + "email": "mail@sayakbanerjee.com", + "homepage": "http://sayakbanerjee.com" + } + ], + "description": "Laravel 4.1 Akismet SPAM Processing Engine, Fork of kenmoini/akismet", + "homepage": "https://github.com/sayakb/akismet", + "keywords": [ + "Akismet", + "laravel", + "spam" + ], + "time": "2013-12-17 02:58:45" + }, + { + "name": "sayakb/captcha", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/sayakb/captcha.git", + "reference": "934f5b273677f11ab0082356403ec35a870563a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sayakb/captcha/zipball/934f5b273677f11ab0082356403ec35a870563a1", + "reference": "934f5b273677f11ab0082356403ec35a870563a1", + "shasum": "" + }, + "require": { + "illuminate/support": ">=4.0.0", + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Sayakb\\Captcha": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Muharrem ERIN", + "email": "me@mewebstudio.com" + }, + { + "name": "Sayak Banerjee", + "email": "mail@sayakbanerjee.com" + } + ], + "description": "Captcha Package for Laravel 4, Fork of mewebstudio/captcha", + "homepage": "https://github.com/sayakb/captcha", + "keywords": [ + "L4", + "Laravel 4", + "captcha", + "captcha image", + "laravel", + "mecaptcha", + "security" + ], + "time": "2013-12-17 02:42:17" + }, + { + "name": "stack/builder", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/stackphp/builder.git", + "reference": "14afc80a5bd9dc160470e3131b697f7d0c070729" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stackphp/builder/zipball/14afc80a5bd9dc160470e3131b697f7d0c070729", + "reference": "14afc80a5bd9dc160470e3131b697f7d0c070729", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/http-foundation": "~2.1", + "symfony/http-kernel": "~2.1" + }, + "require-dev": { + "silex/silex": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Stack": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Builder for stack middlewares based on HttpKernelInterface.", + "keywords": [ + "stack" + ], + "time": "2014-12-09 14:20:11" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "e29c7b692bae6610b137670d6749151fd6e41608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/e29c7b692bae6610b137670d6749151fd6e41608", + "reference": "e29c7b692bae6610b137670d6749151fd6e41608", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "mockery/mockery": "~0.9.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3-dev" + } + }, + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "http://swiftmailer.org", + "keywords": [ + "mail", + "mailer" + ], + "time": "2014-12-05 14:17:46" + }, + { + "name": "symfony/browser-kit", + "version": "2.5.x-dev", + "target-dir": "Symfony/Component/BrowserKit", + "source": { + "type": "git", + "url": "https://github.com/symfony/BrowserKit.git", + "reference": "a4e87ee941bcf8c96ddac676d90762ed1cd6c5fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/BrowserKit/zipball/a4e87ee941bcf8c96ddac676d90762ed1cd6c5fa", + "reference": "a4e87ee941bcf8c96ddac676d90762ed1cd6c5fa", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/dom-crawler": "~2.0" + }, + "require-dev": { + "symfony/css-selector": "~2.0", + "symfony/process": "~2.0" + }, + "suggest": { + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\BrowserKit\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony BrowserKit Component", + "homepage": "http://symfony.com", + "time": "2014-12-02 20:15:53" + }, + { + "name": "symfony/console", + "version": "2.5.x-dev", + "target-dir": "Symfony/Component/Console", + "source": { + "type": "git", + "url": "https://github.com/symfony/Console.git", + "reference": "78e19162652f9fcc2a8f5557bfb21372286d9edd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Console/zipball/78e19162652f9fcc2a8f5557bfb21372286d9edd", + "reference": "78e19162652f9fcc2a8f5557bfb21372286d9edd", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Console\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony Console Component", + "homepage": "http://symfony.com", + "time": "2014-12-08 08:28:21" + }, + { + "name": "symfony/css-selector", + "version": "2.5.x-dev", + "target-dir": "Symfony/Component/CssSelector", + "source": { + "type": "git", + "url": "https://github.com/symfony/CssSelector.git", + "reference": "8819e8e95f14a3544abd7225b0723b617075ca34" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/CssSelector/zipball/8819e8e95f14a3544abd7225b0723b617075ca34", + "reference": "8819e8e95f14a3544abd7225b0723b617075ca34", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\CssSelector\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "http://symfony.com", + "time": "2014-12-02 20:15:53" + }, + { + "name": "symfony/debug", + "version": "2.5.x-dev", + "target-dir": "Symfony/Component/Debug", + "source": { + "type": "git", + "url": "https://github.com/symfony/Debug.git", + "reference": "2394e45f56e621716833617838285ae06e46e7c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Debug/zipball/2394e45f56e621716833617838285ae06e46e7c2", + "reference": "2394e45f56e621716833617838285ae06e46e7c2", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/http-foundation": "~2.1", + "symfony/http-kernel": "~2.1" + }, + "suggest": { + "symfony/http-foundation": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Debug\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony Debug Component", + "homepage": "http://symfony.com", + "time": "2014-12-04 20:26:11" + }, + { + "name": "symfony/dom-crawler", + "version": "2.5.x-dev", + "target-dir": "Symfony/Component/DomCrawler", + "source": { + "type": "git", + "url": "https://github.com/symfony/DomCrawler.git", + "reference": "afbdb416ca0096d2d6be13c6fe42d20b52656076" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/DomCrawler/zipball/afbdb416ca0096d2d6be13c6fe42d20b52656076", + "reference": "afbdb416ca0096d2d6be13c6fe42d20b52656076", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/css-selector": "~2.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\DomCrawler\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony DomCrawler Component", + "homepage": "http://symfony.com", + "time": "2014-12-02 20:15:53" + }, + { + "name": "symfony/event-dispatcher", + "version": "2.7.x-dev", + "target-dir": "Symfony/Component/EventDispatcher", + "source": { + "type": "git", + "url": "https://github.com/symfony/EventDispatcher.git", + "reference": "fb44a3acc6b9788106a2b91ab71b889ce313ac60" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/fb44a3acc6b9788106a2b91ab71b889ce313ac60", + "reference": "fb44a3acc6b9788106a2b91ab71b889ce313ac60", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.0", + "symfony/dependency-injection": "~2.6", + "symfony/expression-language": "~2.6", + "symfony/stopwatch": "~2.2" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "http://symfony.com", + "time": "2014-12-08 08:43:57" + }, + { + "name": "symfony/filesystem", + "version": "2.7.x-dev", + "target-dir": "Symfony/Component/Filesystem", + "source": { + "type": "git", + "url": "https://github.com/symfony/Filesystem.git", + "reference": "c515060ae3829eee36cc22bc416fa60cc5656d8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Filesystem/zipball/c515060ae3829eee36cc22bc416fa60cc5656d8a", + "reference": "c515060ae3829eee36cc22bc416fa60cc5656d8a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Filesystem\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "http://symfony.com", + "time": "2014-12-08 08:43:57" + }, + { + "name": "symfony/finder", + "version": "2.5.x-dev", + "target-dir": "Symfony/Component/Finder", + "source": { + "type": "git", + "url": "https://github.com/symfony/Finder.git", + "reference": "e5487b9a23adc4be228ac550100d89c50f1ee8d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Finder/zipball/e5487b9a23adc4be228ac550100d89c50f1ee8d5", + "reference": "e5487b9a23adc4be228ac550100d89c50f1ee8d5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Finder\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony Finder Component", + "homepage": "http://symfony.com", + "time": "2014-12-02 20:15:53" + }, + { + "name": "symfony/http-foundation", + "version": "2.5.x-dev", + "target-dir": "Symfony/Component/HttpFoundation", + "source": { + "type": "git", + "url": "https://github.com/symfony/HttpFoundation.git", + "reference": "88d0642f6afd56488c9129ebf90839cd3f107df9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/88d0642f6afd56488c9129ebf90839cd3f107df9", + "reference": "88d0642f6afd56488c9129ebf90839cd3f107df9", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/expression-language": "~2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "classmap": [ + "Symfony/Component/HttpFoundation/Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "http://symfony.com", + "time": "2014-12-04 20:26:11" + }, + { + "name": "symfony/http-kernel", + "version": "2.5.x-dev", + "target-dir": "Symfony/Component/HttpKernel", + "source": { + "type": "git", + "url": "https://github.com/symfony/HttpKernel.git", + "reference": "1e92f58d20c0f167f635e493f3552258bbe4b9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/HttpKernel/zipball/1e92f58d20c0f167f635e493f3552258bbe4b9be", + "reference": "1e92f58d20c0f167f635e493f3552258bbe4b9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "psr/log": "~1.0", + "symfony/debug": "~2.5", + "symfony/event-dispatcher": "~2.5", + "symfony/http-foundation": "~2.5" + }, + "require-dev": { + "symfony/browser-kit": "~2.2", + "symfony/class-loader": "~2.1", + "symfony/config": "~2.0", + "symfony/console": "~2.2", + "symfony/dependency-injection": "~2.0", + "symfony/expression-language": "~2.4", + "symfony/finder": "~2.0", + "symfony/process": "~2.0", + "symfony/routing": "~2.2", + "symfony/stopwatch": "~2.2", + "symfony/templating": "~2.2" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\HttpKernel\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony HttpKernel Component", + "homepage": "http://symfony.com", + "time": "2014-12-04 20:26:11" + }, + { + "name": "symfony/process", + "version": "2.5.x-dev", + "target-dir": "Symfony/Component/Process", + "source": { + "type": "git", + "url": "https://github.com/symfony/Process.git", + "reference": "fbdff9a20698d841f4386c3c07f0438c03ff2a56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Process/zipball/fbdff9a20698d841f4386c3c07f0438c03ff2a56", + "reference": "fbdff9a20698d841f4386c3c07f0438c03ff2a56", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Process\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony Process Component", + "homepage": "http://symfony.com", + "time": "2014-12-04 20:26:11" + }, + { + "name": "symfony/routing", + "version": "2.5.x-dev", + "target-dir": "Symfony/Component/Routing", + "source": { + "type": "git", + "url": "https://github.com/symfony/Routing.git", + "reference": "a96c7fdfddea0d3104a9cd47f1d58c4951190573" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Routing/zipball/a96c7fdfddea0d3104a9cd47f1d58c4951190573", + "reference": "a96c7fdfddea0d3104a9cd47f1d58c4951190573", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "psr/log": "~1.0", + "symfony/config": "~2.2", + "symfony/expression-language": "~2.4", + "symfony/http-foundation": "~2.3", + "symfony/yaml": "~2.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/expression-language": "For using expression matching", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Routing\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony Routing Component", + "homepage": "http://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "time": "2014-12-04 20:26:11" + }, + { + "name": "symfony/security-core", + "version": "2.5.x-dev", + "target-dir": "Symfony/Component/Security/Core", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-core.git", + "reference": "1644e3aee8927e990816c1a3108a52055be56ca5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-core/zipball/1644e3aee8927e990816c1a3108a52055be56ca5", + "reference": "1644e3aee8927e990816c1a3108a52055be56ca5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "ircmaxell/password-compat": "1.0.*", + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1", + "symfony/expression-language": "~2.4", + "symfony/http-foundation": "~2.4", + "symfony/validator": "~2.5" + }, + "suggest": { + "ircmaxell/password-compat": "For using the BCrypt password encoder in PHP <5.5", + "symfony/event-dispatcher": "", + "symfony/expression-language": "For using the expression voter", + "symfony/http-foundation": "", + "symfony/validator": "For using the user password constraint" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Security\\Core\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony Security Component - Core Library", + "homepage": "http://symfony.com", + "time": "2014-12-08 08:43:12" + }, + { + "name": "symfony/translation", + "version": "2.5.x-dev", + "target-dir": "Symfony/Component/Translation", + "source": { + "type": "git", + "url": "https://github.com/symfony/Translation.git", + "reference": "a5217a00083d1fab7e3b9261f2b47ec5785269de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Translation/zipball/a5217a00083d1fab7e3b9261f2b47ec5785269de", + "reference": "a5217a00083d1fab7e3b9261f2b47ec5785269de", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/config": "~2.0", + "symfony/intl": "~2.3", + "symfony/yaml": "~2.2" + }, + "suggest": { + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony Translation Component", + "homepage": "http://symfony.com", + "time": "2014-12-04 20:26:11" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": { + "sayakb/akismet": 20, + "sayakb/captcha": 20 + }, + "prefer-stable": false, + "platform": [], + "platform-dev": [] +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100755 index 000000000..ace9541c9 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,18 @@ + + + + + ./app/tests/ + + + diff --git a/public/.htaccess b/public/.htaccess new file mode 100755 index 000000000..b5aa044aa --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,15 @@ + + + Options -MultiViews + + + RewriteEngine On + + # Redirect Trailing Slashes... + RewriteRule ^(.*)/$ /$1 [L,R=301] + + # Handle Front Controller... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + diff --git a/public/assets/bootstrap/css/bootstrap.css b/public/assets/bootstrap/css/bootstrap.css new file mode 100755 index 000000000..037dd0561 --- /dev/null +++ b/public/assets/bootstrap/css/bootstrap.css @@ -0,0 +1,6203 @@ +/*! + * Bootstrap v3.2.0 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +/*! normalize.css v3.0.1 | MIT License | git.io/normalize */ +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + margin: .67em 0; + font-size: 2em; +} +mark { + color: #000; + background: #ff0; +} +small { + font-size: 80%; +} +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sup { + top: -.5em; +} +sub { + bottom: -.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + height: 0; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + margin: 0; + font: inherit; + color: inherit; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + padding: .35em .625em .75em; + margin: 0 2px; + border: 1px solid #c0c0c0; +} +legend { + padding: 0; + border: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-spacing: 0; + border-collapse: collapse; +} +td, +th { + padding: 0; +} +@media print { + * { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + a[href^="javascript:"]:after, + a[href^="#"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + select { + background: #fff !important; + } + .navbar { + display: none; + } + .table td, + .table th { + background-color: #fff !important; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +@font-face { + font-family: 'Glyphicons Halflings'; + + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.glyphicon-asterisk:before { + content: "\2a"; +} +.glyphicon-plus:before { + content: "\2b"; +} +.glyphicon-euro:before { + content: "\20ac"; +} +.glyphicon-minus:before { + content: "\2212"; +} +.glyphicon-cloud:before { + content: "\2601"; +} +.glyphicon-envelope:before { + content: "\2709"; +} +.glyphicon-pencil:before { + content: "\270f"; +} +.glyphicon-glass:before { + content: "\e001"; +} +.glyphicon-music:before { + content: "\e002"; +} +.glyphicon-search:before { + content: "\e003"; +} +.glyphicon-heart:before { + content: "\e005"; +} +.glyphicon-star:before { + content: "\e006"; +} +.glyphicon-star-empty:before { + content: "\e007"; +} +.glyphicon-user:before { + content: "\e008"; +} +.glyphicon-film:before { + content: "\e009"; +} +.glyphicon-th-large:before { + content: "\e010"; +} +.glyphicon-th:before { + content: "\e011"; +} +.glyphicon-th-list:before { + content: "\e012"; +} +.glyphicon-ok:before { + content: "\e013"; +} +.glyphicon-remove:before { + content: "\e014"; +} +.glyphicon-zoom-in:before { + content: "\e015"; +} +.glyphicon-zoom-out:before { + content: "\e016"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-signal:before { + content: "\e018"; +} +.glyphicon-cog:before { + content: "\e019"; +} +.glyphicon-trash:before { + content: "\e020"; +} +.glyphicon-home:before { + content: "\e021"; +} +.glyphicon-file:before { + content: "\e022"; +} +.glyphicon-time:before { + content: "\e023"; +} +.glyphicon-road:before { + content: "\e024"; +} +.glyphicon-download-alt:before { + content: "\e025"; +} +.glyphicon-download:before { + content: "\e026"; +} +.glyphicon-upload:before { + content: "\e027"; +} +.glyphicon-inbox:before { + content: "\e028"; +} +.glyphicon-play-circle:before { + content: "\e029"; +} +.glyphicon-repeat:before { + content: "\e030"; +} +.glyphicon-refresh:before { + content: "\e031"; +} +.glyphicon-list-alt:before { + content: "\e032"; +} +.glyphicon-lock:before { + content: "\e033"; +} +.glyphicon-flag:before { + content: "\e034"; +} +.glyphicon-headphones:before { + content: "\e035"; +} +.glyphicon-volume-off:before { + content: "\e036"; +} +.glyphicon-volume-down:before { + content: "\e037"; +} +.glyphicon-volume-up:before { + content: "\e038"; +} +.glyphicon-qrcode:before { + content: "\e039"; +} +.glyphicon-barcode:before { + content: "\e040"; +} +.glyphicon-tag:before { + content: "\e041"; +} +.glyphicon-tags:before { + content: "\e042"; +} +.glyphicon-book:before { + content: "\e043"; +} +.glyphicon-bookmark:before { + content: "\e044"; +} +.glyphicon-print:before { + content: "\e045"; +} +.glyphicon-camera:before { + content: "\e046"; +} +.glyphicon-font:before { + content: "\e047"; +} +.glyphicon-bold:before { + content: "\e048"; +} +.glyphicon-italic:before { + content: "\e049"; +} +.glyphicon-text-height:before { + content: "\e050"; +} +.glyphicon-text-width:before { + content: "\e051"; +} +.glyphicon-align-left:before { + content: "\e052"; +} +.glyphicon-align-center:before { + content: "\e053"; +} +.glyphicon-align-right:before { + content: "\e054"; +} +.glyphicon-align-justify:before { + content: "\e055"; +} +.glyphicon-list:before { + content: "\e056"; +} +.glyphicon-indent-left:before { + content: "\e057"; +} +.glyphicon-indent-right:before { + content: "\e058"; +} +.glyphicon-facetime-video:before { + content: "\e059"; +} +.glyphicon-picture:before { + content: "\e060"; +} +.glyphicon-map-marker:before { + content: "\e062"; +} +.glyphicon-adjust:before { + content: "\e063"; +} +.glyphicon-tint:before { + content: "\e064"; +} +.glyphicon-edit:before { + content: "\e065"; +} +.glyphicon-share:before { + content: "\e066"; +} +.glyphicon-check:before { + content: "\e067"; +} +.glyphicon-move:before { + content: "\e068"; +} +.glyphicon-step-backward:before { + content: "\e069"; +} +.glyphicon-fast-backward:before { + content: "\e070"; +} +.glyphicon-backward:before { + content: "\e071"; +} +.glyphicon-play:before { + content: "\e072"; +} +.glyphicon-pause:before { + content: "\e073"; +} +.glyphicon-stop:before { + content: "\e074"; +} +.glyphicon-forward:before { + content: "\e075"; +} +.glyphicon-fast-forward:before { + content: "\e076"; +} +.glyphicon-step-forward:before { + content: "\e077"; +} +.glyphicon-eject:before { + content: "\e078"; +} +.glyphicon-chevron-left:before { + content: "\e079"; +} +.glyphicon-chevron-right:before { + content: "\e080"; +} +.glyphicon-plus-sign:before { + content: "\e081"; +} +.glyphicon-minus-sign:before { + content: "\e082"; +} +.glyphicon-remove-sign:before { + content: "\e083"; +} +.glyphicon-ok-sign:before { + content: "\e084"; +} +.glyphicon-question-sign:before { + content: "\e085"; +} +.glyphicon-info-sign:before { + content: "\e086"; +} +.glyphicon-screenshot:before { + content: "\e087"; +} +.glyphicon-remove-circle:before { + content: "\e088"; +} +.glyphicon-ok-circle:before { + content: "\e089"; +} +.glyphicon-ban-circle:before { + content: "\e090"; +} +.glyphicon-arrow-left:before { + content: "\e091"; +} +.glyphicon-arrow-right:before { + content: "\e092"; +} +.glyphicon-arrow-up:before { + content: "\e093"; +} +.glyphicon-arrow-down:before { + content: "\e094"; +} +.glyphicon-share-alt:before { + content: "\e095"; +} +.glyphicon-resize-full:before { + content: "\e096"; +} +.glyphicon-resize-small:before { + content: "\e097"; +} +.glyphicon-exclamation-sign:before { + content: "\e101"; +} +.glyphicon-gift:before { + content: "\e102"; +} +.glyphicon-leaf:before { + content: "\e103"; +} +.glyphicon-fire:before { + content: "\e104"; +} +.glyphicon-eye-open:before { + content: "\e105"; +} +.glyphicon-eye-close:before { + content: "\e106"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-plane:before { + content: "\e108"; +} +.glyphicon-calendar:before { + content: "\e109"; +} +.glyphicon-random:before { + content: "\e110"; +} +.glyphicon-comment:before { + content: "\e111"; +} +.glyphicon-magnet:before { + content: "\e112"; +} +.glyphicon-chevron-up:before { + content: "\e113"; +} +.glyphicon-chevron-down:before { + content: "\e114"; +} +.glyphicon-retweet:before { + content: "\e115"; +} +.glyphicon-shopping-cart:before { + content: "\e116"; +} +.glyphicon-folder-close:before { + content: "\e117"; +} +.glyphicon-folder-open:before { + content: "\e118"; +} +.glyphicon-resize-vertical:before { + content: "\e119"; +} +.glyphicon-resize-horizontal:before { + content: "\e120"; +} +.glyphicon-hdd:before { + content: "\e121"; +} +.glyphicon-bullhorn:before { + content: "\e122"; +} +.glyphicon-bell:before { + content: "\e123"; +} +.glyphicon-certificate:before { + content: "\e124"; +} +.glyphicon-thumbs-up:before { + content: "\e125"; +} +.glyphicon-thumbs-down:before { + content: "\e126"; +} +.glyphicon-hand-right:before { + content: "\e127"; +} +.glyphicon-hand-left:before { + content: "\e128"; +} +.glyphicon-hand-up:before { + content: "\e129"; +} +.glyphicon-hand-down:before { + content: "\e130"; +} +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} +.glyphicon-globe:before { + content: "\e135"; +} +.glyphicon-wrench:before { + content: "\e136"; +} +.glyphicon-tasks:before { + content: "\e137"; +} +.glyphicon-filter:before { + content: "\e138"; +} +.glyphicon-briefcase:before { + content: "\e139"; +} +.glyphicon-fullscreen:before { + content: "\e140"; +} +.glyphicon-dashboard:before { + content: "\e141"; +} +.glyphicon-paperclip:before { + content: "\e142"; +} +.glyphicon-heart-empty:before { + content: "\e143"; +} +.glyphicon-link:before { + content: "\e144"; +} +.glyphicon-phone:before { + content: "\e145"; +} +.glyphicon-pushpin:before { + content: "\e146"; +} +.glyphicon-usd:before { + content: "\e148"; +} +.glyphicon-gbp:before { + content: "\e149"; +} +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; +} +.glyphicon-expand:before { + content: "\e158"; +} +.glyphicon-collapse-down:before { + content: "\e159"; +} +.glyphicon-collapse-up:before { + content: "\e160"; +} +.glyphicon-log-in:before { + content: "\e161"; +} +.glyphicon-flash:before { + content: "\e162"; +} +.glyphicon-log-out:before { + content: "\e163"; +} +.glyphicon-new-window:before { + content: "\e164"; +} +.glyphicon-record:before { + content: "\e165"; +} +.glyphicon-save:before { + content: "\e166"; +} +.glyphicon-open:before { + content: "\e167"; +} +.glyphicon-saved:before { + content: "\e168"; +} +.glyphicon-import:before { + content: "\e169"; +} +.glyphicon-export:before { + content: "\e170"; +} +.glyphicon-send:before { + content: "\e171"; +} +.glyphicon-floppy-disk:before { + content: "\e172"; +} +.glyphicon-floppy-saved:before { + content: "\e173"; +} +.glyphicon-floppy-remove:before { + content: "\e174"; +} +.glyphicon-floppy-save:before { + content: "\e175"; +} +.glyphicon-floppy-open:before { + content: "\e176"; +} +.glyphicon-credit-card:before { + content: "\e177"; +} +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-cutlery:before { + content: "\e179"; +} +.glyphicon-header:before { + content: "\e180"; +} +.glyphicon-compressed:before { + content: "\e181"; +} +.glyphicon-earphone:before { + content: "\e182"; +} +.glyphicon-phone-alt:before { + content: "\e183"; +} +.glyphicon-tower:before { + content: "\e184"; +} +.glyphicon-stats:before { + content: "\e185"; +} +.glyphicon-sd-video:before { + content: "\e186"; +} +.glyphicon-hd-video:before { + content: "\e187"; +} +.glyphicon-subtitles:before { + content: "\e188"; +} +.glyphicon-sound-stereo:before { + content: "\e189"; +} +.glyphicon-sound-dolby:before { + content: "\e190"; +} +.glyphicon-sound-5-1:before { + content: "\e191"; +} +.glyphicon-sound-6-1:before { + content: "\e192"; +} +.glyphicon-sound-7-1:before { + content: "\e193"; +} +.glyphicon-copyright-mark:before { + content: "\e194"; +} +.glyphicon-registration-mark:before { + content: "\e195"; +} +.glyphicon-cloud-download:before { + content: "\e197"; +} +.glyphicon-cloud-upload:before { + content: "\e198"; +} +.glyphicon-tree-conifer:before { + content: "\e199"; +} +.glyphicon-tree-deciduous:before { + content: "\e200"; +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 10px; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333; + background-color: #fff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #428bca; + text-decoration: none; +} +a:hover, +a:focus { + color: #2a6496; + text-decoration: underline; +} +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + width: 100% \9; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 6px; +} +.img-thumbnail { + display: inline-block; + width: 100% \9; + max-width: 100%; + height: auto; + padding: 4px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + -o-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eee; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: normal; + line-height: 1; + color: #777; +} +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 20px; + margin-bottom: 10px; +} +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; +} +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px; +} +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; +} +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 30px; +} +h3, +.h3 { + font-size: 24px; +} +h4, +.h4 { + font-size: 18px; +} +h5, +.h5 { + font-size: 14px; +} +h6, +.h6 { + font-size: 12px; +} +p { + margin: 0 0 10px; +} +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 300; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} +small, +.small { + font-size: 85%; +} +cite { + font-style: normal; +} +mark, +.mark { + padding: .2em; + background-color: #fcf8e3; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +.text-justify { + text-align: justify; +} +.text-nowrap { + white-space: nowrap; +} +.text-lowercase { + text-transform: lowercase; +} +.text-uppercase { + text-transform: uppercase; +} +.text-capitalize { + text-transform: capitalize; +} +.text-muted { + color: #777; +} +.text-primary { + color: #428bca; +} +a.text-primary:hover { + color: #3071a9; +} +.text-success { + color: #3c763d; +} +a.text-success:hover { + color: #2b542c; +} +.text-info { + color: #31708f; +} +a.text-info:hover { + color: #245269; +} +.text-warning { + color: #8a6d3b; +} +a.text-warning:hover { + color: #66512c; +} +.text-danger { + color: #a94442; +} +a.text-danger:hover { + color: #843534; +} +.bg-primary { + color: #fff; + background-color: #428bca; +} +a.bg-primary:hover { + background-color: #3071a9; +} +.bg-success { + background-color: #dff0d8; +} +a.bg-success:hover { + background-color: #c1e2b3; +} +.bg-info { + background-color: #d9edf7; +} +a.bg-info:hover { + background-color: #afd9ee; +} +.bg-warning { + background-color: #fcf8e3; +} +a.bg-warning:hover { + background-color: #f7ecb5; +} +.bg-danger { + background-color: #f2dede; +} +a.bg-danger:hover { + background-color: #e4b9b9; +} +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eee; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + margin-left: -5px; + list-style: none; +} +.list-inline > li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; +} +dl { + margin-top: 0; + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #777; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eee; +} +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #777; +} +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: '\2014 \00A0'; +} +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + text-align: right; + border-right: 5px solid #eee; + border-left: 0; +} +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ''; +} +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: '\00A0 \2014'; +} +blockquote:before, +blockquote:after { + content: ""; +} +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} +kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; + border-radius: 3px; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); +} +kbd kbd { + padding: 0; + font-size: 100%; + -webkit-box-shadow: none; + box-shadow: none; +} +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.container-fluid { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +.row { + margin-right: -15px; + margin-left: -15px; +} +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: auto; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: auto; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0; +} +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0; + } +} +table { + background-color: transparent; +} +th { + text-align: left; +} +.table { + width: 100%; + max-width: 100%; + margin-bottom: 20px; +} +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd; +} +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #ddd; +} +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #ddd; +} +.table .table { + background-color: #fff; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-child(odd) > td, +.table-striped > tbody > tr:nth-child(odd) > th { + background-color: #f9f9f9; +} +.table-hover > tbody > tr:hover > td, +.table-hover > tbody > tr:hover > th { + background-color: #f5f5f5; +} +table col[class*="col-"] { + position: static; + display: table-column; + float: none; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + display: table-cell; + float: none; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr:hover > .success, +.table-hover > tbody > tr.success:hover > th { + background-color: #d0e9c6; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #d9edf7; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr:hover > .info, +.table-hover > tbody > tr.info:hover > th { + background-color: #c4e3f3; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr:hover > .warning, +.table-hover > tbody > tr.warning:hover > th { + background-color: #faf2cc; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr:hover > .danger, +.table-hover > tbody > tr.danger:hover > th { + background-color: #ebcccc; +} +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #ddd; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; +} +input[type="file"] { + display: block; +} +input[type="range"] { + display: block; + width: 100%; +} +select[multiple], +select[size] { + height: auto; +} +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857143; + color: #555; +} +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); +} +.form-control::-moz-placeholder { + color: #777; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #777; +} +.form-control::-webkit-input-placeholder { + color: #777; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + cursor: not-allowed; + background-color: #eee; + opacity: 1; +} +textarea.form-control { + height: auto; +} +input[type="search"] { + -webkit-appearance: none; +} +input[type="date"], +input[type="time"], +input[type="datetime-local"], +input[type="month"] { + line-height: 34px; + line-height: 1.42857143 \0; +} +input[type="date"].input-sm, +input[type="time"].input-sm, +input[type="datetime-local"].input-sm, +input[type="month"].input-sm { + line-height: 30px; +} +input[type="date"].input-lg, +input[type="time"].input-lg, +input[type="datetime-local"].input-lg, +input[type="month"].input-lg { + line-height: 46px; +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + position: relative; + display: block; + min-height: 20px; + margin-top: 10px; + margin-bottom: 10px; +} +.radio label, +.checkbox label { + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-top: 4px \9; + margin-left: -20px; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + vertical-align: middle; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"].disabled, +input[type="checkbox"].disabled, +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"] { + cursor: not-allowed; +} +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed; +} +.form-control-static { + padding-top: 7px; + padding-bottom: 7px; + margin-bottom: 0; +} +.form-control-static.input-lg, +.form-control-static.input-sm { + padding-right: 0; + padding-left: 0; +} +.input-sm, +.form-horizontal .form-group-sm .form-control { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-sm { + height: 30px; + line-height: 30px; +} +textarea.input-sm, +select[multiple].input-sm { + height: auto; +} +.input-lg, +.form-horizontal .form-group-lg .form-control { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} +select.input-lg { + height: 46px; + line-height: 46px; +} +textarea.input-lg, +select[multiple].input-lg { + height: auto; +} +.has-feedback { + position: relative; +} +.has-feedback .form-control { + padding-right: 42.5px; +} +.form-control-feedback { + position: absolute; + top: 25px; + right: 0; + z-index: 2; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; +} +.input-lg + .form-control-feedback { + width: 46px; + height: 46px; + line-height: 46px; +} +.input-sm + .form-control-feedback { + width: 30px; + height: 30px; + line-height: 30px; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline { + color: #3c763d; +} +.has-success .form-control { + border-color: #3c763d; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-success .form-control:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; +} +.has-success .input-group-addon { + color: #3c763d; + background-color: #dff0d8; + border-color: #3c763d; +} +.has-success .form-control-feedback { + color: #3c763d; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline { + color: #8a6d3b; +} +.has-warning .form-control { + border-color: #8a6d3b; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-warning .form-control:focus { + border-color: #66512c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; +} +.has-warning .input-group-addon { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #8a6d3b; +} +.has-warning .form-control-feedback { + color: #8a6d3b; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline { + color: #a94442; +} +.has-error .form-control { + border-color: #a94442; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-error .form-control:focus { + border-color: #843534; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; +} +.has-error .input-group-addon { + color: #a94442; + background-color: #f2dede; + border-color: #a94442; +} +.has-error .form-control-feedback { + color: #a94442; +} +.has-feedback label.sr-only ~ .form-control-feedback { + top: 0; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .input-group { + display: inline-table; + vertical-align: middle; + } + .form-inline .input-group .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 27px; +} +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + padding-top: 7px; + margin-bottom: 0; + text-align: right; + } +} +.form-horizontal .has-feedback .form-control-feedback { + top: 0; + right: 15px; +} +@media (min-width: 768px) { + .form-horizontal .form-group-lg .control-label { + padding-top: 14.3px; + } +} +@media (min-width: 768px) { + .form-horizontal .form-group-sm .control-label { + padding-top: 6px; + } +} +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.btn:focus, +.btn:active:focus, +.btn.active:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus { + color: #333; + text-decoration: none; +} +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + pointer-events: none; + cursor: not-allowed; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; + opacity: .65; +} +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; +} +.btn-default:hover, +.btn-default:focus, +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + background-image: none; +} +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #fff; + border-color: #ccc; +} +.btn-default .badge { + color: #fff; + background-color: #333; +} +.btn-primary { + color: #fff; + background-color: #428bca; + border-color: #357ebd; +} +.btn-primary:hover, +.btn-primary:focus, +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + color: #fff; + background-color: #3071a9; + border-color: #285e8e; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + background-image: none; +} +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #428bca; + border-color: #357ebd; +} +.btn-primary .badge { + color: #428bca; + background-color: #fff; +} +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success:hover, +.btn-success:focus, +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + color: #fff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + background-image: none; +} +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success .badge { + color: #5cb85c; + background-color: #fff; +} +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info:hover, +.btn-info:focus, +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + background-image: none; +} +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info .badge { + color: #5bc0de; + background-color: #fff; +} +.btn-warning { + color: #fff; + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning:hover, +.btn-warning:focus, +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + color: #fff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + background-image: none; +} +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning .badge { + color: #f0ad4e; + background-color: #fff; +} +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger:hover, +.btn-danger:focus, +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + background-image: none; +} +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger .badge { + color: #d9534f; + background-color: #fff; +} +.btn-link { + font-weight: normal; + color: #428bca; + cursor: pointer; + border-radius: 0; +} +.btn-link, +.btn-link:active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #2a6496; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #777; + text-decoration: none; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-block { + display: block; + width: 100%; +} +.btn-block + .btn-block { + margin-top: 5px; +} +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} +.fade { + opacity: 0; + -webkit-transition: opacity .15s linear; + -o-transition: opacity .15s linear; + transition: opacity .15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; +} +.collapse.in { + display: block; +} +tr.collapse.in { + display: table-row; +} +tbody.collapse.in { + display: table-row-group; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition: height .35s ease; + -o-transition: height .35s ease; + transition: height .35s ease; +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px solid; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + text-align: left; + list-style: none; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); + box-shadow: 0 6px 12px rgba(0, 0, 0, .175); +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #333; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + color: #262626; + text-decoration: none; + background-color: #f5f5f5; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #fff; + text-decoration: none; + background-color: #428bca; + outline: 0; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #777; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-menu-right { + right: 0; + left: auto; +} +.dropdown-menu-left { + right: auto; + left: 0; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #777; + white-space: nowrap; +} +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + content: ""; + border-top: 0; + border-bottom: 4px solid; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; + } + .navbar-right .dropdown-menu-left { + right: auto; + left: 0; + } +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus { + outline: 0; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-toolbar { + margin-left: -5px; +} +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left; +} +.btn-toolbar > .btn, +.btn-toolbar > .btn-group, +.btn-toolbar > .input-group { + margin-left: 5px; +} +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child > .btn:last-child, +.btn-group > .btn-group:first-child > .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn-group:last-child > .btn:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group > .btn + .dropdown-toggle { + padding-right: 8px; + padding-left: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-right: 12px; + padding-left: 12px; +} +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn-group.open .dropdown-toggle.btn-link { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn .caret { + margin-left: 0; +} +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group, +.btn-group-vertical > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; +} +.btn-group-vertical > .btn-group > .btn { + float: none; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 4px; +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} +.btn-group-justified > .btn, +.btn-group-justified > .btn-group { + display: table-cell; + float: none; + width: 1%; +} +.btn-group-justified > .btn-group .btn { + width: 100%; +} +.btn-group-justified > .btn-group .dropdown-menu { + left: auto; +} +[data-toggle="buttons"] > .btn > input[type="radio"], +[data-toggle="buttons"] > .btn > input[type="checkbox"] { + position: absolute; + z-index: -1; + filter: alpha(opacity=0); + opacity: 0; +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group[class*="col-"] { + float: none; + padding-right: 0; + padding-left: 0; +} +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 46px; + line-height: 46px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .input-group-btn > .btn { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + color: #555; + text-align: center; + background-color: #eee; + border: 1px solid #ccc; + border-radius: 4px; +} +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} +.input-group-btn > .btn { + position: relative; +} +.input-group-btn > .btn + .btn { + margin-left: -1px; +} +.input-group-btn > .btn:hover, +.input-group-btn > .btn:focus, +.input-group-btn > .btn:active { + z-index: 2; +} +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group { + margin-right: -1px; +} +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group { + margin-left: -1px; +} +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eee; +} +.nav > li.disabled > a { + color: #777; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #777; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eee; + border-color: #428bca; +} +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #ddd; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #eee #eee #ddd; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555; + cursor: default; + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: transparent; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-tabs.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-tabs.nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs.nav-justified > .active > a, +.nav-tabs.nav-justified > .active > a:hover, +.nav-tabs.nav-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs.nav-justified > .active > a, + .nav-tabs.nav-justified > .active > a:hover, + .nav-tabs.nav-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 4px; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #fff; + background-color: #428bca; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs-justified > .active > a, +.nav-tabs-justified > .active > a:hover, +.nav-tabs-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs-justified > .active > a, + .nav-tabs-justified > .active > a:hover, + .nav-tabs-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + -webkit-overflow-scrolling: touch; + border-top: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-right: 0; + padding-left: 0; + } +} +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} +@media (max-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; + -webkit-transform: translate3d(0, 0, 0); + -o-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} +.navbar-brand { + float: left; + height: 50px; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -15px; + } +} +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-top: 8px; + margin-right: 15px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.navbar-toggle:focus { + outline: 0; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 7.5px -15px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } + .navbar-nav.navbar-right:last-child { + margin-right: -15px; + } +} +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + } +} +.navbar-form { + padding: 10px 15px; + margin-top: 8px; + margin-right: -15px; + margin-bottom: 8px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-form.navbar-right:last-child { + margin-right: -15px; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} +.navbar-btn.btn-sm { + margin-top: 10px; + margin-bottom: 10px; +} +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px; +} +.navbar-text { + margin-top: 15px; + margin-bottom: 15px; +} +@media (min-width: 768px) { + .navbar-text { + float: left; + margin-right: 15px; + margin-left: 15px; + } + .navbar-text.navbar-right:last-child { + margin-right: 0; + } +} +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} +.navbar-default .navbar-brand { + color: #777; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} +.navbar-default .navbar-text { + color: #777; +} +.navbar-default .navbar-nav > li > a { + color: #777; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333; + background-color: transparent; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555; + background-color: #e7e7e7; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #ccc; + background-color: transparent; +} +.navbar-default .navbar-toggle { + border-color: #ddd; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #ddd; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #888; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e7e7e7; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + color: #555; + background-color: #e7e7e7; +} +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555; + background-color: #e7e7e7; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #ccc; + background-color: transparent; + } +} +.navbar-default .navbar-link { + color: #777; +} +.navbar-default .navbar-link:hover { + color: #333; +} +.navbar-default .btn-link { + color: #777; +} +.navbar-default .btn-link:hover, +.navbar-default .btn-link:focus { + color: #333; +} +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #ccc; +} +.navbar-inverse { + background-color: #222; + border-color: #080808; +} +.navbar-inverse .navbar-brand { + color: #777; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-text { + color: #777; +} +.navbar-inverse .navbar-nav > li > a { + color: #777; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #fff; + background-color: #080808; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444; + background-color: transparent; +} +.navbar-inverse .navbar-toggle { + border-color: #333; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #fff; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + color: #fff; + background-color: #080808; +} +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #777; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #fff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444; + background-color: transparent; + } +} +.navbar-inverse .navbar-link { + color: #777; +} +.navbar-inverse .navbar-link:hover { + color: #fff; +} +.navbar-inverse .btn-link { + color: #777; +} +.navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link:focus { + color: #fff; +} +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #444; +} +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} +.breadcrumb > li { + display: inline-block; +} +.breadcrumb > li + li:before { + padding: 0 5px; + color: #ccc; + content: "/\00a0"; +} +.breadcrumb > .active { + color: #777; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.42857143; + color: #428bca; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + color: #2a6496; + background-color: #eee; + border-color: #ddd; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 2; + color: #fff; + cursor: default; + background-color: #428bca; + border-color: #428bca; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #777; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} +.pager { + padding-left: 0; + margin: 20px 0; + text-align: center; + list-style: none; +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 15px; +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #eee; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #777; + cursor: not-allowed; + background-color: #fff; +} +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} +a.label:hover, +a.label:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} +.btn .label { + position: relative; + top: -1px; +} +.label-default { + background-color: #777; +} +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #5e5e5e; +} +.label-primary { + background-color: #428bca; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #3071a9; +} +.label-success { + background-color: #5cb85c; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} +.label-info { + background-color: #5bc0de; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} +.label-warning { + background-color: #f0ad4e; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} +.label-danger { + background-color: #d9534f; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + background-color: #777; + border-radius: 10px; +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.btn-xs .badge { + top: 0; + padding: 1px 5px; +} +a.badge:hover, +a.badge:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +a.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #428bca; + background-color: #fff; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} +.jumbotron { + padding: 30px; + margin-bottom: 30px; + color: inherit; + background-color: #eee; +} +.jumbotron h1, +.jumbotron .h1 { + color: inherit; +} +.jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200; +} +.jumbotron > hr { + border-top-color: #d5d5d5; +} +.container .jumbotron { + border-radius: 6px; +} +.jumbotron .container { + max-width: 100%; +} +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron { + padding-right: 60px; + padding-left: 60px; + } + .jumbotron h1, + .jumbotron .h1 { + font-size: 63px; + } +} +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + -o-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; +} +.thumbnail > img, +.thumbnail a > img { + margin-right: auto; + margin-left: auto; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #428bca; +} +.thumbnail .caption { + padding: 9px; + color: #333; +} +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable, +.alert-dismissible { + padding-right: 35px; +} +.alert-dismissable .close, +.alert-dismissible .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.alert-success hr { + border-top-color: #c9e2b3; +} +.alert-success .alert-link { + color: #2b542c; +} +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.alert-info hr { + border-top-color: #a6e1ec; +} +.alert-info .alert-link { + color: #245269; +} +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.alert-warning hr { + border-top-color: #f7e1b5; +} +.alert-warning .alert-link { + color: #66512c; +} +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.alert-danger hr { + border-top-color: #e4b9c0; +} +.alert-danger .alert-link { + color: #843534; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@-o-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); +} +.progress-bar { + float: left; + width: 0; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #fff; + text-align: center; + background-color: #428bca; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + -webkit-transition: width .6s ease; + -o-transition: width .6s ease; + transition: width .6s ease; +} +.progress-striped .progress-bar, +.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); + -webkit-background-size: 40px 40px; + background-size: 40px 40px; +} +.progress.active .progress-bar, +.progress-bar.active { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar[aria-valuenow="1"], +.progress-bar[aria-valuenow="2"] { + min-width: 30px; +} +.progress-bar[aria-valuenow="0"] { + min-width: 30px; + color: #777; + background-color: transparent; + background-image: none; + -webkit-box-shadow: none; + box-shadow: none; +} +.progress-bar-success { + background-color: #5cb85c; +} +.progress-striped .progress-bar-success { + 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); +} +.progress-bar-info { + background-color: #5bc0de; +} +.progress-striped .progress-bar-info { + 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); +} +.progress-bar-warning { + background-color: #f0ad4e; +} +.progress-striped .progress-bar-warning { + 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); +} +.progress-bar-danger { + background-color: #d9534f; +} +.progress-striped .progress-bar-danger { + 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); +} +.media, +.media-body { + overflow: hidden; + zoom: 1; +} +.media, +.media .media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} +.media-object { + display: block; +} +.media-heading { + margin: 0 0 5px; +} +.media > .pull-left { + margin-right: 10px; +} +.media > .pull-right { + margin-left: 10px; +} +.media-list { + padding-left: 0; + list-style: none; +} +.list-group { + padding-left: 0; + margin-bottom: 20px; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid #ddd; +} +.list-group-item:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.list-group-item > .badge { + float: right; +} +.list-group-item > .badge + .badge { + margin-right: 5px; +} +a.list-group-item { + color: #555; +} +a.list-group-item .list-group-item-heading { + color: #333; +} +a.list-group-item:hover, +a.list-group-item:focus { + color: #555; + text-decoration: none; + background-color: #f5f5f5; +} +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + color: #777; + background-color: #eee; +} +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #777; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #fff; + background-color: #428bca; + border-color: #428bca; +} +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading > small, +.list-group-item.active:hover .list-group-item-heading > small, +.list-group-item.active:focus .list-group-item-heading > small, +.list-group-item.active .list-group-item-heading > .small, +.list-group-item.active:hover .list-group-item-heading > .small, +.list-group-item.active:focus .list-group-item-heading > .small { + color: inherit; +} +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #e1edf7; +} +.list-group-item-success { + color: #3c763d; + background-color: #dff0d8; +} +a.list-group-item-success { + color: #3c763d; +} +a.list-group-item-success .list-group-item-heading { + color: inherit; +} +a.list-group-item-success:hover, +a.list-group-item-success:focus { + color: #3c763d; + background-color: #d0e9c6; +} +a.list-group-item-success.active, +a.list-group-item-success.active:hover, +a.list-group-item-success.active:focus { + color: #fff; + background-color: #3c763d; + border-color: #3c763d; +} +.list-group-item-info { + color: #31708f; + background-color: #d9edf7; +} +a.list-group-item-info { + color: #31708f; +} +a.list-group-item-info .list-group-item-heading { + color: inherit; +} +a.list-group-item-info:hover, +a.list-group-item-info:focus { + color: #31708f; + background-color: #c4e3f3; +} +a.list-group-item-info.active, +a.list-group-item-info.active:hover, +a.list-group-item-info.active:focus { + color: #fff; + background-color: #31708f; + border-color: #31708f; +} +.list-group-item-warning { + color: #8a6d3b; + background-color: #fcf8e3; +} +a.list-group-item-warning { + color: #8a6d3b; +} +a.list-group-item-warning .list-group-item-heading { + color: inherit; +} +a.list-group-item-warning:hover, +a.list-group-item-warning:focus { + color: #8a6d3b; + background-color: #faf2cc; +} +a.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus { + color: #fff; + background-color: #8a6d3b; + border-color: #8a6d3b; +} +.list-group-item-danger { + color: #a94442; + background-color: #f2dede; +} +a.list-group-item-danger { + color: #a94442; +} +a.list-group-item-danger .list-group-item-heading { + color: inherit; +} +a.list-group-item-danger:hover, +a.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc; +} +a.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus { + color: #fff; + background-color: #a94442; + border-color: #a94442; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.panel { + margin-bottom: 20px; + background-color: #fff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: 0 1px 1px rgba(0, 0, 0, .05); +} +.panel-body { + padding: 15px; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} +.panel-title > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} +.panel > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.list-group + .panel-footer { + border-top-width: 0; +} +.panel > .table, +.panel > .table-responsive > .table, +.panel > .panel-collapse > .table { + margin-bottom: 0; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; +} +.panel > .panel-body + .table, +.panel > .panel-body + .table-responsive { + border-top: 1px solid #ddd; +} +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; +} +.panel > .table-responsive { + margin-bottom: 0; + border: 0; +} +.panel-group { + margin-bottom: 20px; +} +.panel-group .panel { + margin-bottom: 0; + border-radius: 4px; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse > .panel-body { + border-top: 1px solid #ddd; +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #ddd; +} +.panel-default { + border-color: #ddd; +} +.panel-default > .panel-heading { + color: #333; + background-color: #f5f5f5; + border-color: #ddd; +} +.panel-default > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ddd; +} +.panel-default > .panel-heading .badge { + color: #f5f5f5; + background-color: #333; +} +.panel-default > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ddd; +} +.panel-primary { + border-color: #428bca; +} +.panel-primary > .panel-heading { + color: #fff; + background-color: #428bca; + border-color: #428bca; +} +.panel-primary > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #428bca; +} +.panel-primary > .panel-heading .badge { + color: #428bca; + background-color: #fff; +} +.panel-primary > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #428bca; +} +.panel-success { + border-color: #d6e9c6; +} +.panel-success > .panel-heading { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.panel-success > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #d6e9c6; +} +.panel-success > .panel-heading .badge { + color: #dff0d8; + background-color: #3c763d; +} +.panel-success > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #d6e9c6; +} +.panel-info { + border-color: #bce8f1; +} +.panel-info > .panel-heading { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.panel-info > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #bce8f1; +} +.panel-info > .panel-heading .badge { + color: #d9edf7; + background-color: #31708f; +} +.panel-info > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #bce8f1; +} +.panel-warning { + border-color: #faebcc; +} +.panel-warning > .panel-heading { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.panel-warning > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #faebcc; +} +.panel-warning > .panel-heading .badge { + color: #fcf8e3; + background-color: #8a6d3b; +} +.panel-warning > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #faebcc; +} +.panel-danger { + border-color: #ebccd1; +} +.panel-danger > .panel-heading { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.panel-danger > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ebccd1; +} +.panel-danger > .panel-heading .badge { + color: #f2dede; + background-color: #a94442; +} +.panel-danger > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ebccd1; +} +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} +.embed-responsive.embed-responsive-16by9 { + padding-bottom: 56.25%; +} +.embed-responsive.embed-responsive-4by3 { + padding-bottom: 75%; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, .15); +} +.well-lg { + padding: 24px; + border-radius: 6px; +} +.well-sm { + padding: 9px; + border-radius: 3px; +} +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + filter: alpha(opacity=20); + opacity: .2; +} +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + filter: alpha(opacity=50); + opacity: .5; +} +button.close { + -webkit-appearance: none; + padding: 0; + cursor: pointer; + background: transparent; + border: 0; +} +.modal-open { + overflow: hidden; +} +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + display: none; + overflow: hidden; + -webkit-overflow-scrolling: touch; + outline: 0; +} +.modal.fade .modal-dialog { + -webkit-transition: -webkit-transform .3s ease-out; + -o-transition: -o-transform .3s ease-out; + transition: transform .3s ease-out; + -webkit-transform: translate3d(0, -25%, 0); + -o-transform: translate3d(0, -25%, 0); + transform: translate3d(0, -25%, 0); +} +.modal.in .modal-dialog { + -webkit-transform: translate3d(0, 0, 0); + -o-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} +.modal-content { + position: relative; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + outline: 0; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); + box-shadow: 0 3px 9px rgba(0, 0, 0, .5); +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; +} +.modal-backdrop.fade { + filter: alpha(opacity=0); + opacity: 0; +} +.modal-backdrop.in { + filter: alpha(opacity=50); + opacity: .5; +} +.modal-header { + min-height: 16.42857143px; + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.42857143; +} +.modal-body { + position: relative; + padding: 15px; +} +.modal-footer { + padding: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + } + .modal-sm { + width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-size: 12px; + line-height: 1.4; + visibility: visible; + filter: alpha(opacity=0); + opacity: 0; +} +.tooltip.in { + filter: alpha(opacity=90); + opacity: .9; +} +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + text-decoration: none; + background-color: #000; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-left .tooltip-arrow { + bottom: 0; + left: 5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-right .tooltip-arrow { + right: 5px; + bottom: 0; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + left: 5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + right: 5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + text-align: left; + white-space: normal; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2); +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + font-weight: normal; + line-height: 18px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + content: ""; + border-width: 10px; +} +.popover.top > .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999; + border-top-color: rgba(0, 0, 0, .25); + border-bottom-width: 0; +} +.popover.top > .arrow:after { + bottom: 1px; + margin-left: -10px; + content: " "; + border-top-color: #fff; + border-bottom-width: 0; +} +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999; + border-right-color: rgba(0, 0, 0, .25); + border-left-width: 0; +} +.popover.right > .arrow:after { + bottom: -10px; + left: 1px; + content: " "; + border-right-color: #fff; + border-left-width: 0; +} +.popover.bottom > .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999; + border-bottom-color: rgba(0, 0, 0, .25); +} +.popover.bottom > .arrow:after { + top: 1px; + margin-left: -10px; + content: " "; + border-top-width: 0; + border-bottom-color: #fff; +} +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999; + border-left-color: rgba(0, 0, 0, .25); +} +.popover.left > .arrow:after { + right: 1px; + bottom: -10px; + content: " "; + border-right-width: 0; + border-left-color: #fff; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: .6s ease-in-out left; + -o-transition: .6s ease-in-out left; + transition: .6s ease-in-out left; +} +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + line-height: 1; +} +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel-inner > .next { + left: 100%; +} +.carousel-inner > .prev { + left: -100%; +} +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} +.carousel-inner > .active.left { + left: -100%; +} +.carousel-inner > .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 15%; + font-size: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); + filter: alpha(opacity=50); + opacity: .5; +} +.carousel-control.left { + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control.right { + right: 0; + left: auto; + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control:hover, +.carousel-control:focus { + color: #fff; + text-decoration: none; + filter: alpha(opacity=90); + outline: 0; + opacity: .9; +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + margin-top: -10px; + font-family: serif; +} +.carousel-control .icon-prev:before { + content: '\2039'; +} +.carousel-control .icon-next:before { + content: '\203a'; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); + border: 1px solid #fff; + border-radius: 10px; +} +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + background-color: #fff; +} +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); +} +.carousel-caption .btn { + text-shadow: none; +} +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + font-size: 30px; + } + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -15px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -15px; + } + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} +.clearfix:after, +.dl-horizontal dd:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-footer:after { + clear: both; +} +.center-block { + display: block; + margin-right: auto; + margin-left: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; + visibility: hidden !important; +} +.affix { + position: fixed; + -webkit-transform: translate3d(0, 0, 0); + -o-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); +} +@-ms-viewport { + width: device-width; +} +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +.visible-print-block { + display: none !important; +} +@media print { + .visible-print-block { + display: block !important; + } +} +.visible-print-inline { + display: none !important; +} +@media print { + .visible-print-inline { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; +} +@media print { + .visible-print-inline-block { + display: inline-block !important; + } +} +@media print { + .hidden-print { + display: none !important; + } +} +/*# sourceMappingURL=bootstrap.css.map */ diff --git a/public/assets/bootstrap/css/bootstrap.css.map b/public/assets/bootstrap/css/bootstrap.css.map new file mode 100755 index 000000000..bfb561689 --- /dev/null +++ b/public/assets/bootstrap/css/bootstrap.css.map @@ -0,0 +1 @@ +{"version":3,"file":"bootstrap.css","sources":["bootstrap.css","less/normalize.less","less/print.less","less/glyphicons.less","less/scaffolding.less","less/mixins/vendor-prefixes.less","less/mixins/tab-focus.less","less/mixins/image.less","less/type.less","less/mixins/text-emphasis.less","less/mixins/background-variant.less","less/mixins/text-overflow.less","less/code.less","less/grid.less","less/mixins/grid.less","less/mixins/grid-framework.less","less/tables.less","less/mixins/table-row.less","less/forms.less","less/mixins/forms.less","less/buttons.less","less/mixins/buttons.less","less/mixins/opacity.less","less/component-animations.less","less/dropdowns.less","less/mixins/nav-divider.less","less/mixins/reset-filter.less","less/button-groups.less","less/mixins/border-radius.less","less/input-groups.less","less/navs.less","less/navbar.less","less/mixins/nav-vertical-align.less","less/utilities.less","less/breadcrumbs.less","less/pagination.less","less/mixins/pagination.less","less/pager.less","less/labels.less","less/mixins/labels.less","less/badges.less","less/jumbotron.less","less/thumbnails.less","less/alerts.less","less/mixins/alerts.less","less/progress-bars.less","less/mixins/gradients.less","less/mixins/progress-bar.less","less/media.less","less/list-group.less","less/mixins/list-group.less","less/panels.less","less/mixins/panels.less","less/responsive-embed.less","less/wells.less","less/close.less","less/modals.less","less/tooltip.less","less/popovers.less","less/carousel.less","less/mixins/clearfix.less","less/mixins/center-block.less","less/mixins/hide-text.less","less/responsive-utilities.less","less/mixins/responsive-visibility.less"],"names":[],"mappings":"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;EAAW,kBAAA;EHoR9C;AGnRmC;EAAW,kBAAA;EHsR9C;AGrRmC;EAAW,kBAAA;EHwR9C;AGvRmC;EAAW,kBAAA;EH0R9C;AGzRmC;EAAW,kBAAA;EH4R9C;AG3RmC;EAAW,kBAAA;EH8R9C;AG7RmC;EAAW,kBAAA;EHgS9C;AG/RmC;EAAW,kBAAA;EHkS9C;AGjSmC;EAAW,kBAAA;EHoS9C;AGnSmC;EAAW,kBAAA;EHsS9C;AGrSmC;EAAW,kBAAA;EHwS9C;AGvSmC;EAAW,kBAAA;EH0S9C;AGzSmC;EAAW,kBAAA;EH4S9C;AG3SmC;EAAW,kBAAA;EH8S9C;AG7SmC;EAAW,kBAAA;EHgT9C;AG/SmC;EAAW,kBAAA;EHkT9C;AGjTmC;EAAW,kBAAA;EHoT9C;AGnTmC;EAAW,kBAAA;EHsT9C;AGrTmC;EAAW,kBAAA;EHwT9C;AGvTmC;EAAW,kBAAA;EH0T9C;AGzTmC;EAAW,kBAAA;EH4T9C;AG3TmC;EAAW,kBAAA;EH8T9C;AG7TmC;EAAW,kBAAA;EHgU9C;AG/TmC;EAAW,kBAAA;EHkU9C;AGjUmC;EAAW,kBAAA;EHoU9C;AGnUmC;EAAW,kBAAA;EHsU9C;AGrUmC;EAAW,kBAAA;EHwU9C;AGvUmC;EAAW,kBAAA;EH0U9C;AGzUmC;EAAW,kBAAA;EH4U9C;AG3UmC;EAAW,kBAAA;EH8U9C;AG7UmC;EAAW,kBAAA;EHgV9C;AG/UmC;EAAW,kBAAA;EHkV9C;AGjVmC;EAAW,kBAAA;EHoV9C;AGnVmC;EAAW,kBAAA;EHsV9C;AGrVmC;EAAW,kBAAA;EHwV9C;AGvVmC;EAAW,kBAAA;EH0V9C;AGzVmC;EAAW,kBAAA;EH4V9C;AG3VmC;EAAW,kBAAA;EH8V9C;AG7VmC;EAAW,kBAAA;EHgW9C;AG/VmC;EAAW,kBAAA;EHkW9C;AGjWmC;EAAW,kBAAA;EHoW9C;AGnWmC;EAAW,kBAAA;EHsW9C;AGrWmC;EAAW,kBAAA;EHwW9C;AGvWmC;EAAW,kBAAA;EH0W9C;AGzWmC;EAAW,kBAAA;EH4W9C;AG3WmC;EAAW,kBAAA;EH8W9C;AG7WmC;EAAW,kBAAA;EHgX9C;AG/WmC;EAAW,kBAAA;EHkX9C;AGjXmC;EAAW,kBAAA;EHoX9C;AGnXmC;EAAW,kBAAA;EHsX9C;AGrXmC;EAAW,kBAAA;EHwX9C;AGvXmC;EAAW,kBAAA;EH0X9C;AGzXmC;EAAW,kBAAA;EH4X9C;AG3XmC;EAAW,kBAAA;EH8X9C;AG7XmC;EAAW,kBAAA;EHgY9C;AG/XmC;EAAW,kBAAA;EHkY9C;AGjYmC;EAAW,kBAAA;EHoY9C;AGnYmC;EAAW,kBAAA;EHsY9C;AGrYmC;EAAW,kBAAA;EHwY9C;AGvYmC;EAAW,kBAAA;EH0Y9C;AGzYmC;EAAW,kBAAA;EH4Y9C;AG3YmC;EAAW,kBAAA;EH8Y9C;AG7YmC;EAAW,kBAAA;EHgZ9C;AG/YmC;EAAW,kBAAA;EHkZ9C;AGjZmC;EAAW,kBAAA;EHoZ9C;AGnZmC;EAAW,kBAAA;EHsZ9C;AGrZmC;EAAW,kBAAA;EHwZ9C;AGvZmC;EAAW,kBAAA;EH0Z9C;AGzZmC;EAAW,kBAAA;EH4Z9C;AG3ZmC;EAAW,kBAAA;EH8Z9C;AG7ZmC;EAAW,kBAAA;EHga9C;AG/ZmC;EAAW,kBAAA;EHka9C;AGjamC;EAAW,kBAAA;EHoa9C;AGnamC;EAAW,kBAAA;EHsa9C;AGramC;EAAW,kBAAA;EHwa9C;AGvamC;EAAW,kBAAA;EH0a9C;AGzamC;EAAW,kBAAA;EH4a9C;AG3amC;EAAW,kBAAA;EH8a9C;AG7amC;EAAW,kBAAA;EHgb9C;AG/amC;EAAW,kBAAA;EHkb9C;AGjbmC;EAAW,kBAAA;EHob9C;AGnbmC;EAAW,kBAAA;EHsb9C;AGrbmC;EAAW,kBAAA;EHwb9C;AGvbmC;EAAW,kBAAA;EH0b9C;AGzbmC;EAAW,kBAAA;EH4b9C;AG3bmC;EAAW,kBAAA;EH8b9C;AG7bmC;EAAW,kBAAA;EHgc9C;AG/bmC;EAAW,kBAAA;EHkc9C;AGjcmC;EAAW,kBAAA;EHoc9C;AGncmC;EAAW,kBAAA;EHsc9C;AGrcmC;EAAW,kBAAA;EHwc9C;AGvcmC;EAAW,kBAAA;EH0c9C;AGzcmC;EAAW,kBAAA;EH4c9C;AG3cmC;EAAW,kBAAA;EH8c9C;AG7cmC;EAAW,kBAAA;EHgd9C;AG/cmC;EAAW,kBAAA;EHkd9C;AGjdmC;EAAW,kBAAA;EHod9C;AGndmC;EAAW,kBAAA;EHsd9C;AGrdmC;EAAW,kBAAA;EHwd9C;AGvdmC;EAAW,kBAAA;EH0d9C;AGzdmC;EAAW,kBAAA;EH4d9C;AG3dmC;EAAW,kBAAA;EH8d9C;AG7dmC;EAAW,kBAAA;EHge9C;AG/dmC;EAAW,kBAAA;EHke9C;AGjemC;EAAW,kBAAA;EHoe9C;AGnemC;EAAW,kBAAA;EHse9C;AGremC;EAAW,kBAAA;EHwe9C;AGvemC;EAAW,kBAAA;EH0e9C;AGzemC;EAAW,kBAAA;EH4e9C;AG3emC;EAAW,kBAAA;EH8e9C;AG7emC;EAAW,kBAAA;EHgf9C;AG/emC;EAAW,kBAAA;EHkf9C;AGjfmC;EAAW,kBAAA;EHof9C;AGnfmC;EAAW,kBAAA;EHsf9C;AGrfmC;EAAW,kBAAA;EHwf9C;AGvfmC;EAAW,kBAAA;EH0f9C;AGzfmC;EAAW,kBAAA;EH4f9C;AG3fmC;EAAW,kBAAA;EH8f9C;AG7fmC;EAAW,kBAAA;EHggB9C;AG/fmC;EAAW,kBAAA;EHkgB9C;AGjgBmC;EAAW,kBAAA;EHogB9C;AGngBmC;EAAW,kBAAA;EHsgB9C;AGrgBmC;EAAW,kBAAA;EHwgB9C;AGvgBmC;EAAW,kBAAA;EH0gB9C;AGzgBmC;EAAW,kBAAA;EH4gB9C;AG3gBmC;EAAW,kBAAA;EH8gB9C;AG7gBmC;EAAW,kBAAA;EHghB9C;AG/gBmC;EAAW,kBAAA;EHkhB9C;AGjhBmC;EAAW,kBAAA;EHohB9C;AGnhBmC;EAAW,kBAAA;EHshB9C;AGrhBmC;EAAW,kBAAA;EHwhB9C;AGvhBmC;EAAW,kBAAA;EH0hB9C;AGzhBmC;EAAW,kBAAA;EH4hB9C;AG3hBmC;EAAW,kBAAA;EH8hB9C;AG7hBmC;EAAW,kBAAA;EHgiB9C;AG/hBmC;EAAW,kBAAA;EHkiB9C;AGjiBmC;EAAW,kBAAA;EHoiB9C;AGniBmC;EAAW,kBAAA;EHsiB9C;AGriBmC;EAAW,kBAAA;EHwiB9C;AGviBmC;EAAW,kBAAA;EH0iB9C;AGziBmC;EAAW,kBAAA;EH4iB9C;AG3iBmC;EAAW,kBAAA;EH8iB9C;AG7iBmC;EA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A;Efk8CH;Ae97CC;EACE,YAAA;Efg8CH;Ael7CC;EACE,mBAAA;Efo7CH;Aer7CC;EACE,2BAAA;Efu7CH;Aex7CC;EACE,2BAAA;Ef07CH;Ae37CC;EACE,kBAAA;Ef67CH;Ae97CC;EACE,2BAAA;Efg8CH;Aej8CC;EACE,2BAAA;Efm8CH;Aep8CC;EACE,kBAAA;Efs8CH;Aev8CC;EACE,2BAAA;Efy8CH;Ae18CC;EACE,2BAAA;Ef48CH;Ae78CC;EACE,kBAAA;Ef+8CH;Aeh9CC;EACE,2BAAA;Efk9CH;Aen9CC;EACE,0BAAA;Efq9CH;Aet9CC;EACE,iBAAA;Efw9CH;Aa59CD;EE9BI;IACE,aAAA;If6/CH;Eet/CD;IACE,aAAA;Ifw/CD;Eez/CD;IACE,qBAAA;If2/CD;Ee5/CD;IACE,qBAAA;If8/CD;Ee//CD;IACE,YAAA;IfigDD;EelgDD;IACE,qBAAA;IfogDD;EergDD;IACE,qBAAA;IfugDD;EexgDD;IACE,YAAA;If0gDD;Ee3gDD;IACE,qBAAA;If6gDD;Ee9gDD;IACE,qBAAA;IfghDD;EejhDD;IACE,YAAA;IfmhDD;EephDD;IACE,qBAAA;IfshDD;EevhDD;IACE,oBAAA;IfyhDD;Ee3gDD;IACE,aAAA;If6gDD;Ee9gDD;IACE,qBAAA;IfghDD;EejhDD;IACE,qBAAA;IfmhDD;EephDD;IACE,YAAA;IfshDD;EevhDD;IACE,qBAAA;IfyhDD;Ee1hDD;IACE,qBAAA;If4hDD;Ee7hDD;IACE,YAAA;If+hDD;EehiDD;IACE,qBAAA;IfkiDD;EeniDD;IACE,qBAAA;IfqiDD;EetiDD;IACE,YAAA;IfwiDD;EeziDD;IACE,qBAAA;If2iDD;Ee5iDD;IACE,oBAAA;If8iDD;Ee1iDD;IACE,aAAA;If4iDD;Ee5jDD;IACE,YAAA;If8jDD;Ee/jDD;IACE,oBAAA;IfikDD;EelkDD;IACE,oBAAA;IfokDD;EerkDD;IACE,WAAA;IfukDD;EexkDD;IACE,oBAAA;If0kDD;Ee3kDD;IACE,oBAAA;If6kDD;Ee9kDD;IACE,WAAA;IfglDD;EejlDD;IACE,oBAAA;IfmlDD;EeplDD;IACE,oBAAA;IfslDD;EevlDD;IACE,WAAA;IfylDD;Ee1lDD;IACE,oBAAA;If4lDD;Ee7lDD;IACE,mBAAA;If+lDD;Ee3lDD;IACE,YAAA;If6lDD;Ee/kDD;IACE,mBAAA;IfilDD;EellDD;IACE,2BAAA;IfolDD;EerlDD;IACE,2BAAA;IfulDD;EexlDD;IACE,kBAAA;If0lDD;Ee3lDD;IACE,2BAAA;If6lDD;Ee9lDD;IACE,2BAAA;IfgmDD;EejmDD;IACE,kBAAA;IfmmDD;EepmDD;IACE,2BAAA;IfsmDD;EevmDD;IACE,2BAAA;IfymDD;Ee1mDD;IACE,kBAAA;If4mDD;Ee7mDD;IACE,2BAAA;If+mDD;EehnDD;IACE,0BAAA;IfknDD;EennDD;IACE,iBAAA;IfqnDD;EACF;AajnDD;EEvCI;IACE,aAAA;If2pDH;EeppDD;IACE,aAAA;IfspDD;EevpDD;IACE,qBAAA;IfypDD;Ee1pDD;IACE,qBAAA;If4pDD;Ee7pDD;IACE,YAAA;If+pDD;EehqDD;IACE,qBAAA;IfkqDD;EenqDD;IACE,qBAAA;IfqqDD;EetqDD;IACE,YAAA;IfwqDD;EezqDD;IACE,qBAAA;If2qDD;Ee5qDD;IACE,qBAAA;If8qDD;Ee/qDD;IACE,YAAA;IfirDD;EelrDD;IACE,qBAAA;IforDD;EerrDD;IACE,oBAAA;IfurDD;EezqDD;IACE,aAAA;If2qDD;Ee5qDD;IACE,qBAAA;If8qDD;Ee/qDD;IACE,qBAAA;IfirDD;EelrDD;IACE,YAAA;IforDD;EerrDD;IACE,qBAAA;IfurDD;EexrDD;IACE,qBAAA;If0rDD;Ee3rDD;IACE,YAAA;If6rDD;Ee9rDD;IACE,qBAAA;IfgsDD;EejsDD;IACE,qBAAA;IfmsDD;EepsDD;IACE,YAAA;IfssDD;EevsDD;IACE,qBAAA;IfysDD;Ee1sDD;IACE,oBAAA;If4sDD;EexsDD;IACE,aAAA;If0sDD;Ee1tDD;IACE,YAAA;If4tDD;Ee7tDD;IACE,oBAAA;If+tDD;EehuDD;IACE,oBAAA;IfkuDD;EenuDD;IACE,WAAA;IfquDD;EetuDD;IACE,oBAAA;IfwuDD;EezuDD;IACE,oBAAA;If2uDD;Ee5uDD;IACE,WAAA;If8uDD;Ee/uDD;IACE,oBAAA;IfivDD;EelvDD;IACE,oBAAA;IfovDD;EervDD;IACE,WAAA;IfuvDD;EexvDD;IACE,oBAAA;If0vDD;Ee3vDD;IACE,mBAAA;If6vDD;EezvDD;IACE,YAAA;If2vDD;Ee7uDD;IACE,mBAAA;If+uDD;EehvDD;IACE,2BAAA;IfkvDD;EenvDD;IACE,2BAAA;IfqvDD;EetvDD;IACE,kBAAA;IfwvDD;EezvDD;IACE,2BAAA;If2vDD;Ee5vDD;IACE,2BAAA;If8vDD;Ee/vDD;IACE,kBAAA;IfiwDD;EelwDD;IACE,2BAAA;IfowDD;EerwDD;IACE,2BAAA;IfuwDD;EexwDD;IACE,kBAAA;If0wDD;Ee3wDD;IACE,2BAAA;If6wDD;Ee9wDD;IACE,0BAAA;IfgxDD;EejxDD;IACE,iBAAA;IfmxDD;EACF;AaxwDD;EE9CI;IACE,aAAA;IfyzDH;EelzDD;IACE,aAAA;IfozDD;EerzDD;IACE,qBAAA;IfuzDD;EexzDD;IACE,qBAAA;If0zDD;Ee3zDD;IACE,YAAA;If6zDD;Ee9zDD;IACE,qBAAA;Ifg0DD;Eej0DD;IACE,qBAAA;Ifm0DD;Eep0DD;IACE,YAAA;Ifs0DD;Eev0DD;IACE,qBAAA;Ify0DD;Ee10DD;IACE,qBAAA;If40DD;Ee70DD;IACE,YAAA;If+0DD;Eeh1DD;IACE,qBAAA;Ifk1DD;Een1DD;IACE,oBAAA;Ifq1DD;Eev0DD;IACE,aAAA;Ify0DD;Ee10DD;IACE,qBAAA;If40DD;Ee70DD;IACE,qBAAA;If+0DD;Eeh1DD;IACE,YAAA;Ifk1DD;Een1DD;IACE,qBAAA;Ifq1DD;Eet1DD;IACE,qBAAA;Ifw1DD;Eez1DD;IACE,YAAA;If21DD;Ee51DD;IACE,qBAAA;If81DD;Ee/1DD;IACE,qBAAA;Ifi2DD;Eel2DD;IACE,YAAA;Ifo2DD;Eer2DD;IACE,qBAAA;Ifu2DD;Eex2DD;IACE,oBAAA;If02DD;Eet2DD;IACE,aAAA;Ifw2DD;Eex3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AAA;EACI,uBAAA;ErBqsFT;AoB9qFD;EClBI,gBAAA;EACA,2BAAA;ErBmsFH;AoB/qFD;ECrDE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBuuFD;AqBruFC;;;;;EAKE,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBuuFP;AqBruFC;;;EAGE,wBAAA;ErBuuFH;AqBluFG;;;;;;;;;;;;;;;EAKE,2BAAA;EACI,uBAAA;ErB8uFT;AoBptFD;ECrBI,gBAAA;EACA,2BAAA;ErB4uFH;AoBptFD;ECzDE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBgxFD;AqB9wFC;;;;;EAKE,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBgxFP;AqB9wFC;;;EAGE,wBAAA;ErBgxFH;AqB3wFG;;;;;;;;;;;;;;;EAKE,2BAAA;EACI,uBAAA;ErBuxFT;AoBzvFD;ECzBI,gBAAA;EACA,2BAAA;ErBqxFH;AoBzvFD;EC7DE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErByzFD;AqBvzFC;;;;;EAKE,gBAAA;EACA,2BAAA;EACI,uBAAA;ErByzFP;AqBvzFC;;;EAGE,wBAAA;ErByzFH;AqBpzFG;;;;;;;;;;;;;;;EAKE,2BAAA;EACI,uBAAA;ErBg0FT;AoB9xFD;EC7BI,gBAAA;EACA,2BAAA;ErB8zFH;AoB9xFD;ECjEE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBk2FD;AqBh2FC;;;;;EAKE,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBk2FP;AqBh2FC;;;EAGE,wBAAA;ErBk2FH;AqB71FG;;;;;;;;;;;;;;;EAKE,2BAAA;EACI,uBAAA;ErBy2FT;AoBn0FD;ECjCI,gBAAA;EACA,2BAAA;ErBu2FH;AoBn0FD;ECrEE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErB24FD;AqBz4FC;;;;;EAKE,gBAAA;EACA,2BAAA;EACI,uBAAA;ErB24FP;AqBz4FC;;;EAGE,wBAAA;ErB24FH;AqBt4FG;;;;;;;;;;;;;;;EAKE,2BAAA;EACI,uBAAA;ErBk5FT;AoBx2FD;ECrCI,gBAAA;EACA,2BAAA;ErBg5FH;AoBn2FD;EACE,gBAAA;EACA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","sourcesContent":[null,"/*! normalize.css v3.0.1 | MIT License | git.io/normalize */\n\n//\n// 1. Set default font family to sans-serif.\n// 2. Prevent iOS text size adjust after orientation change, without disabling\n// user zoom.\n//\n\nhtml {\n font-family: sans-serif; // 1\n -ms-text-size-adjust: 100%; // 2\n -webkit-text-size-adjust: 100%; // 2\n}\n\n//\n// Remove default margin.\n//\n\nbody {\n margin: 0;\n}\n\n// HTML5 display definitions\n// ==========================================================================\n\n//\n// Correct `block` display not defined for any HTML5 element in IE 8/9.\n// Correct `block` display not defined for `details` or `summary` in IE 10/11 and Firefox.\n// Correct `block` display not defined for `main` in IE 11.\n//\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nnav,\nsection,\nsummary {\n display: block;\n}\n\n//\n// 1. Correct `inline-block` display not defined in IE 8/9.\n// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.\n//\n\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block; // 1\n vertical-align: baseline; // 2\n}\n\n//\n// Prevent modern browsers from displaying `audio` without controls.\n// Remove excess height in iOS 5 devices.\n//\n\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n\n//\n// Address `[hidden]` styling not present in IE 8/9/10.\n// Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.\n//\n\n[hidden],\ntemplate {\n display: none;\n}\n\n// Links\n// ==========================================================================\n\n//\n// Remove the gray background color from active links in IE 10.\n//\n\na {\n background: transparent;\n}\n\n//\n// Improve readability when focused and also mouse hovered in all browsers.\n//\n\na:active,\na:hover {\n outline: 0;\n}\n\n// Text-level semantics\n// ==========================================================================\n\n//\n// Address styling not present in IE 8/9/10/11, Safari, and Chrome.\n//\n\nabbr[title] {\n border-bottom: 1px dotted;\n}\n\n//\n// Address style set to `bolder` in Firefox 4+, Safari, and Chrome.\n//\n\nb,\nstrong {\n font-weight: bold;\n}\n\n//\n// Address styling not present in Safari and Chrome.\n//\n\ndfn {\n font-style: italic;\n}\n\n//\n// Address variable `h1` font-size and margin within `section` and `article`\n// contexts in Firefox 4+, Safari, and Chrome.\n//\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n//\n// Address styling not present in IE 8/9.\n//\n\nmark {\n background: #ff0;\n color: #000;\n}\n\n//\n// Address inconsistent and variable font size in all browsers.\n//\n\nsmall {\n font-size: 80%;\n}\n\n//\n// Prevent `sub` and `sup` affecting `line-height` in all browsers.\n//\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsup {\n top: -0.5em;\n}\n\nsub {\n bottom: -0.25em;\n}\n\n// Embedded content\n// ==========================================================================\n\n//\n// Remove border when inside `a` element in IE 8/9/10.\n//\n\nimg {\n border: 0;\n}\n\n//\n// Correct overflow not hidden in IE 9/10/11.\n//\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\n// Grouping content\n// ==========================================================================\n\n//\n// Address margin not present in IE 8/9 and Safari.\n//\n\nfigure {\n margin: 1em 40px;\n}\n\n//\n// Address differences between Firefox and other browsers.\n//\n\nhr {\n -moz-box-sizing: content-box;\n box-sizing: content-box;\n height: 0;\n}\n\n//\n// Contain overflow in all browsers.\n//\n\npre {\n overflow: auto;\n}\n\n//\n// Address odd `em`-unit font size rendering in all browsers.\n//\n\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\n\n// Forms\n// ==========================================================================\n\n//\n// Known limitation: by default, Chrome and Safari on OS X allow very limited\n// styling of `select`, unless a `border` property is set.\n//\n\n//\n// 1. Correct color not being inherited.\n// Known issue: affects color of disabled elements.\n// 2. Correct font properties not being inherited.\n// 3. Address margins set differently in Firefox 4+, Safari, and Chrome.\n//\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit; // 1\n font: inherit; // 2\n margin: 0; // 3\n}\n\n//\n// Address `overflow` set to `hidden` in IE 8/9/10/11.\n//\n\nbutton {\n overflow: visible;\n}\n\n//\n// Address inconsistent `text-transform` inheritance for `button` and `select`.\n// All other form control elements do not inherit `text-transform` values.\n// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.\n// Correct `select` style inheritance in Firefox.\n//\n\nbutton,\nselect {\n text-transform: none;\n}\n\n//\n// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n// and `video` controls.\n// 2. Correct inability to style clickable `input` types in iOS.\n// 3. Improve usability and consistency of cursor style between image-type\n// `input` and others.\n//\n\nbutton,\nhtml input[type=\"button\"], // 1\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button; // 2\n cursor: pointer; // 3\n}\n\n//\n// Re-set default cursor for disabled elements.\n//\n\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\n\n//\n// Remove inner padding and border in Firefox 4+.\n//\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\n\n//\n// Address Firefox 4+ setting `line-height` on `input` using `!important` in\n// the UA stylesheet.\n//\n\ninput {\n line-height: normal;\n}\n\n//\n// It's recommended that you don't attempt to style these elements.\n// Firefox's implementation doesn't respect box-sizing, padding, or width.\n//\n// 1. Address box sizing set to `content-box` in IE 8/9/10.\n// 2. Remove excess padding in IE 8/9/10.\n//\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box; // 1\n padding: 0; // 2\n}\n\n//\n// Fix the cursor style for Chrome's increment/decrement buttons. For certain\n// `font-size` values of the `input`, it causes the cursor style of the\n// decrement button to change from `default` to `text`.\n//\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n//\n// 1. Address `appearance` set to `searchfield` in Safari and Chrome.\n// 2. Address `box-sizing` set to `border-box` in Safari and Chrome\n// (include `-moz` to future-proof).\n//\n\ninput[type=\"search\"] {\n -webkit-appearance: textfield; // 1\n -moz-box-sizing: content-box;\n -webkit-box-sizing: content-box; // 2\n box-sizing: content-box;\n}\n\n//\n// Remove inner padding and search cancel button in Safari and Chrome on OS X.\n// Safari (but not Chrome) clips the cancel button when the search input has\n// padding (and `textfield` appearance).\n//\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// Define consistent border, margin, and padding.\n//\n\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\n\n//\n// 1. Correct `color` not being inherited in IE 8/9/10/11.\n// 2. Remove padding so people aren't caught out if they zero out fieldsets.\n//\n\nlegend {\n border: 0; // 1\n padding: 0; // 2\n}\n\n//\n// Remove default vertical scrollbar in IE 8/9/10/11.\n//\n\ntextarea {\n overflow: auto;\n}\n\n//\n// Don't inherit the `font-weight` (applied by a rule above).\n// NOTE: the default cannot safely be changed in Chrome and Safari on OS X.\n//\n\noptgroup {\n font-weight: bold;\n}\n\n// Tables\n// ==========================================================================\n\n//\n// Remove most spacing between table cells.\n//\n\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\n\ntd,\nth {\n padding: 0;\n}\n","//\n// Basic print styles\n// --------------------------------------------------\n// Source: https://github.com/h5bp/html5-boilerplate/blob/master/css/main.css\n\n@media print {\n\n * {\n text-shadow: none !important;\n color: #000 !important; // Black prints faster: h5bp.com/s\n background: transparent !important;\n box-shadow: none !important;\n }\n\n a,\n a:visited {\n text-decoration: underline;\n }\n\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n\n // Don't show links for images, or javascript/internal links\n a[href^=\"javascript:\"]:after,\n a[href^=\"#\"]:after {\n content: \"\";\n }\n\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n\n thead {\n display: table-header-group; // h5bp.com/t\n }\n\n tr,\n img {\n page-break-inside: avoid;\n }\n\n img {\n max-width: 100% !important;\n }\n\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n\n h2,\n h3 {\n page-break-after: avoid;\n }\n\n // Chrome (OSX) fix for https://github.com/twbs/bootstrap/issues/11245\n // Once fixed, we can just straight up remove this.\n select {\n background: #fff !important;\n }\n\n // Bootstrap components\n .navbar {\n display: none;\n }\n .table {\n td,\n th {\n background-color: #fff !important;\n }\n }\n .btn,\n .dropup > .btn {\n > .caret {\n border-top-color: #000 !important;\n }\n }\n .label {\n border: 1px solid #000;\n }\n\n .table {\n border-collapse: collapse !important;\n }\n .table-bordered {\n th,\n td {\n border: 1px solid #ddd !important;\n }\n }\n\n}\n","//\n// Glyphicons for Bootstrap\n//\n// Since icons are fonts, they can be placed anywhere text is placed and are\n// thus automatically sized to match the surrounding child. To use, create an\n// inline element with the appropriate classes, like so:\n//\n// Star\n\n// Import the fonts\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('@{icon-font-path}@{icon-font-name}.eot');\n src: url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype'),\n url('@{icon-font-path}@{icon-font-name}.woff') format('woff'),\n url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype'),\n url('@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}') format('svg');\n}\n\n// Catchall baseclass\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n// Individual icons\n.glyphicon-asterisk { &:before { content: \"\\2a\"; } }\n.glyphicon-plus { &:before { content: \"\\2b\"; } }\n.glyphicon-euro { &:before { content: \"\\20ac\"; } }\n.glyphicon-minus { &:before { content: \"\\2212\"; } }\n.glyphicon-cloud { &:before { content: \"\\2601\"; } }\n.glyphicon-envelope { &:before { content: \"\\2709\"; } }\n.glyphicon-pencil { &:before { content: \"\\270f\"; } }\n.glyphicon-glass { &:before { content: \"\\e001\"; } }\n.glyphicon-music { &:before { content: \"\\e002\"; } }\n.glyphicon-search { &:before { content: \"\\e003\"; } }\n.glyphicon-heart { &:before { content: \"\\e005\"; } }\n.glyphicon-star { &:before { content: \"\\e006\"; } }\n.glyphicon-star-empty { &:before { content: \"\\e007\"; } }\n.glyphicon-user { &:before { content: \"\\e008\"; } }\n.glyphicon-film { &:before { content: \"\\e009\"; } }\n.glyphicon-th-large { &:before { content: \"\\e010\"; } }\n.glyphicon-th { &:before { content: \"\\e011\"; } }\n.glyphicon-th-list { &:before { content: \"\\e012\"; } }\n.glyphicon-ok { &:before { content: \"\\e013\"; } }\n.glyphicon-remove { &:before { content: \"\\e014\"; } }\n.glyphicon-zoom-in { &:before { content: \"\\e015\"; } }\n.glyphicon-zoom-out { &:before { content: \"\\e016\"; } }\n.glyphicon-off { &:before { content: \"\\e017\"; } }\n.glyphicon-signal { &:before { content: \"\\e018\"; } }\n.glyphicon-cog { &:before { content: \"\\e019\"; } }\n.glyphicon-trash { &:before { content: \"\\e020\"; } }\n.glyphicon-home { &:before { content: \"\\e021\"; } }\n.glyphicon-file { &:before { content: \"\\e022\"; } }\n.glyphicon-time { &:before { content: \"\\e023\"; } }\n.glyphicon-road { &:before { content: \"\\e024\"; } }\n.glyphicon-download-alt { &:before { content: \"\\e025\"; } }\n.glyphicon-download { &:before { content: \"\\e026\"; } }\n.glyphicon-upload { &:before { content: \"\\e027\"; } }\n.glyphicon-inbox { &:before { content: \"\\e028\"; } }\n.glyphicon-play-circle { &:before { content: \"\\e029\"; } }\n.glyphicon-repeat { &:before { content: \"\\e030\"; } }\n.glyphicon-refresh { &:before { content: \"\\e031\"; } }\n.glyphicon-list-alt { &:before { content: \"\\e032\"; } }\n.glyphicon-lock { &:before { content: \"\\e033\"; } }\n.glyphicon-flag { &:before { content: \"\\e034\"; } }\n.glyphicon-headphones { &:before { content: \"\\e035\"; } }\n.glyphicon-volume-off { &:before { content: \"\\e036\"; } }\n.glyphicon-volume-down { &:before { content: \"\\e037\"; } }\n.glyphicon-volume-up { &:before { content: \"\\e038\"; } }\n.glyphicon-qrcode { &:before { content: \"\\e039\"; } }\n.glyphicon-barcode { &:before { content: \"\\e040\"; } }\n.glyphicon-tag { &:before { content: \"\\e041\"; } }\n.glyphicon-tags { &:before { content: \"\\e042\"; } }\n.glyphicon-book { &:before { content: \"\\e043\"; } }\n.glyphicon-bookmark { &:before { content: \"\\e044\"; } }\n.glyphicon-print { &:before { content: \"\\e045\"; } }\n.glyphicon-camera { &:before { content: \"\\e046\"; } }\n.glyphicon-font { &:before { content: \"\\e047\"; } }\n.glyphicon-bold { &:before { content: \"\\e048\"; } }\n.glyphicon-italic { &:before { content: \"\\e049\"; } }\n.glyphicon-text-height { &:before { content: \"\\e050\"; } }\n.glyphicon-text-width { &:before { content: \"\\e051\"; } }\n.glyphicon-align-left { &:before { content: \"\\e052\"; } }\n.glyphicon-align-center { &:before { content: \"\\e053\"; } }\n.glyphicon-align-right { &:before { content: \"\\e054\"; } }\n.glyphicon-align-justify { &:before { content: \"\\e055\"; } }\n.glyphicon-list { &:before { content: \"\\e056\"; } }\n.glyphicon-indent-left { &:before { content: \"\\e057\"; } }\n.glyphicon-indent-right { &:before { content: \"\\e058\"; } }\n.glyphicon-facetime-video { &:before { content: \"\\e059\"; } }\n.glyphicon-picture { &:before { content: \"\\e060\"; } }\n.glyphicon-map-marker { &:before { content: \"\\e062\"; } }\n.glyphicon-adjust { &:before { content: \"\\e063\"; } }\n.glyphicon-tint { &:before { content: \"\\e064\"; } }\n.glyphicon-edit { &:before { content: \"\\e065\"; } }\n.glyphicon-share { &:before { content: \"\\e066\"; } }\n.glyphicon-check { &:before { content: \"\\e067\"; } }\n.glyphicon-move { &:before { content: \"\\e068\"; } }\n.glyphicon-step-backward { &:before { content: \"\\e069\"; } }\n.glyphicon-fast-backward { &:before { content: \"\\e070\"; } }\n.glyphicon-backward { &:before { content: \"\\e071\"; } }\n.glyphicon-play { &:before { content: \"\\e072\"; } }\n.glyphicon-pause { &:before { content: \"\\e073\"; } }\n.glyphicon-stop { &:before { content: \"\\e074\"; } }\n.glyphicon-forward { &:before { content: \"\\e075\"; } }\n.glyphicon-fast-forward { &:before { content: \"\\e076\"; } }\n.glyphicon-step-forward { &:before { content: \"\\e077\"; } }\n.glyphicon-eject { &:before { content: \"\\e078\"; } }\n.glyphicon-chevron-left { &:before { content: \"\\e079\"; } }\n.glyphicon-chevron-right { &:before { content: \"\\e080\"; } }\n.glyphicon-plus-sign { &:before { content: \"\\e081\"; } }\n.glyphicon-minus-sign { &:before { content: \"\\e082\"; } }\n.glyphicon-remove-sign { &:before { content: \"\\e083\"; } }\n.glyphicon-ok-sign { &:before { content: \"\\e084\"; } }\n.glyphicon-question-sign { &:before { content: \"\\e085\"; } }\n.glyphicon-info-sign { &:before { content: \"\\e086\"; } }\n.glyphicon-screenshot { &:before { content: \"\\e087\"; } }\n.glyphicon-remove-circle { &:before { content: \"\\e088\"; } }\n.glyphicon-ok-circle { &:before { content: \"\\e089\"; } }\n.glyphicon-ban-circle { &:before { content: \"\\e090\"; } }\n.glyphicon-arrow-left { &:before { content: \"\\e091\"; } }\n.glyphicon-arrow-right { &:before { content: \"\\e092\"; } }\n.glyphicon-arrow-up { &:before { content: \"\\e093\"; } }\n.glyphicon-arrow-down { &:before { content: \"\\e094\"; } }\n.glyphicon-share-alt { &:before { content: \"\\e095\"; } }\n.glyphicon-resize-full { &:before { content: \"\\e096\"; } }\n.glyphicon-resize-small { &:before { content: \"\\e097\"; } }\n.glyphicon-exclamation-sign { &:before { content: \"\\e101\"; } }\n.glyphicon-gift { &:before { content: \"\\e102\"; } }\n.glyphicon-leaf { &:before { content: \"\\e103\"; } }\n.glyphicon-fire { &:before { content: \"\\e104\"; } }\n.glyphicon-eye-open { &:before { content: \"\\e105\"; } }\n.glyphicon-eye-close { &:before { content: \"\\e106\"; } }\n.glyphicon-warning-sign { &:before { content: \"\\e107\"; } }\n.glyphicon-plane { &:before { content: \"\\e108\"; } }\n.glyphicon-calendar { &:before { content: \"\\e109\"; } }\n.glyphicon-random { &:before { content: \"\\e110\"; } }\n.glyphicon-comment { &:before { content: \"\\e111\"; } }\n.glyphicon-magnet { &:before { content: \"\\e112\"; } }\n.glyphicon-chevron-up { &:before { content: \"\\e113\"; } }\n.glyphicon-chevron-down { &:before { content: \"\\e114\"; } }\n.glyphicon-retweet { &:before { content: \"\\e115\"; } }\n.glyphicon-shopping-cart { &:before { content: \"\\e116\"; } }\n.glyphicon-folder-close { &:before { content: \"\\e117\"; } }\n.glyphicon-folder-open { &:before { content: \"\\e118\"; } }\n.glyphicon-resize-vertical { &:before { content: \"\\e119\"; } }\n.glyphicon-resize-horizontal { &:before { content: \"\\e120\"; } }\n.glyphicon-hdd { &:before { content: \"\\e121\"; } }\n.glyphicon-bullhorn { &:before { content: \"\\e122\"; } }\n.glyphicon-bell { &:before { content: \"\\e123\"; } }\n.glyphicon-certificate { &:before { content: \"\\e124\"; } }\n.glyphicon-thumbs-up { &:before { content: \"\\e125\"; } }\n.glyphicon-thumbs-down { &:before { content: \"\\e126\"; } }\n.glyphicon-hand-right { &:before { content: \"\\e127\"; } }\n.glyphicon-hand-left { &:before { content: \"\\e128\"; } }\n.glyphicon-hand-up { &:before { content: \"\\e129\"; } }\n.glyphicon-hand-down { &:before { content: \"\\e130\"; } }\n.glyphicon-circle-arrow-right { &:before { content: \"\\e131\"; } }\n.glyphicon-circle-arrow-left { &:before { content: \"\\e132\"; } }\n.glyphicon-circle-arrow-up { &:before { content: \"\\e133\"; } }\n.glyphicon-circle-arrow-down { &:before { content: \"\\e134\"; } }\n.glyphicon-globe { &:before { content: \"\\e135\"; } }\n.glyphicon-wrench { &:before { content: \"\\e136\"; } }\n.glyphicon-tasks { &:before { content: \"\\e137\"; } }\n.glyphicon-filter { &:before { content: \"\\e138\"; } }\n.glyphicon-briefcase { &:before { content: \"\\e139\"; } }\n.glyphicon-fullscreen { &:before { content: \"\\e140\"; } }\n.glyphicon-dashboard { &:before { content: \"\\e141\"; } }\n.glyphicon-paperclip { &:before { content: \"\\e142\"; } }\n.glyphicon-heart-empty { &:before { content: \"\\e143\"; } }\n.glyphicon-link { &:before { content: \"\\e144\"; } }\n.glyphicon-phone { &:before { content: \"\\e145\"; } }\n.glyphicon-pushpin { &:before { content: \"\\e146\"; } }\n.glyphicon-usd { &:before { content: \"\\e148\"; } }\n.glyphicon-gbp { &:before { content: \"\\e149\"; } }\n.glyphicon-sort { &:before { content: \"\\e150\"; } }\n.glyphicon-sort-by-alphabet { &:before { content: \"\\e151\"; } }\n.glyphicon-sort-by-alphabet-alt { &:before { content: \"\\e152\"; } }\n.glyphicon-sort-by-order { &:before { content: \"\\e153\"; } }\n.glyphicon-sort-by-order-alt { &:before { content: \"\\e154\"; } }\n.glyphicon-sort-by-attributes { &:before { content: \"\\e155\"; } }\n.glyphicon-sort-by-attributes-alt { &:before { content: \"\\e156\"; } }\n.glyphicon-unchecked { &:before { content: \"\\e157\"; } }\n.glyphicon-expand { &:before { content: \"\\e158\"; } }\n.glyphicon-collapse-down { &:before { content: \"\\e159\"; } }\n.glyphicon-collapse-up { &:before { content: \"\\e160\"; } }\n.glyphicon-log-in { &:before { content: \"\\e161\"; } }\n.glyphicon-flash { &:before { content: \"\\e162\"; } }\n.glyphicon-log-out { &:before { content: \"\\e163\"; } }\n.glyphicon-new-window { &:before { content: \"\\e164\"; } }\n.glyphicon-record { &:before { content: \"\\e165\"; } }\n.glyphicon-save { &:before { content: \"\\e166\"; } }\n.glyphicon-open { &:before { content: \"\\e167\"; } }\n.glyphicon-saved { &:before { content: \"\\e168\"; } }\n.glyphicon-import { &:before { content: \"\\e169\"; } }\n.glyphicon-export { &:before { content: \"\\e170\"; } }\n.glyphicon-send { &:before { content: \"\\e171\"; } }\n.glyphicon-floppy-disk { &:before { content: \"\\e172\"; } }\n.glyphicon-floppy-saved { &:before { content: \"\\e173\"; } }\n.glyphicon-floppy-remove { &:before { content: \"\\e174\"; } }\n.glyphicon-floppy-save { &:before { content: \"\\e175\"; } }\n.glyphicon-floppy-open { &:before { content: \"\\e176\"; } }\n.glyphicon-credit-card { &:before { content: \"\\e177\"; } }\n.glyphicon-transfer { &:before { content: \"\\e178\"; } }\n.glyphicon-cutlery { &:before { content: \"\\e179\"; } }\n.glyphicon-header { &:before { content: \"\\e180\"; } }\n.glyphicon-compressed { &:before { content: \"\\e181\"; } }\n.glyphicon-earphone { &:before { content: \"\\e182\"; } }\n.glyphicon-phone-alt { &:before { content: \"\\e183\"; } }\n.glyphicon-tower { &:before { content: \"\\e184\"; } }\n.glyphicon-stats { &:before { content: \"\\e185\"; } }\n.glyphicon-sd-video { &:before { content: \"\\e186\"; } }\n.glyphicon-hd-video { &:before { content: \"\\e187\"; } }\n.glyphicon-subtitles { &:before { content: \"\\e188\"; } }\n.glyphicon-sound-stereo { &:before { content: \"\\e189\"; } }\n.glyphicon-sound-dolby { &:before { content: \"\\e190\"; } }\n.glyphicon-sound-5-1 { &:before { content: \"\\e191\"; } }\n.glyphicon-sound-6-1 { &:before { content: \"\\e192\"; } }\n.glyphicon-sound-7-1 { &:before { content: \"\\e193\"; } }\n.glyphicon-copyright-mark { &:before { content: \"\\e194\"; } }\n.glyphicon-registration-mark { &:before { content: \"\\e195\"; } }\n.glyphicon-cloud-download { &:before { content: \"\\e197\"; } }\n.glyphicon-cloud-upload { &:before { content: \"\\e198\"; } }\n.glyphicon-tree-conifer { &:before { content: \"\\e199\"; } }\n.glyphicon-tree-deciduous { &:before { content: \"\\e200\"; } }\n","//\n// Scaffolding\n// --------------------------------------------------\n\n\n// Reset the box-sizing\n//\n// Heads up! This reset may cause conflicts with some third-party widgets.\n// For recommendations on resolving such conflicts, see\n// http://getbootstrap.com/getting-started/#third-box-sizing\n* {\n .box-sizing(border-box);\n}\n*:before,\n*:after {\n .box-sizing(border-box);\n}\n\n\n// Body reset\n\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0,0,0,0);\n}\n\nbody {\n font-family: @font-family-base;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @text-color;\n background-color: @body-bg;\n}\n\n// Reset fonts for relevant elements\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\n\n// Links\n\na {\n color: @link-color;\n text-decoration: none;\n\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: underline;\n }\n\n &:focus {\n .tab-focus();\n }\n}\n\n\n// Figures\n//\n// We reset this here because previously Normalize had no `figure` margins. This\n// ensures we don't break anyone's use of the element.\n\nfigure {\n margin: 0;\n}\n\n\n// Images\n\nimg {\n vertical-align: middle;\n}\n\n// Responsive images (ensure images don't scale beyond their parents)\n.img-responsive {\n .img-responsive();\n}\n\n// Rounded corners\n.img-rounded {\n border-radius: @border-radius-large;\n}\n\n// Image thumbnails\n//\n// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.\n.img-thumbnail {\n padding: @thumbnail-padding;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(all .2s ease-in-out);\n\n // Keep them at most 100% wide\n .img-responsive(inline-block);\n}\n\n// Perfect circle\n.img-circle {\n border-radius: 50%; // set radius in percents\n}\n\n\n// Horizontal rules\n\nhr {\n margin-top: @line-height-computed;\n margin-bottom: @line-height-computed;\n border: 0;\n border-top: 1px solid @hr-border;\n}\n\n\n// Only display content to screen readers\n//\n// See: http://a11yproject.com/posts/how-to-hide-content/\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0,0,0,0);\n border: 0;\n}\n\n// Use in conjunction with .sr-only to only display content when it's focused.\n// Useful for \"Skip to main content\" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1\n// Credit: HTML5 Boilerplate\n\n.sr-only-focusable {\n &:active,\n &:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n }\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They will be removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n &::-moz-placeholder { color: @color; // Firefox\n opacity: 1; } // See https://github.com/twbs/bootstrap/pull/11526\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// WebKit-style focus\n\n.tab-focus() {\n // Default\n outline: thin dotted;\n // WebKit\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n","// Image Mixins\n// - Responsive image\n// - Retina image\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n.img-responsive(@display: block) {\n display: @display;\n width: 100% \\9; // Force IE10 and below to size SVG images correctly\n max-width: 100%; // Part 1: Set a maximum relative to the parent\n height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size. Note that the\n// spelling of `min--moz-device-pixel-ratio` is intentional.\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n background-image: url(\"@{file-1x}\");\n\n @media\n only screen and (-webkit-min-device-pixel-ratio: 2),\n only screen and ( min--moz-device-pixel-ratio: 2),\n only screen and ( -o-min-device-pixel-ratio: 2/1),\n only screen and ( min-device-pixel-ratio: 2),\n only screen and ( min-resolution: 192dpi),\n only screen and ( min-resolution: 2dppx) {\n background-image: url(\"@{file-2x}\");\n background-size: @width-1x @height-1x;\n }\n}\n","//\n// Typography\n// --------------------------------------------------\n\n\n// Headings\n// -------------------------\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n font-family: @headings-font-family;\n font-weight: @headings-font-weight;\n line-height: @headings-line-height;\n color: @headings-color;\n\n small,\n .small {\n font-weight: normal;\n line-height: 1;\n color: @headings-small-color;\n }\n}\n\nh1, .h1,\nh2, .h2,\nh3, .h3 {\n margin-top: @line-height-computed;\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 65%;\n }\n}\nh4, .h4,\nh5, .h5,\nh6, .h6 {\n margin-top: (@line-height-computed / 2);\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 75%;\n }\n}\n\nh1, .h1 { font-size: @font-size-h1; }\nh2, .h2 { font-size: @font-size-h2; }\nh3, .h3 { font-size: @font-size-h3; }\nh4, .h4 { font-size: @font-size-h4; }\nh5, .h5 { font-size: @font-size-h5; }\nh6, .h6 { font-size: @font-size-h6; }\n\n\n// Body text\n// -------------------------\n\np {\n margin: 0 0 (@line-height-computed / 2);\n}\n\n.lead {\n margin-bottom: @line-height-computed;\n font-size: floor((@font-size-base * 1.15));\n font-weight: 300;\n line-height: 1.4;\n\n @media (min-width: @screen-sm-min) {\n font-size: (@font-size-base * 1.5);\n }\n}\n\n\n// Emphasis & misc\n// -------------------------\n\n// Ex: (12px small font / 14px base font) * 100% = about 85%\nsmall,\n.small {\n font-size: floor((100% * @font-size-small / @font-size-base));\n}\n\n// Undo browser default styling\ncite {\n font-style: normal;\n}\n\nmark,\n.mark {\n background-color: @state-warning-bg;\n padding: .2em;\n}\n\n// Alignment\n.text-left { text-align: left; }\n.text-right { text-align: right; }\n.text-center { text-align: center; }\n.text-justify { text-align: justify; }\n.text-nowrap { white-space: nowrap; }\n\n// Transformation\n.text-lowercase { text-transform: lowercase; }\n.text-uppercase { text-transform: uppercase; }\n.text-capitalize { text-transform: capitalize; }\n\n// Contextual colors\n.text-muted {\n color: @text-muted;\n}\n.text-primary {\n .text-emphasis-variant(@brand-primary);\n}\n.text-success {\n .text-emphasis-variant(@state-success-text);\n}\n.text-info {\n .text-emphasis-variant(@state-info-text);\n}\n.text-warning {\n .text-emphasis-variant(@state-warning-text);\n}\n.text-danger {\n .text-emphasis-variant(@state-danger-text);\n}\n\n// Contextual backgrounds\n// For now we'll leave these alongside the text classes until v4 when we can\n// safely shift things around (per SemVer rules).\n.bg-primary {\n // Given the contrast here, this is the only class to have its color inverted\n // automatically.\n color: #fff;\n .bg-variant(@brand-primary);\n}\n.bg-success {\n .bg-variant(@state-success-bg);\n}\n.bg-info {\n .bg-variant(@state-info-bg);\n}\n.bg-warning {\n .bg-variant(@state-warning-bg);\n}\n.bg-danger {\n .bg-variant(@state-danger-bg);\n}\n\n\n// Page header\n// -------------------------\n\n.page-header {\n padding-bottom: ((@line-height-computed / 2) - 1);\n margin: (@line-height-computed * 2) 0 @line-height-computed;\n border-bottom: 1px solid @page-header-border-color;\n}\n\n\n// Lists\n// -------------------------\n\n// Unordered and Ordered lists\nul,\nol {\n margin-top: 0;\n margin-bottom: (@line-height-computed / 2);\n ul,\n ol {\n margin-bottom: 0;\n }\n}\n\n// List options\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n .list-unstyled();\n margin-left: -5px;\n\n > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n }\n}\n\n// Description Lists\ndl {\n margin-top: 0; // Remove browser default\n margin-bottom: @line-height-computed;\n}\ndt,\ndd {\n line-height: @line-height-base;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0; // Undo browser default\n}\n\n// Horizontal description lists\n//\n// Defaults to being stacked without any of the below styles applied, until the\n// grid breakpoint is reached (default of ~768px).\n\n.dl-horizontal {\n dd {\n &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present\n }\n\n @media (min-width: @grid-float-breakpoint) {\n dt {\n float: left;\n width: (@dl-horizontal-offset - 20);\n clear: left;\n text-align: right;\n .text-overflow();\n }\n dd {\n margin-left: @dl-horizontal-offset;\n }\n }\n}\n\n\n// Misc\n// -------------------------\n\n// Abbreviations and acronyms\nabbr[title],\n// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted @abbr-border-color;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n// Blockquotes\nblockquote {\n padding: (@line-height-computed / 2) @line-height-computed;\n margin: 0 0 @line-height-computed;\n font-size: @blockquote-font-size;\n border-left: 5px solid @blockquote-border-color;\n\n p,\n ul,\n ol {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n // Note: Deprecated small and .small as of v3.1.0\n // Context: https://github.com/twbs/bootstrap/issues/11660\n footer,\n small,\n .small {\n display: block;\n font-size: 80%; // back to default font-size\n line-height: @line-height-base;\n color: @blockquote-small-color;\n\n &:before {\n content: '\\2014 \\00A0'; // em dash, nbsp\n }\n }\n}\n\n// Opposite alignment of blockquote\n//\n// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid @blockquote-border-color;\n border-left: 0;\n text-align: right;\n\n // Account for citation\n footer,\n small,\n .small {\n &:before { content: ''; }\n &:after {\n content: '\\00A0 \\2014'; // nbsp, em dash\n }\n }\n}\n\n// Quotes\nblockquote:before,\nblockquote:after {\n content: \"\";\n}\n\n// Addresses\naddress {\n margin-bottom: @line-height-computed;\n font-style: normal;\n line-height: @line-height-base;\n}\n","// Typography\n\n.text-emphasis-variant(@color) {\n color: @color;\n a&:hover {\n color: darken(@color, 10%);\n }\n}\n","// Contextual backgrounds\n\n.bg-variant(@color) {\n background-color: @color;\n a&:hover {\n background-color: darken(@color, 10%);\n }\n}\n","// Text overflow\n// Requires inline-block or block for proper styling\n\n.text-overflow() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n","//\n// Code (inline and block)\n// --------------------------------------------------\n\n\n// Inline and block code styles\ncode,\nkbd,\npre,\nsamp {\n font-family: @font-family-monospace;\n}\n\n// Inline code\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: @code-color;\n background-color: @code-bg;\n border-radius: @border-radius-base;\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: @kbd-color;\n background-color: @kbd-bg;\n border-radius: @border-radius-small;\n box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);\n\n kbd {\n padding: 0;\n font-size: 100%;\n box-shadow: none;\n }\n}\n\n// Blocks of code\npre {\n display: block;\n padding: ((@line-height-computed - 1) / 2);\n margin: 0 0 (@line-height-computed / 2);\n font-size: (@font-size-base - 1); // 14px to 13px\n line-height: @line-height-base;\n word-break: break-all;\n word-wrap: break-word;\n color: @pre-color;\n background-color: @pre-bg;\n border: 1px solid @pre-border-color;\n border-radius: @border-radius-base;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: @pre-scrollable-max-height;\n overflow-y: scroll;\n}\n","//\n// Grid system\n// --------------------------------------------------\n\n\n// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n.container {\n .container-fixed();\n\n @media (min-width: @screen-sm-min) {\n width: @container-sm;\n }\n @media (min-width: @screen-md-min) {\n width: @container-md;\n }\n @media (min-width: @screen-lg-min) {\n width: @container-lg;\n }\n}\n\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but without any defined\n// width for fluid, full width layouts.\n\n.container-fluid {\n .container-fixed();\n}\n\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n.row {\n .make-row();\n}\n\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n.make-grid-columns();\n\n\n// Extra small grid\n//\n// Columns, offsets, pushes, and pulls for extra small devices like\n// smartphones.\n\n.make-grid(xs);\n\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n\n@media (min-width: @screen-sm-min) {\n .make-grid(sm);\n}\n\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n\n@media (min-width: @screen-md-min) {\n .make-grid(md);\n}\n\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n\n@media (min-width: @screen-lg-min) {\n .make-grid(lg);\n}\n","// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n// Centered container element\n.container-fixed(@gutter: @grid-gutter-width) {\n margin-right: auto;\n margin-left: auto;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-left: (@gutter / -2);\n margin-right: (@gutter / -2);\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n margin-left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-push(@columns) {\n left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-pull(@columns) {\n right: percentage((@columns / @grid-columns));\n}\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n","// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `@grid-columns`.\n\n.make-grid-columns() {\n // Common styles for all sizes of grid columns, widths 1-12\n .col(@index) when (@index = 1) { // initial\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general; \"=<\" isn't a typo\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n position: relative;\n // Prevent columns from collapsing when empty\n min-height: 1px;\n // Inner gutter via padding\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n }\n }\n .col(1); // kickstart it\n}\n\n.float-grid-columns(@class) {\n .col(@index) when (@index = 1) { // initial\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n float: left;\n }\n }\n .col(1); // kickstart it\n}\n\n.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {\n .col-@{class}-@{index} {\n width: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {\n .col-@{class}-push-@{index} {\n left: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {\n .col-@{class}-push-0 {\n left: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {\n .col-@{class}-pull-@{index} {\n right: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {\n .col-@{class}-pull-0 {\n right: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = offset) {\n .col-@{class}-offset-@{index} {\n margin-left: percentage((@index / @grid-columns));\n }\n}\n\n// Basic looping in LESS\n.loop-grid-columns(@index, @class, @type) when (@index >= 0) {\n .calc-grid-column(@index, @class, @type);\n // next iteration\n .loop-grid-columns((@index - 1), @class, @type);\n}\n\n// Create grid for specific class\n.make-grid(@class) {\n .float-grid-columns(@class);\n .loop-grid-columns(@grid-columns, @class, width);\n .loop-grid-columns(@grid-columns, @class, pull);\n .loop-grid-columns(@grid-columns, @class, push);\n .loop-grid-columns(@grid-columns, @class, offset);\n}\n","//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n background-color: @table-bg;\n}\nth {\n text-align: left;\n}\n\n\n// Baseline styles\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: @line-height-computed;\n // Cells\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-cell-padding;\n line-height: @line-height-base;\n vertical-align: top;\n border-top: 1px solid @table-border-color;\n }\n }\n }\n // Bottom align for column headings\n > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid @table-border-color;\n }\n // Remove top border from thead by default\n > caption + thead,\n > colgroup + thead,\n > thead:first-child {\n > tr:first-child {\n > th,\n > td {\n border-top: 0;\n }\n }\n }\n // Account for multiple tbody instances\n > tbody + tbody {\n border-top: 2px solid @table-border-color;\n }\n\n // Nesting\n .table {\n background-color: @body-bg;\n }\n}\n\n\n// Condensed table w/ half padding\n\n.table-condensed {\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-condensed-cell-padding;\n }\n }\n }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n border: 1px solid @table-border-color;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border: 1px solid @table-border-color;\n }\n }\n }\n > thead > tr {\n > th,\n > td {\n border-bottom-width: 2px;\n }\n }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n > tbody > tr:nth-child(odd) {\n > td,\n > th {\n background-color: @table-bg-accent;\n }\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n > tbody > tr:hover {\n > td,\n > th {\n background-color: @table-bg-hover;\n }\n }\n}\n\n\n// Table cell sizing\n//\n// Reset default table behavior\n\ntable col[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9/10 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-column;\n}\ntable {\n td,\n th {\n &[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9/10 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-cell;\n }\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n// Generate the contextual variants\n.table-row-variant(active; @table-bg-active);\n.table-row-variant(success; @state-success-bg);\n.table-row-variant(info; @state-info-bg);\n.table-row-variant(warning; @state-warning-bg);\n.table-row-variant(danger; @state-danger-bg);\n\n\n// Responsive tables\n//\n// Wrap your tables in `.table-responsive` and we'll make them mobile friendly\n// by enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n.table-responsive {\n @media screen and (max-width: @screen-xs-max) {\n width: 100%;\n margin-bottom: (@line-height-computed * 0.75);\n overflow-y: hidden;\n overflow-x: auto;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid @table-border-color;\n -webkit-overflow-scrolling: touch;\n\n // Tighten up spacing\n > .table {\n margin-bottom: 0;\n\n // Ensure the content doesn't wrap\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n white-space: nowrap;\n }\n }\n }\n }\n\n // Special overrides for the bordered tables\n > .table-bordered {\n border: 0;\n\n // Nuke the appropriate borders so that the parent can handle them\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n\n // Only nuke the last row's bottom-border in `tbody` and `tfoot` since\n // chances are there will be only one `tr` in a `thead` and that would\n // remove the border altogether.\n > tbody,\n > tfoot {\n > tr:last-child {\n > th,\n > td {\n border-bottom: 0;\n }\n }\n }\n\n }\n }\n}\n","// Tables\n\n.table-row-variant(@state; @background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table > thead > tr,\n .table > tbody > tr,\n .table > tfoot > tr {\n > td.@{state},\n > th.@{state},\n &.@{state} > td,\n &.@{state} > th {\n background-color: @background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover > tbody > tr {\n > td.@{state}:hover,\n > th.@{state}:hover,\n &.@{state}:hover > td,\n &:hover > .@{state},\n &.@{state}:hover > th {\n background-color: darken(@background, 5%);\n }\n }\n}\n","//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n // Chrome and Firefox set a `min-width: min-content;` on fieldsets,\n // so we reset that to ensure it behaves more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359.\n min-width: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: @line-height-computed;\n font-size: (@font-size-base * 1.5);\n line-height: inherit;\n color: @legend-color;\n border: 0;\n border-bottom: 1px solid @legend-border-color;\n}\n\nlabel {\n display: inline-block;\n max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141)\n margin-bottom: 5px;\n font-weight: bold;\n}\n\n\n// Normalize form controls\n//\n// While most of our form styles require extra classes, some basic normalization\n// is required to ensure optimum display with or without those classes to better\n// address browser inconsistencies.\n\n// Override content-box in Normalize (* isn't specific enough)\ninput[type=\"search\"] {\n .box-sizing(border-box);\n}\n\n// Position radios and checkboxes better\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9; // IE8-9\n line-height: normal;\n}\n\n// Set the height of file controls to match text inputs\ninput[type=\"file\"] {\n display: block;\n}\n\n// Make range inputs behave like textual form controls\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\n\n// Make multiple select elements height not fixed\nselect[multiple],\nselect[size] {\n height: auto;\n}\n\n// Focus for file, radio, and checkbox\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n .tab-focus();\n}\n\n// Adjust output element\noutput {\n display: block;\n padding-top: (@padding-base-vertical + 1);\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n}\n\n\n// Common form controls\n//\n// Shared size and type resets for form controls. Apply `.form-control` to any\n// of the following form controls:\n//\n// select\n// textarea\n// input[type=\"text\"]\n// input[type=\"password\"]\n// input[type=\"datetime\"]\n// input[type=\"datetime-local\"]\n// input[type=\"date\"]\n// input[type=\"month\"]\n// input[type=\"time\"]\n// input[type=\"week\"]\n// input[type=\"number\"]\n// input[type=\"email\"]\n// input[type=\"url\"]\n// input[type=\"search\"]\n// input[type=\"tel\"]\n// input[type=\"color\"]\n\n.form-control {\n display: block;\n width: 100%;\n height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n background-color: @input-bg;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid @input-border;\n border-radius: @input-border-radius;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));\n .transition(~\"border-color ease-in-out .15s, box-shadow ease-in-out .15s\");\n\n // Customize the `:focus` state to imitate native WebKit styles.\n .form-control-focus();\n\n // Placeholder\n .placeholder();\n\n // Disabled and read-only inputs\n //\n // HTML5 says that controls under a fieldset > legend:first-child won't be\n // disabled if the fieldset is disabled. Due to implementation difficulty, we\n // don't honor that edge case; we style them as disabled anyway.\n &[disabled],\n &[readonly],\n fieldset[disabled] & {\n cursor: not-allowed;\n background-color: @input-bg-disabled;\n opacity: 1; // iOS fix for unreadable disabled content\n }\n\n // Reset height for `textarea`s\n textarea& {\n height: auto;\n }\n}\n\n\n// Search inputs in iOS\n//\n// This overrides the extra rounded corners on search inputs in iOS so that our\n// `.form-control` class can properly style them. Note that this cannot simply\n// be added to `.form-control` as it's not specific enough. For details, see\n// https://github.com/twbs/bootstrap/issues/11586.\n\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n\n\n// Special styles for iOS temporal inputs\n//\n// In Mobile Safari, setting `display: block` on temporal inputs causes the\n// text within the input to become vertically misaligned.\n// As a workaround, we set a pixel line-height that matches the\n// given height of the input. Since this fucks up everything else, we have to\n// appropriately reset it for Internet Explorer and the size variations.\n\ninput[type=\"date\"],\ninput[type=\"time\"],\ninput[type=\"datetime-local\"],\ninput[type=\"month\"] {\n line-height: @input-height-base;\n // IE8+ misaligns the text within date inputs, so we reset\n line-height: @line-height-base ~\"\\0\";\n\n &.input-sm {\n line-height: @input-height-small;\n }\n &.input-lg {\n line-height: @input-height-large;\n }\n}\n\n\n// Form groups\n//\n// Designed to help with the organization and spacing of vertical forms. For\n// horizontal forms, use the predefined grid classes.\n\n.form-group {\n margin-bottom: 15px;\n}\n\n\n// Checkboxes and radios\n//\n// Indent the labels to position radios/checkboxes as hanging controls.\n\n.radio,\n.checkbox {\n position: relative;\n display: block;\n min-height: @line-height-computed; // clear the floating input if there is no label text\n margin-top: 10px;\n margin-bottom: 10px;\n\n label {\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n }\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-left: -20px;\n margin-top: 4px \\9;\n}\n\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing\n}\n\n// Radios and checkboxes on same line\n.radio-inline,\n.checkbox-inline {\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px; // space out consecutive inline controls\n}\n\n// Apply same disabled cursor tweak as for inputs\n// Some special care is needed because