diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..c2f33e73 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,41 @@ +Contributions to PHPCI are very much encouraged, and we do our best to make it as welcoming and simple as possible. + +### Before You Start +Before you start, please make sure that you are aware of, and agree to, the following conditions of contribution: + +* By making a contribution to PHPCI, you accept that you are granting copyright ownership for that contribution to Block 8 Limited - the company responsible for PHPCI. In countries where copyright reassignment is not permitted, you grant Block 8 Limited a perpetual, non-exclusive licence to use your contribution in any way and for any purpose. + +* By making a contribution to PHPCI, you accept that your code will be released under the open-source [BSD 2-Clause Licence](https://github.com/Block8/PHPCI/blob/master/LICENSE.md). + +Block 8 are committed to PHPCI being a truly free and open source project, providing easy to use continuous integration and testing to as many developers as possible. We may, at our sole discretion, provide paid services based upon PHPCI - but PHPCI will always remain free (as in cost, and freedom) and open source. + +### The Contribution Process + +1. If you are thinking of making a large change or feature addition, [open an issue](/Block8/PHPCI/issues) titled "Intent to implement: ". Describe your idea in detail and discuss it with the community. It might be that someone already has a plan, could help you out, or your idea may simply not be suitable for the project at this time. +2. Fork the PHPCI project on Github +3. Add a feature or fix a bug - We recommend that you do this on a branch within your repository. +4. Create a pull request containing just the one change you want to contribute back to PHPCI. If you have more than one feature or bug fix, please create separate branches within your repository, and then submit a separate pull request for each one. Your pull request should use the template detailed below. +5. We'll then review your pull request and give any necessary feedback, this could be: + * Suggestions to improve your implementation + * Questions + * Issues/bugs related to the change + * Coding standards pointers +6. Once everyone is happy with the submission, we'll merge it back into PHPCI. Your change will then be included in the next project release. + +### Not sure what to start with? +We maintain two labels within our issue tracker that may be of interest to new contributors: + +* [The "Easy Fix" List](https://github.com/Block8/PHPCI/labels/flag:easy-fix) +* [The "Priority" List](https://github.com/Block8/PHPCI/labels/flag:priority) + +### Coding Standards +We require that all contributions meet at least the following guidelines: + +* PSR-1 & PSR-2 compliance for all code +* Doc-blocks for all classes and methods +* All files must contain the standard file-level docblock, including the copyright, license and link tags. + +All pull requests will be checked against these standards. If you're modifying a file as part of your change which does not comply with the above, please make the necessary changes to bring it into compliance prior to submitting the pull request. + +### Other Requirements +When you're adding new features or functionality, or you're updating existing functionality, please ensure that the relevant documentation is also either created or updated on the Wiki. diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..16692061 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,28 @@ +Before submitting your issue, please make sure that you've checked all of the checkboxes below. + +- [ ] You're running the [latest release](https://github.com/Block8/PHPCI/releases/latest) version of PHPCI. +- [ ] Ensure that you're running at least PHP 5.3.6, you can check this by running `php -v` +- [ ] You've run `composer install --no-dev -o` from the root of your installation. +- [ ] You have set up either the PHPCI [Worker](https://github.com/Block8/PHPCI/wiki/Run-Builds-Using-a-Worker), [Daemon](https://github.com/Block8/PHPCI/wiki/Run-Builds-Using-a-Daemon) or [Cron Job](https://github.com/Block8/PHPCI/wiki/Run-Builds-Using-Cron) to run builds. + +To help us better understand your issue, please answer the following. + +### Expected behaviour + +*Please describe what you're expecting to see happen.* + +### Actual behaviour + +*Please describe what you're actually seeing happen.* + +### Steps to reproduce + +*If your issue requires any specific steps to reproduce, please outline them here.* + +### Environment info +Operating System: +PHP Version: +MySQL Version: + +### Logs or other output that would be helpful +(If logs are large, please upload as attachment). \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..65d060ab --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ +Contribution Type: bug fix | new plugin | new feature | refactor | cosmetic +Link to Intent to Implement: +Link to Bug: + +This pull request affects the following areas: + +* [ ] Front-End +* [ ] Builder +* [ ] Build Plugins + +**In raising this pull request, I confirm the following (please check boxes):** + +- [ ] I have read and understood the [contributing guidelines](/.github/CONTRIBUTING.md)? +- [ ] I have checked that another pull request for this purpose does not exist. +- [ ] I have considered, and confirmed that this submission will be valuable to others. +- [ ] I have created or updated the relevant documentation for this change on the PHPCI Wiki. +- [ ] Do the PHPCI tests pass? + + +Detailed description of change: + + + diff --git a/.gitignore b/.gitignore index 3904ba6d..63e81c5b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,12 @@ config.php .buildpath .htaccess PHPCI/config.yml +cache +/loggerconfig.php +/pluginconfig.php +PHPCI/Model/Migration.php +PHPCI/Model/Base/MigrationBase.php +PHPCI/Store/MigrationStore.php +PHPCI/Store/Base/MigrationStoreBase.php +local_vars.php +Tests/PHPCI/config.yml diff --git a/.phpci.yml b/.phpci.yml new file mode 100644 index 00000000..d6ea0921 --- /dev/null +++ b/.phpci.yml @@ -0,0 +1,38 @@ +build_settings: + verbose: false + ignore: + - "vendor" + - "Tests" + - "PHPCI/Command" # PHPMD complains about un-used parameters, but they are required. + - "public/install.php" # PHPCS really doesn't like PHP mixed with HTML (and so it shouldn't) + - "PHPCI/Migrations" # Ignore the migrations directory, as both PHPMD and PHPCS can't cope with them. + - "PHPCI/Model/Base" # These files are auto-generated, and sometimes hit PHPMD complexity thresholds. + - "PHPCI/Languages" # PHPCS fails on character counts for non-Latin languages + - "public/assets" # If there are any PHP files in here, we didn't write them. + +setup: + composer: + action: "install" + +test: + php_parallel_lint: + ignore: + # Only ignore vendor + - vendor/ + php_mess_detector: + allowed_warnings: 0 + rules: + - phpmd.xml + php_code_sniffer: + standard: phpcs.xml + allowed_warnings: 0 + allowed_errors: 0 + php_loc: + php_unit: + php_docblock_checker: + allowed_warnings: 0 + +broken: + email: + committer: true + cc: ["php-ci@googlegroups.com"] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..5fb6df83 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM ubuntu:14.04 +MAINTAINER Dan Cryer dan.cryer@block8.co.uk + +RUN echo "deb http://ppa.launchpad.net/ondrej/php5/ubuntu trusty main" >> /etc/apt/sources.list +RUN echo "deb http://archive.ubuntu.com/ubuntu/ precise universe" >> /etc/apt/sources.list +RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C300EE8C E5267A6C 0xcbcb082a1bb943db +RUN apt-get update + +# Install PHP: +RUN apt-get install -qy git-core php5-common php5-cli php5-curl php5-imap php5-mysqlnd + +# Give Git some fake user details to prevent it asking when trying to test merges: +RUN git config --global user.name "PHPCI" +RUN git config --global user.email "hello@php.ci" + +ADD ./ /phpci + +RUN php -r "readfile('https://getcomposer.org/installer');" | php +RUN mv composer.phar /phpci/composer + +CMD /phpci/daemonise phpci:daemonise diff --git a/LICENSE.md b/LICENSE.md index 7d8d2f31..4c4f6952 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2013, Block 8 Limited +Copyright (c) 2013-2014, Block 8 Limited All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -6,4 +6,4 @@ Redistribution and use in source and binary forms, with or without modification, - 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. \ No newline at end of file +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/PHPCI/Application.php b/PHPCI/Application.php index 0723c2ad..a6eb646c 100644 --- a/PHPCI/Application.php +++ b/PHPCI/Application.php @@ -2,14 +2,16 @@ /** * PHPCI - Continuous Integration for PHP * -* @copyright Copyright 2013, Block 8 Limited. +* @copyright Copyright 2014, Block 8 Limited. * @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md -* @link http://www.phptesting.org/ +* @link https://www.phptesting.org/ */ namespace PHPCI; use b8; +use b8\Exception\HttpException; +use b8\Http\Response; use b8\Http\Response\RedirectResponse; use b8\View; @@ -20,83 +22,151 @@ use b8\View; class Application extends b8\Application { /** - * Handle an incoming web request. - */ + * @var \PHPCI\Controller + */ + protected $controller; + + /** + * Initialise PHPCI - Handles session verification, routing, etc. + */ + public function init() + { + $request =& $this->request; + $route = '/:controller/:action'; + $opts = array('controller' => 'Home', 'action' => 'index'); + + // Inlined as a closure to fix "using $this when not in object context" on 5.3 + $validateSession = function () { + if (!empty($_SESSION['phpci_user_id'])) { + $user = b8\Store\Factory::getStore('User')->getByPrimaryKey($_SESSION['phpci_user_id']); + + if ($user) { + $_SESSION['phpci_user'] = $user; + return true; + } + + unset($_SESSION['phpci_user_id']); + } + + return false; + }; + + $skipAuth = array($this, 'shouldSkipAuth'); + + // Handler for the route we're about to register, checks for a valid session where necessary: + $routeHandler = function (&$route, Response &$response) use (&$request, $validateSession, $skipAuth) { + $skipValidation = in_array($route['controller'], array('session', 'webhook', 'build-status')); + + if (!$skipValidation && !$validateSession() && (!is_callable($skipAuth) || !$skipAuth())) { + if ($request->isAjax()) { + $response->setResponseCode(401); + $response->setContent(''); + } else { + $_SESSION['phpci_login_redirect'] = substr($request->getPath(), 1); + $response = new RedirectResponse($response); + $response->setHeader('Location', PHPCI_URL.'session/login'); + } + + return false; + } + + return true; + }; + + $this->router->clearRoutes(); + $this->router->register($route, $opts, $routeHandler); + } + + /** + * Handle an incoming web request. + * + * @return b8\b8\Http\Response|Response + */ public function handleRequest() { try { - $this->initRequest(); + $this->response = parent::handleRequest(); + } catch (HttpException $ex) { + $this->config->set('page_title', 'Error'); - // Validate the user's session unless it is a login/logout action or a web hook: - $sessionAction = ($this->controllerName == 'Session' && in_array($this->action, array('login', 'logout'))); - $externalAction = in_array($this->controllerName, array('Bitbucket', 'Github', 'Gitlab', 'BuildStatus')); - $skipValidation = ($externalAction || $sessionAction); + $view = new View('exception'); + $view->exception = $ex; - if ($skipValidation || $this->validateSession()) { - parent::handleRequest(); - } + $this->response->setResponseCode($ex->getErrorCode()); + $this->response->setContent($view->render()); } catch (\Exception $ex) { - $content = '

There was a problem with this request

-

Please paste the details below into a - new bug report - so that we can investigate and fix it.

'; + $this->config->set('page_title', 'Error'); - ob_start(); - var_dump(array( - 'message' => $ex->getMessage(), - 'file' => $ex->getFile(), - 'line' => $ex->getLine(), - 'trace' => $ex->getTraceAsString() - )); - var_dump(array( - 'PATH_INFO' => $_SERVER['PATH_INFO'], - 'REDIRECT_PATH_INFO' => $_SERVER['REDIRECT_PATH_INFO'], - 'REQUEST_URI' => $_SERVER['REQUEST_URI'], - 'PHP_SELF' => $_SERVER['PHP_SELF'], - 'SCRIPT_NAME' => $_SERVER['SCRIPT_NAME'], - 'DOCUMENT_ROOT' => $_SERVER['DOCUMENT_ROOT'], - 'SCRIPT_FILENAME' => $_SERVER['SCRIPT_FILENAME'], - 'SERVER_SOFTWARE' => $_SERVER['SERVER_SOFTWARE'], - )); - $content .= ob_get_contents(); - ob_end_clean(); + $view = new View('exception'); + $view->exception = $ex; - $this->response->setContent($content); - $this->response->disableLayout(); + $this->response->setResponseCode(500); + $this->response->setContent($view->render()); } + if ($this->response->hasLayout() && $this->controller->layout) { + $this->setLayoutVariables($this->controller->layout); - if (View::exists('layout') && $this->response->hasLayout()) { - $view = new View('layout'); - $view->content = $this->response->getContent(); - $this->response->setContent($view->render()); + $this->controller->layout->content = $this->response->getContent(); + $this->response->setContent($this->controller->layout->render()); } return $this->response; } /** - * Validate whether or not the remote user has a valid session: - */ - protected function validateSession() + * Loads a particular controller, and injects our layout view into it. + * @param $class + * @return mixed + */ + protected function loadController($class) { - if (!empty($_SESSION['user_id'])) { - $user = b8\Store\Factory::getStore('User')->getByPrimaryKey($_SESSION['user_id']); + $controller = parent::loadController($class); + $controller->layout = new View('layout'); + $controller->layout->title = 'PHPCI'; + $controller->layout->breadcrumb = array(); - if ($user) { - $_SESSION['user'] = $user; - return true; - } + return $controller; + } - unset($_SESSION['user_id']); + /** + * Injects variables into the layout before rendering it. + * @param View $layout + */ + protected function setLayoutVariables(View &$layout) + { + $groups = array(); + $groupStore = b8\Store\Factory::getStore('ProjectGroup'); + $groupList = $groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC')); + + foreach ($groupList['items'] as $group) { + $thisGroup = array('title' => $group->getTitle()); + $projects = b8\Store\Factory::getStore('Project')->getByGroupId($group->getId()); + $thisGroup['projects'] = $projects['items']; + $groups[] = $thisGroup; } - if ($this->request->isAjax()) { - $this->response->setResponseCode(401); - $this->response->setContent(''); - } else { - $this->response = new RedirectResponse($this->response); - $this->response->setHeader('Location', PHPCI_URL.'session/login'); + $layout->groups = $groups; + } + + /** + * Check whether we should skip auth (because it is disabled) + * @return bool + */ + protected function shouldSkipAuth() + { + $config = b8\Config::getInstance(); + $state = (bool)$config->get('phpci.authentication_settings.state', false); + $userId = $config->get('phpci.authentication_settings.user_id', 0); + + if (false !== $state && 0 != (int)$userId) { + $user = b8\Store\Factory::getStore('User') + ->getByPrimaryKey($userId); + + if ($user) { + $_SESSION['phpci_user'] = $user; + return true; + } } return false; diff --git a/PHPCI/BuildFactory.php b/PHPCI/BuildFactory.php index e4628434..91e618a8 100644 --- a/PHPCI/BuildFactory.php +++ b/PHPCI/BuildFactory.php @@ -2,17 +2,15 @@ /** * PHPCI - Continuous Integration for PHP * -* @copyright Copyright 2013, Block 8 Limited. +* @copyright Copyright 2014, Block 8 Limited. * @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md -* @link http://www.phptesting.org/ +* @link https://www.phptesting.org/ */ namespace PHPCI; +use b8\Store\Factory; use PHPCI\Model\Build; -use PHPCI\Model\Build\LocalBuild; -use PHPCI\Model\Build\GithubBuild; -use PHPCI\Model\Build\BitbucketBuild; /** * PHPCI Build Factory - Takes in a generic "Build" and returns a type-specific build model. @@ -21,35 +19,61 @@ use PHPCI\Model\Build\BitbucketBuild; class BuildFactory { /** - * Takes a generic build and returns a type-specific build model. - * @return PHPCI\Model\Build\LocalBuild|PHPCI\Model\Build\GithubBuild|PHPCI\Model\Build\BitbucketBuild - */ - public static function getBuild(Build $base) + * @param $buildId + * @return Build + * @throws \Exception + */ + public static function getBuildById($buildId) { - switch($base->getProject()->getType()) - { - case 'remote': - $type = 'RemoteGitBuild'; - break; - case 'local': - $type = 'LocalBuild'; - break; - case 'github': - $type = 'GithubBuild'; - break; - case 'bitbucket': - $type = 'BitbucketBuild'; - break; - case 'gitlab': - $type = 'GitlabBuild'; - break; - case 'hg': - $type = 'MercurialBuild'; - break; + $build = Factory::getStore('Build')->getById($buildId); + + if (empty($build)) { + throw new \Exception('Build ID ' . $buildId . ' does not exist.'); } - $type = '\\PHPCI\\Model\\Build\\' . $type; + return self::getBuild($build); + } - return new $type($base->getDataArray()); + /** + * Takes a generic build and returns a type-specific build model. + * @param Build $build The build from which to get a more specific build type. + * @return Build + */ + public static function getBuild(Build $build) + { + $project = $build->getProject(); + + if (!empty($project)) { + switch ($project->getType()) { + case 'remote': + $type = 'RemoteGitBuild'; + break; + case 'local': + $type = 'LocalBuild'; + break; + case 'github': + $type = 'GithubBuild'; + break; + case 'bitbucket': + $type = 'BitbucketBuild'; + break; + case 'gitlab': + $type = 'GitlabBuild'; + break; + case 'hg': + $type = 'MercurialBuild'; + break; + case 'svn': + $type = 'SubversionBuild'; + break; + default: + return $build; + } + + $class = '\\PHPCI\\Model\\Build\\' . $type; + $build = new $class($build->getDataArray()); + } + + return $build; } } diff --git a/PHPCI/Builder.php b/PHPCI/Builder.php index 73486fee..1aed3d3f 100644 --- a/PHPCI/Builder.php +++ b/PHPCI/Builder.php @@ -1,72 +1,65 @@ -*/ -class Builder + * PHPCI Build Runner + * @author Dan Cryer + */ +class Builder implements LoggerAwareInterface { /** - * @var string - */ + * @var string + */ public $buildPath; /** - * @var string[] - */ - public $ignore = array(); + * @var string[] + */ + public $ignore = array(); /** - * @var string - */ - protected $ciDir; - - /** - * @var string - */ + * @var string + */ protected $directory; /** - * @var bool - */ - protected $success = true; + * @var bool + */ + protected $verbose = true; /** - * @var string - */ - protected $log = ''; - - /** - * @var bool - */ - protected $verbose = true; - - /** - * @var \PHPCI\Model\Build - */ + * @var \PHPCI\Model\Build + */ protected $build; /** - * @var callable - */ - protected $logCallback; + * @var LoggerInterface + */ + protected $logger; /** - * @var array - */ + * @var array + */ protected $config; /** @@ -75,12 +68,9 @@ class Builder protected $lastOutput; /** - * An array of key => value pairs that will be used for - * interpolation and environment variables - * @var array - * @see setInterpolationVars() + * @var BuildInterpolator */ - protected $interpolation_vars = array(); + protected $interpolator; /** * @var \PHPCI\Store\BuildStore @@ -93,30 +83,70 @@ class Builder public $quiet = false; /** - * Set up the builder. - * @param \PHPCI\Model\Build - * @param callable - */ - public function __construct(Build $build, callable $logCallback) + * @var \PHPCI\Plugin\Util\Executor + */ + protected $pluginExecutor; + + /** + * @var Helper\CommandExecutor + */ + protected $commandExecutor; + + /** + * @var Logging\BuildLogger + */ + protected $buildLogger; + + /** + * Set up the builder. + * @param \PHPCI\Model\Build $build + * @param LoggerInterface $logger + */ + public function __construct(Build $build, LoggerInterface $logger = null) { $this->build = $build; - $this->store = Store\Factory::getStore('Build'); - $this->logCallback = $logCallback; + $this->store = Factory::getStore('Build'); + + $this->buildLogger = new BuildLogger($logger, $build); + + $pluginFactory = $this->buildPluginFactory($build); + $pluginFactory->addConfigFromFile(PHPCI_DIR . "/pluginconfig.php"); + $this->pluginExecutor = new Plugin\Util\Executor($pluginFactory, $this->buildLogger); + + $executorClass = 'PHPCI\Helper\UnixCommandExecutor'; + if (IS_WIN) { + $executorClass = 'PHPCI\Helper\WindowsCommandExecutor'; + } + + $this->commandExecutor = new $executorClass( + $this->buildLogger, + PHPCI_DIR, + $this->quiet, + $this->verbose + ); + + $this->interpolator = new BuildInterpolator(); } /** - * Set the config array, as read from phpci.yml - * @param array - */ - public function setConfigArray(array $config) + * Set the config array, as read from phpci.yml + * @param array|null $config + * @throws \Exception + */ + public function setConfigArray($config) { + if (is_null($config) || !is_array($config)) { + throw new \Exception(Lang::get('missing_phpci_yml')); + } + $this->config = $config; } /** - * Access a variable from the phpci.yml file. - * @param string - */ + * Access a variable from the phpci.yml file. + * @param string + * @return mixed + */ public function getConfig($key) { $rtn = null; @@ -147,81 +177,86 @@ class Builder } /** - * Run the active build. - */ + * Run the active build. + */ public function execute() { // Update the build in the database, ping any external services. - $this->build->setStatus(1); + $this->build->setStatus(Build::STATUS_RUNNING); $this->build->setStarted(new \DateTime()); $this->store->save($this->build); $this->build->sendStatusPostback(); + $success = true; + + $previous_build = $this->build->getProject()->getPreviousBuild($this->build->getBranch()); + + $previous_state = Build::STATUS_NEW; + + if ($previous_build) { + $previous_state = $previous_build->getStatus(); + } try { // Set up the build: $this->setupBuild(); // Run the core plugin stages: - foreach (array('setup', 'test', 'complete') as $stage) { - $this->executePlugins($stage); - $this->log(''); + foreach (array('setup', 'test') as $stage) { + $success &= $this->pluginExecutor->executePlugins($this->config, $stage); } - // Failed build? Execute failure plugins and then mark the build as failed. - if (!$this->success) { - $this->executePlugins('failure'); - throw new \Exception('BUILD FAILED!'); + // Set the status so this can be used by complete, success and failure + // stages. + if ($success) { + $this->build->setStatus(Build::STATUS_SUCCESS); + } else { + $this->build->setStatus(Build::STATUS_FAILED); } - // If we got this far, the build was successful! - if ($this->success) { - $this->build->setStatus(2); - $this->executePlugins('success'); - $this->logSuccess('BUILD SUCCESSFUL!'); - } + if ($success) { + $this->pluginExecutor->executePlugins($this->config, 'success'); + + if ($previous_state == Build::STATUS_FAILED) { + $this->pluginExecutor->executePlugins($this->config, 'fixed'); + } + + $this->buildLogger->logSuccess(Lang::get('build_success')); + } else { + $this->pluginExecutor->executePlugins($this->config, 'failure'); + + if ($previous_state == Build::STATUS_SUCCESS || $previous_state == Build::STATUS_NEW) { + $this->pluginExecutor->executePlugins($this->config, 'broken'); + } + + $this->buildLogger->logFailure(Lang::get('build_failed')); + } } catch (\Exception $ex) { - $this->logFailure($ex->getMessage()); - $this->build->setStatus(3); + $this->build->setStatus(Build::STATUS_FAILED); + $this->buildLogger->logFailure(Lang::get('exception') . $ex->getMessage()); + }finally{ + // Complete stage plugins are always run + $this->pluginExecutor->executePlugins($this->config, 'complete'); } - - // Clean up: - $this->log('Removing build.'); - shell_exec(sprintf('rm -Rf "%s"', $this->buildPath)); + // Update the build in the database, ping any external services, etc. $this->build->sendStatusPostback(); $this->build->setFinished(new \DateTime()); - $this->build->setLog($this->log); + + // Clean up: + $this->buildLogger->log(Lang::get('removing_build')); + $this->build->removeBuildDirectory(); + $this->store->save($this->build); } /** - * Used by this class, and plugins, to execute shell commands. - */ + * Used by this class, and plugins, to execute shell commands. + */ public function executeCommand() { - $command = call_user_func_array('sprintf', func_get_args()); - - if (!$this->quiet) { - $this->log('Executing: ' . $command, ' '); - } - - $status = 0; - exec($command, $this->lastOutput, $status); - - if (!empty($this->lastOutput) && ($this->verbose || $status != 0)) { - $this->log($this->lastOutput, ' '); - } - - - $rtn = false; - - if ($status == 0) { - $rtn = true; - } - - return $rtn; + return $this->commandExecutor->executeCommand(func_get_args()); } /** @@ -229,104 +264,59 @@ class Builder */ public function getLastOutput() { - return implode(PHP_EOL, $this->lastOutput); + return $this->commandExecutor->getLastOutput(); } /** - * Add an entry to the build log. - * @param string|string[] - * @param string - */ - public function log($message, $prefix = '') + * Specify whether exec output should be logged. + * @param bool $enableLog + */ + public function logExecOutput($enableLog = true) { - if (!is_array($message)) { - $message = array($message); - } - - foreach ($message as $item) { - call_user_func_array($this->logCallback, array($prefix . $item)); - $this->log .= $prefix . $item . PHP_EOL; - } - - $this->build->setLog($this->log); - $this->store->save($this->build); + $this->commandExecutor->logExecOutput = $enableLog; } /** - * Add a success-coloured message to the log. - * @param string - */ - public function logSuccess($message) + * Find a binary required by a plugin. + * @param string $binary + * @param bool $quiet + * + * @return null|string + */ + public function findBinary($binary, $quiet = false) { - $this->log("\033[0;32m" . $message . "\033[0m"); + return $this->commandExecutor->findBinary($binary, $quiet = false); } /** - * Add a failure-coloured message to the log. - * @param string - */ - public function logFailure($message) - { - $this->log("\033[0;31m" . $message . "\033[0m"); - } - - /** - * Replace every occurance of the interpolation vars in the given string + * Replace every occurrence of the interpolation vars in the given string * Example: "This is build %PHPCI_BUILD%" => "This is build 182" * @param string $input * @return string */ public function interpolate($input) { - $keys = array_keys($this->interpolation_vars); - $values = array_values($this->interpolation_vars); - return str_replace($keys, $values, $input); + return $this->interpolator->interpolate($input); } /** - * Sets the variables that will be used for interpolation. This must be run - * from setupBuild() because prior to that, we don't know the buildPath + * Set up a working copy of the project for building. */ - protected function setInterpolationVars() - { - $this->interpolation_vars = array(); - $this->interpolation_vars['%PHPCI%'] = 1; - $this->interpolation_vars['%COMMIT%'] = $this->build->getCommitId(); - $this->interpolation_vars['%PROJECT%'] = $this->build->getProjectId(); - $this->interpolation_vars['%BUILD%'] = $this->build->getId(); - $this->interpolation_vars['%PROJECT_TITLE%'] = $this->getBuildProjectTitle(); - $this->interpolation_vars['%BUILD_PATH%'] = $this->buildPath; - $this->interpolation_vars['%BUILD_URI%'] = PHPCI_URL . "build/view/" . $this->build->getId(); - $this->interpolation_vars['%PHPCI_COMMIT%'] = $this->interpolation_vars['%COMMIT%']; - $this->interpolation_vars['%PHPCI_PROJECT%'] = $this->interpolation_vars['%PROJECT%']; - $this->interpolation_vars['%PHPCI_BUILD%'] = $this->interpolation_vars['%BUILD%']; - $this->interpolation_vars['%PHPCI_PROJECT_TITLE%'] = $this->interpolation_vars['%PROJECT_TITLE%']; - $this->interpolation_vars['%PHPCI_BUILD_PATH%'] = $this->interpolation_vars['%BUILD_PATH%']; - $this->interpolation_vars['%PHPCI_BUILD_URI%'] = $this->interpolation_vars['%BUILD_URI%']; - - putenv('PHPCI=1'); - putenv('PHPCI_COMMIT='.$this->interpolation_vars['%COMMIT%']); - putenv('PHPCI_PROJECT='.$this->interpolation_vars['%PROJECT%']); - putenv('PHPCI_BUILD='.$this->interpolation_vars['%BUILD%']); - putenv('PHPCI_PROJECT_TITLE='.$this->interpolation_vars['%PROJECT_TITLE%']); - putenv('PHPCI_BUILD_PATH='.$this->interpolation_vars['%BUILD_PATH%']); - putenv('PHPCI_BUILD_URI='.$this->interpolation_vars['%BUILD_URI%']); - } - - /** - * Set up a working copy of the project for building. - */ protected function setupBuild() { - $buildId = 'project' . $this->build->getProject()->getId() . '-build' . $this->build->getId(); - $this->ciDir = dirname(__FILE__) . '/../'; - $this->buildPath = $this->ciDir . 'build/' . $buildId . '/'; - - $this->setInterpolationVars(); - + $this->buildPath = $this->build->getBuildPath(); + + $this->interpolator->setupInterpolationVars( + $this->build, + $this->buildPath, + PHPCI_URL + ); + + $this->commandExecutor->setBuildPath($this->buildPath); + // Create a working copy of the project: if (!$this->build->createWorkingCopy($this, $this->buildPath)) { - throw new \Exception('Could not create a working copy.'); + throw new \Exception(Lang::get('could_not_create_working')); } // Does the project's phpci.yml request verbose mode? @@ -339,110 +329,95 @@ class Builder $this->ignore = $this->config['build_settings']['ignore']; } - $this->logSuccess('Working copy created: ' . $this->buildPath); + $this->buildLogger->logSuccess(Lang::get('working_copy_created', $this->buildPath)); return true; } /** - * Execute a the appropriate set of plugins for a given build stage. - */ - protected function executePlugins($stage) + * Sets a logger instance on the object + * + * @param LoggerInterface $logger + * @return null + */ + public function setLogger(LoggerInterface $logger) { - // Ignore any stages for which we don't have plugins set: - if (!array_key_exists($stage, $this->config) || !is_array($this->config[$stage])) { - return; - } - - foreach ($this->config[$stage] as $plugin => $options) { - $this->log(''); - $this->log('RUNNING PLUGIN: ' . $plugin); - - // Is this plugin allowed to fail? - if ($stage == 'test' && !isset($options['allow_failures'])) { - $options['allow_failures'] = false; - } - - // Try and execute it: - if ($this->executePlugin($plugin, $options)) { - - // Execution was successful: - $this->logSuccess('PLUGIN STATUS: SUCCESS!'); - - } else { - - // If we're in the "test" stage and the plugin is not allowed to fail, - // then mark the build as failed: - if ($stage == 'test' && !$options['allow_failures']) { - $this->success = false; - } - - $this->logFailure('PLUGIN STATUS: FAILED'); - } - } + $this->buildLogger->setLogger($logger); } /** - * Executes a given plugin, with options and returns the result. + * Write to the build log. + * @param $message + * @param string $level + * @param array $context */ - protected function executePlugin($plugin, $options) + public function log($message, $level = LogLevel::INFO, $context = array()) { - // Figure out the class name and check the plugin exists: - $class = str_replace('_', ' ', $plugin); - $class = ucwords($class); - $class = 'PHPCI\\Plugin\\' . str_replace(' ', '', $class); + $this->buildLogger->log($message, $level, $context); + } - if (!class_exists($class)) { - $this->logFailure('Plugin does not exist: ' . $plugin); - return false; - } - - $rtn = true; - - // Try running it: - try { - $obj = new $class($this, $this->build, $options); - - if (!$obj->execute()) { - $rtn = false; - } - } catch (\Exception $ex) { - $this->logFailure('EXCEPTION: ' . $ex->getMessage()); - $rtn = false; - } - - return $rtn; + /** + * Add a success-coloured message to the log. + * @param string + */ + public function logSuccess($message) + { + $this->buildLogger->logSuccess($message); } /** - * Find a binary required by a plugin. - * @param $binary - * @return null|string + * Add a failure-coloured message to the log. + * @param string $message + * @param \Exception $exception The exception that caused the error. */ - public function findBinary($binary) + public function logFailure($message, \Exception $exception = null) { - if (is_string($binary)) { - $binary = array($binary); - } + $this->buildLogger->logFailure($message, $exception); + } + /** + * Returns a configured instance of the plugin factory. + * + * @param Build $build + * @return PluginFactory + */ + private function buildPluginFactory(Build $build) + { + $pluginFactory = new PluginFactory(); - foreach ($binary as $bin) { - // Check project root directory: - if (is_file(PHPCI_DIR . $bin)) { - return PHPCI_DIR . $bin; - } + $self = $this; + $pluginFactory->registerResource( + function () use ($self) { + return $self; + }, + null, + 'PHPCI\Builder' + ); - // Check Composer bin dir: - if (is_file(PHPCI_DIR . 'vendor/bin/' . $bin)) { - return PHPCI_DIR . 'vendor/bin/' . $bin; - } + $pluginFactory->registerResource( + function () use ($build) { + return $build; + }, + null, + 'PHPCI\Model\Build' + ); - // Use "which" - $which = trim(shell_exec('which ' . $bin)); + $logger = $this->logger; + $pluginFactory->registerResource( + function () use ($logger) { + return $logger; + }, + null, + 'Psr\Log\LoggerInterface' + ); - if (!empty($which)) { - return $which; - } - } + $pluginFactory->registerResource( + function () use ($self) { + $factory = new MailerFactory($self->getSystemConfig('phpci')); + return $factory->getSwiftMailerFromConfig(); + }, + null, + 'Swift_Mailer' + ); - return null; + return $pluginFactory; } } diff --git a/PHPCI/Command/CreateAdminCommand.php b/PHPCI/Command/CreateAdminCommand.php new file mode 100644 index 00000000..265f03b4 --- /dev/null +++ b/PHPCI/Command/CreateAdminCommand.php @@ -0,0 +1,82 @@ +userStore = $userStore; + } + + protected function configure() + { + $this + ->setName('phpci:create-admin') + ->setDescription(Lang::get('create_admin_user')); + } + + /** + * Creates an admin user in the existing PHPCI database + * + * {@inheritDoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $userService = new UserService($this->userStore); + + /** @var $dialog \Symfony\Component\Console\Helper\DialogHelper */ + $dialog = $this->getHelperSet()->get('dialog'); + + // Function to validate mail address. + $mailValidator = function ($answer) { + if (!filter_var($answer, FILTER_VALIDATE_EMAIL)) { + throw new \InvalidArgumentException(Lang::get('must_be_valid_email')); + } + + return $answer; + }; + + $adminEmail = $dialog->askAndValidate($output, Lang::get('enter_email'), $mailValidator, false); + $adminName = $dialog->ask($output, Lang::get('enter_name')); + $adminPass = $dialog->askHiddenResponse($output, Lang::get('enter_password')); + + try { + $userService->createUser($adminName, $adminEmail, $adminPass, true); + $output->writeln(Lang::get('user_created')); + } catch (\Exception $e) { + $output->writeln(sprintf('%s', Lang::get('failed_to_create'))); + $output->writeln(sprintf('%s', $e->getMessage())); + } + } +} diff --git a/PHPCI/Command/CreateBuildCommand.php b/PHPCI/Command/CreateBuildCommand.php new file mode 100644 index 00000000..01260080 --- /dev/null +++ b/PHPCI/Command/CreateBuildCommand.php @@ -0,0 +1,85 @@ +projectStore = $projectStore; + $this->buildService = $buildService; + } + + /** + * {@inheritDoc} + */ + protected function configure() + { + $this + ->setName('phpci:create-build') + ->setDescription(Lang::get('create_build_project')) + ->addArgument('projectId', InputArgument::REQUIRED, Lang::get('project_id_argument')) + ->addOption('commit', null, InputOption::VALUE_OPTIONAL, Lang::get('commit_id_option')) + ->addOption('branch', null, InputOption::VALUE_OPTIONAL, Lang::get('branch_name_option')); + } + + /** + * {@inheritDoc} + */ + public function execute(InputInterface $input, OutputInterface $output) + { + $projectId = $input->getArgument('projectId'); + $commitId = $input->getOption('commit'); + $branch = $input->getOption('branch'); + + $project = $this->projectStore->getById($projectId); + if (empty($project)) { + throw new \InvalidArgumentException('Project does not exist: ' . $projectId); + } + + try { + $this->buildService->createBuild($project, $commitId, $branch); + $output->writeln(Lang::get('build_created')); + } catch (\Exception $e) { + $output->writeln(sprintf('%s', Lang::get('failed'))); + $output->writeln(sprintf('%s', $e->getMessage())); + } + } +} diff --git a/PHPCI/Command/DaemonCommand.php b/PHPCI/Command/DaemonCommand.php index d6458f2b..cb303eff 100644 --- a/PHPCI/Command/DaemonCommand.php +++ b/PHPCI/Command/DaemonCommand.php @@ -1,49 +1,90 @@ /dev/null 2>&1 & -* -* @copyright Copyright 2013, Block 8 Limited. -* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md -* @link http://www.phptesting.org/ -*/ + * PHPCI - Continuous Integration for PHP + * + * @copyright Copyright 2014, Block 8 Limited. + * @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md + * @link https://www.phptesting.org/ + */ namespace PHPCI\Command; +use Monolog\Logger; +use PHPCI\ProcessControl\Factory; +use PHPCI\ProcessControl\ProcessControlInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use b8\Store\Factory; -use PHPCI\Builder; -use PHPCI\BuildFactory; /** -* Daemon that loops and call the run-command. -* @author Gabriel Baker -* @package PHPCI -* @subpackage Console -*/ + * Daemon that loops and call the run-command. + * @author Gabriel Baker + * @package PHPCI + * @subpackage Console + */ class DaemonCommand extends Command { + + /** + * @var Logger + */ + protected $logger; + + /** + * @var string + */ + protected $pidFilePath; + + /** + * @var string + */ + protected $logFilePath; + + /** + * @var ProcessControlInterface + */ + protected $processControl; + + public function __construct(Logger $logger, ProcessControlInterface $processControl = null, $name = null) + { + parent::__construct($name); + $this->logger = $logger; + $this->processControl = $processControl ?: Factory::getInstance(); + } + protected function configure() { $this ->setName('phpci:daemon') ->setDescription('Initiates the daemon to run commands.') ->addArgument( - 'state', - InputArgument::REQUIRED, - 'start|stop|status' - ); + 'state', InputArgument::REQUIRED, 'start|stop|status' + ) + ->addOption( + 'pid-file', 'p', InputOption::VALUE_REQUIRED, + 'Path of the PID file', + implode(DIRECTORY_SEPARATOR, + array(PHPCI_DIR, 'daemon', 'daemon.pid')) + ) + ->addOption( + 'log-file', 'l', InputOption::VALUE_REQUIRED, + 'Path of the log file', + implode(DIRECTORY_SEPARATOR, + array(PHPCI_DIR, 'daemon', 'daemon.log')) + ); } /** - * Loops through running. - */ + * Loops through running. + */ protected function execute(InputInterface $input, OutputInterface $output) { + $this->pidFilePath = $input->getOption('pid-file'); + $this->logFilePath = $input->getOption('log-file'); + $state = $input->getArgument('state'); switch ($state) { @@ -54,69 +95,108 @@ class DaemonCommand extends Command $this->stopDaemon(); break; case 'status': - $this->statusDaemon(); + $this->statusDaemon($output); break; default: - echo "Not a valid choice, please use start stop or status"; + $this->output->writeln("Not a valid choice, please use start, stop or status"); break; } - } protected function startDaemon() { - - if (file_exists(PHPCI_DIR.'/daemon/daemon.pid')) { - echo "Already started\n"; + $pid = $this->getRunningPid(); + if ($pid) { + $this->logger->notice("Daemon already started", array('pid' => $pid)); return "alreadystarted"; } - $logfile = PHPCI_DIR."/daemon/daemon.log"; + $this->logger->info("Trying to start the daemon"); + $cmd = "nohup %s/daemonise phpci:daemonise > %s 2>&1 &"; - $command = sprintf($cmd, PHPCI_DIR, $logfile); - exec($command); + $command = sprintf($cmd, PHPCI_DIR, $this->logFilePath); + $output = $exitCode = null; + exec($command, $output, $exitCode); + + if ($exitCode !== 0) { + $this->logger->error(sprintf("daemonise exited with status %d", $exitCode)); + return "notstarted"; + } + + for ($i = 0; !($pid = $this->getRunningPid()) && $i < 5; $i++) { + sleep(1); + } + + if (!$pid) { + $this->logger->error("Could not start the daemon"); + return "notstarted"; + } + + $this->logger->notice("Daemon started", array('pid' => $pid)); + return "started"; } protected function stopDaemon() { - - if (!file_exists(PHPCI_DIR.'/daemon/daemon.pid')) { - echo "Not started\n"; + $pid = $this->getRunningPid(); + if (!$pid) { + $this->logger->notice("Cannot stop the daemon as it is not started"); return "notstarted"; } - $cmd = "kill $(cat %s/daemon/daemon.pid)"; - $command = sprintf($cmd, PHPCI_DIR); - exec($command); - unlink(PHPCI_DIR.'/daemon/daemon.pid'); - } + $this->logger->info("Trying to terminate the daemon", array('pid' => $pid)); + $this->processControl->kill($pid); - protected function statusDaemon() - { - - if (!file_exists(PHPCI_DIR.'/daemon/daemon.pid')) { - echo "Not running\n"; - return "notrunning"; + for ($i = 0; ($pid = $this->getRunningPid()) && $i < 5; $i++) { + sleep(1); } - $pid = trim(file_get_contents(PHPCI_DIR.'/daemon/daemon.pid')); - $pidcheck = sprintf("/proc/%s", $pid); - if (is_dir($pidcheck)) { - echo "Running\n"; + if ($pid) { + $this->logger->warning("The daemon is resiting, trying to kill it", array('pid' => $pid)); + $this->processControl->kill($pid, true); + + for ($i = 0; ($pid = $this->getRunningPid()) && $i < 5; $i++) { + sleep(1); + } + } + + if (!$pid) { + $this->logger->notice("Daemon stopped"); + return "stopped"; + } + + $this->logger->error("Could not stop the daemon"); + } + + protected function statusDaemon(OutputInterface $output) + { + $pid = $this->getRunningPid(); + if ($pid) { + $output->writeln(sprintf('The daemon is running, PID: %d', $pid)); return "running"; } - unlink(PHPCI_DIR.'/daemon/daemon.pid'); - echo "Not running\n"; + $output->writeln('The daemon is not running'); return "notrunning"; } - /** - * Called when log entries are made in Builder / the plugins. - * @see \PHPCI\Builder::log() - */ - public function logCallback($log) + /** Check if there is a running daemon + * + * @return int|null + */ + protected function getRunningPid() { - $this->output->writeln($log); + if (!file_exists($this->pidFilePath)) { + return; + } + + $pid = intval(trim(file_get_contents($this->pidFilePath))); + + if($this->processControl->isRunning($pid, true)) { + return $pid; + } + + // Not found, remove the stale PID file + unlink($this->pidFilePath); } } diff --git a/PHPCI/Command/DaemoniseCommand.php b/PHPCI/Command/DaemoniseCommand.php index 3c490d75..eadd334e 100644 --- a/PHPCI/Command/DaemoniseCommand.php +++ b/PHPCI/Command/DaemoniseCommand.php @@ -1,23 +1,19 @@ /dev/null 2>&1 & -* -* @copyright Copyright 2013, Block 8 Limited. -* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md -* @link http://www.phptesting.org/ -*/ + * PHPCI - Continuous Integration for PHP + * + * @copyright Copyright 2014, Block 8 Limited. + * @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md + * @link https://www.phptesting.org/ + */ namespace PHPCI\Command; +use Monolog\Logger; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use b8\Store\Factory; -use PHPCI\Builder; -use PHPCI\BuildFactory; /** * Daemon that loops and call the run-command. @@ -27,6 +23,36 @@ use PHPCI\BuildFactory; */ class DaemoniseCommand extends Command { + /** + * @var Logger + */ + protected $logger; + + /** + * @var OutputInterface + */ + protected $output; + + /** + * @var boolean + */ + protected $run; + + /** + * @var int + */ + protected $sleep; + + /** + * @param \Monolog\Logger $logger + * @param string $name + */ + public function __construct(Logger $logger, $name = null) + { + parent::__construct($name); + $this->logger = $logger; + } + protected function configure() { $this @@ -43,16 +69,24 @@ class DaemoniseCommand extends Command $command = sprintf($cmd, getmypid(), PHPCI_DIR); exec($command); + $this->output = $output; $this->run = true; $this->sleep = 0; - $runner = new RunCommand; + $runner = new RunCommand($this->logger); + $runner->setMaxBuilds(1); + $runner->setDaemon(true); + + $emptyInput = new ArgvInput(array()); while ($this->run) { + $buildCount = 0; + try { - $buildCount = $runner->execute($input, $output); + $buildCount = $runner->run($emptyInput, $output); } catch (\Exception $e) { - var_dump($e); + $output->writeln('Exception: ' . $e->getMessage() . ''); + $output->writeln('Line: ' . $e->getLine() . ' - File: ' . $e->getFile() . ''); } if (0 == $buildCount && $this->sleep < 15) { diff --git a/PHPCI/Command/GenerateCommand.php b/PHPCI/Command/GenerateCommand.php index 3a97f70c..ddd227d9 100644 --- a/PHPCI/Command/GenerateCommand.php +++ b/PHPCI/Command/GenerateCommand.php @@ -1,18 +1,16 @@ 'PHPCI'), + array('default' => PHPCI_DIR), + false + ); + $gen->generateModels(); $gen->generateStores(); } diff --git a/PHPCI/Command/InstallCommand.php b/PHPCI/Command/InstallCommand.php index 7f9daf6d..5f5cbd67 100644 --- a/PHPCI/Command/InstallCommand.php +++ b/PHPCI/Command/InstallCommand.php @@ -1,169 +1,422 @@ -* @package PHPCI -* @subpackage Console -*/ + * Install console command - Installs PHPCI. + * @author Dan Cryer + * @package PHPCI + * @subpackage Console + */ class InstallCommand extends Command { + protected $configFilePath; + protected function configure() { + $defaultPath = PHPCI_DIR . 'PHPCI/config.yml'; + $this ->setName('phpci:install') - ->setDescription('Install PHPCI.'); + ->addOption('url', null, InputOption::VALUE_OPTIONAL, Lang::get('installation_url')) + ->addOption('db-host', null, InputOption::VALUE_OPTIONAL, Lang::get('db_host')) + ->addOption('db-name', null, InputOption::VALUE_OPTIONAL, Lang::get('db_name')) + ->addOption('db-user', null, InputOption::VALUE_OPTIONAL, Lang::get('db_user')) + ->addOption('db-pass', null, InputOption::VALUE_OPTIONAL, Lang::get('db_pass')) + ->addOption('admin-name', null, InputOption::VALUE_OPTIONAL, Lang::get('admin_name')) + ->addOption('admin-pass', null, InputOption::VALUE_OPTIONAL, Lang::get('admin_pass')) + ->addOption('admin-mail', null, InputOption::VALUE_OPTIONAL, Lang::get('admin_email')) + ->addOption('config-path', null, InputOption::VALUE_OPTIONAL, Lang::get('config_path'), $defaultPath) + ->addOption('queue-disabled', null, InputOption::VALUE_NONE, 'Don\'t ask for queue details') + ->addOption('queue-server', null, InputOption::VALUE_OPTIONAL, 'Beanstalkd queue server hostname') + ->addOption('queue-name', null, InputOption::VALUE_OPTIONAL, 'Beanstalkd queue name') + ->setDescription(Lang::get('install_phpci')); } /** - * Installs PHPCI - Can be run more than once as long as you ^C instead of entering an email address. - */ + * Installs PHPCI - Can be run more than once as long as you ^C instead of entering an email address. + */ protected function execute(InputInterface $input, OutputInterface $output) { - // Gather initial data from the user: - $conf = array(); - $conf['b8']['database']['servers']['read'] = $this->ask('Enter your MySQL host: '); - $conf['b8']['database']['servers']['write'] = $conf['b8']['database']['servers']['read']; - $conf['b8']['database']['name'] = $this->ask('Enter the database name PHPCI should use: '); - $conf['b8']['database']['username'] = $this->ask('Enter your MySQL username: '); - $conf['b8']['database']['password'] = $this->ask('Enter your MySQL password: ', true); - $ask = 'Your PHPCI URL (without trailing slash): '; - $conf['phpci']['url'] = $this->ask($ask, false, array(FILTER_VALIDATE_URL,"/[^\/]$/i")); - $conf['phpci']['github']['id'] = $this->ask('(Optional) Github Application ID: ', true); - $conf['phpci']['github']['secret'] = $this->ask('(Optional) Github Application Secret: ', true); + $this->configFilePath = $input->getOption('config-path'); - $conf['phpci']['email_settings']['smtp_address'] = $this->ask('(Optional) Smtp server address: ', true); - $conf['phpci']['email_settings']['smtp_port'] = $this->ask('(Optional) Smtp port: ', true); - $conf['phpci']['email_settings']['smtp_encryption'] = $this->ask('(Optional) Smtp encryption: ', true); - $conf['phpci']['email_settings']['smtp_username'] = $this->ask('(Optional) Smtp Username: ', true); - $conf['phpci']['email_settings']['smtp_password'] = $this->ask('(Optional) Smtp Password: ', true); - $conf['phpci']['email_settings']['from_address'] = $this->ask('(Optional) Email address to send from: ', true); - - $ask = '(Optional) Default address to email notifications to: '; - $conf['phpci']['email_settings']['default_mailto_address'] = $this->ask($ask, true); - - $dbUser = $conf['b8']['database']['username']; - $dbPass = $conf['b8']['database']['password']; - $dbHost = $conf['b8']['database']['servers']['write']; - $dbName = $conf['b8']['database']['name']; - - // Create the database if it doesn't exist: - $cmd = 'mysql -u' . $dbUser . (!empty($dbPass) ? ' -p' . $dbPass : '') . ' -h' . $dbHost . - ' -e "CREATE DATABASE IF NOT EXISTS ' . $dbName . '"'; - - shell_exec($cmd); - - $dumper = new \Symfony\Component\Yaml\Dumper(); - $yaml = $dumper->dump($conf); - - file_put_contents(PHPCI_DIR . 'PHPCI/config.yml', $yaml); - - require(PHPCI_DIR . 'bootstrap.php'); - - // Update the database: - $gen = new \b8\Database\Generator(\b8\Database::getConnection(), 'PHPCI', './PHPCI/Model/Base/'); - $gen->generate(); - - // Try to create a user account: - $adminEmail = $this->ask('Enter your email address (leave blank if updating): ', true, FILTER_VALIDATE_EMAIL); - - if (empty($adminEmail)) { + if (!$this->verifyNotInstalled($output)) { return; } - $adminPass = $this->ask('Enter your desired admin password: '); - $adminName = $this->ask('Enter your name: '); - try { - $user = new \PHPCI\Model\User(); - $user->setEmail($adminEmail); - $user->setName($adminName); - $user->setIsAdmin(1); - $user->setHash(password_hash($adminPass, PASSWORD_DEFAULT)); + $output->writeln(''); + $output->writeln('******************'); + $output->writeln(' '.Lang::get('welcome_to_phpci').''); + $output->writeln('******************'); + $output->writeln(''); - $store = \b8\Store\Factory::getStore('User'); - $store->save($user); + $this->checkRequirements($output); - print 'User account created!' . PHP_EOL; - } catch (\Exception $ex) { - print 'There was a problem creating your account. :(' . PHP_EOL; - print $ex->getMessage(); + $output->writeln(Lang::get('please_answer')); + $output->writeln('-------------------------------------'); + $output->writeln(''); + + // ---- + // Get MySQL connection information and verify that it works: + // ---- + $connectionVerified = false; + + while (!$connectionVerified) { + $db = $this->getDatabaseInformation($input, $output); + + $connectionVerified = $this->verifyDatabaseDetails($db, $output); } + + $output->writeln(''); + + $conf = array(); + $conf['b8']['database'] = $db; + + // ---- + // Get basic installation details (URL, etc) + // ---- + $conf['phpci'] = $this->getPhpciConfigInformation($input, $output); + + $this->writeConfigFile($conf); + $this->setupDatabase($output); + $admin = $this->getAdminInformation($input, $output); + $this->createAdminUser($admin, $output); } - protected function ask($question, $emptyOk = false, $validationFilter = null) + /** + * Check PHP version, required modules and for disabled functions. + * + * @param OutputInterface $output + * @throws \Exception + */ + protected function checkRequirements(OutputInterface $output) { - print $question . ' '; + $output->write('Checking requirements...'); + $errors = false; - $rtn = ''; - $stdin = fopen('php://stdin', 'r'); - $rtn = fgets($stdin); - fclose($stdin); + // Check PHP version: + if (!(version_compare(PHP_VERSION, '5.3.8') >= 0)) { + $output->writeln(''); + $output->writeln(''.Lang::get('phpci_php_req').''); + $errors = true; + } - $rtn = trim($rtn); + // Check required extensions are present: + $requiredExtensions = array('PDO', 'pdo_mysql'); - if (!$emptyOk && empty($rtn)) { - $rtn = $this->ask($question, $emptyOk, $validationFilter); - } elseif ($validationFilter != null && ! empty($rtn)) { - if (! $this -> controlFormat($rtn, $validationFilter, $statusMessage)) { - print $statusMessage; - $rtn = $this->ask($question, $emptyOk, $validationFilter); + foreach ($requiredExtensions as $extension) { + if (!extension_loaded($extension)) { + $output->writeln(''); + $output->writeln(''.Lang::get('extension_required', $extension).''); + $errors = true; } } + // Check required functions are callable: + $requiredFunctions = array('exec', 'shell_exec'); + + foreach ($requiredFunctions as $function) { + if (!function_exists($function)) { + $output->writeln(''); + $output->writeln(''.Lang::get('function_required', $function).''); + $errors = true; + } + } + + if (!function_exists('password_hash')) { + $output->writeln(''); + $output->writeln(''.Lang::get('function_required', $function).''); + $errors = true; + } + + if ($errors) { + throw new Exception(Lang::get('requirements_not_met')); + } + + $output->writeln(' '.Lang::get('ok').''); + $output->writeln(''); + } + + /** + * Load information for admin user form CLI options or ask info to user. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return array + */ + protected function getAdminInformation(InputInterface $input, OutputInterface $output) + { + $admin = array(); + + /** + * @var \Symfony\Component\Console\Helper\DialogHelper + */ + $dialog = $this->getHelperSet()->get('dialog'); + + // Function to validate mail address. + $mailValidator = function ($answer) { + if (!filter_var($answer, FILTER_VALIDATE_EMAIL)) { + throw new \InvalidArgumentException(Lang::get('must_be_valid_email')); + } + + return $answer; + }; + + if ($adminEmail = $input->getOption('admin-mail')) { + $adminEmail = $mailValidator($adminEmail); + } else { + $adminEmail = $dialog->askAndValidate($output, Lang::get('enter_email'), $mailValidator, false); + } + if (!$adminName = $input->getOption('admin-name')) { + $adminName = $dialog->ask($output, Lang::get('enter_name')); + } + if (!$adminPass = $input->getOption('admin-pass')) { + $adminPass = $dialog->askHiddenResponse($output, Lang::get('enter_password')); + } + + $admin['mail'] = $adminEmail; + $admin['name'] = $adminName; + $admin['pass'] = $adminPass; + + return $admin; + } + + /** + * Load configuration for PHPCI form CLI options or ask info to user. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return array + */ + protected function getPhpciConfigInformation(InputInterface $input, OutputInterface $output) + { + $phpci = array(); + + /** + * @var \Symfony\Component\Console\Helper\DialogHelper + */ + $dialog = $this->getHelperSet()->get('dialog'); + + // FUnction do validate URL. + $urlValidator = function ($answer) { + if (!filter_var($answer, FILTER_VALIDATE_URL)) { + throw new Exception(Lang::get('must_be_valid_url')); + } + + return rtrim($answer, '/'); + }; + + if ($url = $input->getOption('url')) { + $url = $urlValidator($url); + } else { + $url = $dialog->askAndValidate($output, Lang::get('enter_phpci_url'), $urlValidator, false); + } + + $phpci['url'] = $url; + $phpci['worker'] = $this->getQueueInformation($input, $output, $dialog); + + return $phpci; + } + + /** + * If the user wants to use a queue, get the necessary details. + * @param InputInterface $input + * @param OutputInterface $output + * @param DialogHelper $dialog + * @return array + */ + protected function getQueueInformation(InputInterface $input, OutputInterface $output, DialogHelper $dialog) + { + if ($input->getOption('queue-disabled')) { + return null; + } + + $rtn = []; + + $helper = $this->getHelper('question'); + $question = new ConfirmationQuestion('Use beanstalkd to manage build queue? ', true); + + if (!$helper->ask($input, $output, $question)) { + $output->writeln('Skipping beanstalkd configuration.'); + return null; + } + + if (!$rtn['host'] = $input->getOption('queue-server')) { + $rtn['host'] = $dialog->ask($output, 'Enter your beanstalkd hostname [localhost]: ', 'localhost'); + } + + if (!$rtn['queue'] = $input->getOption('queue-name')) { + $rtn['queue'] = $dialog->ask($output, 'Enter the queue (tube) name to use [phpci]: ', 'phpci'); + } + return $rtn; } - protected function controlFormat($valueToInspect, $filter, &$statusMessage) + + /** + * Load configuration for DB form CLI options or ask info to user. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return array + */ + protected function getDatabaseInformation(InputInterface $input, OutputInterface $output) { - $filters = !(is_array($filter))? array($filter) : $filter; - $statusMessage = ''; - $status = true; - $options = array(); + $db = array(); - foreach ($filters as $filter) { - if (! is_int($filter)) { - $regexp = $filter; - $filter = FILTER_VALIDATE_REGEXP; - $options = array( - 'options' => array( - 'regexp' => $regexp, - ) - ); - } - if (! filter_var($valueToInspect, $filter, $options)) { - $status = false; + /** + * @var \Symfony\Component\Console\Helper\DialogHelper + */ + $dialog = $this->getHelperSet()->get('dialog'); - switch ($filter) - { - case FILTER_VALIDATE_URL: - $statusMessage = 'Incorrect url format.' . PHP_EOL; - break; - case FILTER_VALIDATE_EMAIL: - $statusMessage = 'Incorrect e-mail format.' . PHP_EOL; - break; - case FILTER_VALIDATE_REGEXP: - $statusMessage = 'Incorrect format.' . PHP_EOL; - break; - } + if (!$dbHost = $input->getOption('db-host')) { + $dbHost = $dialog->ask($output, Lang::get('enter_db_host'), 'localhost'); + } + + if (!$dbName = $input->getOption('db-name')) { + $dbName = $dialog->ask($output, Lang::get('enter_db_name'), 'phpci'); + } + + if (!$dbUser = $input->getOption('db-user')) { + $dbUser = $dialog->ask($output, Lang::get('enter_db_user'), 'phpci'); + } + + if (!$dbPass = $input->getOption('db-pass')) { + $dbPass = $dialog->askHiddenResponse($output, Lang::get('enter_db_pass')); + } + + $db['servers']['read'] = $dbHost; + $db['servers']['write'] = $dbHost; + $db['name'] = $dbName; + $db['username'] = $dbUser; + $db['password'] = $dbPass; + + return $db; + } + + /** + * Try and connect to MySQL using the details provided. + * @param array $db + * @param OutputInterface $output + * @return bool + */ + protected function verifyDatabaseDetails(array $db, OutputInterface $output) + { + try { + $pdo = new PDO( + 'mysql:host='.$db['servers']['write'].';dbname='.$db['name'], + $db['username'], + $db['password'], + array( + \PDO::ATTR_PERSISTENT => false, + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::ATTR_TIMEOUT => 2, + \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'', + ) + ); + + unset($pdo); + + return true; + + } catch (Exception $ex) { + $output->writeln(''.Lang::get('could_not_connect').''); + $output->writeln('' . $ex->getMessage() . ''); + } + + return false; + } + + /** + * Write the PHPCI config.yml file. + * @param array $config + */ + protected function writeConfigFile(array $config) + { + $dumper = new \Symfony\Component\Yaml\Dumper(); + $yaml = $dumper->dump($config, 4); + + file_put_contents($this->configFilePath, $yaml); + } + + protected function setupDatabase(OutputInterface $output) + { + $output->write(Lang::get('setting_up_db')); + + $phinxBinary = escapeshellarg(PHPCI_DIR . 'vendor/bin/phinx'); + $phinxScript = escapeshellarg(PHPCI_DIR . 'phinx.php'); + shell_exec($phinxBinary . ' migrate -c ' . $phinxScript); + + $output->writeln(''.Lang::get('ok').''); + } + + /** + * Create admin user using information loaded before. + * + * @param array $admin + * @param OutputInterface $output + */ + protected function createAdminUser($admin, $output) + { + try { + $this->reloadConfig(); + + $userStore = Factory::getStore('User'); + $userService = new UserService($userStore); + $userService->createUser($admin['name'], $admin['mail'], $admin['pass'], 1); + + $output->writeln(''.Lang::get('user_created').''); + } catch (\Exception $ex) { + $output->writeln(''.Lang::get('failed_to_create').''); + $output->writeln('' . $ex->getMessage() . ''); + } + } + + protected function reloadConfig() + { + $config = Config::getInstance(); + + if (file_exists($this->configFilePath)) { + $config->loadYaml($this->configFilePath); + } + } + + /** + * @param OutputInterface $output + * @return bool + */ + protected function verifyNotInstalled(OutputInterface $output) + { + if (file_exists($this->configFilePath)) { + $content = file_get_contents($this->configFilePath); + + if (!empty($content)) { + $output->writeln(''.Lang::get('config_exists').''); + $output->writeln(''.Lang::get('update_instead').''); + return false; } } - return $status; + return true; } } diff --git a/PHPCI/Command/PollCommand.php b/PHPCI/Command/PollCommand.php new file mode 100644 index 00000000..a8729515 --- /dev/null +++ b/PHPCI/Command/PollCommand.php @@ -0,0 +1,105 @@ + + * @package PHPCI + * @subpackage Console + */ +class PollCommand extends Command +{ + /** + * @var \Monolog\Logger + */ + protected $logger; + + public function __construct(Logger $logger, $name = null) + { + parent::__construct($name); + $this->logger = $logger; + } + + protected function configure() + { + $this + ->setName('phpci:poll-github') + ->setDescription(Lang::get('poll_github')); + } + + /** + * Pulls all pending builds from the database and runs them. + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $parser = new Parser(); + $yaml = file_get_contents(APPLICATION_PATH . 'PHPCI/config.yml'); + $this->settings = $parser->parse($yaml); + + $token = $this->settings['phpci']['github']['token']; + + if (!$token) { + $this->logger->error(Lang::get('no_token')); + return; + } + + $buildStore = Factory::getStore('Build'); + + $this->logger->addInfo(Lang::get('finding_projects')); + $projectStore = Factory::getStore('Project'); + $result = $projectStore->getWhere(); + $this->logger->addInfo(Lang::get('found_n_projects', count($result['items']))); + + foreach ($result['items'] as $project) { + $http = new HttpClient('https://api.github.com'); + $commits = $http->get('/repos/' . $project->getReference() . '/commits', array('access_token' => $token)); + + $last_commit = $commits['body'][0]['sha']; + $last_committer = $commits['body'][0]['commit']['committer']['email']; + $message = $commits['body'][0]['commit']['message']; + + $this->logger->info(Lang::get('last_commit_is', $project->getTitle(), $last_commit)); + + if ($project->getLastCommit() != $last_commit && $last_commit != "") { + $this->logger->info( + Lang::get('adding_new_build') + ); + + $build = new Build(); + $build->setProjectId($project->getId()); + $build->setCommitId($last_commit); + $build->setStatus(Build::STATUS_NEW); + $build->setBranch($project->getBranch()); + $build->setCreated(new \DateTime()); + $build->setCommitMessage($message); + if (!empty($last_committer)) { + $build->setCommitterEmail($last_committer); + } + $buildStore->save($build); + + $project->setLastCommit($last_commit); + $projectStore->save($project); + } + } + + $this->logger->addInfo(Lang::get('finished_processing_builds')); + } +} diff --git a/PHPCI/Command/RebuildCommand.php b/PHPCI/Command/RebuildCommand.php new file mode 100644 index 00000000..4c24b359 --- /dev/null +++ b/PHPCI/Command/RebuildCommand.php @@ -0,0 +1,93 @@ + +* @package PHPCI +* @subpackage Console +*/ +class RebuildCommand extends Command +{ + /** + * @var Logger + */ + protected $logger; + + /** + * @var OutputInterface + */ + protected $output; + + /** + * @var boolean + */ + protected $run; + + /** + * @var int + */ + protected $sleep; + + /** + * @param \Monolog\Logger $logger + * @param string $name + */ + public function __construct(Logger $logger, $name = null) + { + parent::__construct($name); + $this->logger = $logger; + } + + protected function configure() + { + $this + ->setName('phpci:rebuild') + ->setDescription('Re-runs the last run build.'); + } + + /** + * Loops through running. + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $runner = new RunCommand($this->logger); + $runner->setMaxBuilds(1); + $runner->setDaemon(false); + + /** @var \PHPCI\Store\BuildStore $store */ + $store = Factory::getStore('Build'); + $service = new BuildService($store); + + $builds = $store->getLatestBuilds(null, 1); + $lastBuild = array_shift($builds); + $service->createDuplicateBuild($lastBuild); + + $runner->run(new ArgvInput(array()), $output); + } + + /** + * Called when log entries are made in Builder / the plugins. + * @see \PHPCI\Builder::log() + */ + public function logCallback($log) + { + $this->output->writeln($log); + } +} diff --git a/PHPCI/Command/RebuildQueueCommand.php b/PHPCI/Command/RebuildQueueCommand.php new file mode 100644 index 00000000..4b0af6d8 --- /dev/null +++ b/PHPCI/Command/RebuildQueueCommand.php @@ -0,0 +1,85 @@ + +* @package PHPCI +* @subpackage Console +*/ +class RebuildQueueCommand extends Command +{ + /** + * @var OutputInterface + */ + protected $output; + + /** + * @var Logger + */ + protected $logger; + + /** + * @param \Monolog\Logger $logger + * @param string $name + */ + public function __construct(Logger $logger, $name = null) + { + parent::__construct($name); + $this->logger = $logger; + } + + protected function configure() + { + $this + ->setName('phpci:rebuild-queue') + ->setDescription('Rebuilds the PHPCI worker queue.'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->output = $output; + + // For verbose mode we want to output all informational and above + // messages to the symphony output interface. + if ($input->hasOption('verbose') && $input->getOption('verbose')) { + $this->logger->pushHandler( + new OutputLogHandler($this->output, Logger::INFO) + ); + } + + $store = Factory::getStore('Build'); + $result = $store->getByStatus(0); + + $this->logger->addInfo(Lang::get('found_n_builds', count($result['items']))); + + $buildService = new BuildService($store); + + while (count($result['items'])) { + $build = array_shift($result['items']); + $build = BuildFactory::getBuild($build); + + $this->logger->addInfo('Added build #' . $build->getId() . ' to queue.'); + $buildService->addBuildToQueue($build); + } + } +} diff --git a/PHPCI/Command/RunCommand.php b/PHPCI/Command/RunCommand.php index 94d191d3..c2c352e6 100644 --- a/PHPCI/Command/RunCommand.php +++ b/PHPCI/Command/RunCommand.php @@ -1,22 +1,27 @@ logger = $logger; + } + protected function configure() { $this ->setName('phpci:run-builds') - ->setDescription('Run all pending PHPCI builds.'); + ->setDescription(Lang::get('run_all_pending')); } /** - * Pulls all pending builds from the database and runs them. - */ + * Pulls all pending builds from the database and runs them. + */ protected function execute(InputInterface $input, OutputInterface $output) { $this->output = $output; - $store = Factory::getStore('Build'); - $result = $store->getByStatus(0); + // For verbose mode we want to output all informational and above + // messages to the symphony output interface. + if ($input->hasOption('verbose') && $input->getOption('verbose')) { + $this->logger->pushHandler( + new OutputLogHandler($this->output, Logger::INFO) + ); + } + + $running = $this->validateRunningBuilds(); + + $this->logger->pushProcessor(new LoggedBuildContextTidier()); + $this->logger->addInfo(Lang::get('finding_builds')); + $store = Factory::getStore('Build'); + $result = $store->getByStatus(0, $this->maxBuilds); + $this->logger->addInfo(Lang::get('found_n_builds', count($result['items']))); + $builds = 0; - foreach ($result['items'] as $build) { - $builds++; - + while (count($result['items'])) { + $build = array_shift($result['items']); $build = BuildFactory::getBuild($build); - if ($input->getOption('verbose')) { - $builder = new Builder($build, array($this, 'logCallback')); - } else { - $builder = new Builder($build, function () { - // Empty stub function. - }); + // Skip build (for now) if there's already a build running in that project: + if (!$this->isFromDaemon && in_array($build->getProjectId(), $running)) { + $this->logger->addInfo(Lang::get('skipping_build', $build->getId())); + $result['items'][] = $build; + + // Re-run build validator: + $running = $this->validateRunningBuilds(); + continue; + } + + $builds++; + + try { + // Logging relevant to this build should be stored + // against the build itself. + $buildDbLog = new BuildDBLogHandler($build, Logger::INFO); + $this->logger->pushHandler($buildDbLog); + + $builder = new Builder($build, $this->logger); + $builder->execute(); + + // After execution we no longer want to record the information + // back to this specific build so the handler should be removed. + $this->logger->popHandler($buildDbLog); + } catch (\Exception $ex) { + $build->setStatus(Build::STATUS_FAILED); + $build->setFinished(new \DateTime()); + $build->setLog($build->getLog() . PHP_EOL . PHP_EOL . $ex->getMessage()); + $store->save($build); } - $builder->execute(); } + $this->logger->addInfo(Lang::get('finished_processing_builds')); + return $builds; } - /** - * Called when log entries are made in Builder / the plugins. - * @see \PHPCI\Builder::log() - */ - public function logCallback($log) + public function setMaxBuilds($numBuilds) { - $this->output->writeln($log); + $this->maxBuilds = (int)$numBuilds; + } + + public function setDaemon($fromDaemon) + { + $this->isFromDaemon = (bool)$fromDaemon; + } + + protected function validateRunningBuilds() + { + /** @var \PHPCI\Store\BuildStore $store */ + $store = Factory::getStore('Build'); + $running = $store->getByStatus(1); + $rtn = array(); + + $timeout = Config::getInstance()->get('phpci.build.failed_after', 1800); + + foreach ($running['items'] as $build) { + /** @var \PHPCI\Model\Build $build */ + $build = BuildFactory::getBuild($build); + + $now = time(); + $start = $build->getStarted()->getTimestamp(); + + if (($now - $start) > $timeout) { + $this->logger->addInfo(Lang::get('marked_as_failed', $build->getId())); + $build->setStatus(Build::STATUS_FAILED); + $build->setFinished(new \DateTime()); + $store->save($build); + $build->removeBuildDirectory(); + continue; + } + + $rtn[$build->getProjectId()] = true; + } + + return $rtn; } } diff --git a/PHPCI/Command/UpdateCommand.php b/PHPCI/Command/UpdateCommand.php index e5806025..13e31f79 100644 --- a/PHPCI/Command/UpdateCommand.php +++ b/PHPCI/Command/UpdateCommand.php @@ -2,17 +2,18 @@ /** * PHPCI - Continuous Integration for PHP * - * @copyright Copyright 2013, Block 8 Limited. + * @copyright Copyright 2014, Block 8 Limited. * @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md - * @link http://www.phptesting.org/ + * @link https://www.phptesting.org/ */ namespace PHPCI\Command; +use b8\Config; +use Monolog\Logger; +use PHPCI\Helper\Lang; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** @@ -23,11 +24,22 @@ use Symfony\Component\Console\Output\OutputInterface; */ class UpdateCommand extends Command { + /** + * @var \Monolog\Logger + */ + protected $logger; + + public function __construct(Logger $logger, $name = null) + { + parent::__construct($name); + $this->logger = $logger; + } + protected function configure() { $this ->setName('phpci:update') - ->setDescription('Update the database to reflect modified models.'); + ->setDescription(Lang::get('update_phpci')); } /** @@ -35,8 +47,22 @@ class UpdateCommand extends Command */ protected function execute(InputInterface $input, OutputInterface $output) { - // Update the database: - $gen = new \b8\Database\Generator(\b8\Database::getConnection(), 'PHPCI', './PHPCI/Model/Base/'); - $gen->generate(); + if (!$this->verifyInstalled($output)) { + return; + } + + $output->write(Lang::get('updating_phpci')); + + shell_exec(PHPCI_DIR . 'vendor/bin/phinx migrate -c "' . PHPCI_DIR . 'phinx.php"'); + + $output->writeln(''.Lang::get('ok').''); + } + + protected function verifyInstalled(OutputInterface $output) + { + $config = Config::getInstance(); + $phpciUrl = $config->get('phpci.url'); + + return !empty($phpciUrl); } } diff --git a/PHPCI/Command/WorkerCommand.php b/PHPCI/Command/WorkerCommand.php new file mode 100644 index 00000000..5ceb6a84 --- /dev/null +++ b/PHPCI/Command/WorkerCommand.php @@ -0,0 +1,87 @@ + +* @package PHPCI +* @subpackage Console +*/ +class WorkerCommand extends Command +{ + /** + * @var OutputInterface + */ + protected $output; + + /** + * @var Logger + */ + protected $logger; + + /** + * @param \Monolog\Logger $logger + * @param string $name + */ + public function __construct(Logger $logger, $name = null) + { + parent::__construct($name); + $this->logger = $logger; + } + + protected function configure() + { + $this + ->setName('phpci:worker') + ->setDescription('Runs the PHPCI build worker.') + ->addOption('debug', null, null, 'Run PHPCI in Debug Mode'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->output = $output; + + // For verbose mode we want to output all informational and above + // messages to the symphony output interface. + if ($input->hasOption('verbose') && $input->getOption('verbose')) { + $this->logger->pushHandler( + new OutputLogHandler($this->output, Logger::INFO) + ); + } + + // Allow PHPCI to run in "debug mode" + if ($input->hasOption('debug') && $input->getOption('debug')) { + $output->writeln('Debug mode enabled.'); + define('PHPCI_DEBUG_MODE', true); + } + + $config = Config::getInstance()->get('phpci.worker', []); + + if (empty($config['host']) || empty($config['queue'])) { + $error = 'The worker is not configured. You must set a host and queue in your config.yml file.'; + throw new \Exception($error); + } + + $worker = new BuildWorker($config['host'], $config['queue']); + $worker->setLogger($this->logger); + $worker->setMaxJobs(Config::getInstance()->get('phpci.worker.max_jobs', -1)); + $worker->startWorker(); + } +} diff --git a/PHPCI/Controller.php b/PHPCI/Controller.php index d95c24e0..68b1c845 100644 --- a/PHPCI/Controller.php +++ b/PHPCI/Controller.php @@ -1,12 +1,24 @@ setControllerView(); } + /** + * Set the view that this controller should use. + */ protected function setControllerView() { if (View::exists($this->className)) { $this->controllerView = new View($this->className); } else { - $this->controllerView = new View\UserView('{@content}'); + $this->controllerView = new View\Template('{@content}'); } } + /** + * Set the view that this controller action should use. + * @param $action + */ protected function setView($action) { if (View::exists($this->className . '/' . $action)) { @@ -49,11 +81,21 @@ class Controller extends \b8\Controller } } + /** + * Handle the incoming request. + * @param $action + * @param $actionParams + * @return \b8\b8\Http\Response|Response + */ public function handleAction($action, $actionParams) { $this->setView($action); $response = parent::handleAction($action, $actionParams); + if ($response instanceof Response) { + return $response; + } + if (is_string($response)) { $this->controllerView->content = $response; } elseif (isset($this->view)) { @@ -64,4 +106,24 @@ class Controller extends \b8\Controller return $this->response; } + + /** + * Require that the currently logged in user is an administrator. + * @throws ForbiddenException + */ + protected function requireAdmin() + { + if (!$this->currentUserIsAdmin()) { + throw new ForbiddenException('You do not have permission to do that.'); + } + } + + /** + * Check if the currently logged in user is an administrator. + * @return bool + */ + protected function currentUserIsAdmin() + { + return $_SESSION['phpci_user']->getIsAdmin(); + } } diff --git a/PHPCI/Controller/BitbucketController.php b/PHPCI/Controller/BitbucketController.php deleted file mode 100644 index 9ec4bb39..00000000 --- a/PHPCI/Controller/BitbucketController.php +++ /dev/null @@ -1,67 +0,0 @@ - -* @package PHPCI -* @subpackage Web -*/ -class BitbucketController extends \PHPCI\Controller -{ - /** - * @var \PHPCI\Store\BuildStore - */ - protected $buildStore; - - public function init() - { - $this->buildStore = Store\Factory::getStore('Build'); - } - - /** - * Called by Bitbucket POST service. - */ - public function webhook($project) - { - $payload = json_decode($this->getParam('payload'), true); - $branches = array(); - $commits = array(); - - foreach ($payload['commits'] as $commit) { - if (!in_array($commit['branch'], $branches)) { - $branches[] = $commit['branch']; - $commits[$commit['branch']] = $commit['raw_node']; - } - } - - foreach ($branches as $branch) { - try { - - $build = new Build(); - $build->setProjectId($project); - $build->setCommitId($commits[$branch]); - $build->setStatus(0); - $build->setLog(''); - $build->setCreated(new \DateTime()); - $build->setBranch($branch); - $this->buildStore->save($build); - } catch (\Exception $ex) { - } - } - - die('OK'); - } -} diff --git a/PHPCI/Controller/BuildController.php b/PHPCI/Controller/BuildController.php index 8c183774..55ccbb37 100644 --- a/PHPCI/Controller/BuildController.php +++ b/PHPCI/Controller/BuildController.php @@ -1,16 +1,23 @@ buildStore = b8\Store\Factory::getStore('Build'); + $this->buildStore = b8\Store\Factory::getStore('Build'); + $this->buildService = new BuildService($this->buildStore); } /** @@ -35,12 +51,60 @@ class BuildController extends \PHPCI\Controller */ public function view($buildId) { - $build = $this->buildStore->getById($buildId); + try { + $build = BuildFactory::getBuildById($buildId); + } catch (\Exception $ex) { + $build = null; + } + + if (empty($build)) { + throw new NotFoundException(Lang::get('build_x_not_found', $buildId)); + } + $this->view->plugins = $this->getUiPlugins(); $this->view->build = $build; $this->view->data = $this->getBuildData($build); + + $this->layout->title = Lang::get('build_n', $buildId); + $this->layout->subtitle = $build->getProjectTitle(); + + switch ($build->getStatus()) { + case 0: + $this->layout->skin = 'blue'; + break; + + case 1: + $this->layout->skin = 'yellow'; + break; + + case 2: + $this->layout->skin = 'green'; + break; + + case 3: + $this->layout->skin = 'red'; + break; + } + + $rebuild = Lang::get('rebuild_now'); + $rebuildLink = PHPCI_URL . 'build/rebuild/' . $build->getId(); + + $delete = Lang::get('delete_build'); + $deleteLink = PHPCI_URL . 'build/delete/' . $build->getId(); + + $actions = "{$rebuild} "; + + if ($this->currentUserIsAdmin()) { + $actions .= " {$delete}"; + } + + $this->layout->actions = $actions; } + /** + * Returns an array of the JS plugins to include. + * @return array + */ protected function getUiPlugins() { $rtn = array(); @@ -63,7 +127,17 @@ class BuildController extends \PHPCI\Controller */ public function data($buildId) { - die($this->getBuildData($this->buildStore->getById($buildId))); + $response = new JsonResponse(); + $build = BuildFactory::getBuildById($buildId); + + if (!$build) { + $response->setResponseCode(404); + $response->setContent(array()); + return $response; + } + + $response->setContent($this->getBuildData($build)); + return $response; } /** @@ -71,32 +145,46 @@ class BuildController extends \PHPCI\Controller */ public function meta($buildId) { - $build = $this->buildStore->getById($buildId); + $build = BuildFactory::getBuildById($buildId); $key = $this->getParam('key', null); $numBuilds = $this->getParam('num_builds', 1); $data = null; if ($key && $build) { - $data = $this->buildStore->getMeta($key, $build->getProjectId(), $buildId, $numBuilds); + $data = $this->buildStore->getMeta($key, $build->getProjectId(), $buildId, $build->getBranch(), $numBuilds); } - die(json_encode($data)); + $response = new JsonResponse(); + $response->setContent($data); + return $response; } /** * Get build data from database and json encode it: */ - protected function getBuildData($build) + protected function getBuildData(Build $build) { $data = array(); $data['status'] = (int)$build->getStatus(); $data['log'] = $this->cleanLog($build->getLog()); - $data['plugins'] = json_decode($build->getPlugins(), true); $data['created'] = !is_null($build->getCreated()) ? $build->getCreated()->format('Y-m-d H:i:s') : null; $data['started'] = !is_null($build->getStarted()) ? $build->getStarted()->format('Y-m-d H:i:s') : null; $data['finished'] = !is_null($build->getFinished()) ? $build->getFinished()->format('Y-m-d H:i:s') : null; + $data['duration'] = $build->getDuration(); - return json_encode($data); + /** @var \PHPCI\Store\BuildErrorStore $errorStore */ + $errorStore = b8\Store\Factory::getStore('BuildError'); + $errors = $errorStore->getErrorsForBuild($build->getId(), $this->getParam('since', null)); + + $errorView = new b8\View('Build/errors'); + $errorView->build = $build; + $errorView->errors = $errors; + + $data['errors'] = $errorStore->getErrorTotalForBuild($build->getId()); + $data['error_html'] = $errorView->render(); + $data['since'] = (new \DateTime())->format('Y-m-d H:i:s'); + + return $data; } /** @@ -104,19 +192,21 @@ class BuildController extends \PHPCI\Controller */ public function rebuild($buildId) { - $copy = $this->buildStore->getById($buildId); + $copy = BuildFactory::getBuildById($buildId); - $build = new Build(); - $build->setProjectId($copy->getProjectId()); - $build->setCommitId($copy->getCommitId()); - $build->setStatus(0); - $build->setBranch($copy->getBranch()); - $build->setCreated(new \DateTime()); + if (empty($copy)) { + throw new NotFoundException(Lang::get('build_x_not_found', $buildId)); + } - $build = $this->buildStore->save($build); + $build = $this->buildService->createDuplicateBuild($copy); - header('Location: '.PHPCI_URL.'build/view/' . $build->getId()); - exit; + if ($this->buildService->queueError) { + $_SESSION['global_error'] = Lang::get('add_to_queue_failed'); + } + + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', PHPCI_URL.'build/view/' . $build->getId()); + return $response; } /** @@ -124,15 +214,19 @@ class BuildController extends \PHPCI\Controller */ public function delete($buildId) { - if (empty($_SESSION['user']) || !$_SESSION['user']->getIsAdmin()) { - throw new \Exception('You do not have permission to do that.'); + $this->requireAdmin(); + + $build = BuildFactory::getBuildById($buildId); + + if (empty($build)) { + throw new NotFoundException(Lang::get('build_x_not_found', $buildId)); } - $build = $this->buildStore->getById($buildId); - $this->buildStore->delete($build); + $this->buildService->deleteBuild($build); - header('Location: '.PHPCI_URL.'project/view/' . $build->getProjectId()); - exit; + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', PHPCI_URL.'project/view/' . $build->getProjectId()); + return $response; } /** @@ -140,10 +234,46 @@ class BuildController extends \PHPCI\Controller */ protected function cleanLog($log) { - $log = str_replace('[0;32m', '', $log); - $log = str_replace('[0;31m', '', $log); - $log = str_replace('[0m', '', $log); + return AnsiConverter::convert($log); + } - return $log; + /** + * Allows the UI to poll for the latest running and pending builds. + */ + public function latest() + { + $rtn = array( + 'pending' => $this->formatBuilds($this->buildStore->getByStatus(Build::STATUS_NEW)), + 'running' => $this->formatBuilds($this->buildStore->getByStatus(Build::STATUS_RUNNING)), + ); + + $response = new JsonResponse(); + $response->setContent($rtn); + return $response; + } + + /** + * Formats a list of builds into rows suitable for the dropdowns in the PHPCI header bar. + * @param $builds + * @return array + */ + protected function formatBuilds($builds) + { + Project::$sleepable = array('id', 'title', 'reference', 'type'); + + $rtn = array('count' => $builds['count'], 'items' => array()); + + foreach ($builds['items'] as $build) { + $item = $build->toArray(1); + + $header = new b8\View('Build/header-row'); + $header->build = $build; + + $item['header_row'] = $header->render(); + $rtn['items'][$item['id']] = $item; + } + + ksort($rtn['items']); + return $rtn; } } diff --git a/PHPCI/Controller/BuildStatusController.php b/PHPCI/Controller/BuildStatusController.php index bc5c188d..62cb9ba7 100644 --- a/PHPCI/Controller/BuildStatusController.php +++ b/PHPCI/Controller/BuildStatusController.php @@ -1,18 +1,21 @@ projectStore = Store\Factory::getStore('Project'); + $this->response->disableLayout(); + $this->buildStore = Store\Factory::getStore('Build'); + $this->projectStore = Store\Factory::getStore('Project'); } /** - * Returns the appropriate build status image for a given project. + * Returns status of the last build + * @param $projectId + * @return string + */ + protected function getStatus($projectId) + { + $branch = $this->getParam('branch', 'master'); + try { + $project = $this->projectStore->getById($projectId); + $status = 'passing'; + + if (!$project->getAllowPublicStatus()) { + return null; + } + + if (isset($project) && $project instanceof Project) { + $build = $project->getLatestBuild($branch, array(2,3)); + + if (isset($build) && $build instanceof Build && $build->getStatus() != 2) { + $status = 'failed'; + } + } + } catch (\Exception $e) { + $status = 'error'; + } + + return $status; + } + + /** + * Displays projects information in ccmenu format + * + * @param $projectId + * @return bool + * @throws \Exception + * @throws b8\Exception\HttpException + */ + public function ccxml($projectId) + { + /* @var Project $project */ + $project = $this->projectStore->getById($projectId); + $xml = new \SimpleXMLElement(''); + + if (!$project instanceof Project || !$project->getAllowPublicStatus()) { + return $this->renderXml($xml); + } + + try { + $branchList = $this->buildStore->getBuildBranches($projectId); + + if (!$branchList) { + $branchList = array($project->getBranch()); + } + + foreach ($branchList as $branch) { + $buildStatusService = new BuildStatusService($branch, $project, $project->getLatestBuild($branch)); + if ($attributes = $buildStatusService->toArray()) { + $projectXml = $xml->addChild('Project'); + foreach ($attributes as $attributeKey => $attributeValue) { + $projectXml->addAttribute($attributeKey, $attributeValue); + } + } + } + } catch (\Exception $e) { + $xml = new \SimpleXMLElement(''); + } + + return $this->renderXml($xml); + } + + /** + * @param \SimpleXMLElement $xml + * @return bool + */ + protected function renderXml(\SimpleXMLElement $xml = null) + { + $this->response->setHeader('Content-Type', 'text/xml'); + $this->response->setContent($xml->asXML()); + $this->response->flush(); + echo $xml->asXML(); + + return true; + } + + /** + * Returns the appropriate build status image in SVG format for a given project. */ public function image($projectId) { - $branch = $this->getParam('branch', 'master'); - $project = $this->projectStore->getById($projectId); - $status = 'ok'; + $style = $this->getParam('style', 'plastic'); + $label = $this->getParam('label', 'build'); - if (isset($project) && $project instanceof Project) { - $build = $project->getLatestBuild($branch, array(2,3)); + $status = $this->getStatus($projectId); - if (isset($build) && $build instanceof Build && $build->getStatus() != 2) { - $status = 'failed'; - } + if (is_null($status)) { + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', '/'); + return $response; } - header('Content-Type: image/png'); - die(file_get_contents(APPLICATION_PATH . 'public/assets/img/build-' . $status . '.png')); + $color = ($status == 'passing') ? 'green' : 'red'; + $image = file_get_contents(sprintf( + 'http://img.shields.io/badge/%s-%s-%s.svg?style=%s', + $label, + $status, + $color, + $style + )); + + $this->response->disableLayout(); + $this->response->setHeader('Content-Type', 'image/svg+xml'); + $this->response->setContent($image); + return $this->response; + } + + /** + * View the public status page of a given project, if enabled. + * @param $projectId + * @return string + * @throws \b8\Exception\HttpException\NotFoundException + */ + public function view($projectId) + { + $project = $this->projectStore->getById($projectId); + + if (empty($project)) { + throw new NotFoundException('Project with id: ' . $projectId . ' not found'); + } + + if (!$project->getAllowPublicStatus()) { + throw new NotFoundException('Project with id: ' . $projectId . ' not found'); + } + + $builds = $this->getLatestBuilds($projectId); + + if (count($builds)) { + $this->view->latest = $builds[0]; + } + + $this->view->builds = $builds; + $this->view->project = $project; + + return $this->view->render(); + } + + /** + * Render latest builds for project as HTML table. + */ + protected function getLatestBuilds($projectId) + { + $criteria = array('project_id' => $projectId); + $order = array('id' => 'DESC'); + $builds = $this->buildStore->getWhere($criteria, 10, 0, array(), $order); + + foreach ($builds['items'] as &$build) { + $build = BuildFactory::getBuild($build); + } + + return $builds['items']; } } diff --git a/PHPCI/Controller/GithubController.php b/PHPCI/Controller/GithubController.php deleted file mode 100644 index b616cbb4..00000000 --- a/PHPCI/Controller/GithubController.php +++ /dev/null @@ -1,77 +0,0 @@ - -* @package PHPCI -* @subpackage Web -*/ -class GithubController extends \PHPCI\Controller -{ - /** - * @var \PHPCI\Store\BuildStore - */ - protected $buildStore; - - public function init() - { - $this->buildStore = Store\Factory::getStore('Build'); - } - - /** - * Called by Github Webhooks: - */ - public function webhook($project) - { - $payload = json_decode($this->getParam('payload'), true); - - // Github sends a payload when you close a pull request with a - // non-existant commit. We don't want this. - if ($payload['after'] === '0000000000000000000000000000000000000000') { - die('OK'); - } - - try { - $build = new Build(); - $build->setProjectId($project); - $build->setCommitId($payload['after']); - $build->setStatus(0); - $build->setLog(''); - $build->setCreated(new \DateTime()); - $build->setBranch(str_replace('refs/heads/', '', $payload['ref'])); - - if (!empty($payload['pusher']['email'])) { - $build->setCommitterEmail($payload['pusher']['email']); - } - - } catch (\Exception $ex) { - header('HTTP/1.1 400 Bad Request'); - header('Ex: ' . $ex->getMessage()); - die('FAIL'); - } - - try { - $build = $this->buildStore->save($build); - $build->sendStatusPostback(); - } catch (\Exception $ex) { - header('HTTP/1.1 500 Internal Server Error'); - header('Ex: ' . $ex->getMessage()); - die('FAIL'); - } - - die('OK'); - } -} diff --git a/PHPCI/Controller/GitlabController.php b/PHPCI/Controller/GitlabController.php deleted file mode 100644 index 020b6346..00000000 --- a/PHPCI/Controller/GitlabController.php +++ /dev/null @@ -1,66 +0,0 @@ -, Dan Cryer -* @package PHPCI -* @subpackage Web -*/ -class GitlabController extends \PHPCI\Controller -{ - /** - * @var \PHPCI\Store\BuildStore - */ - protected $buildStore; - - public function init() - { - $this->buildStore = Store\Factory::getStore('Build'); - } - - /** - * Called by Gitlab Webhooks: - */ - public function webhook($project) - { - $payload = json_decode(file_get_contents("php://input"), true); - - try { - $build = new Build(); - $build->setProjectId($project); - $build->setCommitId($payload['after']); - $build->setStatus(0); - $build->setLog(''); - $build->setCreated(new \DateTime()); - $build->setBranch(str_replace('refs/heads/', '', $payload['ref'])); - } catch (\Exception $ex) { - header('HTTP/1.1 400 Bad Request'); - header('Ex: ' . $ex->getMessage()); - die('FAIL'); - } - - try { - $build = $this->buildStore->save($build); - $build->sendStatusPostback(); - } catch (\Exception $ex) { - header('HTTP/1.1 500 Internal Server Error'); - header('Ex: ' . $ex->getMessage()); - die('FAIL'); - } - - die('OK'); - } -} diff --git a/PHPCI/Controller/GroupController.php b/PHPCI/Controller/GroupController.php new file mode 100644 index 00000000..898f9e41 --- /dev/null +++ b/PHPCI/Controller/GroupController.php @@ -0,0 +1,120 @@ + + * @package PHPCI + * @subpackage Web + */ +class GroupController extends Controller +{ + /** + * @var \PHPCI\Store\ProjectGroupStore + */ + protected $groupStore; + + /** + * Set up this controller. + */ + public function init() + { + $this->groupStore = b8\Store\Factory::getStore('ProjectGroup'); + } + + /** + * List project groups. + */ + public function index() + { + $this->requireAdmin(); + + $groups = array(); + $groupList = $this->groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC')); + + foreach ($groupList['items'] as $group) { + $thisGroup = array( + 'title' => $group->getTitle(), + 'id' => $group->getId(), + ); + $projects = b8\Store\Factory::getStore('Project')->getByGroupId($group->getId()); + $thisGroup['projects'] = $projects['items']; + $groups[] = $thisGroup; + } + + $this->view->groups = $groups; + } + + /** + * Add or edit a project group. + * @param null $groupId + * @return void|b8\Http\Response\RedirectResponse + */ + public function edit($groupId = null) + { + $this->requireAdmin(); + + if (!is_null($groupId)) { + $group = $this->groupStore->getById($groupId); + } else { + $group = new ProjectGroup(); + } + + if ($this->request->getMethod() == 'POST') { + $group->setTitle($this->getParam('title')); + $this->groupStore->save($group); + + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', PHPCI_URL.'group'); + return $response; + } + + $form = new Form(); + $form->setMethod('POST'); + $form->setAction(PHPCI_URL . 'group/edit' . (!is_null($groupId) ? '/' . $groupId : '')); + + $title = new Form\Element\Text('title'); + $title->setContainerClass('form-group'); + $title->setClass('form-control'); + $title->setLabel('Group Title'); + $title->setValue($group->getTitle()); + + $submit = new Form\Element\Submit(); + $submit->setValue('Save Group'); + + $form->addField($title); + $form->addField($submit); + + $this->view->form = $form; + } + + /** + * Delete a project group. + * @param $groupId + * @return b8\Http\Response\RedirectResponse + */ + public function delete($groupId) + { + $this->requireAdmin(); + $group = $this->groupStore->getById($groupId); + + $this->groupStore->delete($group); + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', PHPCI_URL.'group'); + return $response; + } +} diff --git a/PHPCI/Controller/HomeController.php b/PHPCI/Controller/HomeController.php index 586f46f1..4241d324 100644 --- a/PHPCI/Controller/HomeController.php +++ b/PHPCI/Controller/HomeController.php @@ -1,15 +1,18 @@ buildStore = b8\Store\Factory::getStore('Build'); $this->projectStore = b8\Store\Factory::getStore('Project'); + $this->groupStore = b8\Store\Factory::getStore('ProjectGroup'); } /** @@ -40,20 +52,15 @@ class HomeController extends \PHPCI\Controller */ public function index() { - $projects = $this->projectStore->getWhere(array(), 50, 0, array(), array('title' => 'ASC')); + $this->layout->title = Lang::get('dashboard'); + $builds = $this->buildStore->getLatestBuilds(null, 10); - $summaryBuilds = array(); - foreach ($projects['items'] as $project) { - $summaryBuilds[$project->getId()] = $this->buildStore->getLatestBuilds($project->getId()); + foreach ($builds as &$build) { + $build = BuildFactory::getBuild($build); } - $summaryView = new b8\View('SummaryTable'); - $summaryView->projects = $projects['items']; - $summaryView->builds = $summaryBuilds; - - $this->view->builds = $this->getLatestBuildsHtml(); - $this->view->projects = $projects['items']; - $this->view->summary = $summaryView->render(); + $this->view->builds = $builds; + $this->view->groups = $this->getGroupInfo(); return $this->view->render(); } @@ -63,7 +70,61 @@ class HomeController extends \PHPCI\Controller */ public function latest() { - die($this->getLatestBuildsHtml()); + $this->response->disableLayout(); + $this->response->setContent($this->getLatestBuildsHtml()); + return $this->response; + } + + /** + * Ajax request for the project overview section of the dashboard. + */ + public function summary() + { + $this->response->disableLayout(); + $projects = $this->projectStore->getWhere(array(), 50, 0, array(), array('title' => 'ASC')); + $this->response->setContent($this->getSummaryHtml($projects)); + return $this->response; + } + + /** + * Generate the HTML for the project overview section of the dashboard. + * @param $projects + * @return string + */ + protected function getSummaryHtml($projects) + { + $summaryBuilds = array(); + $successes = array(); + $failures = array(); + $counts = array(); + + foreach ($projects as $project) { + $summaryBuilds[$project->getId()] = $this->buildStore->getLatestBuilds($project->getId()); + + $count = $this->buildStore->getWhere( + array('project_id' => $project->getId()), + 1, + 0, + array(), + array('id' => 'DESC') + ); + $counts[$project->getId()] = $count['count']; + + $success = $this->buildStore->getLastBuildByStatus($project->getId(), Build::STATUS_SUCCESS); + $failure = $this->buildStore->getLastBuildByStatus($project->getId(), Build::STATUS_FAILED); + + $successes[$project->getId()] = $success; + $failures[$project->getId()] = $failure; + } + + $summaryView = new b8\View('SummaryTable'); + $summaryView->projects = $projects; + $summaryView->builds = $summaryBuilds; + $summaryView->successful = $successes; + $summaryView->failed = $failures; + $summaryView->counts = $counts; + + return $summaryView->render(); } /** @@ -73,8 +134,33 @@ class HomeController extends \PHPCI\Controller { $builds = $this->buildStore->getWhere(array(), 5, 0, array(), array('id' => 'DESC')); $view = new b8\View('BuildsTable'); + + foreach ($builds['items'] as &$build) { + $build = BuildFactory::getBuild($build); + } + $view->builds = $builds['items']; return $view->render(); } + + /** + * Get a summary of the project groups we have, and what projects they have in them. + * @return array + */ + protected function getGroupInfo() + { + $rtn = array(); + $groups = $this->groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC')); + + foreach ($groups['items'] as $group) { + $thisGroup = array('title' => $group->getTitle()); + $projects = $this->projectStore->getByGroupId($group->getId()); + $thisGroup['projects'] = $projects['items']; + $thisGroup['summary'] = $this->getSummaryHtml($thisGroup['projects']); + $rtn[] = $thisGroup; + } + + return $rtn; + } } diff --git a/PHPCI/Controller/PluginController.php b/PHPCI/Controller/PluginController.php index 22b25bfc..64cb1463 100644 --- a/PHPCI/Controller/PluginController.php +++ b/PHPCI/Controller/PluginController.php @@ -2,15 +2,18 @@ /** * PHPCI - Continuous Integration for PHP * - * @copyright Copyright 2013, Block 8 Limited. + * @copyright Copyright 2014, Block 8 Limited. * @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md - * @link http://www.phptesting.org/ + * @link https://www.phptesting.org/ */ namespace PHPCI\Controller; use b8; -use PHPCI\Model\Build; +use PHPCI\Helper\Lang; +use PHPCI\Plugin\Util\ComposerPluginInformation; +use PHPCI\Plugin\Util\FilesPluginInformation; +use PHPCI\Plugin\Util\PluginInformationCollection; /** * Plugin Controller - Provides support for installing Composer packages. @@ -20,154 +23,39 @@ use PHPCI\Model\Build; */ class PluginController extends \PHPCI\Controller { - protected $required = array( - 'block8/b8framework', - 'ircmaxell/password-compat', - 'swiftmailer/swiftmailer', - 'symfony/yaml', - 'symfony/console' - ); - - protected $canInstall; - protected $composerPath; - - public function init() - { - parent::init(); - $this->canInstall = function_exists('shell_exec'); - - if ($this->canInstall) { - $this->composerPath = $this->findBinary(array('composer', 'composer.phar')); - - if (!$this->composerPath) { - $this->canInstall = false; - } - } - } - + /** + * List all enabled plugins, installed and recommend packages. + * @return string + */ public function index() { - if (!$_SESSION['user']->getIsAdmin()) { - throw new \Exception('You do not have permission to do that.'); - } - - $this->view->canWrite = is_writable(APPLICATION_PATH . 'composer.json'); - $this->view->canInstall = $this->canInstall; - $this->view->required = $this->required; + $this->requireAdmin(); $json = $this->getComposerJson(); - $this->view->installed = $json['require']; - $this->view->suggested = $json['suggest']; + $this->view->installedPackages = $json['require']; + + $pluginInfo = new PluginInformationCollection(); + $pluginInfo->add(FilesPluginInformation::newFromDir( + PHPCI_DIR . "PHPCI/Plugin/" + )); + $pluginInfo->add(ComposerPluginInformation::buildFromYaml( + PHPCI_DIR . "vendor/composer/installed.json" + )); + + $this->view->plugins = $pluginInfo->getInstalledPlugins(); + + $this->layout->title = Lang::get('plugins'); return $this->view->render(); } - public function remove() - { - if (!$_SESSION['user']->getIsAdmin()) { - throw new \Exception('You do not have permission to do that.'); - } - - $package = $this->getParam('package', null); - $json = $this->getComposerJson(); - - if (!in_array($package, $this->required)) { - unset($json['require'][$package]); - $this->setComposerJson($json); - - if ($this->canInstall) { - shell_exec($this->composerPath . ' update --working-dir=' . APPLICATION_PATH . ' > /dev/null 2>&1 &'); - } - - header('Location: ' . PHPCI_URL . 'plugin?r=' . $package); - die; - } - - header('Location: ' . PHPCI_URL); - die; - } - - public function install() - { - if (!$_SESSION['user']->getIsAdmin()) { - throw new \Exception('You do not have permission to do that.'); - } - - $package = $this->getParam('package', null); - $version = $this->getParam('version', '*'); - - $json = $this->getComposerJson(); - $json['require'][$package] = $version; - $this->setComposerJson($json); - - if ($this->canInstall) { - shell_exec($this->composerPath . ' update --working-dir=' . APPLICATION_PATH . ' > /dev/null 2>&1 &'); - - header('Location: ' . PHPCI_URL . 'plugin?i=' . $package); - die; - } - - header('Location: ' . PHPCI_URL . 'plugin?w=' . $package); - die; - } - + /** + * Get the json-decoded contents of the composer.json file. + * @return mixed + */ protected function getComposerJson() { $json = file_get_contents(APPLICATION_PATH . 'composer.json'); return json_decode($json, true); } - - protected function setComposerJson($array) - { - $json = json_encode($array); - file_put_contents(APPLICATION_PATH . 'composer.json', $json); - } - - protected function findBinary($binary) - { - if (is_string($binary)) { - $binary = array($binary); - } - - foreach ($binary as $bin) { - // Check project root directory: - if (is_file(APPLICATION_PATH . $bin)) { - return APPLICATION_PATH . $bin; - } - - // Check Composer bin dir: - if (is_file(APPLICATION_PATH . 'vendor/bin/' . $bin)) { - return APPLICATION_PATH . 'vendor/bin/' . $bin; - } - - // Use "which" - $which = trim(shell_exec('which ' . $bin)); - - if (!empty($which)) { - return $which; - } - } - - return null; - } - - public function packagistSearch() - { - $searchQuery = $this->getParam('q', ''); - $http = new \b8\HttpClient(); - $http->setHeaders(array('User-Agent: PHPCI/1.0 (+http://www.phptesting.org)')); - $res = $http->get('https://packagist.org/search.json', array('q' => $searchQuery)); - - die(json_encode($res['body'])); - } - - public function packagistVersions() - { - $name = $this->getParam('p', ''); - $http = new \b8\HttpClient(); - $http->setHeaders(array('User-Agent: PHPCI/1.0 (+http://www.phptesting.org)')); - $res = $http->get('https://packagist.org/packages/'.$name.'.json'); - - die(json_encode($res['body'])); - } } diff --git a/PHPCI/Controller/ProjectController.php b/PHPCI/Controller/ProjectController.php index 1a4fc496..72be46e5 100644 --- a/PHPCI/Controller/ProjectController.php +++ b/PHPCI/Controller/ProjectController.php @@ -1,21 +1,25 @@ buildStore = Store\Factory::getStore('Build'); - $this->projectStore = Store\Factory::getStore('Project'); + $this->buildStore = Store\Factory::getStore('Build'); + $this->projectStore = Store\Factory::getStore('Project'); + $this->projectService = new ProjectService($this->projectStore); + $this->buildService = new BuildService($this->buildStore); } /** @@ -46,14 +65,34 @@ class ProjectController extends \PHPCI\Controller */ public function view($projectId) { - $project = $this->projectStore->getById($projectId); - $page = $this->getParam('p', 1); - $builds = $this->getLatestBuildsHtml($projectId, (($page - 1) * 10)); + $branch = $this->getParam('branch', ''); + $project = $this->projectStore->getById($projectId); + + if (empty($project)) { + throw new NotFoundException(Lang::get('project_x_not_found', $projectId)); + } + + $per_page = 10; + $page = $this->getParam('p', 1); + $builds = $this->getLatestBuildsHtml($projectId, urldecode($branch), (($page - 1) * $per_page)); + $pages = $builds[1] == 0 ? 1 : ceil($builds[1] / $per_page); + + if ($page > $pages) { + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', PHPCI_URL.'project/view/'.$projectId); + return $response; + } $this->view->builds = $builds[0]; $this->view->total = $builds[1]; $this->view->project = $project; + $this->view->branch = urldecode($branch); + $this->view->branches = $this->projectStore->getKnownBranches($projectId); $this->view->page = $page; + $this->view->pages = $pages; + + $this->layout->title = $project->getTitle(); + $this->layout->subtitle = $this->view->branch; return $this->view->render(); } @@ -61,22 +100,29 @@ class ProjectController extends \PHPCI\Controller /** * Create a new pending build for a project. */ - public function build($projectId) + public function build($projectId, $branch = '') { /* @var \PHPCI\Model\Project $project */ $project = $this->projectStore->getById($projectId); - $build = new Build(); - $build->setProjectId($projectId); - $build->setCommitId('Manual'); - $build->setStatus(0); - $build->setBranch($project->getType() === 'hg' ? 'default' : 'master'); - $build->setCreated(new \DateTime()); + if (empty($branch)) { + $branch = $project->getBranch(); + } - $build = $this->buildStore->save($build); + if (empty($project)) { + throw new NotFoundException(Lang::get('project_x_not_found', $projectId)); + } - header('Location: '.PHPCI_URL.'build/view/' . $build->getId()); - exit; + $email = $_SESSION['phpci_user']->getEmail(); + $build = $this->buildService->createBuild($project, null, urldecode($branch), $email); + + if ($this->buildService->queueError) { + $_SESSION['global_error'] = Lang::get('add_to_queue_failed'); + } + + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', PHPCI_URL.'build/view/' . $build->getId()); + return $response; } /** @@ -84,15 +130,14 @@ class ProjectController extends \PHPCI\Controller */ public function delete($projectId) { - if (!$_SESSION['user']->getIsAdmin()) { - throw new \Exception('You do not have permission to do that.'); - } + $this->requireAdmin(); $project = $this->projectStore->getById($projectId); - $this->projectStore->delete($project); + $this->projectService->deleteProject($project); - header('Location: '.PHPCI_URL); - exit; + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', PHPCI_URL); + return $response; } /** @@ -100,19 +145,37 @@ class ProjectController extends \PHPCI\Controller */ public function builds($projectId) { - $builds = $this->getLatestBuildsHtml($projectId); - die($builds[0]); + $branch = $this->getParam('branch', ''); + $builds = $this->getLatestBuildsHtml($projectId, urldecode($branch)); + + $this->response->disableLayout(); + $this->response->setContent($builds[0]); + return $this->response; } /** - * Render latest builds for project as HTML table. - */ - protected function getLatestBuildsHtml($projectId, $start = 0) + * Render latest builds for project as HTML table. + * + * @param $projectId + * @param string $branch A urldecoded branch name. + * @param int $start + * @return array + */ + protected function getLatestBuildsHtml($projectId, $branch = '', $start = 0) { - $criteria = array('project_id' => $projectId); - $order = array('id' => 'DESC'); - $builds = $this->buildStore->getWhere($criteria, 10, $start, array(), $order); - $view = new b8\View('BuildsTable'); + $criteria = array('project_id' => $projectId); + if (!empty($branch)) { + $criteria['branch'] = $branch; + } + + $order = array('id' => 'DESC'); + $builds = $this->buildStore->getWhere($criteria, 10, $start, array(), $order); + $view = new b8\View('BuildsTable'); + + foreach ($builds['items'] as &$build) { + $build = BuildFactory::getBuild($build); + } + $view->builds = $builds['items']; return array($view->render(), $builds['count']); @@ -123,38 +186,21 @@ class ProjectController extends \PHPCI\Controller */ public function add() { - if (!$_SESSION['user']->getIsAdmin()) { - throw new \Exception('You do not have permission to do that.'); - } + $this->layout->title = Lang::get('add_project'); + $this->requireAdmin(); $method = $this->request->getMethod(); - if ($method == 'POST') { - $values = $this->getParams(); - $pub = null; - } else { - $tempPath = sys_get_temp_dir() . '/'; + $pub = null; + $values = $this->getParams(); - // FastCGI fix for Windows machines, where temp path is not available to - // PHP, and defaults to the unwritable system directory. If the temp - // path is pointing to the system directory, shift to the 'TEMP' - // sub-folder, which should also exist, but actually be writable. - if ($tempPath == getenv("SystemRoot") . '/') { - $tempPath = getenv("SystemRoot") . '/TEMP/'; - } + if ($method != 'POST') { + $sshKey = new SshKey(); + $key = $sshKey->generate(); - $keyFile = $tempPath . md5(microtime(true)); - - if (!is_dir($tempPath)) { - mkdir($tempPath); - } - - shell_exec('ssh-keygen -q -t rsa -b 2048 -f '.$keyFile.' -N "" -C "deploy@phpci"'); - - $pub = file_get_contents($keyFile . '.pub'); - $prv = file_get_contents($keyFile); - - $values = array('key' => $prv, 'pubkey' => $pub); + $values['key'] = $key['private_key']; + $values['pubkey'] = $key['public_key']; + $pub = $key['public_key']; } $form = $this->projectForm($values); @@ -167,28 +213,26 @@ class ProjectController extends \PHPCI\Controller $view->key = $pub; return $view->render(); + } else { + $title = $this->getParam('title', 'New Project'); + $reference = $this->getParam('reference', null); + $type = $this->getParam('type', null); + + $options = array( + 'ssh_private_key' => $this->getParam('key', null), + 'ssh_public_key' => $this->getParam('pubkey', null), + 'build_config' => $this->getParam('build_config', null), + 'allow_public_status' => $this->getParam('allow_public_status', 0), + 'branch' => $this->getParam('branch', null), + 'group' => $this->getParam('group_id', null), + ); + + $project = $this->projectService->createProject($title, $type, $reference, $options); + + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', PHPCI_URL.'project/view/' . $project->getId()); + return $response; } - - $values = $form->getValues(); - - if ($values['type'] == "gitlab") { - preg_match('`^(.*)@(.*):(.*)/(.*)\.git`', $values['reference'], $matches); - $info = array(); - $info["user"] = $matches[1]; - $info["domain"] = $matches[2]; - $values['access_information'] = serialize($info); - $values['reference'] = $matches[3]."/".$matches[4]; - } - - $values['git_key'] = $values['key']; - - $project = new Project(); - $project->setValues($values); - - $project = $this->projectStore->save($project); - - header('Location: '.PHPCI_URL.'project/view/' . $project->getId()); - die; } /** @@ -196,56 +240,63 @@ class ProjectController extends \PHPCI\Controller */ public function edit($projectId) { - if (!$_SESSION['user']->getIsAdmin()) { - throw new \Exception('You do not have permission to do that.'); + $this->requireAdmin(); + + $method = $this->request->getMethod(); + $project = $this->projectStore->getById($projectId); + + if (empty($project)) { + throw new NotFoundException(Lang::get('project_x_not_found', $projectId)); } - $method = $this->request->getMethod(); - $project = $this->projectStore->getById($projectId); + $this->layout->title = $project->getTitle(); + $this->layout->subtitle = Lang::get('edit_project'); + + $values = $project->getDataArray(); + $values['key'] = $values['ssh_private_key']; + $values['pubkey'] = $values['ssh_public_key']; + + if ($values['type'] == "gitlab") { + $accessInfo = $project->getAccessInformation(); + $reference = $accessInfo["user"].'@'.$accessInfo["domain"].':' . $project->getReference().".git"; + $values['reference'] = $reference; + } if ($method == 'POST') { $values = $this->getParams(); - } else { - $values = $project->getDataArray(); - $values['key'] = $values['git_key']; - - if ($values['type'] == "gitlab") { - $accessInfo = $project->getAccessInformation(); - $reference = $accessInfo["user"].'@'.$accessInfo["domain"].':' . $project->getReference().".git"; - $values['reference'] = $reference; - } } - - $form = $this->projectForm($values, 'edit/' . $projectId); + $form = $this->projectForm($values, 'edit/' . $projectId); if ($method != 'POST' || ($method == 'POST' && !$form->validate())) { $view = new b8\View('ProjectForm'); $view->type = 'edit'; $view->project = $project; $view->form = $form; - $view->key = null; + $view->key = $values['pubkey']; return $view->render(); } - $values = $form->getValues(); - $values['git_key'] = $values['key']; + $title = $this->getParam('title', Lang::get('new_project')); + $reference = $this->getParam('reference', null); + $type = $this->getParam('type', null); - if ($values['type'] == "gitlab") { - preg_match('`^(.*)@(.*):(.*)/(.*)\.git`', $values['reference'], $matches); - $info = array(); - $info["user"] = $matches[1]; - $info["domain"] = $matches[2]; - $values['access_information'] = serialize($info); - $values['reference'] = $matches[3] . "/" . $matches[4]; - } + $options = array( + 'ssh_private_key' => $this->getParam('key', null), + 'ssh_public_key' => $this->getParam('pubkey', null), + 'build_config' => $this->getParam('build_config', null), + 'allow_public_status' => $this->getParam('allow_public_status', 0), + 'archived' => $this->getParam('archived', 0), + 'branch' => $this->getParam('branch', null), + 'group' => $this->getParam('group_id', null), + ); - $project->setValues($values); - $project = $this->projectStore->save($project); + $project = $this->projectService->updateProject($project, $title, $type, $reference, $options); - header('Location: '.PHPCI_URL.'project/view/' . $project->getId()); - die; + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', PHPCI_URL.'project/view/' . $project->getId()); + return $response; } /** @@ -260,60 +311,81 @@ class ProjectController extends \PHPCI\Controller $form->addField(new Form\Element\Hidden('pubkey')); $options = array( - 'choose' => 'Select repository type...', - 'github' => 'Github', - 'bitbucket' => 'Bitbucket', - 'gitlab' => 'Gitlab', - 'remote' => 'Remote URL', - 'local' => 'Local Path', - 'hg' => 'Mercurial', + 'choose' => Lang::get('select_repository_type'), + 'github' => Lang::get('github'), + 'bitbucket' => Lang::get('bitbucket'), + 'gitlab' => Lang::get('gitlab'), + 'remote' => Lang::get('remote'), + 'local' => Lang::get('local'), + 'hg' => Lang::get('hg'), + 'svn' => Lang::get('svn'), ); - $field = new Form\Element\Select('type'); - $field->setRequired(true); - $field->setPattern('^(github|bitbucket|gitlab|remote|local|hg)'); + $field = Form\Element\Select::create('type', Lang::get('where_hosted'), true); + $field->setPattern('^(github|bitbucket|gitlab|remote|local|hg|svn)'); $field->setOptions($options); - $field->setLabel('Where is your project hosted?'); - $field->setClass('form-control'); - $field->setContainerClass('form-group'); + $field->setClass('form-control')->setContainerClass('form-group'); $form->addField($field); - $container = new Form\ControlGroup('github-container'); $container->setClass('github-container'); - $field = new Form\Element\Select('github'); - $field->setLabel('Choose a Github repository:'); - $field->setClass('form-control'); - $field->setContainerClass('form-group'); + $field = Form\Element\Select::create('github', Lang::get('choose_github'), false); + $field->setClass('form-control')->setContainerClass('form-group'); $container->addField($field); $form->addField($container); - $field = new Form\Element\Text('reference'); - $field->setRequired(true); + $field = Form\Element\Text::create('reference', Lang::get('repo_name'), true); $field->setValidator($this->getReferenceValidator($values)); - $field->setLabel('Repository Name / URL (Remote) or Path (Local)'); - $field->setClass('form-control'); - $field->setContainerClass('form-group'); + $field->setClass('form-control')->setContainerClass('form-group'); $form->addField($field); - $field = new Form\Element\Text('title'); - $field->setRequired(true); - $field->setLabel('Project Title'); - $field->setClass('form-control'); - $field->setContainerClass('form-group'); + $field = Form\Element\Text::create('title', Lang::get('project_title'), true); + $field->setClass('form-control')->setContainerClass('form-group'); $form->addField($field); - $field = new Form\Element\TextArea('key'); - $field->setRequired(false); - $field->setLabel('Private key to use to access repository (leave blank for local and/or anonymous remotes)'); - $field->setClass('form-control'); - $field->setContainerClass('form-group'); + $field = Form\Element\TextArea::create('key', Lang::get('project_private_key'), false); + $field->setClass('form-control')->setContainerClass('form-group'); $field->setRows(6); $form->addField($field); + $field = Form\Element\TextArea::create('build_config', Lang::get('build_config'), false); + $field->setClass('form-control')->setContainerClass('form-group'); + $field->setRows(6); + $form->addField($field); + + $field = Form\Element\Text::create('branch', Lang::get('default_branch'), true); + $field->setClass('form-control')->setContainerClass('form-group')->setValue('master'); + $form->addField($field); + + $field = Form\Element\Select::create('group_id', 'Project Group', true); + $field->setClass('form-control')->setContainerClass('form-group')->setValue(1); + + $groups = array(); + $groupStore = b8\Store\Factory::getStore('ProjectGroup'); + $groupList = $groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC')); + + foreach ($groupList['items'] as $group) { + $groups[$group->getId()] = $group->getTitle(); + } + + $field->setOptions($groups); + $form->addField($field); + + $field = Form\Element\Checkbox::create('allow_public_status', Lang::get('allow_public_status'), false); + $field->setContainerClass('form-group'); + $field->setCheckedValue(1); + $field->setValue(0); + $form->addField($field); + + $field = Form\Element\Checkbox::create('archived', Lang::get('archived'), false); + $field->setContainerClass('form-group'); + $field->setCheckedValue(1); + $field->setValue(0); + $form->addField($field); + $field = new Form\Element\Submit(); - $field->setValue('Save Project'); + $field->setValue(Lang::get('save_project')); $field->setContainerClass('form-group'); $field->setClass('btn-success'); $form->addField($field); @@ -327,48 +399,18 @@ class ProjectController extends \PHPCI\Controller */ protected function githubRepositories() { - $token = Config::getInstance()->get('phpci.github.token'); + $github = new Github(); - if (!$token) { - die(json_encode(null)); - } - - $cache = \b8\Cache::getCache(\b8\Cache::TYPE_APC); - $rtn = $cache->get('phpci_github_repos'); - - if (!$rtn) { - $orgs = $this->doGithubApiRequest('/user/orgs', array('access_token' => $token)); - - $params = array('type' => 'all', 'access_token' => $token); - $repos = array(); - $repos['user'] = $this->doGithubApiRequest('/user/repos', $params); - - - foreach ($orgs as $org) { - $repos[$org['login']] = $this->doGithubApiRequest('/orgs/'.$org['login'].'/repos', $params); - } - - $rtn = array(); - foreach ($repos as $repoGroup) { - foreach ($repoGroup as $repo) { - $rtn['repos'][] = $repo['full_name']; - } - } - - $cache->set('phpci_github_repos', $rtn); - } - - die(json_encode($rtn)); - } - - protected function doGithubApiRequest($url, $params) - { - $http = new \b8\HttpClient('https://api.github.com'); - $res = $http->get($url, $params); - - return $res['body']; + $response = new b8\Http\Response\JsonResponse(); + $response->setContent($github->getRepositories()); + return $response; } + /** + * Get the validator to use to check project references. + * @param $values + * @return callable + */ protected function getReferenceValidator($values) { return function ($val) use ($values) { @@ -377,30 +419,30 @@ class ProjectController extends \PHPCI\Controller $validators = array( 'hg' => array( 'regex' => '/^(https?):\/\//', - 'message' => 'Mercurial repository URL must be start with http:// or https://' + 'message' => Lang::get('error_mercurial') ), 'remote' => array( 'regex' => '/^(git|https?):\/\//', - 'message' => 'Repository URL must be start with git://, http:// or https://' + 'message' => Lang::get('error_remote') ), 'gitlab' => array( 'regex' => '`^(.*)@(.*):(.*)/(.*)\.git`', - 'message' => 'GitLab Repository name must be in the format "user@domain.tld:owner/repo.git"' + 'message' => Lang::get('error_gitlab') ), 'github' => array( 'regex' => '/^[a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-\.]+$/', - 'message' => 'Repository name must be in the format "owner/repo"' + 'message' => Lang::get('error_github') ), 'bitbucket' => array( 'regex' => '/^[a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-\.]+$/', - 'message' => 'Repository name must be in the format "owner/repo"' + 'message' => Lang::get('error_bitbucket') ), ); if (in_array($type, $validators) && !preg_match($validators[$type]['regex'], $val)) { throw new \Exception($validators[$type]['message']); } elseif ($type == 'local' && !is_dir($val)) { - throw new \Exception('The path you specified does not exist.'); + throw new \Exception(Lang::get('error_path')); } return true; diff --git a/PHPCI/Controller/SessionController.php b/PHPCI/Controller/SessionController.php index 15563b19..b68d61f7 100644 --- a/PHPCI/Controller/SessionController.php +++ b/PHPCI/Controller/SessionController.php @@ -1,15 +1,17 @@ response->disableLayout(); @@ -38,14 +43,23 @@ class SessionController extends \PHPCI\Controller $isLoginFailure = false; if ($this->request->getMethod() == 'POST') { - $user = $this->userStore->getByEmail($this->getParam('email')); - - if ($user && password_verify($this->getParam('password', ''), $user->getHash())) { - $_SESSION['user_id'] = $user->getId(); - header('Location: ' . PHPCI_URL); - die; - } else { + $token = $this->getParam('token'); + if (!isset($token, $_SESSION['login_token']) || $token !== $_SESSION['login_token']) { $isLoginFailure = true; + } else { + unset($_SESSION['login_token']); + + $user = $this->userStore->getByEmail($this->getParam('email')); + + if ($user && password_verify($this->getParam('password', ''), $user->getHash())) { + session_regenerate_id(true); + $_SESSION['phpci_user_id'] = $user->getId(); + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', $this->getLoginRedirect()); + return $response; + } else { + $isLoginFailure = true; + } } } @@ -54,27 +68,33 @@ class SessionController extends \PHPCI\Controller $form->setAction(PHPCI_URL.'session/login'); $email = new b8\Form\Element\Email('email'); - $email->setLabel('Email Address'); + $email->setLabel(Lang::get('email_address')); $email->setRequired(true); $email->setContainerClass('form-group'); $email->setClass('form-control'); $form->addField($email); $pwd = new b8\Form\Element\Password('password'); - $pwd->setLabel('Password'); + $pwd->setLabel(Lang::get('password')); $pwd->setRequired(true); $pwd->setContainerClass('form-group'); $pwd->setClass('form-control'); $form->addField($pwd); $pwd = new b8\Form\Element\Submit(); - $pwd->setValue('Log in »'); + $pwd->setValue(Lang::get('log_in')); $pwd->setClass('btn-success'); $form->addField($pwd); + $tokenValue = $this->generateToken(); + $_SESSION['login_token'] = $tokenValue; + $token = new b8\Form\Element\Hidden('token'); + $token->setValue($tokenValue); + $form->addField($token); + $this->view->form = $form->render(); $this->view->failed = $isLoginFailure; - + return $this->view->render(); } @@ -83,9 +103,111 @@ class SessionController extends \PHPCI\Controller */ public function logout() { - $_SESSION = array(); + unset($_SESSION['phpci_user']); + unset($_SESSION['phpci_user_id']); + session_destroy(); - header('Location: ' . PHPCI_URL); - die; + + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', PHPCI_URL); + return $response; + } + + /** + * Allows the user to request a password reset email. + * @return string + */ + public function forgotPassword() + { + if ($this->request->getMethod() == 'POST') { + $email = $this->getParam('email', null); + $user = $this->userStore->getByEmail($email); + + if (empty($user)) { + $this->view->error = Lang::get('reset_no_user_exists'); + return $this->view->render(); + } + + $key = md5(date('Y-m-d') . $user->getHash()); + $url = PHPCI_URL; + + $message = Lang::get('reset_email_body', $user->getName(), $url, $user->getId(), $key); + + $email = new Email(); + $email->setEmailTo($user->getEmail(), $user->getName()); + $email->setSubject(Lang::get('reset_email_title', $user->getName())); + $email->setBody($message); + $email->send(); + + $this->view->emailed = true; + } + + return $this->view->render(); + } + + /** + * Allows the user to change their password after a password reset email. + * @param $userId + * @param $key + * @return string + */ + public function resetPassword($userId, $key) + { + $user = $this->userStore->getById($userId); + $userKey = md5(date('Y-m-d') . $user->getHash()); + + if (empty($user) || $key != $userKey) { + $this->view->error = Lang::get('reset_invalid'); + return $this->view->render(); + } + + if ($this->request->getMethod() == 'POST') { + $hash = password_hash($this->getParam('password'), PASSWORD_DEFAULT); + $user->setHash($hash); + + $_SESSION['phpci_user'] = $this->userStore->save($user); + $_SESSION['phpci_user_id'] = $user->getId(); + + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', PHPCI_URL); + return $response; + } + + $this->view->id = $userId; + $this->view->key = $key; + + return $this->view->render(); + } + + /** + * Get the URL the user was trying to go to prior to being asked to log in. + * @return string + */ + protected function getLoginRedirect() + { + $rtn = PHPCI_URL; + + if (!empty($_SESSION['phpci_login_redirect'])) { + $rtn .= $_SESSION['phpci_login_redirect']; + $_SESSION['phpci_login_redirect'] = null; + } + + return $rtn; + } + + /** Generate a random token. + * + * @return string + */ + protected function generateToken() + { + if (function_exists('openssl_random_pseudo_bytes')) { + return bin2hex(openssl_random_pseudo_bytes(16)); + } + + return sprintf("%04x", mt_rand(0, 0xFFFF)) + . sprintf("%04x", mt_rand(0, 0xFFFF)) + . sprintf("%04x", mt_rand(0, 0xFFFF)) + . sprintf("%04x", mt_rand(0, 0xFFFF)); } } diff --git a/PHPCI/Controller/SettingsController.php b/PHPCI/Controller/SettingsController.php index d0582f6a..645e19a4 100644 --- a/PHPCI/Controller/SettingsController.php +++ b/PHPCI/Controller/SettingsController.php @@ -2,9 +2,9 @@ /** * PHPCI - Continuous Integration for PHP * - * @copyright Copyright 2013, Block 8 Limited. + * @copyright Copyright 2014, Block 8 Limited. * @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md - * @link http://www.phptesting.org/ + * @link https://www.phptesting.org/ */ namespace PHPCI\Controller; @@ -13,33 +13,76 @@ use b8; use b8\Form; use b8\HttpClient; use PHPCI\Controller; -use PHPCI\Model\Build; +use PHPCI\Helper\Lang; use Symfony\Component\Yaml\Dumper; use Symfony\Component\Yaml\Parser; /** * Settings Controller + * * @author Dan Cryer * @package PHPCI * @subpackage Web */ class SettingsController extends Controller { + + /** + * @var array + */ protected $settings; + /** + * Initialise the controller, set up stores and services. + */ public function init() { parent::init(); - $parser = new Parser(); - $yaml = file_get_contents(APPLICATION_PATH . 'PHPCI/config.yml'); + $parser = new Parser(); + $yaml = file_get_contents(PHPCI_CONFIG_FILE); $this->settings = $parser->parse($yaml); } + /** + * Display settings forms. + * @return string + */ public function index() { + $this->requireAdmin(); + + $this->layout->title = Lang::get('settings'); + $this->view->settings = $this->settings; + + $basicSettings = array(); + if (isset($this->settings['phpci']['basic'])) { + $basicSettings = $this->settings['phpci']['basic']; + } + + $buildSettings = array(); + if (isset($this->settings['phpci']['build'])) { + $buildSettings = $this->settings['phpci']['build']; + } + + $emailSettings = array(); + if (isset($this->settings['phpci']['email_settings'])) { + $emailSettings = $this->settings['phpci']['email_settings']; + } + + $authSettings = array(); + if (isset($this->settings['phpci']['authentication_settings'])) { + $authSettings = $this->settings['phpci']['authentication_settings']; + } + + $this->view->configFile = PHPCI_CONFIG_FILE; + $this->view->basicSettings = $this->getBasicForm($basicSettings); + $this->view->buildSettings = $this->getBuildForm($buildSettings); $this->view->github = $this->getGithubForm(); + $this->view->emailSettings = $this->getEmailForm($emailSettings); + $this->view->authenticationSettings = $this->getAuthenticationForm($authSettings); + $this->view->isWriteable = $this->canWriteConfig(); if (!empty($this->settings['phpci']['github']['token'])) { $this->view->githubUser = $this->getGithubUser($this->settings['phpci']['github']['token']); @@ -48,15 +91,115 @@ class SettingsController extends Controller return $this->view->render(); } + /** + * Save Github settings. + */ public function github() { - $this->settings['phpci']['github']['id'] = $this->getParam('githubid', ''); + $this->requireAdmin(); + + $this->settings['phpci']['github']['id'] = $this->getParam('githubid', ''); $this->settings['phpci']['github']['secret'] = $this->getParam('githubsecret', ''); + $error = $this->storeSettings(); - $this->storeSettings(); + $response = new b8\Http\Response\RedirectResponse(); - header('Location: ' . PHPCI_URL . 'settings?saved=1'); - die; + if ($error) { + $response->setHeader('Location', PHPCI_URL . 'settings?saved=2'); + } else { + $response->setHeader('Location', PHPCI_URL . 'settings?saved=1'); + } + + return $response; + } + + /** + * Save email settings. + */ + public function email() + { + $this->requireAdmin(); + + $this->settings['phpci']['email_settings'] = $this->getParams(); + $this->settings['phpci']['email_settings']['smtp_encryption'] = $this->getParam('smtp_encryption', 0); + + $error = $this->storeSettings(); + + $response = new b8\Http\Response\RedirectResponse(); + + if ($error) { + $response->setHeader('Location', PHPCI_URL . 'settings?saved=2'); + } else { + $response->setHeader('Location', PHPCI_URL . 'settings?saved=1'); + } + + return $response; + } + + /** + * Save build settings. + */ + public function build() + { + $this->requireAdmin(); + + $this->settings['phpci']['build'] = $this->getParams(); + + $error = $this->storeSettings(); + + $response = new b8\Http\Response\RedirectResponse(); + + if ($error) { + $response->setHeader('Location', PHPCI_URL . 'settings?saved=2'); + } else { + $response->setHeader('Location', PHPCI_URL . 'settings?saved=1'); + } + + return $response; + } + + /** + * Save basic settings. + */ + public function basic() + { + $this->requireAdmin(); + + $this->settings['phpci']['basic'] = $this->getParams(); + $error = $this->storeSettings(); + + $response = new b8\Http\Response\RedirectResponse(); + + if ($error) { + $response->setHeader('Location', PHPCI_URL . 'settings?saved=2'); + } else { + $response->setHeader('Location', PHPCI_URL . 'settings?saved=1'); + } + + return $response; + } + + /** + * Handle authentication settings + */ + public function authentication() + { + $this->requireAdmin(); + + $this->settings['phpci']['authentication_settings']['state'] = $this->getParam('disable_authentication', 0); + $this->settings['phpci']['authentication_settings']['user_id'] = $_SESSION['phpci_user_id']; + + $error = $this->storeSettings(); + + $response = new b8\Http\Response\RedirectResponse(); + + if ($error) { + $response->setHeader('Location', PHPCI_URL . 'settings?saved=2'); + } else { + $response->setHeader('Location', PHPCI_URL . 'settings?saved=1'); + } + + return $response; } /** @@ -64,14 +207,14 @@ class SettingsController extends Controller */ public function githubCallback() { - $code = $this->getParam('code', null); + $code = $this->getParam('code', null); $github = $this->settings['phpci']['github']; if (!is_null($code)) { - $http = new HttpClient(); - $url = 'https://github.com/login/oauth/access_token'; + $http = new HttpClient(); + $url = 'https://github.com/login/oauth/access_token'; $params = array('client_id' => $github['id'], 'client_secret' => $github['secret'], 'code' => $code); - $resp = $http->post($url, $params); + $resp = $http->post($url, $params); if ($resp['success']) { parse_str($resp['body'], $resp); @@ -79,23 +222,38 @@ class SettingsController extends Controller $this->settings['phpci']['github']['token'] = $resp['access_token']; $this->storeSettings(); - header('Location: ' . PHPCI_URL . 'settings?linked=1'); - die; + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', PHPCI_URL . 'settings?linked=1'); + return $response; } } - - header('Location: ' . PHPCI_URL . 'settings?linked=2'); - die; + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', PHPCI_URL . 'settings?linked=2'); + return $response; } + /** + * Convert config to yaml and store to file. + * + * @return mixed + */ protected function storeSettings() { $dumper = new Dumper(); - $yaml = $dumper->dump($this->settings); - file_put_contents(APPLICATION_PATH . 'PHPCI/config.yml', $yaml); + $yaml = $dumper->dump($this->settings, 4); + file_put_contents(PHPCI_CONFIG_FILE, $yaml); + + if (error_get_last()) { + $error_get_last = error_get_last(); + return $error_get_last['message']; + } } + /** + * Get the Github settings form. + * @return Form + */ protected function getGithubForm() { $form = new Form(); @@ -106,30 +264,115 @@ class SettingsController extends Controller $field = new Form\Element\Text('githubid'); $field->setRequired(true); $field->setPattern('[a-zA-Z0-9]+'); - $field->setLabel('Application ID'); + $field->setLabel(Lang::get('application_id')); $field->setClass('form-control'); $field->setContainerClass('form-group'); - $field->setValue($this->settings['phpci']['github']['id']); $form->addField($field); + if (isset($this->settings['phpci']['github']['id'])) { + $field->setValue($this->settings['phpci']['github']['id']); + } + $field = new Form\Element\Text('githubsecret'); $field->setRequired(true); $field->setPattern('[a-zA-Z0-9]+'); - $field->setLabel('Application Secret'); + $field->setLabel(Lang::get('application_secret')); $field->setClass('form-control'); $field->setContainerClass('form-group'); - $field->setValue($this->settings['phpci']['github']['secret']); $form->addField($field); + if (isset($this->settings['phpci']['github']['secret'])) { + $field->setValue($this->settings['phpci']['github']['secret']); + } $field = new Form\Element\Submit(); - $field->setValue('Save »'); + $field->setValue(Lang::get('save')); $field->setClass('btn btn-success pull-right'); $form->addField($field); return $form; } + /** + * Get the email settings form. + * @param array $values + * @return Form + */ + protected function getEmailForm($values = array()) + { + $form = new Form(); + $form->setMethod('POST'); + $form->setAction(PHPCI_URL . 'settings/email'); + $form->addField(new Form\Element\Csrf('csrf')); + + $field = new Form\Element\Text('smtp_address'); + $field->setRequired(false); + $field->setLabel(Lang::get('smtp_server')); + $field->setClass('form-control'); + $field->setContainerClass('form-group'); + $field->setValue('localhost'); + $form->addField($field); + + $field = new Form\Element\Text('smtp_port'); + $field->setRequired(false); + $field->setPattern('[0-9]+'); + $field->setLabel(Lang::get('smtp_port')); + $field->setClass('form-control'); + $field->setContainerClass('form-group'); + $field->setValue(25); + $form->addField($field); + + $field = new Form\Element\Text('smtp_username'); + $field->setRequired(false); + $field->setLabel(Lang::get('smtp_username')); + $field->setClass('form-control'); + $field->setContainerClass('form-group'); + $form->addField($field); + + $field = new Form\Element\Password('smtp_password'); + $field->setRequired(false); + $field->setLabel(Lang::get('smtp_password')); + $field->setClass('form-control'); + $field->setContainerClass('form-group'); + $form->addField($field); + + $field = new Form\Element\Email('from_address'); + $field->setRequired(false); + $field->setLabel(Lang::get('from_email_address')); + $field->setClass('form-control'); + $field->setContainerClass('form-group'); + $form->addField($field); + + $field = new Form\Element\Email('default_mailto_address'); + $field->setRequired(false); + $field->setLabel(Lang::get('default_notification_address')); + $field->setClass('form-control'); + $field->setContainerClass('form-group'); + $form->addField($field); + + $field = new Form\Element\Select('smtp_encryption'); + $field->setOptions(array('' => Lang::get('none'), 'tls' => Lang::get('tls'), 'ssl' => Lang::get('ssl'))); + $field->setRequired(false); + $field->setLabel(Lang::get('use_smtp_encryption')); + $field->setContainerClass('form-group'); + $field->setValue(1); + $form->addField($field); + + $field = new Form\Element\Submit(); + $field->setValue(Lang::get('save')); + $field->setClass('btn btn-success pull-right'); + $form->addField($field); + + $form->setValues($values); + + return $form; + } + + /** + * Call Github API for our Github user object. + * @param $token + * @return mixed + */ protected function getGithubUser($token) { $http = new HttpClient('https://api.github.com'); @@ -137,4 +380,117 @@ class SettingsController extends Controller return $user['body']; } + + /** + * Check if we can write the PHPCI config file. + * @return bool + */ + protected function canWriteConfig() + { + return is_writeable(PHPCI_CONFIG_FILE); + } + + /** + * Get the Build settings form. + * @param array $values + * @return Form + */ + protected function getBuildForm($values = array()) + { + $form = new Form(); + $form->setMethod('POST'); + $form->setAction(PHPCI_URL . 'settings/build'); + + $field = new Form\Element\Select('failed_after'); + $field->setRequired(false); + $field->setLabel(Lang::get('failed_after')); + $field->setClass('form-control'); + $field->setContainerClass('form-group'); + $field->setOptions(array( + 300 => Lang::get('5_mins'), + 900 => Lang::get('15_mins'), + 1800 => Lang::get('30_mins'), + 3600 => Lang::get('1_hour'), + 10800 => Lang::get('3_hours'), + )); + $field->setValue(1800); + $form->addField($field); + + + $field = new Form\Element\Submit(); + $field->setValue(Lang::get('save')); + $field->setClass('btn btn-success pull-right'); + $form->addField($field); + + $form->setValues($values); + + return $form; + } + + /** + * Get the Basic settings form. + * @param array $values + * @return Form + */ + protected function getBasicForm($values = array()) + { + $form = new Form(); + $form->setMethod('POST'); + $form->setAction(PHPCI_URL . 'settings/basic'); + + $field = new Form\Element\Select('language'); + $field->setRequired(true); + $field->setLabel(Lang::get('language')); + $field->setClass('form-control'); + $field->setContainerClass('form-group'); + $field->setOptions(Lang::getLanguageOptions()); + $field->setValue(Lang::getLanguage()); + $form->addField($field); + + + $field = new Form\Element\Submit(); + $field->setValue(Lang::get('save')); + $field->setClass('btn btn-success pull-right'); + $form->addField($field); + + $form->setValues($values); + + return $form; + } + + /** + * Form for disabling user authentication while using a default user + * + * @param array $values + * @return Form + */ + protected function getAuthenticationForm($values = array()) + { + $form = new Form(); + $form->setMethod('POST'); + $form->setAction(PHPCI_URL . 'settings/authentication'); + $form->addField(new Form\Element\Csrf('csrf')); + + $field = new Form\Element\Checkbox('disable_authentication'); + $field->setCheckedValue(1); + $field->setRequired(false); + $field->setLabel('Disable Authentication?'); + $field->setContainerClass('form-group'); + $field->setValue(0); + + if (isset($values['state'])) { + $field->setValue((int)$values['state']); + } + + $form->addField($field); + + $field = new Form\Element\Submit(); + $field->setValue('Save »'); + $field->setClass('btn btn-success pull-right'); + $form->addField($field); + + $form->setValues($values); + + return $form; + } } diff --git a/PHPCI/Controller/UserController.php b/PHPCI/Controller/UserController.php index ad41aff6..19a7313f 100644 --- a/PHPCI/Controller/UserController.php +++ b/PHPCI/Controller/UserController.php @@ -1,18 +1,20 @@ userStore = b8\Store\Factory::getStore('User'); + $this->userStore = b8\Store\Factory::getStore('User'); + $this->userService = new UserService($this->userStore); } /** @@ -40,6 +51,90 @@ class UserController extends Controller $users = $this->userStore->getWhere(array(), 1000, 0, array(), array('email' => 'ASC')); $this->view->users = $users; + $this->layout->title = Lang::get('manage_users'); + + return $this->view->render(); + } + + /** + * Allows the user to edit their profile. + * @return string + */ + public function profile() + { + $user = $_SESSION['phpci_user']; + + if ($this->request->getMethod() == 'POST') { + $name = $this->getParam('name', null); + $email = $this->getParam('email', null); + $password = $this->getParam('password', null); + + $currentLang = Lang::getLanguage(); + $chosenLang = $this->getParam('language', $currentLang); + + if ($chosenLang !== $currentLang) { + setcookie('phpcilang', $chosenLang, time() + (10 * 365 * 24 * 60 * 60), '/'); + Lang::setLanguage($chosenLang); + } + + $_SESSION['phpci_user'] = $this->userService->updateUser($user, $name, $email, $password); + $user = $_SESSION['phpci_user']; + + $this->view->updated = 1; + } + + $this->layout->title = $user->getName(); + $this->layout->subtitle = Lang::get('edit_profile'); + + $values = $user->getDataArray(); + + if (array_key_exists('phpcilang', $_COOKIE)) { + $values['language'] = $_COOKIE['phpcilang']; + } + + $form = new Form(); + $form->setAction(PHPCI_URL.'user/profile'); + $form->setMethod('POST'); + + $name = new Form\Element\Text('name'); + $name->setClass('form-control'); + $name->setContainerClass('form-group'); + $name->setLabel(Lang::get('name')); + $name->setRequired(true); + $form->addField($name); + + $email = new Form\Element\Email('email'); + $email->setClass('form-control'); + $email->setContainerClass('form-group'); + $email->setLabel(Lang::get('email_address')); + $email->setRequired(true); + $form->addField($email); + + $password = new Form\Element\Password('password'); + $password->setClass('form-control'); + $password->setContainerClass('form-group'); + $password->setLabel(Lang::get('password_change')); + $password->setRequired(false); + $form->addField($password); + + $lang = new Form\Element\Select('language'); + $lang->setClass('form-control'); + $lang->setContainerClass('form-group'); + $lang->setLabel(Lang::get('language')); + $lang->setRequired(true); + $lang->setOptions(Lang::getLanguageOptions()); + $lang->setValue(Lang::getLanguage()); + $form->addField($lang); + + $submit = new Form\Element\Submit(); + $submit->setClass('btn btn-success'); + $submit->setValue(Lang::get('save')); + $form->addField($submit); + + $form->setValues($values); + + $this->view->form = $form; + return $this->view->render(); } @@ -48,9 +143,9 @@ class UserController extends Controller */ public function add() { - if (!$_SESSION['user']->getIsAdmin()) { - throw new \Exception('You do not have permission to do that.'); - } + $this->requireAdmin(); + + $this->layout->title = Lang::get('add_user'); $method = $this->request->getMethod(); @@ -71,17 +166,17 @@ class UserController extends Controller return $view->render(); } - $values = $form->getValues(); - $values['is_admin'] = $values['admin'] ? 1 : 0; - $values['hash'] = password_hash($values['password'], PASSWORD_DEFAULT); - $user = new User(); - $user->setValues($values); + $name = $this->getParam('name', null); + $email = $this->getParam('email', null); + $password = $this->getParam('password', null); + $isAdmin = (int)$this->getParam('is_admin', 0); - $user = $this->userStore->save($user); + $this->userService->createUser($name, $email, $password, $isAdmin); - header('Location: '.PHPCI_URL.'user'); - die; + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', PHPCI_URL . 'user'); + return $response; } /** @@ -89,43 +184,40 @@ class UserController extends Controller */ public function edit($userId) { - if (!$_SESSION['user']->getIsAdmin()) { - throw new \Exception('You do not have permission to do that.'); + $this->requireAdmin(); + + $method = $this->request->getMethod(); + $user = $this->userStore->getById($userId); + + if (empty($user)) { + throw new NotFoundException(Lang::get('user_n_not_found', $userId)); } - $method = $this->request->getMethod(); - $user = $this->userStore->getById($userId); + $this->layout->title = $user->getName(); + $this->layout->subtitle = Lang::get('edit_user'); - if ($method == 'POST') { - $values = $this->getParams(); - } else { - $values = $user->getDataArray(); - $values['admin'] = $values['is_admin']; - } - - $form = $this->userForm($values, 'edit/' . $userId); + $values = array_merge($user->getDataArray(), $this->getParams()); + $form = $this->userForm($values, 'edit/' . $userId); if ($method != 'POST' || ($method == 'POST' && !$form->validate())) { - $view = new b8\View('UserForm'); - $view->type = 'edit'; - $view->user = $user; - $view->form = $form; + $view = new b8\View('UserForm'); + $view->type = 'edit'; + $view->user = $user; + $view->form = $form; return $view->render(); } - $values = $form->getValues(); - $values['is_admin'] = $values['admin'] ? 1 : 0; + $name = $this->getParam('name', null); + $email = $this->getParam('email', null); + $password = $this->getParam('password', null); + $isAdmin = (int)$this->getParam('is_admin', 0); - if (!empty($values['password'])) { - $values['hash'] = password_hash($values['password'], PASSWORD_DEFAULT); - } + $this->userService->updateUser($user, $name, $email, $password, $isAdmin); - $user->setValues($values); - $user = $this->userStore->save($user); - - header('Location: '.PHPCI_URL.'user'); - die; + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', PHPCI_URL . 'user'); + return $response; } /** @@ -140,34 +232,41 @@ class UserController extends Controller $field = new Form\Element\Email('email'); $field->setRequired(true); - $field->setLabel('Email Address'); + $field->setLabel(Lang::get('email_address')); $field->setClass('form-control'); $field->setContainerClass('form-group'); $form->addField($field); $field = new Form\Element\Text('name'); $field->setRequired(true); - $field->setLabel('Name'); + $field->setLabel(Lang::get('name')); $field->setClass('form-control'); $field->setContainerClass('form-group'); $form->addField($field); $field = new Form\Element\Password('password'); - $field->setRequired(true); - $field->setLabel('Password' . ($type == 'edit' ? ' (leave blank to keep current password)' : '')); + + if ($type == 'add') { + $field->setRequired(true); + $field->setLabel(Lang::get('password')); + } else { + $field->setRequired(false); + $field->setLabel(Lang::get('password_change')); + } + $field->setClass('form-control'); $field->setContainerClass('form-group'); $form->addField($field); - $field = new Form\Element\Checkbox('admin'); + $field = new Form\Element\Checkbox('is_admin'); $field->setRequired(false); $field->setCheckedValue(1); - $field->setLabel('Is this user an administrator?'); + $field->setLabel(Lang::get('is_user_admin')); $field->setContainerClass('form-group'); $form->addField($field); $field = new Form\Element\Submit(); - $field->setValue('Save User'); + $field->setValue(Lang::get('save_user')); $field->setClass('btn-success'); $form->addField($field); @@ -180,14 +279,18 @@ class UserController extends Controller */ public function delete($userId) { - if (!$_SESSION['user']->getIsAdmin()) { - throw new \Exception('You do not have permission to do that.'); - } - - $user = $this->userStore->getById($userId); - $this->userStore->delete($user); + $this->requireAdmin(); - header('Location: '.PHPCI_URL.'user'); - die; + $user = $this->userStore->getById($userId); + + if (empty($user)) { + throw new NotFoundException(Lang::get('user_n_not_found', $userId)); + } + + $this->userService->deleteUser($user); + + $response = new b8\Http\Response\RedirectResponse(); + $response->setHeader('Location', PHPCI_URL . 'user'); + return $response; } } diff --git a/PHPCI/Controller/WebhookController.php b/PHPCI/Controller/WebhookController.php new file mode 100644 index 00000000..4b444d03 --- /dev/null +++ b/PHPCI/Controller/WebhookController.php @@ -0,0 +1,453 @@ + + * @author Sami Tikka + * @author Alex Russell + * @author Guillaume Perréal + * @package PHPCI + * @subpackage Web + * + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + */ +class WebhookController extends \b8\Controller +{ + /** + * @var BuildStore + */ + protected $buildStore; + + /** + * @var ProjectStore + */ + protected $projectStore; + + /** + * @var BuildService + */ + protected $buildService; + + /** + * Initialise the controller, set up stores and services. + */ + public function init() + { + $this->buildStore = Store\Factory::getStore('Build'); + $this->projectStore = Store\Factory::getStore('Project'); + $this->buildService = new BuildService($this->buildStore); + } + + /** Handle the action, Ensuring to return a JsonResponse. + * + * @param string $action + * @param mixed $actionParams + * + * @return \b8\Http\Response + */ + public function handleAction($action, $actionParams) + { + $response = new b8\Http\Response\JsonResponse(); + try { + $data = parent::handleAction($action, $actionParams); + if (isset($data['responseCode'])) { + $response->setResponseCode($data['responseCode']); + unset($data['responseCode']); + } + $response->setContent($data); + } catch (Exception $ex) { + $response->setResponseCode(500); + $response->setContent(array('status' => 'failed', 'error' => $ex->getMessage())); + } + return $response; + } + + /** + * Called by Bitbucket. + */ + public function bitbucket($projectId) + { + $project = $this->fetchProject($projectId, 'bitbucket'); + + // Support both old services and new webhooks + if ($payload = $this->getParam('payload')) { + return $this->bitbucketService(json_decode($payload, true), $project); + } + + $payload = json_decode(file_get_contents("php://input"), true); + + if (empty($payload['push']['changes'])) { + // Invalid event from bitbucket + return [ + 'status' => 'failed', + 'commits' => [] + ]; + } + + return $this->bitbucketWebhook($payload, $project); + } + + /** + * Bitbucket webhooks. + */ + protected function bitbucketWebhook($payload, $project) + { + $results = array(); + $status = 'failed'; + foreach ($payload['push']['changes'] as $commit) { + try { + $email = $commit['new']['target']['author']['raw']; + $email = substr($email, 0, strpos($email, '>')); + $email = substr($email, strpos($email, '<') + 1); + + $results[$commit['new']['target']['hash']] = $this->createBuild( + $project, + $commit['new']['target']['hash'], + $commit['new']['name'], + $email, + $commit['new']['target']['message'] + ); + $status = 'ok'; + } catch (Exception $ex) { + $results[$commit['new']['target']['hash']] = array('status' => 'failed', 'error' => $ex->getMessage()); + } + } + + return array('status' => $status, 'commits' => $results); + } + + /** + * Bitbucket POST service. + */ + protected function bitbucketService($payload, $project) + { + $payload = json_decode($this->getParam('payload'), true); + + $results = array(); + $status = 'failed'; + foreach ($payload['commits'] as $commit) { + try { + $email = $commit['raw_author']; + $email = substr($email, 0, strpos($email, '>')); + $email = substr($email, strpos($email, '<') + 1); + + $results[$commit['raw_node']] = $this->createBuild( + $project, + $commit['raw_node'], + $commit['branch'], + $email, + $commit['message'] + ); + $status = 'ok'; + } catch (Exception $ex) { + $results[$commit['raw_node']] = array('status' => 'failed', 'error' => $ex->getMessage()); + } + } + + return array('status' => $status, 'commits' => $results); + } + + /** + * Called by POSTing to /webhook/git/?branch=&commit= + * + * @param string $projectId + */ + public function git($projectId) + { + $project = $this->fetchProject($projectId, array('local', 'remote')); + $branch = $this->getParam('branch', $project->getBranch()); + $commit = $this->getParam('commit'); + $commitMessage = $this->getParam('message'); + $committer = $this->getParam('committer'); + + return $this->createBuild($project, $commit, $branch, $committer, $commitMessage); + } + + /** + * Called by Github Webhooks: + */ + public function github($projectId) + { + $project = $this->fetchProject($projectId, 'github'); + + switch ($_SERVER['CONTENT_TYPE']) { + case 'application/json': + $payload = json_decode(file_get_contents('php://input'), true); + break; + case 'application/x-www-form-urlencoded': + $payload = json_decode($this->getParam('payload'), true); + break; + default: + return array('status' => 'failed', 'error' => 'Content type not supported.', 'responseCode' => 401); + } + + // Handle Pull Request web hooks: + if (array_key_exists('pull_request', $payload)) { + return $this->githubPullRequest($project, $payload); + } + + // Handle Push web hooks: + if (array_key_exists('commits', $payload)) { + return $this->githubCommitRequest($project, $payload); + } + + return array('status' => 'ignored', 'message' => 'Unusable payload.'); + } + + /** + * Handle the payload when Github sends a commit webhook. + * + * @param Project $project + * @param array $payload + * @param b8\Http\Response\JsonResponse $response + * + * @return b8\Http\Response\JsonResponse + */ + protected function githubCommitRequest(Project $project, array $payload) + { + // Github sends a payload when you close a pull request with a + // non-existent commit. We don't want this. + if (array_key_exists('after', $payload) && $payload['after'] === '0000000000000000000000000000000000000000') { + return array('status' => 'ignored'); + } + + if (isset($payload['commits']) && is_array($payload['commits'])) { + // If we have a list of commits, then add them all as builds to be tested: + + $results = array(); + $status = 'failed'; + foreach ($payload['commits'] as $commit) { + if (!$commit['distinct']) { + $results[$commit['id']] = array('status' => 'ignored'); + continue; + } + + try { + $branch = str_replace('refs/heads/', '', $payload['ref']); + $committer = $commit['committer']['email']; + $results[$commit['id']] = $this->createBuild( + $project, + $commit['id'], + $branch, + $committer, + $commit['message'] + ); + $status = 'ok'; + } catch (Exception $ex) { + $results[$commit['id']] = array('status' => 'failed', 'error' => $ex->getMessage()); + } + } + return array('status' => $status, 'commits' => $results); + } + + if (substr($payload['ref'], 0, 10) == 'refs/tags/') { + // If we don't, but we're dealing with a tag, add that instead: + $branch = str_replace('refs/tags/', 'Tag: ', $payload['ref']); + $committer = $payload['pusher']['email']; + $message = $payload['head_commit']['message']; + return $this->createBuild($project, $payload['after'], $branch, $committer, $message); + } + + return array('status' => 'ignored', 'message' => 'Unusable payload.'); + } + + /** + * Handle the payload when Github sends a Pull Request webhook. + * + * @param Project $project + * @param array $payload + */ + protected function githubPullRequest(Project $project, array $payload) + { + // We only want to know about open pull requests: + if (!in_array($payload['action'], array('opened', 'synchronize', 'reopened'))) { + return array('status' => 'ok'); + } + + $headers = array(); + $token = \b8\Config::getInstance()->get('phpci.github.token'); + + if (!empty($token)) { + $headers[] = 'Authorization: token ' . $token; + } + + $url = $payload['pull_request']['commits_url']; + $http = new \b8\HttpClient(); + $http->setHeaders($headers); + $response = $http->get($url); + + // Check we got a success response: + if (!$response['success']) { + throw new Exception('Could not get commits, failed API request.'); + } + + $results = array(); + $status = 'failed'; + foreach ($response['body'] as $commit) { + // Skip all but the current HEAD commit ID: + $id = $commit['sha']; + if ($id != $payload['pull_request']['head']['sha']) { + $results[$id] = array('status' => 'ignored', 'message' => 'not branch head'); + continue; + } + + try { + $branch = str_replace('refs/heads/', '', $payload['pull_request']['base']['ref']); + $committer = $commit['commit']['author']['email']; + $message = $commit['commit']['message']; + + $remoteUrlKey = $payload['pull_request']['head']['repo']['private'] ? 'ssh_url' : 'clone_url'; + + $extra = array( + 'build_type' => 'pull_request', + 'pull_request_id' => $payload['pull_request']['id'], + 'pull_request_number' => $payload['number'], + 'remote_branch' => $payload['pull_request']['head']['ref'], + 'remote_url' => $payload['pull_request']['head']['repo'][$remoteUrlKey], + ); + + $results[$id] = $this->createBuild($project, $id, $branch, $committer, $message, $extra); + $status = 'ok'; + } catch (Exception $ex) { + $results[$id] = array('status' => 'failed', 'error' => $ex->getMessage()); + } + } + + return array('status' => $status, 'commits' => $results); + } + + /** + * Called by Gitlab Webhooks: + */ + public function gitlab($projectId) + { + $project = $this->fetchProject($projectId, 'gitlab'); + + $payloadString = file_get_contents("php://input"); + $payload = json_decode($payloadString, true); + + // build on merge request events + if (isset($payload['object_kind']) && $payload['object_kind'] == 'merge_request') { + $attributes = $payload['object_attributes']; + if ($attributes['state'] == 'opened' || $attributes['state'] == 'reopened') { + $branch = $attributes['source_branch']; + $commit = $attributes['last_commit']; + $committer = $commit['author']['email']; + + return $this->createBuild($project, $commit['id'], $branch, $committer, $commit['message']); + } + } + + // build on push events + if (isset($payload['commits']) && is_array($payload['commits'])) { + // If we have a list of commits, then add them all as builds to be tested: + + $results = array(); + $status = 'failed'; + foreach ($payload['commits'] as $commit) { + try { + $branch = str_replace('refs/heads/', '', $payload['ref']); + $committer = $commit['author']['email']; + $results[$commit['id']] = $this->createBuild( + $project, + $commit['id'], + $branch, + $committer, + $commit['message'] + ); + $status = 'ok'; + } catch (Exception $ex) { + $results[$commit['id']] = array('status' => 'failed', 'error' => $ex->getMessage()); + } + } + return array('status' => $status, 'commits' => $results); + } + + return array('status' => 'ignored', 'message' => 'Unusable payload.'); + } + + /** + * Wrapper for creating a new build. + * + * @param Project $project + * @param string $commitId + * @param string $branch + * @param string $committer + * @param string $commitMessage + * @param array $extra + * + * @return array + * + * @throws Exception + */ + protected function createBuild( + Project $project, + $commitId, + $branch, + $committer, + $commitMessage, + array $extra = null + ) { + // Check if a build already exists for this commit ID: + $builds = $this->buildStore->getByProjectAndCommit($project->getId(), $commitId); + + if ($builds['count']) { + return array( + 'status' => 'ignored', + 'message' => sprintf('Duplicate of build #%d', $builds['items'][0]->getId()) + ); + } + + // If not, create a new build job for it: + $build = $this->buildService->createBuild($project, $commitId, $branch, $committer, $commitMessage, $extra); + + return array('status' => 'ok', 'buildID' => $build->getID()); + } + + /** + * Fetch a project and check its type. + * + * @param int $projectId + * @param array|string $expectedType + * + * @return Project + * + * @throws Exception If the project does not exist or is not of the expected type. + */ + protected function fetchProject($projectId, $expectedType) + { + $project = $this->projectStore->getById($projectId); + + if (empty($projectId)) { + throw new Exception('Project does not exist: ' . $projectId); + } + + if (is_array($expectedType) + ? !in_array($project->getType(), $expectedType) + : $project->getType() !== $expectedType + ) { + throw new Exception('Wrong project type: ' . $project->getType()); + } + + return $project; + } +} diff --git a/PHPCI/ErrorHandler.php b/PHPCI/ErrorHandler.php new file mode 100644 index 00000000..d268965d --- /dev/null +++ b/PHPCI/ErrorHandler.php @@ -0,0 +1,68 @@ + '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', + ); + + /** + * Registers an instance of the error handler to throw ErrorException. + */ + public static function register() + { + $handler = new static(); + set_error_handler(array($handler, 'handleError')); + } + + /** + * @param integer $level + * @param string $message + * @param string $file + * @param integer $line + * + * @throws \ErrorException + * + * @internal + */ + public function handleError($level, $message, $file, $line) + { + if (error_reporting() & $level === 0) { + return; + } + + $exceptionLevel = isset($this->levels[$level]) ? $this->levels[$level] : $level; + throw new \ErrorException( + sprintf('%s: %s in %s line %d', $exceptionLevel, $message, $file, $line), + 0, + $level, + $file, + $line + ); + } +} diff --git a/PHPCI/Helper/AnsiConverter.php b/PHPCI/Helper/AnsiConverter.php new file mode 100644 index 00000000..7df6663c --- /dev/null +++ b/PHPCI/Helper/AnsiConverter.php @@ -0,0 +1,55 @@ +convert($text); + } + + /** + * Do not instantiate this class. + */ + private function __construct() + { + } +} diff --git a/PHPCI/Helper/BaseCommandExecutor.php b/PHPCI/Helper/BaseCommandExecutor.php new file mode 100644 index 00000000..b3b47f7b --- /dev/null +++ b/PHPCI/Helper/BaseCommandExecutor.php @@ -0,0 +1,227 @@ +logger = $logger; + $this->quiet = $quiet; + $this->verbose = $verbose; + $this->lastOutput = array(); + $this->rootDir = $rootDir; + } + + /** + * Executes shell commands. + * @param array $args + * @return bool Indicates success + */ + public function executeCommand($args = array()) + { + $this->lastOutput = array(); + + $command = call_user_func_array('sprintf', $args); + $this->logger->logDebug($command); + + if ($this->quiet) { + $this->logger->log('Executing: ' . $command); + } + + $status = 0; + $descriptorSpec = array( + 0 => array("pipe", "r"), // stdin + 1 => array("pipe", "w"), // stdout + 2 => array("pipe", "w"), // stderr + ); + + $pipes = array(); + $process = proc_open($command, $descriptorSpec, $pipes, $this->buildPath, null); + + if (is_resource($process)) { + fclose($pipes[0]); + + $this->lastOutput = stream_get_contents($pipes[1]); + $this->lastError = stream_get_contents($pipes[2]); + + fclose($pipes[1]); + fclose($pipes[2]); + + $status = proc_close($process); + } + + $this->lastOutput = array_filter(explode(PHP_EOL, $this->lastOutput)); + + $shouldOutput = ($this->logExecOutput && ($this->verbose || $status != 0)); + + if ($shouldOutput && !empty($this->lastOutput)) { + $this->logger->log($this->lastOutput); + } + + if (!empty($this->lastError)) { + $this->logger->log("\033[0;31m" . $this->lastError . "\033[0m", LogLevel::ERROR); + } + + $rtn = false; + + if ($status == 0) { + $rtn = true; + } + + return $rtn; + } + + /** + * Returns the output from the last command run. + */ + public function getLastOutput() + { + return implode(PHP_EOL, $this->lastOutput); + } + + /** + * Returns the stderr output from the last command run. + */ + public function getLastError() + { + return $this->lastError; + } + + /** + * Find a binary required by a plugin. + * @param string $binary + * @param bool $quiet + * @return null|string + */ + public function findBinary($binary, $quiet = false) + { + $composerBin = $this->getComposerBinDir(realpath($this->buildPath)); + + if (is_string($binary)) { + $binary = array($binary); + } + + foreach ($binary as $bin) { + $this->logger->log(Lang::get('looking_for_binary', $bin), LogLevel::DEBUG); + + if (is_dir($composerBin) && is_file($composerBin.'/'.$bin)) { + $this->logger->log(Lang::get('found_in_path', $composerBin, $bin), LogLevel::DEBUG); + return $composerBin . '/' . $bin; + } + + if (is_file($this->rootDir . $bin)) { + $this->logger->log(Lang::get('found_in_path', 'root', $bin), LogLevel::DEBUG); + return $this->rootDir . $bin; + } + + if (is_file($this->rootDir . 'vendor/bin/' . $bin)) { + $this->logger->log(Lang::get('found_in_path', 'vendor/bin', $bin), LogLevel::DEBUG); + return $this->rootDir . 'vendor/bin/' . $bin; + } + + $findCmdResult = $this->findGlobalBinary($bin); + if (is_file($findCmdResult)) { + $this->logger->log(Lang::get('found_in_path', '', $bin), LogLevel::DEBUG); + return $findCmdResult; + } + } + + if ($quiet) { + return; + } + throw new Exception(Lang::get('could_not_find', implode('/', $binary))); + } + + /** + * Find a binary which is installed globally on the system + * @param string $binary + * @return null|string + */ + abstract protected function findGlobalBinary($binary); + + /** + * Try to load the composer.json file in the building project + * If the bin-dir is configured, return the full path to it + * @param string $path Current build path + * @return string|null + */ + public function getComposerBinDir($path) + { + if (is_dir($path)) { + $composer = $path.'/composer.json'; + if (is_file($composer)) { + $json = json_decode(file_get_contents($composer)); + + if (isset($json->config->{"bin-dir"})) { + return $path.'/'.$json->config->{"bin-dir"}; + } elseif (is_dir($path . '/vendor/bin')) { + return $path . '/vendor/bin'; + } + } + } + return null; + } + + /** + * Set the buildPath property. + * @param string $path + */ + public function setBuildPath($path) + { + $this->buildPath = $path; + } +} diff --git a/PHPCI/Helper/Build.php b/PHPCI/Helper/Build.php index 3aa7403f..65231b4a 100644 --- a/PHPCI/Helper/Build.php +++ b/PHPCI/Helper/Build.php @@ -2,9 +2,9 @@ /** * PHPCI - Continuous Integration for PHP * - * @copyright Copyright 2013, Block 8 Limited. + * @copyright Copyright 2014, Block 8 Limited. * @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md - * @link http://www.phptesting.org/ + * @link https://www.phptesting.org/ */ namespace PHPCI\Helper; @@ -17,6 +17,11 @@ namespace PHPCI\Helper; */ class Build { + /** + * Returns a more human-friendly version of a plugin name. + * @param $name + * @return mixed + */ public function formatPluginName($name) { return str_replace('Php', 'PHP', ucwords(str_replace('_', ' ', $name))); diff --git a/PHPCI/Helper/BuildInterpolator.php b/PHPCI/Helper/BuildInterpolator.php new file mode 100644 index 00000000..1e7222bb --- /dev/null +++ b/PHPCI/Helper/BuildInterpolator.php @@ -0,0 +1,88 @@ + value pairs that will be used for + * interpolation and environment variables + * @var mixed[] + * @see setupInterpolationVars() + */ + protected $interpolation_vars = array(); + + /** + * Sets the variables that will be used for interpolation. + * @param Build $build + * @param string $buildPath + * @param string $phpCiUrl + */ + public function setupInterpolationVars(Build $build, $buildPath, $phpCiUrl) + { + $this->interpolation_vars = array(); + $this->interpolation_vars['%PHPCI%'] = 1; + $this->interpolation_vars['%COMMIT%'] = $build->getCommitId(); + $this->interpolation_vars['%SHORT_COMMIT%'] = substr($build->getCommitId(), 0, 7); + $this->interpolation_vars['%COMMIT_EMAIL%'] = $build->getCommitterEmail(); + $this->interpolation_vars['%COMMIT_MESSAGE%'] = $build->getCommitMessage(); + $this->interpolation_vars['%COMMIT_URI%'] = $build->getCommitLink(); + $this->interpolation_vars['%BRANCH%'] = $build->getBranch(); + $this->interpolation_vars['%BRANCH_URI%'] = $build->getBranchLink(); + $this->interpolation_vars['%PROJECT%'] = $build->getProjectId(); + $this->interpolation_vars['%BUILD%'] = $build->getId(); + $this->interpolation_vars['%PROJECT_TITLE%'] = $build->getProjectTitle(); + $this->interpolation_vars['%PROJECT_URI%'] = $phpCiUrl . "project/view/" . $build->getProjectId(); + $this->interpolation_vars['%BUILD_PATH%'] = $buildPath; + $this->interpolation_vars['%BUILD_URI%'] = $phpCiUrl . "build/view/" . $build->getId(); + $this->interpolation_vars['%PHPCI_COMMIT%'] = $this->interpolation_vars['%COMMIT%']; + $this->interpolation_vars['%PHPCI_SHORT_COMMIT%'] = $this->interpolation_vars['%SHORT_COMMIT%']; + $this->interpolation_vars['%PHPCI_COMMIT_MESSAGE%'] = $this->interpolation_vars['%COMMIT_MESSAGE%']; + $this->interpolation_vars['%PHPCI_COMMIT_EMAIL%'] = $this->interpolation_vars['%COMMIT_EMAIL%']; + $this->interpolation_vars['%PHPCI_COMMIT_URI%'] = $this->interpolation_vars['%COMMIT_URI%']; + $this->interpolation_vars['%PHPCI_PROJECT%'] = $this->interpolation_vars['%PROJECT%']; + $this->interpolation_vars['%PHPCI_BUILD%'] = $this->interpolation_vars['%BUILD%']; + $this->interpolation_vars['%PHPCI_PROJECT_TITLE%'] = $this->interpolation_vars['%PROJECT_TITLE%']; + $this->interpolation_vars['%PHPCI_PROJECT_URI%'] = $this->interpolation_vars['%PROJECT_URI%']; + $this->interpolation_vars['%PHPCI_BUILD_PATH%'] = $this->interpolation_vars['%BUILD_PATH%']; + $this->interpolation_vars['%PHPCI_BUILD_URI%'] = $this->interpolation_vars['%BUILD_URI%']; + + putenv('PHPCI=1'); + putenv('PHPCI_COMMIT=' . $this->interpolation_vars['%COMMIT%']); + putenv('PHPCI_SHORT_COMMIT=' . $this->interpolation_vars['%SHORT_COMMIT%']); + putenv('PHPCI_COMMIT_MESSAGE=' . $this->interpolation_vars['%COMMIT_MESSAGE%']); + putenv('PHPCI_COMMIT_EMAIL=' . $this->interpolation_vars['%COMMIT_EMAIL%']); + putenv('PHPCI_COMMIT_URI=' . $this->interpolation_vars['%COMMIT_URI%']); + putenv('PHPCI_PROJECT=' . $this->interpolation_vars['%PROJECT%']); + putenv('PHPCI_BUILD=' . $this->interpolation_vars['%BUILD%']); + putenv('PHPCI_PROJECT_TITLE=' . $this->interpolation_vars['%PROJECT_TITLE%']); + putenv('PHPCI_BUILD_PATH=' . $this->interpolation_vars['%BUILD_PATH%']); + putenv('PHPCI_BUILD_URI=' . $this->interpolation_vars['%BUILD_URI%']); + } + + /** + * Replace every occurrence of the interpolation vars in the given string + * Example: "This is build %PHPCI_BUILD%" => "This is build 182" + * @param string $input + * @return string + */ + public function interpolate($input) + { + $keys = array_keys($this->interpolation_vars); + $values = array_values($this->interpolation_vars); + return str_replace($keys, $values, $input); + } +} diff --git a/PHPCI/Helper/CommandExecutor.php b/PHPCI/Helper/CommandExecutor.php new file mode 100644 index 00000000..4f0028eb --- /dev/null +++ b/PHPCI/Helper/CommandExecutor.php @@ -0,0 +1,42 @@ +'; + + protected $emailTo = array(); + protected $emailCc = array(); + protected $subject = 'Email from PHPCI'; + protected $body = ''; + protected $isHtml = false; + protected $config; + + /** + * Create a new email object. + */ + public function __construct() + { + $this->config = Config::getInstance(); + } + + /** + * Set the email's To: header. + * @param string $email + * @param string|null $name + * @return $this + */ + public function setEmailTo($email, $name = null) + { + $this->emailTo[$email] = $name; + + return $this; + } + + /** + * Add an address to the email's CC header. + * @param string $email + * @param string|null $name + * @return $this + */ + public function addCc($email, $name = null) + { + $this->emailCc[$email] = $name; + + return $this; + } + + /** + * Set the email subject. + * @param string $subject + * @return $this + */ + public function setSubject($subject) + { + $this->subject = $subject; + + return $this; + } + + /** + * Set the email body. + * @param string $body + * @return $this + */ + public function setBody($body) + { + $this->body = $body; + + return $this; + } + + /** + * Set whether or not the email body is HTML. + * @param bool $isHtml + * @return $this + */ + public function setHtml($isHtml = false) + { + $this->isHtml = $isHtml; + + return $this; + } + + /** + * Send the email. + * @return bool|int + */ + public function send() + { + $smtpServer = $this->config->get('phpci.email_settings.smtp_address'); + + if (empty($smtpServer)) { + return $this->sendViaMail(); + } else { + return $this->sendViaSwiftMailer(); + } + } + + /** + * Sends the email via the built in PHP mail() function. + * @return bool + */ + protected function sendViaMail() + { + $headers = ''; + + if ($this->isHtml) { + $headers = 'Content-Type: text/html' . PHP_EOL; + } + + $headers .= 'From: ' . $this->getFrom() . PHP_EOL; + + $emailTo = array(); + foreach ($this->emailTo as $email => $name) { + $thisTo = $email; + + if (!is_null($name)) { + $thisTo = '"' . $name . '" <' . $thisTo . '>'; + } + + $emailTo[] = $thisTo; + } + + $emailTo = implode(', ', $emailTo); + + return mail($emailTo, $this->subject, $this->body, $headers); + } + + /** + * Sends the email using SwiftMailer. + * @return int + */ + protected function sendViaSwiftMailer() + { + $factory = new MailerFactory($this->config->get('phpci')); + $mailer = $factory->getSwiftMailerFromConfig(); + + $message = \Swift_Message::newInstance($this->subject) + ->setFrom($this->getFrom()) + ->setTo($this->emailTo) + ->setBody($this->body); + + if ($this->isHtml) { + $message->setContentType('text/html'); + } + + if (is_array($this->emailCc) && count($this->emailCc)) { + $message->setCc($this->emailCc); + } + + return $mailer->send($message); + } + + /** + * Get the from address to use for the email. + * @return mixed|string + */ + protected function getFrom() + { + $email = $this->config->get('phpci.email_settings.from_address', self::DEFAULT_FROM); + + if (empty($email)) { + $email = self::DEFAULT_FROM; + } + + return $email; + } +} diff --git a/PHPCI/Helper/Github.php b/PHPCI/Helper/Github.php new file mode 100644 index 00000000..67173eb4 --- /dev/null +++ b/PHPCI/Helper/Github.php @@ -0,0 +1,175 @@ +get($url, $params); + + return $res['body']; + } + + /** + * Make all GitHub requests following the Link HTTP headers. + * + * @param string $url + * @param mixed $params + * @param array $results + * + * @return array + */ + public function makeRecursiveRequest($url, $params, $results = array()) + { + $http = new HttpClient('https://api.github.com'); + $res = $http->get($url, $params); + + foreach ($res['body'] as $item) { + $results[] = $item; + } + + foreach ($res['headers'] as $header) { + if (preg_match('/^Link: <([^>]+)>; rel="next"/', $header, $r)) { + $host = parse_url($r[1]); + + parse_str($host['query'], $params); + $results = $this->makeRecursiveRequest($host['path'], $params, $results); + + break; + } + } + + return $results; + } + + /** + * Get an array of repositories from Github's API. + */ + public function getRepositories() + { + $token = Config::getInstance()->get('phpci.github.token'); + + if (!$token) { + return null; + } + + $cache = Cache::getCache(Cache::TYPE_APC); + $rtn = $cache->get('phpci_github_repos'); + + if (!$rtn) { + $orgs = $this->makeRequest('/user/orgs', array('access_token' => $token)); + + $params = array('type' => 'all', 'access_token' => $token); + $repos = array('user' => array()); + $repos['user'] = $this->makeRecursiveRequest('/user/repos', $params); + + foreach ($orgs as $org) { + $repos[$org['login']] = $this->makeRecursiveRequest('/orgs/'.$org['login'].'/repos', $params); + } + + $rtn = array(); + foreach ($repos as $repoGroup) { + foreach ($repoGroup as $repo) { + $rtn['repos'][] = $repo['full_name']; + } + } + + $cache->set('phpci_github_repos', $rtn); + } + + return $rtn; + } + + /** + * Create a comment on a specific file (and commit) in a Github Pull Request. + * @param $repo + * @param $pullId + * @param $commitId + * @param $file + * @param $line + * @param $comment + * @return null + */ + public function createPullRequestComment($repo, $pullId, $commitId, $file, $line, $comment) + { + $token = Config::getInstance()->get('phpci.github.token'); + + if (!$token) { + return null; + } + + $url = '/repos/' . strtolower($repo) . '/pulls/' . $pullId . '/comments'; + + $params = array( + 'body' => $comment, + 'commit_id' => $commitId, + 'path' => $file, + 'position' => $line, + ); + + $http = new HttpClient('https://api.github.com'); + $http->setHeaders(array( + 'Content-Type: application/x-www-form-urlencoded', + 'Authorization: Basic ' . base64_encode($token . ':x-oauth-basic'), + )); + + $http->post($url, json_encode($params)); + } + + /** + * Create a comment on a Github commit. + * @param $repo + * @param $commitId + * @param $file + * @param $line + * @param $comment + * @return null + */ + public function createCommitComment($repo, $commitId, $file, $line, $comment) + { + $token = Config::getInstance()->get('phpci.github.token'); + + if (!$token) { + return null; + } + + $url = '/repos/' . strtolower($repo) . '/commits/' . $commitId . '/comments'; + + $params = array( + 'body' => $comment, + 'path' => $file, + 'position' => $line, + ); + + $http = new HttpClient('https://api.github.com'); + $http->setHeaders(array( + 'Content-Type: application/x-www-form-urlencoded', + 'Authorization: Basic ' . base64_encode($token . ':x-oauth-basic'), + )); + + $http->post($url, json_encode($params)); + } +} diff --git a/PHPCI/Helper/Lang.php b/PHPCI/Helper/Lang.php new file mode 100644 index 00000000..42027cee --- /dev/null +++ b/PHPCI/Helper/Lang.php @@ -0,0 +1,200 @@ +get('phpci.basic.language', null); + if (self::setLanguage($language)) { + return; + } + + // Fall back to English: + self::$language = 'en'; + self::$strings = self::loadLanguage(); + } + + /** + * Load a specific language file. + * + * @return string[]|null + */ + protected static function loadLanguage() + { + $langFile = PHPCI_DIR . 'PHPCI/Languages/lang.' . self::$language . '.php'; + + if (!file_exists($langFile)) { + return null; + } + + require($langFile); + + if (is_null($strings) || !is_array($strings) || !count($strings)) { + return null; + } + + return $strings; + } + + /** + * Load the names of all available languages. + */ + protected static function loadAvailableLanguages() + { + $matches = array(); + foreach (glob(PHPCI_DIR . 'PHPCI/Languages/lang.*.php') as $file) { + if (preg_match('/lang\.([a-z]{2}\-?[a-z]*)\.php/', $file, $matches)) { + self::$languages[] = $matches[1]; + } + } + } + + /** + * Create a time tag for localization. + * + * See http://momentjs.com/docs/#/displaying/format/ for a list of supported formats. + * + * @param \DateTime $dateTime The dateTime to represent. + * @param string $format The moment.js format to use. + * + * @return string The formatted tag. + */ + public static function formatDateTime(\DateTime $dateTime, $format = 'lll') + { + return sprintf( + '', + $dateTime->format(\DateTime::ISO8601), + $format, + $dateTime->format(\DateTime::RFC2822) + ); + } +} diff --git a/PHPCI/Helper/LoginIsDisabled.php b/PHPCI/Helper/LoginIsDisabled.php new file mode 100644 index 00000000..ec23858c --- /dev/null +++ b/PHPCI/Helper/LoginIsDisabled.php @@ -0,0 +1,37 @@ + +* @package PHPCI +* @subpackage Web +*/ +class LoginIsDisabled +{ + /** + * Checks if + * @param $method + * @param array $params + * @return mixed|null + */ + public function __call($method, $params = array()) + { + unset($method, $params); + + $config = Config::getInstance(); + $state = (bool) $config->get('phpci.authentication_settings.state', false); + + return (false !== $state); + } +} diff --git a/PHPCI/Helper/MailerFactory.php b/PHPCI/Helper/MailerFactory.php new file mode 100644 index 00000000..c84c068d --- /dev/null +++ b/PHPCI/Helper/MailerFactory.php @@ -0,0 +1,88 @@ +emailConfig = isset($config['email_settings']) ? $config['email_settings'] : array(); + } + + /** + * Returns an instance of Swift_Mailer based on the config.s + * @return \Swift_Mailer + */ + public function getSwiftMailerFromConfig() + { + $encryptionType = $this->getMailConfig('smtp_encryption'); + + // Workaround issue where smtp_encryption could == 1 in the past by + // checking it is a valid transport + if ($encryptionType && !in_array($encryptionType, stream_get_transports())) { + $encryptionType = null; + } + + /** @var \Swift_SmtpTransport $transport */ + $transport = \Swift_SmtpTransport::newInstance( + $this->getMailConfig('smtp_address'), + $this->getMailConfig('smtp_port'), + $encryptionType + ); + $transport->setUsername($this->getMailConfig('smtp_username')); + $transport->setPassword($this->getMailConfig('smtp_password')); + + return \Swift_Mailer::newInstance($transport); + } + + /** + * Return a specific configuration value by key. + * @param $configName + * @return null|string + */ + public function getMailConfig($configName) + { + if (isset($this->emailConfig[$configName]) && $this->emailConfig[$configName] != "") { + return $this->emailConfig[$configName]; + } else { + // Check defaults + + switch ($configName) { + case 'smtp_address': + return "localhost"; + case 'default_mailto_address': + return null; + case 'smtp_port': + return '25'; + case 'smtp_encryption': + return null; + default: + return ""; + } + } + } +} diff --git a/PHPCI/Helper/SshKey.php b/PHPCI/Helper/SshKey.php new file mode 100644 index 00000000..932686f2 --- /dev/null +++ b/PHPCI/Helper/SshKey.php @@ -0,0 +1,59 @@ + '', 'public_key' => ''); + + $output = @shell_exec('ssh-keygen -t rsa -b 2048 -f '.$keyFile.' -N "" -C "deploy@phpci"'); + + if (!empty($output)) { + $pub = file_get_contents($keyFile . '.pub'); + $prv = file_get_contents($keyFile); + + if (!empty($pub)) { + $return['public_key'] = $pub; + } + + if (!empty($prv)) { + $return['private_key'] = $prv; + } + } + + return $return; + } +} diff --git a/PHPCI/Helper/UnixCommandExecutor.php b/PHPCI/Helper/UnixCommandExecutor.php new file mode 100644 index 00000000..b922caff --- /dev/null +++ b/PHPCI/Helper/UnixCommandExecutor.php @@ -0,0 +1,27 @@ + 'Dansk', + 'language' => 'Sprog', + + // Log in: + 'log_in_to_phpci' => 'Log ind i PHPCI', + 'login_error' => 'Forkert email-adresse eller adgangskode', + 'forgotten_password_link' => 'Har du glemt din adgangskode?', + 'reset_emailed' => 'Vi har sendt dig en email med et link til at nulstille din adgangskode.', + 'reset_header' => 'Bare rolig!
Indtast blot din email-adresse, så sender +vi dig et link til at nulstille din adgangskode.', + 'reset_email_address' => 'Indtast din email-adresse:', + 'reset_send_email' => 'Send nulstillings-link', + 'reset_enter_password' => 'Indtast venligst en ny adgangskode', + 'reset_new_password' => 'Ny adgangskode:', + 'reset_change_password' => 'Skift adgangskode', + 'reset_no_user_exists' => 'Der findes ingen bruger med den email-adresse, prøv igen.', + 'reset_email_body' => 'Hej %s, + +Du modtager denne email fordi du eller en anden person har anmodet om at nulstille din adgangskode til PHPCI. + +Hvis det var dig kan du klikke følgende link for at nulstille din adgangskode: %ssession/reset-password/%d/%s + +Hvis det ikke var dig kan du ignorere denne email og intet vil ske. + +Tak, + +PHPCI', + + 'reset_email_title' => 'PHPCI Adgangskode-nulstilling for %s', + 'reset_invalid' => 'Ugyldig anmodning om adgangskode-nulstilling.', + 'email_address' => 'Email-addresse', + 'login' => 'Login / Email Address', + 'password' => 'Adgangskode', + 'log_in' => 'Log ind', + + + // Top Nav + 'toggle_navigation' => 'Vis/skjul navigation', + 'n_builds_pending' => '%d builds i køen', + 'n_builds_running' => '%d builds kører', + 'edit_profile' => 'Redigér profil', + 'sign_out' => 'Log ud', + 'branch_x' => 'Branch: %s', + 'created_x' => 'Oprettet: %s', + 'started_x' => 'Startet: %s', + + // Sidebar + 'hello_name' => 'Hej %s', + 'dashboard' => 'Dashboard', + 'admin_options' => 'Administrator-indstillinger', + 'add_project' => 'Tilføj projekt', + 'settings' => 'Indstillinger', + 'manage_users' => 'Administrér brugere', + 'plugins' => 'Plugins', + 'view' => 'Vis', + 'build_now' => 'Start build nu', + 'edit_project' => 'Redigér projekt', + 'delete_project' => 'Slet projekt', + + // Project Summary: + 'no_builds_yet' => 'Ingen builds pt.!', + 'x_of_x_failed' => '%d af de sidste %d builds fejlede.', + 'x_of_x_failed_short' => '%d / %d fejlede.', + 'last_successful_build' => 'Sidste succesfulde build var %s.', + 'never_built_successfully' => 'Dette projekt har indtil videre ingen succesfulde builds.', + 'all_builds_passed' => 'All de sidste %d builds fejlede.', + 'all_builds_passed_short' => '%d / %d lykkedes.', + 'last_failed_build' => 'Det sidste mislykkede build var %s', + 'never_failed_build' => 'Dette projekt er endnu ikke blevet kørt.', + 'view_project' => 'Vis Projekt', + + // Timeline: + 'latest_builds' => 'Nyeste Builds', + 'pending' => 'Venter', + 'running' => 'Kører', + 'success' => 'Succes', + 'successful' => 'Lykkedes', + 'failed' => 'Fejlede', + 'manual_build' => 'Manuelt Build', + + // Add/Edit Project: + 'new_project' => 'Nyt Projekt', + 'project_x_not_found' => 'Projektet med ID %d findes ikke.', + 'project_details' => 'Projekt-detaljer', + 'public_key_help' => 'For at gøre det lettere at starte har vi genereret en SSH-nøgle som du kan bruge til dette projekt. For at bruge den behøver du blot tilføje den følgende public key til "deployment keys" sektionen +i din foretrukne hosting-platform.', + 'select_repository_type' => 'Vælg repository-type...', + 'github' => 'GitHub', + 'bitbucket' => 'Bitbucket', + 'gitlab' => 'GitLab', + 'remote' => 'Ekstern URL', + 'local' => 'Lokalt filsystem', + 'hg' => 'Mercurial', + + 'where_hosted' => 'Hvor er dit projekt hosted?', + 'choose_github' => 'Vælg et GitHub-repository:', + + 'repo_name' => 'Repository-navn / URL (ekstern) eller filsystem-sti (lokal)', + 'project_title' => 'Projekt-titel', + 'project_private_key' => 'Privat nøgle med adgang til dette repository +(tom for lokal nøgle og/eller anonym adgang)', + 'build_config' => 'PHPCI build-konfiguration for dette projekt +(hvis du ikke har mulighed for at tilføje en phpci.yml fil i projektets repository)', + 'default_branch' => 'Default branch navn', + 'allow_public_status' => 'Tillad offentlig status-side og -billede for dette projekt?', + 'archived' => 'Archived', + 'archived_menu' => 'Archived', + 'save_project' => 'Gem Projekt', + + 'error_mercurial' => 'Mercurial repository-URL skal starte med http:// eller https://', + 'error_remote' => 'Repository-URL skal starte med git://, http:// eller https://', + 'error_gitlab' => 'GitLab repository-navn skal være i formatet "user@domæne.tld:ejernavn/repositorynavn.git"', + 'error_github' => 'Repository-navn skal være i formatet "ejernavn/repositorynavn"', + 'error_bitbucket' => 'Repository-navn skal være i formatet "ejernavn/repositorynavn"', + 'error_path' => 'Stien du indtastede findes ikke.', + + // View Project: + 'all_branches' => 'Alle branches', + 'builds' => 'Builds', + 'id' => 'ID', + 'date' => 'Date', + 'project' => 'Projekt', + 'commit' => 'Commit', + 'branch' => 'Branch', + 'status' => 'Status', + 'prev_link' => '« Forrige', + 'next_link' => 'Næste »', + 'public_key' => 'Offentlig nøgle', + 'delete_build' => 'Slet Build', + + 'webhooks' => 'Webhooks', + 'webhooks_help_github' => 'For at køre dette build automatisk når nye commits bliver pushed skal du tilføje nedenstående +URL som nyt "Webhook" i Webhooks +and Services under dit GitHub-repository.', + + 'webhooks_help_gitlab' => 'For at køre dette build automatisk når nye commits bliver pushed kan du tilføje nedenstående URL +som en "WebHook URL" i Web Hooks-sektionen i dit GitLab-repository.', + + 'webhooks_help_bitbucket' => 'For at køre dette build automatisk når nye commits bliver pushed skal du tilføje nedenstående +URL som "POST" service i + +Services sektionen under dit Bitbucket-repository.', + + // View Build + 'build_x_not_found' => 'Build med ID %d findes ikke.', + 'build_n' => 'Build %d', + 'rebuild_now' => 'Gentag Build', + + + 'committed_by_x' => 'Committed af %s', + 'commit_id_x' => 'Commit: %s', + + 'chart_display' => 'Denne graf vises når buildet er færdigt.', + + 'build' => 'Build', + 'lines' => 'Linjer', + 'comment_lines' => 'Kommentar-linjer', + 'noncomment_lines' => 'Ikke-kommentar-linjer', + 'logical_lines' => 'Logiske linjer', + 'lines_of_code' => 'Kode-linjer', + 'build_log' => 'Build-log', + 'quality_trend' => 'Kvalitets-trend', + 'codeception_errors' => 'Codeception-fejl', + 'phpmd_warnings' => 'PHPMD-advarsler', + 'phpcs_warnings' => 'PHPCS-advarsler', + 'phpcs_errors' => 'PHPCS-fejl', + 'phplint_errors' => 'Lint-fejl', + 'phpunit_errors' => 'PHPUnit-fejl', + 'phpdoccheck_warnings' => 'Manglende Docblocks', + 'issues' => 'Sager', + + 'codeception' => 'Codeception', + 'phpcpd' => 'PHP Copy/Paste Detector', + 'phpcs' => 'PHP Code Sniffer', + 'phpdoccheck' => 'Manglende Docblocks', + 'phpmd' => 'PHP Mess Detector', + 'phpspec' => 'PHP Spec', + 'phpunit' => 'PHP Unit', + + 'file' => 'Fil', + 'line' => 'Linje', + 'class' => 'Klasse', + 'method' => 'Funktion', + 'message' => 'Besked', + 'start' => 'Start', + 'end' => 'Slut', + 'from' => 'Fra', + 'to' => 'Til', + 'result' => 'Resultat', + 'ok' => 'OK', + 'took_n_seconds' => 'Tog %d sekunder', + 'build_created' => 'Build Oprettet', + 'build_started' => 'Build Startet', + 'build_finished' => 'Build Afsluttet', + 'test_message' => 'Message', + 'test_no_message' => 'No message', + 'test_success' => 'Successful: %d', + 'test_fail' => 'Failures: %d', + 'test_skipped' => 'Skipped: %d', + 'test_error' => 'Errors: %d', + 'test_todo' => 'Todos: %d', + 'test_total' => '%d test(s)', + + // Users + 'name' => 'Navn', + 'password_change' => 'Adgangskode (tom hvis du ikke ønsker at ændre koden)', + 'save' => 'Gem »', + 'update_your_details' => 'Opdatér oplysninger', + 'your_details_updated' => 'Dine oplysninger blev gemt.', + 'add_user' => 'Tilføj bruger', + 'is_admin' => 'Administrator?', + 'yes' => 'Ja', + 'no' => 'Nej', + 'edit' => 'Redigér', + 'edit_user' => 'Redigér Bruger', + 'delete_user' => 'Slet Bruger', + 'user_n_not_found' => 'Brugeren med ID %d findes ikke.', + 'is_user_admin' => 'Er denne bruger en administrator?', + 'save_user' => 'Gem Bruger', + + // Settings: + 'settings_saved' => 'Dine indstillinger blev gemt.', + 'settings_check_perms' => 'Dine indstillinger kunne ikke gemmes, kontrollér rettighederne på din config.yml fil.', + 'settings_cannot_write' => 'PHPCI kan ikke skrive til din config.yml fil, indstillinger bliver muligvis ikke gemt korrekt før dette problem løses.', + 'settings_github_linked' => 'Din GitHub-konto er nu tilsluttet.', + 'settings_github_not_linked' => 'Din GitHub-konto kunne ikke tilsluttes.', + 'build_settings' => 'Build-indstillinger', + 'github_application' => 'GitHub-applikation', + 'github_sign_in' => 'Før du kan bruge GitHub skal du logge ind og give PHPCI +adgang til din konto.', + 'github_phpci_linked' => 'PHPCI blev tilsluttet din GitHub-konto.', + 'github_where_to_find' => 'Hvor disse findes...', + 'github_where_help' => 'Hvis du ejer applikationen du ønsker at bruge kan du finde denne information i +applications under indstillinger.', + + 'email_settings' => 'Email-indstillinger', + 'email_settings_help' => 'Før PHPCI kan sende build-notifikationer via email +skal du konfigurere nedenstående SMTP-indstillinger.', + + 'application_id' => 'Application ID', + 'application_secret' => 'Application Secret', + + 'smtp_server' => 'SMTP-server', + 'smtp_port' => 'SMTP-port', + 'smtp_username' => 'SMTP-brugernavn', + 'smtp_password' => 'SMTP-adgangskode', + 'from_email_address' => 'Fra email-adresse', + 'default_notification_address' => 'Default notifikations-email-adresse', + 'use_smtp_encryption' => 'Brug SMTP-kryptering?', + 'none' => 'Ingen', + 'ssl' => 'SSL', + 'tls' => 'TLS', + + 'failed_after' => 'Betragt et build som fejlet efter', + '5_mins' => '5 minutter', + '15_mins' => '15 minutter', + '30_mins' => '30 minutter', + '1_hour' => '1 time', + '3_hours' => '3 timer', + + // Plugins + 'cannot_update_composer' => 'PHPCI kan ikke opdatere composer.json da filen ikke kan skrives.', + 'x_has_been_removed' => '%s er blevet slettet.', + 'x_has_been_added' => '%s blev tilføjet til composer.json for dig og vil blive installeret næste gang +du kører composer update.', + 'enabled_plugins' => 'Aktive plugins', + 'provided_by_package' => 'Via pakke', + 'installed_packages' => 'Installerede pakker', + 'suggested_packages' => 'Forslag til pakker', + 'title' => 'Titel', + 'description' => 'Beskrivelse', + 'version' => 'Version', + 'install' => 'Installér »', + 'remove' => 'Fjern »', + 'search_packagist_for_more' => 'Søg på Packagist efter flere pakker', + 'search' => 'Søg »', + + // Installer + 'installation_url' => 'PHPCI Installations-URL', + 'db_host' => 'Database-hostnavn', + 'db_name' => 'Database-navn', + 'db_user' => 'Database-brugernavn', + 'db_pass' => 'Database-adgangskode', + 'admin_name' => 'Administrator-navn', + 'admin_pass' => 'Administrator-adgangskode', + 'admin_email' => 'Administrators email-adresse', + 'config_path' => 'Konfigurations-fil', + 'install_phpci' => 'Installér PHPCI', + 'welcome_to_phpci' => 'Velkommen til PHPCI', + 'please_answer' => 'Besvar venligst følgende spørgsmål:', + 'phpci_php_req' => 'PHPCI kræver minimum PHP version 5.3.8 for at fungere.', + 'extension_required' => 'Extension påkrævet: %s', + 'function_required' => 'PHPCI behøver adgang til funktion %s() i PHP. Er den deaktiveret i php.ini?', + 'requirements_not_met' => 'PHPCI kan ikke installeres da nogle krav ikke opfyldtes. +Kontrollér venligst nedenstående fejl før du fortsætter.', + 'must_be_valid_email' => 'Skal være en gyldig email-adresse.', + 'must_be_valid_url' => 'Skal være en gyldig URL.', + 'enter_name' => 'Administrator-navn: ', + 'enter_email' => 'Administrators email-adresse: ', + 'enter_password' => 'Administrator-adgangskode: ', + 'enter_phpci_url' => 'Din PHPCI URL (eksempelvis "http://phpci.local"): ', + + 'enter_db_host' => 'Indtast dit MySQL-hostnavn [localhost]: ', + 'enter_db_name' => 'Indtast dit MySQL database-navn [phpci]: ', + 'enter_db_user' => 'Indtast dit MySQL-brugernavn [phpci]: ', + 'enter_db_pass' => 'Indtast dit MySQL-password: ', + 'could_not_connect' => 'PHPCI kunne ikke forbinde til MySQL med de angivning oplysninger. Forsøg igen.', + 'setting_up_db' => 'Indlæser database...', + 'user_created' => 'Brugerkonto oprettet!', + 'failed_to_create' => 'PHPCI kunne ikke oprette din administrator-konto.', + 'config_exists' => 'PHPCI konfigurationsfilen findes og er ikke tom.', + 'update_instead' => 'Hvis du forsøgte at opdatere PHPCI, forsøg da venligst med phpci:update istedet.', + + // Update + 'update_phpci' => 'Opdatér databasen med ændrede modeller', + 'updating_phpci' => 'Opdaterer PHPCI-database:', + 'not_installed' => 'PHPCI lader til ikke at være installeret.', + 'install_instead' => 'Installér venligst PHPCI via phpci:install istedet.', + + // Poll Command + 'poll_github' => 'Check via GitHub om et build skal startes.', + 'no_token' => 'GitHub-token findes ikke', + 'finding_projects' => 'Finder projekter der kan forespørges', + 'found_n_projects' => '%d projekter fundet', + 'last_commit_is' => 'Sidste commit til GitHub for %s er %s', + 'adding_new_build' => 'Sidste commit er forskellig fra databasen, tilføjer nyt build.', + 'finished_processing_builds' => 'Kørsel af builds afsluttet.', + + // Create Admin + 'create_admin_user' => 'Tilføj en administrator', + 'incorrect_format' => 'Forkert format', + + // Create Build Command + 'create_build_project' => 'Create a build for a project', + 'project_id_argument' => 'A project ID', + 'commit_id_option' => 'Commit ID to build', + 'branch_name_option' => 'Branch to build', + + // Run Command + 'run_all_pending' => 'Kør alle PHPCI builds i køen.', + 'finding_builds' => 'Finder builds der skal køres', + 'found_n_builds' => '%d builds fundet', + 'skipping_build' => 'Springer over Build %d - projektet kører et build lige nu.', + 'marked_as_failed' => 'Build %d blev markeret som fejlet pga. timeout.', + + // Builder + 'missing_phpci_yml' => 'Dette projekt har ingen phpci.yml fil, eller filen er tom.', + 'build_success' => 'BUILD SUCCES', + 'build_failed' => 'BUILD FEJLET', + 'removing_build' => 'Fjerner Build', + 'exception' => 'Undtagelse:', + 'could_not_create_working' => 'Kunne ikke oprette en arbejds-kopi.', + 'working_copy_created' => 'Arbejds-kopi oprettet: %s', + 'looking_for_binary' => 'Leder efter kommando: %s', + 'found_in_path' => 'Fundet i %s: %s', + 'running_plugin' => 'KØRER PLUGIN: %s', + 'plugin_success' => 'PLUGIN: SUCCES', + 'plugin_failed' => 'PLUGIN: FEJL', + 'plugin_missing' => 'Plugin findes ikke: %s', + 'tap_version' => 'TapParser understøtter kun TAP version 13.', + 'tap_error' => 'Ugyldig TAP-streng, antallet af tests passer ikke med det angivne antal tests.', + + // Build Plugins: + 'no_tests_performed' => 'Ingen tests udført.', + 'could_not_find' => 'Kunne ikke finde %s', + 'no_campfire_settings' => 'Ingen forbindelses-oplysninger angivet i Campfire plugin', + 'failed_to_wipe' => 'Kunne ikke slette eksisterende mappe %s før kopi', + 'passing_build' => 'Succesfuldt Build', + 'failing_build' => 'Fejlet Build', + 'log_output' => 'Log-output:', + 'n_emails_sent' => '%d emails afsendt.', + 'n_emails_failed' => '%d emails kunne ikke afsendes.', + 'unable_to_set_env' => 'Kunne ikke sætte environment-variabel', + 'tag_created' => 'Tag oprettet af PHPCI: %s', + 'x_built_at_x' => '%PROJECT_TITLE% bygget på %BUILD_URI%', + 'hipchat_settings' => 'Angiv venligst rum og autoToken i hipchat_notify plugin', + 'irc_settings' => 'Du skal som minimum indstille en server, et rum og et nicknavn.', + 'invalid_command' => 'Ugyldig kommando', + 'import_file_key' => 'Importen skal indeholde en \'file\' variabel', + 'cannot_open_import' => 'Kunne ikke åbne SQL import-fil: %s', + 'unable_to_execute' => 'Kunne ikke udføre instruktionerne i SQL-filen', + 'phar_internal_error' => 'Phar Plugin Internal Error', + 'build_file_missing' => 'Den angivne build-fil findes ikke.', + 'property_file_missing' => 'Den angivne property-fil findes ikke', + 'could_not_process_report' => 'Kunne ikke behandle rapporten, som dette værktøj genererede.', + 'shell_not_enabled' => 'Shell-plugin er ikke aktiveret. Aktivér det via config.yml.' +); diff --git a/PHPCI/Languages/lang.de.php b/PHPCI/Languages/lang.de.php new file mode 100644 index 00000000..8e5bb3b2 --- /dev/null +++ b/PHPCI/Languages/lang.de.php @@ -0,0 +1,433 @@ + 'Deutsch', + 'language' => 'Sprache', + + // Log in: + 'log_in_to_phpci' => 'In PHPCI einloggen', + 'login_error' => 'Fehlerhafte Emailadresse oder fehlerhaftes Passwort', + 'forgotten_password_link' => 'Passwort vergessen?', + 'reset_emailed' => 'Wir haben Ihnen einen Link geschickt, um Ihr Passwort zurückzusetzen', + 'reset_header' => 'Keine Panik!
Geben Sie einfach unten Ihre Emailadresse an + und wir senden Ihnen einen Link, um Ihr Passwort zurückzusetzen', + 'reset_email_address' => 'Geben Sie Ihre Emailadresse an:', + 'reset_send_email' => 'Link senden', + 'reset_enter_password' => 'Bitte geben Sie ein neues Passwort ein', + 'reset_new_password' => 'Neues Passwort:', + 'reset_change_password' => 'Passwort ändern', + 'reset_no_user_exists' => 'Es existiert kein User mit dieser Emailadresse, versuchen Sie es bitte noch einmal.', + 'reset_email_body' => 'Hallo %s, + +Sie haben diese Email erhalten, weil Sie, oder jemand anders, einen Link zum Zurücksetzen Ihres Passwortes für PHPCI verlangt hat. + +Wenn Sie diesen Link verlangt haben, klicken Sie bitte hier, um Ihr Passwort zurückzusetzen: %ssession/reset-password/%d/%s + +Falls nicht, ignorieren Sie diese Email bitte, und es wird nichts geändert. + +Danke, + +PHPCI', + + 'reset_email_title' => 'PHPCI Passwort zurücksetzen für %s', + 'reset_invalid' => 'Fehlerhafte Anfrage für das Zurücksetzen eines Passwortes', + 'email_address' => 'Emailadresse', + 'login' => 'Login / Emailadresse', + 'password' => 'Passwort', + 'log_in' => 'Einloggen', + + + // Top Nav + 'toggle_navigation' => 'Navigation umschalten', + 'n_builds_pending' => '%d Builds ausstehend', + 'n_builds_running' => '%d Builds werden ausgeführt', + 'edit_profile' => 'Profil bearbeiten', + 'sign_out' => 'Ausloggen', + 'branch_x' => 'Branch: %s', + 'created_x' => 'Erstellt: %s', + 'started_x' => 'Gestartet: %s', + + // Sidebar + 'hello_name' => 'Hallo, %s', + 'dashboard' => 'Dashboard', + 'admin_options' => 'Administration', + 'add_project' => 'Projekt hinzufügen', + 'settings' => 'Einstellungen', + 'manage_users' => 'Benutzereinstellungen', + 'plugins' => 'Plugins', + 'view' => 'Ansehen', + 'build_now' => 'Jetzt bauen', + 'edit_project' => 'Projekt bearbeiten', + 'delete_project' => 'Projekt löschen', + + // Project Summary: + 'no_builds_yet' => 'Bisher noch keine Builds!', + 'x_of_x_failed' => '%d der letzten %d Builds sind fehlgeschlagen.', + 'x_of_x_failed_short' => '%d / %d fehlgeschlagen.', + 'last_successful_build' => ' Der letzte erfolgreiche Build war %s.', + 'never_built_successfully' => ' Dieses Projekt hatte bisher noch keinen erfolgreichen Build.', + 'all_builds_passed' => 'Jeder der letzten %d Builds war erfolgreich.', + 'all_builds_passed_short' => '%d / %d erfolgreich.', + 'last_failed_build' => ' Der letzte fehlgeschlagene Build war %s.', + 'never_failed_build' => ' Dieses Projekt hat keine fehlgeschlagenen Builds.', + 'view_project' => 'Projekt ansehen', + + // Timeline: + 'latest_builds' => 'Die neusten Builds', + 'pending' => 'Ausstehend', + 'running' => 'Wird ausgeführt', + 'success' => 'Erfolg', + 'successful' => 'Erfolgreich', + 'failed' => 'Fehlgeschlagen', + 'manual_build' => 'Manueller Build', + + // Add/Edit Project: + 'new_project' => 'Neues Projekt', + 'project_x_not_found' => 'Projekt mit ID %d existiert nicht.', + 'project_details' => 'Projektdetails', + 'public_key_help' => 'Um Ihnen den Einstieg zu erleichtern, haben wir ein SSH-Key-Paar für dieses Projekt +generiert. Um es zu verwenden, fügen Sie einfach den folgenden Public Key im Abschnitt +"Deploy Keys" Ihrer bevorzugten Quellcodehostingplattform hinzu.', + 'select_repository_type' => 'Wählen Sie den Typ des Repositories...', + 'github' => 'GitHub', + 'bitbucket' => 'Bitbucket', + 'gitlab' => 'GitLab', + 'remote' => 'Externe URL', + 'local' => 'Lokaler Pfad', + 'hg' => 'Mercurial', + 'svn' => 'Subversion', + + 'where_hosted' => 'Wo wird Ihr Projekt gehostet?', + 'choose_github' => 'Wählen Sie ein GitHub Repository:', + + 'repo_name' => 'Name/URL (extern) oder Pfad (lokal) des Repositories', + 'project_title' => 'Projekttitel', + 'project_private_key' => 'Private Key für den Zugang zum Repository + (leer lassen für lokale und oder anonyme externe Zugriffe)', + 'build_config' => 'PHPCI Buildkonfiguration für dieses Projekt + (falls Sie Ihrem Projektrepository kein phpci.yml hinzufügen können)', + 'default_branch' => 'Name des Standardbranches', + 'allow_public_status' => 'Öffentliche Statusseite und -bild für dieses Projekt einschalten?', + 'archived' => 'Archiviert', + 'archived_menu' => 'Archiviert', + 'save_project' => 'Projekt speichern', + + 'error_mercurial' => 'Mercurial Repository-URL muss mit http://, oder https:// beginnen', + 'error_remote' => 'Repository-URL muss mit git://, http://, oder https:// beginnen', + 'error_gitlab' => 'GitLab Repositoryname muss im Format "user@domain.tld:owner/repo.git" sein', + 'error_github' => 'Repositoryname muss im Format "besitzer/repo" sein', + 'error_bitbucket' => 'Repositoryname muss im Format "besitzer/repo" sein', + 'error_path' => 'Der angegebene Pfad existiert nicht', + + // View Project: + 'all_branches' => 'Alle Branches', + 'builds' => 'Builds', + 'id' => 'ID', + 'date' => 'Datum', + 'project' => 'Projekt', + 'commit' => 'Commit', + 'branch' => 'Branch', + 'status' => 'Status', + 'prev_link' => '« Vorherige', + 'next_link' => 'Nächste »', + 'public_key' => 'Public Key', + 'delete_build' => 'Build löschen', + + 'webhooks' => 'Webhooks', + 'webhooks_help_github' => 'Um für dieses Projekt automatisch einen Build zu starten, wenn neue Commits gepushed + werden, fügen Sie die untenstehende URL in der + Webhooks and Services-Sektion Ihres + GitHub Repositories als neuen "Webhook" hinzu.', + + 'webhooks_help_gitlab' => 'Um für dieses Projekt automatisch einen Build zu starten, wenn neue Commits gepushed werden, fügen Sie die untenstehende URL in der Web Hooks Sektion Ihres GitLab Repositories hinzu.', + + 'webhooks_help_bitbucket' => 'Um für dieses Projekt automatisch einen Build zu starten, wenn neue Commits gepushed werden, fügen Sie die untenstehende URL als "POST" Service in der Services-Sektion Ihres Bitbucket Repositories hinzu.', + + // View Build + 'errors' => 'Fehler', + 'information' => 'Information', + + 'build_x_not_found' => 'Build mit ID %d existiert nicht.', + 'build_n' => 'Build %d', + 'rebuild_now' => 'Build neu starten', + + + 'committed_by_x' => 'Committed von %s', + 'commit_id_x' => 'Commit: %s', + + 'chart_display' => 'Dieses Diagramm wird angezeigt, sobald der Build abgeschlossen ist.', + + 'build' => 'Build', + 'lines' => 'Zeilen', + 'comment_lines' => 'Kommentarzeilen', + 'noncomment_lines' => 'Nicht-Kommentarzeilen', + 'logical_lines' => 'Zeilen mit Logik', + 'lines_of_code' => 'Anzahl Codezeilen', + 'build_log' => 'Buildprotokoll', + 'quality_trend' => 'Qualitätstrend', + 'codeception_errors' => 'Codeception Errors', + 'phpmd_warnings' => 'PHPMD Warnings', + 'phpcs_warnings' => 'PHPCS Warnings', + 'phpcs_errors' => 'PHPCS Errors', + 'phplint_errors' => 'Lint Errors', + 'phpunit_errors' => 'PHPUnit Errors', + 'phpdoccheck_warnings' => 'Fehlende Docblocks', + 'issues' => 'Probleme', + + 'codeception' => 'Codeception', + 'phpcpd' => 'PHP Copy/Paste Detector', + 'phpcs' => 'PHP Code Sniffer', + 'phpdoccheck' => 'Fehlende Docblocks', + 'phpmd' => 'PHP Mess Detector', + 'phpspec' => 'PHP Spec', + 'phpunit' => 'PHP Unit', + 'technical_debt' => 'Technische Schulden', + 'behat' => 'Behat', + + 'codeception_feature' => 'Feature', + 'codeception_suite' => 'Suite', + 'codeception_time' => 'Zeit', + 'codeception_synopsis' => '%1$d Tests in %2$f Sekunden ausgeführt. + %3$d Fehler.', + + 'file' => 'Datei', + 'line' => 'Zeile', + 'class' => 'Klasse', + 'method' => 'Methode', + 'message' => 'Nachricht', + 'start' => 'Start', + 'end' => 'Ende', + 'from' => 'Von', + 'to' => 'Bis', + 'result' => 'Resultat', + 'ok' => 'OK', + 'took_n_seconds' => 'Benötigte %d Sekunden', + 'build_created' => 'Build erstellt', + 'build_started' => 'Build gestartet', + 'build_finished' => 'Build abgeschlossen', + 'test_message' => 'Nachricht', + 'test_no_message' => 'Keine Nachricht', + 'test_success' => 'Erfolgreich: %d', + 'test_fail' => 'Fehlschläge: %d', + 'test_skipped' => 'Übersprungen: %d', + 'test_error' => 'Fehler: %d', + 'test_todo' => 'Todos: %d', + 'test_total' => '%d Test(s)', + + // Users + 'name' => 'Name', + 'password_change' => 'Passwort (leerlassen, wenn Sie es nicht ändern möchten)', + 'save' => 'Speichern »', + 'update_your_details' => 'Aktualisieren Sie Ihre Details', + 'your_details_updated' => 'Ihre Details wurden aktualisiert.', + 'add_user' => 'Benutzer hinzufügen', + 'is_admin' => 'Administrator?', + 'yes' => 'Ja', + 'no' => 'Nein', + 'edit' => 'Bearbeiten', + 'edit_user' => 'Benutzer bearbeiten', + 'delete_user' => 'Benutzer löschen', + 'user_n_not_found' => 'Benutzer mit ID %d existiert nicht.', + 'is_user_admin' => 'Ist dieser Benutzer Administrator?', + 'save_user' => 'Benutzer speichern', + + // Settings: + 'settings_saved' => 'Ihre Einstellungen wurden gespeichert.', + 'settings_check_perms' => 'Ihre Einstellungen konnten nicht gespeichert werden, bitte überprüfen Sie die + Berechtigungen Ihrer config.yml-Datei', + 'settings_cannot_write' => 'PHPCI konnte config.yml nicht schreiben. Einstellungen könnten nicht richtig gespeichert werden, bis das Problem behoben ist.', + 'settings_github_linked' => 'Ihr GitHub-Konto wurde verknüpft.', + 'settings_github_not_linked' => 'Ihr GitHub-Konto konnte nicht verknüpft werden.', + 'build_settings' => 'Buildeinstellungen', + 'github_application' => 'GitHub-Applikation', + 'github_sign_in' => 'Bevor Sie anfangen GitHub zu verwenden, müssen Sie sich erst einloggen und PHPCI Zugriff auf Ihr Nutzerkonto gewähren', + 'github_phpci_linked' => 'PHPCI wurde erfolgreich mit Ihrem GitHub-Konto verknüpft.', + 'github_where_to_find' => 'Wo Sie diese finden...', + 'github_where_help' => 'Wenn Sie der Besitzer der Applikation sind, die Sie gerne verwenden möchten, können Sie + diese Einstellungen in Ihrem "applications + settings"-Bereich finden.', + + 'email_settings' => 'Emaileinstellungen', + 'email_settings_help' => 'Bevor PHPCI E-Mails zum Buildstatus verschicken kann, + müssen Sie Ihre SMTP-Einstellungen unten konfigurieren', + + 'application_id' => 'Applikations-ID', + 'application_secret' => 'Applikations-Secret', + + 'smtp_server' => 'SMTP Server', + 'smtp_port' => 'SMTP Port', + 'smtp_username' => 'SMTP Benutzername', + 'smtp_password' => 'SMTP Passwort', + 'from_email_address' => 'Absenderadresse', + 'default_notification_address' => 'Standardadresse für Benachrichtigungen', + 'use_smtp_encryption' => 'SMTP-Verschlüsselung verwenden?', + 'none' => 'Keine', + 'ssl' => 'SSL', + 'tls' => 'TLS', + + 'failed_after' => 'Einen Build als fehlgeschlagen ansehen nach', + '5_mins' => '5 Minuten', + '15_mins' => '15 Minuten', + '30_mins' => '30 Minuten', + '1_hour' => '1 Stunde', + '3_hours' => '3 Stunden', + + // Plugins + 'cannot_update_composer' => 'PHPCI kann composer.json nicht für Sie aktualisieren, da Schreibrechte benötigt werden.', + 'x_has_been_removed' => '%s wurde entfernt.', + 'x_has_been_added' => '%s wurde für Sie dem composer.json hinzugefügt und wird installiert, sobald Sie das nächste mal composer update ausführen.', + 'enabled_plugins' => 'Eingeschaltene Plugins', + 'provided_by_package' => 'Von Package bereitgestellt', + 'installed_packages' => 'Installierte Packages', + 'suggested_packages' => 'Vorgeschlagene Packages', + 'title' => 'Titel', + 'description' => 'Beschreibung', + 'version' => 'Version', + 'install' => 'Installieren »', + 'remove' => 'Entfernen »', + 'search_packagist_for_more' => 'Packagist nach mehr Packages durchsuchen', + 'search' => 'Suchen »', + + // Summary plugin + 'build-summary' => 'Zusammenfassung', + 'stage' => 'Abschnitt', + 'duration' => 'Dauer', + 'plugin' => 'Plugin', + 'stage_setup' => 'Vorbereitung', + 'stage_test' => 'Test', + 'stage_complete' => 'Vollständig', + 'stage_success' => 'Erfolg', + 'stage_failure' => 'Fehlschlag', + 'stage_broken' => 'Defekt', + 'stage_fixed' => 'Behoben', + + // Installer + 'installation_url' => 'PHPCI Installations-URL', + 'db_host' => 'Datenbankserver', + 'db_name' => 'Datenbankname', + 'db_user' => 'Datenbankbenutzer', + 'db_pass' => 'Datenbankpasswort', + 'admin_name' => 'Administratorname', + 'admin_pass' => 'Administratorpasswort', + 'admin_email' => 'Emailadresse des Administrators', + 'config_path' => 'Dateipfad für Konfiguration', + 'install_phpci' => 'PHPCI installieren', + 'welcome_to_phpci' => 'Willkommen bei PHPCI', + 'please_answer' => 'Bitte beantworten Sie die folgenden Fragen:', + 'phpci_php_req' => 'PHPCI benötigt mindestens PHP 5.3.8 um zu funktionieren.', + 'extension_required' => 'Benötigte Extensions: %s', + 'function_required' => 'PHPCI muss die Funktion %s() aufrufen können. Ist sie in php.ini deaktiviert?', + 'requirements_not_met' => 'PHPCI konnte nicht installiert werden, weil nicht alle Bedingungen erfüllt sind. + Bitte überprüfen Sie die Fehler, bevor Sie fortfahren,', + 'must_be_valid_email' => 'Muss eine gültige Emailadresse sein.', + 'must_be_valid_url' => 'Muss eine valide URL sein.', + 'enter_name' => 'Name des Administrators: ', + 'enter_email' => 'Emailadresse des Administrators: ', + 'enter_password' => 'Passwort des Administrators: ', + 'enter_phpci_url' => 'Ihre PHPCI-URL (z.B. "http://phpci.local"): ', + + 'enter_db_host' => 'Bitte geben Sie Ihren MySQL-Host ein [localhost]: ', + 'enter_db_name' => 'Bitte geben Sie Ihren MySQL-Namen ein [phpci]: ', + 'enter_db_user' => 'Bitte geben Sie Ihren MySQL-Benutzernamen ein [phpci]: ', + 'enter_db_pass' => 'Bitte geben Sie Ihr MySQL-Passwort ein: ', + 'could_not_connect' => 'PHPCI konnte wegen folgender Details nicht mit MySQL verbinden. Bitte versuchen Sie es erneut.', + 'setting_up_db' => 'Ihre Datenbank wird aufgesetzt... ', + 'user_created' => 'Benutzerkonto wurde erstellt!', + 'failed_to_create' => 'PHPCI konnte Ihr Administratorenkonto nicht erstellen.', + 'config_exists' => 'Die PHPCI-Konfigurationsdatei existiert und ist nicht leer..', + 'update_instead' => 'Falls Sie versucht haben PHPCI zu aktualisieren, benutzen Sie bitte stattdessen phpci:update.', + + // Update + 'update_phpci' => 'Datenbank wird aktualisiert, um den Änderungen der Models zu entsprechen.', + 'updating_phpci' => 'Aktualisiere PHPCI-Datenbank:', + 'not_installed' => 'PHPCI scheint nicht installiert zu sein.', + 'install_instead' => 'Bitte installieren Sie PHPCI stattdessen via phpci:install.', + + // Poll Command + 'poll_github' => 'GitHub abfragen, um herauszufinden, ob ein Build gestartet werden muss.', + 'no_token' => 'Kein GitHub-Token gefunden', + 'finding_projects' => 'Suche Projekte, um diese abzufragen', + 'found_n_projects' => '%d Projekte gefunden', + 'last_commit_is' => 'Der letzte Commit zu GitHub für %s ist %s', + 'adding_new_build' => 'Letzter Commit unterscheidet sich von der Datenbank, füge neuen Build hinzu.', + 'finished_processing_builds' => 'Bearbeiten der Builds abgeschlossen.', + + // Create Admin + 'create_admin_user' => 'Administratorenbenutzer erstellen', + 'incorrect_format' => 'Falsches Format', + + // Create Build Command + 'create_build_project' => 'Create a build for a project', + 'project_id_argument' => 'A project ID', + 'commit_id_option' => 'Commit ID to build', + 'branch_name_option' => 'Branch to build', + + // Run Command + 'run_all_pending' => 'Führe alle ausstehenden PHPCI Builds aus.', + 'finding_builds' => 'Suche verarbeitbare Builds', + 'found_n_builds' => '%d Builds gefunden', + 'skipping_build' => 'Überspringe Build %d - Es wird bereits ein Build auf diesem Projekt ausgeführt.', + 'marked_as_failed' => 'Build %d wegen Zeitüberschreitung als fehlgeschlagen markiert.', + + // Builder + 'missing_phpci_yml' => 'Dieses Projekt beinhaltet keine phpci.yml-Datei, oder sie ist leer.', + 'build_success' => 'BUILD ERFOLGREICH', + 'build_failed' => 'BUILD FEHLGESCHLAGEN', + 'removing_build' => 'Entferne Build.', + 'exception' => 'Exception: ', + 'could_not_create_working' => 'Konnte keine Arbeitskopie erstellen.', + 'working_copy_created' => 'Arbeitskopie erstellt: %s', + 'looking_for_binary' => 'Suche Binärdatei: %s', + 'found_in_path' => 'Gefunden in %s: %s', + 'running_plugin' => 'AUSGEFÜHRTES PLUGIN: %s', + 'plugin_success' => 'PLUGIN: ERFOLGREICH', + 'plugin_failed' => 'PLUGIN: FEHLGECHLAGEN', + 'plugin_missing' => 'Plugin existiert nicht: %s', + 'tap_version' => 'TapParser unterstützt nur TAP version 13', + 'tap_error' => 'Ungültiger TAP String, Anzahl Tests entspricht nicht angegebener Testzahl.', + + // Build Plugins: + 'no_tests_performed' => 'Keine Tests wurden ausgeführt.', + 'could_not_find' => '%s wurde nicht gefunden', + 'no_campfire_settings' => 'Keine Verbindungsparameter für das Campfire plugin gefunden', + 'failed_to_wipe' => 'Konnte Ordner %s nicht vor dem Kopieren leeren', + 'passing_build' => 'Durchlaufender Build', + 'failing_build' => 'Fehlschlagender Build', + 'log_output' => 'Protokollausgabe: ', + 'n_emails_sent' => '%d Emails verschickt.', + 'n_emails_failed' => 'Konnte %d Emails nicht verschicken.', + 'unable_to_set_env' => 'Konnte Umgebungsvariable nicht setzen', + 'tag_created' => 'Tag erstellt durch PHPCI: %s', + 'x_built_at_x' => '%PROJECT_TITLE% gebuildet auf %BUILD_URI%', + 'hipchat_settings' => 'Bitte definieren Sie Room und AuthToken für das hipchat_notify-Plugin', + 'irc_settings' => 'Sie müssen einen Server, Room und Nick definieren.', + 'invalid_command' => 'Ungültiges Kommando', + 'import_file_key' => 'Import-Statements müssen einen \'file\'-Key enthalten', + 'cannot_open_import' => 'Konnte SQL-Importdatei nicht öffnen: %s', + 'unable_to_execute' => 'Konnte SQL-Datei nicht ausführen', + 'phar_internal_error' => 'Phar Plugin Interner Fehler', + 'build_file_missing' => 'Angegebene Builddatei existiert nicht.', + 'property_file_missing' => 'Angegebene Eigenschaftsdatei existiert nicht.', + 'could_not_process_report' => 'Konnte den von diesem Tool erstellten Bericht nicht verarbeiten.', + 'shell_not_enabled' => 'Das Shell-Plugin ist nicht aktiviert. Bitte aktivieren Sie es via config.yml.', + + // Error Levels: + 'critical' => 'Kritisch', + 'high' => 'Hoch', + 'normal' => 'Normal', + 'low' => 'Niedrig', + + // Plugins that generate errors: + 'php_mess_detector' => 'PHP Mess Detector', + 'php_code_sniffer' => 'PHP Code Sniffer', + 'php_unit' => 'PHP Unit', + 'php_cpd' => 'PHP Copy/Paste Detector', + 'php_docblock_checker' => 'PHP Docblock Checker', +); diff --git a/PHPCI/Languages/lang.el.php b/PHPCI/Languages/lang.el.php new file mode 100644 index 00000000..45ee4bee --- /dev/null +++ b/PHPCI/Languages/lang.el.php @@ -0,0 +1,399 @@ + 'Ελληνικά', + 'language' => 'Γλώσσα', + + // Log in: + 'log_in_to_phpci' => 'Είσοδος στο PHPCI', + 'login_error' => 'Λάθος διεύθυνση e-mail ή κωδικός πρόσβασης', + 'forgotten_password_link' => 'Ξεχάσατε τον κωδικό σας;', + 'reset_emailed' => 'Σας έχουμε αποσταλεί ένα σύνδεσμο για να επαναφέρετε τον κωδικό πρόσβασής σας.', + 'reset_header' => ' Μην ανησυχείτε!
Απλά εισάγετε το email σας παρακάτω και θα θα σας αποστείλουμε ένα email +με ένα σύνδεσμο για να επαναφέρετε τον κωδικό πρόσβασής σας.', + 'reset_email_address' => 'Εισάγετε τη διεύθυνση e-mail σας:', + 'reset_send_email' => 'Email επαναφοράς κωδικού πρόσβασης', + 'reset_enter_password' => 'Παρακαλώ εισάγετε ένα νέο κωδικό πρόσβασης', + 'reset_new_password' => 'Νέος κωδικός πρόσβασης:', + 'reset_change_password' => 'Αλλαγή κωδικού πρόσβασης', + 'reset_no_user_exists' => 'Δεν υπάρχει χρήστης με αυτή την διεύθυνση ηλεκτρονικού ταχυδρομείου, παρακαλώ προσπαθήστε ξανά.', + 'reset_email_body' => 'Γεια %s, + +Έχετε λάβει αυτό το μήνυμα επειδή εσείς, ή κάποιος άλλος, ζήτησε επαναφορά κωδικού πρόσβασης για το PHPCI. + +Αν ήσασταν εσείς, παρακαλώ κάντε κλικ στον παρακάτω σύνδεσμο για να επαναφέρετε τον κωδικό πρόσβασής σας: %ssession/reset-password/%d/%s + +Σε αντίθετη περίπτωση, παρακαλούμε να αγνοήσετε αυτό το μήνυμα και δεν πρόκεται να πραγματοποιηθεί η επαναφορά. + +Σας ευχαριστούμε, + +PHPCI', + + 'reset_email_title' => 'PHPCI Επαναφορά Κωδικού για %s', + 'reset_invalid' => 'Μη έγκυρο αίτημα επαναφοράς κωδικού πρόσβασης.', + 'email_address' => 'Διεύθυνση email', + 'login' => 'Login / Email Address', + 'password' => 'Κωδικός πρόσβασης', + 'log_in' => 'Είσοδος', + + + // Top Nav + 'toggle_navigation' => 'Εναλλαγή πλοήγησης', + 'n_builds_pending' => '%d κατασκευές σε εκκρεμότητα', + 'n_builds_running' => '%d τρέχοντες κατασκευές', + 'edit_profile' => 'Επεξεργασία Προφίλ', + 'sign_out' => 'Έξοδος', + 'branch_x' => 'Διακλάδωση: %s', + 'created_x' => 'Δημιουργήθηκε: %s', + 'started_x' => 'Ξεκίνησε: %s', + + // Sidebar + 'hello_name' => 'Γειά, %s', + 'dashboard' => 'Πίνακας ελέγχου', + 'admin_options' => 'Επιλογές Διαχειριστή', + 'add_project' => 'Προσθήκη έργου', + 'settings' => 'Ρυθμίσεις', + 'manage_users' => 'Διαχείριση χρηστών', + 'plugins' => 'Πρόσθετα', + 'view' => 'Προβολή', + 'build_now' => 'Κατασκευή τώρα', + 'edit_project' => 'Επεξεργασία Έργου', + 'delete_project' => 'Διαγραφή Έργου', + + // Project Summary: + 'no_builds_yet' => 'Καμία κατασκευή ακόμα!', + 'x_of_x_failed' => '%d από τις %d τελευταίες κατασκευές απέτυχαν', + 'x_of_x_failed_short' => '%d / %d απέτυχαν.', + 'last_successful_build' => 'Η τελευταία επιτυχής κατασκεύη ήταν %s.', + 'never_built_successfully' => 'Αυτό το έργο δεν έχει ποτέ κατασκευαστεί με επιτυχία.', + 'all_builds_passed' => 'Όλες από τις %d κατασκευές πέρασαν', + 'all_builds_passed_short' => '%d / %d πέρασαν.', + 'last_failed_build' => 'H τελευταία αποτυχημένη κατασκευή ήταν %s.', + 'never_failed_build' => 'Το έργο αυτό δεν παρέλειψε ποτέ μια κατασκευή.', + 'view_project' => 'Προβολή του έργου', + + // Timeline: + 'latest_builds' => 'Τελευταίες κατασκευές', + 'pending' => 'Σε εκκρεμότητα', + 'running' => 'Τρέχοντα', + 'success' => 'Επιτυχία', + 'successful' => 'Επιτυχής', + 'failed' => 'Αποτυχία', + 'manual_build' => 'Χειροκίνητη κατασκευή', + + // Add/Edit Project: + 'new_project' => 'Νέο έργο', + 'project_x_not_found' => 'Το έργο με αριθμό %d δεν υπάρχει', + 'project_details' => 'Στοιχεία Έργου', + 'public_key_help' => 'Για να είναι πιο εύκολο να ξεκινήσετε, έχουμε δημιουργήσει ένα ζεύγος κλειδιών SSH για να χρησιμοποιήσετε +για το έργο αυτό. Για να τα χρησιμοποιήσετε, απλά προσθέστε το ακόλουθο δημόσιο κλειδί στο τμήμα "ανάπτυξη κλειδιών" +του επιλεγμένου πηγαίου κώδικα της πλατφόρμας φιλοξενίας σας.', + 'select_repository_type' => 'Επιλέξτε τον τύπο του αποθετηρίου...', + 'github' => 'GitHub', + 'bitbucket' => 'Bitbucket', + 'gitlab' => 'GitLab', + 'remote' => 'Απομακρυσμένη διεύθυνση URL', + 'local' => 'Τοπική Διαδρομή', + 'hg' => 'Ευμετάβλητο', + + 'where_hosted' => 'Πού φιλοξενείται το έργο σας;', + 'choose_github' => 'Επιλέξτε ένα αποθετήριο GitHub:', + + 'repo_name' => 'Αποθετήριο Όνομα / διεύθυνση URL (Απομακρυσμένα) ή Διαδρομή (Τοπικά)', + 'project_title' => 'Τίτλος Έργου', + 'project_private_key' => 'Ιδιωτικό κλειδί για πρόσβαση σε αποθετήριο +(αφήστε κενό για την τοπική ή / και ανώνυμα απομακρυσμένα)', + 'build_config' => 'Kατασκευή διαμόρφωσης PHPCI για αυτό το έργο +(αν δεν μπορείτε να προσθέσετε ένα αρχείο phpci.yml στο αποθετήριο έργων)', + 'default_branch' => 'Προκαθορισμένο όνομα διακλάδωσης', + 'allow_public_status' => 'Ενεργοποίηση της σελίδας δημόσιας κατάστασης και την εικόνα για το έργο αυτό;', + 'archived' => 'Archived', + 'archived_menu' => 'Archived', + 'save_project' => 'Αποθήκευση έργου', + + 'error_mercurial' => 'Ο σύνδεσμος URL του ευμετάβλητου αποθετηρίου πρέπει να ξεκινάει με http:// ή https://', + 'error_remote' => 'Ο σύνδεσμος URL του αποθετηρίου πρέπει να ξεκινάει με git://, http:// ή https://', + 'error_gitlab' => 'Το όνομα του αποθετηρίου GitLab πρέπει να είναι της μορφής "user@domain.tld:owner/repo.git"', + 'error_github' => 'Το όνομα του αποθετηρίου θα πρέπει να είναι της μορφής "owner/repo" ιδιοκτήτης/αποθετήριο', + 'error_bitbucket' => 'Το όνομα του αποθετηρίου θα πρέπει να είναι της μορφής "owner/repo" ιδιοκτήτης/αποθετήριο', + 'error_path' => 'Η διαδρομή που καθορίσατε δεν υπάρχει.', + + // View Project: + 'all_branches' => 'Όλες οι διακλαδώσεις', + 'builds' => 'Κατασκευές', + 'id' => 'Αριθμός αναγνώρισης', + 'date' => 'Date', + 'project' => 'Έργο', + 'commit' => 'Συνεισφορά', + 'branch' => 'Διακλάδωση', + 'status' => 'Κατάσταση', + 'prev_link' => '« Προηγούμενο', + 'next_link' => 'Επόμενο «', + 'public_key' => 'Δημόσιο κλειδί', + 'delete_build' => 'Διαγραφή κλειδιού', + + 'webhooks' => 'Webhooks', + 'webhooks_help_github' => 'Για την αυτόματη κατασκευή αυτού του έργου όταν υπάρχουν νέες συνεισφορές, προσθέστε τη διεύθυνση URL παρακάτω +ως ένα νέο "Webhook" στο τμήμα Webhooks +and Services του GitHub αποθετηρίου σας.', + + 'webhooks_help_gitlab' => 'Για την αυτόματη κατασκευή αυτού του έργου όταν υπάρχουν νέες συνεισφορές, προσθέστε την διεύθυνση URL παρακάτω +σαν "WebHook URL" στο τμήμα Web Hooks του GitLab αποθετηρίου σας.', + + 'webhooks_help_bitbucket' => 'Για την αυτόματη κατασκευή αυτού του έργου όταν υπάρχουν νέες συνεισφορές, προσθέστε τη διεύθυνση URL παρακάτω +ως μια υπηρεσία "POST" στο τμήμα +Services του Bitbucket αποθετηρίου σας.', + + // View Build + 'build_x_not_found' => 'Η κατασκευή με αριθμό %d δεν υπάρχει', + 'build_n' => 'Κατασκευή %d', + 'rebuild_now' => 'Αναδόμηση τώρα', + + + 'committed_by_x' => 'Έγινε συνεισφορά από %s', + 'commit_id_x' => 'Συνεισφορά: %s', + + 'chart_display' => 'Αυτό το γράφημα θα εμφανιστεί μόλις η κατασκευή έχει ολοκληρωθεί.', + + 'build' => 'Κατασκευή', + 'lines' => 'Γραμμές', + 'comment_lines' => 'Γραμμές σχολίων', + 'noncomment_lines' => 'Μη σχολιασμένες γραμμές', + 'logical_lines' => 'Λογικές γραμμές', + 'lines_of_code' => 'Γραμμές Κώδικα', + 'build_log' => 'Αρχείο καταγραφής κατασκευών', + 'quality_trend' => 'Ποιότητα τρέντ', + 'codeception_errors' => 'Λάθη Codeception', + 'phpmd_warnings' => 'Προειδοποιήσεις PHPMD', + 'phpcs_warnings' => 'Προειδοποιήσεις PHPCS ', + 'codeception_errors' => 'Λάθη Codeception', + 'phpcs_errors' => 'Λάθη PHPCS', + 'phplint_errors' => 'Λάθη Lint', + 'phpunit_errors' => 'Λάθη PHPUnit ', + 'phpdoccheck_warnings' => 'Χαμένα Docblocks', + 'issues' => 'Θέματα', + + 'codeception' => 'Codeception', + 'phpcpd' => 'PHP Ανιχνευτής Αντιγραφής/Επικόλλησης', + 'phpcs' => 'Sniffer Κώδικα PHP', + 'phpdoccheck' => 'Χαμένα Docblocks', + 'phpmd' => 'Aνιχνευτής PHP Mess', + 'phpspec' => 'PHP Spec', + 'phpunit' => 'PHP Unit', + + 'file' => 'Αρχείο', + 'line' => 'Γραμμή', + 'class' => 'Κατηγορία', + 'method' => 'Μέθοδος', + 'message' => 'Μήνυμα', + 'start' => 'Έναρξη', + 'end' => 'Τέλος', + 'from' => 'Από', + 'to' => 'Προς', + 'result' => 'Αποτέλεσμα', + 'ok' => 'ΟΚ', + 'took_n_seconds' => 'Χρειάστηκαν %d δευτερόλεπτα', + 'build_created' => 'Η κατασκευή δημιουργήθηκε', + 'build_started' => 'Η κατασκευή άρχισε', + 'build_finished' => 'Η κατασκευή ολοκληρώθηκε', + 'test_message' => 'Message', + 'test_no_message' => 'No message', + 'test_success' => 'Successful: %d', + 'test_fail' => 'Failures: %d', + 'test_skipped' => 'Skipped: %d', + 'test_error' => 'Errors: %d', + 'test_todo' => 'Todos: %d', + 'test_total' => '%d test(s)', + + // Users + 'name' => 'Όνομα', + 'password_change' => 'Κωδικός πρόσβασης (αφήστε κενό αν δεν θέλετε να αλλάξετε)', + 'save' => 'Αποθήκευση »', + 'update_your_details' => 'Ενημερώστε τα στοιχεία σας', + 'your_details_updated' => 'Τα στοιχεία σας έχουν ενημερωθεί.', + 'add_user' => 'Προσθήκη χρήστη', + 'is_admin' => 'Είναι διαχειριστής;', + 'yes' => 'Ναι', + 'no' => 'Όχι', + 'edit' => 'Επεξεργασία', + 'edit_user' => 'Επεξεργασία χρήστη', + 'delete_user' => 'Διαγραφή χρήστη', + 'user_n_not_found' => 'Ο χρήστης με αριθμό %d δεν υπάρχει.', + 'is_user_admin' => 'Είναι αυτός ο χρήστης διαχειριστής;', + 'save_user' => 'Αποθήκευση χρήστη', + + // Settings: + 'settings_saved' => 'Οι ρυθμίσεις σας έχουν αποθηκευτεί.', + 'settings_check_perms' => 'Οι ρυθμίσεις σας δεν αποθηκεύτηκαν, ελέγξτε τα δικαιώματα του αρχείου σας config.yml.', + 'settings_cannot_write' => 'Το PHPCI δεν μπορεί να γράψει στο αρχείο config.yml, οι ρυθμίσεις ενδέχεται να μην αποθηκευτούν σωστά +μέχρι να διορθωθεί.', + 'settings_github_linked' => 'Ο λογαριασμός σας GitHub έχει συνδεθεί.', + 'settings_github_not_linked' => 'Ο λογαριασμός σας Github δεν μπόρεσε να συνδεθεί.', + 'build_settings' => 'Ρυθμίσεις κατασκευής', + 'github_application' => 'GitHub Εφαρμογή', + 'github_sign_in' => 'Πριν αρχίσετε να χρησιμοποιείτε το GitHub, θα πρέπει να συνδεθείται και να δώσει +το PHPCI πρόσβαση στο λογαριασμό σας.', + 'github_phpci_linked' => 'Το PHPCI συνδέθηκε με επιτυχία με το λογαριασμό Github.', + 'github_where_to_find' => 'Πού να βρείτε αυτά ...', + 'github_where_help' => 'Εάν έχετε στην κατοχή σας την εφαρμογή που θέλετε να χρησιμοποιήσετε, μπορείτε να βρείτε αυτές τις πληροφορίες στην περιοχή +Ρυθμίσεις εφαρμογών ', + + 'email_settings' => 'Ρυθμίσεις email', + 'email_settings_help' => 'Πριν το PHPCI μπορεί να στείλει μηνύματα ηλεκτρονικού ταχυδρομείου για την κατάσταση κατασκευής, +θα πρέπει να διαμορφώσετε τις ρυθμίσεις SMTP παρακάτω.', + + 'application_id' => 'Αναγνωριστικό εφαρμογής', + 'application_secret' => 'Μυστική Εφαρμογή', + + 'smtp_server' => 'Διακομισής SMTP', + 'smtp_port' => 'Θύρα SMTP', + 'smtp_username' => 'Όνομα χρήστη SMTP', + 'smtp_password' => 'Κωδικός πρόσβασης SMTP', + 'from_email_address' => 'Εmail διεύθυνση αποστολέα', + 'default_notification_address' => 'Προεπιλεγμένη διεύθυνση ειδοποίησης ηλεκτρονικού ταχυδρομείου ', + 'use_smtp_encryption' => 'Εφαρμογή SMTP κρυπτογράφησης;', + 'none' => 'Κανένα', + 'ssl' => 'Κρυπτογράφηση SSL', + 'tls' => 'Κρυπτογράφηση TLS', + + 'failed_after' => 'Να θεωρηθεί μια κατασκευή αποτυχημένη μετά ', + '5_mins' => '5 λεπτά', + '15_mins' => '15 λεπτά', + '30_mins' => '30 λεπτά', + '1_hour' => '1 ώρα', + '3_hours' => '3 ώρες', + + // Plugins + 'cannot_update_composer' => 'To PHPCI δεν μπορεί να ενημερώσει to composer.json για σας, γιατί δεν είναι εγγράψιμο.', + 'x_has_been_removed' => '%s έχει αφαιρεθεί.', + 'x_has_been_added' => '%s προσθέιηκε στο αρχείο composer.json για εσάς και θα εγκατασταθεί την επόμενη φορά +που θα τρέξετε την ενημέρωση για το composer.', + 'enabled_plugins' => 'Ενεργοποιημένα πρόσθετα', + 'provided_by_package' => 'Παρέχεται από πακέτο', + 'installed_packages' => 'Εγκατεστημένα πακέτα', + 'suggested_packages' => 'Προτεινόμενα πακέτα', + 'title' => 'Τίτλος', + 'description' => 'Περιγραφή', + 'version' => 'Έκδοση', + 'install' => 'Εγκατάσταση »', + 'remove' => 'Αφαίρεση »', + 'search_packagist_for_more' => 'Αναζήτηση στο Packagist για περισσότερα πακέτα', + 'search' => 'Αναζήτηση »', + + // Installer + 'installation_url' => 'Σύνδεσμος URL εγκατάστασης του PHPCI', + 'db_host' => 'Φιλοξενία βάσης δεδομένων', + 'db_name' => 'Όνομα βάσης δεδομένων', + 'db_user' => 'Όνομα χρήστη βάσης δεδομένων', + 'db_pass' => 'Κωδικός πρόσβασης βάσης δεδομένων', + 'admin_name' => 'Όνομα διαχειριστή', + 'admin_pass' => 'Κωδικός πρόσβασης διαχειριστή', + 'admin_email' => 'Διεύθυνση email διαχειριστή', + 'config_path' => 'Διαδρομή αρχείου ρυθμίσεων', + 'install_phpci' => 'Εγκατάσταση PHPCI', + 'welcome_to_phpci' => 'Καλώς ήρθατε στο PHPCI', + 'please_answer' => 'Παρακαλώ απαντήστε στις ακόλουθες ερωτήσεις:', + 'phpci_php_req' => 'Το PHPCI απαιτεί τουλάχιστον την έκδοση PHP 5.3.8 για να λειτουργήσει', + 'extension_required' => 'Απαιτούμενη επέκταση: %s ', + 'function_required' => 'Το PHPCI πρέπει να είναι σε θέση να καλέσει την %s() συνάρτηση. Είναι απενεργοποιημένη στο php.ini;', + 'requirements_not_met' => 'Το PHPCI δεν μπορεί να εγκατασταθεί, καθώς όλες οι απαιτήσεις δεν ικανοποιούνται. +Παρακαλούμε διαβάστε τα παραπάνω λάθη πριν συνεχίσετε.', + 'must_be_valid_email' => 'Πρέπει να είναι μια έγκυρη διεύθυνση ηλεκτρονικού ταχυδρομείου.', + 'must_be_valid_url' => 'Πρέπει να είναι μια έγκυρη διεύθυνση URL.', + 'enter_name' => 'Όνομα διαχειριστή: ', + 'enter_email' => 'Ηλ. Διεύθυνση διαχειριστή: ', + 'enter_password' => 'Κωδικός πρόσβασης διαχειριστή: ', + 'enter_phpci_url' => 'Ο URL σύνδεσμος σας για το PHPCI ("http://phpci.local" για παράδειγμα): ', + + 'enter_db_host' => 'Παρακαλώ εισάγετε τον MySQL οικοδεσπότη σας [localhost]: ', + 'enter_db_name' => 'Παρακαλώ εισάγετε το όνομα της MySQL βάσης δεδομένων σας [phpci]: ', + 'enter_db_user' => 'Παρακαλώ εισάγετε το όνομα χρήστη της MySQL σας [phpci]: ', + 'enter_db_pass' => 'Παρακαλώ εισάγετε τον κωδικό χρήστη της MySQL σας: ', + 'could_not_connect' => 'Το PHPCI δεν μπόρεσε να συνδεθεί με την MySQL με τα στοχεία που δώσατε. Παρακαλώ δοκιμάστε ξανά.', + 'setting_up_db' => 'Γίνεται ρύθμιση της βάσης δεδομένων σας ...', + 'user_created' => 'Λογαριασμός χρήστη δημιουργήθηκε!', + 'failed_to_create' => 'Το PHPCI απέτυχε να δημιουργήσει το λογαριασμό διαχειριστή σας.', + 'config_exists' => 'Το αρχείο ρυθμίσεων PHPCI υπάρχει και δεν είναι άδειο.', + 'update_instead' => 'Εάν προσπαθούσατε να ενημερώσετε PHPCI, παρακαλούμε χρησιμοποιήστε καλύτερα το phpci:update αντ \'αυτού.', + + // Update + 'update_phpci' => 'Ενημέρωστε την βάση δεδομένων ώστε να αντικατοπτρίζει τροποποιημένα μοντέλα.', + 'updating_phpci' => 'Γίνεται ενημέρωση της βάσης δεδομένων PHPCI:', + 'not_installed' => 'Το PHPCI δεν φένεται να είναι εγκατεστημένο', + 'install_instead' => 'Παρακαλούμε εγκαταστήστε το PHPCI καλύτερα με το phpci:install αντ \'αυτού.', + + // Poll Command + 'poll_github' => 'Δημοσκόπηση στο GitHub για να ελέγξετε αν θα πρέπει να ξεκινήσει μια κατασκευή.', + 'no_token' => 'Δεν βρέθηκε GitHub token', + 'finding_projects' => 'Αναζήτηση έργων για δημοσκόπηση', + 'found_n_projects' => 'Βρέθηκαν %d έργα', + 'last_commit_is' => 'H τελευταία συνεισφορά στο GitHub για %s είναι %s', + 'adding_new_build' => 'Τελευταία συνεισφορά είναι διαφορετική από τη βάση δεδομένων, γίνεται προσθήκη νέας κατασκευής.', + 'finished_processing_builds' => 'Ολοκληρώθηκε η επεξεργασία κατασκευής.', + + // Create Admin + 'create_admin_user' => 'Δημιουργήστε ένα χρήστη διαχειριστή', + 'incorrect_format' => 'Λανθασμένη μορφοποίηση', + + // Create Build Command + 'create_build_project' => 'Create a build for a project', + 'project_id_argument' => 'A project ID', + 'commit_id_option' => 'Commit ID to build', + 'branch_name_option' => 'Branch to build', + + // Run Command + 'run_all_pending' => 'Εκτελέστε όλες τις εκκρεμείς PHPCI κατασκευές.', + 'finding_builds' => 'Αναζήτηση κατασκευών για επεξεργασία', + 'found_n_builds' => 'Βρέθηκαν %d κατασκευές', + 'skipping_build' => 'Παράκαμψη κατασκευής %d - Η διαδικασία κατασκευής του έργου βρίσκεται ήδη σε εξέλιξη.', + 'marked_as_failed' => 'Η κατασκεύη %d επισημάνθηκε ως αποτυχημένη λόγω χρονικού ορίου', + + // Builder + 'missing_phpci_yml' => 'Το έργο δεν περιέχει το αρχείο phpci.yml ή είναι άδειο.', + 'build_success' => 'ΚΑΤΑΣΚΕΥΗ ΕΠΙΤΥΧΗΣ', + 'build_failed' => 'ΚΑΤΑΣΚΕΥΗ ΑΠΕΤΥΧΕ', + 'removing_build' => 'Γίνεται αφαίρεση κατασκευής', + 'exception' => 'Εξαίρεση:', + 'could_not_create_working' => 'Αδυναμία δημιουργίας αντίγραφου εργασίας.', + 'working_copy_created' => 'Αντίγραφο εργασίας που δημιουργήθηκαν: %s', + 'looking_for_binary' => 'Αναζήτηση για δυαδικό: %s', + 'found_in_path' => 'Βρέθηκε στο %s: %s', + 'running_plugin' => 'ΤΡΕΧΩΝ ΠΡΟΣΘΕΤΟ: %s', + 'plugin_success' => 'ΠΡΟΣΘΕΤΟ: ΕΠΙΤΥΧΙΑ', + 'plugin_failed' => 'ΠΡΟΣΘΕΤΟ: ΑΠΟΤΥΧΙΑ', + 'plugin_missing' => 'Το πρόσθετο δεν υπάρχει: %s', + 'tap_version' => 'Το TapParser υποστηρίζει μόνο το TAP έκδοση 13', + 'tap_error' => 'Μη έγκυρη συμβολοσειρά TAP, ο αριθμός των δοκιμών δεν ταιριάζει με την καθορισμένη καταμέτρηση της δοκιμής.', + + // Build Plugins: + 'no_tests_performed' => 'Δεν έγιναν δοκιμές.', + 'could_not_find' => 'Δεν ήταν δυνατή η εύρεση του %s', + 'no_campfire_settings' => 'Δεν έχουν δωθεί παράμετροι της σύνδεσης για το πρόσθετο Campfire', + 'failed_to_wipe' => 'Αποτυχία πλήρους διαγραφής του καταλόγου %s πριν την αντιγραφή', + 'passing_build' => 'Επιτυχημένη κατασκευή', + 'failing_build' => 'Αποτυχημένη κατασκευή', + 'log_output' => 'Σύνδεση εξόδου:', + 'n_emails_sent' => 'Στάλθηκαν %d emails ', + 'n_emails_failed' => 'Δεν στάλθηκαν %d emails ', + 'unable_to_set_env' => 'Δεν είναι δυνατός ο ορισμος μεταβλητής περιβάλλοντος', + 'tag_created' => 'Ετικέτα δημιουργήθηκε από PHPCI: %s', + 'x_built_at_x' => '%PROJECT_TITLE% χτισμένο σε %BUILD_URI%', + 'hipchat_settings' => 'Παρακαλώ ορίστε δωμάτιο και authToken για το πρόσθετο hipchat_notify', + 'irc_settings' => 'Θα πρέπει να ρυθμίσετε ένα διακομιστή, το δωμάτιο και το ψευδώνυμο.', + 'invalid_command' => 'Μη έγκυρη εντολή', + 'import_file_key' => 'Η δήλωση εισαγωγής πρέπει να περιέχει ένα κλειδί "αρχείο"', + 'cannot_open_import' => 'Δεν είναι δυνατό το άνοιγμα του SQL αρχείο εισαγωγής: %s ', + 'unable_to_execute' => 'Δεν είναι δυνατή η εκτέλεση του αρχείου SQL', + 'phar_internal_error' => 'Phar Πρόσθετο Εσωτερικό σφάλμα', + 'build_file_missing' => 'Καθορισμένο αρχείο κατασκευής δεν υπάρχει.', + 'property_file_missing' => 'Καθορισμένο αρχείο ιδιοκτησίας δεν υπάρχει.', + 'could_not_process_report' => 'Δεν ήταν δυνατή η επεξεργασία της έκθεσης που δημιουργείται από αυτό το εργαλείο.', + 'shell_not_enabled' => 'Το πρόσθετο για το κέλυφος δεν είναι ενεργοποιημένο. Παρακαλούμε ενεργοποιήστε το μέσω του αρχείου config.yml.' +); diff --git a/PHPCI/Languages/lang.en.php b/PHPCI/Languages/lang.en.php new file mode 100644 index 00000000..63df75d2 --- /dev/null +++ b/PHPCI/Languages/lang.en.php @@ -0,0 +1,444 @@ + 'English', + 'language' => 'Language', + + // Log in: + 'log_in_to_phpci' => 'Log in to PHPCI', + 'login_error' => 'Incorrect email address or password', + 'forgotten_password_link' => 'Forgotten your password?', + 'reset_emailed' => 'We\'ve emailed you a link to reset your password.', + 'reset_header' => 'Don\'t worry!
Just enter your email address below and we\'ll email + you a link to reset your password.', + 'reset_email_address' => 'Enter your email address:', + 'reset_send_email' => 'Email password reset', + 'reset_enter_password' => 'Please enter a new password', + 'reset_new_password' => 'New password:', + 'reset_change_password' => 'Change password', + 'reset_no_user_exists' => 'No user exists with that email address, please try again.', + 'reset_email_body' => 'Hi %s, + +You have received this email because you, or someone else, has requested a password reset for PHPCI. + +If this was you, please click the following link to reset your password: %ssession/reset-password/%d/%s + +Otherwise, please ignore this email and no action will be taken. + +Thank you, + +PHPCI', + + 'reset_email_title' => 'PHPCI Password Reset for %s', + 'reset_invalid' => 'Invalid password reset request.', + 'email_address' => 'Email Address', + 'login' => 'Login / Email Address', + 'password' => 'Password', + 'log_in' => 'Log in', + + + // Top Nav + 'toggle_navigation' => 'Toggle Navigation', + 'n_builds_pending' => '%d builds pending', + 'n_builds_running' => '%d builds running', + 'edit_profile' => 'Edit Profile', + 'sign_out' => 'Sign Out', + 'branch_x' => 'Branch: %s', + 'created_x' => 'Created: %s', + 'started_x' => 'Started: %s', + + // Sidebar + 'hello_name' => 'Hello, %s', + 'dashboard' => 'Dashboard', + 'admin_options' => 'Admin Options', + 'add_project' => 'Add Project', + 'settings' => 'Settings', + 'manage_users' => 'Manage Users', + 'plugins' => 'Plugins', + 'view' => 'View', + 'build_now' => 'Build Now', + 'edit_project' => 'Edit Project', + 'delete_project' => 'Delete Project', + + // Project Summary: + 'no_builds_yet' => 'No builds yet!', + 'x_of_x_failed' => '%d out of the last %d builds failed.', + 'x_of_x_failed_short' => '%d / %d failed.', + 'last_successful_build' => ' The last successful build was %s.', + 'never_built_successfully' => ' This project has never built successfully.', + 'all_builds_passed' => 'All of the last %d builds passed.', + 'all_builds_passed_short' => '%d / %d passed.', + 'last_failed_build' => ' The last failed build was %s.', + 'never_failed_build' => ' This project has never failed a build.', + 'view_project' => 'View Project', + + // Timeline: + 'latest_builds' => 'Latest Builds', + 'pending' => 'Pending', + 'running' => 'Running', + 'success' => 'Success', + 'successful' => 'Successful', + 'failed' => 'Failed', + 'manual_build' => 'Manual Build', + + // Add/Edit Project: + 'new_project' => 'New Project', + 'project_x_not_found' => 'Project with ID %d does not exist.', + 'project_details' => 'Project Details', + 'public_key_help' => 'To make it easier to get started, we\'ve generated an SSH key pair for you to use + for this project. To use it, just add the following public key to the "deploy keys" section + of your chosen source code hosting platform.', + 'select_repository_type' => 'Select repository type...', + 'github' => 'GitHub', + 'bitbucket' => 'Bitbucket', + 'gitlab' => 'GitLab', + 'remote' => 'Remote URL', + 'local' => 'Local Path', + 'hg' => 'Mercurial', + 'svn' => 'Subversion', + + 'where_hosted' => 'Where is your project hosted?', + 'choose_github' => 'Choose a GitHub repository:', + + 'repo_name' => 'Repository Name / URL (Remote) or Path (Local)', + 'project_title' => 'Project Title', + 'project_private_key' => 'Private key to use to access repository + (leave blank for local and/or anonymous remotes)', + 'build_config' => 'PHPCI build config for this project + (if you cannot add a phpci.yml file in the project repository)', + 'default_branch' => 'Default branch name', + 'allow_public_status' => 'Enable public status page and image for this project?', + 'archived' => 'Archived', + 'archived_menu' => 'Archived', + 'save_project' => 'Save Project', + + 'error_mercurial' => 'Mercurial repository URL must be start with http:// or https://', + 'error_remote' => 'Repository URL must be start with git://, http:// or https://', + 'error_gitlab' => 'GitLab Repository name must be in the format "user@domain.tld:owner/repo.git"', + 'error_github' => 'Repository name must be in the format "owner/repo"', + 'error_bitbucket' => 'Repository name must be in the format "owner/repo"', + 'error_path' => 'The path you specified does not exist.', + + // View Project: + 'all_branches' => 'All Branches', + 'builds' => 'Builds', + 'id' => 'ID', + 'date' => 'Date', + 'project' => 'Project', + 'commit' => 'Commit', + 'branch' => 'Branch', + 'status' => 'Status', + 'prev_link' => '« Prev', + 'next_link' => 'Next »', + 'public_key' => 'Public Key', + 'delete_build' => 'Delete Build', + + 'webhooks' => 'Webhooks', + 'webhooks_help_github' => 'To automatically build this project when new commits are pushed, add the URL below + as a new "Webhook" in the Webhooks + and Services section of your GitHub repository.', + + 'webhooks_help_gitlab' => 'To automatically build this project when new commits are pushed, add the URL below + as a "WebHook URL" in the Web Hooks section of your GitLab repository.', + + 'webhooks_help_bitbucket' => 'To automatically build this project when new commits are pushed, add the URL below + as a "POST" service in the + + Services section of your Bitbucket repository.', + + // View Build + 'errors' => 'Errors', + 'information' => 'Information', + + 'build_x_not_found' => 'Build with ID %d does not exist.', + 'build_n' => 'Build %d', + 'rebuild_now' => 'Rebuild Now', + + + 'committed_by_x' => 'Committed by %s', + 'commit_id_x' => 'Commit: %s', + + 'chart_display' => 'This chart will display once the build has completed.', + + 'build' => 'Build', + 'lines' => 'Lines', + 'comment_lines' => 'Comment Lines', + 'noncomment_lines' => 'Non-Comment Lines', + 'logical_lines' => 'Logical Lines', + 'lines_of_code' => 'Lines of Code', + 'build_log' => 'Build Log', + 'quality_trend' => 'Quality Trend', + 'codeception_errors' => 'Codeception Errors', + 'phpmd_warnings' => 'PHPMD Warnings', + 'phpcs_warnings' => 'PHPCS Warnings', + 'phpcs_errors' => 'PHPCS Errors', + 'phplint_errors' => 'Lint Errors', + 'phpunit_errors' => 'PHPUnit Errors', + 'phpdoccheck_warnings' => 'Missing Docblocks', + 'issues' => 'Issues', + + 'codeception' => 'Codeception', + 'phpcpd' => 'PHP Copy/Paste Detector', + 'phpcs' => 'PHP Code Sniffer', + 'phpdoccheck' => 'Missing Docblocks', + 'phpmd' => 'PHP Mess Detector', + 'phpspec' => 'PHP Spec', + 'phpunit' => 'PHP Unit', + 'technical_debt' => 'Technical Debt', + 'behat' => 'Behat', + + 'codeception_feature' => 'Feature', + 'codeception_suite' => 'Suite', + 'codeception_time' => 'Time', + 'codeception_synopsis' => '%1$d tests carried out in %2$f seconds. + %3$d failures.', + + 'file' => 'File', + 'line' => 'Line', + 'class' => 'Class', + 'method' => 'Method', + 'message' => 'Message', + 'start' => 'Start', + 'end' => 'End', + 'from' => 'From', + 'to' => 'To', + 'result' => 'Result', + 'ok' => 'OK', + 'took_n_seconds' => 'Took %d seconds', + 'build_created' => 'Build Created', + 'build_started' => 'Build Started', + 'build_finished' => 'Build Finished', + 'test_message' => 'Message', + 'test_no_message' => 'No message', + 'test_success' => 'Successful: %d', + 'test_fail' => 'Failures: %d', + 'test_skipped' => 'Skipped: %d', + 'test_error' => 'Errors: %d', + 'test_todo' => 'Todos: %d', + 'test_total' => '%d test(s)', + + // Users + 'name' => 'Name', + 'password_change' => 'Password (leave blank if you don\'t want to change)', + 'save' => 'Save »', + 'update_your_details' => 'Update your details', + 'your_details_updated' => 'Your details have been updated.', + 'add_user' => 'Add User', + 'is_admin' => 'Is Admin?', + 'yes' => 'Yes', + 'no' => 'No', + 'edit' => 'Edit', + 'edit_user' => 'Edit User', + 'delete_user' => 'Delete User', + 'user_n_not_found' => 'User with ID %d does not exist.', + 'is_user_admin' => 'Is this user an administrator?', + 'save_user' => 'Save User', + + // Settings: + 'settings_saved' => 'Your settings have been saved.', + 'settings_check_perms' => 'Your settings could not be saved, check the permissions of your config.yml file.', + 'settings_cannot_write' => 'PHPCI cannot write to your config.yml file, settings may not be saved properly + until this is rectified.', + 'settings_github_linked' => 'Your GitHub account has been linked.', + 'settings_github_not_linked' => 'Your GitHub account could not be linked.', + 'build_settings' => 'Build Settings', + 'github_application' => 'GitHub Application', + 'github_sign_in' => 'Before you can start using GitHub, you need to sign in and grant + PHPCI access to your account.', + 'github_phpci_linked' => 'PHPCI is successfully linked to GitHub account.', + 'github_where_to_find' => 'Where to find these...', + 'github_where_help' => 'If you own the application you would like to use, you can find this information within your + applications settings area.', + + 'email_settings' => 'Email Settings', + 'email_settings_help' => 'Before PHPCI can send build status emails, + you need to configure your SMTP settings below.', + + 'application_id' => 'Application ID', + 'application_secret' => 'Application Secret', + + 'smtp_server' => 'SMTP Server', + 'smtp_port' => 'SMTP Port', + 'smtp_username' => 'SMTP Username', + 'smtp_password' => 'SMTP Password', + 'from_email_address' => 'From Email Address', + 'default_notification_address' => 'Default Notification Email Address', + 'use_smtp_encryption' => 'Use SMTP Encryption?', + 'none' => 'None', + 'ssl' => 'SSL', + 'tls' => 'TLS', + + 'failed_after' => 'Consider a build failed after', + '5_mins' => '5 Minutes', + '15_mins' => '15 Minutes', + '30_mins' => '30 Minutes', + '1_hour' => '1 Hour', + '3_hours' => '3 Hours', + + // Plugins + 'cannot_update_composer' => 'PHPCI cannot update composer.json for you as it is not writable.', + 'x_has_been_removed' => '%s has been removed.', + 'x_has_been_added' => '%s has been added to composer.json for you and will be installed next time + you run composer update.', + 'enabled_plugins' => 'Enabled Plugins', + 'provided_by_package' => 'Provided By Package', + 'installed_packages' => 'Installed Packages', + 'suggested_packages' => 'Suggested Packages', + 'title' => 'Title', + 'description' => 'Description', + 'version' => 'Version', + 'install' => 'Install »', + 'remove' => 'Remove »', + 'search_packagist_for_more' => 'Search Packagist for more packages', + 'search' => 'Search »', + + // Summary plugin + 'build-summary' => 'Summary', + 'stage' => 'Stage', + 'duration' => 'Duration', + 'plugin' => 'Plugin', + 'stage_setup' => 'Setup', + 'stage_test' => 'Test', + 'stage_complete' => 'Complete', + 'stage_success' => 'Success', + 'stage_failure' => 'Failure', + 'stage_broken' => 'Broken', + 'stage_fixed' => 'Fixed', + + // Installer + 'installation_url' => 'PHPCI Installation URL', + 'db_host' => 'Database Host', + 'db_name' => 'Database Name', + 'db_user' => 'Database Username', + 'db_pass' => 'Database Password', + 'admin_name' => 'Admin Name', + 'admin_pass' => 'Admin Password', + 'admin_email' => 'Admin Email Address', + 'config_path' => 'Config File Path', + 'install_phpci' => 'Install PHPCI', + 'welcome_to_phpci' => 'Welcome to PHPCI', + 'please_answer' => 'Please answer the following questions:', + 'phpci_php_req' => 'PHPCI requires at least PHP 5.3.8 to function.', + 'extension_required' => 'Extension required: %s', + 'function_required' => 'PHPCI needs to be able to call the %s() function. Is it disabled in php.ini?', + 'requirements_not_met' => 'PHPCI cannot be installed, as not all requirements are met. + Please review the errors above before continuing.', + 'must_be_valid_email' => 'Must be a valid email address.', + 'must_be_valid_url' => 'Must be a valid URL.', + 'enter_name' => 'Admin Name: ', + 'enter_email' => 'Admin Email: ', + 'enter_password' => 'Admin Password: ', + 'enter_phpci_url' => 'Your PHPCI URL ("http://phpci.local" for example): ', + + 'enter_db_host' => 'Please enter your MySQL host [localhost]: ', + 'enter_db_name' => 'Please enter your MySQL database name [phpci]: ', + 'enter_db_user' => 'Please enter your MySQL username [phpci]: ', + 'enter_db_pass' => 'Please enter your MySQL password: ', + 'could_not_connect' => 'PHPCI could not connect to MySQL with the details provided. Please try again.', + 'setting_up_db' => 'Setting up your database... ', + 'user_created' => 'User account created!', + 'failed_to_create' => 'PHPCI failed to create your admin account.', + 'config_exists' => 'The PHPCI config file exists and is not empty.', + 'update_instead' => 'If you were trying to update PHPCI, please use phpci:update instead.', + + // Update + 'update_phpci' => 'Update the database to reflect modified models.', + 'updating_phpci' => 'Updating PHPCI database: ', + 'not_installed' => 'PHPCI does not appear to be installed.', + 'install_instead' => 'Please install PHPCI via phpci:install instead.', + + // Poll Command + 'poll_github' => 'Poll GitHub to check if we need to start a build.', + 'no_token' => 'No GitHub token found', + 'finding_projects' => 'Finding projects to poll', + 'found_n_projects' => 'Found %d projects', + 'last_commit_is' => 'Last commit to GitHub for %s is %s', + 'adding_new_build' => 'Last commit is different to database, adding new build.', + 'finished_processing_builds' => 'Finished processing builds.', + + // Create Admin + 'create_admin_user' => 'Create an admin user', + 'incorrect_format' => 'Incorrect format', + + // Create Build Command + 'create_build_project' => 'Create a build for a project', + 'project_id_argument' => 'A project ID', + 'commit_id_option' => 'Commit ID to build', + 'branch_name_option' => 'Branch to build', + 'add_to_queue_failed' => 'Build created successfully, but failed to add to build queue. This usually happens + when PHPCI is set to use a beanstalkd server that does not exist, + or your beanstalkd server has stopped.', + + // Run Command + 'run_all_pending' => 'Run all pending PHPCI builds.', + 'finding_builds' => 'Finding builds to process', + 'found_n_builds' => 'Found %d builds', + 'skipping_build' => 'Skipping Build %d - Project build already in progress.', + 'marked_as_failed' => 'Build %d marked as failed due to timeout.', + + // Builder + 'missing_phpci_yml' => 'This project does not contain a phpci.yml file, or it is empty.', + 'build_success' => 'BUILD SUCCESS', + 'build_failed' => 'BUILD FAILED', + 'removing_build' => 'Removing Build.', + 'exception' => 'Exception: ', + 'could_not_create_working' => 'Could not create a working copy.', + 'working_copy_created' => 'Working copy created: %s', + 'looking_for_binary' => 'Looking for binary: %s', + 'found_in_path' => 'Found in %s: %s', + 'running_plugin' => 'RUNNING PLUGIN: %s', + 'plugin_success' => 'PLUGIN: SUCCESS', + 'plugin_failed' => 'PLUGIN: FAILED', + 'plugin_missing' => 'Plugin does not exist: %s', + 'tap_version' => 'TapParser only supports TAP version 13', + 'tap_error' => 'Invalid TAP string, number of tests does not match specified test count.', + + // Build Plugins: + 'no_tests_performed' => 'No tests have been performed.', + 'could_not_find' => 'Could not find %s', + 'no_campfire_settings' => 'No connection parameters given for Campfire plugin', + 'failed_to_wipe' => 'Failed to wipe existing directory %s before copy', + 'passing_build' => 'Passing Build', + 'failing_build' => 'Failing Build', + 'log_output' => 'Log Output: ', + 'n_emails_sent' => '%d emails sent.', + 'n_emails_failed' => '%d emails failed to send.', + 'unable_to_set_env' => 'Unable to set environment variable', + 'tag_created' => 'Tag created by PHPCI: %s', + 'x_built_at_x' => '%PROJECT_TITLE% built at %BUILD_URI%', + 'hipchat_settings' => 'Please define room and authToken for hipchat_notify plugin', + 'irc_settings' => 'You must configure a server, room and nick.', + 'invalid_command' => 'Invalid command', + 'import_file_key' => 'Import statement must contain a \'file\' key', + 'cannot_open_import' => 'Cannot open SQL import file: %s', + 'unable_to_execute' => 'Unable to execute SQL file', + 'phar_internal_error' => 'Phar Plugin Internal Error', + 'build_file_missing' => 'Specified build file does not exist.', + 'property_file_missing' => 'Specified property file does not exist.', + 'could_not_process_report' => 'Could not process the report generated by this tool.', + 'shell_not_enabled' => 'The shell plugin is not enabled. Please enable it via config.yml.', + + + // Error Levels: + 'critical' => 'Critical', + 'high' => 'High', + 'normal' => 'Normal', + 'low' => 'Low', + + // Plugins that generate errors: + 'php_mess_detector' => 'PHP Mess Detector', + 'php_code_sniffer' => 'PHP Code Sniffer', + 'php_unit' => 'PHP Unit', + 'php_cpd' => 'PHP Copy/Paste Detector', + 'php_docblock_checker' => 'PHP Docblock Checker', + 'behat' => 'Behat', + 'technical_debt' => 'Technical Debt', + +); diff --git a/PHPCI/Languages/lang.es.php b/PHPCI/Languages/lang.es.php new file mode 100644 index 00000000..1b7b33f7 --- /dev/null +++ b/PHPCI/Languages/lang.es.php @@ -0,0 +1,387 @@ + 'Español', + 'language' => 'Lenguaje', + + // Log in: + 'log_in_to_phpci' => 'Ingresar a PHPCI', + 'login_error' => 'Email o contraseña incorrectos', + 'forgotten_password_link' => '¿Olvidaste tu contraseña?', + 'reset_emailed' => 'Te hemos enviado un email para reiniciar tu contraseña.', + 'reset_header' => '¡No te preocupes!
Solo tienes que ingresar tu dirección de email + y te enviaremos por email un enlace para reiniciar tu contraseña.', + 'reset_email_address' => 'Ingresa tu dirección de email:', + 'reset_send_email' => 'Enviar enlace', + 'reset_enter_password' => 'Ingresa una nueva contraseña', + 'reset_new_password' => 'Nueva contraseña:', + 'reset_change_password' => 'Cambiar contraseña', + 'reset_no_user_exists' => 'No existe ningún usuario con ese email, por favor intenta nuevamente.', + 'reset_email_body' => 'Hola %s, + +Has recibido este correo porque tú, o alguien más, ha solicitado reiniciar la contraseña de PHPCI + +Si fuiste tú, por favor haz click en el siguiente enlace para reiniciar tu contraseña: %ssession/reset-password/%d/%s + +De lo contrario, por favor ignora este correo y ninguna acción será realizada. + +Gracias, + +PHPCI', + + 'reset_email_title' => 'Reiniciar contraseña de PHPCI para %s', + 'reset_invalid' => 'Pedido inválido.', + 'email_address' => 'Dirección de email', + 'password' => 'Contraseña', + 'log_in' => 'Ingresar', + + + // Top Nav + 'toggle_navigation' => 'Activar navegación', + 'n_builds_pending' => '%d builds pendientes', + 'n_builds_running' => '%d builds ejecutándose', + 'edit_profile' => 'Editar Perfil', + 'sign_out' => 'Cerrar Sesión', + 'branch_x' => 'Rama: %s', + 'created_x' => 'Creada el: %s', + 'started_x' => 'Comenzó: %s', + + // Sidebar + 'hello_name' => 'Hola, %s', + 'dashboard' => 'Escritorio', + 'admin_options' => 'Opciones de Admin.', + 'add_project' => 'Agregar Proyecto', + 'settings' => 'Configuración', + 'manage_users' => 'Administrar Usuarios', + 'plugins' => 'Plugins', + 'view' => 'Vista', + 'build_now' => 'Ejecutar Build', + 'edit_project' => 'Editar Proyecto', + 'delete_project' => 'Eliminar Proyecto', + + // Project Summary: + 'no_builds_yet' => '¡No existen builds aún!', + 'x_of_x_failed' => '%d de los últimos %d builds fallaron.', + 'x_of_x_failed_short' => '%d / %d fallaron.', + 'last_successful_build' => ' El último build exitoso fue %s.', + 'never_built_successfully' => ' Este proyecto nunca tuvo un build exitoso.', + 'all_builds_passed' => 'Todos los últimos %d builds pasaron.', + 'all_builds_passed_short' => '%d / %d pasaron.', + 'last_failed_build' => ' El último build en fallar fue %s.', + 'never_failed_build' => ' Este proyecto no tiene ningún build fallido.', + 'view_project' => 'Ver Proyecto', + + // Timeline: + 'latest_builds' => 'Últimos builds', + 'pending' => 'Pediente', + 'running' => 'Ejecutando', + 'success' => 'Éxito', + 'successful' => 'Exitoso', + 'failed' => 'Falló', + 'manual_build' => 'Build Manual', + + // Add/Edit Project: + 'new_project' => 'Nuevo Proyecto', + 'project_x_not_found' => 'El Proyecto con ID %d no existe.', + 'project_details' => 'Detalles del Proyecto', + 'public_key_help' => 'Para facilitarte, hemos generado un par de llaves SSH para que uses en este proyecto. + Para usarlo, sólo agrega la siguiente llave pública a la sección de "deploy keys" + de tu plataforma de hosting de versionado de código.', + 'select_repository_type' => 'Selecciona tipo de repositorio...', + 'github' => 'GitHub', + 'bitbucket' => 'Bitbucket', + 'gitlab' => 'GitLab', + 'remote' => 'URL Remota', + 'local' => 'Path local', + 'hg' => 'Mercurial', + 'svn' => 'Subversion', + + 'where_hosted' => '¿Dónde está alojado tu proyecto?', + 'choose_github' => 'Selecciona un repositorio de GitHub:', + + 'repo_name' => 'Nombre del repositorio / URL (Remoto) o Ruta (Local)', + 'project_title' => 'Titulo del proyecto', + 'project_private_key' => 'Clave privada a usar para acceder al repositorio + (dejar en blanco para remotos locales o anónimos)', + 'build_config' => 'Configuración PHPCI para builds del proyecto + (en caso que no puedas agregar el archivo phpci.yml al repositorio)', + 'default_branch' => 'Nombre de la rama por defecto', + 'allow_public_status' => '¿Activar página pública con el estado del proyecto?', + 'archived' => 'Archivado', + 'archived_menu' => 'Archivado', + 'save_project' => 'Guardar Proyecto', + + 'error_mercurial' => 'La URL del repositorio de Mercurial debe comenzar con http:// or https://', + 'error_remote' => 'La URL del repositorio debe comenzar con git://, http:// or https://', + 'error_gitlab' => 'El nombre del repositorio de GitLab debe tener el formato "user@domain.tld:owner/repo.git"', + 'error_github' => 'El nombre del repositorio debe tener el formato "owner/repo"', + 'error_bitbucket' => 'El nombre del repo debe tener el formato "owner/repo"', + 'error_path' => 'La ruta especificada no existe.', + + // View Project: + 'all_branches' => 'Todas las ramas', + 'builds' => 'Builds', + 'id' => 'ID', + 'project' => 'Proyecto', + 'commit' => 'Commit', + 'branch' => 'Rama', + 'status' => 'Estado', + 'prev_link' => '« Anterior', + 'next_link' => 'Siguiente »', + 'public_key' => 'Llave pública', + 'delete_build' => 'Eliminar Build', + + 'webhooks' => 'Webhooks', + 'webhooks_help_github' => 'Para compilar automáticamente este proyecto cada vez que se realiza un commit, agreagar la siguiente URL + como un nuevo "webhook" en la sección Webhooks + and Services de tu repositorio en GitHub.', + + 'webhooks_help_gitlab' => 'Para compilar automáticamente este proyecto, cada vez que se realiza un commit, agreagar la siguiente URL + como una "WebHook URL" en la sección "web hooks" de tu repositorio en GitLab.', + + 'webhooks_help_bitbucket' => 'Para compilar automáticamente este proyecto, cada vez que se realiza un commit, agreagar la siguiente URL + como un servicio "POST" en la sección + + Services de tu repositorio en Bitbucket.', + + // View Build + 'build_x_not_found' => 'El build con ID %d no existe.', + 'build_n' => 'Build %d', + 'rebuild_now' => 'Rebuild Ahora', + + + 'committed_by_x' => 'Commit hecho por %s', + 'commit_id_x' => 'Commit: %s', + + 'chart_display' => 'Este gráfico será mostrado una vez que el build se haya completado.', + + 'build' => 'Build', + 'lines' => 'Líneas', + 'comment_lines' => 'Líneas de comentario', + 'noncomment_lines' => 'Líneas no comentario', + 'logical_lines' => 'Líneas lógicas', + 'lines_of_code' => 'Líneas de código', + 'build_log' => 'Log', + 'quality_trend' => 'Tendencia de calidad', + 'codeception_errors' => 'Errores de Codeception', + 'phpmd_warnings' => 'PHPMD Warnings', + 'phpcs_warnings' => 'PHPCS Warnings', + 'phpcs_errors' => 'PHPCS Errors', + 'phplint_errors' => 'Lint Errors', + 'phpunit_errors' => 'PHPUnit Errors', + 'phpdoccheck_warnings' => 'Docblocks faltantes', + 'issues' => 'Incidencias', + + 'codeception' => 'Codeception', + 'phpcpd' => 'PHP Copy/Paste Detector', + 'phpcs' => 'PHP Code Sniffer', + 'phpdoccheck' => 'Missing Docblocks', + 'phpmd' => 'PHP Mess Detector', + 'phpspec' => 'PHP Spec', + 'phpunit' => 'PHP Unit', + 'technical_debt' => 'Deuda Técnica', + 'behat' => 'Behat', + + 'file' => 'Archivo', + 'line' => 'Línea', + 'class' => 'Clase', + 'method' => 'Método', + 'message' => 'Mensaje', + 'start' => 'Inicio', + 'end' => 'Fin', + 'from' => 'De', + 'to' => 'Para', + 'suite' => 'Suite', + 'test' => 'Test', + 'result' => 'Resultado', + 'ok' => 'OK', + 'took_n_seconds' => 'Tomó %d segundos', + 'build_created' => 'Build Creado', + 'build_started' => 'Build Comenzado', + 'build_finished' => 'Build Terminado', + + // Users + 'name' => 'Nombre', + 'password_change' => 'Contraseña (dejar en blanco si no quiere cambiarla)', + 'save' => 'Guardar »', + 'update_your_details' => 'Actualizar los detalles', + 'your_details_updated' => 'Tus detalles han sido actualizados.', + 'add_user' => 'Agregar Usuario', + 'is_admin' => '¿Es Admin?', + 'yes' => 'Si', + 'no' => 'No', + 'edit' => 'Editar', + 'edit_user' => 'Editar Usuario', + 'delete_user' => 'Delete Usuario', + 'user_n_not_found' => 'Usuario con ID %d no existe.', + 'is_user_admin' => '¿Es un usuario administrador?', + 'save_user' => 'Guardar Usuario', + + // Settings: + 'settings_saved' => 'Tu configuración ha sido guardada.', + 'settings_check_perms' => 'Tu configuración no fue guardada, verificar los permisos del archivo config.yml.', + 'settings_cannot_write' => 'PHPCI no puede escribir en el archivo config.yml file, la configuración no será guardada correctamente + hasta no corregir esto.', + 'settings_github_linked' => 'Tu cuenta GitHub ha sido conectada.', + 'settings_github_not_linked' => 'No se pudo conectar a tu cuenta GitHub.', + 'build_settings' => 'Configuración del Build ', + 'github_application' => 'Aplicación GitHub', + 'github_sign_in' => 'Antes de comenzar a utilizar GitHub, tienes que ingresar y permitir + el acceso a tu cuenta a PHPCI.', + 'github_phpci_linked' => 'PHPCI ha sido conectado a tu cuenta GitHub.', + 'github_where_to_find' => 'Donde encontrar estos...', + 'github_where_help' => 'Si eres priopietario de la aplicaión que quieres usar, puedes encontrar esta información en + el área de configuración de aplicaciones.', + + 'email_settings' => 'Configuraciones de Email', + 'email_settings_help' => 'Para que PHPCI pueda enviar email con el status de los builds, + debes configurar las siguientes propiedades SMTP.', + + 'application_id' => 'ID de aplicación', + 'application_secret' => 'Application Secret', + + 'smtp_server' => 'Servidor SMTP', + 'smtp_port' => 'Puerto SMTP', + 'smtp_username' => 'Usuario SMTP', + 'smtp_password' => 'Contraseña SMTP', + 'from_email_address' => 'Dirección de email DE', + 'default_notification_address' => 'Dirección de correo de notificación por defecto', + 'use_smtp_encryption' => 'Usar encriptación SMTP?', + 'none' => 'None', + 'ssl' => 'SSL', + 'tls' => 'TLS', + + 'failed_after' => 'Considerar el build como fallido luego de ', + '5_mins' => '5 Minutos', + '15_mins' => '15 Minutos', + '30_mins' => '30 Minutos', + '1_hour' => '1 Hora', + '3_hours' => '3 Horas', + + // Plugins + 'cannot_update_composer' => 'PHPCI no puede actualizar composer.json porque no tiene permisos de escritura.', + 'x_has_been_removed' => '%s ha sido elimiando.', + 'x_has_been_added' => '%s ha sido agregado a composer.json y será instalado la próxima vez que ejecutes composer update.', + 'enabled_plugins' => 'Activar Plugins', + 'provided_by_package' => 'Provisto por Paquete', + 'installed_packages' => 'Paquetes Instalados', + 'suggested_packages' => 'Paquetes Sugeridos', + 'title' => 'Título', + 'description' => 'Descripción', + 'version' => 'Versión', + 'install' => 'Instalar »', + 'remove' => 'Eliminar »', + 'search_packagist_for_more' => 'Buscar más paquetes en Packagist', + 'search' => 'Buscar »', + + // Installer + 'installation_url' => 'URL de la instalación PHPCI', + 'db_host' => 'Host', + 'db_name' => 'Nombre de la base de datos', + 'db_user' => 'Usuario de la base de datos', + 'db_pass' => 'Clave de la base de datos', + 'admin_name' => 'Nombre del Admin', + 'admin_pass' => 'Clave del Admin', + 'admin_email' => 'Email de Admin', + 'config_path' => 'Ruta al archivo config', + 'install_phpci' => 'Instalar PHPCI', + 'welcome_to_phpci' => 'Bienvenido a PHPCI', + 'please_answer' => 'Por favor, responde las siguientes preguntas:', + 'phpci_php_req' => 'PHPCI requiere al menos PHP 5.3.8 para funcionar.', + 'extension_required' => 'Extensión requerida: %s', + 'function_required' => 'PHPCI debe poder invocar la función %s(). Está deshabilitada en php.ini?', + 'requirements_not_met' => 'PHPCI no pudo ser instalado, ya que no se cumplen todos los requerimientos. + Por favor, corrige los errores antes de continuar.', + 'must_be_valid_email' => 'Debe ser una dirección de correos válida.', + 'must_be_valid_url' => 'Debe ser una URL válida.', + 'enter_name' => 'Nombre del Admin:', + 'enter_email' => 'Email del Admin:', + 'enter_password' => 'Contraseña de Admin:', + 'enter_phpci_url' => 'La URL de PHPCI ("Por ejemplo: http://phpci.local"): ', + + 'enter_db_host' => 'Por favor, ingresa el servidor MySQL [localhost]: ', + 'enter_db_name' => 'Por favor, ingresa el nombre de la base de datos MySQL [phpci]: ', + 'enter_db_user' => 'Por favor, ingresa el usuario MySQL [phpci]: ', + 'enter_db_pass' => 'Por favor, ingresa la contraseña MySQL: ', + 'could_not_connect' => 'PHPCI no pudo conectarse a MySQL con los datos dados. Por favor, intenta nuevamente.', + 'setting_up_db' => 'Configurando base de datos... ', + 'user_created' => '¡Cuenta de usuario creada!', + 'failed_to_create' => 'PHPCI no pudo crear la cuenta de admin.', + 'config_exists' => 'El archivo config de PHPCI ya existe y no es vacío.', + 'update_instead' => 'Si está intentando actualizar PHPCI, por favor, utiliza phpci:update.', + + // Update + 'update_phpci' => 'Actuliza la base de datos para reflejar los modelos actualizados.', + 'updating_phpci' => 'Actualizando base de datos PHPCI: ', + 'not_installed' => 'PHPCI no está instalado.', + 'install_instead' => 'Por favor, instala PHPCI via phpci:install.', + + // Poll Command + 'poll_github' => 'Chequear en GitHub si se necesita comenzar un Build.', + 'no_token' => 'No se encontró ningún token GitHub', + 'finding_projects' => 'Buscando proyectos para chequear', + 'found_n_projects' => 'Se encontraron %d proyectos', + 'last_commit_is' => 'El último commit en GitHub para %s es %s', + 'adding_new_build' => 'Último commit es diferente a la base de datos, agregando nuevo build.', + 'finished_processing_builds' => 'Fin de procesamiento de builds.', + + // Create Admin + 'create_admin_user' => 'Crear un usuario Admin', + 'incorrect_format' => 'Formato incorrecto', + + // Run Command + 'run_all_pending' => 'Ejecutar todos los builds PHPCI pendientes.', + 'finding_builds' => 'Buscando builds a procesar', + 'found_n_builds' => 'Se encontraron %d builds', + 'skipping_build' => 'Saltando Build %d - Build del proyecto ya en ejecución.', + 'marked_as_failed' => 'Build %d falló debido a timeout.', + + // Builder + 'missing_phpci_yml' => 'Este proyecto no contiene el archivo phpci.yml o está vacío.', + 'build_success' => 'BUILD EXITOSO', + 'build_failed' => 'BUILD FALLIDO', + 'removing_build' => 'Eliminando Build.', + 'exception' => 'Excepción: ', + 'could_not_create_working' => 'Imposible crear copia de trabajo.', + 'working_copy_created' => 'Copia de trabajo creada: %s', + 'looking_for_binary' => 'Buscando binario: %s', + 'found_in_path' => 'Encontrado en %s: %s', + 'running_plugin' => 'EJECUTANDO PLUGIN: %s', + 'plugin_success' => 'PLUGIN: EXITO', + 'plugin_failed' => 'PLUGIN: FALLÓ', + 'plugin_missing' => 'No existe el plugin: %s', + 'tap_version' => 'TapParser únicamente soporta la verisón 13 de TAP', + 'tap_error' => 'Cadena de caracteres TAP inválida, el número de tests no coincide con la cuenta de tests declarada.', + + // Build Plugins: + 'no_tests_performed' => 'No se ejecutaron tests.', + 'could_not_find' => 'No se encontró %s', + 'no_campfire_settings' => 'No se especificaron parámetros de conexión para el plugin Campfire', + 'failed_to_wipe' => 'Imposible eliminar directorio existente %s antes de copiarlo', + 'passing_build' => 'Build Exitoso', + 'failing_build' => 'Build Fallido', + 'log_output' => 'Log de Salida: ', + 'n_emails_sent' => '%d emails enviados.', + 'n_emails_failed' => '%d emails no pudieron ser enviados.', + 'unable_to_set_env' => 'Imposible setear variable de entorno', + 'tag_created' => 'Tag creado por PHPCI: %s', + 'x_built_at_x' => 'Build de %PROJECT_TITLE% en %BUILD_URI%', + 'hipchat_settings' => 'Por favor, definir room y authToken para el plugin hipchat_notify', + 'irc_settings' => 'Debes configurar un servidor, room y apodo.', + 'invalid_command' => 'Comando inválido', + 'import_file_key' => 'Sentencia de importación debe contener una llave \'file\'', + 'cannot_open_import' => 'Imposible abrir archivo de importación SQL: %s', + 'unable_to_execute' => 'Imposible ejecutar archivo SQL', + 'phar_internal_error' => 'Error interno en plugin Phar', + 'build_file_missing' => 'El archivo de build especificado no existe.', + 'property_file_missing' => 'El archivo de propiedades especificado no existe.', + 'could_not_process_report' => 'Imposible procesar el reporte generado por la herramienta.', + 'shell_not_enabled' => 'El plugin shell no está habilitado. Por favor, habilitalo desde config.yml.' +); diff --git a/PHPCI/Languages/lang.fr.php b/PHPCI/Languages/lang.fr.php new file mode 100644 index 00000000..a0f2cab8 --- /dev/null +++ b/PHPCI/Languages/lang.fr.php @@ -0,0 +1,410 @@ + 'Français', + 'language' => 'Langue', + + // Log in: + 'log_in_to_phpci' => 'Connectez-vous à PHPCI', + 'login_error' => 'Adresse email ou mot de passe invalide', + 'forgotten_password_link' => 'Mot de passe oublié ?', + 'reset_emailed' => 'Nous vous avons envoyé un email avec un lien pour réinitialiser votre mot de passe.', + 'reset_header' => 'Pas d\'inquiétude
Entrez simplement votre adresse email ci-dessous + et nous vous enverrons un message avec un lien pour réinitialiser votre mot de passe.', + 'reset_email_address' => 'Entrez votre adresse email:', + 'reset_send_email' => 'Envoyer le mail', + 'reset_enter_password' => 'Veuillez entrer un nouveau mot de passe', + 'reset_new_password' => 'Nouveau mot de passe :', + 'reset_change_password' => 'Modifier le mot de passe', + 'reset_no_user_exists' => 'Il n\'existe aucun utilisateur avec cette adresse email, merci de réessayer.', + 'reset_email_body' => 'Bonjour %s, + +Vous avez reçu cet email parce qu\'une demande de réinitialisation de mot de passe a été faite pour votre compte PHPCI. + +Si c\'est bien vous, merci de cliquer sur le lien suivant pour réinitialiser votre mot de passe : %ssession/reset-password/%d/%s + +Sinon, merci d\'ignorer ce message. + +Merci, + +PHPCI', + + 'reset_email_title' => 'Réinitialisation du mot de passe PHPCI pour %s', + 'reset_invalid' => 'Requête de réinitialisation de mot de passe invalide.', + 'email_address' => 'Adresse email', + 'login' => 'Login / Email Address', + 'password' => 'Mot de passe', + 'log_in' => 'Connexion', + + + // Top Nav + 'toggle_navigation' => 'Afficher/cacher la navigation', + 'n_builds_pending' => '%d builds en attente', + 'n_builds_running' => '%d builds en cours d\'exécution', + 'edit_profile' => 'Éditer le profil', + 'sign_out' => 'Déconnexion', + 'branch_x' => 'Branche : %s', + 'created_x' => 'Créé à : %s', + 'started_x' => 'Démarré à : %s', + + // Sidebar + 'hello_name' => 'Salut %s', + 'dashboard' => 'Tableau de bord', + 'admin_options' => 'Options d\'administration', + 'add_project' => 'Ajouter un projet', + 'settings' => 'Paramètres', + 'manage_users' => 'Gérer les utilisateurs', + 'plugins' => 'Plugins', + 'view' => 'Voir', + 'build_now' => 'Démarrer le build', + 'edit_project' => 'Éditer le projet', + 'delete_project' => 'Supprimer le projet', + + // Project Summary: + 'no_builds_yet' => 'Aucun build pour le moment !', + 'x_of_x_failed' => '%d des %d derniers builds ont échoué.', + 'x_of_x_failed_short' => '%d échecs / %d.', + 'last_successful_build' => ' Le dernier build réussi date du %s.', + 'never_built_successfully' => ' Aucun build de ce projet n\'a réussi.', + 'all_builds_passed' => 'Les %d derniers builds ont réussi.', + 'all_builds_passed_short' => '%d réussites / %d.', + 'last_failed_build' => ' Le dernier build en échec date du %s.', + 'never_failed_build' => ' Aucun build de ce projet n\'a échoué.', + 'view_project' => 'Voir le projet', + + // Timeline: + 'latest_builds' => 'Derniers builds', + 'pending' => 'En attente', + 'running' => 'En cours', + 'success' => 'Terminé', + 'successful' => 'Réussi', + 'failed' => 'Échoué', + 'manual_build' => 'Build manuel', + + // Add/Edit Project: + 'new_project' => 'Nouveau Projet', + 'project_x_not_found' => 'Il n\'existe pas de Projet avec l\'ID %d.', + 'project_details' => 'Détails du Projet', + 'public_key_help' => 'Pour pouvoir démarrer plus facilement, nous avons généré une paire de clés SSH à utiliser avec ce projet. + Pour l\'utiliser, il faut simplement ajouter la clé publique dans la section "Clés de déploiement" + de votre outil d\'hébergement de code.', + 'select_repository_type' => 'Sélectionnez le type de dépôt...', + 'github' => 'GitHub', + 'bitbucket' => 'Bitbucket', + 'gitlab' => 'GitLab', + 'remote' => 'URL distante', + 'local' => 'Chemin local', + 'hg' => 'Mercurial', + + 'where_hosted' => 'Où est hébergé votre projet ?', + 'choose_github' => 'Choisissez un dépôt GitHub :', + + 'repo_name' => 'Nom du dépôt / URL (distance) ou chemin (local)', + 'project_title' => 'Titre du projet', + 'project_private_key' => 'Clé privée à utiliser pour accéder au dépôt + (laissez le champ vide pour les dépôts locaux ou les URLs distantes anonymes)', + 'build_config' => 'Configuration PHPCI spécifique pour ce projet + (si vous ne pouvez pas ajouter de fichier phpci.yml à la racine du dépôt)', + 'default_branch' => 'Nom de la branche par défaut', + 'allow_public_status' => 'Activer la page de statut publique et l\'image pour ce projet ?', + 'archived' => 'Archived', + 'archived_menu' => 'Archived', + 'save_project' => 'Enregistrer le projet', + + 'error_mercurial' => 'Les URLs de dépôt Mercurial doivent commencer par http:// ou https://', + 'error_remote' => 'Les URLs de dépôt doivent commencer par git://, http:// ou https://', + 'error_gitlab' => 'Le nom du dépôt GitLab doit avoir le format "user@domain.tld:owner/repo.git"', + 'error_github' => 'Le nom du dépôt doit être dans le format "proprietaire/dépôt"', + 'error_bitbucket' => 'Le nom du dépôt doit être dans le format "proprietaire/dépôt"', + 'error_path' => 'Le chemin que vous avez spécifié n\'existe pas.', + + // View Project: + 'all_branches' => 'Toutes les branches', + 'builds' => 'Builds', + 'id' => 'ID', + 'date' => 'Date', + 'project' => 'Projet', + 'commit' => 'Commit', + 'branch' => 'Branche', + 'status' => 'Statut', + 'prev_link' => '« Précédent', + 'next_link' => 'Suivant »', + 'public_key' => 'Clé Publique', + 'delete_build' => 'Supprimer le build', + + 'webhooks' => 'Webhooks', + 'webhooks_help_github' => 'Pour générer un build quand de nouveaux commits sont poussés, ajouter l\'url suivante + en tant que new "Webhook" dans la section Webhooks + and Services de votre dépôt GitHub.', + + 'webhooks_help_gitlab' => 'Pour générer un build quand de nouveaux commits sont poussés, ajouter l\'url suivante + and tant que "WebHook URL" dans la section Web Hooks de votre dépôt GitLab.', + + 'webhooks_help_bitbucket' => 'Pour générer un build quand de nouveaux commits sont poussés, ajouter l\'url suivante + en tant que service "POST" dans la section + + Services de votre dépôt Bitbucket.', + + // View Build + 'build_x_not_found' => 'Le Build avec l\'ID %d n\'existe pas.', + 'build_n' => 'Build %d', + 'rebuild_now' => 'Relancer maintenant', + + + 'committed_by_x' => 'Committé par %s', + 'commit_id_x' => 'Commit : %s', + + 'chart_display' => 'Ce graphique s\'affichera une fois que le build sera terminé.', + + 'build' => 'Build', + 'lines' => 'Lignes', + 'comment_lines' => 'Lignes de commentaires', + 'noncomment_lines' => 'Lignes qui ne sont pas des commentaires', + 'logical_lines' => 'Lignes logiques', + 'lines_of_code' => 'Lignes de code', + 'build_log' => 'Log du build', + 'quality_trend' => 'Tendance de la qualité', + 'codeception_errors' => 'Erreurs Codeception', + 'phpmd_warnings' => 'Alertes PHPMD', + 'phpcs_warnings' => 'Alertes PHPCS', + 'phpcs_errors' => 'Erreurs PHPCS', + 'phplint_errors' => 'Erreurs de Lint', + 'phpunit_errors' => 'Erreurs PHPUnit', + 'phpdoccheck_warnings' => 'Blocs de documentation manquants', + 'issues' => 'Tickets', + + 'codeception' => 'Codeception', + 'phpcpd' => 'PHP Copy/Paste Detector', + 'phpcs' => 'PHP Code Sniffer', + 'phpdoccheck' => 'Missing Docblocks', + 'phpmd' => 'PHP Mess Detector', + 'phpspec' => 'PHP Spec', + 'phpunit' => 'PHP Unit', + + 'file' => 'Fichier', + 'line' => 'Ligne', + 'class' => 'Classe', + 'method' => 'Méthode', + 'message' => 'Message', + 'start' => 'Démarrage', + 'end' => 'Fin', + 'from' => 'À partir de', + 'to' => 'jusque', + 'result' => 'Resultat', + 'ok' => 'OK', + 'took_n_seconds' => 'Exécuté en %d secondes', + 'build_created' => 'Build créé', + 'build_started' => 'Build démarré', + 'build_finished' => 'Build terminé', + 'test_message' => 'Message', + 'test_no_message' => 'Pas de message', + 'test_success' => 'Réussi(s): %d', + 'test_fail' => 'Echec(s): %d', + 'test_skipped' => 'Passé(s): %d', + 'test_error' => 'Erreurs: %d', + 'test_todo' => 'Todos: %d', + 'test_total' => '%d test(s)', + + // Users + 'name' => 'Nom', + 'password_change' => 'Mot de passe (laissez vide si vous ne voulez pas le changer)', + 'save' => 'Sauvegarder »', + 'update_your_details' => 'Mettre à jour vos préférences', + 'your_details_updated' => 'Vos préférences ont été bien mises à jour.', + 'add_user' => 'Ajouter un utilisateur', + 'is_admin' => 'Est-il administrateur ?', + 'yes' => 'Oui', + 'no' => 'Non', + 'edit' => 'Éditer', + 'edit_user' => 'Éditer l\'utilisateur', + 'delete_user' => 'Supprimer l\'utilisateur', + 'user_n_not_found' => 'L\'utilisateur avec l\'ID %d n\'existe pas.', + 'is_user_admin' => 'Est-ce que cet utilisateur est administrateur?', + 'save_user' => 'Sauvegarder l\'utilisateur', + + // Settings: + 'settings_saved' => 'Vos paramètres ont été sauvegardés.', + 'settings_check_perms' => 'Vos paramètres n\'ont pas pu être sauvegardés, vérifiez les permissions sur le fichier config.yml.', + 'settings_cannot_write' => 'PHPCI ne peut pas écrire dans votre fichier config.yml, les paramètres ne pourront pas être sauvegardés correctement + tant que ce ne sera pas corrigé.', + 'settings_github_linked' => 'Votre compte GitHub n\'a pas été lié.', + 'settings_github_not_linked' => 'Votre compte GitHub ne peut pas être lié.', + 'build_settings' => 'Configuration du Build', + 'github_application' => 'Application GitHub', + 'github_sign_in' => 'Avant de commencer à utiliser GitHub, vous devez vous connecter et autoriser + PHPCI à accéder à votre compte.', + 'github_phpci_linked' => 'PHPCI s\'est connecté avec succès au compte GitHub.', + 'github_where_to_find' => 'Où trouver ces informations...', + 'github_where_help' => 'Si vous souhaitez utiliser une application qui vous appartient, vous pouvez trouver ces informations dans + la zone de paramètres applications.', + + 'email_settings' => 'Configuration Email', + 'email_settings_help' => 'Avant que PHPCI puisse envoyer des emails concernant les statuts de build, + vous devez entrer les configurations SMTP ci-dessous.', + + 'application_id' => 'Identifiant d\'application', + 'application_secret' => 'Clé secrète de l\'application', + + 'smtp_server' => 'Serveur SMTP', + 'smtp_port' => 'Port SMTP', + 'smtp_username' => 'Nom d\'utilisateur SMTP', + 'smtp_password' => 'Mot de passe SMTP', + 'from_email_address' => 'Adresse à partir de laquelle sont envoyés les emails', + 'default_notification_address' => 'Adresse de notification par défaut', + 'use_smtp_encryption' => 'Est-ce que vous voulez utiliser le chiffrement SMTP', + 'none' => 'Non', + 'ssl' => 'SSL', + 'tls' => 'TLS', + + 'failed_after' => 'Considérer qu\'un build a échoué après', + '5_mins' => '5 Minutes', + '15_mins' => '15 Minutes', + '30_mins' => '30 Minutes', + '1_hour' => '1 Heure', + '3_hours' => '3 Heures', + + // Plugins + 'cannot_update_composer' => 'PHPCI ne peut pas mettre à jour le fichier composer.json pour vous, il n\'est pas modifiable.', + 'x_has_been_removed' => '%s a été supprimé.', + 'x_has_been_added' => '%s a été ajouté au fichier composer.json pour vous et il sera installé la prochaine fois + que vous lancerez "composer update".', + 'enabled_plugins' => 'Plugins activés', + 'provided_by_package' => 'Fournis par le paquet', + 'installed_packages' => 'Paquets installés', + 'suggested_packages' => 'Paquets suggérés', + 'title' => 'Titre', + 'description' => 'Description', + 'version' => 'Version', + 'install' => 'Installer »', + 'remove' => 'Supprimer »', + 'search_packagist_for_more' => 'Rechercher sur Packagist pour trouver plus de paquets', + 'search' => 'Rechercher »', + + // Summary plugin + 'build-summary' => 'Résumé', + 'stage' => 'Étape', + 'duration' => 'Durée', + 'plugin' => 'Plugin', + 'stage_setup' => 'Préparation', + 'stage_test' => 'Test', + 'stage_complete' => 'Terminé', + 'stage_success' => 'Succes', + 'stage_failure' => 'Échec', + + // Installer + 'installation_url' => 'URL d\'installation de PHPCI', + 'db_host' => 'Hôte de la BDD', + 'db_name' => 'Nom de la BDD', + 'db_user' => 'Nom d\'utilisateur de la BDD', + 'db_pass' => 'Mot de passe de la BDD', + 'admin_name' => 'Nom de l\'admin', + 'admin_pass' => 'Mot de passe admin', + 'admin_email' => 'Adresse email de l\'admin', + 'config_path' => 'Chemin vers le fichier de configuration', + 'install_phpci' => 'Installer PHPCI', + 'welcome_to_phpci' => 'Bienvenue sur PHPCI', + 'please_answer' => 'Merci de répondre aux questions suivantes :', + 'phpci_php_req' => 'PHPCI requiert au moins PHP 5.3.8 pour fonctionner.', + 'extension_required' => 'Extensions requises : %s', + 'function_required' => 'PHPCI doit être capable d\'appeler la fonction %s(). Est-ce qu\'elle est désactivée dans votre php.ini?', + 'requirements_not_met' => 'PHPCI ne peut pas être installé parce que toutes les conditions requises ne sont pas respectées. + Merci de corriger les erreurs ci-dessus avant de continuer.', + 'must_be_valid_email' => 'Doit être une adresse email valide.', + 'must_be_valid_url' => 'Doit être une URL valide.', + 'enter_name' => 'Nom de l\'admin: ', + 'enter_email' => 'Email de l\'admin: ', + 'enter_password' => 'Mot de passe de l\'admin: ', + 'enter_phpci_url' => 'Votre URL vers PHPCI (par exemple "http://phpci.local"): ', + + 'enter_db_host' => 'Merci d\'entrer le nom d\'hôte MySQL [localhost]: ', + 'enter_db_name' => 'Merci d\'entrer le nom de la base MySQL [phpci]: ', + 'enter_db_user' => 'Merci d\'entrer le nom d\'utilisateur MySQL [phpci]: ', + 'enter_db_pass' => 'Merci d\'entrer le mot de passe MySQL: ', + 'could_not_connect' => 'PHPCI ne peut pas se connecter à MySQL à partir des informations fournies. Veuillez réessayer..', + 'setting_up_db' => 'Paramétrage de la base de données... ', + 'user_created' => 'Le compte utilisateur a été créé !', + 'failed_to_create' => 'PHPCI n\'a pas réussi à créer votre compte admin.', + 'config_exists' => 'Le fichier de configuration PHPCI existe et n\'est pas vide.', + 'update_instead' => 'Si vous essayez de mettre à jour PHPCI, merci d\'utiliser la commande phpci:update.', + + // Update + 'update_phpci' => 'Mise à jour de la base de données pour refléter les modifications apportées aux modèles.', + 'updating_phpci' => 'Mise à jour de la base de données PHPCI : ', + 'not_installed' => 'PHPCI n\'a pas l\'air d\'être installé.', + 'install_instead' => 'Merci d\'installer PHPCI grâce à la commande phpci:install.', + + // Poll Command + 'poll_github' => 'Demander à GitHub de vérifier si nous devons démarrer un build.', + 'no_token' => 'Aucun token GitHub n\'a été trouvé', + 'finding_projects' => 'Recherche des projets à sonder', + 'found_n_projects' => '%d projets trouvés', + 'last_commit_is' => 'Le dernier commit sur GitHub pour %s est %s', + 'adding_new_build' => 'Le dernier commit est différent de celui présent en base de données, ajout d\'un nouveau build.', + 'finished_processing_builds' => 'Traitement des builds terminé.', + + // Create Admin + 'create_admin_user' => 'Créer un utilisateur admin', + 'incorrect_format' => 'Format incorrect', + + // Create Build Command + 'create_build_project' => 'Créer un build projet', + 'project_id_argument' => 'ID du projet', + 'commit_id_option' => 'ID du commit', + 'branch_name_option' => 'Branche', + + // Run Command + 'run_all_pending' => 'Démarrage de tout les builds PHPCI en attente.', + 'finding_builds' => 'Découverte des builds à traiter', + 'found_n_builds' => '%d builds trouvés', + 'skipping_build' => 'Saut du build %d - Un build sur le projet est déjà en cours.', + 'marked_as_failed' => 'Le build %d a été marqué échoué à cause d\'un timeout.', + + // Builder + 'missing_phpci_yml' => 'Ce projet ne contient pas de fichier phpci.yml, ou il est vide.', + 'build_success' => 'BUILD RÉUSSI', + 'build_failed' => 'BUILD ÉCHOUÉ', + 'removing_build' => 'Suppression du build.', + 'exception' => 'Exception: ', + 'could_not_create_working' => 'Impossible de créer un copie de travail.', + 'working_copy_created' => 'Copie de travail créée: %s', + 'looking_for_binary' => 'Recherche du binaire: %s', + 'found_in_path' => 'Trouver dans %s: %s', + 'running_plugin' => 'EXÉCUTION DU PLUGIN: %s', + 'plugin_success' => 'PLUGIN: RÉUSSI', + 'plugin_failed' => 'PLUGIN: ÉCHOUÉ', + 'plugin_missing' => 'Le plugins n\'existe pas: %s', + 'tap_version' => 'TapParser supporte seulement les TAP version 13', + 'tap_error' => 'Chaîne de TAP invalide, le nombre de tests ne correspond pas à celui spécifié.', + + // Build Plugins: + 'no_tests_performed' => 'Aucun test n\'a été exécuté.', + 'could_not_find' => 'Impossible de trouver %s', + 'no_campfire_settings' => 'Aucune information de connexion n\'a été fournie pour le plugin Campfire', + 'failed_to_wipe' => 'Impossible de supprimer le dossier %s avant de copier', + 'passing_build' => 'Passing Build', + 'failing_build' => 'Failing Build', + 'log_output' => 'Sortie de log : ', + 'n_emails_sent' => '%d emails envoyés.', + 'n_emails_failed' => '%d emails dont l\'envoi a échoué.', + 'unable_to_set_env' => 'Impossible d\'initialiser la variable d\'environnement', + 'tag_created' => 'Tag créé par PHPCI : %s', + 'x_built_at_x' => '%PROJECT_TITLE% construit à %BUILD_URI%', + 'hipchat_settings' => 'Merci de définir une "room" et un "authToken" pour le plugin hipchat_notify', + 'irc_settings' => 'Vous devez configurer un serveur, une "room" et un "nick".', + 'invalid_command' => 'Commande invalide', + 'import_file_key' => 'La déclaration d\'import doit contenir un \'fichier\' clé', + 'cannot_open_import' => 'Impossible d\'importer le ficher SQL : %s', + 'unable_to_execute' => 'Impossible d\'exécuter le ficher SQL', + 'phar_internal_error' => 'Erreur interne au plugin Phar', + 'build_file_missing' => 'Le fichier de build spécifié n\'existe pas.', + 'property_file_missing' => 'Le fichier de propriété spécifié n\'existe pas.', + 'could_not_process_report' => 'Impossible de traiter le rapport généré par cet outil.', + 'shell_not_enabled' => 'Le plugn shell n\'est pas activé. Merci de l\'activer via le fichier config.yml.' +); diff --git a/PHPCI/Languages/lang.it.php b/PHPCI/Languages/lang.it.php new file mode 100644 index 00000000..d254fbcd --- /dev/null +++ b/PHPCI/Languages/lang.it.php @@ -0,0 +1,401 @@ + 'Italiano', + 'language' => 'Lingua', + + // Log in: + 'log_in_to_phpci' => 'Accedi a PHPCI', + 'login_error' => 'Indirizzo email o password errati', + 'forgotten_password_link' => 'Hai dimenticato la tua password?', + 'reset_emailed' => 'Ti abbiamo inviato un link via email per ripristinare la tua password.', + 'reset_header' => 'Non preoccuparti!
E\' sufficiente inserire il tuo indirizzo email di seguito e ti invieremo una email con il link per il ripristino della tua password.', + 'reset_email_address' => 'Inserisci il tuo indirizzo email:', + 'reset_send_email' => 'Invia il link di reset della password', + 'reset_enter_password' => 'Per favore inserisci la nuova password', + 'reset_new_password' => 'Nuova password:', + 'reset_change_password' => 'Cambia password', + 'reset_no_user_exists' => 'Non esiste nessun utente con questo indirizzo email, per favore prova ancora.', + 'reset_email_body' => 'Ciao %s, + +hai ricevuto questa email perché tu, o qualcun\'altro, ha richiesto un reset della password per PHPCI. + +Se questa mail è tua, per favore apri il seguente link per ripristinare la tua password: %ssession/reset-password/%d/%s + +altrimenti, per favore, ignora questa email e nessuna azione verrà intrapresa. + +Grazie, + +PHPCI', + + 'reset_email_title' => 'Ripristino della password di PHPCI per %s', + 'reset_invalid' => 'Richeista di ripristino password non valida.', + 'email_address' => 'Indirizzo Email', + 'login' => 'Login / Email Address', + 'password' => 'Password', + 'log_in' => 'Accedi', + + // Top Nav + 'toggle_navigation' => 'Alterna navigazione', + 'n_builds_pending' => '%d build in attesa', + 'n_builds_running' => '%d build in corso', + 'edit_profile' => 'Modifica il Profilo', + 'sign_out' => 'Disconnettiti', + 'branch_x' => 'Branch: %s', + 'created_x' => 'Creato: %s', + 'started_x' => 'Avviato: %s', + + // Sidebar + 'hello_name' => 'Ciao, %s', + 'dashboard' => 'Dashboard', + 'admin_options' => 'Opzioni di amministrazione', + 'add_project' => 'Aggiungi un Progetto', + 'settings' => 'Impostazioni', + 'manage_users' => 'Gestisci Utenti', + 'plugins' => 'Plugins', + 'view' => 'Visualizzazione', + 'build_now' => 'Avvia una build ora', + 'edit_project' => 'Modifica il Progetto', + 'delete_project' => 'Cancella il Progetto', + + // Project Summary: + 'no_builds_yet' => 'Ancora nessuna build!', + 'x_of_x_failed' => '%d delle ultime %d build sono fallite.', + 'x_of_x_failed_short' => '%d / %d fallite.', + 'last_successful_build' => ' L\'ultima build è %s.', + 'never_built_successfully' => ' Questo progetto non ha nessuna build eseguita con successo.', + 'all_builds_passed' => 'Tutte le ultime %d build sono valide.', + 'all_builds_passed_short' => '%d / %d valide.', + 'last_failed_build' => ' L\'ultima build è %s.', + 'never_failed_build' => ' Questo progetto non ha nessuna build fallita.', + 'view_project' => 'Visualizza il Progetto', + + // Timeline: + 'latest_builds' => 'Ultime Build', + 'pending' => 'In attesa', + 'running' => 'In corso', + 'success' => 'Successo', + 'successful' => 'Con successo', + 'failed' => 'Fallita', + 'manual_build' => 'Build Manuale', + + // Add/Edit Project: + 'new_project' => 'Nuovo Progetto', + 'project_x_not_found' => 'Progetto con ID %d non esistente.', + 'project_details' => 'Dettagli del Progetto', + 'public_key_help' => 'Per rendere più facile la procedura, abbiamo generato una chiave SSH per te da + usare per questo progetto. Per usarla, aggiungi la chiave pubblica alle "deploy keys" + della piattaforma di gestione del codice che hai scelto.', + 'select_repository_type' => 'Seleziona il tipo di repository...', + 'github' => 'GitHub', + 'bitbucket' => 'Bitbucket', + 'gitlab' => 'GitLab', + 'remote' => 'URL Remoto', + 'local' => 'Percorso Locale', + 'hg' => 'Mercurial', + + 'where_hosted' => 'Dove è archiviato il tuo progetto?', + 'choose_github' => 'Scegli il repository di GitHub:', + + 'repo_name' => 'Nome del Repository / URL (Remoto) o Percorso (Locale)', + 'project_title' => 'Titolo del Progetto', + 'project_private_key' => 'Chiave provata da usare per accedere al repository + (lascia vuota per repository locali o remoti con accesso anonimo)', + 'build_config' => 'condigurazione della build di PHPCI per questo progetto + (se non puoi aggiungere il file phpci.yml nel repository di questo progetto)', + 'default_branch' => 'Nome del branch di default', + 'allow_public_status' => 'Vuoi rendere pubblica la pagina dello stato e l\'immagine per questo progetto?', + 'archived' => 'Archived', + 'archived_menu' => 'Archived', + 'save_project' => 'Salva il Progetto', + + 'error_mercurial' => 'L\'URL del repository Mercurial URL deve iniziare con http:// o https://', + 'error_remote' => 'L\'URL del repository deve iniziare con git://, http:// o https://', + 'error_gitlab' => 'Il nome del repository di GitLab deve essere nel seguente formato "utente@dominio.tld:proprietario/repository.git"', + 'error_github' => 'Il nome del repository deve essere nel formato "proprietario/repository"', + 'error_bitbucket' => 'Il nome del repository deve essere nel formato "proprietario/repository"', + 'error_path' => 'The path you specified does not exist.', + 'error_path' => 'Il percorso che hai indicato non esiste.', + + // View Project: + 'all_branches' => 'Tutti i Branche', + 'builds' => 'Builds', + 'id' => 'ID', + 'date' => 'Data', + 'project' => 'Progetto', + 'commit' => 'Commit', + 'branch' => 'Branch', + 'status' => 'Stato', + 'prev_link' => '« Precedente', + 'next_link' => 'Successivo »', + 'public_key' => 'Chiave pubblica', + 'delete_build' => 'Rimuovi build', + + 'webhooks' => 'Webhooks', + 'webhooks_help_github' => 'Per effettuare la build automatica di questo progetto quando vengono inseriti nuovi commit, + aggiungi l\'URL seguente come "Webhook" nella sezione + Webhooks and Services del tuo + repository su GitHub.', + + 'webhooks_help_gitlab' => 'Per effettuare la build automatica di questo progetto quando vengono inseriti nuovi commit, + aggiungi l\'URL seguente come "Webhook URL" nella sezione "WebHook URL" del tuo + repository GitLab.', + + 'webhooks_help_bitbucket' => 'Per effettuare la build automatica di questo progetto quando vengono inseriti nuovi + commit, aggiungi l\'URL seguente come serizio "POST" nella sezione + Services del tuo repository su + BITBUCKET.', + + // View Build + 'build_x_not_found' => 'La build con ID %d non esite.', + 'build_n' => 'Build %d', + 'rebuild_now' => 'Esegui nuovamente la build ora', + + + 'committed_by_x' => 'Inviato da %s', + 'commit_id_x' => 'Commit: %s', + + 'chart_display' => 'Questo grafico verrà mostrato una volta terminata la build.', + + 'build' => 'Build', + 'lines' => 'Linee', + 'comment_lines' => 'Linee di commenti', + 'noncomment_lines' => 'Linee che non sono commenti', + 'logical_lines' => 'Linee di logica', + 'lines_of_code' => 'Linee di codice', + 'build_log' => 'Log della build', + 'quality_trend' => 'Trend della qualità', + 'codeception_errors' => 'Errori di Codeception', + 'phpmd_warnings' => 'Avvisi di PHPMD', + 'phpcs_warnings' => 'Avvisi di PHPCS', + 'phpcs_errors' => 'Errori di PHPCS', + 'phplint_errors' => 'Errori di Lint', + 'phpunit_errors' => 'Errori di PHPUnit', + 'phpdoccheck_warnings' => 'Docblocks mancanti', + 'issues' => 'Segnalazioni', + + 'codeception' => 'Codeception', + 'phpcpd' => 'PHP Copy/Paste Detector', + 'phpcs' => 'PHP Code Sniffer', + 'phpdoccheck' => 'Docblocks mancanti', + 'phpmd' => 'PHP Mess Detector', + 'phpspec' => 'PHP Spec', + 'phpunit' => 'PHP Unit', + + 'file' => 'File', + 'line' => 'Lina', + 'class' => 'Classe', + 'method' => 'Metodo', + 'message' => 'Messaggio', + 'start' => 'Inizia', + 'end' => 'Finisci', + 'from' => 'Da', + 'to' => 'A', + 'result' => 'Risultati', + 'ok' => 'OK', + 'took_n_seconds' => 'Sono stati impiegati %d seconds', + 'build_created' => 'Build Creata', + 'build_started' => 'Build Avviata', + 'build_finished' => 'Build Terminata', + 'test_message' => 'Message', + 'test_no_message' => 'No message', + 'test_success' => 'Successful: %d', + 'test_fail' => 'Failures: %d', + 'test_skipped' => 'Skipped: %d', + 'test_error' => 'Errors: %d', + 'test_todo' => 'Todos: %d', + 'test_total' => '%d test(s)', + + // Users + 'name' => 'Nome', + 'password_change' => 'Password (lascia vuota se non vuoi modificarla)', + 'save' => 'Salva »', + 'update_your_details' => 'Aggiorna le tue informazioni', + 'your_details_updated' => 'Le tue informazioni sono state aggiornate.', + 'add_user' => 'Aggiung utent', + 'is_admin' => 'E\' amministratore?', + 'yes' => 'Si', + 'no' => 'No', + 'edit' => 'Modifica', + 'edit_user' => 'Modifica utente', + 'delete_user' => 'Cancella utente', + 'user_n_not_found' => 'L\'utente con ID %d non esiste.', + 'is_user_admin' => 'Questo utente è un amministratore?', + 'save_user' => 'Salva utente', + + // Settings: + 'settings_saved' => 'Le configurazioni sono state salvate.', + 'settings_check_perms' => 'Le configurazioni non possono essere salvate, controlla i permessi del filer config.yml.', + 'settings_cannot_write' => 'PHPCI non può scrivere il file config.yml, le configurazioni potrebbero non essere + salvate correttamente fintanto che il problema non verrà risolto.', + 'settings_github_linked' => 'Il tuo account GitHub è stato collegato.', + 'settings_github_not_linked' => 'Il tuo account GitHub non può essere collegato.', + 'build_settings' => 'Configurazioni della build', + 'github_application' => 'Applicazione GitHub', + 'github_sign_in' => 'Prima di poter iniziare ad usare GitHub, è necessario collegarsi e garantire + a PHPCI l\'accesso al tuo account.', + 'github_phpci_linked' => 'PHPCI è stato collegato correttamente al tuo account GitHub.', + 'github_where_to_find' => 'Dove trovare queste...', + 'github_where_help' => 'Se sei il proprietario dell\'applicazione, puoi trovare queste informazioni nell\'area delle + configurazioni dell\'applicazione.', + + 'email_settings' => 'Impostazioni Email', + 'email_settings_help' => 'Prima che possa inviare le email con lo status PHPCI, devi configurare l\'SMTP qui sotto.', + + 'application_id' => 'ID dell\'Applicazione', + 'application_secret' => 'Secret dell\'Applicazione', + + 'smtp_server' => 'Server SMTP', + 'smtp_port' => 'Porta SMTP', + 'smtp_username' => 'Username SMTP', + 'smtp_password' => 'Password SMTP', + 'from_email_address' => 'Indirizzio Email del mittente', + 'default_notification_address' => 'Indirizzo email delle notifiche predefinito', + 'use_smtp_encryption' => 'Utilizzare l\'Encrypting per SMTP?', + 'none' => 'No', + 'ssl' => 'SSL', + 'tls' => 'TLS', + + 'failed_after' => 'Considera la build fallita dopo', + '5_mins' => '5 Minuti', + '15_mins' => '15 Minuti', + '30_mins' => '30 Minuti', + '1_hour' => '1 Ora', + '3_hours' => '3 Ore', + + // Plugins + 'cannot_update_composer' => 'PHPCI non può aggiornare composer.json per te non essendo scrivibile.', + 'x_has_been_removed' => '%s è stato rimosso.', + 'x_has_been_added' => '%s è stato aggiunto al file composer.json per te, verrà installato la prossima volta che eseguirai + composer update.', + 'enabled_plugins' => 'Plugins attivati', + 'provided_by_package' => 'Fornito dal pacchetto', + 'installed_packages' => 'Pacchetti installati', + 'suggested_packages' => 'Paccehtti suggeriti', + 'title' => 'Titolo', + 'description' => 'Descrizione', + 'version' => 'Versione', + 'install' => 'Installa »', + 'remove' => 'Rimuovi »', + 'search_packagist_for_more' => 'Cerca altri pacchetti su Packagist', + 'search' => 'Cerca »', + + // Installer + 'installation_url' => 'URL di installazione di PHPCI', + 'db_host' => 'Host del Database', + 'db_name' => 'Nome del Database', + 'db_user' => 'Username del Database', + 'db_pass' => 'Password del Database', + 'admin_name' => 'Nome dell\'amministratore', + 'admin_pass' => 'Password dell\'amministratore', + 'admin_email' => 'Email dell\'amministratore', + 'config_path' => 'Percorso del file di configurazione', + 'install_phpci' => 'Installa PHPCI', + 'welcome_to_phpci' => 'Benvenuto in PHPCI', + 'please_answer' => 'Per favore rispondi alle seguenti domande:', + 'phpci_php_req' => 'PHPCI richiede come minimo PHP 5.3.8 per funzionare.', + 'extension_required' => 'Le estensioni richieste sono: %s', + 'function_required' => 'PHPCI richiede di poter chiamare la funzione %s(). Questa funzionalità è disabibiltata nel + php.ini?', + 'requirements_not_met' => 'PHPCI non può essere installato, non tutti i requisiti sono soddisfatti. + Per favore controlla gli errori riportati prima di proseguire.', + 'must_be_valid_email' => 'Deve essere un indirizzo email valido.', + 'must_be_valid_url' => 'Deve essere un URL valido.', + 'enter_name' => 'Nome dell\'amministratore: ', + 'enter_email' => 'Email dell\'amministratore: ', + 'enter_password' => 'Password dell\'amministratore: ', + 'enter_phpci_url' => 'L\'URL di PHPCI ("http://phpci.locale" ad esempio): ', + + 'enter_db_host' => 'Per favore inserisci l\'host MySQL [localhost]: ', + 'enter_db_name' => 'Per favore inserisci il nome MySQL [phpci]: ', + 'enter_db_user' => 'Per favore inserisci l\'username MySQL [phpci]: ', + 'enter_db_pass' => 'Per favore inserisci la password MySQL: ', + 'could_not_connect' => 'PHPCI non può connettersi a MySQL con le informazioni fornite. Per favore prova ancora.', + 'setting_up_db' => 'Configurzione del tuo database... ', + 'user_created' => 'Account utente creato!', + 'failed_to_create' => 'PHPCI non è riuscito a creare il tuo account amministrativo.', + 'config_exists' => 'Il file di configurazione di PHPCI esiste e non è vuoto.', + 'update_instead' => 'Se stai cercando di aggiornare PHPCI, per favore usa phpci:update.', + + // Update + 'update_phpci' => 'Aggiorna il database per riflettere le modifiche ai model.', + 'updating_phpci' => 'Aggiornamenti del database di PHPCI: ', + 'not_installed' => 'PHPCI sembra non essere installato.', + 'install_instead' => 'Per favore installa PHPCI tramite phpci:install.', + + // Poll Command + 'poll_github' => 'Richiesta a GitHub per verificare se è necessario avviare una build.', + 'no_token' => 'Nessuno token per GitHub trovato', + 'finding_projects' => 'Ricerca dei progetti da aggiornare', + 'found_n_projects' => 'Trovati %d progetti', + 'last_commit_is' => 'Ultimo commit su GitHub per %s è %s', + 'adding_new_build' => 'L\'ultimo commit è diverso da quello registrato, new build aggiunta.', + 'finished_processing_builds' => 'Terminato di processare le build.', + + // Create Admin + 'create_admin_user' => 'Crea un nuovo utente amministrarore', + 'incorrect_format' => 'Formato errato', + + // Create Build Command + 'create_build_project' => 'Create a build for a project', + 'project_id_argument' => 'A project ID', + 'commit_id_option' => 'Commit ID to build', + 'branch_name_option' => 'Branch to build', + + // Run Command + 'run_all_pending' => 'Esegui tutte le build in attesa su PHPCI.', + 'finding_builds' => 'Ricerca delel build da processare', + 'found_n_builds' => 'Trovate %d build', + 'skipping_build' => 'Saltata la build %d - La build del progetto è già in corso.', + 'marked_as_failed' => 'Build %d è stata contrassegnata come fallita per un timeout.', + + // Builder + 'missing_phpci_yml' => 'Questo progetto non contiene il file phpci.yml, o il file è vuoto.', + 'build_success' => 'BUILD PASSATA', + 'build_failed' => 'BUILD FALLITA', + 'removing_build' => 'Rimozione build.', + 'exception' => 'Eccezione: ', + 'could_not_create_working' => 'Non può essere creata una copia di lavoro.', + 'working_copy_created' => 'Copia di lavoro creata: %s', + 'looking_for_binary' => 'Ricerca per il binario: %s', + 'found_in_path' => 'Trovato in %s: %s', + 'running_plugin' => 'PLUGIN IN ESECUZIONE: %s', + 'plugin_success' => 'PLUGIN: PASSATO', + 'plugin_failed' => 'PLUGIN: FALLITO', + 'plugin_missing' => 'Plugin non esistente: %s', + 'tap_version' => 'TapParser supporta solo la versione TAP 13', + 'tap_error' => 'Stringa TAP non valida, il numero dei test non corrisponde con il numero di test contati.', + + // Build Plugins: + 'no_tests_performed' => 'Nessun test è stato eseguito.', + 'could_not_find' => 'Non posso trovare %s', + 'no_campfire_settings' => 'Nessun parametro di connessione trovato per il plugin Campfire.', + 'failed_to_wipe' => 'Errore nel pulire la cartella %s prima di effettuare la copia', + 'passing_build' => 'Build passata', + 'failing_build' => 'Build fallita', + 'log_output' => 'Log: ', + 'n_emails_sent' => '%d email inviate.', + 'n_emails_failed' => '%d email da inviare fallite.', + 'unable_to_set_env' => 'Errore nel settare la variabile di ambiente', + 'tag_created' => 'Tag creato da PHPCI: %s', + 'x_built_at_x' => '%PROJECT_TITLE% buildato in %BUILD_URI%', + 'hipchat_settings' => 'Per favore definire la stanza e authToken per il plugin hipchat_notify', + 'irc_settings' => 'Devi configurare server, stanza e nick.', + 'invalid_command' => 'Comando non valido', + 'import_file_key' => 'L\'import deve contenrere la chiave \'file\'', + 'cannot_open_import' => 'Impossobile aprire il file SQL da importare: %s', + 'unable_to_execute' => 'Impossibile eseguire il file SQL', + 'phar_internal_error' => 'Errore interno del plugin Phar', + 'build_file_missing' => 'Il file di build specificato non esiste.', + 'property_file_missing' => 'Il file di proprietà specificato non esiste.', + 'could_not_process_report' => 'Non è possibile processare il report generato da questo tool.', + 'shell_not_enabled' => 'Il plugin shell non è attivato. Per favore attivalo tramite config.yml.' +); diff --git a/PHPCI/Languages/lang.nl.php b/PHPCI/Languages/lang.nl.php new file mode 100644 index 00000000..96d9f5d5 --- /dev/null +++ b/PHPCI/Languages/lang.nl.php @@ -0,0 +1,399 @@ + 'Nederlands', + 'language' => 'Taal', + + // Log in: + 'log_in_to_phpci' => 'Log in op PHPCI', + 'login_error' => 'Incorrect e-mailadres of wachtwoord', + 'forgotten_password_link' => 'Wachtwoord vergeten?', + 'reset_emailed' => 'We hebben je een link gemaild om je wachtwoord opnieuw in te stellen.', + 'reset_header' => 'Geen zorgen!
Vul hieronder gewoon je e-mailadres in en we sturen +je een link on je wachtwoord te resetten.', + 'reset_email_address' => 'Vul je e-mailadres in:', + 'reset_send_email' => 'Verstuur wachtwoord reset', + 'reset_enter_password' => 'Gelieve een nieuw wachtwoord in te voeren', + 'reset_new_password' => 'Nieuw wachtwoord:', + 'reset_change_password' => 'Wijzig wachtwoord', + 'reset_no_user_exists' => 'Er bestaat geen gebruiker met dit e-mailadres, gelieve opnieuw te proberen.', + 'reset_email_body' => 'Hallo %s, + +Je ontvangt deze email omdat jij, of iemand anders, je wachtwoord voor PHPCI opnieuw wenst in te stellen. + +Indien jij dit was, klik op deze link op je wachtwoord opnieuw in te stellen: %ssession/reset-password/%d/%s + +Zoniet, negeer deze e-mail en er zal geen verdere actie ondernomen worden. + +Bedankt, + +PHPCI', + + 'reset_email_title' => 'PHPCI wachtwoord reset voor %s', + 'reset_invalid' => 'Ongeldig wachtwoord reset verzoek', + 'email_address' => 'E-mailadres', + 'login' => 'Login / Email Address', + 'password' => 'Wachtwoord', + 'log_in' => 'Log in', + + + // Top Nav + 'toggle_navigation' => 'Wissel Navigatie', + 'n_builds_pending' => '%d builds wachtend', + 'n_builds_running' => '%d builds lopende', + 'edit_profile' => 'Wijzig profiel', + 'sign_out' => 'Uitloggen', + 'branch_x' => 'Branch: %s', + 'created_x' => 'Aangemaakt: %s', + 'started_x' => 'Gestart: %s', + + // Sidebar + 'hello_name' => 'Hallo, %s', + 'dashboard' => 'Startpagina', + 'admin_options' => 'Administratie opties', + 'add_project' => 'Project toevoegen', + 'settings' => 'Instellingen', + 'manage_users' => 'Gebruikers beheren', + 'plugins' => 'Plugins', + 'view' => 'Bekijk', + 'build_now' => 'Build nu', + 'edit_project' => 'Wijzig project', + 'delete_project' => 'Verwijder project', + + // Project Summary: + 'no_builds_yet' => 'Nog geen builds!', + 'x_of_x_failed' => '%d van de laatste %d builds faalden.', + 'x_of_x_failed_short' => '%d / %d faalden.', + 'last_successful_build' => 'De laatste succesvolle build was %s.', + 'never_built_successfully' => 'Dit project heeft geen succesvolle build gehad.', + 'all_builds_passed' => 'Elk van de laatste %d builds slaagden.', + 'all_builds_passed_short' => '%d / %d slaagden.', + 'last_failed_build' => 'De laatste gefaalde build was %s.', + 'never_failed_build' => 'Dit project heeft geen gefaalde build gehad.', + 'view_project' => 'Bekijk project', + + // Timeline: + 'latest_builds' => 'Laatste builds', + 'pending' => 'In afwachting', + 'running' => 'Lopende', + 'success' => 'Succes', + 'successful' => 'Succesvol', + 'failed' => 'Gefaald', + 'manual_build' => 'Manuele build', + + // Add/Edit Project: + 'new_project' => 'Nieuw project', + 'project_x_not_found' => 'Project met ID %d bestaat niet.', + 'project_details' => 'Project details', + 'public_key_help' => 'Om eenvoudiger te kunnen starten, hebben we een SSH sleutelpaar gegenereerd +voor dit project. Om het te gebruiken, voeg onderstaande public key toe aan de "deploy keys" sectie +van je gekozen source code hosting platform', + 'select_repository_type' => 'Selecteer repository type...', + 'github' => 'GitHub', + 'bitbucket' => 'Bitbucket', + 'gitlab' => 'GitLab', + 'remote' => 'Externe URL', + 'local' => 'Lokaal pad', + 'hg' => 'Mercurial', + + 'where_hosted' => 'Waar wordt je project gehost?', + 'choose_github' => 'Selecteer een GitHub repository:', + + 'repo_name' => 'Repository naam / URL (extern) of pad (lokaal)', + 'project_title' => 'Projecttitel', + 'project_private_key' => 'Private key voor toegang tot repository +(laat leeg voor lokaal en/of anonieme externen)', + 'build_config' => 'PHPCI build configuratie voor dit project +(indien je geen phpci.yml bestand aan de project repository kan toevoegen)', + 'default_branch' => 'Standaard branch naam', + 'allow_public_status' => 'Publieke statuspagina en afbeelding beschikbaar maken voor dit project?', + 'archived' => 'Archived', + 'archived_menu' => 'Archived', + 'save_project' => 'Project opslaan', + + 'error_mercurial' => 'Mercurial repository URL dient te starten met http:// of https://', + 'error_remote' => 'Repository URL dient te starten met git://, http:// of https://', + 'error_gitlab' => 'GitLab repository naam dient in het formaat "gebruiker@domain.tld/eigenaar/repo.git" te zijn', + 'error_github' => 'Repository naam dient in het formaat "eigenaar/repo" te zijn', + 'error_bitbucket' => 'Repository naam dient in het formaat "eigenaar/repo" te zijn', + 'error_path' => 'Het opgegeven pad bestaat niet.', + + // View Project: + 'all_branches' => 'Alle brances', + 'builds' => 'Builds', + 'id' => 'ID', + 'date' => 'Datum', + 'project' => 'Project', + 'commit' => 'Commit', + 'branch' => 'Branch', + 'status' => 'Status', + 'prev_link' => '« Vorig', + 'next_link' => 'Volgend »', + 'public_key' => 'Public Key', + 'delete_build' => 'Verwijder build', + + 'webhooks' => 'Webhooks', + 'webhooks_help_github' => 'Voor automatische builds wanneer nieuwe commits worden gepusht, dient onderstaande URL +als nieuwe "Webhook" in de Webhooks +and Services sectie van je GitHub repository toegevoegd worden.', + + 'webhooks_help_gitlab' => 'Voor automatische builds wanneer nieuwe commits worden gepusht, dient onderstaande URL +als nieuwe "Webhook URL" in de Web Hooks sectie van je GitLab repository toegevoegd worden.', + + 'webhooks_help_bitbucket' => 'Voor automatische builds wanneer nieuwe commits worden gepusht, dient onderstaande URL +als "POST" service in de in de + +Services sectie van je Bitbucket repository toegevoegd worden.', + + // View Build + 'build_x_not_found' => 'Build met ID %d bestaat niet.', + 'build_n' => 'Build %d', + 'rebuild_now' => 'Rebuild nu', + + + 'committed_by_x' => 'Committed door %s', + 'commit_id_x' => 'Commit: %s', + + 'chart_display' => 'Deze grafiek wordt getoond zodra de build compleet is.', + + 'build' => 'Build', + 'lines' => 'Lijnen', + 'comment_lines' => 'Commentaarlijnen', + 'noncomment_lines' => 'Niet-commentaarlijnen', + 'logical_lines' => 'Logische lijnen', + 'lines_of_code' => 'Lijnen code', + 'build_log' => 'Build Log', + 'quality_trend' => 'Kwaliteitstrend', + 'codeception_errors' => 'Codeception Fouten', + 'phpmd_warnings' => 'PHPMD Waarschuwingen', + 'phpcs_warnings' => 'PHPCS Waarschuwingen', + 'phpcs_errors' => 'PHPCS Fouten', + 'phplint_errors' => 'Lint Fouten', + 'phpunit_errors' => 'PHPUnit Fouten', + 'phpdoccheck_warnings' => 'Ontbrekende Docblocks', + 'issues' => 'Problemen', + + 'codeception' => 'Codeception', + 'phpcpd' => 'PHP Copy/Paste Detector', + 'phpcs' => 'PHP Code Sniffer', + 'phpdoccheck' => 'Ontbrekende Docblocks', + 'phpmd' => 'PHP Mess Detector', + 'phpspec' => 'PHP Spec', + 'phpunit' => 'PHPUnit', + + 'file' => 'Bestand', + 'line' => 'Lijn', + 'class' => 'Class', + 'method' => 'Method', + 'message' => 'Boodschap', + 'start' => 'Start', + 'end' => 'Einde', + 'from' => 'Van', + 'to' => 'Tot', + 'result' => 'Resultaat', + 'ok' => 'OK', + 'took_n_seconds' => 'Duurde %d seconden', + 'build_created' => 'Build aangemaakt', + 'build_started' => 'Build gestart', + 'build_finished' => 'Build beëindigd', + 'test_message' => 'Message', + 'test_no_message' => 'No message', + 'test_success' => 'Successful: %d', + 'test_fail' => 'Failures: %d', + 'test_skipped' => 'Skipped: %d', + 'test_error' => 'Errors: %d', + 'test_todo' => 'Todos: %d', + 'test_total' => '%d test(s)', + + // Users + 'name' => 'Naam', + 'password_change' => 'Wachtwoord (laat leeg indien je niet wenst te veranderen)', + 'save' => 'Opslaan »', + 'update_your_details' => 'Wijzig je gegevens', + 'your_details_updated' => 'Je gegevens werden gewijzigd', + 'add_user' => 'Gebruiker toevoegen', + 'is_admin' => 'Is administrator?', + 'yes' => 'Ja', + 'no' => 'Nee', + 'edit' => 'Wijzig', + 'edit_user' => 'Gebruiker wijzigen', + 'delete_user' => 'Gebruiker wissen', + 'user_n_not_found' => 'Gebruiker met ID %d bestaat niet.', + 'is_user_admin' => 'Is deze gebruiker administrator?', + 'save_user' => 'Gebruiker opslaan', + + // Settings: + 'settings_saved' => 'Je instellingen werden opgeslagen.', + 'settings_check_perms' => 'Je instellingen konden niet worden opgeslagen, controleer de permissies van je config.yml bestand.', + 'settings_cannot_write' => 'PHPCI kan niet schrijven naar je config.yml bestand, instellingen worden mogelijks +niet goed opgeslagen tot dit opgelost is.', + 'settings_github_linked' => 'Je GitHub account werd gelinkt.', + 'settings_github_not_linked' => 'Je GitHub account kon niet gelinkt worden.', + 'build_settings' => 'Build instellingen', + 'github_application' => 'GitHub toepassing', + 'github_sign_in' => 'Vooraleer je GitHub kan gebruiken, dien je in te loggen en +PHPCI toegang te verlenen tot je account.', + 'github_phpci_linked' => 'PHP werd succesvol gelinkt aan je GitHub account.', + 'github_where_to_find' => 'Waar zijn deze te vinden...', + 'github_where_help' => 'Indien je eigenaar bent van de toepassing die je wens te gebruiken, kan je deze informatie +in je applications instellingen pagina vinden.', + + 'email_settings' => 'E-mail instellingen', + 'email_settings_help' => 'Vooraleer PHPCI je build status e-mails kan sturen, +dien je eerst je SMTP instellingen te configureren.', + + 'application_id' => 'Toepassings ID', + 'application_secret' => 'Toepassings geheime code', + + 'smtp_server' => 'SMTP Server', + 'smtp_port' => 'SMTP Poort', + 'smtp_username' => 'SMTP Gebruikersnaam', + 'smtp_password' => 'SMTP Wachtwoord', + 'from_email_address' => 'Van e-mailadres', + 'default_notification_address' => 'Standaard melding e-mailadres', + 'use_smtp_encryption' => 'SMTP Encryptie gebruiken?', + 'none' => 'Geen', + 'ssl' => 'SSL', + 'tls' => 'TLS', + + 'failed_after' => 'Beschouw een build gefaald na', + '5_mins' => '5 minuten', + '15_mins' => '15 minuten', + '30_mins' => '30 minuten', + '1_hour' => '1 uur', + '3_hours' => '3 uur', + + // Plugins + 'cannot_update_composer' => 'PHPCI kan composer.json niet aanpassen gezien het niet schrijfbaar is.', + 'x_has_been_removed' => '%s werd verwijderd.', + 'x_has_been_added' => '%s werd toegevoegd aan composer.json en zal geïnstalleerd worden de volgende +keer je composer update uitvoert.', + 'enabled_plugins' => 'Ingeschakelde plugins', + 'provided_by_package' => 'Voorzien door package', + 'installed_packages' => 'Geinstalleerde packages', + 'suggested_packages' => 'Voorgestelde packages', + 'title' => 'Titel', + 'description' => 'Beschrijving', + 'version' => 'Versie', + 'install' => 'Installeer »', + 'remove' => 'Verwijder »', + 'search_packagist_for_more' => 'Doorzoek Packagist naar meer packages', + 'search' => 'Zoek »', + + // Installer + 'installation_url' => 'PHPCI installatie URL', + 'db_host' => 'Database host', + 'db_name' => 'Database naam', + 'db_user' => 'Database gebruikersnaam', + 'db_pass' => 'Database wachtwoord', + 'admin_name' => 'Administrator naam', + 'admin_pass' => 'Administrator wachtwoord', + 'admin_email' => 'Administrator e-mailadres', + 'config_path' => 'Pad naar configuratiebestand', + 'install_phpci' => 'Installeer PHPCI', + 'welcome_to_phpci' => 'Welkom bij PHPCI', + 'please_answer' => 'Gelieve onderstaande vragen te beantwoorden:', + 'phpci_php_req' => 'PHPCI heeft ten minste PHP 5.3.8 nodig om te werken.', + 'extension_required' => 'Extensie benodigd: %s', + 'function_required' => 'PHPCI moet functie %s() kunnen aanroepen. Is deze uitgeschakeld in php.ini?', + 'requirements_not_met' => 'PHPCI kan niet worden geïnstalleerd omdat niet aan alle vereisten is voldaan. +Gelieve de fouten na te kijken vooraleer verder te gaan.', + 'must_be_valid_email' => 'Moet een geldig e-mailadres zijn.', + 'must_be_valid_url' => 'Moet een geldige URL zijn.', + 'enter_name' => 'Administrator naam: ', + 'enter_email' => 'Administrator e-mailadres: ', + 'enter_password' => 'Administrator wachtwoord: ', + 'enter_phpci_url' => 'Je PHPCI URL (bijvoorbeeld "http://phpci.local"): ', + + 'enter_db_host' => 'Vul je MySQL host in [localhost]: ', + 'enter_db_name' => 'Vul je MySQL databasenaam in [phpci]: ', + 'enter_db_user' => 'Vul je MySQL gebruikersnaam in [phpci]: ', + 'enter_db_pass' => 'Vul je MySQL watchtwoord in: ', + 'could_not_connect' => 'PHPCI kon met deze gegevens geen verbinding maken met MySQL. Gelieve opnieuw te proberen.', + 'setting_up_db' => 'Database wordt aangemaakt...', + 'user_created' => 'Gebruikersprofiel aangemaakt!', + 'failed_to_create' => 'PHPCI kon je administratorprofiel niet aanmaken.', + 'config_exists' => 'Het PHPCI configuratiebestand bestaat en is niet leeg.', + 'update_instead' => 'Liever phpci:update te gebruiken indien je PHPCI probeerde te updaten, ', + + // Update + 'update_phpci' => 'Update de database naar het beeld van gewijzigde modellen.', + 'updating_phpci' => 'PHPCI database wordt geüpdatet:', + 'not_installed' => 'PHPCI lijkt niet geïnstalleerd te zijn.', + 'install_instead' => 'Gelieve PHPCI via phpci:install te installeren.', + + // Poll Command + 'poll_github' => 'Poll GitHub om te controleren of we een build moeten starten.', + 'no_token' => 'Geen GitHub token gevonden', + 'finding_projects' => 'Vind projecten om te pollen', + 'found_n_projects' => '%d projecten gevonden', + 'last_commit_is' => 'Laatste commit naar GitHub voor %s is %s', + 'adding_new_build' => 'Laatste commit verschilt van database, nieuwe build wordt toegevoegd', + 'finished_processing_builds' => 'Verwerking builds voltooid.', + + // Create Admin + 'create_admin_user' => 'Administrator-gebruiker aanmaken', + 'incorrect_format' => 'Incorrect formaat', + + // Create Build Command + 'create_build_project' => 'Create a build for a project', + 'project_id_argument' => 'A project ID', + 'commit_id_option' => 'Commit ID to build', + 'branch_name_option' => 'Branch to build', + + // Run Command + 'run_all_pending' => 'Voer alle wachtende PHPCI builds uit.', + 'finding_builds' => 'Zoekt builds om te verwerken', + 'found_n_builds' => '%d builds gevonden', + 'skipping_build' => 'Build %d overslaan - Project build reeds aan de gang.', + 'marked_as_failed' => 'Build %d gemarkeerd als falende door timeout.', + + // Builder + 'missing_phpci_yml' => 'Dit project bevat geen phpci.yml bestand, of het is leeg.', + 'build_success' => 'BUILD SUCCES', + 'build_failed' => 'BUILD GEFAALD', + 'removing_build' => 'Build wordt verwijderd.', + 'exception' => 'Uitzondering:', + 'could_not_create_working' => 'Kon geen werkende kopie maken.', + 'working_copy_created' => 'Werkende kopie aangemaakt: %s', + 'looking_for_binary' => 'Zoekend naar binary: %s', + 'found_in_path' => 'Gevonden in %s: %s', + 'running_plugin' => 'UITVOEREN PLUGIN: %s', + 'plugin_success' => 'PLUGIN: SUCCES', + 'plugin_failed' => 'PLUGIN: GEFAALD', + 'plugin_missing' => 'Plugin bestaat niet: %s', + 'tap_version' => 'TapParser ondersteunt enkel TAP versie 13', + 'tap_error' => 'Ongeldige TAP string, aantal tests niet in overeenstemming met opgegeven aantal.', + + // Build Plugins: + 'no_tests_performed' => 'Er werden geen tests uitgevoerd.', + 'could_not_find' => 'Kon %s niet vinden', + 'no_campfire_settings' => 'Geen verbindingsparameters opgegeven voor Campfire plugin', + 'failed_to_wipe' => 'Kon bestaande map %s niet wissen voor kopie', + 'passing_build' => 'Slagende build', + 'failing_build' => 'Falende build', + 'log_output' => 'Log output:', + 'n_emails_sent' => '%d e-mails versuurd.', + 'n_emails_failed' => '%d e-mails faalden te versturen.', + 'unable_to_set_env' => 'Niet geslaagd om environment variable in te stellen', + 'tag_created' => 'Tag aangemaakt door PHPCI: %s', + 'x_built_at_x' => '%PROJECT_TITLE% built op %BUILD_URI%', + 'hipchat_settings' => 'Gelieve kamer & authToken voor hipchat_notify plugin te definiëren', + 'irc_settings' => 'Je dient server, kamer & nick op te geven.', + 'invalid_command' => 'Ongeldig commando', + 'import_file_key' => 'Import statement moet \'file\' key bevatten', + 'cannot_open_import' => 'Het is niet mogelijk om het SQL bestand %s te openen', + 'unable_to_execute' => 'Het is niet mogelijk om het SQL bestand uit te voeren', + 'phar_internal_error' => 'Er is iets fout gegaan in de Phar Plugin', + 'build_file_missing' => 'Opgegeven build bestand bestaat niet.', + 'property_file_missing' => 'Opgegeven bestand bestaat niet', + 'could_not_process_report' => 'Het is niet mogelijk om het gegenereerde rapport van deze tool te verwerken.', + 'shell_not_enabled' => 'De shell plugin is niet ingeschakeld, schakel deze a.u.b. in via het config.yml bestand.' +); diff --git a/PHPCI/Languages/lang.pl.php b/PHPCI/Languages/lang.pl.php new file mode 100644 index 00000000..e1d3c3bf --- /dev/null +++ b/PHPCI/Languages/lang.pl.php @@ -0,0 +1,400 @@ + 'Polski', + 'language' => 'Język', + + // Log in: + 'log_in_to_phpci' => 'Zaloguj się do PHPCI', + 'login_error' => 'Nieprawidłowy email lub hasło', + 'forgotten_password_link' => 'Zapomniałeś hasła?', + 'reset_emailed' => 'Email z linkiem resetującym hasło został wysłany.', + 'reset_header' => 'Spokojnie!
Wpisz swój adres email w polu poniżej a my wyślemy Ci link +resetujący hasło.', + 'reset_email_address' => 'Podaj swój adres email:', + 'reset_send_email' => 'Wyślij reset hasła emailem', + 'reset_enter_password' => 'Wpisz nowe hasło', + 'reset_new_password' => 'Nowe hasło:', + 'reset_change_password' => 'Zmień hasło', + 'reset_no_user_exists' => 'Użytkownik o takim emailu nie istnieje. Spróbuj jeszcze raz.', + 'reset_email_body' => 'Witaj %s, + +Otrzymałeś ten email ponieważ Ty, lub ktoś inny, wysłał prośbę o zmianę hasła w PHPCI. + +Jeśli to faktycznie Ty, kliknij w następujący link aby zresetować hasło: %ssession/reset-password/%d/%s + +Jeśli nie, zignoruj tego emaila i wszystko pozostanie bez zmian, + +Pozdrawiamy, + +PHPCI', + + 'reset_email_title' => 'Reset Hasła PHPCI dla %s', + 'reset_invalid' => 'Prośba o zmianę hasła jest nieważna.', + 'email_address' => 'Adres email', + 'login' => 'Login / Email Address', + 'password' => 'Hasło', + 'log_in' => 'Zaloguj się', + + + // Top Nav + 'toggle_navigation' => 'Otwórz/zamknij nawigację', + 'n_builds_pending' => '%d budowań w kolejce', + 'n_builds_running' => '%d budowań w toku', + 'edit_profile' => 'Edytuj Profil', + 'sign_out' => 'Wyloguj się', + 'branch_x' => 'Gałąź: %s', + 'created_x' => 'Utworzono: %s', + 'started_x' => 'Rozpoczęto: %s', + + // Sidebar + 'hello_name' => 'Witaj, %s', + 'dashboard' => 'Panel administracyjny', + 'admin_options' => 'Opcje Administratora', + 'add_project' => 'Dodaj Projekt', + 'settings' => 'Ustawienia', + 'manage_users' => 'Zarządaj Uzytkownikami', + 'plugins' => 'Pluginy', + 'view' => 'Podgląd', + 'build_now' => 'Zbuduj', + 'edit_project' => 'Edytuj Projekt', + 'delete_project' => 'Usuń Projekt', + + // Project Summary: + 'no_builds_yet' => 'Brak budowań!', + 'x_of_x_failed' => '%d z ostatnich %d budowań nie powiodło się', + 'x_of_x_failed_short' => '%d / %d nie powiodło się', + 'last_successful_build' => 'Ostatnie budowanie zakończone sukesem odbyło się %s', + 'never_built_successfully' => 'Projekt nie został zbudowany z powodzeniem.', + 'all_builds_passed' => 'Wszystkie z ostatnich %d budowań przeszły.', + 'all_builds_passed_short' => '%d / %d przeszło.', + 'last_failed_build' => 'Ostatnie budowanie zakończone niepowodzeniam było %s.', + 'never_failed_build' => 'Ten projekt nigdy nie zakończył się niepowodzeniem budowania', + 'view_project' => 'Podgląd Projektu', + + // Timeline: + 'latest_builds' => 'Ostatnie Budowania', + 'pending' => 'Oczekujące', + 'running' => 'W toku', + 'success' => 'Sukces', + 'successful' => 'Zakończone sukcesem', + 'failed' => 'Nieudane', + 'manual_build' => 'Budowanie Manualne', + + // Add/Edit Project: + 'new_project' => 'Nowy Projekt', + 'project_x_not_found' => 'Projekt o ID %d nie istnieje.', + 'project_details' => 'Szczegóły Projektu', + 'public_key_help' => 'Aby łatwiej zacząć, wygenerowaliśmy parę kluczy SSH, które możesz użyć +do tego projektu. Żeby je użyć, wystarczy dodać następujący klucz publiczny do sekcji "wdrożyć klucze" +od wybranego kodu źródłowego platformy hostingowej.', + 'select_repository_type' => 'Wybierz typ repozytorium...', + 'github' => 'GitHub', + 'bitbucket' => 'Bitbucket', + 'gitlab' => 'GitLab', + 'remote' => 'Zdalny URL ', + 'local' => 'Lokalna Ścieżka ', + 'hg' => 'Mercurial', + 'svn' => 'Subversion', + + 'where_hosted' => 'Gdzie hostowany jest Twój projekt?', + 'choose_github' => 'Wybierz repozytorium GitHub:', + + 'repo_name' => 'Nazwa repozytorium / URL (Zdalne) lub Ścieżka (Lokalne)', + 'project_title' => 'Tytuł Projektu', + 'project_private_key' => 'Prywanty klucz dostępu do repozytoriów +(pozostaw puste pole dla zdalnych lokalnych i/lub anonimowych)', + 'build_config' => 'PHPCI zbudowało config dla tego projektu +(jeśli nie możesz dodać pliku phpci.yml do repozytorium projektu)', + 'default_branch' => 'Domyślna nazwa gałęzi', + 'allow_public_status' => 'Włączyć status publiczny dla tego projektu?', + 'archived' => 'W archiwum', + 'archived_menu' => 'W archiwum', + 'save_project' => 'Zachowaj Projekt', + + 'error_mercurial' => 'URL repozytorium Mercurialnego powinno zaczynać się od http:// and https://', + 'error_remote' => 'URL repozytorium powinno zaczynać się od git://, http:// lub https://', + 'error_gitlab' => 'Nazwa Repozytorium GitLab powinna być w następującym formacie: "user@domain.tld:owner/repo.git"', + 'error_github' => 'Nazwa repozytorium powinna być w formacie: "użytkownik/repo"', + 'error_bitbucket' => 'Nazwa repozytorium powinna być w formacie: " użytkownik/repo\'', + 'error_path' => 'Wybrana sieżka nie istnieje', + + // View Project: + 'all_branches' => 'Wszystkie Gałęzie', + 'builds' => 'Budowania', + 'id' => 'ID', + 'date' => 'Data', + 'project' => 'Projekt', + 'commit' => 'Commit', + 'branch' => 'Gałąź', + 'status' => 'Status', + 'prev_link' => '« Poprzedni', + 'next_link' => 'Następny »', + 'public_key' => 'Klucz Publiczny', + 'delete_build' => 'Usuń Budowanie', + + 'webhooks' => 'Webhooks', + 'webhooks_help_github' => 'Aby automatycznie uruchomić nową budowę po wysłaniu commitów dodaj poniższy adres URL + jako nowy "WebHook" w sekcji Webhooks and Services + Twojego repozytoria GitLab.', + + 'webhooks_help_gitlab' => 'Aby automatycznie uruchomić nową budowę po wysłaniu commitów dodaj poniższy adres URL + jako "WebHook URL" w sekcji Web Hook Twojego repozytoria GitLab.', + + 'webhooks_help_bitbucket' => 'Aby automatycznie uruchomić nową budowę po wysłaniu commitów, dodaj poniższy adres URL + jako usługę "POST" w sekcji + +Services repozytoria Bitbucket.', + + // View Build + 'build_x_not_found' => 'Budowanie o ID %d nie istnieje.', + 'build_n' => 'Budowanie %d', + 'rebuild_now' => 'Przebuduj Teraz', + + + 'committed_by_x' => 'Commitowane przez %s', + 'commit_id_x' => 'Commit: %s', + + 'chart_display' => 'Ten wykres wyświetli się po zakończeniu budowy.', + + 'build' => 'Budowanie', + 'lines' => 'Linie', + 'comment_lines' => 'Linie Komentarza', + 'noncomment_lines' => 'Linie Bez Komentarza', + 'logical_lines' => 'Lokalne Linie', + 'lines_of_code' => 'Linie Kodu', + 'build_log' => 'Log Budowania', + 'quality_trend' => 'Trend Jakości', + 'codeception_errors' => 'Błędy Codeception', + 'phpmd_warnings' => 'Alerty PHPMD', + 'phpcs_warnings' => 'Alerty PHPCS', + 'phpcs_errors' => 'Błędy PHPCS', + 'phplint_errors' => 'Błędy Lint', + 'phpunit_errors' => 'Błędy PHPUnit', + 'phpdoccheck_warnings' => 'Brakuje sekcji DocBlock', + 'issues' => 'Problemy', + + 'codeception' => 'Codeception', + 'phpcpd' => 'PHP Copy/Paste Detector', + 'phpcs' => 'PHP Code Sniffer', + 'phpdoccheck' => 'Brakuje sekcji DocBlock', + 'phpmd' => 'PHP Mess Detector', + 'phpspec' => 'PHPSpec', + 'phpunit' => 'PHPUnit', + 'technical_debt' => 'Dług technologiczny', + 'behat' => 'Behat', + + 'file' => 'Plik', + 'line' => 'Linia', + 'class' => 'Klasa', + 'method' => 'Metoda', + 'message' => 'Wiadomość', + 'start' => 'Początek', + 'end' => 'Koniec', + 'from' => 'Od', + 'to' => 'Do', + 'result' => 'Wynik', + 'ok' => 'OK', + 'took_n_seconds' => 'Zajęło %d sekund', + 'build_created' => 'Budowanie Stworzone', + 'build_started' => 'Budowanie Rozpoczęte', + 'build_finished' => 'Budowanie Zakończone', + 'test_message' => 'Wiadomość', + 'test_no_message' => 'Brak wiadomości', + 'test_success' => 'Powodzenie: %d', + 'test_fail' => 'Niepowodzenia: %d', + 'test_skipped' => 'Pominęte: %d', + 'test_error' => 'Błędy: %d', + 'test_todo' => 'Do zrobienia: %d', + 'test_total' => '%d test(ów)', + + // Users + 'name' => 'Nazwa', + 'password_change' => 'Hasło (pozostaw puste jeśli nie chcesz zmienić hasła)', + 'save' => 'Zapisz »', + 'update_your_details' => 'Aktualizuj swoje dane', + 'your_details_updated' => 'Twoje dane zostały zaktualizowane.', + 'add_user' => 'Dodaj Użytkownika', + 'is_admin' => 'Jest Adminem?', + 'yes' => 'Tak', + 'no' => 'Nie', + 'edit' => 'Edytuj', + 'edit_user' => 'Edytuj Użytkownika', + 'delete_user' => 'Usuń Użytkownika', + 'user_n_not_found' => 'Użytkownik z ID %d nie istnieje.', + 'is_user_admin' => 'Czy użytkownik jest administratorem?', + 'save_user' => 'Zapisz Użytkownika', + + // Settings: + 'settings_saved' => 'Ustawienia zostały zapisane.', + 'settings_check_perms' => 'Twoje ustawienia nie mogły zostać zapisane. Sprawdź uprawnienia do pliku config.yml.', + 'settings_cannot_write' => 'PHPCI nie może zapisać do pliku config.yml. Dopóty nie będzie można poprawnie zachować ustawie, +dopóki nie będzie to naprawione.', + 'settings_github_linked' => 'Połaczono z Twoim kontem Github', + 'settings_github_not_linked' => 'Nie udało się połaczyć z Twoim kontem Github', + 'build_settings' => 'Ustawienia budowania', + 'github_application' => 'Aplikacja GitHub', + 'github_sign_in' => 'Zanim będzie można zacząć korzystać z GitHub, musisz najpierw Sign in, a następnie udzielić dostęp dla PHPCI do Twojego konta.', + 'github_phpci_linked' => 'PHPCI zostało pomyślnie połączone z konten GitHub.', + 'github_where_to_find' => 'Gdzie można znaleźć...', + 'github_where_help' => 'Jeśli to jest Twoja aplikacjia i chcesz jej użyć to więcej informacji znajdziesz w sekcji ustawień: + applications', + + 'email_settings' => 'Ustawienia Email', + 'email_settings_help' => 'Aby PHPCI mógł wysyłać emaile z stanem budowy, musisz najpierw skonfigurować poniższe ustawienia SMTP.', + + 'application_id' => 'ID Aplikacji', + 'application_secret' => 'Klucz Secret aplikacji', + + 'smtp_server' => 'Serwer SMTP', + 'smtp_port' => 'Port SMTP', + 'smtp_username' => 'SMTP Login', + 'smtp_password' => 'Hasło SMTP', + 'from_email_address' => 'E-mail adres Od:', + 'default_notification_address' => 'Domyślny adres email powiadamiania', + 'use_smtp_encryption' => 'Użyć szyfrowane SMTP?', + 'none' => 'Żadne', + 'ssl' => 'SSL', + 'tls' => 'TLS', + + 'failed_after' => 'Uznaj, że budowanie nie powiodło się po', + '5_mins' => '5 Minutach', + '15_mins' => '15 Minutach', + '30_mins' => '30 Minutach', + '1_hour' => '1 Godzinie', + '3_hours' => '3 Godzinach', + + // Plugins + 'cannot_update_composer' => 'PHPCI nie może zaktualizować copmposer.json, ponieważ nie ma uprawnień do zapisu.', + 'x_has_been_removed' => 'Usunięto %s. ', + 'x_has_been_added' => 'Dodano %s do composer.json. Zostanie zainstalowane po +wywołaniu polecenia composer update.', + 'enabled_plugins' => 'Aktywne Pluginy', + 'provided_by_package' => 'Dostarczone w pakiecie', + 'installed_packages' => 'Zainstalowane Pakiety', + 'suggested_packages' => 'Sugerowane Pakiety', + 'title' => 'Tytuł', + 'description' => 'Opis', + 'version' => 'Wersja', + 'install' => 'Zainstaluj »', + 'remove' => 'Usuń »', + 'search_packagist_for_more' => 'Przeszukaj Packagist po więcej pakietów', + 'search' => 'Szukaj »', + + // Installer + 'installation_url' => 'URL instalacyjny PHPCI', + 'db_host' => 'Host Bazy Danych', + 'db_name' => 'Nazwa Bazy Danych', + 'db_user' => 'Nazwa Użytkownika Bazy Danych', + 'db_pass' => 'Hasło Bazy Danych', + 'admin_name' => 'Imię Admina', + 'admin_pass' => 'Hasło Admina', + 'admin_email' => 'Adres Email Admina', + 'config_path' => 'Ścieżka Pliku Config', + 'install_phpci' => 'Zainstaluj PHPCI', + 'welcome_to_phpci' => 'Witaj w PHPCI', + 'please_answer' => 'Odpowiedz na poniższe pytania:', + 'phpci_php_req' => 'PHPCI wymaga przynajmniej PHP 5.3.8 do prawidłowego funkcjonowania.', + 'extension_required' => 'Wymagane rozszerzenie: %s', + 'function_required' => 'PHPCI musi mieć możliwość wywołania funkcji %s(). Czy ona jest wyłączona w php.ini?', + 'requirements_not_met' => 'Nie można zainstalować PHPCI, ponieważ nie wszystkie wymagania zostały spełnione. +Przejrzyj powyższą listę błędów przed kontynuowaniem.', + 'must_be_valid_email' => 'Poprawny adres email jest wymagany.', + 'must_be_valid_url' => 'Poprawny URL jest wymagany.', + 'enter_name' => 'Imię Admina: ', + 'enter_email' => 'Email Admina: ', + 'enter_password' => 'Hasło Admina: ', + 'enter_phpci_url' => 'URL PHPCI (na przykład "http://phpci.local"): ', + + 'enter_db_host' => 'Wpisz hosta MySQL [host lokalny]: ', + 'enter_db_name' => 'Wpisz nazwę bazy danych MySQL [phpci]: ', + 'enter_db_user' => 'Wpisz nazwę użytkownika MySQL [phpci]: ', + 'enter_db_pass' => 'Wpisz hasło MySQL: ', + 'could_not_connect' => 'Z podanymi ustawieniami PHPCI nie udało się połączyć z MySQL. Spróbuj ponownie.', + 'setting_up_db' => 'Ustawianie Twojej bazy danych...', + 'user_created' => 'Utworzono konto użytkownika!', + 'failed_to_create' => 'PHPCI nie udało się założyc Twojego konta administratora.', + 'config_exists' => 'Plik konfiguracji PHPCI istnieje i nie jest pusty.', + 'update_instead' => 'Jeśli próbowałeś zaktualizować PHPCI, użyj phpci:update.', + + // Update + 'update_phpci' => 'Zaktualizuj bazę danych zgodnie ze zmodyfikowanymi modelami.', + 'updating_phpci' => 'Aktualizacja bazy danych PHPCI:', + 'not_installed' => 'Wygląda na to, że PHPCI nie jest zainstalowane.', + 'install_instead' => 'Proszę zainstalować PHPCI poprzez phpci:install', + + // Poll Command + 'poll_github' => 'Odpytuj GitHub, aby sprawdzić czy należy uruchomić budowę.', + 'no_token' => 'Nie znaleziono tokena GitHub', + 'finding_projects' => 'Szukanie projektów do odpytywania', + 'found_n_projects' => 'Znaleziono %d projektów', + 'last_commit_is' => 'Ostatni commit do GitHuba dla %s to %s', + 'adding_new_build' => 'Ostatni commit jest inny w bazie danych, dodaję nową budowę.', + 'finished_processing_builds' => 'Ukończono przetwarzanie budów.', + + // Create Admin + 'create_admin_user' => 'Utwórz admina', + 'incorrect_format' => 'Niepoprawny format', + + // Create Build Command + 'create_build_project' => 'Utwórz budowanie dla projektu', + 'project_id_argument' => 'ID projektu', + 'commit_id_option' => 'ID Commita do budowania', + 'branch_name_option' => 'Gałąź do budowania', + + // Run Command + 'run_all_pending' => 'Uruchom wszystkie oczekujące budowy w PHPCI', + 'finding_builds' => 'Szukam budów do przetwarzania.', + 'found_n_builds' => 'Znaleziono %d budowań', + 'skipping_build' => 'Budowanie %d jest pomijane - Budowanie projektu jest już w toku', + 'marked_as_failed' => 'Budowanie %d nie powiodło się z powodu przekroczenia limitu czasu.', + + // Builder + 'missing_phpci_yml' => 'Projekt nie zawiera pliku phpci.yml lub projekt jest pusty.', + 'build_success' => 'BUDOWANIE ZAKOŃCZONE SUKCESEM', + 'build_failed' => 'BUDOWANIE NIE POWIODŁO SIĘ', + 'removing_build' => 'Usuwanie Budowania.', + 'exception' => 'Wyjątek:', + 'could_not_create_working' => 'Nie można utworzyć wersji roboczej.', + 'working_copy_created' => 'Stworzono wersję roboczą: %s', + 'looking_for_binary' => 'Szukam binarek: %s', + 'found_in_path' => 'Znaleziono w %s: %s', + 'running_plugin' => 'Uruchomiony Plugin: %s', + 'plugin_success' => 'Plugin: Sukces', + 'plugin_failed' => 'Plugin: Niepowodzenie', + 'plugin_missing' => 'Plugin nie istnieje: %s', + 'tap_version' => 'TapParser obsługuje tylko TAP w wersji 13', + 'tap_error' => 'Nieprawidłowy łańcuch TAP, liczba testów nie zgadza się z policzoną ilością testów.', + + // Build Plugins: + 'no_tests_performed' => 'Nie przeprowadzono żadnych testów.', + 'could_not_find' => 'Nie znaleziono %s', + 'no_campfire_settings' => 'Nie zdefiniowano parametrów połączenia dla pluginu Campfire', + 'failed_to_wipe' => 'Nie udało się wyczyścić istniejącego katalogu %s przed kopiowaniem', + 'passing_build' => 'Pomijanie Budowania', + 'failing_build' => 'Niepowodzenie Budowania', + 'log_output' => 'Log Wyjściowy:', + 'n_emails_sent' => 'Wysłano %d emaili.', + 'n_emails_failed' => 'Nie wysłano %d emaili.', + 'unable_to_set_env' => 'Nie można ustawić zmiennej środowiskowej', + 'tag_created' => 'Tag stworzony przez PHPCI: %s', + 'x_built_at_x' => '%PROJECT_TITLE% zbudowano pod %BUILD_URI%', + 'hipchat_settings' => 'Proszę podać pokój i authToken dla pluginu hipchat_notify.', + 'irc_settings' => 'Musisz skonfigurować serwer, pokój i swoją nazwę.', + 'invalid_command' => 'Nieprawidłowe polecenie', + 'import_file_key' => 'Potwierdzenie importu musi zawierać klucz "plik"', + 'cannot_open_import' => 'Nie można otworzyć importowanego pliku SQL: %s', + 'unable_to_execute' => 'Nie można wykonać pliku SQL', + 'phar_internal_error' => 'Wewnętrzny Błąd Pluginu Phar', + 'build_file_missing' => 'Podany plik budowy nie istnieje.', + 'property_file_missing' => 'Podany plik właściwości nie istnieje.', + 'could_not_process_report' => 'Nie udało się przetworzyć raportu wygenerowanego przez to narzędzie.', + 'shell_not_enabled' => 'Plugin powłoki jest nieaktywny. Aktywuj go poprzez config.yml.' +); diff --git a/PHPCI/Languages/lang.ru.php b/PHPCI/Languages/lang.ru.php new file mode 100644 index 00000000..0a7acff2 --- /dev/null +++ b/PHPCI/Languages/lang.ru.php @@ -0,0 +1,412 @@ + 'Pусский', + 'language' => 'язык', + + // Log in: + 'log_in_to_phpci' => 'Войти в PHPCI', + 'login_error' => 'Неправильный email или пароль', + 'forgotten_password_link' => 'Забыли пароль?', + 'reset_emailed' => 'Вы получите письмо со ссылкой на сброс пароля.', + 'reset_header' => 'Не волнуйтесь!
Просто введите ваш email, и вам придет письмо со ссылкой на сброс пароля.', + 'reset_email_address' => 'Введите ваш email:', + 'reset_send_email' => 'Сброс пароля', + 'reset_enter_password' => 'Пожалуйста, введите новый пароль', + 'reset_new_password' => 'Новый пароль:', + 'reset_change_password' => 'Сменить пароль', + 'reset_no_user_exists' => 'Пользователь с таким email-адресом не найден, пожалуйста, попробуйте еще раз.', + 'reset_email_body' => 'Привет %s, + +Вы получили это письмо, потому что вы или кто-то другой запросили сброс пароля в PHPCI. + +Если это были вы, пожалуйста, перейдите по ссылке для сброса пароля: %ssession/reset-password/%d/%s, + +иначе игнорируйте это письмо и ничего не предпринимайте. + +Спасибо, + +PHPCI', + + 'reset_email_title' => 'Сброс пароля PHPCI для %s', + 'reset_invalid' => 'Некорректный запрос на сброс пароля.', + 'email_address' => 'Email', + 'login' => 'Логин / Email', + 'password' => 'Пароль', + 'log_in' => 'Войти', + + // Top Nav + 'toggle_navigation' => 'Скрыть/показать панель навигации', + 'n_builds_pending' => '%d сборок ожидает', + 'n_builds_running' => '%d сборок запущено', + 'edit_profile' => 'Редактировать профиль', + 'sign_out' => 'Выйти', + 'branch_x' => 'Ветка: %s', + 'created_x' => 'Создан: %s', + 'started_x' => 'Запущен: %s', + + // Sidebar + 'hello_name' => 'Привет, %s', + 'dashboard' => 'Панель управления', + 'admin_options' => 'Меню администратора', + 'add_project' => 'Добавить проект', + 'settings' => 'Настройки', + 'manage_users' => 'Пользователи', + 'plugins' => 'Плагины', + 'view' => 'Отчет', + 'build_now' => 'Собрать', + 'edit_project' => 'Редактировать проект', + 'delete_project' => 'Удалить проект', + + // Project Summary: + 'no_builds_yet' => 'Нет сборок!', + 'x_of_x_failed' => '%d из последних %d сборок были провалены.', + 'x_of_x_failed_short' => '%d / %d провалены.', + 'last_successful_build' => ' Последняя успешная сборка была %s.', + 'never_built_successfully' => ' Этот проект никогда не собирался успешно.', + 'all_builds_passed' => 'Все последние сборки (%d) прошли успешно.', + 'all_builds_passed_short' => '%d / %d успешные.', + 'last_failed_build' => ' Последней проваленной сборкой была %s.', + 'never_failed_build' => ' У этого проекта никогда не было проваленных сборок.', + 'view_project' => 'Обзор проекта', + + // Timeline: + 'latest_builds' => 'Последние сборки', + 'pending' => 'Ожидает', + 'running' => 'Запущена', + 'success' => 'Успешно', + 'successful' => 'Успешна', + 'failed' => 'Провалена', + 'manual_build' => 'Запущена вручную', + + // Add/Edit Project: + 'new_project' => 'Новый проект', + 'project_x_not_found' => 'Проекта с ID %d не существует.', + 'project_details' => 'Подробности проекта', + 'public_key_help' => 'Чтобы было легче начать, мы сгенерировали пару SSH-ключей для использования в вашем проекте. + Для их использования, просто добавьте эту публичную часть ключа в поле "deploy keys" на выбранном вами хостинге исходного кода.', + 'select_repository_type' => 'Выберите тип репозитория...', + 'github' => 'GitHub', + 'bitbucket' => 'Bitbucket', + 'gitlab' => 'GitLab', + 'remote' => 'Внешний URL', + 'local' => 'Локальный путь', + 'hg' => 'Mercurial', + 'svn' => 'Subversion', + + 'where_hosted' => 'Расположение проекта', + 'choose_github' => 'Выберите GitHub репозиторий:', + + 'repo_name' => 'Репозиторий / Внешний URL / Локальный путь', + 'project_title' => 'Название проекта', + 'project_private_key' => 'Приватный ключ для доступа к репозиторию + (оставьте поле пустым для локального использования и/или анонимного доступа)', + 'build_config' => 'Конфигурация сборки проекта для PHPCI + (если вы не добавили файл phpci.yml в репозиторий вашего проекта)', + 'default_branch' => 'Ветка по умолчанию', + 'allow_public_status' => 'Разрешить публичный статус и изображение (статуса) для проекта', + 'archived' => 'Архивный', + 'archived_menu' => 'Архив', + 'save_project' => 'Сохранить проект', + + 'error_mercurial' => 'URL репозитория Mercurial должен начинаться с http:// или https://', + 'error_remote' => 'URL репозитория должен начинаться с git://, http:// или https://', + 'error_gitlab' => 'Имя репозитория в GitLab должно иметь формат: "user@domain.tld:owner/repo.git"', + 'error_github' => 'Имя репозитория должно иметь формат: "owner/repo"', + 'error_bitbucket' => 'Имя репозитория должно иметь формат: "owner/repo"', + 'error_path' => 'Пути, который вы указали, не существует.', + + // View Project: + 'all_branches' => 'Все ветки', + 'builds' => 'Сборки', + 'id' => 'ID', + 'date' => 'Дата', + 'project' => 'Проект', + 'commit' => 'Коммит', + 'branch' => 'Ветка', + 'status' => 'Статус', + 'prev_link' => '« Пред.', + 'next_link' => 'След. »', + 'public_key' => 'Публичный ключ', + 'delete_build' => 'Удалить сборку', + + 'webhooks' => 'Webhooks', + 'webhooks_help_github' => 'Чтобы Автоматически собирать этот проект при публикации новых коммитов, добавьте URL ниже в качестве нового хука в разделе настроек Webhooks + and Services вашего GitHub репозитория.', + + 'webhooks_help_gitlab' => 'Чтобы Автоматически собирать этот проект при публикации новых коммитов, добавьте URL ниже в качестве "WebHook URL" + в разделе "Web Hooks" вашего GitLab репозитория.', + + 'webhooks_help_bitbucket' => 'Чтобы Автоматически собирать этот проект при публикации новых коммитов, добавьте URL ниже как "POST" сервис в разделе + Services вашего Bitbucket репозитория.', + + // View Build + 'build_x_not_found' => 'Сборки с ID %d не существует.', + 'build_n' => 'Сборка %d', + 'rebuild_now' => 'Пересобрать сейчас', + + + 'committed_by_x' => 'Отправил %s', + 'commit_id_x' => 'Коммит: %s', + + 'chart_display' => 'Этот график будет показан после окончания сборки.', + + 'build' => 'Сборка', + 'lines' => 'Строк', + 'comment_lines' => 'Строк комментариев', + 'noncomment_lines' => 'Строк некомментариев', + 'logical_lines' => 'Строк логики', + 'lines_of_code' => 'Строк кода', + 'build_log' => 'Лог сборки', + 'quality_trend' => 'Тенденция качества', + 'codeception_errors' => 'Ошибки Codeception', + 'phpmd_warnings' => 'Предупреждения PHPMD', + 'phpcs_warnings' => 'Предупреждения PHPCS', + 'phpcs_errors' => 'Ошибки PHPCS', + 'phplint_errors' => 'Ошибки Lint', + 'phpunit_errors' => 'Ошибки PHPUnit', + 'phpdoccheck_warnings' => 'Пропущенные Docblocks', + 'issues' => 'Проблемы', + + 'codeception' => 'Codeception', + 'phpcpd' => 'PHP Copy/Paste Detector', + 'phpcs' => 'PHP Code Sniffer', + 'phpdoccheck' => 'Missing Docblocks', + 'phpmd' => 'PHP Mess Detector', + 'phpspec' => 'PHP Spec', + 'phpunit' => 'PHP Unit', + 'technical_debt' => 'Технические долги', + 'behat' => 'Behat', + + 'codeception_feature' => 'Свойство', + 'codeception_suite' => 'Набор', + 'codeception_time' => 'Время', + 'codeception_synopsis' => 'Тестов выполнено: %1$d (за %2$f сек.). Провалов: %3$d.', + + 'file' => 'Файл', + 'line' => 'Строка', + 'class' => 'Класс', + 'method' => 'Метод', + 'message' => 'Сообщение', + 'start' => 'Начало', + 'end' => 'Конец', + 'from' => 'Из', + 'to' => 'В', + 'result' => 'Результат', + 'ok' => 'OK', + 'took_n_seconds' => 'Заняло секунд: %d', + 'build_created' => 'Сборка создана', + 'build_started' => 'Сборка запущена', + 'build_finished' => 'Сборка окончена', + 'test_message' => 'Message', + 'test_no_message' => 'No message', + 'test_success' => 'Успешно: %d', + 'test_fail' => 'Провалено: %d', + 'test_skipped' => 'Пропущено: %d', + 'test_error' => 'Ошибок: %d', + 'test_todo' => 'Todo: %d', + 'test_total' => 'Тестов: %d', + + // Users + 'name' => 'Имя', + 'password_change' => 'Пароль (оставьте поле пустым, если не собираетесь менять его)', + 'save' => 'Сохранить »', + 'update_your_details' => 'Обновить свои данные', + 'your_details_updated' => 'Ваши данные были обновлены.', + 'add_user' => 'Добавить пользователя', + 'is_admin' => 'Является администратором', + 'yes' => 'Да', + 'no' => 'Нет', + 'edit' => 'Править', + 'edit_user' => 'Редактировать пользователя', + 'delete_user' => 'Удалить пользователя', + 'user_n_not_found' => 'Пользователя с ID %d не существует.', + 'is_user_admin' => 'Этот пользователь является администратором', + 'save_user' => 'Сохранить пользователя', + + // Settings: + 'settings_saved' => 'Ваши настройки были сохранены.', + 'settings_check_perms' => 'Ваши настройки не могут быть сохранены, проверьте права на файл настроек config.yml.', + 'settings_cannot_write' => 'PHPCI не может записать config.yml файл, настройки не могут быть сохранены корректно, пока это не будет исправлено.', + 'settings_github_linked' => 'Ваш GitHub аккаунт привязан.', + 'settings_github_not_linked' => 'Ваш GitHub аккаунт не может быть привязан.', + 'build_settings' => 'Настройки сборки', + 'github_application' => 'GitHub приложение', + 'github_sign_in' => 'Перед тем как начать использовать GitHub аккаунт, вам необходимо войти и разрешить доступ для + PHPCI до вашего аккаунта.', + 'github_phpci_linked' => 'PHPCI успешно привязал GitHub аккаунт.', + 'github_where_to_find' => 'Где это найти...', + 'github_where_help' => 'Если вы владелец приложения, которое вы хотели бы использовать, то вы можете найти информацию об этом в разделе + applications настроек.', + + 'email_settings' => 'Настройки email', + 'email_settings_help' => 'Перед тем, как PHPCI начнет отсылать статус сборок по почте, + вам необходимо настроить параметры SMTP ниже.', + + 'application_id' => 'ID приложения', + 'application_secret' => 'Секретный ключ приложения', + + 'smtp_server' => 'SMTP сервер', + 'smtp_port' => 'SMTP порт', + 'smtp_username' => 'SMTP пользователь', + 'smtp_password' => 'SMTP пароль', + 'from_email_address' => 'Отправлять с email', + 'default_notification_address' => 'Email по умолчанию для оповещений', + 'use_smtp_encryption' => 'Использовать SMTP шифрование', + 'none' => 'Нет', + 'ssl' => 'SSL', + 'tls' => 'TLS', + + 'failed_after' => 'Признать сборку проваленной по прошествии', + '5_mins' => '5 минут', + '15_mins' => '15 минут', + '30_mins' => '30 минут', + '1_hour' => '1 часа', + '3_hours' => '3 часов', + + // Plugins + 'cannot_update_composer' => 'PHPCI не может обновить composer.json, если он недоступен на запись.', + 'x_has_been_removed' => '%s был удален.', + 'x_has_been_added' => '%s был добавлен в composer.json и будет установлен, как только вы запустите composer update.', + 'enabled_plugins' => 'Включенные плагины', + 'provided_by_package' => 'Предоставляется пакетом', + 'installed_packages' => 'Установленные пакеты', + 'suggested_packages' => 'Рекомендуемые пакеты', + 'title' => 'Название', + 'description' => 'Описание', + 'version' => 'Версия', + 'install' => 'Установить »', + 'remove' => 'Удалить »', + 'search_packagist_for_more' => 'Искать на Packagist', + 'search' => 'Искать »', + + // Summary plugin + 'build-summary' => 'Сводка', + 'stage' => 'Этап', + 'duration' => 'Продолжительность', + 'plugin' => 'Плагин', + 'stage_setup' => 'Установка', + 'stage_test' => 'тестирование', + 'stage_complete' => 'Завершение', + 'stage_success' => 'Успешное завершение', + 'stage_failure' => 'Провал', + 'stage_broken' => 'Поломка', + 'stage_fixed' => 'Исправление', + + // Installer + 'installation_url' => 'URL-адрес PHPCI для установки', + 'db_host' => 'Хост базы данных', + 'db_name' => 'Имя базы данных', + 'db_user' => 'Пользователь базы данных', + 'db_pass' => 'Пароль базы данных', + 'admin_name' => 'Имя администратора', + 'admin_pass' => 'Пароль администратора', + 'admin_email' => 'Email администратора', + 'config_path' => 'Путь до файла конфигурации', + 'install_phpci' => 'Установить PHPCI', + 'welcome_to_phpci' => 'Добро пожаловать в PHPCI', + 'please_answer' => 'Пожалуйста, ответьте на несколько вопросов:', + 'phpci_php_req' => 'PHPCI необходима для работы версия PHP не ниже 5.3.8.', + 'extension_required' => 'Требуется расширение PHP: %s', + 'function_required' => 'PHPCI необходима возможность вызывать %s() функцию. Она выключена в php.ini?', + 'requirements_not_met' => 'PHPCI не может быть установлен, пока не все требования выполнены. + Пожалуйста, просмотрите возникшие ошибки перед тем, как продолжить.', + 'must_be_valid_email' => 'Должен быть корректным email-адресом.', + 'must_be_valid_url' => 'Должен быть корректным URL-адресом.', + 'enter_name' => 'Имя администратора: ', + 'enter_email' => 'Email администратора: ', + 'enter_password' => 'Пароль администратора: ', + 'enter_phpci_url' => 'URL-адрес вашего PHPCI (например: "http://phpci.local"): ', + + 'enter_db_host' => 'Пожалуйста, введите хост MySQL [localhost]: ', + 'enter_db_name' => 'Пожалуйста, введите имя базы данных MySQL [phpci]: ', + 'enter_db_user' => 'Пожалуйста, введите пользователя MySQL [phpci]: ', + 'enter_db_pass' => 'Пожалуйста, введите пароль MySQL: ', + 'could_not_connect' => 'PHPCI не может подключится к MySQL с переданными параметрами. Пожалуйста, попробуйте еще раз.', + 'setting_up_db' => 'Установка базы данных... ', + 'user_created' => 'Аккаунт пользователя создан!', + 'failed_to_create' => 'PHPCI не удалось создать аккаунт администратора.', + 'config_exists' => 'Файл конфигурации PHPCI уже существует, и он не пустой.', + 'update_instead' => 'Если вы собираетесь обновить PHPCI, пожалуйста, используйте команду phpci:update.', + + // Update + 'update_phpci' => 'Обновите базу данных с учетом обновленных моделей.', + 'updating_phpci' => 'Обновление базы данных PHPCI: ', + 'not_installed' => 'PHPCI не может быть установлен.', + 'install_instead' => 'Пожалуйста, установите PHPCI с помощью команды phpci:install.', + + // Poll Command + 'poll_github' => 'Опрос GitHub для проверки запуска сборки.', + 'no_token' => 'GitHub токен не найден', + 'finding_projects' => 'Поиск проектов для опроса', + 'found_n_projects' => 'Найдено проектов: %d', + 'last_commit_is' => 'Последний коммит на GitHub для %s - %s', + 'adding_new_build' => 'Последний коммит имеет различия с базой данных, создана сборка.', + 'finished_processing_builds' => 'Процесс сборки завершен.', + + // Create Admin + 'create_admin_user' => 'Добавить аккаунт администратора', + 'incorrect_format' => 'Неверный формат', + + // Create Build Command + 'create_build_project' => 'Создать сборку проекта', + 'project_id_argument' => 'ID проекта', + 'commit_id_option' => 'ID коммита для сборки', + 'branch_name_option' => 'Ветка для сборки', + + // Run Command + 'run_all_pending' => 'Запустить все ожидающие PHPCI сборки.', + 'finding_builds' => 'Поиск сборок для запуска', + 'found_n_builds' => 'Найдено сборок: %d', + 'skipping_build' => 'Сборка %d пропущена - Сборка проекта уже идет.', + 'marked_as_failed' => 'Сборка %d отмечена как неудавшаяся из-за превышения лимита времени.', + + // Builder + 'missing_phpci_yml' => 'Этот проект не содержит файла phpci.yml, или файл пустой.', + 'build_success' => 'СБОРКА УСПЕШНА', + 'build_failed' => 'СБОРКА ПРОВАЛЕНА', + 'removing_build' => 'Удаление сборки.', + 'exception' => 'Исключение: ', + 'could_not_create_working' => 'Не удалось создать рабочую копию.', + 'working_copy_created' => 'Рабочая копия создана: %s', + 'looking_for_binary' => 'Поиск пакета: %s', + 'found_in_path' => 'Найден в %s: %s', + 'running_plugin' => 'ЗАПУЩЕН ПЛАГИН: %s', + 'plugin_success' => 'ПЛАГИН: УСПЕШНО', + 'plugin_failed' => 'ПЛАГИН: ПРОВАЛ', + 'plugin_missing' => 'Плагина не существует: %s', + 'tap_version' => 'TapParser поддерживает только TAP версии 13', + 'tap_error' => 'Некорректная TAP-строка, количество тестов не совпадает с заявленным.', + + // Build Plugins: + 'no_tests_performed' => 'Никакие тесты не были запущены.', + 'could_not_find' => 'Не удается найти %s', + 'no_campfire_settings' => 'Не переданы параметры подключения для плагина Campfire', + 'failed_to_wipe' => 'Не удалось уничтожить существующую директорию %s перед копированием', + 'passing_build' => 'Успех сборки', + 'failing_build' => 'Провал сборки', + 'log_output' => 'Вывод лога: ', + 'n_emails_sent' => 'Писем отправлено: %d.', + 'n_emails_failed' => 'Писем не удалось отправить: %d.', + 'unable_to_set_env' => 'Невозможно установить переменную окружения', + 'tag_created' => 'Метка создана PHPCI: %s', + 'x_built_at_x' => '%PROJECT_TITLE% собран: %BUILD_URI%', + 'hipchat_settings' => 'Пожалуйста, укажите комнату и токен (authToken) для плагина hipchat_notify', + 'irc_settings' => 'Вы должны задать сервер, комнату и ник.', + 'invalid_command' => 'Некорректная команда', + 'import_file_key' => 'Выражение импорта должно содержать ключ \'file\'', + 'cannot_open_import' => 'Не удалось открыть файл SQL для импорта: %s', + 'unable_to_execute' => 'Невозможно выполнить файл SQL', + 'phar_internal_error' => 'Внутренняя ошибка плагина Phar', + 'build_file_missing' => 'Указанного файла сборки не существует.', + 'property_file_missing' => 'Указанного файла сборки не существует.', + 'could_not_process_report' => 'Невозможно обработать отчет этой утилиты.', + 'shell_not_enabled' => 'Плагин shell не включен. Пожалуйста, включите его в файле config.yml.' +); diff --git a/PHPCI/Languages/lang.uk.php b/PHPCI/Languages/lang.uk.php new file mode 100644 index 00000000..54ced220 --- /dev/null +++ b/PHPCI/Languages/lang.uk.php @@ -0,0 +1,399 @@ + 'Українська', + 'language' => 'Мова', + + // Log in: + 'log_in_to_phpci' => 'Увійти до PHPCI', + 'login_error' => 'Невірний email або пароль', + 'forgotten_password_link' => 'Забули свій пароль?', + 'reset_emailed' => 'Ми відправили вам посилання для скидання вашого паролю.', + 'reset_header' => 'Не хвилюйтесь!
Просто введіть ваш email +і вам буде надіслано листа із посиланням на скидання паролю.', + 'reset_email_address' => 'Введіть свою email адресу:', + 'reset_send_email' => 'Скидання пароля', + 'reset_enter_password' => 'Введіть, будь ласка, новий пароль', + 'reset_new_password' => 'Новий пароль:', + 'reset_change_password' => 'Змінити пароль', + 'reset_no_user_exists' => 'Не існує користувача з такою email адресою, будь ласка, повторіть знову.', + 'reset_email_body' => 'Привіт, %s, + +Ви отримали цей лист, тому що ви або хтось інший запросили скидання пароля в PHPCI. + +Якщо це були ви, будь ласка, перейдіть за посиланням нижче для скидання пароля: %ssession/reset-password/%d/%s, + +або ж проігноруйте цей лист та нічого не робіть. + +Дякуємо, + +PHPCI', + + 'reset_email_title' => 'Скидання пароль PHPCI для %s', + 'reset_invalid' => 'Невірний запит скидання паролю.', + 'email_address' => 'Email адреса', + 'login' => 'Логин / Email адреса', + 'password' => 'Пароль', + 'log_in' => 'Увійти', + + + // Top Nav + 'toggle_navigation' => 'Сховати/відобразити панель навігації', + 'n_builds_pending' => '%d збірок очікує', + 'n_builds_running' => '%d збірок виконується', + 'edit_profile' => 'Редагувати профіль', + 'sign_out' => 'Вийти', + 'branch_x' => 'Гілка: %s', + 'created_x' => 'Створено: %s', + 'started_x' => 'Розпочато: %s', + + // Sidebar + 'hello_name' => 'Привіт, %s', + 'dashboard' => 'Панель управління', + 'admin_options' => 'Меню адміністратора', + 'add_project' => 'Додати проект', + 'settings' => 'Налаштування', + 'manage_users' => 'Управління користувачами', + 'plugins' => 'Плагіни', + 'view' => 'Переглянути', + 'build_now' => 'Зібрати', + 'edit_project' => 'Редагувати проект', + 'delete_project' => 'Видалити проект', + + // Project Summary: + 'no_builds_yet' => 'Немає збірок!', + 'x_of_x_failed' => '%d із останніх %d збірок були провалені.', + 'x_of_x_failed_short' => '%d / %d провалені.', + 'last_successful_build' => 'Останнью успішною збіркою була %s.', + 'never_built_successfully' => 'У цього проекта ніколи не було успішних збірок.', + 'all_builds_passed' => 'Усі із останніх %d збірок успішні.', + 'all_builds_passed_short' => '%d / %d успішні.', + 'last_failed_build' => 'Останньою проваленою збіркою була %s.', + 'never_failed_build' => 'У цього проекта ніколи не було провалених збірок.', + 'view_project' => 'Переглянути проект', + + // Timeline: + 'latest_builds' => 'Останні збірки', + 'pending' => 'Очікує', + 'running' => 'Виконується', + 'success' => 'Успіх', + 'successful' => 'Успішно', + 'failed' => 'Провалена', + 'manual_build' => 'Ручна збірка', + + // Add/Edit Project: + 'new_project' => 'Новий проект', + 'project_x_not_found' => 'Проект із ID %d не існує.', + 'project_details' => 'Деталі проекта', + 'public_key_help' => 'Для полегшення початку, ми згенерували пару SSH-ключів для вас для використання в цьому проекті. +Для їх використання - просто додайте наступний публічний ключ у розділ "deploy keys" обраної вами системи зберігання програмного коду.', + 'select_repository_type' => 'Оберіть тип репозиторію...', + 'github' => 'GitHub', + 'bitbucket' => 'Bitbucket', + 'gitlab' => 'GitLab', + 'remote' => 'Віддалений URL', + 'local' => 'Локальний шлях', + 'hg' => 'Mercurial', + + 'where_hosted' => 'Де зберігається ваш проект?', + 'choose_github' => 'Оберіть GitHub репозиторій:', + + 'repo_name' => 'Ім’я репозиторія / URL (зовнішній) / Шлях (локальний)', + 'project_title' => 'Заголовок проекту', + 'project_private_key' => 'Приватний ключ доступу до репозиторія +(залишити поле порожнім для локального використання та/або анонімного доступу)', + 'build_config' => 'Конфігурація збірки цього проекта для PHPCI +(якщо ви не додали файл phpci.yml до репозиторію вашого проекту)', + 'default_branch' => 'Назва гілки за замовчуванням', + 'allow_public_status' => 'Увімкнути публічну сторінку статусу та зображення для цього проекта?', + 'archived' => 'Архівний', + 'archived_menu' => 'Архів', + 'save_project' => 'Зберегти проект', + + 'error_mercurial' => 'URL репозиторію Mercurial повинен починатись із http:// або https://', + 'error_remote' => 'URL репозиторію повинен починатись із git://, http:// або https://', + 'error_gitlab' => 'Ім’я репозиторія GitLab повинно бути у форматі "user@domain.tld:owner/repo.git"', + 'error_github' => 'Ім’я репозиторія повинно відповідати формату "owner/repo"', + 'error_bitbucket' => 'Ім’я репозиторія повинно відповідати формату "owner/repo"', + 'error_path' => 'Вказаний шлях не існує.', + + // View Project: + 'all_branches' => 'Усі гілки', + 'builds' => 'Збірки', + 'id' => 'ID', + 'date' => 'Дата', + 'project' => 'Проект', + 'commit' => 'Комміт', + 'branch' => 'Гілка', + 'status' => 'Статус', + 'prev_link' => '« Попер.', + 'next_link' => 'Наст. »', + 'public_key' => 'Публічний ключ', + 'delete_build' => 'Видалити збірку', + + 'webhooks' => 'Webhooks', + 'webhooks_help_github' => 'Для автоматичної збірки цього проекту, при надходженні нових комітів, додайте наступний URL +у якості нового "Webhook" у розділі налаштувань +Webhooks and Services +вашого GitHub репозиторію.', + + 'webhooks_help_gitlab' => 'Для автоматичної збірки цього проекту, при надходженні нових комітів, додайте наступний URL +у якості нового "WebHook URL" у розділі "Web Hooks" вашого GitLab репозиторію.', + + 'webhooks_help_bitbucket' => 'Для автоматичної збірки цього проекту, при надходженні нових комітів, додайте наступний URL +у якості нового "POST" сервісу у розділі +Services +вашого Bitbucket репозиторію.', + + // View Build + 'build_x_not_found' => 'Збірка із ID %d не існує.', + 'build_n' => 'Збірка %d', + 'rebuild_now' => 'Перезібрати зараз', + + + 'committed_by_x' => 'Комміт від %s', + 'commit_id_x' => 'Комміт: %s', + + 'chart_display' => 'Цей графік відобразиться після завершення збірки.', + + 'build' => 'Збірка', + 'lines' => 'Рядків', + 'comment_lines' => 'Рядків коментарів', + 'noncomment_lines' => 'Рядків не коментарів', + 'logical_lines' => 'Рядків логіки', + 'lines_of_code' => 'Рядки коду', + 'build_log' => 'Лог збірки', + 'quality_trend' => 'Тенденція якості', + 'codeception_errors' => 'Помилки Codeception', + 'phpmd_warnings' => 'Попередження PHPMD', + 'phpcs_warnings' => 'Попередження PHPCS', + 'phpcs_errors' => 'Помилки PHPCS', + 'phplint_errors' => 'Помилки Lint', + 'phpunit_errors' => 'Помилки PHPUnit', + 'phpdoccheck_warnings' => 'Відсутні Docblocks', + 'issues' => 'Проблеми', + + 'codeception' => 'Codeception', + 'phpcpd' => 'PHP Copy/Paste Detector', + 'phpcs' => 'PHP Code Sniffer', + 'phpdoccheck' => 'Відсутні Docblocks', + 'phpmd' => 'PHP Mess Detector', + 'phpspec' => 'PHP Spec', + 'phpunit' => 'PHP Unit', + + 'file' => 'Файл', + 'line' => 'Рядок', + 'class' => 'Клас', + 'method' => 'Метод', + 'message' => 'Повідомлення', + 'start' => 'Запуск', + 'end' => 'Кінець', + 'from' => 'Від', + 'to' => 'До', + 'result' => 'Результат', + 'ok' => 'OK', + 'took_n_seconds' => 'Зайняло %d секунд', + 'build_created' => 'Збірка створена', + 'build_started' => 'Збірка розпочата', + 'build_finished' => 'Збірка завершена', + 'test_message' => 'Message', + 'test_no_message' => 'No message', + 'test_success' => 'Successful: %d', + 'test_fail' => 'Failures: %d', + 'test_skipped' => 'Skipped: %d', + 'test_error' => 'Errors: %d', + 'test_todo' => 'Todos: %d', + 'test_total' => '%d test(s)', + + // Users + 'name' => 'Ім’я', + 'password_change' => 'Пароль (залишити порожнім, якщо не бажаєте змінювати його)', + 'save' => 'Зберегти »', + 'update_your_details' => 'Оновити ваші деталі', + 'your_details_updated' => 'Ваші деталі були оновлені.', + 'add_user' => 'Додати користувача', + 'is_admin' => 'Адміністратор?', + 'yes' => 'Так', + 'no' => 'Ні', + 'edit' => 'Редагувати', + 'edit_user' => 'Редагувати користувача', + 'delete_user' => 'Видалити користувача', + 'user_n_not_found' => 'Користувач із ID %d не існує.', + 'is_user_admin' => 'Чи є цей користувач адміністратором?', + 'save_user' => 'Зберегти користувача', + + // Settings: + 'settings_saved' => 'Ваші налаштування були збережені.', + 'settings_check_perms' => 'Ваші налаштування не можуть бути збережені, перевірте права на ваш файл налаштувань config.yml.', + 'settings_cannot_write' => 'PHPCI не може записати файл config.yml, налаштування не будуть коректно збережені, +доки це не буде виправлено.', + 'settings_github_linked' => 'Ваш GitHub аккаунт було підключено.', + 'settings_github_not_linked' => 'Ваш GitHub аккаунт не може бути підключеним.', + 'build_settings' => 'Налаштування збірки', + 'github_application' => 'GitHub додаток', + 'github_sign_in' => 'Перед початком користування GitHub, вам необхідно увійти та надати +доступ для PHPCI до вашого аккаунту.', + 'github_phpci_linked' => 'PHPCI успішно зв\'язаний з аккаунтом GitHub.', + 'github_where_to_find' => 'Де це знайти...', + 'github_where_help' => 'Якщо ви є власником додатку, який би ви хотіли використовувати, то ви можете знайти інформацію про це у розділі +налаштувань ваших додатків.', + + 'email_settings' => 'Налаштування Email', + 'email_settings_help' => 'Перед тим, як PHPCI почне надсилати статуси збірок на email, +вам необхідно налаштувати параметри SMTP нижче.', + + 'application_id' => 'ID додатка', + 'application_secret' => 'Таємний ключ додатка', + + 'smtp_server' => 'Сервер SMTP', + 'smtp_port' => 'Порт SMTP', + 'smtp_username' => 'Ім’я користувача SMTP', + 'smtp_password' => 'Пароль SMTP', + 'from_email_address' => 'Відправляти з Email', + 'default_notification_address' => 'Email для повідомлень за замовчуванням', + 'use_smtp_encryption' => 'Використовувати SMTP шифрування?', + 'none' => 'Ні', + 'ssl' => 'SSL', + 'tls' => 'TLS', + + 'failed_after' => 'Вважати збірку проваленою після', + '5_mins' => '5 хвилин', + '15_mins' => '15 хвилин', + '30_mins' => '30 хвилин', + '1_hour' => '1 година', + '3_hours' => '3 години', + + // Plugins + 'cannot_update_composer' => 'PHPCI не може оновити composer.json, оскільки він не є доступним для запису.', + 'x_has_been_removed' => '%s було видалено.', + 'x_has_been_added' => '%s був доданий до composer.json і буде встановлений, як тільки +ви виконаєте composer update.', + 'enabled_plugins' => 'Увімкнені плагіни', + 'provided_by_package' => 'Наданий пакетом', + 'installed_packages' => 'Встановлені пакети', + 'suggested_packages' => 'Запропоновані пакети', + 'title' => 'Заголовок', + 'description' => 'Опис', + 'version' => 'Версія', + 'install' => 'Встановити »', + 'remove' => 'Видалити »', + 'search_packagist_for_more' => 'Знайти більше пакетів на Packagist', + 'search' => 'Знайти »', + + // Installer + 'installation_url' => 'URL встановлення PHPCI', + 'db_host' => 'Хост бази даних', + 'db_name' => 'Назва бази даних', + 'db_user' => 'Ім’я користувача бази даних', + 'db_pass' => 'Пароль бази даних', + 'admin_name' => 'Ім’я адміністратора', + 'admin_pass' => 'Пароль адміністратора', + 'admin_email' => 'Email адреса адміністратора', + 'config_path' => 'Шлях до файла конфігурації', + 'install_phpci' => 'Встановити PHPCI', + 'welcome_to_phpci' => 'Ласкаво просимо до PHPCI', + 'please_answer' => 'Будь ласка, дайте відповідь на наступні питання:', + 'phpci_php_req' => 'PHPCI вимагає для роботи, принаймні, версію PHP 5.3.8.', + 'extension_required' => 'Необхідне розширення: %s', + 'function_required' => 'PHPCI необхідна можливість викликати функцію %s(). Вона відключена у php.ini?', + 'requirements_not_met' => 'Неможливо встановити PHPCI, оскільки не всі вимоги виконані. +Будь ласка, продивіться наявні помилки перед тим, як продовжити.', + 'must_be_valid_email' => 'Повинно бути коректною email адресою.', + 'must_be_valid_url' => 'Повинно бути коректним URL.', + 'enter_name' => 'Ім’я адміністратора: ', + 'enter_email' => 'Email адміністратора: ', + 'enter_password' => 'Пароль адміністратора: ', + 'enter_phpci_url' => 'URL адреса вашого PHPCI (наприклад, "http://phpci.local"): ', + + 'enter_db_host' => 'Будь ласка, введіть хост MySQL [localhost]: ', + 'enter_db_name' => 'Будь ласка, введить ім’я бази даних MySQL [phpci]: ', + 'enter_db_user' => 'Будь ласка, введить ім’я користувача MySQL [phpci]: ', + 'enter_db_pass' => 'Будь ласка, введить ваш пароль MySQL: ', + 'could_not_connect' => 'PHPCI не може підключитися до MySQL із наданими параметрами. Будь ласка, спробуйте ще раз.', + 'setting_up_db' => 'Налаштування вашої бази даних...', + 'user_created' => 'Аккаунт користувача створено!', + 'failed_to_create' => 'PHPCI не вдалося створити ваш аккаунт адміністратора.', + 'config_exists' => 'Файл конфігурації PHPCI вже існує та не є порожнім.', + 'update_instead' => 'Якщо ви збираєтесь оновити PHPCI, будь ласка, використовуйте команду phpci:update.', + + // Update + 'update_phpci' => 'Оновити базу даних для відображення змінених моделей.', + 'updating_phpci' => 'Оновлення бази даних PHPCI:', + 'not_installed' => 'Неможливо встановити PHPCI.', + 'install_instead' => 'Будь ласка, встановіть PHPCI через команду phpci:install.', + + // Poll Command + 'poll_github' => 'Зробити запит до GitHub для перевірки запуску збірки.', + 'no_token' => 'GitHub токен не знайдено', + 'finding_projects' => 'Пошук проектів для запиту', + 'found_n_projects' => 'Знайдено %d проектів', + 'last_commit_is' => 'Останній коміт на GitHub для %s - %s', + 'adding_new_build' => 'Останній коміт має відмінності із базою даних, створена нова збірка.', + 'finished_processing_builds' => 'Завершено обробку збірок.', + + // Create Admin + 'create_admin_user' => 'Створити аккаунт адміністратора', + 'incorrect_format' => 'Невірний формат', + + // Create Build Command + 'create_build_project' => 'Create a build for a project', + 'project_id_argument' => 'A project ID', + 'commit_id_option' => 'Commit ID to build', + 'branch_name_option' => 'Branch to build', + + // Run Command + 'run_all_pending' => 'Запустити всі PHPCI збірки, які очікують.', + 'finding_builds' => 'Пошук збірок для обробки', + 'found_n_builds' => 'Знайдено %d збірок', + 'skipping_build' => 'Збірка %d пропущена - Збірка проекта вже у процесі.', + 'marked_as_failed' => 'Збірка %d відмічена як невдала через перевищення ліміту часу.', + + // Builder + 'missing_phpci_yml' => 'Цей проект не містить файл phpci.yml або він є порожнім.', + 'build_success' => 'ЗБІРКА УСПІШНА', + 'build_failed' => 'ЗБІРКА НЕВДАЛА', + 'removing_build' => 'Видалення збірки.', + 'exception' => 'Виключення:', + 'could_not_create_working' => 'Не вдалося створити робочу копію.', + 'working_copy_created' => 'Робоча копія створена: %s', + 'looking_for_binary' => 'Пошук бінарного пакета: %s', + 'found_in_path' => 'Знайдено у %s: %s', + 'running_plugin' => 'ВИКОНУЄТЬСЯ ПЛАГІН: %s', + 'plugin_success' => 'ПЛАГІН: УСПІШНО', + 'plugin_failed' => 'ПЛАГІН: НЕВДАЛО', + 'plugin_missing' => 'Плагін не існує: %s', + 'tap_version' => 'TapParser підтримує тільки TAP версії 13', + 'tap_error' => 'Некоректний TAP-рядок, кількість тестів не співпадає із вказаними.', + + // Build Plugins: + 'no_tests_performed' => 'Жодних тестів не було запущено.', + 'could_not_find' => 'Неможливо знайти %s', + 'no_campfire_settings' => 'Не вказані параметри з’єднання для плагіна Campfire', + 'failed_to_wipe' => 'Не вдалося знищити існуючу директорію %s перед копіюванням', + 'passing_build' => 'Успішно збірка', + 'failing_build' => 'Невдала збірка', + 'log_output' => 'Вивід лога:', + 'n_emails_sent' => '%d листів відправлено.', + 'n_emails_failed' => '%d листів не вдалося відправити.', + 'unable_to_set_env' => 'Неможливо встановити змінну оточення', + 'tag_created' => 'Тег, створений PHPCI: %s', + 'x_built_at_x' => '%PROJECT_TITLE% зібрано у %BUILD_URI%', + 'hipchat_settings' => 'Будь ласка, вкажіть кімнату та "authToken" параметр для плагіна hipchat_notify', + 'irc_settings' => 'Ви повинні вказати сервер, кімнату та нік.', + 'invalid_command' => 'Невірна команда', + 'import_file_key' => 'Вираз імпорту повинен містити ключ \'file\'', + 'cannot_open_import' => 'Неможливо відкрити файл імпорту SQL: %s', + 'unable_to_execute' => 'Неможливо виконати файл SQL', + 'phar_internal_error' => 'Внутрішня помилка плагіну Phar', + 'build_file_missing' => 'Вказаний файл збірки не існує.', + 'property_file_missing' => 'Вказаний файл властивості не існує.', + 'could_not_process_report' => 'Неможливо обробити звіт, згенерований цією утилітою.', + 'shell_not_enabled' => 'Плагін shell не увімкнений. Будь ласка, увімкніть його через config.yml.' +); diff --git a/PHPCI/Logging/BuildDBLogHandler.php b/PHPCI/Logging/BuildDBLogHandler.php new file mode 100644 index 00000000..91664b25 --- /dev/null +++ b/PHPCI/Logging/BuildDBLogHandler.php @@ -0,0 +1,60 @@ +build = $build; + // We want to add to any existing saved log information. + $this->logValue = $build->getLog(); + } + + /** + * Write a log entry to the build log. + * @param array $record + */ + protected function write(array $record) + { + $message = (string)$record['message']; + $message = str_replace($this->build->currentBuildPath, '/', $message); + + $this->logValue .= $message . PHP_EOL; + $this->build->setLog($this->logValue); + + Factory::getStore('Build')->save($this->build); + } +} diff --git a/PHPCI/Logging/BuildLogger.php b/PHPCI/Logging/BuildLogger.php new file mode 100644 index 00000000..a68e9e6d --- /dev/null +++ b/PHPCI/Logging/BuildLogger.php @@ -0,0 +1,122 @@ +logger = $logger; + $this->build = $build; + } + + /** + * Add an entry to the build log. + * @param string|string[] $message + * @param string $level + * @param mixed[] $context + */ + public function log($message, $level = LogLevel::INFO, $context = array()) + { + // Skip if no logger has been loaded. + if (!$this->logger) { + return; + } + + if (!is_array($message)) { + $message = array($message); + } + + // The build is added to the context so the logger can use + // details from it if required. + $context['build'] = $this->build; + + foreach ($message as $item) { + $this->logger->log($level, $item, $context); + } + } + + /** + * Add a success-coloured message to the log. + * @param string + */ + public function logSuccess($message) + { + $this->log("\033[0;32m" . $message . "\033[0m"); + } + + /** + * Add a failure-coloured message to the log. + * @param string $message + * @param \Exception $exception The exception that caused the error. + */ + public function logFailure($message, \Exception $exception = null) + { + $context = array(); + + // The psr3 log interface stipulates that exceptions should be passed + // as the exception key in the context array. + if ($exception) { + $context['exception'] = $exception; + } + + $this->log( + "\033[0;31m" . $message . "\033[0m", + LogLevel::ERROR, + $context + ); + } + + /** + * Add a debug message to the log. + * @param string + */ + public function logDebug($message) + { + if (defined('PHPCI_DEBUG_MODE') && PHPCI_DEBUG_MODE) { + $this->log("\033[0;33m" . $message . "\033[0m"); + } + } + + /** + * Sets a logger instance on the object + * + * @param LoggerInterface $logger + * @return null + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } +} diff --git a/PHPCI/Logging/Handler.php b/PHPCI/Logging/Handler.php new file mode 100644 index 00000000..e22351da --- /dev/null +++ b/PHPCI/Logging/Handler.php @@ -0,0 +1,153 @@ + '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', + ); + + /** + * @var LoggerInterface + */ + protected $logger; + + /** + * @param LoggerInterface $logger + */ + public function __construct(LoggerInterface $logger = null) + { + $this->logger = $logger; + } + + /** + * Register a new log handler. + * @param LoggerInterface $logger + */ + public static function register(LoggerInterface $logger = null) + { + $handler = new static($logger); + + set_error_handler(array($handler, 'handleError')); + register_shutdown_function(array($handler, 'handleFatalError')); + + set_exception_handler(array($handler, 'handleException')); + } + + /** + * @param integer $level + * @param string $message + * @param string $file + * @param integer $line + * + * @throws \ErrorException + */ + public function handleError($level, $message, $file, $line) + { + if (error_reporting() & $level) { + $exception_level = isset($this->levels[$level]) ? $this->levels[$level] : $level; + + throw new \ErrorException( + sprintf('%s: %s in %s line %d', $exception_level, $message, $file, $line), + 0, + $level, + $file, + $line + ); + } + } + + /** + * @throws \ErrorException + */ + public function handleFatalError() + { + $fatal_error = error_get_last(); + + try { + if (($error = error_get_last()) !== null) { + $error = new \ErrorException( + sprintf( + '%s: %s in %s line %d', + $fatal_error['type'], + $fatal_error['message'], + $fatal_error['file'], + $fatal_error['line'] + ), + 0, + $fatal_error['type'], + $fatal_error['file'], + $fatal_error['line'] + ); + $this->log($error); + } + } catch (\Exception $e) { + $error = new \ErrorException( + sprintf( + '%s: %s in %s line %d', + $fatal_error['type'], + $fatal_error['message'], + $fatal_error['file'], + $fatal_error['line'] + ), + 0, + $fatal_error['type'], + $fatal_error['file'], + $fatal_error['line'] + ); + $this->log($error); + } + } + + /** + * @param \Exception $exception + */ + public function handleException(\Exception $exception) + { + $this->log($exception); + } + + /** + * Write to the build log. + * @param \Exception $exception + */ + protected function log(\Exception $exception) + { + if (null !== $this->logger) { + $message = sprintf( + '%s: %s (uncaught exception) at %s line %s', + get_class($exception), + $exception->getMessage(), + $exception->getFile(), + $exception->getLine() + ); + + $this->logger->error($message, array('exception' => $exception)); + } + } +} diff --git a/PHPCI/Logging/LoggedBuildContextTidier.php b/PHPCI/Logging/LoggedBuildContextTidier.php new file mode 100644 index 00000000..cfcfb8b8 --- /dev/null +++ b/PHPCI/Logging/LoggedBuildContextTidier.php @@ -0,0 +1,46 @@ +tidyLoggedBuildContext(func_get_arg(0)); + } + + /** + * Removes the build object from the logged record and adds the ID as + * this is more useful to display. + * + * @param array $logRecord + * @return array + */ + protected function tidyLoggedBuildContext(array $logRecord) + { + if (isset($logRecord['context']['build'])) { + $build = $logRecord['context']['build']; + if ($build instanceof Build) { + $logRecord['context']['buildID'] = $build->getId(); + unset($logRecord['context']['build']); + } + } + return $logRecord; + } +} diff --git a/PHPCI/Logging/LoggerConfig.php b/PHPCI/Logging/LoggerConfig.php new file mode 100644 index 00000000..fd929aeb --- /dev/null +++ b/PHPCI/Logging/LoggerConfig.php @@ -0,0 +1,97 @@ +config = $configArray; + } + + /** + * Returns an instance of Monolog with all configured handlers + * added. The Monolog instance will be given $name. + * @param $name + * @return Logger + */ + public function getFor($name) + { + if (isset($this->cache[$name])) { + return $this->cache[$name]; + } + + $handlers = $this->getHandlers(self::KEY_ALWAYS_LOADED); + if ($name !== self::KEY_ALWAYS_LOADED) { + $handlers = array_merge($handlers, $this->getHandlers($name)); + } + + $logger = new Logger($name, $handlers); + ErrorHandler::register($logger); + $this->cache[$name] = $logger; + + return $logger; + } + + /** + * Return an array of enabled log handlers. + * @param $key + * @return array|mixed + */ + protected function getHandlers($key) + { + $handlers = array(); + + // They key is expected to either be an array or + // a callable function that returns an array + if (isset($this->config[$key])) { + if (is_callable($this->config[$key])) { + $handlers = call_user_func($this->config[$key]); + } elseif (is_array($this->config[$key])) { + $handlers = $this->config[$key]; + } + } + return $handlers; + } +} diff --git a/PHPCI/Logging/OutputLogHandler.php b/PHPCI/Logging/OutputLogHandler.php new file mode 100644 index 00000000..4b4c81a4 --- /dev/null +++ b/PHPCI/Logging/OutputLogHandler.php @@ -0,0 +1,49 @@ +output = $output; + } + + /** + * Write a log entry to the terminal. + * @param array $record + */ + protected function write(array $record) + { + $this->output->writeln((string)$record['formatted']); + } +} diff --git a/PHPCI/Migrations/20140513143726_initial_migration.php b/PHPCI/Migrations/20140513143726_initial_migration.php new file mode 100644 index 00000000..5fca43bf --- /dev/null +++ b/PHPCI/Migrations/20140513143726_initial_migration.php @@ -0,0 +1,234 @@ +createBuildTable(); + $this->createBuildMetaTable(); + $this->createProjectTable(); + $this->createUserTable(); + + // Set up foreign keys: + $build = $this->table('build'); + + if (!$build->hasForeignKey('project_id')) { + $build->addForeignKey('project_id', 'project', 'id', array('delete'=> 'CASCADE', 'update' => 'CASCADE')); + } + + $build->save(); + + $buildMeta = $this->table('build_meta'); + + if (!$buildMeta->hasForeignKey('build_id')) { + $buildMeta->addForeignKey('build_id', 'build', 'id', array('delete'=> 'CASCADE', 'update' => 'CASCADE')); + } + + if (!$buildMeta->hasForeignKey('project_id')) { + $buildMeta->addForeignKey('project_id', 'project', 'id', array('delete'=> 'CASCADE', 'update' => 'CASCADE')); + } + + $buildMeta->save(); + } + + /** + * Migrate Down. + */ + public function down() + { + + } + + protected function createBuildTable() + { + $table = $this->table('build'); + + if (!$this->hasTable('build')) { + $table->create(); + } + + if (!$table->hasColumn('project_id')) { + $table->addColumn('project_id', 'integer'); + } + + if (!$table->hasColumn('commit_id')) { + $table->addColumn('commit_id', 'string', array('limit' => 50)); + } + + if (!$table->hasColumn('status')) { + $table->addColumn('status', 'integer', array('limit' => 4)); + } + + if (!$table->hasColumn('log')) { + $table->addColumn('log', 'text'); + } + + if (!$table->hasColumn('branch')) { + $table->addColumn('branch', 'string', array('limit' => 50)); + } + + if (!$table->hasColumn('created')) { + $table->addColumn('created', 'datetime'); + } + + if (!$table->hasColumn('started')) { + $table->addColumn('started', 'datetime'); + } + + if (!$table->hasColumn('finished')) { + $table->addColumn('finished', 'datetime'); + } + + if (!$table->hasColumn('committer_email')) { + $table->addColumn('committer_email', 'string', array('limit' => 250)); + } + + if (!$table->hasColumn('commit_message')) { + $table->addColumn('commit_message', 'text'); + } + + if (!$table->hasColumn('extra')) { + $table->addColumn('extra', 'text'); + } + + if ($table->hasColumn('plugins')) { + $table->removeColumn('plugins'); + } + + if (!$table->hasIndex(array('project_id'))) { + $table->addIndex(array('project_id')); + } + + if (!$table->hasIndex(array('status'))) { + $table->addIndex(array('status')); + } + + $table->save(); + } + + protected function createBuildMetaTable() + { + $table = $this->table('build_meta'); + + if (!$this->hasTable('build_meta')) { + $table->create(); + } + + if (!$table->hasColumn('project_id')) { + $table->addColumn('project_id', 'integer'); + } + + if (!$table->hasColumn('build_id')) { + $table->addColumn('build_id', 'integer'); + } + + if (!$table->hasColumn('meta_key')) { + $table->addColumn('meta_key', 'string', array('limit' => 250)); + } + + if (!$table->hasColumn('meta_value')) { + $table->addColumn('meta_value', 'text'); + } + + if (!$table->hasIndex(array('build_id', 'meta_key'))) { + $table->addIndex(array('build_id', 'meta_key')); + } + + $table->save(); + } + + protected function createProjectTable() + { + $table = $this->table('project'); + + if (!$this->hasTable('project')) { + $table->create(); + } + + if (!$table->hasColumn('title')) { + $table->addColumn('title', 'string', array('limit' => 250)); + } + + if (!$table->hasColumn('reference')) { + $table->addColumn('reference', 'string', array('limit' => 250)); + } + + if (!$table->hasColumn('git_key')) { + $table->addColumn('git_key', 'text'); + } + + if (!$table->hasColumn('public_key')) { + $table->addColumn('public_key', 'text'); + } + + if (!$table->hasColumn('type')) { + $table->addColumn('type', 'string', array('limit' => 50)); + } + + if (!$table->hasColumn('access_information')) { + $table->addColumn('access_information', 'string', array('limit' => 250)); + } + + if (!$table->hasColumn('last_commit')) { + $table->addColumn('last_commit', 'string', array('limit' => 250)); + } + + if (!$table->hasColumn('build_config')) { + $table->addColumn('build_config', 'text'); + } + + if (!$table->hasColumn('allow_public_status')) { + $table->addColumn('allow_public_status', 'integer'); + } + + if ($table->hasColumn('token')) { + $table->removeColumn('token'); + } + + if (!$table->hasIndex(array('title'))) { + $table->addIndex(array('title')); + } + + $table->save(); + } + + protected function createUserTable() + { + $table = $this->table('user'); + + if (!$this->hasTable('user')) { + $table->create(); + } + + if (!$table->hasColumn('email')) { + $table->addColumn('email', 'string', array('limit' => 250)); + } + + if (!$table->hasColumn('hash')) { + $table->addColumn('hash', 'string', array('limit' => 250)); + } + + if (!$table->hasColumn('name')) { + $table->addColumn('name', 'string', array('limit' => 250)); + } + + if (!$table->hasColumn('is_admin')) { + $table->addColumn('is_admin', 'integer'); + } + + if (!$table->hasIndex(array('email'))) { + $table->addIndex(array('email')); + } + + $table->save(); + } +} diff --git a/PHPCI/Migrations/20140513153133_change_build_keys_migration.php b/PHPCI/Migrations/20140513153133_change_build_keys_migration.php new file mode 100644 index 00000000..7fd253cc --- /dev/null +++ b/PHPCI/Migrations/20140513153133_change_build_keys_migration.php @@ -0,0 +1,26 @@ +table('project'); + $project->renameColumn('git_key', 'ssh_private_key'); + $project->renameColumn('public_key', 'ssh_public_key'); + } + + /** + * Migrate Down. + */ + public function down() + { + $project = $this->table('project'); + $project->renameColumn('ssh_private_key', 'git_key'); + $project->renameColumn('ssh_public_key', 'public_key'); + } +} diff --git a/PHPCI/Migrations/20140611170618_choose_branch.php b/PHPCI/Migrations/20140611170618_choose_branch.php new file mode 100644 index 00000000..26b4ed4c --- /dev/null +++ b/PHPCI/Migrations/20140611170618_choose_branch.php @@ -0,0 +1,40 @@ +table('project'); + $project->addColumn('branch', 'string', array( + 'after' => 'reference', + 'limit' => 250 + ))->save(); + } + + /** + * Migrate Down. + */ + public function down() + { + $project = $this->table('project'); + $project->removeColumn('branch')->save(); + } +} diff --git a/PHPCI/Migrations/20140730143702_fix_database_columns.php b/PHPCI/Migrations/20140730143702_fix_database_columns.php new file mode 100644 index 00000000..a1ac2493 --- /dev/null +++ b/PHPCI/Migrations/20140730143702_fix_database_columns.php @@ -0,0 +1,61 @@ +getAdapter(); + + if ($dbAdapter instanceof \Phinx\Db\Adapter\PdoAdapter) { + $pdo = $dbAdapter->getConnection(); + $pdo->exec('SET foreign_key_checks = 0'); + } + + $build = $this->table('build'); + $build->changeColumn('project_id', 'integer', array('null' => false)); + $build->changeColumn('commit_id', 'string', array('limit' => 50, 'null' => false)); + $build->changeColumn('status', 'integer', array('null' => false)); + $build->changeColumn('log', 'text', array('null' => true)); + $build->changeColumn('branch', 'string', array('limit' => 50, 'null' => false, 'default' => 'master')); + $build->changeColumn('created', 'datetime', array('null' => true)); + $build->changeColumn('started', 'datetime', array('null' => true)); + $build->changeColumn('finished', 'datetime', array('null' => true)); + $build->changeColumn('committer_email', 'string', array('limit' => 512, 'null' => true)); + $build->changeColumn('commit_message', 'text', array('null' => true)); + $build->changeColumn('extra', 'text', array('null' => true)); + + $buildMeta = $this->table('build_meta'); + $buildMeta->changeColumn('project_id', 'integer', array('null' => false)); + $buildMeta->changeColumn('build_id', 'integer', array('null' => false)); + $buildMeta->changeColumn('meta_key', 'string', array('limit' => 250, 'null' => false)); + $buildMeta->changeColumn('meta_value', 'text', array('null' => false)); + + $project = $this->table('project'); + $project->changeColumn('title', 'string', array('limit' => 250, 'null' => false)); + $project->changeColumn('reference', 'string', array('limit' => 250, 'null' => false)); + $project->changeColumn('branch', 'string', array('limit' => 50, 'null' => false, 'default' => 'master')); + $project->changeColumn('ssh_private_key', 'text', array('null' => true, 'default' => null)); + $project->changeColumn('ssh_public_key', 'text', array('null' => true, 'default' => null)); + $project->changeColumn('type', 'string', array('limit' => 50, 'null' => false)); + $project->changeColumn('access_information', 'string', array('limit' => 250, 'null' => true, 'default' => null)); + $project->changeColumn('last_commit', 'string', array('limit' => 250, 'null' => true, 'default' => null)); + $project->changeColumn('ssh_public_key', 'text', array('null' => true, 'default' => null)); + $project->changeColumn('allow_public_status', 'integer', array('null' => false, 'default' => 0)); + + $user = $this->table('user'); + $user->changeColumn('email', 'string', array('limit' => 250, 'null' => false)); + $user->changeColumn('hash', 'string', array('limit' => 250, 'null' => false)); + $user->changeColumn('is_admin', 'integer', array('null' => false, 'default' => 0)); + $user->changeColumn('name', 'string', array('limit' => 250, 'null' => false)); + + if ($dbAdapter instanceof \Phinx\Db\Adapter\PdoAdapter) { + $pdo = $dbAdapter->getConnection(); + $pdo->exec('SET foreign_key_checks = 1'); + } + } +} diff --git a/PHPCI/Migrations/20150131075425_archive_project.php b/PHPCI/Migrations/20150131075425_archive_project.php new file mode 100644 index 00000000..796fd48b --- /dev/null +++ b/PHPCI/Migrations/20150131075425_archive_project.php @@ -0,0 +1,26 @@ +table('project'); + $project->addColumn('archived', 'boolean'); + $project->save(); + } + + /** + * Migrate Down. + */ + public function down() + { + $project = $this->table('project'); + $project->removeColumn('archived'); + $project->save(); + } +} \ No newline at end of file diff --git a/PHPCI/Migrations/20150203105015_fix_column_types.php b/PHPCI/Migrations/20150203105015_fix_column_types.php new file mode 100644 index 00000000..53db2ad6 --- /dev/null +++ b/PHPCI/Migrations/20150203105015_fix_column_types.php @@ -0,0 +1,27 @@ +table('build'); + $build->changeColumn('log', 'text', array( + 'null' => true, + 'limit' => MysqlAdapter::TEXT_MEDIUM, + )); + + // Update the build meta value column to MEDIUMTEXT: + $buildMeta = $this->table('build_meta'); + $buildMeta->changeColumn('meta_value', 'text', array( + 'null' => false, + 'limit' => MysqlAdapter::TEXT_MEDIUM, + )); + } +} diff --git a/PHPCI/Migrations/20150324174958_unique_email_and_name_user_fields.php b/PHPCI/Migrations/20150324174958_unique_email_and_name_user_fields.php new file mode 100644 index 00000000..f8d24ba0 --- /dev/null +++ b/PHPCI/Migrations/20150324174958_unique_email_and_name_user_fields.php @@ -0,0 +1,30 @@ +table('user'); + $user_table + ->addIndex('email', array('unique' => true)) + ->addIndex('name', array('unique' => true)) + ->save(); + } + + /** + * Migrate Down. + */ + public function down() + { + $user_table = $this->table('user'); + $user_table + ->removeIndex('email', array('unique' => true)) + ->removeIndex('name', array('unique' => true)) + ->save(); + } +} diff --git a/PHPCI/Migrations/20151008140800_add_project_groups.php b/PHPCI/Migrations/20151008140800_add_project_groups.php new file mode 100644 index 00000000..f6035014 --- /dev/null +++ b/PHPCI/Migrations/20151008140800_add_project_groups.php @@ -0,0 +1,29 @@ +table('project_group'); + $table->addColumn('title', 'string', array('limit' => 100, 'null' => false)); + $table->save(); + + $group = new \PHPCI\Model\ProjectGroup(); + $group->setTitle('Projects'); + + /** @var \PHPCI\Model\ProjectGroup $group */ + $group = \b8\Store\Factory::getStore('ProjectGroup')->save($group); + + $table = $this->table('project'); + $table->addColumn('group_id', 'integer', array( + 'signed' => true, + 'null' => false, + 'default' => $group->getId(), + )); + + $table->addForeignKey('group_id', 'project_group', 'id', array('delete'=> 'RESTRICT', 'update' => 'CASCADE')); + $table->save(); + } +} diff --git a/PHPCI/Migrations/20151009100610_remove_unique_name_index.php b/PHPCI/Migrations/20151009100610_remove_unique_name_index.php new file mode 100644 index 00000000..283dfbc0 --- /dev/null +++ b/PHPCI/Migrations/20151009100610_remove_unique_name_index.php @@ -0,0 +1,40 @@ +table('user'); + + if ($user->hasIndex('name', array('unique' => true))) { + $user->removeIndex('name', array('unique' => true)); + $user->save(); + } + + $user->addIndex('name', array('unique' => false)); + $user->save(); + } +} diff --git a/PHPCI/Migrations/20151014091859_errors_table.php b/PHPCI/Migrations/20151014091859_errors_table.php new file mode 100644 index 00000000..a064f6e5 --- /dev/null +++ b/PHPCI/Migrations/20151014091859_errors_table.php @@ -0,0 +1,24 @@ +table('build_error'); + $table->addColumn('build_id', 'integer', array('signed' => true)); + $table->addColumn('plugin', 'string', array('limit' => 100)); + $table->addColumn('file', 'string', array('limit' => 250, 'null' => true)); + $table->addColumn('line_start', 'integer', array('signed' => false, 'null' => true)); + $table->addColumn('line_end', 'integer', array('signed' => false, 'null' => true)); + $table->addColumn('severity', 'integer', array('signed' => false, 'limit' => MysqlAdapter::INT_TINY)); + $table->addColumn('message', 'string', array('limit' => 250)); + $table->addColumn('created_date', 'datetime'); + $table->addIndex(array('build_id', 'created_date'), array('unique' => false)); + $table->addForeignKey('build_id', 'build', 'id', array('delete'=> 'CASCADE', 'update' => 'CASCADE')); + $table->save(); + + } +} diff --git a/PHPCI/Migrations/20151015124825_convert_errors.php b/PHPCI/Migrations/20151015124825_convert_errors.php new file mode 100644 index 00000000..3622bf14 --- /dev/null +++ b/PHPCI/Migrations/20151015124825_convert_errors.php @@ -0,0 +1,183 @@ +metaStore = \b8\Store\Factory::getStore('BuildMeta'); + $this->errorStore = \b8\Store\Factory::getStore('BuildError'); + + while ($count == 100) { + $data = $this->metaStore->getErrorsForUpgrade(100); + $count = count($data); + + /** @var \PHPCI\Model\BuildMeta $meta */ + foreach ($data as $meta) { + try { + switch ($meta->getMetaKey()) { + case 'phpmd-data': + $this->processPhpMdMeta($meta); + break; + + case 'phpcs-data': + $this->processPhpCsMeta($meta); + break; + + case 'phpdoccheck-data': + $this->processPhpDocCheckMeta($meta); + break; + + case 'phpcpd-data': + $this->processPhpCpdMeta($meta); + break; + + case 'technicaldebt-data': + $this->processTechnicalDebtMeta($meta); + break; + } + } catch (\Exception $ex) {} + + $this->metaStore->delete($meta); + } + } + } + + protected function processPhpMdMeta(BuildMeta $meta) + { + $data = json_decode($meta->getMetaValue(), true); + + if (is_array($data) && count($data)) { + foreach ($data as $error) { + $buildError = new BuildError(); + $buildError->setBuildId($meta->getBuildId()); + $buildError->setPlugin('php_mess_detector'); + $buildError->setCreatedDate(new \DateTime()); + $buildError->setFile($error['file']); + $buildError->setLineStart($error['line_start']); + $buildError->setLineEnd($error['line_end']); + $buildError->setSeverity(BuildError::SEVERITY_HIGH); + $buildError->setMessage($error['message']); + + $this->errorStore->save($buildError); + } + } + } + + protected function processPhpCsMeta(BuildMeta $meta) + { + $data = json_decode($meta->getMetaValue(), true); + + if (is_array($data) && count($data)) { + foreach ($data as $error) { + $buildError = new BuildError(); + $buildError->setBuildId($meta->getBuildId()); + $buildError->setPlugin('php_code_sniffer'); + $buildError->setCreatedDate(new \DateTime()); + $buildError->setFile($error['file']); + $buildError->setLineStart($error['line']); + $buildError->setLineEnd($error['line']); + $buildError->setMessage($error['message']); + + switch ($error['type']) { + case 'ERROR': + $buildError->setSeverity(BuildError::SEVERITY_HIGH); + break; + + case 'WARNING': + $buildError->setSeverity(BuildError::SEVERITY_LOW); + break; + } + + $this->errorStore->save($buildError); + } + } + } + + protected function processPhpDocCheckMeta(BuildMeta $meta) + { + $data = json_decode($meta->getMetaValue(), true); + + if (is_array($data) && count($data)) { + foreach ($data as $error) { + $buildError = new BuildError(); + $buildError->setBuildId($meta->getBuildId()); + $buildError->setPlugin('php_docblock_checker'); + $buildError->setCreatedDate(new \DateTime()); + $buildError->setFile($error['file']); + $buildError->setLineStart($error['line']); + $buildError->setLineEnd($error['line']); + + switch ($error['type']) { + case 'method': + $buildError->setMessage($error['class'] . '::' . $error['method'] . ' is missing a docblock.'); + $buildError->setSeverity(BuildError::SEVERITY_NORMAL); + break; + + case 'class': + $buildError->setMessage('Class ' . $error['class'] . ' is missing a docblock.'); + $buildError->setSeverity(BuildError::SEVERITY_LOW); + break; + } + + $this->errorStore->save($buildError); + } + } + } + + protected function processPhpCpdMeta(BuildMeta $meta) + { + $data = json_decode($meta->getMetaValue(), true); + + if (is_array($data) && count($data)) { + foreach ($data as $error) { + $buildError = new BuildError(); + $buildError->setBuildId($meta->getBuildId()); + $buildError->setPlugin('php_cpd'); + $buildError->setCreatedDate(new \DateTime()); + $buildError->setFile($error['file']); + $buildError->setLineStart($error['line_start']); + $buildError->setLineEnd($error['line_end']); + $buildError->setSeverity(BuildError::SEVERITY_NORMAL); + $buildError->setMessage('Copy and paste detected.'); + + $this->errorStore->save($buildError); + } + } + } + + protected function processTechnicalDebtMeta(BuildMeta $meta) + { + $data = json_decode($meta->getMetaValue(), true); + + if (is_array($data) && count($data)) { + foreach ($data as $error) { + $buildError = new BuildError(); + $buildError->setBuildId($meta->getBuildId()); + $buildError->setPlugin('technical_debt'); + $buildError->setCreatedDate(new \DateTime()); + $buildError->setFile($error['file']); + $buildError->setLineStart($error['line']); + $buildError->setSeverity(BuildError::SEVERITY_NORMAL); + $buildError->setMessage($error['message']); + + $this->errorStore->save($buildError); + } + } + } +} diff --git a/PHPCI/Migrations/20160623100223_project_table_defaults.php b/PHPCI/Migrations/20160623100223_project_table_defaults.php new file mode 100644 index 00000000..079db327 --- /dev/null +++ b/PHPCI/Migrations/20160623100223_project_table_defaults.php @@ -0,0 +1,18 @@ +table('project') + ->changeColumn('build_config', MysqlAdapter::PHINX_TYPE_TEXT, array('null' => true)) + ->changeColumn('archived', MysqlAdapter::PHINX_TYPE_INTEGER, array( + 'length' => MysqlAdapter::INT_TINY, + 'default' => 0, + )) + ->save(); + } +} diff --git a/PHPCI/Model.php b/PHPCI/Model.php new file mode 100644 index 00000000..5d911533 --- /dev/null +++ b/PHPCI/Model.php @@ -0,0 +1,19 @@ + null, 'started' => null, 'finished' => null, - 'plugins' => null, 'committer_email' => null, + 'commit_message' => null, + 'extra' => null, ); /** @@ -60,8 +61,9 @@ class BuildBase extends Model 'created' => 'getCreated', 'started' => 'getStarted', 'finished' => 'getFinished', - 'plugins' => 'getPlugins', 'committer_email' => 'getCommitterEmail', + 'commit_message' => 'getCommitMessage', + 'extra' => 'getExtra', // Foreign key getters: 'Project' => 'getProject', @@ -81,8 +83,9 @@ class BuildBase extends Model 'created' => 'setCreated', 'started' => 'setStarted', 'finished' => 'setFinished', - 'plugins' => 'setPlugins', 'committer_email' => 'setCommitterEmail', + 'commit_message' => 'setCommitMessage', + 'extra' => 'setExtra', // Foreign key setters: 'Project' => 'setProject', @@ -107,15 +110,15 @@ class BuildBase extends Model 'commit_id' => array( 'type' => 'varchar', 'length' => 50, - 'nullable' => true, 'default' => null, ), 'status' => array( - 'type' => 'tinyint', - 'length' => 4, + 'type' => 'int', + 'length' => 11, + 'default' => null, ), 'log' => array( - 'type' => 'longtext', + 'type' => 'mediumtext', 'nullable' => true, 'default' => null, ), @@ -139,14 +142,19 @@ class BuildBase extends Model 'nullable' => true, 'default' => null, ), - 'plugins' => array( + 'committer_email' => array( + 'type' => 'varchar', + 'length' => 512, + 'nullable' => true, + 'default' => null, + ), + 'commit_message' => array( 'type' => 'text', 'nullable' => true, 'default' => null, ), - 'committer_email' => array( - 'type' => 'varchar', - 'length' => 512, + 'extra' => array( + 'type' => 'text', 'nullable' => true, 'default' => null, ), @@ -294,18 +302,6 @@ class BuildBase extends Model return $rtn; } - /** - * Get the value of Plugins / plugins. - * - * @return string - */ - public function getPlugins() - { - $rtn = $this->data['plugins']; - - return $rtn; - } - /** * Get the value of CommitterEmail / committer_email. * @@ -318,6 +314,30 @@ class BuildBase extends Model return $rtn; } + /** + * Get the value of CommitMessage / commit_message. + * + * @return string + */ + public function getCommitMessage() + { + $rtn = $this->data['commit_message']; + + return $rtn; + } + + /** + * Get the value of Extra / extra. + * + * @return string + */ + public function getExtra() + { + $rtn = $this->data['extra']; + + return $rtn; + } + /** * Set the value of Id / id. * @@ -361,10 +381,12 @@ class BuildBase extends Model /** * Set the value of CommitId / commit_id. * + * Must not be null. * @param $value string */ public function setCommitId($value) { + $this->_validateNotNull('CommitId', $value); $this->_validateString('CommitId', $value); if ($this->data['commit_id'] === $value) { @@ -488,24 +510,6 @@ class BuildBase extends Model $this->_setModified('finished'); } - /** - * Set the value of Plugins / plugins. - * - * @param $value string - */ - public function setPlugins($value) - { - $this->_validateString('Plugins', $value); - - if ($this->data['plugins'] === $value) { - return; - } - - $this->data['plugins'] = $value; - - $this->_setModified('plugins'); - } - /** * Set the value of CommitterEmail / committer_email. * @@ -524,6 +528,42 @@ class BuildBase extends Model $this->_setModified('committer_email'); } + /** + * Set the value of CommitMessage / commit_message. + * + * @param $value string + */ + public function setCommitMessage($value) + { + $this->_validateString('CommitMessage', $value); + + if ($this->data['commit_message'] === $value) { + return; + } + + $this->data['commit_message'] = $value; + + $this->_setModified('commit_message'); + } + + /** + * Set the value of Extra / extra. + * + * @param $value string + */ + public function setExtra($value) + { + $this->_validateString('Extra', $value); + + if ($this->data['extra'] === $value) { + return; + } + + $this->data['extra'] = $value; + + $this->_setModified('extra'); + } + /** * Get the Project model for this Build by Id. * @@ -543,7 +583,7 @@ class BuildBase extends Model $rtn = $this->cache->get($cacheKey, null); if (empty($rtn)) { - $rtn = Factory::getStore('Project')->getById($key); + $rtn = Factory::getStore('Project', 'PHPCI')->getById($key); $this->cache->set($cacheKey, $rtn); } @@ -581,6 +621,18 @@ class BuildBase extends Model return $this->setProjectId($value->getId()); } + /** + * Get BuildError models by BuildId for this Build. + * + * @uses \PHPCI\Store\BuildErrorStore::getByBuildId() + * @uses \PHPCI\Model\BuildError + * @return \PHPCI\Model\BuildError[] + */ + public function getBuildBuildErrors() + { + return Factory::getStore('BuildError', 'PHPCI')->getByBuildId($this->getId()); + } + /** * Get BuildMeta models by BuildId for this Build. * @@ -590,6 +642,6 @@ class BuildBase extends Model */ public function getBuildBuildMetas() { - return Factory::getStore('BuildMeta')->getByBuildId($this->getId()); + return Factory::getStore('BuildMeta', 'PHPCI')->getByBuildId($this->getId()); } } diff --git a/PHPCI/Model/Base/BuildErrorBase.php b/PHPCI/Model/Base/BuildErrorBase.php new file mode 100644 index 00000000..6364bb29 --- /dev/null +++ b/PHPCI/Model/Base/BuildErrorBase.php @@ -0,0 +1,503 @@ + null, + 'build_id' => null, + 'plugin' => null, + 'file' => null, + 'line_start' => null, + 'line_end' => null, + 'severity' => null, + 'message' => null, + 'created_date' => null, + ); + + /** + * @var array + */ + protected $getters = array( + // Direct property getters: + 'id' => 'getId', + 'build_id' => 'getBuildId', + 'plugin' => 'getPlugin', + 'file' => 'getFile', + 'line_start' => 'getLineStart', + 'line_end' => 'getLineEnd', + 'severity' => 'getSeverity', + 'message' => 'getMessage', + 'created_date' => 'getCreatedDate', + + // Foreign key getters: + 'Build' => 'getBuild', + ); + + /** + * @var array + */ + protected $setters = array( + // Direct property setters: + 'id' => 'setId', + 'build_id' => 'setBuildId', + 'plugin' => 'setPlugin', + 'file' => 'setFile', + 'line_start' => 'setLineStart', + 'line_end' => 'setLineEnd', + 'severity' => 'setSeverity', + 'message' => 'setMessage', + 'created_date' => 'setCreatedDate', + + // Foreign key setters: + 'Build' => 'setBuild', + ); + + /** + * @var array + */ + public $columns = array( + 'id' => array( + 'type' => 'int', + 'length' => 11, + 'primary_key' => true, + 'auto_increment' => true, + 'default' => null, + ), + 'build_id' => array( + 'type' => 'int', + 'length' => 11, + 'default' => null, + ), + 'plugin' => array( + 'type' => 'varchar', + 'length' => 100, + 'default' => null, + ), + 'file' => array( + 'type' => 'varchar', + 'length' => 250, + 'nullable' => true, + 'default' => null, + ), + 'line_start' => array( + 'type' => 'int', + 'length' => 11, + 'nullable' => true, + 'default' => null, + ), + 'line_end' => array( + 'type' => 'int', + 'length' => 11, + 'nullable' => true, + 'default' => null, + ), + 'severity' => array( + 'type' => 'tinyint', + 'length' => 3, + 'default' => null, + ), + 'message' => array( + 'type' => 'varchar', + 'length' => 250, + 'default' => null, + ), + 'created_date' => array( + 'type' => 'datetime', + 'default' => null, + ), + ); + + /** + * @var array + */ + public $indexes = array( + 'PRIMARY' => array('unique' => true, 'columns' => 'id'), + 'build_id' => array('columns' => 'build_id, created_date'), + ); + + /** + * @var array + */ + public $foreignKeys = array( + 'build_error_ibfk_1' => array( + 'local_col' => 'build_id', + 'update' => 'CASCADE', + 'delete' => 'CASCADE', + 'table' => 'build', + 'col' => 'id' + ), + ); + + /** + * Get the value of Id / id. + * + * @return int + */ + public function getId() + { + $rtn = $this->data['id']; + + return $rtn; + } + + /** + * Get the value of BuildId / build_id. + * + * @return int + */ + public function getBuildId() + { + $rtn = $this->data['build_id']; + + return $rtn; + } + + /** + * Get the value of Plugin / plugin. + * + * @return string + */ + public function getPlugin() + { + $rtn = $this->data['plugin']; + + return $rtn; + } + + /** + * Get the value of File / file. + * + * @return string + */ + public function getFile() + { + $rtn = $this->data['file']; + + return $rtn; + } + + /** + * Get the value of LineStart / line_start. + * + * @return int + */ + public function getLineStart() + { + $rtn = $this->data['line_start']; + + return $rtn; + } + + /** + * Get the value of LineEnd / line_end. + * + * @return int + */ + public function getLineEnd() + { + $rtn = $this->data['line_end']; + + return $rtn; + } + + /** + * Get the value of Severity / severity. + * + * @return int + */ + public function getSeverity() + { + $rtn = $this->data['severity']; + + return $rtn; + } + + /** + * Get the value of Message / message. + * + * @return string + */ + public function getMessage() + { + $rtn = $this->data['message']; + + return $rtn; + } + + /** + * Get the value of CreatedDate / created_date. + * + * @return \DateTime + */ + public function getCreatedDate() + { + $rtn = $this->data['created_date']; + + if (!empty($rtn)) { + $rtn = new \DateTime($rtn); + } + + return $rtn; + } + + /** + * Set the value of Id / id. + * + * Must not be null. + * @param $value int + */ + public function setId($value) + { + $this->_validateNotNull('Id', $value); + $this->_validateInt('Id', $value); + + if ($this->data['id'] === $value) { + return; + } + + $this->data['id'] = $value; + + $this->_setModified('id'); + } + + /** + * Set the value of BuildId / build_id. + * + * Must not be null. + * @param $value int + */ + public function setBuildId($value) + { + $this->_validateNotNull('BuildId', $value); + $this->_validateInt('BuildId', $value); + + if ($this->data['build_id'] === $value) { + return; + } + + $this->data['build_id'] = $value; + + $this->_setModified('build_id'); + } + + /** + * Set the value of Plugin / plugin. + * + * Must not be null. + * @param $value string + */ + public function setPlugin($value) + { + $this->_validateNotNull('Plugin', $value); + $this->_validateString('Plugin', $value); + + if ($this->data['plugin'] === $value) { + return; + } + + $this->data['plugin'] = $value; + + $this->_setModified('plugin'); + } + + /** + * Set the value of File / file. + * + * @param $value string + */ + public function setFile($value) + { + $this->_validateString('File', $value); + + if ($this->data['file'] === $value) { + return; + } + + $this->data['file'] = $value; + + $this->_setModified('file'); + } + + /** + * Set the value of LineStart / line_start. + * + * @param $value int + */ + public function setLineStart($value) + { + $this->_validateInt('LineStart', $value); + + if ($this->data['line_start'] === $value) { + return; + } + + $this->data['line_start'] = $value; + + $this->_setModified('line_start'); + } + + /** + * Set the value of LineEnd / line_end. + * + * @param $value int + */ + public function setLineEnd($value) + { + $this->_validateInt('LineEnd', $value); + + if ($this->data['line_end'] === $value) { + return; + } + + $this->data['line_end'] = $value; + + $this->_setModified('line_end'); + } + + /** + * Set the value of Severity / severity. + * + * Must not be null. + * @param $value int + */ + public function setSeverity($value) + { + $this->_validateNotNull('Severity', $value); + $this->_validateInt('Severity', $value); + + if ($this->data['severity'] === $value) { + return; + } + + $this->data['severity'] = $value; + + $this->_setModified('severity'); + } + + /** + * Set the value of Message / message. + * + * Must not be null. + * @param $value string + */ + public function setMessage($value) + { + $this->_validateNotNull('Message', $value); + $this->_validateString('Message', $value); + + if ($this->data['message'] === $value) { + return; + } + + $this->data['message'] = $value; + + $this->_setModified('message'); + } + + /** + * Set the value of CreatedDate / created_date. + * + * Must not be null. + * @param $value \DateTime + */ + public function setCreatedDate($value) + { + $this->_validateNotNull('CreatedDate', $value); + $this->_validateDate('CreatedDate', $value); + + if ($this->data['created_date'] === $value) { + return; + } + + $this->data['created_date'] = $value; + + $this->_setModified('created_date'); + } + + /** + * Get the Build model for this BuildError by Id. + * + * @uses \PHPCI\Store\BuildStore::getById() + * @uses \PHPCI\Model\Build + * @return \PHPCI\Model\Build + */ + public function getBuild() + { + $key = $this->getBuildId(); + + if (empty($key)) { + return null; + } + + $cacheKey = 'Cache.Build.' . $key; + $rtn = $this->cache->get($cacheKey, null); + + if (empty($rtn)) { + $rtn = Factory::getStore('Build', 'PHPCI')->getById($key); + $this->cache->set($cacheKey, $rtn); + } + + return $rtn; + } + + /** + * Set Build - Accepts an ID, an array representing a Build or a Build model. + * + * @param $value mixed + */ + public function setBuild($value) + { + // Is this an instance of Build? + if ($value instanceof \PHPCI\Model\Build) { + return $this->setBuildObject($value); + } + + // Is this an array representing a Build item? + if (is_array($value) && !empty($value['id'])) { + return $this->setBuildId($value['id']); + } + + // Is this a scalar value representing the ID of this foreign key? + return $this->setBuildId($value); + } + + /** + * Set Build - Accepts a Build model. + * + * @param $value \PHPCI\Model\Build + */ + public function setBuildObject(\PHPCI\Model\Build $value) + { + return $this->setBuildId($value->getId()); + } +} diff --git a/PHPCI/Model/Base/BuildMetaBase.php b/PHPCI/Model/Base/BuildMetaBase.php index 9ef0f457..dc018819 100644 --- a/PHPCI/Model/Base/BuildMetaBase.php +++ b/PHPCI/Model/Base/BuildMetaBase.php @@ -6,7 +6,7 @@ namespace PHPCI\Model\Base; -use b8\Model; +use PHPCI\Model; use b8\Store\Factory; /** @@ -52,6 +52,7 @@ class BuildMetaBase extends Model 'meta_value' => 'getMetaValue', // Foreign key getters: + 'Project' => 'getProject', 'Build' => 'getBuild', ); @@ -67,6 +68,7 @@ class BuildMetaBase extends Model 'meta_value' => 'setMetaValue', // Foreign key setters: + 'Project' => 'setProject', 'Build' => 'setBuild', ); @@ -89,16 +91,15 @@ class BuildMetaBase extends Model 'build_id' => array( 'type' => 'int', 'length' => 11, - 'nullable' => true, 'default' => null, ), 'meta_key' => array( 'type' => 'varchar', - 'length' => 255, + 'length' => 250, + 'default' => null, ), 'meta_value' => array( - 'type' => 'text', - 'nullable' => true, + 'type' => 'mediumtext', 'default' => null, ), ); @@ -109,12 +110,20 @@ class BuildMetaBase extends Model public $indexes = array( 'PRIMARY' => array('unique' => true, 'columns' => 'id'), 'idx_meta_id' => array('unique' => true, 'columns' => 'build_id, meta_key'), + 'project_id' => array('columns' => 'project_id'), ); /** * @var array */ public $foreignKeys = array( + 'build_meta_ibfk_1' => array( + 'local_col' => 'project_id', + 'update' => 'CASCADE', + 'delete' => 'CASCADE', + 'table' => 'project', + 'col' => 'id' + ), 'fk_meta_build_id' => array( 'local_col' => 'build_id', 'update' => 'CASCADE', @@ -227,10 +236,12 @@ class BuildMetaBase extends Model /** * Set the value of BuildId / build_id. * + * Must not be null. * @param $value int */ public function setBuildId($value) { + $this->_validateNotNull('BuildId', $value); $this->_validateInt('BuildId', $value); if ($this->data['build_id'] === $value) { @@ -265,10 +276,12 @@ class BuildMetaBase extends Model /** * Set the value of MetaValue / meta_value. * + * Must not be null. * @param $value string */ public function setMetaValue($value) { + $this->_validateNotNull('MetaValue', $value); $this->_validateString('MetaValue', $value); if ($this->data['meta_value'] === $value) { @@ -280,6 +293,63 @@ class BuildMetaBase extends Model $this->_setModified('meta_value'); } + /** + * Get the Project model for this BuildMeta by Id. + * + * @uses \PHPCI\Store\ProjectStore::getById() + * @uses \PHPCI\Model\Project + * @return \PHPCI\Model\Project + */ + public function getProject() + { + $key = $this->getProjectId(); + + if (empty($key)) { + return null; + } + + $cacheKey = 'Cache.Project.' . $key; + $rtn = $this->cache->get($cacheKey, null); + + if (empty($rtn)) { + $rtn = Factory::getStore('Project', 'PHPCI')->getById($key); + $this->cache->set($cacheKey, $rtn); + } + + return $rtn; + } + + /** + * Set Project - Accepts an ID, an array representing a Project or a Project model. + * + * @param $value mixed + */ + public function setProject($value) + { + // Is this an instance of Project? + if ($value instanceof \PHPCI\Model\Project) { + return $this->setProjectObject($value); + } + + // Is this an array representing a Project item? + if (is_array($value) && !empty($value['id'])) { + return $this->setProjectId($value['id']); + } + + // Is this a scalar value representing the ID of this foreign key? + return $this->setProjectId($value); + } + + /** + * Set Project - Accepts a Project model. + * + * @param $value \PHPCI\Model\Project + */ + public function setProjectObject(\PHPCI\Model\Project $value) + { + return $this->setProjectId($value->getId()); + } + /** * Get the Build model for this BuildMeta by Id. * @@ -299,7 +369,7 @@ class BuildMetaBase extends Model $rtn = $this->cache->get($cacheKey, null); if (empty($rtn)) { - $rtn = Factory::getStore('Build')->getById($key); + $rtn = Factory::getStore('Build', 'PHPCI')->getById($key); $this->cache->set($cacheKey, $rtn); } diff --git a/PHPCI/Model/Base/ProjectBase.php b/PHPCI/Model/Base/ProjectBase.php index f23e1d7f..0dc0c0eb 100644 --- a/PHPCI/Model/Base/ProjectBase.php +++ b/PHPCI/Model/Base/ProjectBase.php @@ -6,7 +6,7 @@ namespace PHPCI\Model\Base; -use b8\Model; +use PHPCI\Model; use b8\Store\Factory; /** @@ -36,10 +36,16 @@ class ProjectBase extends Model 'id' => null, 'title' => null, 'reference' => null, - 'git_key' => null, + 'branch' => null, + 'ssh_private_key' => null, 'type' => null, - 'token' => null, 'access_information' => null, + 'last_commit' => null, + 'build_config' => null, + 'ssh_public_key' => null, + 'allow_public_status' => null, + 'archived' => null, + 'group_id' => null, ); /** @@ -50,12 +56,19 @@ class ProjectBase extends Model 'id' => 'getId', 'title' => 'getTitle', 'reference' => 'getReference', - 'git_key' => 'getGitKey', + 'branch' => 'getBranch', + 'ssh_private_key' => 'getSshPrivateKey', 'type' => 'getType', - 'token' => 'getToken', 'access_information' => 'getAccessInformation', + 'last_commit' => 'getLastCommit', + 'build_config' => 'getBuildConfig', + 'ssh_public_key' => 'getSshPublicKey', + 'allow_public_status' => 'getAllowPublicStatus', + 'archived' => 'getArchived', + 'group_id' => 'getGroupId', // Foreign key getters: + 'Group' => 'getGroup', ); /** @@ -66,12 +79,19 @@ class ProjectBase extends Model 'id' => 'setId', 'title' => 'setTitle', 'reference' => 'setReference', - 'git_key' => 'setGitKey', + 'branch' => 'setBranch', + 'ssh_private_key' => 'setSshPrivateKey', 'type' => 'setType', - 'token' => 'setToken', 'access_information' => 'setAccessInformation', + 'last_commit' => 'setLastCommit', + 'build_config' => 'setBuildConfig', + 'ssh_public_key' => 'setSshPublicKey', + 'allow_public_status' => 'setAllowPublicStatus', + 'archived' => 'setArchived', + 'group_id' => 'setGroupId', // Foreign key setters: + 'Group' => 'setGroup', ); /** @@ -88,12 +108,19 @@ class ProjectBase extends Model 'title' => array( 'type' => 'varchar', 'length' => 250, + 'default' => null, ), 'reference' => array( 'type' => 'varchar', 'length' => 250, + 'default' => null, ), - 'git_key' => array( + 'branch' => array( + 'type' => 'varchar', + 'length' => 50, + 'default' => 'master', + ), + 'ssh_private_key' => array( 'type' => 'text', 'nullable' => true, 'default' => null, @@ -101,12 +128,6 @@ class ProjectBase extends Model 'type' => array( 'type' => 'varchar', 'length' => 50, - 'default' => 1, - ), - 'token' => array( - 'type' => 'varchar', - 'length' => 50, - 'nullable' => true, 'default' => null, ), 'access_information' => array( @@ -115,6 +136,36 @@ class ProjectBase extends Model 'nullable' => true, 'default' => null, ), + 'last_commit' => array( + 'type' => 'varchar', + 'length' => 250, + 'nullable' => true, + 'default' => null, + ), + 'build_config' => array( + 'type' => 'text', + 'nullable' => true, + 'default' => null, + ), + 'ssh_public_key' => array( + 'type' => 'text', + 'nullable' => true, + 'default' => null, + ), + 'allow_public_status' => array( + 'type' => 'int', + 'length' => 11, + ), + 'archived' => array( + 'type' => 'tinyint', + 'length' => 1, + 'default' => null, + ), + 'group_id' => array( + 'type' => 'int', + 'length' => 11, + 'default' => 1, + ), ); /** @@ -123,12 +174,20 @@ class ProjectBase extends Model public $indexes = array( 'PRIMARY' => array('unique' => true, 'columns' => 'id'), 'idx_project_title' => array('columns' => 'title'), + 'group_id' => array('columns' => 'group_id'), ); /** * @var array */ public $foreignKeys = array( + 'project_ibfk_1' => array( + 'local_col' => 'group_id', + 'update' => 'CASCADE', + 'delete' => '', + 'table' => 'project_group', + 'col' => 'id' + ), ); /** @@ -168,13 +227,25 @@ class ProjectBase extends Model } /** - * Get the value of GitKey / git_key. + * Get the value of Branch / branch. * * @return string */ - public function getGitKey() + public function getBranch() { - $rtn = $this->data['git_key']; + $rtn = $this->data['branch']; + + return $rtn; + } + + /** + * Get the value of SshPrivateKey / ssh_private_key. + * + * @return string + */ + public function getSshPrivateKey() + { + $rtn = $this->data['ssh_private_key']; return $rtn; } @@ -191,18 +262,6 @@ class ProjectBase extends Model return $rtn; } - /** - * Get the value of Token / token. - * - * @return string - */ - public function getToken() - { - $rtn = $this->data['token']; - - return $rtn; - } - /** * Get the value of AccessInformation / access_information. * @@ -215,6 +274,78 @@ class ProjectBase extends Model return $rtn; } + /** + * Get the value of LastCommit / last_commit. + * + * @return string + */ + public function getLastCommit() + { + $rtn = $this->data['last_commit']; + + return $rtn; + } + + /** + * Get the value of BuildConfig / build_config. + * + * @return string + */ + public function getBuildConfig() + { + $rtn = $this->data['build_config']; + + return $rtn; + } + + /** + * Get the value of SshPublicKey / ssh_public_key. + * + * @return string + */ + public function getSshPublicKey() + { + $rtn = $this->data['ssh_public_key']; + + return $rtn; + } + + /** + * Get the value of AllowPublicStatus / allow_public_status. + * + * @return int + */ + public function getAllowPublicStatus() + { + $rtn = $this->data['allow_public_status']; + + return $rtn; + } + + /** + * Get the value of Archived / archived. + * + * @return int + */ + public function getArchived() + { + $rtn = $this->data['archived']; + + return $rtn; + } + + /** + * Get the value of GroupId / group_id. + * + * @return int + */ + public function getGroupId() + { + $rtn = $this->data['group_id']; + + return $rtn; + } + /** * Set the value of Id / id. * @@ -276,21 +407,41 @@ class ProjectBase extends Model } /** - * Set the value of GitKey / git_key. + * Set the value of Branch / branch. * + * Must not be null. * @param $value string */ - public function setGitKey($value) + public function setBranch($value) { - $this->_validateString('GitKey', $value); + $this->_validateNotNull('Branch', $value); + $this->_validateString('Branch', $value); - if ($this->data['git_key'] === $value) { + if ($this->data['branch'] === $value) { return; } - $this->data['git_key'] = $value; + $this->data['branch'] = $value; - $this->_setModified('git_key'); + $this->_setModified('branch'); + } + + /** + * Set the value of SshPrivateKey / ssh_private_key. + * + * @param $value string + */ + public function setSshPrivateKey($value) + { + $this->_validateString('SshPrivateKey', $value); + + if ($this->data['ssh_private_key'] === $value) { + return; + } + + $this->data['ssh_private_key'] = $value; + + $this->_setModified('ssh_private_key'); } /** @@ -313,24 +464,6 @@ class ProjectBase extends Model $this->_setModified('type'); } - /** - * Set the value of Token / token. - * - * @param $value string - */ - public function setToken($value) - { - $this->_validateString('Token', $value); - - if ($this->data['token'] === $value) { - return; - } - - $this->data['token'] = $value; - - $this->_setModified('token'); - } - /** * Set the value of AccessInformation / access_information. * @@ -349,6 +482,177 @@ class ProjectBase extends Model $this->_setModified('access_information'); } + /** + * Set the value of LastCommit / last_commit. + * + * @param $value string + */ + public function setLastCommit($value) + { + $this->_validateString('LastCommit', $value); + + if ($this->data['last_commit'] === $value) { + return; + } + + $this->data['last_commit'] = $value; + + $this->_setModified('last_commit'); + } + + /** + * Set the value of BuildConfig / build_config. + * + * @param $value string + */ + public function setBuildConfig($value) + { + $this->_validateString('BuildConfig', $value); + + if ($this->data['build_config'] === $value) { + return; + } + + $this->data['build_config'] = $value; + + $this->_setModified('build_config'); + } + + /** + * Set the value of SshPublicKey / ssh_public_key. + * + * @param $value string + */ + public function setSshPublicKey($value) + { + $this->_validateString('SshPublicKey', $value); + + if ($this->data['ssh_public_key'] === $value) { + return; + } + + $this->data['ssh_public_key'] = $value; + + $this->_setModified('ssh_public_key'); + } + + /** + * Set the value of AllowPublicStatus / allow_public_status. + * + * Must not be null. + * @param $value int + */ + public function setAllowPublicStatus($value) + { + $this->_validateNotNull('AllowPublicStatus', $value); + $this->_validateInt('AllowPublicStatus', $value); + + if ($this->data['allow_public_status'] === $value) { + return; + } + + $this->data['allow_public_status'] = $value; + + $this->_setModified('allow_public_status'); + } + + /** + * Set the value of Archived / archived. + * + * Must not be null. + * @param $value int + */ + public function setArchived($value) + { + $this->_validateNotNull('Archived', $value); + $this->_validateInt('Archived', $value); + + if ($this->data['archived'] === $value) { + return; + } + + $this->data['archived'] = $value; + + $this->_setModified('archived'); + } + + /** + * Set the value of GroupId / group_id. + * + * Must not be null. + * @param $value int + */ + public function setGroupId($value) + { + $this->_validateNotNull('GroupId', $value); + $this->_validateInt('GroupId', $value); + + if ($this->data['group_id'] === $value) { + return; + } + + $this->data['group_id'] = $value; + + $this->_setModified('group_id'); + } + + /** + * Get the ProjectGroup model for this Project by Id. + * + * @uses \PHPCI\Store\ProjectGroupStore::getById() + * @uses \PHPCI\Model\ProjectGroup + * @return \PHPCI\Model\ProjectGroup + */ + public function getGroup() + { + $key = $this->getGroupId(); + + if (empty($key)) { + return null; + } + + $cacheKey = 'Cache.ProjectGroup.' . $key; + $rtn = $this->cache->get($cacheKey, null); + + if (empty($rtn)) { + $rtn = Factory::getStore('ProjectGroup', 'PHPCI')->getById($key); + $this->cache->set($cacheKey, $rtn); + } + + return $rtn; + } + + /** + * Set Group - Accepts an ID, an array representing a ProjectGroup or a ProjectGroup model. + * + * @param $value mixed + */ + public function setGroup($value) + { + // Is this an instance of ProjectGroup? + if ($value instanceof \PHPCI\Model\ProjectGroup) { + return $this->setGroupObject($value); + } + + // Is this an array representing a ProjectGroup item? + if (is_array($value) && !empty($value['id'])) { + return $this->setGroupId($value['id']); + } + + // Is this a scalar value representing the ID of this foreign key? + return $this->setGroupId($value); + } + + /** + * Set Group - Accepts a ProjectGroup model. + * + * @param $value \PHPCI\Model\ProjectGroup + */ + public function setGroupObject(\PHPCI\Model\ProjectGroup $value) + { + return $this->setGroupId($value->getId()); + } + /** * Get Build models by ProjectId for this Project. * @@ -358,6 +662,18 @@ class ProjectBase extends Model */ public function getProjectBuilds() { - return Factory::getStore('Build')->getByProjectId($this->getId()); + return Factory::getStore('Build', 'PHPCI')->getByProjectId($this->getId()); + } + + /** + * Get BuildMeta models by ProjectId for this Project. + * + * @uses \PHPCI\Store\BuildMetaStore::getByProjectId() + * @uses \PHPCI\Model\BuildMeta + * @return \PHPCI\Model\BuildMeta[] + */ + public function getProjectBuildMetas() + { + return Factory::getStore('BuildMeta', 'PHPCI')->getByProjectId($this->getId()); } } diff --git a/PHPCI/Model/Base/ProjectGroupBase.php b/PHPCI/Model/Base/ProjectGroupBase.php new file mode 100644 index 00000000..3b5eed69 --- /dev/null +++ b/PHPCI/Model/Base/ProjectGroupBase.php @@ -0,0 +1,168 @@ + null, + 'title' => null, + ); + + /** + * @var array + */ + protected $getters = array( + // Direct property getters: + 'id' => 'getId', + 'title' => 'getTitle', + + // Foreign key getters: + ); + + /** + * @var array + */ + protected $setters = array( + // Direct property setters: + 'id' => 'setId', + 'title' => 'setTitle', + + // Foreign key setters: + ); + + /** + * @var array + */ + public $columns = array( + 'id' => array( + 'type' => 'int', + 'length' => 11, + 'primary_key' => true, + 'auto_increment' => true, + 'default' => null, + ), + 'title' => array( + 'type' => 'varchar', + 'length' => 100, + 'default' => null, + ), + ); + + /** + * @var array + */ + public $indexes = array( + 'PRIMARY' => array('unique' => true, 'columns' => 'id'), + ); + + /** + * @var array + */ + public $foreignKeys = array( + ); + + /** + * Get the value of Id / id. + * + * @return int + */ + public function getId() + { + $rtn = $this->data['id']; + + return $rtn; + } + + /** + * Get the value of Title / title. + * + * @return string + */ + public function getTitle() + { + $rtn = $this->data['title']; + + return $rtn; + } + + /** + * Set the value of Id / id. + * + * Must not be null. + * @param $value int + */ + public function setId($value) + { + $this->_validateNotNull('Id', $value); + $this->_validateInt('Id', $value); + + if ($this->data['id'] === $value) { + return; + } + + $this->data['id'] = $value; + + $this->_setModified('id'); + } + + /** + * Set the value of Title / title. + * + * Must not be null. + * @param $value string + */ + public function setTitle($value) + { + $this->_validateNotNull('Title', $value); + $this->_validateString('Title', $value); + + if ($this->data['title'] === $value) { + return; + } + + $this->data['title'] = $value; + + $this->_setModified('title'); + } + + /** + * Get Project models by GroupId for this ProjectGroup. + * + * @uses \PHPCI\Store\ProjectStore::getByGroupId() + * @uses \PHPCI\Model\Project + * @return \PHPCI\Model\Project[] + */ + public function getGroupProjects() + { + return Factory::getStore('Project', 'PHPCI')->getByGroupId($this->getId()); + } +} diff --git a/PHPCI/Model/Base/UserBase.php b/PHPCI/Model/Base/UserBase.php index e8035660..4de48537 100644 --- a/PHPCI/Model/Base/UserBase.php +++ b/PHPCI/Model/Base/UserBase.php @@ -6,7 +6,7 @@ namespace PHPCI\Model\Base; -use b8\Model; +use PHPCI\Model; use b8\Store\Factory; /** @@ -82,19 +82,20 @@ class UserBase extends Model 'email' => array( 'type' => 'varchar', 'length' => 250, + 'default' => null, ), 'hash' => array( 'type' => 'varchar', 'length' => 250, + 'default' => null, ), 'is_admin' => array( - 'type' => 'tinyint', - 'length' => 1, + 'type' => 'int', + 'length' => 11, ), 'name' => array( 'type' => 'varchar', 'length' => 250, - 'nullable' => true, 'default' => null, ), ); @@ -105,6 +106,8 @@ class UserBase extends Model public $indexes = array( 'PRIMARY' => array('unique' => true, 'columns' => 'id'), 'idx_email' => array('unique' => true, 'columns' => 'email'), + 'email' => array('unique' => true, 'columns' => 'email'), + 'name' => array('columns' => 'name'), ); /** @@ -256,10 +259,12 @@ class UserBase extends Model /** * Set the value of Name / name. * + * Must not be null. * @param $value string */ public function setName($value) { + $this->_validateNotNull('Name', $value); $this->_validateString('Name', $value); if ($this->data['name'] === $value) { diff --git a/PHPCI/Model/Build.php b/PHPCI/Model/Build.php index bc814560..a8bfd683 100644 --- a/PHPCI/Model/Build.php +++ b/PHPCI/Model/Build.php @@ -1,16 +1,18 @@ getProject(); + return $project ? $project->getTitle() : ""; + } + /** * Store build metadata */ @@ -66,4 +88,214 @@ class Build extends BuildBase { return ($this->getStatus() === self::STATUS_SUCCESS); } + + /** + * @param Builder $builder + * @param string $buildPath + * + * @return bool + */ + protected function handleConfig(Builder $builder, $buildPath) + { + $build_config = null; + + // Try getting the project build config from the database: + if (empty($build_config)) { + $build_config = $this->getProject()->getBuildConfig(); + } + + // Try .phpci.yml + if (is_file($buildPath . '/.phpci.yml')) { + $build_config = file_get_contents($buildPath . '/.phpci.yml'); + } + + // Try phpci.yml first: + if (empty($build_config) && is_file($buildPath . '/phpci.yml')) { + $build_config = file_get_contents($buildPath . '/phpci.yml'); + } + + // Fall back to zero config plugins: + if (empty($build_config)) { + $build_config = $this->getZeroConfigPlugins($builder); + } + + if (is_string($build_config)) { + $yamlParser = new YamlParser(); + $build_config = $yamlParser->parse($build_config); + } + + $builder->setConfigArray($build_config); + return true; + } + + /** + * Get an array of plugins to run if there's no phpci.yml file. + * @param Builder $builder + * @return array + */ + protected function getZeroConfigPlugins(Builder $builder) + { + $pluginDir = PHPCI_DIR . 'PHPCI/Plugin/'; + $dir = new \DirectoryIterator($pluginDir); + + $config = array( + 'build_settings' => array( + 'ignore' => array( + 'vendor', + ) + ) + ); + + foreach ($dir as $item) { + if ($item->isDot()) { + continue; + } + + if (!$item->isFile()) { + continue; + } + + if ($item->getExtension() != 'php') { + continue; + } + + $className = '\PHPCI\Plugin\\'.$item->getBasename('.php'); + + $reflectedPlugin = new \ReflectionClass($className); + + if (!$reflectedPlugin->implementsInterface('\PHPCI\ZeroConfigPlugin')) { + continue; + } + + foreach (array('setup', 'test', 'complete', 'success', 'failure') as $stage) { + if ($className::canExecute($stage, $builder, $this)) { + $config[$stage][$className] = array( + 'zero_config' => true + ); + } + } + } + + return $config; + } + + /** + * Return a value from the build's "extra" JSON array. + * @param null $key + * @return mixed|null|string + */ + public function getExtra($key = null) + { + $data = json_decode($this->data['extra'], true); + + if (is_null($key)) { + $rtn = $data; + } elseif (isset($data[$key])) { + $rtn = $data[$key]; + } else { + $rtn = null; + } + + return $rtn; + } + + /** + * Returns the commit message for this build. + * @return string + */ + public function getCommitMessage() + { + $rtn = htmlspecialchars($this->data['commit_message']); + + return $rtn; + } + + /** + * Allows specific build types (e.g. Github) to report violations back to their respective services. + * @param Builder $builder + * @param $plugin + * @param $message + * @param int $severity + * @param null $file + * @param null $lineStart + * @param null $lineEnd + * @return BuildError + */ + public function reportError( + Builder $builder, + $plugin, + $message, + $severity = BuildError::SEVERITY_NORMAL, + $file = null, + $lineStart = null, + $lineEnd = null + ) { + unset($builder); + + $error = new BuildError(); + $error->setBuild($this); + $error->setCreatedDate(new \DateTime()); + $error->setPlugin($plugin); + $error->setMessage($message); + $error->setSeverity($severity); + $error->setFile($file); + $error->setLineStart($lineStart); + $error->setLineEnd($lineEnd); + + return Factory::getStore('BuildError')->save($error); + } + + /** + * Return the path to run this build into. + * + * @return string|null + */ + public function getBuildPath() + { + if (!$this->getId()) { + return null; + } + + if (empty($this->currentBuildPath)) { + $buildDirectory = $this->getId() . '_' . substr(md5(microtime(true)), 0, 5); + $this->currentBuildPath = PHPCI_BUILD_ROOT_DIR . $buildDirectory . DIRECTORY_SEPARATOR; + } + + return $this->currentBuildPath; + } + + /** + * Removes the build directory. + */ + public function removeBuildDirectory() + { + $buildPath = $this->getBuildPath(); + + if (!$buildPath || !is_dir($buildPath)) { + return; + } + + exec(sprintf(IS_WIN ? 'rmdir /S /Q "%s"' : 'rm -Rf "%s"', $buildPath)); + } + + /** + * Get the number of seconds a build has been running for. + * @return int + */ + public function getDuration() + { + $start = $this->getStarted(); + + if (empty($start)) { + return 0; + } + + $end = $this->getFinished(); + + if (empty($end)) { + $end = new \DateTime(); + } + + return $end->getTimestamp() - $start->getTimestamp(); + } } diff --git a/PHPCI/Model/Build/BitbucketBuild.php b/PHPCI/Model/Build/BitbucketBuild.php index fb50a710..3af0d2b5 100644 --- a/PHPCI/Model/Build/BitbucketBuild.php +++ b/PHPCI/Model/Build/BitbucketBuild.php @@ -1,11 +1,11 @@ getProject()->getGitKey()); + $key = trim($this->getProject()->getSshPrivateKey()); if (!empty($key)) { return 'git@bitbucket.org:' . $this->getProject()->getReference() . '.git'; diff --git a/PHPCI/Model/Build/GithubBuild.php b/PHPCI/Model/Build/GithubBuild.php index 98d7a8eb..a9a18913 100644 --- a/PHPCI/Model/Build/GithubBuild.php +++ b/PHPCI/Model/Build/GithubBuild.php @@ -1,14 +1,17 @@ get('phpci.github.token'); - if (empty($token)) { + if (empty($token) || empty($this->data['id'])) { return; } $project = $this->getProject(); + if (empty($project)) { + return; + } + $url = 'https://api.github.com/repos/'.$project->getReference().'/statuses/'.$this->getCommitId(); $http = new \b8\HttpClient(); - switch($this->getStatus()) - { + switch ($this->getStatus()) { case 0: case 1: $status = 'pending'; + $description = 'PHPCI build running.'; break; case 2: $status = 'success'; + $description = 'PHPCI build passed.'; break; case 3: $status = 'failure'; + $description = 'PHPCI build failed.'; break; default: $status = 'error'; + $description = 'PHPCI build failed to complete.'; break; } $phpciUrl = \b8\Config::getInstance()->get('phpci.url'); - $params = array( 'state' => $status, - 'target_url' => $phpciUrl . '/build/view/' . $this->getId()); + + $params = array( + 'state' => $status, + 'target_url' => $phpciUrl . '/build/view/' . $this->getId(), + 'description' => $description, + 'context' => 'PHPCI', + ); + $headers = array( 'Authorization: token ' . $token, 'Content-Type: application/x-www-form-urlencoded' - ); + ); $http->setHeaders($headers); $http->request('POST', $url, json_encode($params)); @@ -85,7 +101,7 @@ class GithubBuild extends RemoteGitBuild */ protected function getCloneUrl() { - $key = trim($this->getProject()->getGitKey()); + $key = trim($this->getProject()->getSshPrivateKey()); if (!empty($key)) { return 'git@github.com:' . $this->getProject()->getReference() . '.git'; @@ -93,4 +109,146 @@ class GithubBuild extends RemoteGitBuild return 'https://github.com/' . $this->getProject()->getReference() . '.git'; } } + + /** + * Get a parsed version of the commit message, with links to issues and commits. + * @return string + */ + public function getCommitMessage() + { + $rtn = parent::getCommitMessage($this->data['commit_message']); + + $project = $this->getProject(); + + if (!is_null($project)) { + $reference = $project->getReference(); + $commitLink = '#$1'; + $rtn = preg_replace('/\#([0-9]+)/', $commitLink, $rtn); + $rtn = preg_replace('/\@([a-zA-Z0-9_]+)/', '@$1', $rtn); + } + + return $rtn; + } + + /** + * Get a template to use for generating links to files. + * e.g. https://github.com/block8/phpci/blob/master/{FILE}#L{LINE} + * @return string + */ + public function getFileLinkTemplate() + { + $reference = $this->getProject()->getReference(); + $branch = $this->getBranch(); + + if ($this->getExtra('build_type') == 'pull_request') { + $matches = array(); + preg_match('/[\/:]([a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+)/', $this->getExtra('remote_url'), $matches); + + $reference = $matches[1]; + $branch = $this->getExtra('remote_branch'); + } + + $link = 'https://github.com/' . $reference . '/'; + $link .= 'blob/' . $branch . '/'; + $link .= '{FILE}'; + $link .= '#L{LINE}-L{LINE_END}'; + + return $link; + } + + /** + * Handle any post-clone tasks, like applying a pull request patch on top of the branch. + * @param Builder $builder + * @param $cloneTo + * @return bool + */ + protected function postCloneSetup(Builder $builder, $cloneTo) + { + $buildType = $this->getExtra('build_type'); + + $success = true; + + try { + if (!empty($buildType) && $buildType == 'pull_request') { + $remoteUrl = $this->getExtra('remote_url'); + $remoteBranch = $this->getExtra('remote_branch'); + + $cmd = 'cd "%s" && git checkout -b phpci/' . $this->getId() . ' %s && git pull -q --no-edit %s %s'; + $success = $builder->executeCommand($cmd, $cloneTo, $this->getBranch(), $remoteUrl, $remoteBranch); + } + } catch (\Exception $ex) { + $success = false; + } + + if ($success) { + $success = parent::postCloneSetup($builder, $cloneTo); + } + + return $success; + } + + /** + * @inheritDoc + */ + public function reportError( + Builder $builder, + $plugin, + $message, + $severity = BuildError::SEVERITY_NORMAL, + $file = null, + $lineStart = null, + $lineEnd = null + ) { + $diffLineNumber = $this->getDiffLineNumber($builder, $file, $lineStart); + + if (!is_null($diffLineNumber)) { + $helper = new Github(); + + $repo = $this->getProject()->getReference(); + $prNumber = $this->getExtra('pull_request_number'); + $commit = $this->getCommitId(); + + if (!empty($prNumber)) { + $helper->createPullRequestComment($repo, $prNumber, $commit, $file, $diffLineNumber, $message); + } else { + $helper->createCommitComment($repo, $commit, $file, $diffLineNumber, $message); + } + } + + return parent::reportError($builder, $plugin, $message, $severity, $file, $lineStart, $lineEnd); + } + + /** + * Uses git diff to figure out what the diff line position is, based on the error line number. + * @param Builder $builder + * @param $file + * @param $line + * @return int|null + */ + protected function getDiffLineNumber(Builder $builder, $file, $line) + { + $line = (integer)$line; + + $builder->logExecOutput(false); + + $prNumber = $this->getExtra('pull_request_number'); + $path = $builder->buildPath; + + if (!empty($prNumber)) { + $builder->executeCommand('cd %s && git diff origin/%s "%s"', $path, $this->getBranch(), $file); + } else { + $commitId = $this->getCommitId(); + $compare = $commitId == 'Manual' ? 'HEAD' : $commitId; + $builder->executeCommand('cd %s && git diff %s^^ "%s"', $path, $compare, $file); + } + + $builder->logExecOutput(true); + + $diff = $builder->getLastOutput(); + + $helper = new Diff(); + $lines = $helper->getLinePositions($diff); + + return isset($lines[$line]) ? $lines[$line] : null; + } } diff --git a/PHPCI/Model/Build/GitlabBuild.php b/PHPCI/Model/Build/GitlabBuild.php index f3f75ebe..a1e0562c 100644 --- a/PHPCI/Model/Build/GitlabBuild.php +++ b/PHPCI/Model/Build/GitlabBuild.php @@ -1,11 +1,11 @@ getProject()->getAccessInformation()["domain"]; + $domain = $this->getProject()->getAccessInformation("domain"); return 'http://' . $domain . '/' . $this->getProject()->getReference() . '/commit/' . $this->getCommitId(); } @@ -34,21 +34,44 @@ class GitlabBuild extends RemoteGitBuild */ public function getBranchLink() { - $domain = $this->getProject()->getAccessInformation()["domain"]; + $domain = $this->getProject()->getAccessInformation("domain"); return 'http://' . $domain . '/' . $this->getProject()->getReference() . '/tree/' . $this->getBranch(); } + /** + * Get link to specific file (and line) in a the repo's branch + */ + public function getFileLinkTemplate() + { + return sprintf( + 'http://%s/%s/blob/%s/{FILE}#L{LINE}', + $this->getProject()->getAccessInformation("domain"), + $this->getProject()->getReference(), + $this->getCommitId() + ); + } + /** * Get the URL to be used to clone this remote repository. */ protected function getCloneUrl() { - $key = trim($this->getProject()->getGitKey()); + $key = trim($this->getProject()->getSshPrivateKey()); if (!empty($key)) { - $user = $this->getProject()->getAccessInformation()["user"]; - $domain = $this->getProject()->getAccessInformation()["domain"]; - return $user . '@' . $domain . ':' . $this->getProject()->getReference() . '.git'; + $user = $this->getProject()->getAccessInformation("user"); + $domain = $this->getProject()->getAccessInformation("domain"); + $port = $this->getProject()->getAccessInformation('port'); + + $url = $user . '@' . $domain . ':'; + + if (!empty($port)) { + $url .= $port . '/'; + } + + $url .= $this->getProject()->getReference() . '.git'; + + return $url; } } } diff --git a/PHPCI/Model/Build/LocalBuild.php b/PHPCI/Model/Build/LocalBuild.php index bfb8ac9e..5131abe1 100644 --- a/PHPCI/Model/Build/LocalBuild.php +++ b/PHPCI/Model/Build/LocalBuild.php @@ -1,17 +1,16 @@ handleBareRepository($builder, $reference, $buildPath) === true) { - return true; + return $this->handleConfig($builder, $buildPath) !== false; } - $buildSettings = $this->handleConfig($builder, $reference); + $configHandled = $this->handleConfig($builder, $reference); - if ($buildSettings === false) { + if ($configHandled === false) { return false; } + $buildSettings = $builder->getConfig('build_settings'); + if (isset($buildSettings['prefer_symlink']) && $buildSettings['prefer_symlink'] === true) { return $this->handleSymlink($builder, $reference, $buildPath); } else { - $builder->executeCommand('cp -Rf "%s" "%s/"', $reference, $buildPath); + $cmd = 'cp -Rf "%s" "%s/"'; + if (IS_WIN) { + $cmd = 'xcopy /E /Y "%s" "%s/*"'; + } + $builder->executeCommand($cmd, $reference, $buildPath); } return true; } + /** + * Check if this is a "bare" git repository, and if so, unarchive it. + * @param Builder $builder + * @param $reference + * @param $buildPath + * @return bool + */ protected function handleBareRepository(Builder $builder, $reference, $buildPath) { $gitConfig = parse_ini_file($reference.'/config', true); // If it is indeed a bare repository, then extract it into our build path: if ($gitConfig['core']['bare']) { - $builder->executeCommand('git --git-dir="%s" archive master | tar -x -C "%s"', $reference, $buildPath); + $cmd = 'mkdir %2$s; git --git-dir="%1$s" archive %3$s | tar -x -C "%2$s"'; + $builder->executeCommand($cmd, $reference, $buildPath, $this->getBranch()); return true; } return false; } + /** + * Create a symlink if required. + * @param Builder $builder + * @param $reference + * @param $buildPath + * @return bool + */ protected function handleSymlink(Builder $builder, $reference, $buildPath) { if (is_link($buildPath) && is_file($buildPath)) { @@ -79,18 +99,4 @@ class LocalBuild extends Build return true; } - - protected function handleConfig(Builder $builder, $reference) - { - /** @todo Add support for database-based yml definition */ - if (!is_file($reference . '/phpci.yml')) { - $builder->logFailure('Project does not contain a phpci.yml file.'); - return false; - } - - $yamlParser = new YamlParser(); - $yamlFile = file_get_contents($reference . '/phpci.yml'); - $builder->setConfigArray($yamlParser->parse($yamlFile)); - return $builder->getConfig('build_settings'); - } } diff --git a/PHPCI/Model/Build/MercurialBuild.php b/PHPCI/Model/Build/MercurialBuild.php index dbfcf4e2..0f38940b 100644 --- a/PHPCI/Model/Build/MercurialBuild.php +++ b/PHPCI/Model/Build/MercurialBuild.php @@ -1,59 +1,99 @@ -* @package PHPCI -* @subpackage Core -*/ + * Mercurial Build Model + * @author Pavel Gopanenko + * @package PHPCI + * @subpackage Core + */ class MercurialBuild extends Build { /** - * Get the URL to be used to clone this remote repository. - */ + * Get the URL to be used to clone this remote repository. + */ protected function getCloneUrl() { return $this->getProject()->getReference(); } /** - * Create a working copy by cloning, copying, or similar. - */ + * Create a working copy by cloning, copying, or similar. + */ public function createWorkingCopy(Builder $builder, $buildPath) { - $yamlParser = new YamlParser(); + $key = trim($this->getProject()->getSshPublicKey()); - $this->cloneByHttp($builder, $buildPath); + if (!empty($key) && strpos($this->getProject()->getReference(), 'ssh') > -1) { + $success = $this->cloneBySsh($builder, $buildPath); + } else { + $success = $this->cloneByHttp($builder, $buildPath); + } - if (!is_file($buildPath . 'phpci.yml')) { - $builder->logFailure('Project does not contain a phpci.yml file.'); + if (!$success) { + $builder->logFailure('Failed to clone remote git repository.'); return false; } - $yamlFile = file_get_contents($buildPath . 'phpci.yml'); - $builder->setConfigArray($yamlParser->parse($yamlFile)); - - return true; + return $this->handleConfig($builder, $buildPath); } /** - * Use an mercurial clone. - */ + * Use a HTTP-based Mercurial clone. + */ protected function cloneByHttp(Builder $builder, $cloneTo) { return $builder->executeCommand('hg clone %s "%s" -r %s', $this->getCloneUrl(), $cloneTo, $this->getBranch()); } + + /** + * Use an SSH-based Mercurial clone. + */ + protected function cloneBySsh(Builder $builder, $cloneTo) + { + $keyFile = $this->writeSshKey(); + + // Do the git clone: + $cmd = 'hg clone --ssh "ssh -i '.$keyFile.'" %s "%s"'; + $success = $builder->executeCommand($cmd, $this->getCloneUrl(), $cloneTo); + + if ($success) { + $success = $this->postCloneSetup($builder, $cloneTo); + } + + // Remove the key file: + unlink($keyFile); + return $success; + } + + /** + * Handle post-clone tasks (switching branch, etc.) + * @param Builder $builder + * @param $cloneTo + * @return bool + */ + protected function postCloneSetup(Builder $builder, $cloneTo) + { + $success = true; + $commit = $this->getCommitId(); + + // Allow switching to a specific branch: + if (!empty($commit) && $commit != 'Manual') { + $cmd = 'cd "%s" && hg checkout %s'; + $success = $builder->executeCommand($cmd, $cloneTo, $this->getBranch()); + } + + return $success; + } } diff --git a/PHPCI/Model/Build/RemoteGitBuild.php b/PHPCI/Model/Build/RemoteGitBuild.php index c4b03d2c..faaccfb5 100644 --- a/PHPCI/Model/Build/RemoteGitBuild.php +++ b/PHPCI/Model/Build/RemoteGitBuild.php @@ -1,17 +1,16 @@ getProject()->getGitKey()); + $key = trim($this->getProject()->getSshPrivateKey()); if (!empty($key)) { $success = $this->cloneBySsh($builder, $buildPath); @@ -49,15 +46,7 @@ class RemoteGitBuild extends Build return false; } - if (!is_file($buildPath . 'phpci.yml')) { - $builder->logFailure('Project does not contain a phpci.yml file.'); - return false; - } - - $yamlFile = file_get_contents($buildPath . 'phpci.yml'); - $builder->setConfigArray($yamlParser->parse($yamlFile)); - - return true; + return $this->handleConfig($builder, $buildPath); } /** @@ -65,7 +54,22 @@ class RemoteGitBuild extends Build */ protected function cloneByHttp(Builder $builder, $cloneTo) { - return $builder->executeCommand('git clone -b %s %s "%s"', $this->getBranch(), $this->getCloneUrl(), $cloneTo); + $cmd = 'git clone --recursive '; + + $depth = $builder->getConfig('clone_depth'); + + if (!is_null($depth)) { + $cmd .= ' --depth ' . intval($depth) . ' '; + } + + $cmd .= ' -b %s %s "%s"'; + $success = $builder->executeCommand($cmd, $this->getBranch(), $this->getCloneUrl(), $cloneTo); + + if ($success) { + $success = $this->postCloneSetup($builder, $cloneTo); + } + + return $success; } /** @@ -73,25 +77,109 @@ class RemoteGitBuild extends Build */ protected function cloneBySsh(Builder $builder, $cloneTo) { - // Copy the project's keyfile to disk: - $keyPath = realpath($cloneTo); + $keyFile = $this->writeSshKey($cloneTo); - if ($keyPath === false) { - $keyPath = dirname($cloneTo); + if (!IS_WIN) { + $gitSshWrapper = $this->writeSshWrapper($cloneTo, $keyFile); } - $keyFile = $keyPath . '.key'; + // Do the git clone: + $cmd = 'git clone --recursive '; - file_put_contents($keyFile, $this->getProject()->getGitKey()); - chmod($keyFile, 0600); + $depth = $builder->getConfig('clone_depth'); - // Use the key file to do an SSH clone: - $cmd = 'eval `ssh-agent -s` && ssh-add "%s" && git clone -b %s %s "%s" && ssh-agent -k'; - $success = $builder->executeCommand($cmd, $keyFile, $this->getBranch(), $this->getCloneUrl(), $cloneTo); - - // Remove the key file: + if (!is_null($depth)) { + $cmd .= ' --depth ' . intval($depth) . ' '; + } + + $cmd .= ' -b %s %s "%s"'; + + if (!IS_WIN) { + $cmd = 'export GIT_SSH="'.$gitSshWrapper.'" && ' . $cmd; + } + + $success = $builder->executeCommand($cmd, $this->getBranch(), $this->getCloneUrl(), $cloneTo); + + if ($success) { + $success = $this->postCloneSetup($builder, $cloneTo); + } + + // Remove the key file and git wrapper: unlink($keyFile); + if (!IS_WIN) { + unlink($gitSshWrapper); + } return $success; } + + /** + * Handle any post-clone tasks, like switching branches. + * @param Builder $builder + * @param $cloneTo + * @return bool + */ + protected function postCloneSetup(Builder $builder, $cloneTo) + { + $success = true; + $commit = $this->getCommitId(); + + $chdir = IS_WIN ? 'cd /d "%s"' : 'cd "%s"'; + + if (!empty($commit) && $commit != 'Manual') { + $cmd = $chdir . ' && git checkout %s --quiet'; + $success = $builder->executeCommand($cmd, $cloneTo, $commit); + } + + // Always update the commit hash with the actual HEAD hash + if ($builder->executeCommand($chdir . ' && git rev-parse HEAD', $cloneTo)) { + $this->setCommitId(trim($builder->getLastOutput())); + } + + return $success; + } + + /** + * Create an SSH key file on disk for this build. + * @param $cloneTo + * @return string + */ + protected function writeSshKey($cloneTo) + { + $keyPath = dirname($cloneTo . '/temp'); + $keyFile = $keyPath . '.key'; + + // Write the contents of this project's git key to the file: + file_put_contents($keyFile, $this->getProject()->getSshPrivateKey()); + chmod($keyFile, 0600); + + // Return the filename: + return $keyFile; + } + + /** + * Create an SSH wrapper script for Git to use, to disable host key checking, etc. + * @param $cloneTo + * @param $keyFile + * @return string + */ + protected function writeSshWrapper($cloneTo, $keyFile) + { + $path = dirname($cloneTo . '/temp'); + $wrapperFile = $path . '.sh'; + + $sshFlags = '-o CheckHostIP=no -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o PasswordAuthentication=no'; + + // Write out the wrapper script for this build: + $script = << + * @package PHPCI + * @subpackage Core + */ +class SubversionBuild extends Build +{ + protected $svnCommand = 'svn export -q --non-interactive '; + + /** + * Get the URL to be used to clone this remote repository. + */ + protected function getCloneUrl() + { + $url = $this->getProject()->getReference(); + + if (substr($url, -1) != '/') { + $url .= '/'; + } + + $branch = $this->getBranch(); + + if (empty($branch) || $branch == 'trunk') { + $url .= 'trunk'; + } else { + $url .= 'branches/' . $branch; + } + + return $url; + } + + /** + * @param Builder $builder + * + * @return void + */ + protected function extendSvnCommandFromConfig(Builder $builder) + { + $cmd = $this->svnCommand; + + $svn = $builder->getConfig('svn'); + if ($svn) { + foreach ($svn as $key => $value) { + $cmd .= " --$key $value "; + } + } + + $depth = $builder->getConfig('clone_depth'); + + if (!is_null($depth)) { + $cmd .= ' --depth ' . intval($depth) . ' '; + } + + $this->svnCommand = $cmd; + } + + /** + * Create a working copy by cloning, copying, or similar. + */ + public function createWorkingCopy(Builder $builder, $buildPath) + { + $this->handleConfig($builder, $buildPath); + + $this->extendSvnCommandFromConfig($builder); + + $key = trim($this->getProject()->getSshPrivateKey()); + + if (!empty($key)) { + $success = $this->cloneBySsh($builder, $buildPath); + } else { + $success = $this->cloneByHttp($builder, $buildPath); + } + + if (!$success) { + $builder->logFailure('Failed to export remote subversion repository.'); + return false; + } + + return $this->handleConfig($builder, $buildPath); + } + + /** + * Use an HTTP-based svn export. + */ + protected function cloneByHttp(Builder $builder, $cloneTo) + { + $cmd = $this->svnCommand; + + if ($this->getCommitId() != 'Manual') { + $cmd .= ' -r %s %s "%s"'; + $success = $builder->executeCommand($cmd, $this->getCommitId(), $this->getCloneUrl(), $cloneTo); + } else { + $cmd .= ' %s "%s"'; + $success = $builder->executeCommand($cmd, $this->getCloneUrl(), $cloneTo); + } + + return $success; + } + + /** + * Use an SSH-based svn export. + */ + protected function cloneBySsh(Builder $builder, $cloneTo) + { + $cmd = $this->svnCommand . ' %s "%s"'; + + if (!IS_WIN) { + $keyFile = $this->writeSshKey($cloneTo); + $sshWrapper = $this->writeSshWrapper($cloneTo, $keyFile); + $cmd = 'export SVN_SSH="' . $sshWrapper . '" && ' . $cmd; + } + + $success = $builder->executeCommand($cmd, $this->getCloneUrl(), $cloneTo); + + if (!IS_WIN) { + // Remove the key file and svn wrapper: + unlink($keyFile); + unlink($sshWrapper); + } + + return $success; + } + + /** + * Create an SSH key file on disk for this build. + * @param $cloneTo + * @return string + */ + protected function writeSshKey($cloneTo) + { + $keyPath = dirname($cloneTo . '/temp'); + $keyFile = $keyPath . '.key'; + + // Write the contents of this project's svn key to the file: + file_put_contents($keyFile, $this->getProject()->getSshPrivateKey()); + chmod($keyFile, 0600); + + // Return the filename: + return $keyFile; + } + + /** + * Create an SSH wrapper script for Svn to use, to disable host key checking, etc. + * @param $cloneTo + * @param $keyFile + * @return string + */ + protected function writeSshWrapper($cloneTo, $keyFile) + { + $path = dirname($cloneTo . '/temp'); + $wrapperFile = $path . '.sh'; + + $sshFlags = '-o CheckHostIP=no -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o PasswordAuthentication=no'; + + // Write out the wrapper script for this build: + $script = <<getSeverity()) { + case self::SEVERITY_CRITICAL: + return 'critical'; + + case self::SEVERITY_HIGH: + return 'high'; + + case self::SEVERITY_NORMAL: + return 'normal'; + + case self::SEVERITY_LOW: + return 'low'; + } + } + + /** + * Get the class to apply to HTML elements representing this error. + * @return string + */ + public function getSeverityClass() + { + switch ($this->getSeverity()) { + case self::SEVERITY_CRITICAL: + return 'danger'; + + case self::SEVERITY_HIGH: + return 'warning'; + + case self::SEVERITY_NORMAL: + return 'info'; + + case self::SEVERITY_LOW: + return 'default'; + } + } +} diff --git a/PHPCI/Model/BuildMeta.php b/PHPCI/Model/BuildMeta.php index caa4b2fa..fc2af129 100644 --- a/PHPCI/Model/BuildMeta.php +++ b/PHPCI/Model/BuildMeta.php @@ -1,7 +1,10 @@ $branch, 'project_id' => $this->getId()); @@ -43,4 +49,109 @@ class Project extends ProjectBase return null; } + + /** + * Return the previous build from a specific branch, for this project. + * @param string $branch + * @return mixed|null + */ + public function getPreviousBuild($branch = 'master') + { + $criteria = array('branch' => $branch, 'project_id' => $this->getId()); + + $order = array('id' => 'DESC'); + $builds = Store\Factory::getStore('Build')->getWhere($criteria, 1, 1, array(), $order); + + if (is_array($builds['items']) && count($builds['items'])) { + $previous = array_shift($builds['items']); + + if (isset($previous) && $previous instanceof Build) { + return $previous; + } + } + + return null; + } + + /** + * Store this project's access_information data + * @param string|array $value + */ + public function setAccessInformation($value) + { + if (is_array($value)) { + $value = json_encode($value); + } + + parent::setAccessInformation($value); + } + + /** + * Get this project's access_information data. Pass a specific key or null for all data. + * @param string|null $key + * @return mixed|null|string + */ + public function getAccessInformation($key = null) + { + $info = $this->data['access_information']; + + // Handle old-format (serialized) access information first: + if (!empty($info) && !in_array(substr($info, 0, 1), array('{', '['))) { + $data = unserialize($info); + } else { + $data = json_decode($info, true); + } + + if (is_null($key)) { + $rtn = $data; + } elseif (isset($data[$key])) { + $rtn = $data[$key]; + } else { + $rtn = null; + } + + return $rtn; + } + + /** + * Get the value of Branch / branch. + * + * @return string + */ + public function getBranch() + { + if (empty($this->data['branch'])) { + return $this->getType() === 'hg' ? 'default' : 'master'; + } else { + return $this->data['branch']; + } + } + + /** + * Return the name of a FontAwesome icon to represent this project, depending on its type. + * @return string + */ + public function getIcon() + { + switch ($this->getType()) { + case 'github': + $icon = 'github'; + break; + + case 'bitbucket': + $icon = 'bitbucket'; + break; + + case 'git': + case 'gitlab': + $icon = 'git'; + break; + + default: + $icon = 'cog'; + break; + } + + return $icon; + } } diff --git a/PHPCI/Model/ProjectGroup.php b/PHPCI/Model/ProjectGroup.php new file mode 100644 index 00000000..f85e6339 --- /dev/null +++ b/PHPCI/Model/ProjectGroup.php @@ -0,0 +1,18 @@ + */ interface Plugin { - public function __construct(Builder $phpci, Build $build, array $options = array()); public function execute(); } diff --git a/PHPCI/Plugin/Atoum.php b/PHPCI/Plugin/Atoum.php index 7a837b0a..877f009b 100644 --- a/PHPCI/Plugin/Atoum.php +++ b/PHPCI/Plugin/Atoum.php @@ -1,16 +1,34 @@ phpci = $phpci; @@ -35,6 +53,10 @@ class Atoum implements \PHPCI\Plugin } } + /** + * Run the Atoum plugin. + * @return bool + */ public function execute() { $cmd = $this->executable; @@ -49,18 +71,18 @@ class Atoum implements \PHPCI\Plugin $dirPath = $this->phpci->buildPath . DIRECTORY_SEPARATOR . $this->directory; $cmd .= " -d '{$dirPath}'"; } - + chdir($this->phpci->buildPath); $output = ''; $status = true; exec($cmd, $output); if (count(preg_grep("/Success \(/", $output)) == 0) { $status = false; - $this->phpci->log($output, ' '); + $this->phpci->log($output); } if (count($output) == 0) { $status = false; - $this->phpci->log("No test have been performed!", ' '); + $this->phpci->log(Lang::get('no_tests_performed')); } return $status; diff --git a/PHPCI/Plugin/Behat.php b/PHPCI/Plugin/Behat.php index 704d76cf..d63016dc 100644 --- a/PHPCI/Plugin/Behat.php +++ b/PHPCI/Plugin/Behat.php @@ -2,15 +2,17 @@ /** * PHPCI - Continuous Integration for PHP * - * @copyright Copyright 2013, Block 8 Limited. + * @copyright Copyright 2014, Block 8 Limited. * @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md - * @link http://www.phptesting.org/ + * @link https://www.phptesting.org/ */ namespace PHPCI\Plugin; use PHPCI\Builder; +use PHPCI\Helper\Lang; use PHPCI\Model\Build; +use PHPCI\Model\BuildError; /** * Behat BDD Plugin @@ -21,13 +23,33 @@ use PHPCI\Model\Build; class Behat implements \PHPCI\Plugin { protected $phpci; + protected $build; protected $features; + /** + * Standard Constructor + * + * $options['directory'] Output Directory. Default: %BUILDPATH% + * $options['filename'] Phar Filename. Default: build.phar + * $options['regexp'] Regular Expression Filename Capture. Default: /\.php$/ + * $options['stub'] Stub Content. No Default Value + * + * @param Builder $phpci + * @param Build $build + * @param array $options + */ public function __construct(Builder $phpci, Build $build, array $options = array()) { - $this->phpci = $phpci; + $this->phpci = $phpci; + $this->build = $build; $this->features = ''; + if (isset($options['executable'])) { + $this->executable = $options['executable']; + } else { + $this->executable = $this->phpci->findBinary('behat'); + } + if (!empty($options['features'])) { $this->features = $options['features']; } @@ -41,16 +63,76 @@ class Behat implements \PHPCI\Plugin $curdir = getcwd(); chdir($this->phpci->buildPath); - $behat = $this->phpci->findBinary('behat'); + $behat = $this->executable; if (!$behat) { - $this->phpci->logFailure('Could not find behat.'); + $this->phpci->logFailure(Lang::get('could_not_find', 'behat')); + return false; } - $success = $this->phpci->executeCommand($behat . ' --no-time --format="failed" %s', $this->features); + $success = $this->phpci->executeCommand($behat . ' %s', $this->features); chdir($curdir); + list($errorCount, $data) = $this->parseBehatOutput(); + + $this->build->storeMeta('behat-warnings', $errorCount); + $this->build->storeMeta('behat-data', $data); + return $success; } + + /** + * Parse the behat output and return details on failures + * + * @return array + */ + public function parseBehatOutput() + { + $output = $this->phpci->getLastOutput(); + + $parts = explode('---', $output); + + if (count($parts) <= 1) { + return array(0, array()); + } + + $lines = explode(PHP_EOL, $parts[1]); + + $storeFailures = false; + $data = array(); + + foreach ($lines as $line) { + $line = trim($line); + if ($line == 'Failed scenarios:') { + $storeFailures = true; + continue; + } + + if (strpos($line, ':') === false) { + $storeFailures = false; + } + + if ($storeFailures) { + $lineParts = explode(':', $line); + $data[] = array( + 'file' => $lineParts[0], + 'line' => $lineParts[1] + ); + + $this->build->reportError( + $this->phpci, + 'behat', + 'Behat scenario failed.', + BuildError::SEVERITY_HIGH, + $lineParts[0], + $lineParts[1] + ); + } + } + + $errorCount = count($data); + + return array($errorCount, $data); + } } diff --git a/PHPCI/Plugin/Campfire.php b/PHPCI/Plugin/Campfire.php index 17b7f077..59ab9128 100644 --- a/PHPCI/Plugin/Campfire.php +++ b/PHPCI/Plugin/Campfire.php @@ -1,8 +1,16 @@ phpci = $phpci; - $this->build = $build; - - $this->message = $options['message']; + $this->phpci = $phpci; + $this->build = $build; + $this->message = $options['message']; $this->userAgent = "PHPCI/1.0 (+http://www.phptesting.org/)"; - $this->cookie = "phpcicookie"; + $this->cookie = "phpcicookie"; $buildSettings = $phpci->getConfig('build_settings'); + if (isset($buildSettings['campfire'])) { - $campfire = $buildSettings['campfire']; - $this->url = $campfire['url']; + $campfire = $buildSettings['campfire']; + $this->url = $campfire['url']; $this->authToken = $campfire['authToken']; - $this->roomId = $campfire['roomId']; + $this->roomId = $campfire['roomId']; } else { - throw new \Exception("No connection parameters given for Campfire plugin"); + throw new \Exception(Lang::get('no_campfire_settings')); } } + /** + * Run the Campfire plugin. + * @return bool|mixed + */ public function execute() { - $url = PHPCI_URL."build/view/".$this->build->getId(); + $url = PHPCI_URL . "build/view/" . $this->build->getId(); $message = str_replace("%buildurl%", $url, $this->message); $this->joinRoom($this->roomId); $status = $this->speak($message, $this->roomId); @@ -54,19 +73,35 @@ class Campfire implements \PHPCI\Plugin } + /** + * Join a Campfire room. + * @param $roomId + */ public function joinRoom($roomId) { $this->getPageByPost('/room/'.$roomId.'/join.json'); } + /** + * Leave a Campfire room. + * @param $roomId + */ public function leaveRoom($roomId) { $this->getPageByPost('/room/'.$roomId.'/leave.json'); } + /** + * Send a message to a campfire room. + * @param $message + * @param $roomId + * @param bool $isPaste + * @return bool|mixed + */ public function speak($message, $roomId, $isPaste = false) { $page = '/room/'.$roomId.'/speak.json'; + if ($isPaste) { $type = 'PasteMessage'; } else { @@ -77,6 +112,12 @@ class Campfire implements \PHPCI\Plugin } + /** + * Make a request to Campfire. + * @param $page + * @param null $data + * @return bool|mixed + */ private function getPageByPost($page, $data = null) { $url = $this->url . $page; @@ -103,12 +144,13 @@ class Campfire implements \PHPCI\Plugin // We tend to get one space with an otherwise blank response $output = trim($output); + if (strlen($output)) { /* Responses are JSON. Decode it to a data structure */ return json_decode($output); } + // Simple 200 OK response (such as for joining a room) - // TODO: check for other result codes here return true; } } diff --git a/PHPCI/Plugin/CleanBuild.php b/PHPCI/Plugin/CleanBuild.php index a7db4fc9..684e8e7a 100644 --- a/PHPCI/Plugin/CleanBuild.php +++ b/PHPCI/Plugin/CleanBuild.php @@ -1,11 +1,11 @@ phpci = $phpci; - $this->remove = isset($options['remove']) && is_array($options['remove']) ? $options['remove'] : array(); + $this->phpci = $phpci; + $this->build = $build; + $this->remove = isset($options['remove']) && is_array($options['remove']) ? $options['remove'] : array(); } /** @@ -36,11 +50,14 @@ class CleanBuild implements \PHPCI\Plugin public function execute() { $cmd = 'rm -Rf "%s"'; + if (IS_WIN) { + $cmd = 'rmdir /S /Q "%s"'; + } $this->phpci->executeCommand($cmd, $this->phpci->buildPath . 'composer.phar'); $this->phpci->executeCommand($cmd, $this->phpci->buildPath . 'composer.lock'); $success = true; - + foreach ($this->remove as $file) { $ok = $this->phpci->executeCommand($cmd, $this->phpci->buildPath . $file); diff --git a/PHPCI/Plugin/Codeception.php b/PHPCI/Plugin/Codeception.php index f2e580c7..c28e3a48 100644 --- a/PHPCI/Plugin/Codeception.php +++ b/PHPCI/Plugin/Codeception.php @@ -2,86 +2,162 @@ /** * PHPCI - Continuous Integration for PHP * - * @copyright Copyright 2013, Block 8 Limited. + * @copyright Copyright 2014, Block 8 Limited. * @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md - * @link http://www.phptesting.org/ + * @link https://www.phptesting.org/ */ namespace PHPCI\Plugin; use PHPCI\Builder; +use PHPCI\Helper\Lang; use PHPCI\Model\Build; +use PHPCI\Plugin\Util\TestResultParsers\Codeception as Parser; +use Psr\Log\LogLevel; /** * Codeception Plugin - Enables full acceptance, unit, and functional testing. * @author Don Gilbert + * @author Igor Timoshenko + * @author Adam Cooper * @package PHPCI * @subpackage Plugins */ -class Codeception implements \PHPCI\Plugin +class Codeception implements \PHPCI\Plugin, \PHPCI\ZeroConfigPlugin { - protected $args; + /** @var string */ + protected $args = ''; + + /** @var Builder */ protected $phpci; + /** @var Build */ + protected $build; + /** - * @var string|string[] $xmlConfigFile The path (or array of paths) of an xml config for PHPUnit + * @var string $ymlConfigFile The path of a yml config for Codeception */ - protected $xmlConfigFile; + protected $ymlConfigFile; - public function __construct(Builder $phpci, Build $build, array $options = array()) + /** + * @var string $path The path to the codeception tests folder. + */ + protected $path; + + /** + * @param $stage + * @param Builder $builder + * @param Build $build + * @return bool + */ + public static function canExecute($stage, Builder $builder, Build $build) { - $this->phpci = $phpci; + return $stage == 'test' && !is_null(self::findConfigFile($builder->buildPath)); + } - if (isset($options['config'])) { - $this->xmlConfigFile = $options['config']; + /** + * Try and find the codeception YML config file. + * @param $buildPath + * @return null|string + */ + public static function findConfigFile($buildPath) + { + if (file_exists($buildPath . 'codeception.yml')) { + return 'codeception.yml'; } + if (file_exists($buildPath . 'codeception.dist.yml')) { + return 'codeception.dist.yml'; + } + + return null; + } + + /** + * Set up the plugin, configure options, etc. + * @param Builder $phpci + * @param Build $build + * @param array $options + */ + public function __construct(Builder $phpci, Build $build, array $options = array()) + { + $this->phpci = $phpci; + $this->build = $build; + $this->path = 'tests' . DIRECTORY_SEPARATOR . '_output' . DIRECTORY_SEPARATOR; + + if (empty($options['config'])) { + $this->ymlConfigFile = self::findConfigFile($this->phpci->buildPath); + } else { + $this->ymlConfigFile = $options['config']; + } if (isset($options['args'])) { - $this->args = $options['args']; + $this->args = (string) $options['args']; + } + if (isset($options['path'])) { + $this->path = $options['path']; } } /** - * Runs Codeception tests, optionally using specified config file(s). + * Runs Codeception tests */ public function execute() { - $success = true; + if (empty($this->ymlConfigFile)) { + throw new \Exception("No configuration file found"); + } // Run any config files first. This can be either a single value or an array. - if ($this->xmlConfigFile !== null) { - $success &= $this->runConfigFile($this->xmlConfigFile); - } - - return $success; + return $this->runConfigFile($this->ymlConfigFile); } + /** + * Run tests from a Codeception config file. + * @param $configPath + * @return bool|mixed + * @throws \Exception + */ protected function runConfigFile($configPath) { - if (is_array($configPath)) { - return $this->recurseArg($configPath, array($this, "runConfigFile")); - } else { + $this->phpci->logExecOutput(false); - $codecept = $this->phpci->findBinary('codecept'); + $codecept = $this->phpci->findBinary('codecept'); - if (!$codecept) { - $this->phpci->logFailure('Could not find codeception.'); - return false; - } + if (!$codecept) { + $this->phpci->logFailure(Lang::get('could_not_find', 'codecept')); - $cmd = $codecept . ' run -c "%s"'; - $success = $this->phpci->executeCommand($cmd, $this->phpci->buildPath . $configPath); - - return $success; + return false; } - } - protected function recurseArg($array, $callable) - { - $success = true; - foreach ($array as $subItem) { - $success &= call_user_func($callable, $subItem); + $cmd = 'cd "%s" && ' . $codecept . ' run -c "%s" --xml ' . $this->args; + + if (IS_WIN) { + $cmd = 'cd /d "%s" && ' . $codecept . ' run -c "%s" --xml ' . $this->args; } + + $configPath = $this->phpci->buildPath . $configPath; + $success = $this->phpci->executeCommand($cmd, $this->phpci->buildPath, $configPath); + + $this->phpci->log( + 'Codeception XML path: '. $this->phpci->buildPath . $this->path . 'report.xml', + Loglevel::DEBUG + ); + + $xml = file_get_contents($this->phpci->buildPath . $this->path . 'report.xml', false); + $parser = new Parser($this->phpci, $xml); + $output = $parser->parse(); + + $meta = array( + 'tests' => $parser->getTotalTests(), + 'timetaken' => $parser->getTotalTimeTaken(), + 'failures' => $parser->getTotalFailures() + ); + + $this->build->storeMeta('codeception-meta', $meta); + $this->build->storeMeta('codeception-data', $output); + $this->build->storeMeta('codeception-errors', $parser->getTotalFailures()); + $this->phpci->logExecOutput(true); + return $success; } } diff --git a/PHPCI/Plugin/Composer.php b/PHPCI/Plugin/Composer.php index 1af4abf6..7bd86ac3 100644 --- a/PHPCI/Plugin/Composer.php +++ b/PHPCI/Plugin/Composer.php @@ -1,16 +1,18 @@ buildPath . DIRECTORY_SEPARATOR . 'composer.json'; + + if (file_exists($path) && $stage == 'setup') { + return true; + } + + return false; + } + + /** + * Set up the plugin, configure options, etc. + * @param Builder $phpci + * @param Build $build + * @param array $options + */ public function __construct(Builder $phpci, Build $build, array $options = array()) { - $path = $phpci->buildPath; - $this->phpci = $phpci; - $this->directory = isset($options['directory']) ? $path . '/' . $options['directory'] : $path; - $this->action = isset($options['action']) ? $options['action'] : 'update'; - $this->preferDist = isset($options['prefer_dist']) ? $options['prefer_dist'] : true; + $path = $phpci->buildPath; + $this->phpci = $phpci; + $this->build = $build; + $this->directory = $path; + $this->action = 'install'; + $this->preferDist = false; + $this->preferSource = false; + $this->nodev = false; + + if (array_key_exists('directory', $options)) { + $this->directory = $path . DIRECTORY_SEPARATOR . $options['directory']; + } + + if (array_key_exists('action', $options)) { + $this->action = $options['action']; + } + + if (array_key_exists('prefer_dist', $options)) { + $this->preferDist = (bool)$options['prefer_dist']; + } + + if (array_key_exists('prefer_source', $options)) { + $this->preferDist = false; + $this->preferSource = (bool)$options['prefer_source']; + } + + if (array_key_exists('no_dev', $options)) { + $this->nodev = (bool)$options['no_dev']; + } } /** @@ -41,13 +93,30 @@ class Composer implements \PHPCI\Plugin { $composerLocation = $this->phpci->findBinary(array('composer', 'composer.phar')); - if (!$composerLocation) { - $this->phpci->logFailure('Could not find Composer.'); - return false; + $cmd = ''; + + if (IS_WIN) { + $cmd = 'php '; } - $cmd = $composerLocation . ' --no-ansi --no-interaction '; - $cmd .= ($this->preferDist ? '--prefer-dist' : null) . ' --working-dir="%s" %s'; + $cmd .= $composerLocation . ' --no-ansi --no-interaction '; + + if ($this->preferDist) { + $this->phpci->log('Using --prefer-dist flag'); + $cmd .= ' --prefer-dist'; + } + + if ($this->preferSource) { + $this->phpci->log('Using --prefer-source flag'); + $cmd .= ' --prefer-source'; + } + + if ($this->nodev) { + $this->phpci->log('Using --no-dev flag'); + $cmd .= ' --no-dev'; + } + + $cmd .= ' --working-dir="%s" %s'; return $this->phpci->executeCommand($cmd, $this->directory, $this->action); } diff --git a/PHPCI/Plugin/CopyBuild.php b/PHPCI/Plugin/CopyBuild.php index 494a0d3f..f9646cac 100644 --- a/PHPCI/Plugin/CopyBuild.php +++ b/PHPCI/Plugin/CopyBuild.php @@ -1,16 +1,17 @@ buildPath; - $this->phpci = $phpci; - $this->directory = isset($options['directory']) ? $options['directory'] : $path; - $this->ignore = isset($options['respect_ignore']) ? (bool)$options['respect_ignore'] : false; + $path = $phpci->buildPath; + $this->phpci = $phpci; + $this->build = $build; + $this->directory = isset($options['directory']) ? $options['directory'] : $path; + $this->wipe = isset($options['wipe']) ? (bool)$options['wipe'] : false; + $this->ignore = isset($options['respect_ignore']) ? (bool)$options['respect_ignore'] : false; } /** - * Executes Composer and runs a specified command (e.g. install / update) + * Copies files from the root of the build directory into the target folder */ public function execute() { @@ -42,16 +54,49 @@ class CopyBuild implements \PHPCI\Plugin return false; } - $cmd = 'mkdir -p "%s" && ls -1a "%s"* | xargs -r -t "%s/"'; + $this->wipeExistingDirectory(); + + $cmd = 'mkdir -p "%s" && cp -R "%s" "%s"'; + if (IS_WIN) { + $cmd = 'mkdir -p "%s" && xcopy /E "%s" "%s"'; + } + $success = $this->phpci->executeCommand($cmd, $this->directory, $build, $this->directory); - if ($this->ignore) { - foreach ($this->phpci->ignore as $file) { - $cmd = 'rm -Rf "%s/%s"'; - $this->phpci->executeCommand($cmd, $this->directory, $file); - } - } + $this->deleteIgnoredFiles(); return $success; } + + /** + * Wipe the destination directory if it already exists. + * @throws \Exception + */ + protected function wipeExistingDirectory() + { + if ($this->wipe === true && $this->directory != '/' && is_dir($this->directory)) { + $cmd = 'rm -Rf "%s*"'; + $success = $this->phpci->executeCommand($cmd, $this->directory); + + if (!$success) { + throw new \Exception(Lang::get('failed_to_wipe', $this->directory)); + } + } + } + + /** + * Delete any ignored files from the build prior to copying. + */ + protected function deleteIgnoredFiles() + { + if ($this->ignore) { + foreach ($this->phpci->ignore as $file) { + $cmd = 'rm -Rf "%s/%s"'; + if (IS_WIN) { + $cmd = 'rmdir /S /Q "%s\%s"'; + } + $this->phpci->executeCommand($cmd, $this->directory, $file); + } + } + } } diff --git a/PHPCI/Plugin/Deployer.php b/PHPCI/Plugin/Deployer.php new file mode 100644 index 00000000..9c56a340 --- /dev/null +++ b/PHPCI/Plugin/Deployer.php @@ -0,0 +1,73 @@ + +* @package PHPCI +* @subpackage Plugins +*/ +class Deployer implements \PHPCI\Plugin +{ + protected $webhookUrl; + protected $reason; + protected $updateOnly; + + /** + * Set up the plugin, configure options, etc. + * @param Builder $phpci + * @param Build $build + * @param array $options + */ + public function __construct(Builder $phpci, Build $build, array $options = array()) + { + $this->phpci = $phpci; + $this->build = $build; + $this->reason = 'PHPCI Build #%BUILD% - %COMMIT_MESSAGE%'; + + if (isset($options['webhook_url'])) { + $this->webhookUrl = $options['webhook_url']; + } + + if (isset($options['reason'])) { + $this->reason = $options['reason']; + } + + $this->updateOnly = isset($options['update_only']) ? (bool) $options['update_only'] : true; + } + + /** + * Copies files from the root of the build directory into the target folder + */ + public function execute() + { + if (empty($this->webhookUrl)) { + $this->phpci->logFailure('You must specify a webhook URL.'); + return false; + } + + $http = new HttpClient(); + + $response = $http->post($this->webhookUrl, array( + 'reason' => $this->phpci->interpolate($this->reason), + 'source' => 'PHPCI', + 'url' => $this->phpci->interpolate('%BUILD_URI%'), + 'branch' => $this->phpci->interpolate('%BRANCH%'), + 'update_only' => $this->updateOnly + )); + + return $response['success']; + } +} diff --git a/PHPCI/Plugin/Email.php b/PHPCI/Plugin/Email.php index 7b80d795..17e235e2 100644 --- a/PHPCI/Plugin/Email.php +++ b/PHPCI/Plugin/Email.php @@ -1,16 +1,21 @@ getSystemConfig('phpci'); - $this->phpci = $phpci; - $this->build = $build; - $this->options = $options; - $this->emailConfig = isset($phpCiSettings['email_settings']) ? $phpCiSettings['email_settings'] : array(); - - $this->loadSwiftMailerFromConfig(); + public function __construct( + Builder $phpci, + Build $build, + array $options = array() + ) { + $this->phpci = $phpci; + $this->build = $build; + $this->options = $options; } /** - * Connects to MySQL and runs a specified set of queries. - */ + * Send a notification mail. + */ public function execute() { $addresses = $this->getEmailAddresses(); @@ -64,100 +70,95 @@ class Email implements \PHPCI\Plugin return false; } - $subjectTemplate = "PHPCI - %s - %s"; - $projectName = $this->phpci->getBuildProjectTitle(); - $logText = $this->build->getLog(); + $buildStatus = $this->build->isSuccessful() ? "Passing Build" : "Failing Build"; + $projectName = $this->build->getProject()->getTitle(); - if ($this->build->isSuccessful()) { - $sendFailures = $this->sendSeparateEmails( - $addresses, - sprintf($subjectTemplate, $projectName, "Passing Build"), - sprintf("Log Output:
%s
", $logText) - ); - } else { - $sendFailures = $this->sendSeparateEmails( - $addresses, - sprintf($subjectTemplate, $projectName, "Failing Build"), - sprintf("Log Output:
%s
", $logText) + try { + $view = $this->getMailTemplate(); + } catch (Exception $e) { + $this->phpci->log( + sprintf('Unknown mail template "%s", falling back to default.', $this->options['template']), + LogLevel::WARNING ); + $view = $this->getDefaultMailTemplate(); } - // This is a success if we've not failed to send anything. - $this->phpci->log(sprintf("%d emails sent", (count($addresses) - count($sendFailures)))); - $this->phpci->log(sprintf("%d emails failed to send", count($sendFailures))); + $view->build = $this->build; + $view->project = $this->build->getProject(); - return (count($sendFailures) == 0); + $layout = new View('Email/layout'); + $layout->build = $this->build; + $layout->project = $this->build->getProject(); + $layout->content = $view->render(); + $body = $layout->render(); + + $sendFailures = $this->sendSeparateEmails( + $addresses, + sprintf("PHPCI - %s - %s", $projectName, $buildStatus), + $body + ); + + // This is a success if we've not failed to send anything. + $this->phpci->log(sprintf("%d emails sent", (count($addresses) - $sendFailures))); + $this->phpci->log(sprintf("%d emails failed to send", $sendFailures)); + + return ($sendFailures === 0); } /** - * @param array|string $toAddresses Array or single address to send to - * @param string $subject Email subject - * @param string $body Email body + * @param string $toAddress Single address to send to + * @param string[] $ccList + * @param string $subject Email subject + * @param string $body Email body * @return array Array of failed addresses */ - public function sendEmail($toAddresses, $subject, $body) + protected function sendEmail($toAddress, $ccList, $subject, $body) { - $message = \Swift_Message::newInstance($subject) - ->setFrom($this->getMailConfig('from_address')) - ->setTo($toAddresses) - ->setBody($body) - ->setContentType("text/html"); - $failedAddresses = array(); - $this->mailer->send($message, $failedAddresses); + $email = new EmailHelper(); - return $failedAddresses; + $email->setEmailTo($toAddress, $toAddress); + $email->setSubject($subject); + $email->setBody($body); + $email->setHtml(true); + + if (is_array($ccList) && count($ccList)) { + foreach ($ccList as $address) { + $email->addCc($address, $address); + } + } + + return $email->send(); } + /** + * Send an email to a list of specified subjects. + * + * @param array $toAddresses + * List of destination addresses for message. + * @param string $subject + * Mail subject + * @param string $body + * Mail body + * + * @return int number of failed messages + */ public function sendSeparateEmails(array $toAddresses, $subject, $body) { - $failures = array(); + $failures = 0; + $ccList = $this->getCcAddresses(); + foreach ($toAddresses as $address) { - $newFailures = $this->sendEmail($address, $subject, $body); - foreach ($newFailures as $failure) { - $failures[] = $failure; + if (!$this->sendEmail($address, $ccList, $subject, $body)) { + $failures++; } } return $failures; } - protected function loadSwiftMailerFromConfig() - { - /** @var \Swift_SmtpTransport $transport */ - $transport = \Swift_SmtpTransport::newInstance( - $this->getMailConfig('smtp_address'), - $this->getMailConfig('smtp_port'), - $this->getMailConfig('smtp_encryption') - ); - $transport->setUsername($this->getMailConfig('smtp_username')); - $transport->setPassword($this->getMailConfig('smtp_password')); - - $this->mailer = \Swift_Mailer::newInstance($transport); - } - - protected function getMailConfig($configName) - { - if (isset($this->emailConfig[$configName]) && $this->emailConfig[$configName] != "") { - return $this->emailConfig[$configName]; - } else { - // Check defaults - - switch($configName) { - case 'smtp_address': - return "localhost"; - case 'default_mailto_address': - return null; - case 'smtp_port': - return '25'; - case 'smtp_encryption': - return null; - case 'from_address': - return "notifications-ci@phptesting.org"; - default: - return ""; - } - } - } - + /** + * Get the list of email addresses to send to. + * @return array + */ protected function getEmailAddresses() { $addresses = array(); @@ -173,10 +174,54 @@ class Email implements \PHPCI\Plugin } } - if (isset($this->options['default_mailto_address'])) { + if (empty($addresses) && isset($this->options['default_mailto_address'])) { $addresses[] = $this->options['default_mailto_address']; - return $addresses; } - return $addresses; + + return array_unique($addresses); + } + + /** + * Get the list of email addresses to CC. + * + * @return array + */ + protected function getCcAddresses() + { + $ccAddresses = array(); + + if (isset($this->options['cc'])) { + foreach ($this->options['cc'] as $address) { + $ccAddresses[] = $address; + } + } + + return $ccAddresses; + } + + /** + * Get the mail template used to sent the mail. + * + * @return View + */ + protected function getMailTemplate() + { + if (isset($this->options['template'])) { + return new View('Email/' . $this->options['template']); + } + + return $this->getDefaultMailTemplate(); + } + + /** + * Get the default mail template. + * + * @return View + */ + protected function getDefaultMailTemplate() + { + $template = $this->build->isSuccessful() ? 'short' : 'long'; + + return new View('Email/' . $template); } } diff --git a/PHPCI/Plugin/Env.php b/PHPCI/Plugin/Env.php index 09ffbb5a..b89bf67d 100644 --- a/PHPCI/Plugin/Env.php +++ b/PHPCI/Plugin/Env.php @@ -1,15 +1,16 @@ phpci = $phpci; + $this->phpci = $phpci; + $this->build = $build; $this->env_vars = $options; } @@ -43,10 +52,10 @@ class Env implements \PHPCI\Plugin // This allows the standard syntax: "FOO: bar" $env_var = "$key=$value"; } - + if (!putenv($this->phpci->interpolate($env_var))) { $success = false; - $this->phpci->logFailure("Unable to set environment variable"); + $this->phpci->logFailure(Lang::get('unable_to_set_env')); } } return $success; diff --git a/PHPCI/Plugin/FlowdockNotify.php b/PHPCI/Plugin/FlowdockNotify.php new file mode 100644 index 00000000..8ac9f2ab --- /dev/null +++ b/PHPCI/Plugin/FlowdockNotify.php @@ -0,0 +1,73 @@ + + * @package PHPCI + * @subpackage Plugins + */ +class FlowdockNotify implements \PHPCI\Plugin +{ + private $api_key; + private $email; + const MESSAGE_DEFAULT = 'Build %BUILD% has finished for commit %SHORT_COMMIT% + (%COMMIT_EMAIL%)> on branch %BRANCH%'; + + /** + * Set up the plugin, configure options, etc. + * @param Builder $phpci + * @param Build $build + * @param array $options + * @throws \Exception + */ + public function __construct(Builder $phpci, Build $build, array $options = array()) + { + $this->phpci = $phpci; + $this->build = $build; + if (!is_array($options) || !isset($options['api_key'])) { + throw new \Exception('Please define the api_key for Flowdock Notify plugin!'); + } + $this->api_key = trim($options['api_key']); + $this->message = isset($options['message']) ? $options['message'] : self::MESSAGE_DEFAULT; + $this->email = isset($options['email']) ? $options['email'] : 'PHPCI'; + } + + /** + * Run the Flowdock plugin. + * @return bool + * @throws \Exception + */ + public function execute() + { + + $message = $this->phpci->interpolate($this->message); + $successfulBuild = $this->build->isSuccessful() ? 'Success' : 'Failed'; + $push = new Push($this->api_key); + $flowMessage = TeamInboxMessage::create() + ->setSource("PHPCI") + ->setFromAddress($this->email) + ->setFromName($this->build->getProject()->getTitle()) + ->setSubject($successfulBuild) + ->setTags(['#ci']) + ->setLink($this->build->getBranchLink()) + ->setContent($message); + + if (!$push->sendTeamInboxMessage($flowMessage, array('connect_timeout' => 5000, 'timeout' => 5000))) { + throw new \Exception(sprintf('Flowdock Failed: %s', $flowMessage->getResponseErrors())); + } + return true; + } +} diff --git a/PHPCI/Plugin/Git.php b/PHPCI/Plugin/Git.php new file mode 100644 index 00000000..60d6b551 --- /dev/null +++ b/PHPCI/Plugin/Git.php @@ -0,0 +1,174 @@ + + * @package PHPCI + * @subpackage Plugins + */ +class Git implements \PHPCI\Plugin +{ + protected $phpci; + protected $build; + protected $actions = array(); + + /** + * Set up the plugin, configure options, etc. + * @param Builder $phpci + * @param Build $build + * @param array $options + */ + public function __construct(Builder $phpci, Build $build, array $options = array()) + { + $this->phpci = $phpci; + $this->build = $build; + $this->actions = $options; + } + + /** + * Run the Git plugin. + * @return bool + */ + public function execute() + { + $buildPath = $this->phpci->buildPath; + + // Check if there are any actions to be run for the branch we're running on: + if (!array_key_exists($this->build->getBranch(), $this->actions)) { + return true; + } + + // If there are, run them: + $curdir = getcwd(); + chdir($buildPath); + + $success = true; + foreach ($this->actions[$this->build->getBranch()] as $action => $options) { + if (!$this->runAction($action, $options)) { + $success = false; + break; + } + } + + chdir($curdir); + + return $success; + } + + /** + * Determine which action to run, and run it. + * @param $action + * @param array $options + * @return bool + */ + protected function runAction($action, array $options = array()) + { + switch ($action) { + case 'merge': + return $this->runMergeAction($options); + + case 'tag': + return $this->runTagAction($options); + + case 'pull': + return $this->runPullAction($options); + + case 'push': + return $this->runPushAction($options); + } + + + return false; + } + + /** + * Handle a merge action. + * @param $options + * @return bool + */ + protected function runMergeAction($options) + { + if (array_key_exists('branch', $options)) { + $cmd = 'cd "%s" && git checkout %s && git merge "%s"'; + $path = $this->phpci->buildPath; + return $this->phpci->executeCommand($cmd, $path, $options['branch'], $this->build->getBranch()); + } + } + + /** + * Handle a tag action. + * @param $options + * @return bool + */ + protected function runTagAction($options) + { + $tagName = date('Ymd-His'); + $message = Lang::get('tag_created', date('Y-m-d H:i:s')); + + if (array_key_exists('name', $options)) { + $tagName = $this->phpci->interpolate($options['name']); + } + + if (array_key_exists('message', $options)) { + $message = $this->phpci->interpolate($options['message']); + } + + $cmd = 'git tag %s -m "%s"'; + return $this->phpci->executeCommand($cmd, $tagName, $message); + } + + /** + * Handle a pull action. + * @param $options + * @return bool + */ + protected function runPullAction($options) + { + $branch = $this->build->getBranch(); + $remote = 'origin'; + + if (array_key_exists('branch', $options)) { + $branch = $this->phpci->interpolate($options['branch']); + } + + if (array_key_exists('remote', $options)) { + $remote = $this->phpci->interpolate($options['remote']); + } + + return $this->phpci->executeCommand('git pull %s %s', $remote, $branch); + } + + /** + * Handle a push action. + * @param $options + * @return bool + */ + protected function runPushAction($options) + { + $branch = $this->build->getBranch(); + $remote = 'origin'; + + if (array_key_exists('branch', $options)) { + $branch = $this->phpci->interpolate($options['branch']); + } + + if (array_key_exists('remote', $options)) { + $remote = $this->phpci->interpolate($options['remote']); + } + + return $this->phpci->executeCommand('git push %s %s', $remote, $branch); + } +} diff --git a/PHPCI/Plugin/Grunt.php b/PHPCI/Plugin/Grunt.php index 477a4849..4d62ffd8 100644 --- a/PHPCI/Plugin/Grunt.php +++ b/PHPCI/Plugin/Grunt.php @@ -1,11 +1,11 @@ buildPath; + $this->build = $build; $this->phpci = $phpci; $this->directory = $path; $this->task = null; @@ -38,7 +52,7 @@ class Grunt implements \PHPCI\Plugin // Handle options: if (isset($options['directory'])) { - $this->directory = $path . '/' . $options['directory']; + $this->directory = $path . DIRECTORY_SEPARATOR . $options['directory']; } if (isset($options['task'])) { @@ -60,12 +74,19 @@ class Grunt implements \PHPCI\Plugin public function execute() { // if npm does not work, we cannot use grunt, so we return false - if (!$this->phpci->executeCommand('cd %s && npm install', $this->directory)) { + $cmd = 'cd %s && npm install'; + if (IS_WIN) { + $cmd = 'cd /d %s && npm install'; + } + if (!$this->phpci->executeCommand($cmd, $this->directory)) { return false; } // build the grunt command $cmd = 'cd %s && ' . $this->grunt; + if (IS_WIN) { + $cmd = 'cd /d %s && ' . $this->grunt; + } $cmd .= ' --no-color'; $cmd .= ' --gruntfile %s'; $cmd .= ' %s'; // the task that will be executed diff --git a/PHPCI/Plugin/Gulp.php b/PHPCI/Plugin/Gulp.php new file mode 100644 index 00000000..b6c6cab4 --- /dev/null +++ b/PHPCI/Plugin/Gulp.php @@ -0,0 +1,97 @@ + +* @package PHPCI +* @subpackage Plugins +*/ +class Gulp implements \PHPCI\Plugin +{ + protected $directory; + protected $task; + protected $preferDist; + protected $phpci; + protected $build; + protected $gulp; + protected $gulpfile; + + /** + * Standard Constructor + * + * $options['directory'] Output Directory. Default: %BUILDPATH% + * $options['filename'] Phar Filename. Default: build.phar + * $options['regexp'] Regular Expression Filename Capture. Default: /\.php$/ + * $options['stub'] Stub Content. No Default Value + * + * @param Builder $phpci + * @param Build $build + * @param array $options + */ + public function __construct(Builder $phpci, Build $build, array $options = array()) + { + $path = $phpci->buildPath; + $this->build = $build; + $this->phpci = $phpci; + $this->directory = $path; + $this->task = null; + $this->gulp = $this->phpci->findBinary('gulp'); + $this->gulpfile = 'gulpfile.js'; + + // Handle options: + if (isset($options['directory'])) { + $this->directory = $path . DIRECTORY_SEPARATOR . $options['directory']; + } + + if (isset($options['task'])) { + $this->task = $options['task']; + } + + if (isset($options['gulp'])) { + $this->gulp = $options['gulp']; + } + + if (isset($options['gulpfile'])) { + $this->gulpfile = $options['gulpfile']; + } + } + + /** + * Executes gulp and runs a specified command (e.g. install / update) + */ + public function execute() + { + // if npm does not work, we cannot use gulp, so we return false + $cmd = 'cd %s && npm install'; + if (IS_WIN) { + $cmd = 'cd /d %s && npm install'; + } + if (!$this->phpci->executeCommand($cmd, $this->directory)) { + return false; + } + + // build the gulp command + $cmd = 'cd %s && ' . $this->gulp; + if (IS_WIN) { + $cmd = 'cd /d %s && ' . $this->gulp; + } + $cmd .= ' --no-color'; + $cmd .= ' --gulpfile %s'; + $cmd .= ' %s'; // the task that will be executed + + // and execute it + return $this->phpci->executeCommand($cmd, $this->directory, $this->gulpfile, $this->task); + } +} diff --git a/PHPCI/Plugin/HipchatNotify.php b/PHPCI/Plugin/HipchatNotify.php new file mode 100644 index 00000000..a39b94fe --- /dev/null +++ b/PHPCI/Plugin/HipchatNotify.php @@ -0,0 +1,94 @@ + + * @package PHPCI + * @subpackage Plugins + */ +class HipchatNotify implements \PHPCI\Plugin +{ + protected $authToken; + protected $color; + protected $notify; + + /** + * Set up the plugin, configure options, etc. + * @param Builder $phpci + * @param Build $build + * @param array $options + * @throws \Exception + */ + public function __construct(Builder $phpci, Build $build, array $options = array()) + { + $this->phpci = $phpci; + $this->build = $build; + + $this->userAgent = "PHPCI/1.0 (+http://www.phptesting.org/)"; + $this->cookie = "phpcicookie"; + + if (is_array($options) && isset($options['authToken']) && isset($options['room'])) { + $this->authToken = $options['authToken']; + $this->room = $options['room']; + + if (isset($options['message'])) { + $this->message = $options['message']; + } else { + $this->message = Lang::get('x_built_at_x'); + } + + if (isset($options['color'])) { + $this->color = $options['color']; + } else { + $this->color = 'yellow'; + } + + if (isset($options['notify'])) { + $this->notify = $options['notify']; + } else { + $this->notify = false; + } + } else { + throw new \Exception(Lang::get('hipchat_settings')); + } + + } + + /** + * Run the HipChat plugin. + * @return bool + */ + public function execute() + { + $hipChat = new \HipChat\HipChat($this->authToken); + $message = $this->phpci->interpolate($this->message); + + $result = true; + if (is_array($this->room)) { + foreach ($this->room as $room) { + if (!$hipChat->message_room($room, 'PHPCI', $message, $this->notify, $this->color)) { + $result = false; + } + } + } else { + if (!$hipChat->message_room($this->room, 'PHPCI', $message, $this->notify, $this->color)) { + $result = false; + } + } + + return $result; + } +} diff --git a/PHPCI/Plugin/Irc.php b/PHPCI/Plugin/Irc.php index 6b294d66..a7610a8c 100644 --- a/PHPCI/Plugin/Irc.php +++ b/PHPCI/Plugin/Irc.php @@ -1,8 +1,16 @@ phpci = $phpci; + $this->build = $build; $this->message = $options['message']; $buildSettings = $phpci->getConfig('build_settings'); @@ -38,12 +60,16 @@ class Irc implements \PHPCI\Plugin } } + /** + * Run IRC plugin. + * @return bool + */ public function execute() { $msg = $this->phpci->interpolate($this->message); if (empty($this->server) || empty($this->room) || empty($this->nick)) { - $this->phpci->logFailure('You must configure a server, room and nick.'); + $this->phpci->logFailure(Lang::get('irc_settings')); } if (empty($this->port)) { @@ -51,18 +77,56 @@ class Irc implements \PHPCI\Plugin } $sock = fsockopen($this->server, $this->port); - fputs($sock, 'USER ' . $this->nick . ' phptesting.org ' . $this->nick . ' :' . $this->nick . "\r\n"); - fputs($sock, 'NICK ' . $this->nick . "\r\n"); - fputs($sock, 'PRIVMSG ' . $this->room . ' :' . $msg . "\r\n"); + stream_set_timeout($sock, 1); - while (fgets($sock)) { - // We don't need to do anything, - // but the IRC server doesn't appear to post the message - // unless we wait for responses. - } + $connectCommands = array( + 'USER ' . $this->nick . ' 0 * :' . $this->nick, + 'NICK ' . $this->nick, + ); + $this->executeIrcCommands($sock, $connectCommands); + $this->executeIrcCommand($sock, 'JOIN ' . $this->room); + $this->executeIrcCommand($sock, 'PRIVMSG ' . $this->room . ' :' . $msg); fclose($sock); return true; } + + /** + * @param resource $socket + * @param array $commands + * @return bool + */ + private function executeIrcCommands($socket, array $commands) + { + foreach ($commands as $command) { + fputs($socket, $command . "\n"); + } + + $pingBack = false; + + // almost all servers expect pingback! + while ($response = fgets($socket)) { + $matches = array(); + if (preg_match('/^PING \\:([A-Z0-9]+)/', $response, $matches)) { + $pingBack = $matches[1]; + } + } + + if ($pingBack) { + $command = 'PONG :' . $pingBack . "\n"; + fputs($socket, $command); + } + } + + /** + * + * @param resource $socket + * @param string $command + * @return bool + */ + private function executeIrcCommand($socket, $command) + { + return $this->executeIrcCommands($socket, array($command)); + } } diff --git a/PHPCI/Plugin/Lint.php b/PHPCI/Plugin/Lint.php index ebab146e..a7ddc55c 100644 --- a/PHPCI/Plugin/Lint.php +++ b/PHPCI/Plugin/Lint.php @@ -2,13 +2,14 @@ /** * PHPCI - Continuous Integration for PHP * - * @copyright Copyright 2013, Block 8 Limited. + * @copyright Copyright 2014, Block 8 Limited. * @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md - * @link http://www.phptesting.org/ + * @link https://www.phptesting.org/ */ namespace PHPCI\Plugin; +use PHPCI; use PHPCI\Builder; use PHPCI\Model\Build; @@ -18,17 +19,31 @@ use PHPCI\Model\Build; * @package PHPCI * @subpackage Plugins */ -class Lint implements \PHPCI\Plugin +class Lint implements PHPCI\Plugin { protected $directories; protected $recursive = true; protected $ignore; protected $phpci; + protected $build; + /** + * Standard Constructor + * + * $options['directory'] Output Directory. Default: %BUILDPATH% + * $options['filename'] Phar Filename. Default: build.phar + * $options['regexp'] Regular Expression Filename Capture. Default: /\.php$/ + * $options['stub'] Stub Content. No Default Value + * + * @param Builder $phpci + * @param Build $build + * @param array $options + */ public function __construct(Builder $phpci, Build $build, array $options = array()) { - $this->phpci = $phpci; - $this->directories = array(''); + $this->phpci = $phpci; + $this->build = $build; + $this->directories = array(''); $this->ignore = $phpci->ignore; if (!empty($options['directory'])) { @@ -65,19 +80,32 @@ class Lint implements \PHPCI\Plugin return $success; } + /** + * Lint an item (file or directory) by calling the appropriate method. + * @param $php + * @param $item + * @param $itemPath + * @return bool + */ protected function lintItem($php, $item, $itemPath) { $success = true; if ($item->isFile() && $item->getExtension() == 'php' && !$this->lintFile($php, $itemPath)) { $success = false; - } elseif ($item->isDir() && $this->recursive && !$this->lintDirectory($php, $itemPath . '/')) { + } elseif ($item->isDir() && $this->recursive && !$this->lintDirectory($php, $itemPath . DIRECTORY_SEPARATOR)) { $success = false; } return $success; } + /** + * Run php -l against a directory of files. + * @param $php + * @param $path + * @return bool + */ protected function lintDirectory($php, $path) { $success = true; @@ -102,6 +130,12 @@ class Lint implements \PHPCI\Plugin return $success; } + /** + * Run php -l against a specific file. + * @param $php + * @param $path + * @return bool + */ protected function lintFile($php, $path) { $success = true; diff --git a/PHPCI/Plugin/Mysql.php b/PHPCI/Plugin/Mysql.php old mode 100755 new mode 100644 index e2fda714..fdb521ef --- a/PHPCI/Plugin/Mysql.php +++ b/PHPCI/Plugin/Mysql.php @@ -1,16 +1,17 @@ phpci = $phpci; + $this->build = $build; + $this->queries = $options; $config = \b8\Database::getConnection('write')->getDetails(); @@ -71,50 +92,53 @@ class Mysql implements \PHPCI\Plugin /** * Connects to MySQL and runs a specified set of queries. + * @return boolean */ public function execute() { - $success = true; - try { $opts = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); - $this->pdo = new PDO('mysql:host=' . $this->host, $this->user, $this->pass, $opts); + $pdo = new PDO('mysql:host=' . $this->host, $this->user, $this->pass, $opts); foreach ($this->queries as $query) { if (!is_array($query)) { // Simple query - $this->pdo->query($this->phpci->interpolate($query)); + $pdo->query($this->phpci->interpolate($query)); } elseif (isset($query['import'])) { // SQL file execution $this->executeFile($query['import']); } else { - throw new \Exception("Invalid command"); + throw new \Exception(Lang::get('invalid_command')); } } } catch (\Exception $ex) { $this->phpci->logFailure($ex->getMessage()); return false; } - - return $success; + return true; } + /** + * @param string $query + * @return boolean + * @throws \Exception + */ protected function executeFile($query) { if (!isset($query['file'])) { - throw new \Exception("Import statement must contain a 'file' key"); + throw new \Exception(Lang::get('import_file_key')); } $import_file = $this->phpci->buildPath . $this->phpci->interpolate($query['file']); if (!is_readable($import_file)) { - throw new \Exception("Cannot open SQL import file: $import_file"); + throw new \Exception(Lang::get('cannot_open_import', $import_file)); } $database = isset($query['database']) ? $this->phpci->interpolate($query['database']) : null; $import_command = $this->getImportCommand($import_file, $database); if (!$this->phpci->executeCommand($import_command)) { - throw new \Exception("Unable to execute SQL file"); + throw new \Exception(Lang::get('unable_to_execute')); } return true; @@ -142,10 +166,11 @@ class Mysql implements \PHPCI\Plugin $args = array( ':import_file' => escapeshellarg($import_file), ':decomp_cmd' => $decomp_cmd, + ':host' => escapeshellarg($this->host), ':user' => escapeshellarg($this->user), ':pass' => escapeshellarg($this->pass), ':database' => ($database === null)? '': escapeshellarg($database), ); - return strtr('cat :import_file :decomp_cmd | mysql -u:user -p:pass :database', $args); + return strtr('cat :import_file :decomp_cmd | mysql -h:host -u:user -p:pass :database', $args); } } diff --git a/PHPCI/Plugin/PackageBuild.php b/PHPCI/Plugin/PackageBuild.php index 3036d13b..fb116640 100644 --- a/PHPCI/Plugin/PackageBuild.php +++ b/PHPCI/Plugin/PackageBuild.php @@ -1,11 +1,11 @@ buildPath; - $this->build = $build; - $this->phpci = $phpci; - $this->directory = isset($options['directory']) ? $options['directory'] : $path; - $this->filename = isset($options['filename']) ? $options['filename'] : 'build'; - $this->format = isset($options['format']) ? $options['format'] : 'zip'; + $path = $phpci->buildPath; + $this->build = $build; + $this->phpci = $phpci; + $this->directory = isset($options['directory']) ? $options['directory'] : $path; + $this->filename = isset($options['filename']) ? $options['filename'] : 'build'; + $this->format = isset($options['format']) ? $options['format'] : 'zip'; } /** @@ -40,7 +46,7 @@ class PackageBuild implements \PHPCI\Plugin */ public function execute() { - $path = $this->phpci->buildPath; + $path = $this->phpci->buildPath; $build = $this->build; if ($this->directory == $path) { @@ -63,8 +69,7 @@ class PackageBuild implements \PHPCI\Plugin } foreach ($this->format as $format) { - switch($format) - { + switch ($format) { case 'tar': $cmd = 'tar cfz "%s/%s.tar.gz" ./*'; break; diff --git a/PHPCI/Plugin/Pdepend.php b/PHPCI/Plugin/Pdepend.php index 70b5e530..faef406d 100644 --- a/PHPCI/Plugin/Pdepend.php +++ b/PHPCI/Plugin/Pdepend.php @@ -2,14 +2,15 @@ /** * PHPCI - Continuous Integration for PHP * - * @copyright Copyright 2013, Block 8 Limited. + * @copyright Copyright 2014, Block 8 Limited. * @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md - * @link http://www.phptesting.org/ + * @link https://www.phptesting.org/ */ namespace PHPCI\Plugin; use PHPCI\Builder; +use PHPCI\Helper\Lang; use PHPCI\Model\Build; /** @@ -47,6 +48,12 @@ class Pdepend implements \PHPCI\Plugin */ protected $location; + /** + * Set up the plugin, configure options, etc. + * @param Builder $phpci + * @param Build $build + * @param array $options + */ public function __construct(Builder $phpci, Build $build, array $options = array()) { $this->phpci = $phpci; @@ -66,21 +73,19 @@ class Pdepend implements \PHPCI\Plugin */ public function execute() { + if (!file_exists($this->location)) { + mkdir($this->location); + } if (!is_writable($this->location)) { - throw new \Exception(sprintf('The location %s is not writable.', $this->location)); + throw new \Exception(sprintf('The location %s is not writable or does not exist.', $this->location)); } $pdepend = $this->phpci->findBinary('pdepend'); - if (!$pdepend) { - $this->phpci->logFailure('Could not find pdepend.'); - return false; - } - $cmd = $pdepend . ' --summary-xml="%s" --jdepend-chart="%s" --overview-pyramid="%s" %s "%s"'; $this->removeBuildArtifacts(); - + // If we need to ignore directories if (count($this->phpci->ignore)) { $ignore = ' --ignore=' . implode(',', $this->phpci->ignore); @@ -110,11 +115,8 @@ class Pdepend implements \PHPCI\Plugin $config['url'] . '/build/pdepend/' . $this->pyramid ) ); - } else { - $this->phpci->logFailure(sprintf("The function '%s' failed")); } - return $success; } diff --git a/PHPCI/Plugin/Pgsql.php b/PHPCI/Plugin/Pgsql.php index 45155ecf..60057464 100644 --- a/PHPCI/Plugin/Pgsql.php +++ b/PHPCI/Plugin/Pgsql.php @@ -1,11 +1,11 @@ phpci = $phpci; - $this->queries = $options; + $this->phpci = $phpci; + $this->build = $build; + $this->queries = $options; $buildSettings = $phpci->getConfig('build_settings'); if (isset($buildSettings['pgsql'])) { - $sql = $buildSettings['pgsql']; + $sql = $buildSettings['pgsql']; $this->host = $sql['host']; $this->user = $sql['user']; $this->pass = $sql['pass']; @@ -44,8 +73,9 @@ class Pgsql implements \PHPCI\Plugin } /** - * Connects to PgSQL and runs a specified set of queries. - */ + * Connects to PgSQL and runs a specified set of queries. + * @return boolean + */ public function execute() { try { @@ -53,13 +83,12 @@ class Pgsql implements \PHPCI\Plugin $pdo = new PDO('pgsql:host=' . $this->host, $this->user, $this->pass, $opts); foreach ($this->queries as $query) { - $pdo->query($query); + $pdo->query($this->phpci->interpolate($query)); } } catch (\Exception $ex) { $this->phpci->logFailure($ex->getMessage()); return false; } - return true; } } diff --git a/PHPCI/Plugin/Phar.php b/PHPCI/Plugin/Phar.php new file mode 100644 index 00000000..c734bb6c --- /dev/null +++ b/PHPCI/Plugin/Phar.php @@ -0,0 +1,247 @@ +phpci = $phpci; + $this->build = $build; + + // Directory? + if (isset($options['directory'])) { + $this->setDirectory($options['directory']); + } + + // Filename? + if (isset($options['filename'])) { + $this->setFilename($options['filename']); + } + + // RegExp? + if (isset($options['regexp'])) { + $this->setRegExp($options['regexp']); + } + + // Stub? + if (isset($options['stub'])) { + $this->setStub($options['stub']); + } + } + + /** + * Returns PHPCI + * + * @return PHPCI + */ + public function getPHPCI() + { + return $this->phpci; + } + + /** + * Returns Build + * + * @return Build + */ + public function getBuild() + { + return $this->build; + } + + /** + * Directory Setter + * + * @param string $directory Configuration Value + * @return Phar Fluent Interface + */ + public function setDirectory($directory) + { + $this->directory = $directory; + return $this; + } + + /** + * Directory Getter + * + * @return string Configurated or Default Value + */ + public function getDirectory() + { + if (!isset($this->directory)) { + $this->setDirectory($this->getPHPCI()->buildPath); + } + return $this->directory; + } + + /** + * Filename Setter + * + * @param string $filename Configuration Value + * @return Phar Fluent Interface + */ + public function setFilename($filename) + { + $this->filename = $filename; + return $this; + } + + /** + * Filename Getter + * + * @return string Configurated or Default Value + */ + public function getFilename() + { + if (!isset($this->filename)) { + $this->setFilename('build.phar'); + } + return $this->filename; + } + + /** + * Regular Expression Setter + * + * @param string $regexp Configuration Value + * @return Phar Fluent Interface + */ + public function setRegExp($regexp) + { + $this->regexp = $regexp; + return $this; + } + + /** + * Regular Expression Getter + * + * @return string Configurated or Default Value + */ + public function getRegExp() + { + if (!isset($this->regexp)) { + $this->setRegExp('/\.php$/'); + } + return $this->regexp; + } + + /** + * Stub Filename Setter + * + * @param string $stub Configuration Value + * @return Phar Fluent Interface + */ + public function setStub($stub) + { + $this->stub = $stub; + return $this; + } + + /** + * Stub Filename Getter + * + * @return string Configurated Value + */ + public function getStub() + { + return $this->stub; + } + + /** + * Get stub content for the Phar file. + * @return string + */ + public function getStubContent() + { + $content = ''; + $filename = $this->getStub(); + if ($filename) { + $content = file_get_contents($this->getPHPCI()->buildPath . DIRECTORY_SEPARATOR . $this->getStub()); + } + return $content; + } + + /** + * Run the phar plugin. + * @return bool + */ + public function execute() + { + $success = false; + + try { + $file = $this->getDirectory() . DIRECTORY_SEPARATOR . $this->getFilename(); + $phar = new PHPPhar($file, 0, $this->getFilename()); + $phar->buildFromDirectory($this->getPHPCI()->buildPath, $this->getRegExp()); + + $stub = $this->getStubContent(); + if ($stub) { + $phar->setStub($stub); + } + + $success = true; + } catch (Exception $e) { + $this->getPHPCI()->log(Lang::get('phar_internal_error')); + $this->getPHPCI()->log($e->getMessage()); + } + + return $success; + } +} diff --git a/PHPCI/Plugin/Phing.php b/PHPCI/Plugin/Phing.php new file mode 100644 index 00000000..e322b72e --- /dev/null +++ b/PHPCI/Plugin/Phing.php @@ -0,0 +1,264 @@ + + * @package PHPCI + * @subpackage Plugins + */ +class Phing implements \PHPCI\Plugin +{ + + private $directory; + private $buildFile = 'build.xml'; + private $targets = array('build'); + private $properties = array(); + private $propertyFile; + + protected $phpci; + protected $build; + + /** + * Set up the plugin, configure options, etc. + * @param Builder $phpci + * @param Build $build + * @param array $options + */ + public function __construct(Builder $phpci, Build $build, array $options = array()) + { + $this->setPhpci($phpci); + $this->build = $build; + + /* + * Set working directory + */ + if (isset($options['directory'])) { + $directory = $phpci->buildPath . DIRECTORY_SEPARATOR . $options['directory']; + } else { + $directory = $phpci->buildPath; + } + + $this->setDirectory($directory); + + /* + * Sen name of a non default build file + */ + if (isset($options['build_file'])) { + $this->setBuildFile($options['build_file']); + } + + if (isset($options['targets'])) { + $this->setTargets($options['targets']); + } + + if (isset($options['properties'])) { + $this->setProperties($options['properties']); + } + + if (isset($options['property_file'])) { + $this->setPropertyFile($options['property_file']); + } + } + + /** + * Executes Phing and runs a specified targets + */ + public function execute() + { + $phingExecutable = $this->phpci->findBinary('phing'); + + $cmd[] = $phingExecutable . ' -f ' . $this->getBuildFilePath(); + + if ($this->getPropertyFile()) { + $cmd[] = '-propertyfile ' . $this->getPropertyFile(); + } + + $cmd[] = $this->propertiesToString(); + + $cmd[] = '-logger phing.listener.DefaultLogger'; + $cmd[] = $this->targetsToString(); + $cmd[] = '2>&1'; + + return $this->phpci->executeCommand(implode(' ', $cmd), $this->directory, $this->targets); + } + + /** + * @return \PHPCI\Builder + */ + public function getPhpci() + { + return $this->phpci; + } + + /** + * @param \PHPCI\Builder $phpci + * + * @return $this + */ + public function setPhpci($phpci) + { + $this->phpci = $phpci; + } + + /** + * @return string + */ + public function getDirectory() + { + return $this->directory; + } + + /** + * @param string $directory + * + * @return $this + */ + public function setDirectory($directory) + { + $this->directory = $directory; + } + + /** + * @return string + */ + public function getTargets() + { + return $this->targets; + } + + /** + * Converts an array of targets into a string. + * @return string + */ + private function targetsToString() + { + return implode(' ', $this->targets); + } + + /** + * @param array|string $targets + * + * @return $this + */ + public function setTargets($targets) + { + if (is_string($targets)) { + $targets = array($targets); + } + + $this->targets = $targets; + } + + /** + * @return string + */ + public function getBuildFile() + { + return $this->buildFile; + } + + /** + * @param mixed $buildFile + * + * @return $this + * @throws \Exception + */ + public function setBuildFile($buildFile) + { + if (!file_exists($this->getDirectory() . $buildFile)) { + throw new \Exception(Lang::get('build_file_missing')); + } + + $this->buildFile = $buildFile; + } + + /** + * Get phing build file path. + * @return string + */ + public function getBuildFilePath() + { + return $this->getDirectory() . $this->buildFile; + } + + /** + * @return mixed + */ + public function getProperties() + { + return $this->properties; + } + + /** + * @return string + */ + public function propertiesToString() + { + /** + * fix the problem when execute phing out of the build dir + * @ticket 748 + */ + if (!isset($this->properties['project.basedir'])) { + $this->properties['project.basedir'] = $this->getDirectory(); + } + + $propertiesString = array(); + + foreach ($this->properties as $name => $value) { + $propertiesString[] = '-D' . $name . '="' . $value . '"'; + } + + return implode(' ', $propertiesString); + } + + /** + * @param array|string $properties + * + * @return $this + */ + public function setProperties($properties) + { + if (is_string($properties)) { + $properties = array($properties); + } + + $this->properties = $properties; + } + + /** + * @return string + */ + public function getPropertyFile() + { + return $this->propertyFile; + } + + /** + * @param string $propertyFile + * + * @return $this + * @throws \Exception + */ + public function setPropertyFile($propertyFile) + { + if (!file_exists($this->getDirectory() . DIRECTORY_SEPARATOR . $propertyFile)) { + throw new \Exception(Lang::get('property_file_missing')); + } + + $this->propertyFile = $propertyFile; + } +} diff --git a/PHPCI/Plugin/PhpCodeSniffer.php b/PHPCI/Plugin/PhpCodeSniffer.php old mode 100755 new mode 100644 index 44fa5893..6f4ed4e0 --- a/PHPCI/Plugin/PhpCodeSniffer.php +++ b/PHPCI/Plugin/PhpCodeSniffer.php @@ -1,16 +1,18 @@ phpci = $phpci; - $this->build = $build; - $this->suffixes = array('php'); - $this->directory = $phpci->buildPath; - $this->standard = 'PSR2'; - $this->tab_width = ''; - $this->encoding = ''; - $this->path = ''; - $this->ignore = $this->phpci->ignore; + $this->phpci = $phpci; + $this->build = $build; + $this->suffixes = array('php'); + $this->directory = $phpci->buildPath; + $this->standard = 'PSR2'; + $this->tab_width = ''; + $this->encoding = ''; + $this->path = ''; + $this->ignore = $this->phpci->ignore; + $this->allowed_warnings = 0; + $this->allowed_errors = 0; + + if (isset($options['zero_config']) && $options['zero_config']) { + $this->allowed_warnings = -1; + $this->allowed_errors = -1; + } if (isset($options['suffixes'])) { $this->suffixes = (array)$options['suffixes']; } - if (isset($options['directory'])) { - $this->directory = $options['directory']; - } - - if (isset($options['standard'])) { - $this->standard = $options['standard']; - } - if (!empty($options['tab_width'])) { $this->tab_width = ' --tab-width='.$options['tab_width']; } @@ -97,12 +125,19 @@ class PhpCodeSniffer implements \PHPCI\Plugin $this->encoding = ' --encoding=' . $options['encoding']; } - if (isset($options['path'])) { - $this->path = $options['path']; - } + $this->setOptions($options); + } - if (isset($options['ignore'])) { - $this->ignore = $options['ignore']; + /** + * Handle this plugin's options. + * @param $options + */ + protected function setOptions($options) + { + foreach (array('directory', 'standard', 'path', 'ignore', 'allowed_warnings', 'allowed_errors') as $key) { + if (array_key_exists($key, $options)) { + $this->{$key} = $options[$key]; + } } } @@ -115,13 +150,10 @@ class PhpCodeSniffer implements \PHPCI\Plugin $phpcs = $this->phpci->findBinary('phpcs'); - if (!$phpcs) { - $this->phpci->logFailure('Could not find phpcs.'); - return false; - } + $this->phpci->logExecOutput(false); - $cmd = $phpcs . ' %s %s %s %s %s "%s"'; - $success = $this->phpci->executeCommand( + $cmd = $phpcs . ' --report=json %s %s %s %s %s "%s"'; + $this->phpci->executeCommand( $cmd, $standard, $suffixes, @@ -132,20 +164,29 @@ class PhpCodeSniffer implements \PHPCI\Plugin ); $output = $this->phpci->getLastOutput(); + list($errors, $warnings) = $this->processReport($output); - $matches = array(); - if (preg_match_all('/WARNING/', $output, $matches)) { - $this->build->storeMeta('phpcs-warnings', count($matches[0])); + $this->phpci->logExecOutput(true); + + $success = true; + $this->build->storeMeta('phpcs-warnings', $warnings); + $this->build->storeMeta('phpcs-errors', $errors); + + if ($this->allowed_warnings != -1 && $warnings > $this->allowed_warnings) { + $success = false; } - $matches = array(); - if (preg_match_all('/ERROR/', $output, $matches)) { - $this->build->storeMeta('phpcs-errors', count($matches[0])); + if ($this->allowed_errors != -1 && $errors > $this->allowed_errors) { + $success = false; } return $success; } + /** + * Process options and produce an arguments string for PHPCS. + * @return array + */ protected function getFlags() { $ignore = ''; @@ -166,4 +207,40 @@ class PhpCodeSniffer implements \PHPCI\Plugin return array($ignore, $standard, $suffixes); } + + /** + * Process the PHPCS output report. + * @param $output + * @return array + * @throws \Exception + */ + protected function processReport($output) + { + $data = json_decode(trim($output), true); + + if (!is_array($data)) { + $this->phpci->log($output); + throw new \Exception(PHPCI\Helper\Lang::get('could_not_process_report')); + } + + $errors = $data['totals']['errors']; + $warnings = $data['totals']['warnings']; + + foreach ($data['files'] as $fileName => $file) { + $fileName = str_replace($this->phpci->buildPath, '', $fileName); + + foreach ($file['messages'] as $message) { + $this->build->reportError( + $this->phpci, + 'php_code_sniffer', + 'PHPCS: ' . $message['message'], + $message['type'] == 'ERROR' ? BuildError::SEVERITY_HIGH : BuildError::SEVERITY_LOW, + $fileName, + $message['line'] + ); + } + } + + return array($errors, $warnings); + } } diff --git a/PHPCI/Plugin/PhpCpd.php b/PHPCI/Plugin/PhpCpd.php index 816bf261..aa076d2d 100755 --- a/PHPCI/Plugin/PhpCpd.php +++ b/PHPCI/Plugin/PhpCpd.php @@ -1,16 +1,18 @@ phpci = $phpci; + $this->build = $build; + $this->path = $phpci->buildPath; - $this->standard = 'PSR1'; $this->ignore = $phpci->ignore; if (!empty($options['path'])) { $this->path = $phpci->buildPath . $options['path']; } - if (!empty($options['standard'])) { - $this->standard = $options['standard']; - } - if (!empty($options['ignore'])) { - $this->ignore = $this->phpci->ignore; + $this->ignore = $options['ignore']; } } @@ -63,7 +69,15 @@ class PhpCpd implements \PHPCI\Plugin $ignore = ''; if (count($this->ignore)) { $map = function ($item) { - return ' --exclude ' . (substr($item, -1) == '/' ? substr($item, 0, -1) : $item); + // remove the trailing slash + $item = rtrim($item, DIRECTORY_SEPARATOR); + + if (is_file(rtrim($this->path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $item)) { + return ' --names-exclude ' . $item; + } else { + return ' --exclude ' . $item; + } + }; $ignore = array_map($map, $this->ignore); @@ -72,15 +86,64 @@ class PhpCpd implements \PHPCI\Plugin $phpcpd = $this->phpci->findBinary('phpcpd'); - if (!$phpcpd) { - $this->phpci->logFailure('Could not find phpcpd.'); - return false; - } + $tmpfilename = tempnam('/tmp', 'phpcpd'); - $success = $this->phpci->executeCommand($phpcpd . ' %s "%s"', $ignore, $this->phpci->buildPath.$this->path); + $cmd = $phpcpd . ' --log-pmd "%s" %s "%s"'; + $success = $this->phpci->executeCommand($cmd, $tmpfilename, $ignore, $this->path); print $this->phpci->getLastOutput(); + $errorCount = $this->processReport(file_get_contents($tmpfilename)); + $this->build->storeMeta('phpcpd-warnings', $errorCount); + + unlink($tmpfilename); + return $success; } + + /** + * Process the PHPCPD XML report. + * @param $xmlString + * @return array + * @throws \Exception + */ + protected function processReport($xmlString) + { + $xml = simplexml_load_string($xmlString); + + if ($xml === false) { + $this->phpci->log($xmlString); + throw new \Exception(Lang::get('could_not_process_report')); + } + + $warnings = 0; + foreach ($xml->duplication as $duplication) { + foreach ($duplication->file as $file) { + $fileName = (string)$file['path']; + $fileName = str_replace($this->phpci->buildPath, '', $fileName); + + $message = <<codefragment} +``` +CPD; + + $this->build->reportError( + $this->phpci, + 'php_cpd', + $message, + BuildError::SEVERITY_NORMAL, + $fileName, + $file['line'], + (int) $file['line'] + (int) $duplication['lines'] + ); + } + + $warnings++; + } + + return $warnings; + } } diff --git a/PHPCI/Plugin/PhpCsFixer.php b/PHPCI/Plugin/PhpCsFixer.php index ca07e9ef..9aa0d33f 100644 --- a/PHPCI/Plugin/PhpCsFixer.php +++ b/PHPCI/Plugin/PhpCsFixer.php @@ -1,40 +1,67 @@ * @package PHPCI * @subpackage Plugins */ class PhpCsFixer implements \PHPCI\Plugin { + /** + * @var \PHPCI\Builder + */ protected $phpci; - protected $workingDir = ''; - protected $level = ' --level=all'; - protected $verbose = ''; - protected $diff = ''; - protected $levels = array('psr0', 'psr1', 'psr2', 'all'); + /** + * @var \PHPCI\Model\Build + */ + protected $build; + protected $workingDir = ''; + protected $level = ' --level=psr2'; + protected $verbose = ''; + protected $diff = ''; + protected $levels = array('psr0', 'psr1', 'psr2', 'symfony'); + + /** + * Standard Constructor + * + * $options['directory'] Output Directory. Default: %BUILDPATH% + * $options['filename'] Phar Filename. Default: build.phar + * $options['regexp'] Regular Expression Filename Capture. Default: /\.php$/ + * $options['stub'] Stub Content. No Default Value + * + * @param Builder $phpci + * @param Build $build + * @param array $options + */ public function __construct(Builder $phpci, Build $build, array $options = array()) { $this->phpci = $phpci; + $this->build = $build; + $this->workingdir = $this->phpci->buildPath; $this->buildArgs($options); } + /** + * Run PHP CS Fixer. + * @return bool + */ public function execute() { $curdir = getcwd(); @@ -42,11 +69,6 @@ class PhpCsFixer implements \PHPCI\Plugin $phpcsfixer = $this->phpci->findBinary('php-cs-fixer'); - if (!$phpcsfixer) { - $this->phpci->logFailure('Could not find php-cs-fixer.'); - return false; - } - $cmd = $phpcsfixer . ' fix . %s %s %s'; $success = $this->phpci->executeCommand($cmd, $this->verbose, $this->diff, $this->level); @@ -55,6 +77,10 @@ class PhpCsFixer implements \PHPCI\Plugin return $success; } + /** + * Build an args string for PHPCS Fixer. + * @param $options + */ public function buildArgs($options) { if (isset($options['verbose']) && $options['verbose']) { diff --git a/PHPCI/Plugin/PhpDocblockChecker.php b/PHPCI/Plugin/PhpDocblockChecker.php new file mode 100644 index 00000000..2396497c --- /dev/null +++ b/PHPCI/Plugin/PhpDocblockChecker.php @@ -0,0 +1,180 @@ + +* @package PHPCI +* @subpackage Plugins +*/ +class PhpDocblockChecker implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin +{ + /** + * @var \PHPCI\Builder + */ + protected $phpci; + + /** + * @var \PHPCI\Model\Build + */ + protected $build; + + /** + * @var string Based on the assumption the root may not hold the code to be + * tested, extends the build path. + */ + protected $path; + + /** + * @var array - paths to ignore + */ + protected $ignore; + + protected $skipClasses = false; + protected $skipMethods = false; + + /** + * Check if this plugin can be executed. + * @param $stage + * @param Builder $builder + * @param Build $build + * @return bool + */ + public static function canExecute($stage, Builder $builder, Build $build) + { + if ($stage == 'test') { + return true; + } + + return false; + } + + /** + * Set up the plugin, configure options, etc. + * @param Builder $phpci + * @param Build $build + * @param array $options + */ + public function __construct(Builder $phpci, Build $build, array $options = array()) + { + $this->phpci = $phpci; + $this->build = $build; + $this->ignore = $phpci->ignore; + $this->path = ''; + $this->allowed_warnings = 0; + + if (isset($options['zero_config']) && $options['zero_config']) { + $this->allowed_warnings = -1; + } + + if (array_key_exists('skip_classes', $options)) { + $this->skipClasses = true; + } + + if (array_key_exists('skip_methods', $options)) { + $this->skipMethods = true; + } + + if (!empty($options['path'])) { + $this->path = $options['path']; + } + + if (array_key_exists('allowed_warnings', $options)) { + $this->allowed_warnings = (int)$options['allowed_warnings']; + } + } + + /** + * Runs PHP Mess Detector in a specified directory. + */ + public function execute() + { + // Check that the binary exists: + $checker = $this->phpci->findBinary('phpdoccheck'); + + // Build ignore string: + $ignore = ''; + if (count($this->ignore)) { + $ignore = ' --exclude="' . implode(',', $this->ignore) . '"'; + } + + // Are we skipping any checks? + $add = ''; + if ($this->skipClasses) { + $add .= ' --skip-classes'; + } + + if ($this->skipMethods) { + $add .= ' --skip-methods'; + } + + // Build command string: + $path = $this->phpci->buildPath . $this->path; + $cmd = $checker . ' --json --directory="%s"%s%s'; + + // Disable exec output logging, as we don't want the XML report in the log: + $this->phpci->logExecOutput(false); + + // Run checker: + $this->phpci->executeCommand( + $cmd, + $path, + $ignore, + $add + ); + + // Re-enable exec output logging: + $this->phpci->logExecOutput(true); + + $output = json_decode($this->phpci->getLastOutput(), true); + $errors = count($output); + $success = true; + + $this->build->storeMeta('phpdoccheck-warnings', $errors); + $this->reportErrors($output); + + if ($this->allowed_warnings != -1 && $errors > $this->allowed_warnings) { + $success = false; + } + + return $success; + } + + /** + * Report all of the errors we've encountered line-by-line. + * @param $output + */ + protected function reportErrors($output) + { + foreach ($output as $error) { + $message = 'Class ' . $error['class'] . ' is missing a docblock.'; + $severity = PHPCI\Model\BuildError::SEVERITY_LOW; + + if ($error['type'] == 'method') { + $message = $error['class'] . '::' . $error['method'] . ' is missing a docblock.'; + $severity = PHPCI\Model\BuildError::SEVERITY_NORMAL; + } + + $this->build->reportError( + $this->phpci, + 'php_docblock_checker', + $message, + $severity, + $error['file'], + $error['line'] + ); + } + } +} diff --git a/PHPCI/Plugin/PhpLoc.php b/PHPCI/Plugin/PhpLoc.php index 809b6409..c8dedb91 100644 --- a/PHPCI/Plugin/PhpLoc.php +++ b/PHPCI/Plugin/PhpLoc.php @@ -2,13 +2,14 @@ /** * PHPCI - Continuous Integration for PHP * - * @copyright Copyright 2013, Block 8 Limited. + * @copyright Copyright 2014, Block 8 Limited. * @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md - * @link http://www.phptesting.org/ + * @link https://www.phptesting.org/ */ namespace PHPCI\Plugin; +use PHPCI; use PHPCI\Builder; use PHPCI\Model\Build; @@ -18,7 +19,7 @@ use PHPCI\Model\Build; * @package PHPCI * @subpackage Plugins */ -class PhpLoc implements \PHPCI\Plugin +class PhpLoc implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin { /** * @var string @@ -29,11 +30,37 @@ class PhpLoc implements \PHPCI\Plugin */ protected $phpci; + /** + * Check if this plugin can be executed. + * @param $stage + * @param Builder $builder + * @param Build $build + * @return bool + */ + public static function canExecute($stage, Builder $builder, Build $build) + { + if ($stage == 'test') { + return true; + } + + return false; + } + + /** + * Set up the plugin, configure options, etc. + * @param Builder $phpci + * @param Build $build + * @param array $options + */ public function __construct(Builder $phpci, Build $build, array $options = array()) { $this->phpci = $phpci; $this->build = $build; - $this->directory = isset($options['directory']) ? $options['directory'] : $phpci->buildPath; + $this->directory = $phpci->buildPath; + + if (isset($options['directory'])) { + $this->directory .= $options['directory']; + } } /** @@ -42,24 +69,20 @@ class PhpLoc implements \PHPCI\Plugin public function execute() { $ignore = ''; - if (count($this->phpci->ignore)) { - $map = function ($item) { - return ' --exclude ' . (substr($item, -1) == '/' ? substr($item, 0, -1) : $item); - }; - $ignore = array_map($map, $this->phpci->ignore); + if (count($this->phpci->ignore)) { + $map = function ($item) { + return ' --exclude ' . rtrim($item, DIRECTORY_SEPARATOR); + }; + + $ignore = array_map($map, $this->phpci->ignore); $ignore = implode('', $ignore); } $phploc = $this->phpci->findBinary('phploc'); - if (!$phploc) { - $this->phpci->logFailure('Could not find phploc.'); - return false; - } - - $success = $this->phpci->executeCommand($phploc . ' %s "%s"', $ignore, $this->phpci->buildPath); - $output = $this->phpci->getLastOutput(); + $success = $this->phpci->executeCommand($phploc . ' %s "%s"', $ignore, $this->directory); + $output = $this->phpci->getLastOutput(); if (preg_match_all('/\((LOC|CLOC|NCLOC|LLOC)\)\s+([0-9]+)/', $output, $matches)) { $data = array(); diff --git a/PHPCI/Plugin/PhpMessDetector.php b/PHPCI/Plugin/PhpMessDetector.php old mode 100755 new mode 100644 index c6291a6e..ec92bc61 --- a/PHPCI/Plugin/PhpMessDetector.php +++ b/PHPCI/Plugin/PhpMessDetector.php @@ -1,14 +1,15 @@ ignore = $phpci->ignore; $this->path = ''; $this->rules = array('codesize', 'unusedcode', 'naming'); + $this->allowed_warnings = 0; + + if (isset($options['zero_config']) && $options['zero_config']) { + $this->allowed_warnings = -1; + } if (!empty($options['path'])) { $this->path = $options['path']; } + if (array_key_exists('allowed_warnings', $options)) { + $this->allowed_warnings = (int)$options['allowed_warnings']; + } + foreach (array('rules', 'ignore', 'suffixes') as $key) { $this->overrideSetting($options, $key); } @@ -75,6 +115,101 @@ class PhpMessDetector implements \PHPCI\Plugin */ public function execute() { + if (!$this->tryAndProcessRules()) { + return false; + } + + $phpmdBinaryPath = $this->phpci->findBinary('phpmd'); + + $this->executePhpMd($phpmdBinaryPath); + + $errorCount = $this->processReport(trim($this->phpci->getLastOutput())); + $this->build->storeMeta('phpmd-warnings', $errorCount); + + return $this->wasLastExecSuccessful($errorCount); + } + + /** + * Override a default setting. + * @param $options + * @param $key + */ + protected function overrideSetting($options, $key) + { + if (isset($options[$key]) && is_array($options[$key])) { + $this->{$key} = $options[$key]; + } + } + + /** + * Process PHPMD's XML output report. + * @param $xmlString + * @return array + * @throws \Exception + */ + protected function processReport($xmlString) + { + $xml = simplexml_load_string($xmlString); + + if ($xml === false) { + $this->phpci->log($xmlString); + throw new \Exception('Could not process PHPMD report XML.'); + } + + $warnings = 0; + + foreach ($xml->file as $file) { + $fileName = (string)$file['name']; + $fileName = str_replace($this->phpci->buildPath, '', $fileName); + + foreach ($file->violation as $violation) { + $warnings++; + + $this->build->reportError( + $this->phpci, + 'php_mess_detector', + (string)$violation, + PHPCI\Model\BuildError::SEVERITY_HIGH, + $fileName, + (int)$violation['beginline'], + (int)$violation['endline'] + ); + } + } + + return $warnings; + } + + /** + * Try and process the rules parameter from phpci.yml. + * @return bool + */ + protected function tryAndProcessRules() + { + if (!empty($this->rules) && !is_array($this->rules)) { + $this->phpci->logFailure('The "rules" option must be an array.'); + return false; + } + + foreach ($this->rules as &$rule) { + if (strpos($rule, '/') !== false) { + $rule = $this->phpci->buildPath . $rule; + } + } + + return true; + } + + /** + * Execute PHP Mess Detector. + * @param $binaryPath + */ + protected function executePhpMd($binaryPath) + { + $cmd = $binaryPath . ' "%s" xml %s %s %s'; + + $path = $this->getTargetPath(); + $ignore = ''; if (count($this->ignore)) { $ignore = ' --exclude ' . implode(',', $this->ignore); @@ -85,38 +220,50 @@ class PhpMessDetector implements \PHPCI\Plugin $suffixes = ' --suffixes ' . implode(',', $this->suffixes); } - foreach ($this->rules as &$rule) { - if ($rule[0] !== '/' && strpos($rule, '/') !== false) { - $rule = $this->phpci->buildPath . $rule; - } - } + // Disable exec output logging, as we don't want the XML report in the log: + $this->phpci->logExecOutput(false); - $phpmd = $this->phpci->findBinary('phpmd'); - - if (!$phpmd) { - $this->phpci->logFailure('Could not find phpmd.'); - return false; - } - - $cmd = $phpmd . ' "%s" text %s %s %s'; - $success = $this->phpci->executeCommand( + // Run PHPMD: + $this->phpci->executeCommand( $cmd, - $this->phpci->buildPath . $this->path, + $path, implode(',', $this->rules), $ignore, $suffixes ); - $errors = count(array_filter(explode(PHP_EOL, $this->phpci->getLastOutput()))); - $this->build->storeMeta('phpmd-warnings', $errors); + // Re-enable exec output logging: + $this->phpci->logExecOutput(true); + } + /** + * Get the path PHPMD should be run against. + * @return string + */ + protected function getTargetPath() + { + $path = $this->phpci->buildPath . $this->path; + if (!empty($this->path) && $this->path{0} == '/') { + $path = $this->path; + return $path; + } + return $path; + } + + /** + * Returns a boolean indicating if the error count can be considered a success. + * + * @param int $errorCount + * @return bool + */ + protected function wasLastExecSuccessful($errorCount) + { + $success = true; + + if ($this->allowed_warnings != -1 && $errorCount > $this->allowed_warnings) { + $success = false; + return $success; + } return $success; } - - protected function overrideSetting($options, $key) - { - if (isset($options[$key]) && is_array($options['key'])) { - $this->{$key} = $options[$key]; - } - } } diff --git a/PHPCI/Plugin/PhpParallelLint.php b/PHPCI/Plugin/PhpParallelLint.php index b7a8cd24..febed528 100644 --- a/PHPCI/Plugin/PhpParallelLint.php +++ b/PHPCI/Plugin/PhpParallelLint.php @@ -1,15 +1,16 @@ buildPath; - $this->phpci = $phpci; - $this->directory = isset($options['directory']) ? $path . $options['directory'] : $path; + $this->phpci = $phpci; + $this->build = $build; + $this->directory = $phpci->buildPath; + $this->ignore = $this->phpci->ignore; + + if (isset($options['directory'])) { + $this->directory = $phpci->buildPath.$options['directory']; + } + + if (isset($options['ignore'])) { + $this->ignore = $options['ignore']; + } } /** @@ -36,10 +74,39 @@ class PhpParallelLint implements \PHPCI\Plugin */ public function execute() { - // build the parallel lint command - $cmd = "run %s"; + list($ignore) = $this->getFlags(); - // and execute it - return $this->phpci->executeCommand(PHPCI_BIN_DIR . $cmd, $this->directory); + $phplint = $this->phpci->findBinary('parallel-lint'); + + $cmd = $phplint . ' %s "%s"'; + $success = $this->phpci->executeCommand( + $cmd, + $ignore, + $this->directory + ); + + $output = $this->phpci->getLastOutput(); + + $matches = array(); + if (preg_match_all('/Parse error\:/', $output, $matches)) { + $this->build->storeMeta('phplint-errors', count($matches[0])); + } + + return $success; + } + + /** + * Produce an argument string for PHP Parallel Lint. + * @return array + */ + protected function getFlags() + { + $ignoreFlags = array(); + foreach ($this->ignore as $ignoreDir) { + $ignoreFlags[] = '--exclude "' . $this->phpci->buildPath . $ignoreDir . '"'; + } + $ignore = implode(' ', $ignoreFlags); + + return array($ignore); } } diff --git a/PHPCI/Plugin/PhpSpec.php b/PHPCI/Plugin/PhpSpec.php index afd36699..e468a718 100644 --- a/PHPCI/Plugin/PhpSpec.php +++ b/PHPCI/Plugin/PhpSpec.php @@ -1,14 +1,15 @@ phpci = $phpci; - - if (!empty($options['bootstrap'])) { - $this->bootstrap = $this->buildPath . $options['bootstrap']; - } + $this->phpci = $phpci; + $this->build = $build; + $this->options = $options; } /** @@ -42,19 +59,91 @@ class PhpSpec implements \PHPCI\Plugin $phpspec = $this->phpci->findBinary(array('phpspec', 'phpspec.php')); - if (!$phpspec) { - $this->phpci->logFailure('Could not find phpspec.'); - return false; - } - - if ($this->bootstrap) { - $success = $this->phpci->executeCommand($phpspec . ' -f d'); - } else { - $success = $this->phpci->executeCommand($phpspec . ' -f d --bootstrap "%s"', $this->bootstrap); - } + $success = $this->phpci->executeCommand($phpspec . ' --format=junit --no-code-generation run'); + $output = $this->phpci->getLastOutput(); chdir($curdir); - + + /* + * process xml output + * + * + * + * + * + * attributes(); + $data = array( + 'time' => (float)$attr['time'], + 'tests' => (int)$attr['tests'], + 'failures' => (int)$attr['failures'], + 'errors' => (int)$attr['errors'], + + // now all the tests + 'suites' => array() + ); + + /** + * @var \SimpleXMLElement $group + */ + foreach ($xml->xpath('testsuite') as $group) { + $attr = $group->attributes(); + $suite = array( + 'name' => (String)$attr['name'], + 'time' => (float)$attr['time'], + 'tests' => (int)$attr['tests'], + 'failures' => (int)$attr['failures'], + 'errors' => (int)$attr['errors'], + 'skipped' => (int)$attr['skipped'], + + // now the cases + 'cases' => array() + ); + + /** + * @var \SimpleXMLElement $child + */ + foreach ($group->xpath('testcase') as $child) { + $attr = $child->attributes(); + $case = array( + 'name' => (String)$attr['name'], + 'classname' => (String)$attr['classname'], + 'time' => (float)$attr['time'], + 'status' => (String)$attr['status'], + ); + + if ($case['status']=='failed') { + $error = array(); + /* + * ok, sad, we had an error + * + * there should be one - foreach makes this easier + */ + foreach ($child->xpath('failure') as $failure) { + $attr = $failure->attributes(); + $error['type'] = (String)$attr['type']; + $error['message'] = (String)$attr['message']; + } + + foreach ($child->xpath('system-err') as $system_err) { + $error['raw'] = (String)$system_err; + } + + $case['error'] = $error; + } + + $suite['cases'][] = $case; + } + + $data['suites'][] = $suite; + } + + $this->build->storeMeta('phpspec', $data); + + return $success; } } diff --git a/PHPCI/Plugin/PhpTalLint.php b/PHPCI/Plugin/PhpTalLint.php new file mode 100644 index 00000000..8513ec36 --- /dev/null +++ b/PHPCI/Plugin/PhpTalLint.php @@ -0,0 +1,265 @@ + + * @package PHPCI + * @subpackage Plugins + */ +class PhpTalLint implements PHPCI\Plugin +{ + protected $directories; + protected $recursive = true; + protected $suffixes; + protected $ignore; + + /** + * @var \PHPCI\Builder + */ + protected $phpci; + + /** + * @var \PHPCI\Model\Build + */ + protected $build; + + /** + * @var string The path to a file contain custom phptal_tales_ functions + */ + protected $tales; + + /** + * @var int + */ + protected $allowed_warnings; + + /** + * @var int + */ + protected $allowed_errors; + + /** + * @var array The results of the lint scan + */ + protected $failedPaths = array(); + + /** + * Standard Constructor + * + * @param Builder $phpci + * @param Build $build + * @param array $options + */ + public function __construct(Builder $phpci, Build $build, array $options = array()) + { + $this->phpci = $phpci; + $this->build = $build; + $this->directories = array(''); + $this->suffixes = array('zpt'); + $this->ignore = $phpci->ignore; + + $this->allowed_warnings = 0; + $this->allowed_errors = 0; + + if (!empty($options['directory'])) { + $this->directories = array($options['directory']); + } + + if (isset($options['suffixes'])) { + $this->suffixes = (array)$options['suffixes']; + } + + $this->setOptions($options); + } + + /** + * Handle this plugin's options. + * @param $options + */ + protected function setOptions($options) + { + foreach (array('directories', 'tales', 'allowed_warnings', 'allowed_errors') as $key) { + if (array_key_exists($key, $options)) { + $this->{$key} = $options[$key]; + } + } + } + + /** + * Executes phptal lint + */ + public function execute() + { + $this->phpci->quiet = true; + $this->phpci->logExecOutput(false); + + foreach ($this->directories as $dir) { + $this->lintDirectory($dir); + } + + $this->phpci->quiet = false; + $this->phpci->logExecOutput(true); + + $errors = 0; + $warnings = 0; + + foreach ($this->failedPaths as $path) { + if ($path['type'] == 'error') { + $errors++; + } else { + $warnings++; + } + } + + $this->build->storeMeta('phptallint-warnings', $warnings); + $this->build->storeMeta('phptallint-errors', $errors); + $this->build->storeMeta('phptallint-data', $this->failedPaths); + + $success = true; + + if ($this->allowed_warnings != -1 && $warnings > $this->allowed_warnings) { + $success = false; + } + + if ($this->allowed_errors != -1 && $errors > $this->allowed_errors) { + $success = false; + } + + return $success; + } + + /** + * Lint an item (file or directory) by calling the appropriate method. + * @param $item + * @param $itemPath + * @return bool + */ + protected function lintItem($item, $itemPath) + { + $success = true; + + if ($item->isFile() && in_array(strtolower($item->getExtension()), $this->suffixes)) { + if (!$this->lintFile($itemPath)) { + $success = false; + } + } elseif ($item->isDir() && $this->recursive && !$this->lintDirectory($itemPath . DIRECTORY_SEPARATOR)) { + $success = false; + } + + return $success; + } + + /** + * Run phptal lint against a directory of files. + * @param $path + * @return bool + */ + protected function lintDirectory($path) + { + $success = true; + $directory = new \DirectoryIterator($this->phpci->buildPath . $path); + + foreach ($directory as $item) { + if ($item->isDot()) { + continue; + } + + $itemPath = $path . $item->getFilename(); + + if (in_array($itemPath, $this->ignore)) { + continue; + } + + if (!$this->lintItem($item, $itemPath)) { + $success = false; + } + } + + return $success; + } + + /** + * Run phptal lint against a specific file. + * @param $path + * @return bool + */ + protected function lintFile($path) + { + $success = true; + + list($suffixes, $tales) = $this->getFlags(); + + $lint = dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR; + $lint .= 'vendor' . DIRECTORY_SEPARATOR . 'phptal' . DIRECTORY_SEPARATOR . 'phptal' . DIRECTORY_SEPARATOR; + $lint .= 'tools' . DIRECTORY_SEPARATOR . 'phptal_lint.php'; + $cmd = '/usr/bin/env php ' . $lint . ' %s %s "%s"'; + + $this->phpci->executeCommand($cmd, $suffixes, $tales, $this->phpci->buildPath . $path); + + $output = $this->phpci->getLastOutput(); + + if (preg_match('/Found (.+?) (error|warning)/i', $output, $matches)) { + $rows = explode(PHP_EOL, $output); + + unset($rows[0]); + unset($rows[1]); + unset($rows[2]); + unset($rows[3]); + + foreach ($rows as $row) { + $name = basename($path); + + $row = str_replace('(use -i to include your custom modifier functions)', '', $row); + $message = str_replace($name . ': ', '', $row); + + $parts = explode(' (line ', $message); + + $message = trim($parts[0]); + $line = str_replace(')', '', $parts[1]); + + $this->failedPaths[] = array( + 'file' => $path, + 'line' => $line, + 'type' => $matches[2], + 'message' => $message + ); + } + + $success = false; + } + + return $success; + } + + /** + * Process options and produce an arguments string for PHPTAL Lint. + * @return array + */ + protected function getFlags() + { + $tales = ''; + if (!empty($this->tales)) { + $tales = ' -i ' . $this->phpci->buildPath . $this->tales; + } + + $suffixes = ''; + if (count($this->suffixes)) { + $suffixes = ' -e ' . implode(',', $this->suffixes); + } + + return array($suffixes, $tales); + } +} diff --git a/PHPCI/Plugin/PhpUnit.php b/PHPCI/Plugin/PhpUnit.php old mode 100755 new mode 100644 index 9b905fb6..f716f079 --- a/PHPCI/Plugin/PhpUnit.php +++ b/PHPCI/Plugin/PhpUnit.php @@ -1,16 +1,18 @@ buildPath))) { + return true; + } + + return false; + } + + /** + * Try and find the phpunit XML config file. + * @param $buildPath + * @return null|string + */ + public static function findConfigFile($buildPath) + { + if (file_exists($buildPath . 'phpunit.xml')) { + return 'phpunit.xml'; + } + + if (file_exists($buildPath . 'tests' . DIRECTORY_SEPARATOR . 'phpunit.xml')) { + return 'tests' . DIRECTORY_SEPARATOR . 'phpunit.xml'; + } + + if (file_exists($buildPath . 'phpunit.xml.dist')) { + return 'phpunit.xml.dist'; + } + + if (file_exists($buildPath . 'tests/phpunit.xml.dist')) { + return 'tests' . DIRECTORY_SEPARATOR . 'phpunit.xml.dist'; + } + + return null; + } + + /** + * Standard Constructor + * + * $options['directory'] Output Directory. Default: %BUILDPATH% + * $options['filename'] Phar Filename. Default: build.phar + * $options['regexp'] Regular Expression Filename Capture. Default: /\.php$/ + * $options['stub'] Stub Content. No Default Value + * + * @param Builder $phpci + * @param Build $build + * @param array $options + */ public function __construct(Builder $phpci, Build $build, array $options = array()) { - $this->phpci = $phpci; + $this->phpci = $phpci; + $this->build = $build; + + if (empty($options['config']) && empty($options['directory'])) { + $this->xmlConfigFile = self::findConfigFile($phpci->buildPath); + } if (isset($options['directory'])) { $this->directory = $options['directory']; @@ -63,7 +125,7 @@ class PhpUnit implements \PHPCI\Plugin } if (isset($options['args'])) { - $this->args = $options['args']; + $this->args = $this->phpci->interpolate($options['args']); } if (isset($options['path'])) { @@ -71,7 +133,7 @@ class PhpUnit implements \PHPCI\Plugin } if (isset($options['coverage'])) { - $this->coverage = " --coverage-html {$options['coverage']} "; + $this->coverage = ' --coverage-html ' . $this->phpci->interpolate($options['coverage']) . ' '; } } @@ -80,8 +142,15 @@ class PhpUnit implements \PHPCI\Plugin */ public function execute() { + if (empty($this->xmlConfigFile) && empty($this->directory)) { + $this->phpci->logFailure('Neither configuration file nor test directory found.'); + return false; + } + $success = true; + $this->phpci->logExecOutput(false); + // Run any config files first. This can be either a single value or an array. if ($this->xmlConfigFile !== null) { $success &= $this->runConfigFile($this->xmlConfigFile); @@ -92,9 +161,32 @@ class PhpUnit implements \PHPCI\Plugin $success &= $this->runDir($this->directory); } + $tapString = $this->phpci->getLastOutput(); + $tapString = mb_convert_encoding($tapString, "UTF-8", "ISO-8859-1"); + + try { + $tapParser = new TapParser($tapString); + $output = $tapParser->parse(); + } catch (\Exception $ex) { + $this->phpci->logFailure($tapString); + throw $ex; + } + + $failures = $tapParser->getTotalFailures(); + + $this->build->storeMeta('phpunit-errors', $failures); + $this->build->storeMeta('phpunit-data', $output); + + $this->phpci->logExecOutput(true); + return $success; } + /** + * Run the tests defined in a PHPUnit config file. + * @param $configPath + * @return bool|mixed + */ protected function runConfigFile($configPath) { if (is_array($configPath)) { @@ -102,19 +194,12 @@ class PhpUnit implements \PHPCI\Plugin } else { if ($this->runFrom) { $curdir = getcwd(); - chdir($this->phpci->buildPath.'/'.$this->runFrom); + chdir($this->phpci->buildPath . DIRECTORY_SEPARATOR . $this->runFrom); } - $phpunit = $this->phpci->findBinary('phpunit'); - if (!$phpunit) { - $this->phpci->logFailure('Could not find phpunit.'); - return false; - } - - - $cmd = $phpunit . ' %s -c "%s" ' . $this->coverage . $this->path; + $cmd = $phpunit . ' --tap %s -c "%s" ' . $this->coverage . $this->path; $success = $this->phpci->executeCommand($cmd, $this->args, $this->phpci->buildPath . $configPath); if ($this->runFrom) { @@ -125,28 +210,33 @@ class PhpUnit implements \PHPCI\Plugin } } - protected function runDir($dirPath) + /** + * Run the PHPUnit tests in a specific directory or array of directories. + * @param $directory + * @return bool|mixed + */ + protected function runDir($directory) { - if (is_array($dirPath)) { - return $this->recurseArg($dirPath, array($this, "runConfigFile")); + if (is_array($directory)) { + return $this->recurseArg($directory, array($this, "runDir")); } else { $curdir = getcwd(); chdir($this->phpci->buildPath); $phpunit = $this->phpci->findBinary('phpunit'); - if (!$phpunit) { - $this->phpci->logFailure('Could not find phpunit.'); - return false; - } - - $cmd = $phpunit . ' %s "%s"'; - $success = $this->phpci->executeCommand($cmd, $this->args, $this->phpci->buildPath . $dirPath); + $cmd = $phpunit . ' --tap %s "%s"'; + $success = $this->phpci->executeCommand($cmd, $this->args, $this->phpci->buildPath . $directory); chdir($curdir); return $success; } } + /** + * @param $array + * @param $callable + * @return bool|mixed + */ protected function recurseArg($array, $callable) { $success = true; diff --git a/PHPCI/Plugin/Shell.php b/PHPCI/Plugin/Shell.php index d86b6e1b..5e914f3e 100644 --- a/PHPCI/Plugin/Shell.php +++ b/PHPCI/Plugin/Shell.php @@ -1,55 +1,98 @@ -* @package PHPCI -* @subpackage Plugins -*/ + * Shell Plugin - Allows execute shell commands. + * @author Kinn Coelho Julião + * @package PHPCI + * @subpackage Plugins + */ class Shell implements \PHPCI\Plugin { - protected $args; + /** + * @var \PHPCI\Builder + */ protected $phpci; /** - * @var string $command The command to be executed + * @var \PHPCI\Model\Build */ - protected $command; + protected $build; + protected $args; + + /** + * @var string[] $commands The commands to be executed + */ + protected $commands = array(); + + /** + * Standard Constructor + * + * $options['directory'] Output Directory. Default: %BUILDPATH% + * $options['filename'] Phar Filename. Default: build.phar + * $options['regexp'] Regular Expression Filename Capture. Default: /\.php$/ + * $options['stub'] Stub Content. No Default Value + * + * @param Builder $phpci + * @param Build $build + * @param array $options + */ public function __construct(Builder $phpci, Build $build, array $options = array()) { - $this->phpci = $phpci; + $this->phpci = $phpci; + $this->build = $build; if (isset($options['command'])) { - $command = $options['command']; - $command = str_replace("%buildpath%", $this->phpci->buildPath, $command); - $this->command = $command; + // Keeping this for backwards compatibility, new projects should use interpolation vars. + $options['command'] = str_replace("%buildpath%", $this->phpci->buildPath, $options['command']); + $this->commands = array($options['command']); + return; + } + + /* + * Support the new syntax: + * + * shell: + * - "cd /www" + * - "rm -f file.txt" + */ + if (is_array($options)) { + $this->commands = $options; } } /** - * Runs the shell command. - */ + * Runs the shell command. + */ public function execute() { if (!defined('ENABLE_SHELL_PLUGIN') || !ENABLE_SHELL_PLUGIN) { - throw new \Exception('The shell plugin is not enabled.'); + throw new \Exception(Lang::get('shell_not_enabled')); } - - $success = $this->phpci->executeCommand($this->command); - + + $success = true; + + foreach ($this->commands as $command) { + $command = $this->phpci->interpolate($command); + + if (!$this->phpci->executeCommand($command)) { + $success = false; + } + } + return $success; } } diff --git a/PHPCI/Plugin/SlackNotify.php b/PHPCI/Plugin/SlackNotify.php new file mode 100644 index 00000000..0e8d1cad --- /dev/null +++ b/PHPCI/Plugin/SlackNotify.php @@ -0,0 +1,137 @@ + + * @package PHPCI + * @subpackage Plugins + */ +class SlackNotify implements \PHPCI\Plugin +{ + private $webHook; + private $room; + private $username; + private $message; + private $icon; + private $show_status; + + /** + * Set up the plugin, configure options, etc. + * @param Builder $phpci + * @param Build $build + * @param array $options + * @throws \Exception + */ + public function __construct(Builder $phpci, Build $build, array $options = array()) + { + $this->phpci = $phpci; + $this->build = $build; + + if (is_array($options) && isset($options['webhook_url'])) { + $this->webHook = trim($options['webhook_url']); + + if (isset($options['message'])) { + $this->message = $options['message']; + } else { + $this->message = '<%PROJECT_URI%|%PROJECT_TITLE%> - <%BUILD_URI%|Build #%BUILD%> has finished '; + $this->message .= 'for commit <%COMMIT_URI%|%SHORT_COMMIT% (%COMMIT_EMAIL%)> '; + $this->message .= 'on branch <%BRANCH_URI%|%BRANCH%>'; + } + + if (isset($options['room'])) { + $this->room = $options['room']; + } else { + $this->room = '#phpci'; + } + + if (isset($options['username'])) { + $this->username = $options['username']; + } else { + $this->username = 'PHPCI'; + } + + if (isset($options['show_status'])) { + $this->show_status = (bool) $options['show_status']; + } else { + $this->show_status = true; + } + + if (isset($options['icon'])) { + $this->icon = $options['icon']; + } + } else { + throw new \Exception('Please define the webhook_url for slack_notify plugin!'); + } + } + + /** + * Run the Slack plugin. + * @return bool + */ + public function execute() + { + $body = $this->phpci->interpolate($this->message); + + $client = new \Maknz\Slack\Client($this->webHook); + + $message = $client->createMessage(); + + if (!empty($this->room)) { + $message->setChannel($this->room); + } + + if (!empty($this->username)) { + $message->setUsername($this->username); + } + + if (!empty($this->icon)) { + $message->setIcon($this->icon); + } + + // Include an attachment which shows the status and hide the message + if ($this->show_status) { + $successfulBuild = $this->build->isSuccessful(); + + if ($successfulBuild) { + $status = 'Success'; + $color = 'good'; + } else { + $status = 'Failed'; + $color = 'danger'; + } + + // Build up the attachment data + $attachment = new \Maknz\Slack\Attachment(array( + 'fallback' => $body, + 'pretext' => $body, + 'color' => $color, + 'fields' => array( + new \Maknz\Slack\AttachmentField(array( + 'title' => 'Status', + 'value' => $status, + 'short' => false + )) + ) + )); + + $message->attach($attachment); + + $body = ''; + } + + $message->send($body); + + return true; + } +} diff --git a/PHPCI/Plugin/Sqlite.php b/PHPCI/Plugin/Sqlite.php new file mode 100644 index 00000000..f80ece3d --- /dev/null +++ b/PHPCI/Plugin/Sqlite.php @@ -0,0 +1,81 @@ + +* @package PHPCI +* @subpackage Plugins +*/ +class Sqlite implements \PHPCI\Plugin +{ + /** + * @var \PHPCI\Builder + */ + protected $phpci; + + /** + * @var \PHPCI\Model\Build + */ + protected $build; + + /** + * @var array + */ + protected $queries = array(); + + /** + * @var string + */ + protected $path; + + /** + * @param Builder $phpci + * @param Build $build + * @param array $options + */ + public function __construct(Builder $phpci, Build $build, array $options = array()) + { + $this->phpci = $phpci; + $this->build = $build; + $this->queries = $options; + $buildSettings = $phpci->getConfig('build_settings'); + + if (isset($buildSettings['sqlite'])) { + $sql = $buildSettings['sqlite']; + $this->path = $sql['path']; + } + } + + /** + * Connects to SQLite and runs a specified set of queries. + * @return boolean + */ + public function execute() + { + try { + $opts = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); + $pdo = new PDO('sqlite:' . $this->path, $opts); + + foreach ($this->queries as $query) { + $pdo->query($this->phpci->interpolate($query)); + } + } catch (\Exception $ex) { + $this->phpci->logFailure($ex->getMessage()); + return false; + } + return true; + } +} diff --git a/PHPCI/Plugin/TechnicalDebt.php b/PHPCI/Plugin/TechnicalDebt.php new file mode 100755 index 00000000..6d4711b5 --- /dev/null +++ b/PHPCI/Plugin/TechnicalDebt.php @@ -0,0 +1,207 @@ + +* @package PHPCI +* @subpackage Plugins +*/ +class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin +{ + /** + * @var \PHPCI\Builder + */ + protected $phpci; + + /** + * @var array + */ + protected $suffixes; + + /** + * @var string + */ + protected $directory; + + /** + * @var int + */ + protected $allowed_errors; + + /** + * @var string, based on the assumption the root may not hold the code to be + * tested, extends the base path + */ + protected $path; + + /** + * @var array - paths to ignore + */ + protected $ignore; + + /** + * @var array - terms to search for + */ + protected $searches; + + + /** + * Check if this plugin can be executed. + * + * @param $stage + * @param Builder $builder + * @param Build $build + * @return bool + */ + public static function canExecute($stage, Builder $builder, Build $build) + { + if ($stage == 'test') { + return true; + } + + return false; + } + + /** + * @param \PHPCI\Builder $phpci + * @param \PHPCI\Model\Build $build + * @param array $options + */ + public function __construct(Builder $phpci, Build $build, array $options = array()) + { + $this->phpci = $phpci; + $this->build = $build; + $this->suffixes = array('php'); + $this->directory = $phpci->buildPath; + $this->path = ''; + $this->ignore = $this->phpci->ignore; + $this->allowed_errors = 0; + $this->searches = array('TODO', 'FIXME', 'TO DO', 'FIX ME'); + + if (isset($options['searches']) && is_array($options['searches'])) { + $this->searches = $options['searches']; + } + + if (isset($options['zero_config']) && $options['zero_config']) { + $this->allowed_errors = -1; + } + + $this->setOptions($options); + } + + /** + * Handle this plugin's options. + * @param $options + */ + protected function setOptions($options) + { + foreach (array('directory', 'path', 'ignore', 'allowed_errors') as $key) { + if (array_key_exists($key, $options)) { + $this->{$key} = $options[$key]; + } + } + } + + /** + * Runs the plugin + */ + public function execute() + { + $success = true; + $this->phpci->logExecOutput(false); + + $errorCount = $this->getErrorList(); + + $this->phpci->log("Found $errorCount instances of " . implode(', ', $this->searches)); + + $this->build->storeMeta('technical_debt-warnings', $errorCount); + + if ($this->allowed_errors != -1 && $errorCount > $this->allowed_errors) { + $success = false; + } + + return $success; + } + + /** + * Gets the number and list of errors returned from the search + * + * @return array + */ + public function getErrorList() + { + $dirIterator = new \RecursiveDirectoryIterator($this->directory); + $iterator = new \RecursiveIteratorIterator($dirIterator, \RecursiveIteratorIterator::SELF_FIRST); + $files = array(); + + $ignores = $this->ignore; + $ignores[] = 'phpci.yml'; + $ignores[] = '.phpci.yml'; + + foreach ($iterator as $file) { + $filePath = $file->getRealPath(); + $skipFile = false; + foreach ($ignores as $ignore) { + if (stripos($filePath, $ignore) !== false) { + $skipFile = true; + break; + } + } + + // Ignore hidden files, else .git, .sass_cache, etc. all get looped over + if (stripos($filePath, DIRECTORY_SEPARATOR . '.') !== false) { + $skipFile = true; + } + + if ($skipFile == false) { + $files[] = $file->getRealPath(); + } + } + + $files = array_filter(array_unique($files)); + $errorCount = 0; + + foreach ($files as $file) { + foreach ($this->searches as $search) { + $fileContent = file_get_contents($file); + $allLines = explode(PHP_EOL, $fileContent); + $beforeString = strstr($fileContent, $search, true); + + if (false !== $beforeString) { + $lines = explode(PHP_EOL, $beforeString); + $lineNumber = count($lines); + $content = trim($allLines[$lineNumber - 1]); + + $errorCount++; + + $fileName = str_replace($this->directory, '', $file); + + $this->build->reportError( + $this->phpci, + 'technical_debt', + $content, + PHPCI\Model\BuildError::SEVERITY_LOW, + $fileName, + $lineNumber + ); + } + } + } + + return $errorCount; + } +} diff --git a/PHPCI/Plugin/Util/ComposerPluginInformation.php b/PHPCI/Plugin/Util/ComposerPluginInformation.php new file mode 100644 index 00000000..d8dcc91f --- /dev/null +++ b/PHPCI/Plugin/Util/ComposerPluginInformation.php @@ -0,0 +1,153 @@ +composerPackages = $composerPackages; + } + + /** + * Returns an array of objects. Each one represents an available plugin + * and will have the following properties: + * name - The friendly name of the plugin (may be an empty string) + * class - The class of the plugin (will include namespace) + * @return \stdClass[] + */ + public function getInstalledPlugins() + { + $this->loadPluginInfo(); + return $this->pluginInfo; + } + + /** + * Returns an array of all the class names of plugins that have been + * loaded. + * + * @return string[] + */ + public function getPluginClasses() + { + return array_map( + function (Plugin $plugin) { + return $plugin->class; + }, + $this->getInstalledPlugins() + ); + } + + /** + * Load a list of available plugins from the installed composer packages. + */ + protected function loadPluginInfo() + { + if ($this->pluginInfo !== null) { + return; + } + $this->pluginInfo = array(); + foreach ($this->composerPackages as $package) { + $this->addPluginsFromPackage($package); + } + } + + /** + * @param \stdClass $package + */ + protected function addPluginsFromPackage($package) + { + if (isset($package->extra->phpci)) { + $phpciData = $package->extra->phpci; + + if (isset($phpciData->pluginNamespace)) { + $rootNamespace = $phpciData->pluginNamespace; + } else { + $rootNamespace = ""; + } + + if (is_array($phpciData->suppliedPlugins)) { + $this->addPlugins( + $phpciData->suppliedPlugins, + $package->name, + $rootNamespace + ); + } + } + } + + /** + * @param \stdClass[] $plugins + * @param string $sourcePackageName + * @param string $rootNamespace + */ + protected function addPlugins( + array $plugins, + $sourcePackageName, + $rootNamespace = "" + ) { + foreach ($plugins as $plugin) { + if (!isset($plugin->class)) { + continue; + } + $this->addPlugin($plugin, $sourcePackageName, $rootNamespace); + } + } + + /** + * @param \stdClass $plugin + * @param string $sourcePackageName + * @param string $rootNamespace + */ + protected function addPlugin( + $plugin, + $sourcePackageName, + $rootNamespace = "" + ) { + $newPlugin = clone $plugin; + + $newPlugin->class = $rootNamespace . $newPlugin->class; + + if (!isset($newPlugin->name)) { + $newPlugin->name = ""; + } + + $newPlugin->source = $sourcePackageName; + + $this->pluginInfo[] = $newPlugin; + } +} diff --git a/PHPCI/Plugin/Util/Executor.php b/PHPCI/Plugin/Util/Executor.php new file mode 100644 index 00000000..bdd06b7e --- /dev/null +++ b/PHPCI/Plugin/Util/Executor.php @@ -0,0 +1,246 @@ +pluginFactory = $pluginFactory; + $this->logger = $logger; + $this->store = $store ?: StoreFactory::getStore('Build'); + } + + /** + * Execute a the appropriate set of plugins for a given build stage. + * @param array $config PHPCI configuration + * @param string $stage + * @return bool + */ + public function executePlugins(&$config, $stage) + { + $success = true; + $pluginsToExecute = array(); + + // If we have global plugins to execute for this stage, add them to the list to be executed: + if (array_key_exists($stage, $config) && is_array($config[$stage])) { + $pluginsToExecute[] = $config[$stage]; + } + + $pluginsToExecute = $this->getBranchSpecificPlugins($config, $stage, $pluginsToExecute); + + foreach ($pluginsToExecute as $pluginSet) { + if (!$this->doExecutePlugins($pluginSet, $stage)) { + $success = false; + } + } + + return $success; + } + + /** + * Check the config for any plugins specific to the branch we're currently building. + * @param $config + * @param $stage + * @param $pluginsToExecute + * @return array + */ + protected function getBranchSpecificPlugins(&$config, $stage, $pluginsToExecute) + { + /** @var \PHPCI\Model\Build $build */ + $build = $this->pluginFactory->getResourceFor('PHPCI\Model\Build'); + $branch = $build->getBranch(); + + // If we don't have any branch-specific plugins: + if (!isset($config['branch-' . $branch][$stage]) || !is_array($config['branch-' . $branch][$stage])) { + return $pluginsToExecute; + } + + // If we have branch-specific plugins to execute, add them to the list to be executed: + $branchConfig = $config['branch-' . $branch]; + $plugins = $branchConfig[$stage]; + + $runOption = 'after'; + + if (!empty($branchConfig['run-option'])) { + $runOption = $branchConfig['run-option']; + } + + switch ($runOption) { + // Replace standard plugin set for this stage with just the branch-specific ones: + case 'replace': + $pluginsToExecute = array(); + $pluginsToExecute[] = $plugins; + break; + + // Run branch-specific plugins before standard plugins: + case 'before': + array_unshift($pluginsToExecute, $plugins); + break; + + // Run branch-specific plugins after standard plugins: + case 'after': + array_push($pluginsToExecute, $plugins); + break; + + default: + array_push($pluginsToExecute, $plugins); + break; + } + + return $pluginsToExecute; + } + + /** + * Execute the list of plugins found for a given testing stage. + * @param $plugins + * @param $stage + * @return bool + * @throws \Exception + */ + protected function doExecutePlugins(&$plugins, $stage) + { + $success = true; + + foreach ($plugins as $plugin => $options) { + $this->logger->log(Lang::get('running_plugin', $plugin)); + + $this->setPluginStatus($stage, $plugin, Build::STATUS_RUNNING); + + // Try and execute it + if ($this->executePlugin($plugin, $options)) { + // Execution was successful + $this->logger->logSuccess(Lang::get('plugin_success')); + $this->setPluginStatus($stage, $plugin, Build::STATUS_SUCCESS); + } else { + // Execution failed + $this->logger->logFailure(Lang::get('plugin_failed')); + $this->setPluginStatus($stage, $plugin, Build::STATUS_FAILED); + + if ($stage === 'setup') { + // If we're in the "setup" stage, execution should not continue after + // a plugin has failed: + throw new Exception('Plugin failed: ' . $plugin); + } elseif ($stage === 'test') { + // If we're in the "test" stage and the plugin is not allowed to fail, + // then mark the build as failed: + if (empty($options['allow_failures'])) { + $success = false; + } + } + } + } + + return $success; + } + + /** + * Executes a given plugin, with options and returns the result. + */ + public function executePlugin($plugin, $options) + { + // Any plugin name without a namespace separator is a PHPCI built in plugin + // if not we assume it's a fully name-spaced class name that implements the plugin interface. + // If not the factory will throw an exception. + if (strpos($plugin, '\\') === false) { + $class = str_replace('_', ' ', $plugin); + $class = ucwords($class); + $class = 'PHPCI\\Plugin\\' . str_replace(' ', '', $class); + } else { + $class = $plugin; + } + + if (!class_exists($class)) { + $this->logger->logFailure(Lang::get('plugin_missing', $plugin)); + return false; + } + + try { + // Build and run it + $obj = $this->pluginFactory->buildPlugin($class, $options); + return $obj->execute(); + } catch (\Exception $ex) { + $this->logger->logFailure(Lang::get('exception') . $ex->getMessage(), $ex); + return false; + } + } + + /** + * Change the status of a plugin for a given stage. + * + * @param string $stage The builder stage. + * @param string $plugin The plugin name. + * @param int $status The new status. + */ + protected function setPluginStatus($stage, $plugin, $status) + { + $summary = $this->getBuildSummary(); + + if (!isset($summary[$stage][$plugin])) { + $summary[$stage][$plugin] = array(); + } + + $summary[$stage][$plugin]['status'] = $status; + + if ($status === Build::STATUS_RUNNING) { + $summary[$stage][$plugin]['started'] = time(); + } elseif ($status >= Build::STATUS_SUCCESS) { + $summary[$stage][$plugin]['ended'] = time(); + } + + $this->setBuildSummary($summary); + } + + /** + * Fetch the summary data of the current build. + * + * @return array + */ + private function getBuildSummary() + { + $build = $this->pluginFactory->getResourceFor('PHPCI\Model\Build'); + $metas = $this->store->getMeta('plugin-summary', $build->getProjectId(), $build->getId()); + return isset($metas[0]['meta_value']) ? $metas[0]['meta_value'] : array(); + } + + /** + * Sets the summary data of the current build. + * + * @param array summary + */ + private function setBuildSummary($summary) + { + $build = $this->pluginFactory->getResourceFor('PHPCI\Model\Build'); + $this->store->setMeta($build->getProjectId(), $build->getId(), 'plugin-summary', json_encode($summary)); + } +} diff --git a/PHPCI/Plugin/Util/Factory.php b/PHPCI/Plugin/Util/Factory.php new file mode 100644 index 00000000..b6d11e5e --- /dev/null +++ b/PHPCI/Plugin/Util/Factory.php @@ -0,0 +1,219 @@ +container = $container; + } else { + $this->container = new \Pimple(); + } + + $self = $this; + $this->registerResource( + function () use ($self) { + return $self->getLastOptions(); + }, + 'options', + 'array' + ); + } + + /** + * Trys to get a function from the file path specified. If the + * file returns a function then $this will be passed to it. + * This enables the config file to call any public methods. + * + * @param $configPath + * @return bool - true if the function exists else false. + */ + public function addConfigFromFile($configPath) + { + // The file is expected to return a function which can + // act on the pluginFactory to register any resources needed. + if (file_exists($configPath)) { + $configFunction = require($configPath); + if (is_callable($configFunction)) { + $configFunction($this); + return true; + } + } + return false; + } + + /** + * Get most recently used factory options. + * @return mixed + */ + public function getLastOptions() + { + return $this->currentPluginOptions; + } + + /** + * Builds an instance of plugin of class $className. $options will + * be passed along with any resources registered with the factory. + * + * @param $className + * @param array|null $options + * @throws \InvalidArgumentException if $className doesn't represent a valid plugin + * @return \PHPCI\Plugin + */ + public function buildPlugin($className, $options = array()) + { + $this->currentPluginOptions = $options; + + $reflectedPlugin = new \ReflectionClass($className); + + if (!$reflectedPlugin->implementsInterface(self::INTERFACE_PHPCI_PLUGIN)) { + throw new \InvalidArgumentException( + "Requested class must implement " . self:: INTERFACE_PHPCI_PLUGIN + ); + } + + $constructor = $reflectedPlugin->getConstructor(); + + if ($constructor) { + $argsToUse = array(); + foreach ($constructor->getParameters() as $param) { + $argsToUse = $this->addArgFromParam($argsToUse, $param); + } + $plugin = $reflectedPlugin->newInstanceArgs($argsToUse); + } else { + $plugin = $reflectedPlugin->newInstance(); + } + + return $plugin; + } + + /** + * @param callable $loader + * @param string|null $name + * @param string|null $type + * @throws \InvalidArgumentException + * @internal param mixed $resource + */ + public function registerResource( + $loader, + $name = null, + $type = null + ) { + if ($name === null && $type === null) { + throw new \InvalidArgumentException( + "Type or Name must be specified" + ); + } + + if (!($loader instanceof \Closure)) { + throw new \InvalidArgumentException( + '$loader is expected to be a function' + ); + } + + $resourceID = $this->getInternalID($type, $name); + + $this->container[$resourceID] = $loader; + } + + /** + * Get an internal resource ID. + * @param null $type + * @param null $name + * @return string + */ + private function getInternalID($type = null, $name = null) + { + $type = $type ? : ""; + $name = $name ? : ""; + return $type . "-" . $name; + } + + /** + * @param string $type + * @param string $name + * @return mixed + */ + public function getResourceFor($type = null, $name = null) + { + $fullId = $this->getInternalID($type, $name); + if (isset($this->container[$fullId])) { + return $this->container[$fullId]; + } + + $typeOnlyID = $this->getInternalID($type, null); + if (isset($this->container[$typeOnlyID])) { + return $this->container[$typeOnlyID]; + } + + $nameOnlyID = $this->getInternalID(null, $name); + if (isset($this->container[$nameOnlyID])) { + return $this->container[$nameOnlyID]; + } + + return null; + } + + /** + * @param \ReflectionParameter $param + * @return null|string + */ + private function getParamType(\ReflectionParameter $param) + { + $class = $param->getClass(); + if ($class) { + return $class->getName(); + } elseif ($param->isArray()) { + return self::TYPE_ARRAY; + } elseif (is_callable($param)) { + return self::TYPE_CALLABLE; + } else { + return null; + } + } + + /** + * @param $existingArgs + * @param \ReflectionParameter $param + * @return array + * @throws \DomainException + */ + private function addArgFromParam($existingArgs, \ReflectionParameter $param) + { + $name = $param->getName(); + $type = $this->getParamType($param); + $arg = $this->getResourceFor($type, $name); + + if ($arg !== null) { + $existingArgs[] = $arg; + } elseif ($arg === null && $param->isOptional()) { + $existingArgs[] = $param->getDefaultValue(); + } else { + throw new \DomainException( + "Unsatisfied dependency: " . $param->getName() + ); + } + + return $existingArgs; + } +} diff --git a/PHPCI/Plugin/Util/FilesPluginInformation.php b/PHPCI/Plugin/Util/FilesPluginInformation.php new file mode 100644 index 00000000..35587283 --- /dev/null +++ b/PHPCI/Plugin/Util/FilesPluginInformation.php @@ -0,0 +1,137 @@ +files = $files; + } + + /** + * Returns an array of objects. Each one represents an available plugin + * and will have the following properties: + * name - The friendly name of the plugin (may be an empty string) + * class - The class of the plugin (will include namespace) + * @return \stdClass[] + */ + public function getInstalledPlugins() + { + if ($this->pluginInfo === null) { + $this->loadPluginInfo(); + } + + return $this->pluginInfo; + } + + /** + * Returns an array of all the class names of plugins that have been + * loaded. + * + * @return string[] + */ + public function getPluginClasses() + { + return array_map( + function (\stdClass $plugin) { + return $plugin->class; + }, + $this->getInstalledPlugins() + ); + } + + /** + * Load plugin information from a given list of files. + */ + protected function loadPluginInfo() + { + $this->pluginInfo = array(); + foreach ($this->files as $fileInfo) { + if ($fileInfo instanceof \SplFileInfo) { + if ($fileInfo->isFile() && $fileInfo->getExtension() == 'php') { + $this->addPluginFromFile($fileInfo); + } + } + } + } + + /** + * Add a plugin to the list from a given file. + * @param \SplFileInfo $fileInfo + */ + protected function addPluginFromFile(\SplFileInfo $fileInfo) + { + $class = $this->getFullClassFromFile($fileInfo); + + if (!is_null($class)) { + $newPlugin = new \stdClass(); + $newPlugin->class = $class; + $newPlugin->source = "core"; + $parts = explode('\\', $newPlugin->class); + $newPlugin->name = end($parts); + + $this->pluginInfo[] = $newPlugin; + } + } + + /** + * Determine plugin class name for a given file. + * @param \SplFileInfo $fileInfo + * @return null|string + */ + protected function getFullClassFromFile(\SplFileInfo $fileInfo) + { + $contents = file_get_contents($fileInfo->getRealPath()); + $matches = array(); + + preg_match('#class +([A-Za-z]+) +implements#i', $contents, $matches); + + if (isset($matches[1])) { + $className = $matches[1]; + + $matches = array(); + preg_match('#namespace +([A-Za-z\\\\]+);#i', $contents, $matches); + $namespace = $matches[1]; + + return $namespace . '\\' . $className; + } else { + return null; + } + } +} diff --git a/PHPCI/Plugin/Util/InstalledPluginInformation.php b/PHPCI/Plugin/Util/InstalledPluginInformation.php new file mode 100644 index 00000000..821c511c --- /dev/null +++ b/PHPCI/Plugin/Util/InstalledPluginInformation.php @@ -0,0 +1,26 @@ +pluginInformations[] = $information; + } + + /** + * Returns an array of objects. Each one represents an available plugin + * and will have the following properties: + * name - The friendly name of the plugin (may be an empty string) + * class - The class of the plugin (will include namespace) + * @return \stdClass[] + */ + public function getInstalledPlugins() + { + $arr = array(); + + foreach ($this->pluginInformations as $single) { + $arr = array_merge($arr, $single->getInstalledPlugins()); + } + + return $arr; + } + + /** + * Returns an array of all the class names of plugins that have been + * loaded. + * + * @return string[] + */ + public function getPluginClasses() + { + $arr = array(); + + foreach ($this->pluginInformations as $single) { + $arr = array_merge($arr, $single->getPluginClasses()); + } + + return $arr; + } +} diff --git a/PHPCI/Plugin/Util/TapParser.php b/PHPCI/Plugin/Util/TapParser.php new file mode 100644 index 00000000..86981566 --- /dev/null +++ b/PHPCI/Plugin/Util/TapParser.php @@ -0,0 +1,286 @@ +tapString = trim($tapString); + } + + /** + * Parse a given TAP format string and return an array of tests and their status. + */ + public function parse() + { + // Split up the TAP string into an array of lines, then + // trim all of the lines so there's no leading or trailing whitespace. + $this->lines = array_map('rtrim', explode("\n", $this->tapString)); + $this->lineNumber = 0; + + $this->testCount = false; + $this->results = array(); + + $header = $this->findTapLog(); + + $line = $this->nextLine(); + if ($line === $header) { + throw new Exception("Duplicated TAP log, please check the configuration."); + } + + while ($line !== false && ($this->testCount === false || count($this->results) < $this->testCount)) { + $this->parseLine($line); + $line = $this->nextLine(); + } + + if (false !== $this->testCount && count($this->results) !== $this->testCount) { + throw new Exception(Lang::get('tap_error')); + } + + return $this->results; + } + + /** Looks for the start of the TAP log in the string. + * + * @return string The TAP header line. + * + * @throws Exception if no TAP log is found or versions mismatch. + */ + protected function findTapLog() + { + // Look for the beginning of the TAP output + do { + $header = $this->nextLine(); + } while ($header !== false && substr($header, 0, 12) !== 'TAP version '); + + // + if ($header === false) { + throw new Exception('No TAP log found, please check the configuration.'); + } elseif ($header !== 'TAP version 13') { + throw new Exception(Lang::get('tap_version')); + } + + return $header; + } + + /** Fetch the next line. + * + * @return string|false The next line or false if the end has been reached. + */ + protected function nextLine() + { + if ($this->lineNumber < count($this->lines)) { + return $this->lines[$this->lineNumber++]; + } + return false; + } + + /** + * @param string $line + * + * @return boolean + */ + protected function testLine($line) + { + if (preg_match(self::TEST_LINE_PATTERN, $line, $matches)) { + $this->results[] = $this->processTestLine( + $matches[1], + isset($matches[2]) ? $matches[2] : '', + isset($matches[3]) ? $matches[3] : null, + isset($matches[4]) ? $matches[4] : null + ); + + return true; + } + + return false; + } + + /** + * @param string $line + * + * @return boolean + */ + protected function yamlLine($line) + { + if (preg_match(self::TEST_YAML_START, $line, $matches)) { + $diagnostic = $this->processYamlBlock($matches[1]); + $test = array_pop($this->results); + if (isset($test['message'], $diagnostic['message'])) { + $test['message'] .= PHP_EOL . $diagnostic['message']; + unset($diagnostic['message']); + } + $this->results[] = array_replace($test, $diagnostic); + + return true; + } + + return false; + } + + /** Parse a single line. + * + * @param string $line + * + * @throws Exception + */ + protected function parseLine($line) + { + if (preg_match(self::TEST_DIAGNOSTIC, $line) || preg_match(self::TEST_COVERAGE, $line) || !$line) { + return; + } + + if (preg_match(self::TEST_COUNTS_PATTERN, $line, $matches)) { + $this->testCount = intval($matches[1]); + + return; + } + + if ($this->testLine($line)) { + return; + } + + if ($this->yamlLine($line)) { + return; + } + + throw new Exception(sprintf('Incorrect TAP data, line %d: %s', $this->lineNumber, $line)); + } + + /** + * Process an individual test line. + * + * @param string $result + * @param string $message + * @param string $directive + * @param string $reason + * + * @return array + */ + protected function processTestLine($result, $message, $directive, $reason) + { + $test = array( + 'pass' => true, + 'message' => $message, + 'severity' => 'success', + ); + + if ($result !== 'ok') { + $test['pass'] = false; + $test['severity'] = substr($message, 0, 6) === 'Error:' ? 'error' : 'fail'; + $this->failures++; + } + + if ($directive) { + $test = $this->processDirective($test, $directive, $reason); + } + + return $test; + } + + /** Process an indented Yaml block. + * + * @param string $indent The block indentation to ignore. + * + * @return array The processed Yaml content. + */ + protected function processYamlBlock($indent) + { + $startLine = $this->lineNumber + 1; + $endLine = $indent . '...'; + $yamlLines = array(); + + do { + $line = $this->nextLine(); + + if ($line === false) { + throw new Exception(Lang::get('tap_error_endless_yaml', $startLine)); + } elseif ($line === $endLine) { + break; + } + + $yamlLines[] = substr($line, strlen($indent)); + } while (true); + + return Yaml::parse(join("\n", $yamlLines)); + } + + /** Process a TAP directive + * + * @param array $test + * @param string $directive + * @param string $reason + * @return array + */ + protected function processDirective($test, $directive, $reason) + { + $test['severity'] = strtolower($directive) === 'skip' ? 'skipped' : 'todo'; + + if (!empty($reason)) { + if (!empty($test['message'])) { + $test['message'] .= ', '.$test['severity'].': '; + } + $test['message'] .= $reason; + } + + return $test; + } + + /** + * Get the total number of failures from the current TAP file. + * @return int + */ + public function getTotalFailures() + { + return $this->failures; + } +} diff --git a/PHPCI/Plugin/Util/TestResultParsers/Codeception.php b/PHPCI/Plugin/Util/TestResultParsers/Codeception.php new file mode 100644 index 00000000..24af62e4 --- /dev/null +++ b/PHPCI/Plugin/Util/TestResultParsers/Codeception.php @@ -0,0 +1,114 @@ + + * @package PHPCI\Plugin\Util\TestResultParsers + */ +class Codeception implements ParserInterface +{ + protected $phpci; + protected $resultsXml; + + protected $results; + + protected $totalTests; + protected $totalTimeTaken; + protected $totalFailures; + protected $totalErrors; + + /** + * @param Builder $phpci + * @param $resultsXml + */ + public function __construct(Builder $phpci, $resultsXml) + { + $this->phpci = $phpci; + $this->resultsXml = $resultsXml; + $this->totalTests = 0; + } + + /** + * @return array An array of key/value pairs for storage in the plugins result metadata + */ + public function parse() + { + $rtn = array(); + + $this->results = new \SimpleXMLElement($this->resultsXml); + + // calculate total results + foreach ($this->results->testsuite as $testsuite) { + $this->totalTests += (int) $testsuite['tests']; + $this->totalTimeTaken += (float) $testsuite['time']; + $this->totalFailures += (int) $testsuite['failures']; + $this->totalErrors += (int) $testsuite['errors']; + + foreach ($testsuite->testcase as $testcase) { + $testresult = array( + 'suite' => (string) $testsuite['name'], + 'file' => str_replace($this->phpci->buildPath, '/', (string) $testcase['file']), + 'name' => (string) $testcase['name'], + 'feature' => (string) $testcase['feature'], + 'assertions' => (int) $testcase['assertions'], + 'time' => (float) $testcase['time'] + ); + + if (isset($testcase['class'])) { + $testresult['class'] = (string) $testcase['class']; + } + + // PHPUnit testcases does not have feature field. Use class::method instead + if (!$testresult['feature']) { + $testresult['feature'] = sprintf('%s::%s', $testresult['class'], $testresult['name']); + } + + if (isset($testcase->failure) || isset($testcase->error)) { + $testresult['pass'] = false; + $testresult['message'] = (string)$testcase->failure . (string)$testcase->error; + } else { + $testresult['pass'] = true; + } + + $rtn[] = $testresult; + } + } + + return $rtn; + } + + /** + * Get the total number of tests performed. + * + * @return int + */ + public function getTotalTests() + { + return $this->totalTests; + } + + /** + * The time take to complete all tests + * + * @return mixed + */ + public function getTotalTimeTaken() + { + return $this->totalTimeTaken; + } + + /** + * A count of the test failures + * + * @return mixed + */ + public function getTotalFailures() + { + return $this->totalFailures + $this->totalErrors; + } +} diff --git a/PHPCI/Plugin/Util/TestResultParsers/ParserInterface.php b/PHPCI/Plugin/Util/TestResultParsers/ParserInterface.php new file mode 100644 index 00000000..d0c77cb7 --- /dev/null +++ b/PHPCI/Plugin/Util/TestResultParsers/ParserInterface.php @@ -0,0 +1,16 @@ + +* @package PHPCI +* @subpackage Plugins +*/ +class Wipe implements \PHPCI\Plugin +{ + /** + * @var \PHPCI\Builder + */ + protected $phpci; + + /** + * @var \PHPCI\Model\Build + */ + protected $build; + + protected $directory; + + /** + * Set up the plugin, configure options, etc. + * @param Builder $phpci + * @param Build $build + * @param array $options + */ + public function __construct(Builder $phpci, Build $build, array $options = array()) + { + $path = $phpci->buildPath; + $this->phpci = $phpci; + $this->build = $build; + $this->directory = isset($options['directory']) ? $this->phpci->interpolate($options['directory']) : $path; + } + + /** + * Wipes a directory's contents + */ + public function execute() + { + $build = $this->phpci->buildPath; + + if ($this->directory == $build || empty($this->directory)) { + return true; + } + if (is_dir($this->directory)) { + $cmd = 'rm -Rf "%s"'; + if (IS_WIN) { + $cmd = 'rmdir /S /Q "%s"'; + } + return $this->phpci->executeCommand($cmd, $this->directory); + } + return true; + } +} diff --git a/PHPCI/Plugin/Xmpp.php b/PHPCI/Plugin/Xmpp.php new file mode 100644 index 00000000..ccfc4399 --- /dev/null +++ b/PHPCI/Plugin/Xmpp.php @@ -0,0 +1,217 @@ + +* @package PHPCI +* @subpackage Plugins +*/ +class XMPP implements \PHPCI\Plugin +{ + protected $directory; + protected $phpci; + protected $build; + + /** + * @var string, username of sender account xmpp + */ + protected $username; + + /** + * @var string, alias server of sender account xmpp + */ + protected $server; + + /** + * @var string, password of sender account xmpp + */ + protected $password; + + /** + * @var string, alias for sender + */ + protected $alias; + + /** + * @var string, use tls + */ + protected $tls; + + /** + * @var array, list of recipients xmpp accounts + */ + protected $recipients; + + /** + * @var string, mask to format date + */ + protected $date_format; + + /** + * + * @param Builder $phpci + * @param Build $build + * @param array $options + */ + public function __construct(Builder $phpci, Build $build, array $options = array()) + { + $this->phpci = $phpci; + $this->build = $build; + + $this->username = ''; + $this->password = ''; + $this->server = ''; + $this->alias = ''; + $this->recipients = array(); + $this->tls = false; + $this->date_format = '%c'; + + /* + * Set recipients list + */ + if (!empty($options['recipients'])) { + if (is_string($options['recipients'])) { + $this->recipients = array($options['recipients']); + } elseif (is_array($options['recipients'])) { + $this->recipients = $options['recipients']; + } + } + + $this->setOptions($options); + } + + /** + * Set options configuration for plugin + * + * @param array $options + */ + protected function setOptions($options) + { + foreach (array('username', 'password', 'alias', 'tls', 'server', 'date_format') as $key) { + if (array_key_exists($key, $options)) { + $this->{$key} = $options[$key]; + } + } + } + + /** + * Get config format for sendxmpp config file + * + * @return string + */ + protected function getConfigFormat() + { + $conf = $this->username; + if (!empty($this->server)) { + $conf .= ';'.$this->server; + } + + $conf .= ' '.$this->password; + + if (!empty($this->alias)) { + $conf .= ' '.$this->alias; + } + + return $conf; + } + + /** + * Find config file for sendxmpp binary (default is .sendxmpprc) + */ + public function findConfigFile() + { + if (file_exists($this->phpci->buildPath . DIRECTORY_SEPARATOR . '.sendxmpprc')) { + if (md5(file_get_contents($this->phpci->buildPath . DIRECTORY_SEPARATOR . '.sendxmpprc')) + !== md5($this->getConfigFormat())) { + return null; + } + + return true; + } + + return null; + } + + /** + * Send notification message. + */ + public function execute() + { + $sendxmpp = $this->phpci->findBinary('sendxmpp'); + + /* + * Without recipients we can't send notification + */ + if (count($this->recipients) == 0) { + return false; + } + + /* + * Try to build conf file + */ + $config_file = $this->phpci->buildPath . DIRECTORY_SEPARATOR . '.sendxmpprc'; + if (is_null($this->findConfigFile())) { + file_put_contents($config_file, $this->getConfigFormat()); + chmod($config_file, 0600); + } + + /* + * Enabled ssl for connection + */ + $tls = ''; + if ($this->tls) { + $tls = ' -t'; + } + + $message_file = $this->phpci->buildPath . DIRECTORY_SEPARATOR . uniqid('xmppmessage'); + if ($this->buildMessage($message_file) === false) { + return false; + } + + /* + * Send XMPP notification for all recipients + */ + $cmd = $sendxmpp . "%s -f %s -m %s %s"; + $recipients = implode(' ', $this->recipients); + + $success = $this->phpci->executeCommand($cmd, $tls, $config_file, $message_file, $recipients); + + print $this->phpci->getLastOutput(); + + /* + * Remove temp message file + */ + $this->phpci->executeCommand("rm -rf ".$message_file); + + return $success; + } + + /** + * @param $message_file + * @return int + */ + protected function buildMessage($message_file) + { + if ($this->build->isSuccessful()) { + $message = "✔ [".$this->build->getProjectTitle()."] Build #" . $this->build->getId()." successful"; + } else { + $message = "✘ [".$this->build->getProjectTitle()."] Build #" . $this->build->getId()." failure"; + } + + $message .= ' ('.strftime($this->date_format).')'; + + return file_put_contents($message_file, $message); + } +} diff --git a/PHPCI/ProcessControl/Factory.php b/PHPCI/ProcessControl/Factory.php new file mode 100644 index 00000000..7622fd49 --- /dev/null +++ b/PHPCI/ProcessControl/Factory.php @@ -0,0 +1,63 @@ + + */ +class Factory +{ + /** + * ProcessControl singleton. + * + * @var ProcessControlInterface + */ + protected static $instance = null; + + /** + * Returns the ProcessControl singleton. + * + * @return ProcessControlInterface + */ + public static function getInstance() + { + if (static::$instance === null) { + static::$instance = static::createProcessControl(); + } + return static::$instance; + } + + /** + * Create a ProcessControl depending on available extensions and the underlying OS. + * + * Check PosixProcessControl, WindowsProcessControl and UnixProcessControl, in that order. + * + * @return ProcessControlInterface + * + * @internal + */ + public static function createProcessControl() + { + switch (true) { + case PosixProcessControl::isAvailable(): + return new PosixProcessControl(); + + case WindowsProcessControl::isAvailable(): + return new WindowsProcessControl(); + + case UnixProcessControl::isAvailable(): + return new UnixProcessControl(); + } + + throw new \Exception("No ProcessControl implementation available."); + } +} diff --git a/PHPCI/ProcessControl/PosixProcessControl.php b/PHPCI/ProcessControl/PosixProcessControl.php new file mode 100644 index 00000000..bac55ee2 --- /dev/null +++ b/PHPCI/ProcessControl/PosixProcessControl.php @@ -0,0 +1,52 @@ + + */ +class PosixProcessControl implements ProcessControlInterface +{ + /** + * + * @param int $pid + * @return bool + */ + public function isRunning($pid) + { + // Signal "0" is not sent to the process, but posix_kill checks the process anyway; + return posix_kill($pid, 0); + } + + /** + * Sends a TERMINATE or KILL signal to the process using posix_kill. + * + * @param int $pid + * @param bool $forcefully Whether to send TERMINATE (false) or KILL (true). + */ + public function kill($pid, $forcefully = false) + { + posix_kill($pid, $forcefully ? 9 : 15); + } + + /** + * Check whether this posix_kill is available. + * + * @return bool + * + * @internal + */ + public static function isAvailable() + { + return function_exists('posix_kill'); + } +} diff --git a/PHPCI/ProcessControl/ProcessControlInterface.php b/PHPCI/ProcessControl/ProcessControlInterface.php new file mode 100644 index 00000000..709e0bee --- /dev/null +++ b/PHPCI/ProcessControl/ProcessControlInterface.php @@ -0,0 +1,33 @@ + + */ +interface ProcessControlInterface +{ + /** Checks if a process exists. + * + * @param int $pid The process identifier. + * + * @return boolean true is the process is running, else false. + */ + public function isRunning($pid); + + /** Terminate a running process. + * + * @param int $pid The process identifier. + * @param bool $forcefully Whether to gently (false) or forcefully (true) terminate the process. + */ + public function kill($pid, $forcefully = false); +} diff --git a/PHPCI/ProcessControl/UnixProcessControl.php b/PHPCI/ProcessControl/UnixProcessControl.php new file mode 100644 index 00000000..8b638073 --- /dev/null +++ b/PHPCI/ProcessControl/UnixProcessControl.php @@ -0,0 +1,54 @@ + + */ +class UnixProcessControl implements ProcessControlInterface +{ + /** + * Check process using the "ps" command. + * + * @param int $pid + * @return boolean + */ + public function isRunning($pid) + { + $output = $exitCode = null; + exec(sprintf("ps %d", $pid), $output, $exitCode); + return $exitCode === 0; + } + + /** + * Sends a signal using the "kill" command. + * + * @param int $pid + * @param bool $forcefully + */ + public function kill($pid, $forcefully = false) + { + exec(sprintf("kill -%d %d", $forcefully ? 9 : 15, $pid)); + } + + /** + * Check whether the commands "ps" and "kill" are available. + * + * @return bool + * + * @internal + */ + public static function isAvailable() + { + return DIRECTORY_SEPARATOR === '/' && exec("which ps") && exec("which kill"); + } +} diff --git a/PHPCI/ProcessControl/WindowsProcessControl.php b/PHPCI/ProcessControl/WindowsProcessControl.php new file mode 100644 index 00000000..e750d321 --- /dev/null +++ b/PHPCI/ProcessControl/WindowsProcessControl.php @@ -0,0 +1,54 @@ + + */ +class WindowsProcessControl implements ProcessControlInterface +{ + /** + * Check if the process is running using the "tasklist" command. + * + * @param type $pid + * @return bool + */ + public function isRunning($pid) + { + $lastLine = exec(sprintf('tasklist /fi "PID eq %d" /nh /fo csv 2>nul:', $pid)); + $record = str_getcsv($lastLine); + return isset($record[1]) && intval($record[1]) === $pid; + } + + /** + * Terminate the process using the "taskkill" command. + * + * @param type $pid + * @param bool $forcefully + */ + public function kill($pid, $forcefully = false) + { + exec(sprintf("taskkill /t /pid %d %s 2>nul:", $pid, $forcefully ? '/f' : '')); + } + + /** + * Check whether the commands "tasklist" and "taskkill" are available. + * + * @return bool + * + * @internal + */ + public static function isAvailable() + { + return DIRECTORY_SEPARATOR === '\\' && exec("where tasklist") && exec("where taskkill"); + } +} diff --git a/PHPCI/Service/BuildService.php b/PHPCI/Service/BuildService.php new file mode 100644 index 00000000..a120a64e --- /dev/null +++ b/PHPCI/Service/BuildService.php @@ -0,0 +1,189 @@ +buildStore = $buildStore; + } + + /** + * @param Project $project + * @param string|null $commitId + * @param string|null $branch + * @param string|null $committerEmail + * @param string|null $commitMessage + * @param string|null $extra + * @return \PHPCI\Model\Build + */ + public function createBuild( + Project $project, + $commitId = null, + $branch = null, + $committerEmail = null, + $commitMessage = null, + $extra = null + ) { + $build = new Build(); + $build->setCreated(new \DateTime()); + $build->setProject($project); + $build->setStatus(0); + + if (!is_null($commitId)) { + $build->setCommitId($commitId); + } else { + $build->setCommitId('Manual'); + $build->setCommitMessage(Lang::get('manual_build')); + } + + if (!is_null($branch)) { + $build->setBranch($branch); + } else { + $build->setBranch($project->getBranch()); + } + + if (!is_null($committerEmail)) { + $build->setCommitterEmail($committerEmail); + } + + if (!is_null($commitMessage)) { + $build->setCommitMessage($commitMessage); + } + + if (!is_null($extra)) { + $build->setExtra(json_encode($extra)); + } + + $build = $this->buildStore->save($build); + + $buildId = $build->getId(); + + if (!empty($buildId)) { + $build = BuildFactory::getBuild($build); + $build->sendStatusPostback(); + $this->addBuildToQueue($build); + } + + return $build; + } + + /** + * @param Build $copyFrom + * @return \PHPCI\Model\Build + */ + public function createDuplicateBuild(Build $copyFrom) + { + $data = $copyFrom->getDataArray(); + + // Clean up unwanted properties from the original build: + unset($data['id']); + unset($data['status']); + unset($data['log']); + unset($data['started']); + unset($data['finished']); + + $build = new Build(); + $build->setValues($data); + $build->setCreated(new \DateTime()); + $build->setStatus(0); + + $build = $this->buildStore->save($build); + + $buildId = $build->getId(); + + if (!empty($buildId)) { + $build = BuildFactory::getBuild($build); + $build->sendStatusPostback(); + $this->addBuildToQueue($build); + } + + return $build; + } + + /** + * Delete a given build. + * @param Build $build + * @return bool + */ + public function deleteBuild(Build $build) + { + $build->removeBuildDirectory(); + return $this->buildStore->delete($build); + } + + /** + * Takes a build and puts it into the queue to be run (if using a queue) + * @param Build $build + */ + public function addBuildToQueue(Build $build) + { + $buildId = $build->getId(); + + if (empty($buildId)) { + return; + } + + $config = Config::getInstance(); + $settings = $config->get('phpci.worker', []); + + if (!empty($settings['host']) && !empty($settings['queue'])) { + try { + $jobData = array( + 'type' => 'phpci.build', + 'build_id' => $build->getId(), + ); + + if ($config->get('using_custom_file')) { + $jobData['config'] = $config->getArray(); + } + + $pheanstalk = new Pheanstalk($settings['host']); + $pheanstalk->useTube($settings['queue']); + $pheanstalk->put( + json_encode($jobData), + PheanstalkInterface::DEFAULT_PRIORITY, + PheanstalkInterface::DEFAULT_DELAY, + $config->get('phpci.worker.job_timeout', 600) + ); + } catch (\Exception $ex) { + $this->queueError = true; + } + } + } +} diff --git a/PHPCI/Service/BuildStatusService.php b/PHPCI/Service/BuildStatusService.php new file mode 100644 index 00000000..b4f3c009 --- /dev/null +++ b/PHPCI/Service/BuildStatusService.php @@ -0,0 +1,222 @@ +project = $project; + $this->branch = $branch; + $this->build = $build; + if ($this->build) { + $this->loadParentBuild($isParent); + } + if (defined('PHPCI_URL')) { + $this->setUrl(PHPCI_URL); + } + } + + /** + * @param $url + */ + public function setUrl($url) + { + $this->url = $url; + } + + /** + * @return Build + */ + public function getBuild() + { + return $this->build; + } + + /** + * @param bool $isParent + * @throws \Exception + */ + protected function loadParentBuild($isParent = true) + { + if ($isParent === false && !$this->isFinished()) { + $lastFinishedBuild = $this->project->getLatestBuild($this->branch, $this->finishedStatusIds); + + if ($lastFinishedBuild) { + $this->prevService = new BuildStatusService( + $this->branch, + $this->project, + $lastFinishedBuild, + true + ); + } + } + } + + /** + * @return string + */ + public function getActivity() + { + if (in_array($this->build->getStatus(), $this->finishedStatusIds)) { + return 'Sleeping'; + } elseif ($this->build->getStatus() == Build::STATUS_NEW) { + return 'Pending'; + } elseif ($this->build->getStatus() == Build::STATUS_RUNNING) { + return 'Building'; + } + return 'Unknown'; + } + + /** + * @return string + */ + public function getName() + { + return $this->project->getTitle() . ' / ' . $this->branch; + } + + /** + * @return bool + */ + public function isFinished() + { + if (in_array($this->build->getStatus(), $this->finishedStatusIds)) { + return true; + } + return false; + } + + /** + * @return null|Build + */ + public function getFinishedBuildInfo() + { + if ($this->isFinished()) { + return $this->build; + } elseif ($this->prevService) { + return $this->prevService->getBuild(); + } + return null; + } + + /** + * @return int|string + */ + public function getLastBuildLabel() + { + if ($buildInfo = $this->getFinishedBuildInfo()) { + return $buildInfo->getId(); + } + return ''; + } + + /** + * @return string + */ + public function getLastBuildTime() + { + $dateFormat = 'Y-m-d\\TH:i:sO'; + if ($buildInfo = $this->getFinishedBuildInfo()) { + return ($buildInfo->getFinished()) ? $buildInfo->getFinished()->format($dateFormat) : ''; + } + return ''; + } + + /** + * @param Build $build + * @return string + */ + public function getBuildStatus(Build $build) + { + switch ($build->getStatus()) { + case Build::STATUS_SUCCESS: + return 'Success'; + case Build::STATUS_FAILED: + return 'Failure'; + } + return 'Unknown'; + } + + /** + * @return string + */ + public function getLastBuildStatus() + { + if ($build = $this->getFinishedBuildInfo()) { + return $this->getBuildStatus($build); + } + return ''; + } + + /** + * @return string + */ + public function getBuildUrl() + { + return $this->url . 'build/view/' . $this->build->getId(); + } + + /** + * @return array + */ + public function toArray() + { + if (!$this->build) { + return array(); + } + return array( + 'name' => $this->getName(), + 'activity' => $this->getActivity(), + 'lastBuildLabel' => $this->getLastBuildLabel(), + 'lastBuildStatus' => $this->getLastBuildStatus(), + 'lastBuildTime' => $this->getLastBuildTime(), + 'webUrl' => $this->getBuildUrl(), + ); + } +} diff --git a/PHPCI/Service/ProjectService.php b/PHPCI/Service/ProjectService.php new file mode 100644 index 00000000..8b07b0a9 --- /dev/null +++ b/PHPCI/Service/ProjectService.php @@ -0,0 +1,137 @@ +projectStore = $projectStore; + } + + /** + * Create a new project model and use the project store to save it. + * @param string $title + * @param string $type + * @param string $reference + * @param array $options + * @return \PHPCI\Model\Project + */ + public function createProject($title, $type, $reference, $options = array()) + { + // Create base project and use updateProject() to set its properties: + $project = new Project(); + return $this->updateProject($project, $title, $type, $reference, $options); + } + + /** + * Update the properties of a given project. + * @param Project $project + * @param string $title + * @param string $type + * @param string $reference + * @param array $options + * @return \PHPCI\Model\Project + */ + public function updateProject(Project $project, $title, $type, $reference, $options = array()) + { + // Set basic properties: + $project->setTitle($title); + $project->setType($type); + $project->setReference($reference); + $project->setAllowPublicStatus(0); + + // Handle extra project options: + if (array_key_exists('ssh_private_key', $options)) { + $project->setSshPrivateKey($options['ssh_private_key']); + } + + if (array_key_exists('ssh_public_key', $options)) { + $project->setSshPublicKey($options['ssh_public_key']); + } + + if (array_key_exists('build_config', $options)) { + $project->setBuildConfig($options['build_config']); + } + + if (array_key_exists('allow_public_status', $options)) { + $project->setAllowPublicStatus((int)$options['allow_public_status']); + } + + if (array_key_exists('archived', $options)) { + $project->setArchived((bool)$options['archived']); + } + + if (array_key_exists('branch', $options)) { + $project->setBranch($options['branch']); + } + + if (array_key_exists('group', $options)) { + $project->setGroup($options['group']); + } + + // Allow certain project types to set access information: + $this->processAccessInformation($project); + + // Save and return the project: + return $this->projectStore->save($project); + } + + /** + * Delete a given project. + * @param Project $project + * @return bool + */ + public function deleteProject(Project $project) + { + return $this->projectStore->delete($project); + } + + /** + * In circumstances where it is necessary, populate access information based on other project properties. + * @see ProjectService::createProject() + * @param Project $project + */ + protected function processAccessInformation(Project &$project) + { + $matches = array(); + $reference = $project->getReference(); + + if ($project->getType() == 'gitlab') { + $info = array(); + + if (preg_match('`^(.+)@(.+):([0-9]*)\/?(.+)\.git`', $reference, $matches)) { + $info['user'] = $matches[1]; + $info['domain'] = $matches[2]; + $info['port'] = $matches[3]; + + $project->setReference($matches[4]); + } + + $project->setAccessInformation($info); + } + } +} diff --git a/PHPCI/Service/UserService.php b/PHPCI/Service/UserService.php new file mode 100644 index 00000000..2d9b070d --- /dev/null +++ b/PHPCI/Service/UserService.php @@ -0,0 +1,88 @@ +store = $store; + } + + /** + * Create a new user within PHPCI. + * @param $name + * @param $emailAddress + * @param $password + * @param bool $isAdmin + * @return \PHPCI\Model\User + */ + public function createUser($name, $emailAddress, $password, $isAdmin = false) + { + $user = new User(); + $user->setName($name); + $user->setEmail($emailAddress); + $user->setHash(password_hash($password, PASSWORD_DEFAULT)); + $user->setIsAdmin(($isAdmin ? 1 : 0)); + + return $this->store->save($user); + } + + /** + * Update a user. + * @param User $user + * @param $name + * @param $emailAddress + * @param null $password + * @param null $isAdmin + * @return \PHPCI\Model\User + */ + public function updateUser(User $user, $name, $emailAddress, $password = null, $isAdmin = null) + { + $user->setName($name); + $user->setEmail($emailAddress); + + if (!empty($password)) { + $user->setHash(password_hash($password, PASSWORD_DEFAULT)); + } + + if (!is_null($isAdmin)) { + $user->setIsAdmin(($isAdmin ? 1 : 0)); + } + + return $this->store->save($user); + } + + /** + * Delete a user. + * @param User $user + * @return bool + */ + public function deleteUser(User $user) + { + return $this->store->delete($user); + } +} diff --git a/PHPCI/Store.php b/PHPCI/Store.php new file mode 100644 index 00000000..71522638 --- /dev/null +++ b/PHPCI/Store.php @@ -0,0 +1,19 @@ +getById($value, $useConnection); + } + + /** + * Get a single BuildError by Id. + * @return null|BuildError + */ + public function getById($value, $useConnection = 'read') + { + if (is_null($value)) { + throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); + } + + $query = 'SELECT * FROM `build_error` WHERE `id` = :id LIMIT 1'; + $stmt = Database::getConnection($useConnection)->prepare($query); + $stmt->bindValue(':id', $value); + + if ($stmt->execute()) { + if ($data = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return new BuildError($data); + } + } + + return null; + } + + /** + * Get multiple BuildError by BuildId. + * @return array + */ + public function getByBuildId($value, $limit = 1000, $useConnection = 'read') + { + if (is_null($value)) { + throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); + } + + + $query = 'SELECT * FROM `build_error` WHERE `build_id` = :build_id LIMIT :limit'; + $stmt = Database::getConnection($useConnection)->prepare($query); + $stmt->bindValue(':build_id', $value); + $stmt->bindValue(':limit', (int)$limit, \PDO::PARAM_INT); + + if ($stmt->execute()) { + $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $map = function ($item) { + return new BuildError($item); + }; + $rtn = array_map($map, $res); + + $count = count($rtn); + + return array('items' => $rtn, 'count' => $count); + } else { + return array('items' => array(), 'count' => 0); + } + } +} diff --git a/PHPCI/Store/Base/BuildMetaStoreBase.php b/PHPCI/Store/Base/BuildMetaStoreBase.php index 01bb6964..52665d79 100644 --- a/PHPCI/Store/Base/BuildMetaStoreBase.php +++ b/PHPCI/Store/Base/BuildMetaStoreBase.php @@ -8,7 +8,7 @@ namespace PHPCI\Store\Base; use b8\Database; use b8\Exception\HttpException; -use b8\Store; +use PHPCI\Store; use PHPCI\Model\BuildMeta; /** @@ -20,18 +20,25 @@ class BuildMetaStoreBase extends Store protected $modelName = '\PHPCI\Model\BuildMeta'; protected $primaryKey = 'id'; + /** + * Get a BuildMeta by primary key (Id) + */ public function getByPrimaryKey($value, $useConnection = 'read') { return $this->getById($value, $useConnection); } + /** + * Get a single BuildMeta by Id. + * @return null|BuildMeta + */ public function getById($value, $useConnection = 'read') { if (is_null($value)) { throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $query = 'SELECT * FROM build_meta WHERE id = :id LIMIT 1'; + $query = 'SELECT * FROM `build_meta` WHERE `id` = :id LIMIT 1'; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':id', $value); @@ -44,23 +51,21 @@ class BuildMetaStoreBase extends Store return null; } - public function getByBuildId($value, $limit = null, $useConnection = 'read') + /** + * Get multiple BuildMeta by ProjectId. + * @return array + */ + public function getByProjectId($value, $limit = 1000, $useConnection = 'read') { if (is_null($value)) { throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $add = ''; - if ($limit) { - $add .= ' LIMIT ' . $limit; - } - - $count = null; - - $query = 'SELECT * FROM build_meta WHERE build_id = :build_id' . $add; + $query = 'SELECT * FROM `build_meta` WHERE `project_id` = :project_id LIMIT :limit'; $stmt = Database::getConnection($useConnection)->prepare($query); - $stmt->bindValue(':build_id', $value); + $stmt->bindValue(':project_id', $value); + $stmt->bindValue(':limit', (int)$limit, \PDO::PARAM_INT); if ($stmt->execute()) { $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); @@ -70,6 +75,40 @@ class BuildMetaStoreBase extends Store }; $rtn = array_map($map, $res); + $count = count($rtn); + + return array('items' => $rtn, 'count' => $count); + } else { + return array('items' => array(), 'count' => 0); + } + } + + /** + * Get multiple BuildMeta by BuildId. + * @return array + */ + public function getByBuildId($value, $limit = 1000, $useConnection = 'read') + { + if (is_null($value)) { + throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); + } + + + $query = 'SELECT * FROM `build_meta` WHERE `build_id` = :build_id LIMIT :limit'; + $stmt = Database::getConnection($useConnection)->prepare($query); + $stmt->bindValue(':build_id', $value); + $stmt->bindValue(':limit', (int)$limit, \PDO::PARAM_INT); + + if ($stmt->execute()) { + $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $map = function ($item) { + return new BuildMeta($item); + }; + $rtn = array_map($map, $res); + + $count = count($rtn); + return array('items' => $rtn, 'count' => $count); } else { return array('items' => array(), 'count' => 0); diff --git a/PHPCI/Store/Base/BuildStoreBase.php b/PHPCI/Store/Base/BuildStoreBase.php index f0e66085..b8b49cb7 100644 --- a/PHPCI/Store/Base/BuildStoreBase.php +++ b/PHPCI/Store/Base/BuildStoreBase.php @@ -8,7 +8,7 @@ namespace PHPCI\Store\Base; use b8\Database; use b8\Exception\HttpException; -use b8\Store; +use PHPCI\Store; use PHPCI\Model\Build; /** @@ -20,18 +20,25 @@ class BuildStoreBase extends Store protected $modelName = '\PHPCI\Model\Build'; protected $primaryKey = 'id'; + /** + * Get a Build by primary key (Id) + */ public function getByPrimaryKey($value, $useConnection = 'read') { return $this->getById($value, $useConnection); } + /** + * Get a single Build by Id. + * @return null|Build + */ public function getById($value, $useConnection = 'read') { if (is_null($value)) { throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $query = 'SELECT * FROM build WHERE id = :id LIMIT 1'; + $query = 'SELECT * FROM `build` WHERE `id` = :id LIMIT 1'; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':id', $value); @@ -44,23 +51,21 @@ class BuildStoreBase extends Store return null; } - public function getByProjectId($value, $limit = null, $useConnection = 'read') + /** + * Get multiple Build by ProjectId. + * @return array + */ + public function getByProjectId($value, $limit = 1000, $useConnection = 'read') { if (is_null($value)) { throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $add = ''; - if ($limit) { - $add .= ' LIMIT ' . $limit; - } - - $count = null; - - $query = 'SELECT * FROM build WHERE project_id = :project_id' . $add; + $query = 'SELECT * FROM `build` WHERE `project_id` = :project_id LIMIT :limit'; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':project_id', $value); + $stmt->bindValue(':limit', (int)$limit, \PDO::PARAM_INT); if ($stmt->execute()) { $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); @@ -70,29 +75,29 @@ class BuildStoreBase extends Store }; $rtn = array_map($map, $res); + $count = count($rtn); + return array('items' => $rtn, 'count' => $count); } else { return array('items' => array(), 'count' => 0); } } - public function getByStatus($value, $limit = null, $useConnection = 'read') + /** + * Get multiple Build by Status. + * @return array + */ + public function getByStatus($value, $limit = 1000, $useConnection = 'read') { if (is_null($value)) { throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $add = ''; - if ($limit) { - $add .= ' LIMIT ' . $limit; - } - - $count = null; - - $query = 'SELECT * FROM build WHERE status = :status' . $add; + $query = 'SELECT * FROM `build` WHERE `status` = :status LIMIT :limit'; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':status', $value); + $stmt->bindValue(':limit', (int)$limit, \PDO::PARAM_INT); if ($stmt->execute()) { $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); @@ -102,6 +107,8 @@ class BuildStoreBase extends Store }; $rtn = array_map($map, $res); + $count = count($rtn); + return array('items' => $rtn, 'count' => $count); } else { return array('items' => array(), 'count' => 0); diff --git a/PHPCI/Store/Base/ProjectGroupStoreBase.php b/PHPCI/Store/Base/ProjectGroupStoreBase.php new file mode 100644 index 00000000..c7cd8772 --- /dev/null +++ b/PHPCI/Store/Base/ProjectGroupStoreBase.php @@ -0,0 +1,53 @@ +getById($value, $useConnection); + } + + /** + * Get a single ProjectGroup by Id. + * @return null|ProjectGroup + */ + public function getById($value, $useConnection = 'read') + { + if (is_null($value)) { + throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); + } + + $query = 'SELECT * FROM `project_group` WHERE `id` = :id LIMIT 1'; + $stmt = Database::getConnection($useConnection)->prepare($query); + $stmt->bindValue(':id', $value); + + if ($stmt->execute()) { + if ($data = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return new ProjectGroup($data); + } + } + + return null; + } +} diff --git a/PHPCI/Store/Base/ProjectStoreBase.php b/PHPCI/Store/Base/ProjectStoreBase.php index 8b78c055..1e2bf65b 100644 --- a/PHPCI/Store/Base/ProjectStoreBase.php +++ b/PHPCI/Store/Base/ProjectStoreBase.php @@ -8,7 +8,7 @@ namespace PHPCI\Store\Base; use b8\Database; use b8\Exception\HttpException; -use b8\Store; +use PHPCI\Store; use PHPCI\Model\Project; /** @@ -20,18 +20,25 @@ class ProjectStoreBase extends Store protected $modelName = '\PHPCI\Model\Project'; protected $primaryKey = 'id'; + /** + * Get a Project by primary key (Id) + */ public function getByPrimaryKey($value, $useConnection = 'read') { return $this->getById($value, $useConnection); } + /** + * Get a single Project by Id. + * @return null|Project + */ public function getById($value, $useConnection = 'read') { if (is_null($value)) { throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $query = 'SELECT * FROM project WHERE id = :id LIMIT 1'; + $query = 'SELECT * FROM `project` WHERE `id` = :id LIMIT 1'; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':id', $value); @@ -44,23 +51,21 @@ class ProjectStoreBase extends Store return null; } - public function getByTitle($value, $limit = null, $useConnection = 'read') + /** + * Get multiple Project by Title. + * @return array + */ + public function getByTitle($value, $limit = 1000, $useConnection = 'read') { if (is_null($value)) { throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $add = ''; - if ($limit) { - $add .= ' LIMIT ' . $limit; - } - - $count = null; - - $query = 'SELECT * FROM project WHERE title = :title' . $add; + $query = 'SELECT * FROM `project` WHERE `title` = :title LIMIT :limit'; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':title', $value); + $stmt->bindValue(':limit', (int)$limit, \PDO::PARAM_INT); if ($stmt->execute()) { $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); @@ -70,6 +75,40 @@ class ProjectStoreBase extends Store }; $rtn = array_map($map, $res); + $count = count($rtn); + + return array('items' => $rtn, 'count' => $count); + } else { + return array('items' => array(), 'count' => 0); + } + } + + /** + * Get multiple Project by GroupId. + * @return array + */ + public function getByGroupId($value, $limit = 1000, $useConnection = 'read') + { + if (is_null($value)) { + throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); + } + + + $query = 'SELECT * FROM `project` WHERE `group_id` = :group_id LIMIT :limit'; + $stmt = Database::getConnection($useConnection)->prepare($query); + $stmt->bindValue(':group_id', $value); + $stmt->bindValue(':limit', (int)$limit, \PDO::PARAM_INT); + + if ($stmt->execute()) { + $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $map = function ($item) { + return new Project($item); + }; + $rtn = array_map($map, $res); + + $count = count($rtn); + return array('items' => $rtn, 'count' => $count); } else { return array('items' => array(), 'count' => 0); diff --git a/PHPCI/Store/Base/UserStoreBase.php b/PHPCI/Store/Base/UserStoreBase.php index c4268804..105ccd3e 100644 --- a/PHPCI/Store/Base/UserStoreBase.php +++ b/PHPCI/Store/Base/UserStoreBase.php @@ -8,7 +8,7 @@ namespace PHPCI\Store\Base; use b8\Database; use b8\Exception\HttpException; -use b8\Store; +use PHPCI\Store; use PHPCI\Model\User; /** @@ -20,18 +20,25 @@ class UserStoreBase extends Store protected $modelName = '\PHPCI\Model\User'; protected $primaryKey = 'id'; + /** + * Get a User by primary key (Id) + */ public function getByPrimaryKey($value, $useConnection = 'read') { return $this->getById($value, $useConnection); } + /** + * Get a single User by Id. + * @return null|User + */ public function getById($value, $useConnection = 'read') { if (is_null($value)) { throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $query = 'SELECT * FROM user WHERE id = :id LIMIT 1'; + $query = 'SELECT * FROM `user` WHERE `id` = :id LIMIT 1'; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':id', $value); @@ -44,13 +51,17 @@ class UserStoreBase extends Store return null; } + /** + * Get a single User by Email. + * @return null|User + */ public function getByEmail($value, $useConnection = 'read') { if (is_null($value)) { throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $query = 'SELECT * FROM user WHERE email = :email LIMIT 1'; + $query = 'SELECT * FROM `user` WHERE `email` = :email LIMIT 1'; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':email', $value); @@ -62,4 +73,36 @@ class UserStoreBase extends Store return null; } + + /** + * Get multiple User by Name. + * @return array + */ + public function getByName($value, $limit = 1000, $useConnection = 'read') + { + if (is_null($value)) { + throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); + } + + + $query = 'SELECT * FROM `user` WHERE `name` = :name LIMIT :limit'; + $stmt = Database::getConnection($useConnection)->prepare($query); + $stmt->bindValue(':name', $value); + $stmt->bindValue(':limit', (int)$limit, \PDO::PARAM_INT); + + if ($stmt->execute()) { + $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $map = function ($item) { + return new User($item); + }; + $rtn = array_map($map, $res); + + $count = count($rtn); + + return array('items' => $rtn, 'count' => $count); + } else { + return array('items' => array(), 'count' => 0); + } + } } diff --git a/PHPCI/Store/BuildErrorStore.php b/PHPCI/Store/BuildErrorStore.php new file mode 100644 index 00000000..c2d32468 --- /dev/null +++ b/PHPCI/Store/BuildErrorStore.php @@ -0,0 +1,80 @@ + :since'; + } + + $query .= ' LIMIT 15000'; + + $stmt = Database::getConnection('read')->prepare($query); + + $stmt->bindValue(':build', $buildId, \PDO::PARAM_INT); + + if (!is_null($since)) { + $stmt->bindValue(':since', $since); + } + + if ($stmt->execute()) { + $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $map = function ($item) { + return new BuildError($item); + }; + $rtn = array_map($map, $res); + + return $rtn; + } else { + return array(); + } + } + + /** + * Gets the total number of errors for a given build. + * @param $buildId + * @param string $since date string + * @return array + */ + public function getErrorTotalForBuild($buildId) + { + $query = 'SELECT COUNT(*) AS total FROM build_error + WHERE build_id = :build'; + + $stmt = Database::getConnection('read')->prepare($query); + + $stmt->bindValue(':build', $buildId, \PDO::PARAM_INT); + + if ($stmt->execute()) { + $res = $stmt->fetch(\PDO::FETCH_ASSOC); + return $res['total']; + } else { + return array(); + } + } +} diff --git a/PHPCI/Store/BuildMetaStore.php b/PHPCI/Store/BuildMetaStore.php index ad17ef93..f932bc11 100644 --- a/PHPCI/Store/BuildMetaStore.php +++ b/PHPCI/Store/BuildMetaStore.php @@ -1,12 +1,17 @@ prepare($query); + + $stmt->bindValue(':limit', $limit, \PDO::PARAM_INT); + + if ($stmt->execute()) { + $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $map = function ($item) { + return new BuildMeta($item); + }; + $rtn = array_map($map, $res); + + return $rtn; + } else { + return array(); + } + } } diff --git a/PHPCI/Store/BuildStore.php b/PHPCI/Store/BuildStore.php index e419c9cc..d6feb084 100644 --- a/PHPCI/Store/BuildStore.php +++ b/PHPCI/Store/BuildStore.php @@ -1,14 +1,16 @@ prepare($query); - $stmt->bindValue(':pid', $projectId); + if (!is_null($projectId)) { + $query = 'SELECT * FROM build WHERE `project_id` = :pid ORDER BY id DESC LIMIT :limit'; + } else { + $query = 'SELECT * FROM build ORDER BY id DESC LIMIT :limit'; + } + + $stmt = Database::getConnection('read')->prepare($query); + + if (!is_null($projectId)) { + $stmt->bindValue(':pid', $projectId); + } + + $stmt->bindValue(':limit', $limit, \PDO::PARAM_INT); if ($stmt->execute()) { $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); @@ -39,19 +57,119 @@ class BuildStore extends BuildStoreBase } } - public function getMeta($key, $projectId, $buildId = null, $numResults = 1) + /** + * Return the latest build for a specific project, of a specific build status. + * @param null $projectId + * @param int $status + * @return array|Build + */ + public function getLastBuildByStatus($projectId = null, $status = Build::STATUS_SUCCESS) { - $select = '`build_id`, `meta_key`, `meta_value`'; - $and = $numResults > 1 ? ' AND (`build_id` <= :buildId) ' : ' AND (`build_id` = :buildId) '; - $where = '`meta_key` = :key AND `project_id` = :projectId ' . $and; - $query = 'SELECT '.$select.' FROM `build_meta` WHERE '.$where.' ORDER BY id DESC LIMIT :numResults'; + $query = 'SELECT * FROM build WHERE project_id = :pid AND status = :status ORDER BY id DESC LIMIT 1'; + $stmt = Database::getConnection('read')->prepare($query); + $stmt->bindValue(':pid', $projectId); + $stmt->bindValue(':status', $status); - $stmt = \b8\Database::getConnection('read')->prepare($query); + if ($stmt->execute()) { + if ($data = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return new Build($data); + } + } else { + return array(); + } + } + + /** + * Return an array of builds for a given project and commit ID. + * @param $projectId + * @param $commitId + * @return array + */ + public function getByProjectAndCommit($projectId, $commitId) + { + $query = 'SELECT * FROM `build` WHERE `project_id` = :project_id AND `commit_id` = :commit_id'; + $stmt = Database::getConnection('read')->prepare($query); + $stmt->bindValue(':project_id', $projectId); + $stmt->bindValue(':commit_id', $commitId); + + if ($stmt->execute()) { + $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $map = function ($item) { + return new Build($item); + }; + + $rtn = array_map($map, $res); + + return array('items' => $rtn, 'count' => count($rtn)); + } else { + return array('items' => array(), 'count' => 0); + } + } + + /** + * Returns all registered branches for project + * + * @param $projectId + * @return array + * @throws \Exception + */ + public function getBuildBranches($projectId) + { + $query = 'SELECT DISTINCT `branch` FROM `build` WHERE `project_id` = :project_id'; + $stmt = Database::getConnection('read')->prepare($query); + $stmt->bindValue(':project_id', $projectId); + + if ($stmt->execute()) { + $res = $stmt->fetchAll(\PDO::FETCH_COLUMN); + return $res; + } else { + return array(); + } + } + + /** + * Return build metadata by key, project and optionally build id. + * @param $key + * @param $projectId + * @param null $buildId + * @param null $branch + * @param int $numResults + * @return array|null + */ + public function getMeta($key, $projectId, $buildId = null, $branch = null, $numResults = 1) + { + $query = 'SELECT bm.build_id, bm.meta_key, bm.meta_value + FROM build_meta AS bm + LEFT JOIN build b ON b.id = bm.build_id + WHERE bm.meta_key = :key + AND bm.project_id = :projectId'; + + // If we're getting comparative meta data, include previous builds + // otherwise just include the specified build ID: + if ($numResults > 1) { + $query .= ' AND bm.build_id <= :buildId '; + } else { + $query .= ' AND bm.build_id = :buildId '; + } + + // Include specific branch information if required: + if (!is_null($branch)) { + $query .= ' AND b.branch = :branch '; + } + + $query .= ' ORDER BY bm.id DESC LIMIT :numResults'; + + $stmt = Database::getConnection('read')->prepare($query); $stmt->bindValue(':key', $key, \PDO::PARAM_STR); $stmt->bindValue(':projectId', (int)$projectId, \PDO::PARAM_INT); $stmt->bindValue(':buildId', (int)$buildId, \PDO::PARAM_INT); $stmt->bindValue(':numResults', (int)$numResults, \PDO::PARAM_INT); + if (!is_null($branch)) { + $stmt->bindValue(':branch', $branch, \PDO::PARAM_STR); + } + if ($stmt->execute()) { $rtn = $stmt->fetchAll(\PDO::FETCH_ASSOC); @@ -66,18 +184,25 @@ class BuildStore extends BuildStoreBase } else { return $rtn; } - } else { return null; } } + /** + * Set a metadata value for a given project and build ID. + * @param $projectId + * @param $buildId + * @param $key + * @param $value + * @return bool + */ public function setMeta($projectId, $buildId, $key, $value) { $cols = '`project_id`, `build_id`, `meta_key`, `meta_value`'; $query = 'REPLACE INTO build_meta ('.$cols.') VALUES (:projectId, :buildId, :key, :value)'; - $stmt = \b8\Database::getConnection('read')->prepare($query); + $stmt = Database::getConnection('read')->prepare($query); $stmt->bindValue(':key', $key, \PDO::PARAM_STR); $stmt->bindValue(':projectId', (int)$projectId, \PDO::PARAM_INT); $stmt->bindValue(':buildId', (int)$buildId, \PDO::PARAM_INT); diff --git a/PHPCI/Store/ProjectGroupStore.php b/PHPCI/Store/ProjectGroupStore.php new file mode 100644 index 00000000..fa254e3e --- /dev/null +++ b/PHPCI/Store/ProjectGroupStore.php @@ -0,0 +1,18 @@ +prepare($query); + $stmt->bindValue(':pid', $projectId); + + if ($stmt->execute()) { + $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $map = function ($item) { + return $item['branch']; + }; + $rtn = array_map($map, $res); + + return $rtn; + } else { + return array(); + } + } + + /** + * Get a list of all projects, ordered by their title. + * @return array + */ + public function getAll() + { + $query = 'SELECT * FROM `project` ORDER BY `title` ASC'; + $stmt = Database::getConnection('read')->prepare($query); + + if ($stmt->execute()) { + $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $map = function ($item) { + return new Project($item); + }; + $rtn = array_map($map, $res); + + $count = count($rtn); + + + return array('items' => $rtn, 'count' => $count); + } else { + return array('items' => array(), 'count' => 0); + } + } + + /** + * Get multiple Project by GroupId. + * @param int $value + * @param int $limit + * @param string $useConnection + * @return array + * @throws \Exception + */ + public function getByGroupId($value, $limit = 1000, $useConnection = 'read') + { + if (is_null($value)) { + throw new \Exception('Value passed to ' . __FUNCTION__ . ' cannot be null.'); + } + + $query = 'SELECT * FROM `project` WHERE `group_id` = :group_id ORDER BY title LIMIT :limit'; + $stmt = Database::getConnection($useConnection)->prepare($query); + $stmt->bindValue(':group_id', $value); + $stmt->bindValue(':limit', (int)$limit, \PDO::PARAM_INT); + + if ($stmt->execute()) { + $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $map = function ($item) { + return new Project($item); + }; + $rtn = array_map($map, $res); + + $count = count($rtn); + + return array('items' => $rtn, 'count' => $count); + } else { + return array('items' => array(), 'count' => 0); + } + } } diff --git a/PHPCI/Store/UserStore.php b/PHPCI/Store/UserStore.php index 1a45ed18..5a8f9ef0 100644 --- a/PHPCI/Store/UserStore.php +++ b/PHPCI/Store/UserStore.php @@ -1,11 +1,11 @@ getFileLinkTemplate(); + +/** @var \PHPCI\Model\BuildError $error */ +foreach ($errors as $error): + + $link = str_replace('{FILE}', $error->getFile(), $linkTemplate); + $link = str_replace('{LINE}', $error->getLineStart(), $link); + $link = str_replace('{LINE_END}', $error->getLineEnd(), $link); +?> + + + + + getSeverityString()); ?> + + + getPlugin()); ?> + getFile(); ?> + + + getLineStart() == $error->getLineEnd() || !$error->getLineEnd()) { + print $error->getLineStart(); + } else { + print $error->getLineStart() . ' - ' . $error->getLineEnd(); + } + ?> + + + getMessage(); ?> + + + + \ No newline at end of file diff --git a/PHPCI/View/Build/header-row.phtml b/PHPCI/View/Build/header-row.phtml new file mode 100644 index 00000000..b0559da0 --- /dev/null +++ b/PHPCI/View/Build/header-row.phtml @@ -0,0 +1,21 @@ + +
  • + + getCommitterEmail()): ?> +
    + +
    + + +

    + getProject()->getTitle(); ?> + + getStatus() == \PHPCI\Model\Build::STATUS_NEW): ?> + getCreated()->format('H:i')); ?> + getStatus() == \PHPCI\Model\Build::STATUS_RUNNING): ?> + getStarted()->format('H:i')); ?> + +

    +

    getBranch()); ?>

    +
    +
  • \ No newline at end of file diff --git a/PHPCI/View/Build/view.phtml b/PHPCI/View/Build/view.phtml index a9cd569a..61fafd33 100644 --- a/PHPCI/View/Build/view.phtml +++ b/PHPCI/View/Build/view.phtml @@ -1,42 +1,189 @@ -
    -

    Build #getId(); ?>

    -

    Branch: getBranch(); ?> - getCommitId() == 'Manual' ? 'Manual Build' : 'Commit: ' . $build->getCommitId(); ?>

    -
    +
    -
    - -
    Options
    - -
    +
    +
    +
    +

    + Build Details +

    +
    -
    -
    -
    +
    + + + + + + + + + + + + + + + +
    Project + + + getProject()->getTitle(); ?> + +
    Branch + + getBranch(); ?> + +
    Duration + +
    +
    +
    +
    + +
    +
    +
    +

    + Commit Details +

    +
    + +
    + + + + + + + + + + + + + + +
    Commit + + getCommitId(), 0, 7); ?> + +
    Committer + getCommitterEmail(); ?> +
    + getCommitMessage(); ?> +
    +
    +
    +
    + +
    +
    +
    +

    + Timing +

    +
    + +
    + + + + + + + + + + + + + + + +
    Created +
    Started +
    Finished + +
    +
    +
    +
    + +
    + + + '; + print '' . PHP_EOL; } ?> \ No newline at end of file + + + diff --git a/PHPCI/View/BuildStatus/view.phtml b/PHPCI/View/BuildStatus/view.phtml new file mode 100644 index 00000000..3b5d29a3 --- /dev/null +++ b/PHPCI/View/BuildStatus/view.phtml @@ -0,0 +1,179 @@ + + + + <?php print $project->getTitle(); ?> - PHPCI + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +

    getTitle(); ?>

    + + + getStatus()) { + case 0: + $statusClass = 'info'; + $statusText = 'Pending'; + break; + case 1: + $statusClass = 'warning'; + $statusText = 'Running'; + break; + case 2: + $statusClass = 'success'; + $statusText = 'Success'; + break; + case 3: + $statusClass = 'danger'; + $statusText = 'Failed'; + break; + } + + ?> + + +
    + + +
    +

    + + getProject()->getTitle(); ?> + #getId(); ?> + + +

    +
    + +
    +
    + getCommitMessage()): ?> +
    + getCommitMessage(); ?> +
    + + + Branch: getBranch(); ?>
    + Committer: getCommitterEmail(); ?> + + getCommitId() != 'Manual'): ?> +
    Commit ID: getCommitId(); ?>
    + +
    +
    +
    + + + + +
    +

    Builds

    + + + + + + + + + + + + + + + + + + + + + getStatus()) + { + case 0: + $cls = 'active'; + $subcls = 'info'; + $status = 'Pending'; + + break; + + case 1: + $cls = 'warning'; + $subcls = 'warning'; + $status = 'Running'; + break; + + case 2: + $cls = 'success'; + $subcls = 'success'; + $status = 'Success'; + break; + + case 3: + $cls = 'danger'; + $subcls = 'danger'; + $status = 'Failed'; + break; + } + ?> + + + + + + + + + + + + +
    IDCommitBranchStatus
    No builds yet.
    #getId(), 6, '0', STR_PAD_LEFT); ?> + getCommitId() !== 'Manual') { + print ''; + } + print $build->getCommitId(); + if ($build->getCommitId() !== 'Manual') { + print ''; + } + ?> + getBranch(); ?> + +
    +
    +
    + + diff --git a/PHPCI/View/BuildsTable.phtml b/PHPCI/View/BuildsTable.phtml index 6d72c9a2..f26eb250 100644 --- a/PHPCI/View/BuildsTable.phtml +++ b/PHPCI/View/BuildsTable.phtml @@ -1,7 +1,8 @@ + - No builds yet. + @@ -13,67 +14,71 @@ switch($build->getStatus()) case 0: $cls = 'active'; $subcls = 'info'; - $status = 'Pending'; + $status = Lang::get('pending'); break; case 1: $cls = 'warning'; $subcls = 'warning'; - $status = 'Running'; + $status = Lang::get('running'); break; case 2: $cls = 'success'; $subcls = 'success'; - $status = 'Success'; + $status = Lang::get('success'); break; case 3: $cls = 'danger'; $subcls = 'danger'; - $status = 'Failed'; + $status = Lang::get('failed'); break; } ?> - #getId(), 6, '0', STR_PAD_LEFT); ?> - - #getId(), 6, '0', STR_PAD_LEFT); ?> + getCreated()->format('Y-m-d H:i:s'); ?> + + + + getProject())) { - print $build->getProject()->getTitle(); + print htmlspecialchars($build->getProject()->getTitle()); } else { print ' - '; } ?> - getCommitId(); ?> - getBranch(); ?> - - getPlugins(), true); - if ( !is_array($plugins) ) { - $plugins = array(); - } - if ( 0 === count($plugins) ) { - ?> - $pluginstatus): - $subcls = $pluginstatus?'label label-success':'label label-danger'; - ?> Build()->formatPluginName($plugin); ?> -
    + + getCommitId() !== 'Manual') { + print sprintf( + '%s (%s)', + $build->getCommitLink(), + substr($build->getCommitId(), 0, 7), + $build->getCommitterEmail() + ); + } else { + print Lang::get('manual_build'); + } + ?> + + + getBranch(); ?> + +
    - View + User()->getIsAdmin()): ?> -
    diff --git a/PHPCI/View/Email/layout.phtml b/PHPCI/View/Email/layout.phtml new file mode 100644 index 00000000..878a9c9e --- /dev/null +++ b/PHPCI/View/Email/layout.phtml @@ -0,0 +1,28 @@ + + + + + +
    +
    +
    getTitle(); ?> - Build #getId(); ?>
    +
    +

    + Your commit getCommitId(); ?> generated a + isSuccessful() ? 'successful' : 'failed'; ?> build in project + getTitle(); ?>. +

    + +
    + +
    +
    + + diff --git a/PHPCI/View/Email/long.phtml b/PHPCI/View/Email/long.phtml new file mode 100644 index 00000000..35464504 --- /dev/null +++ b/PHPCI/View/Email/long.phtml @@ -0,0 +1,2 @@ +

    getCommitMessage(); ?>

    +
    getLog()); ?>
    diff --git a/PHPCI/View/Email/short.phtml b/PHPCI/View/Email/short.phtml new file mode 100644 index 00000000..75df4f83 --- /dev/null +++ b/PHPCI/View/Email/short.phtml @@ -0,0 +1 @@ +

    getCommitMessage(); ?>

    diff --git a/PHPCI/View/Group/edit.phtml b/PHPCI/View/Group/edit.phtml new file mode 100644 index 00000000..1e32165b --- /dev/null +++ b/PHPCI/View/Group/edit.phtml @@ -0,0 +1,9 @@ +
    +
    +

    Add / Edit Group

    +
    + +
    + +
    +
    \ No newline at end of file diff --git a/PHPCI/View/Group/index.phtml b/PHPCI/View/Group/index.phtml new file mode 100644 index 00000000..e07c9af1 --- /dev/null +++ b/PHPCI/View/Group/index.phtml @@ -0,0 +1,41 @@ + + + +
    +
    +

    Project Groups

    +
    + + + + + + + + + + + + + + + + + + +
    TitleProjects
    + + Edit + + + + + Delete + + +
    +
    \ No newline at end of file diff --git a/PHPCI/View/Home/index.phtml b/PHPCI/View/Home/index.phtml index d372786d..6bce4be8 100644 --- a/PHPCI/View/Home/index.phtml +++ b/PHPCI/View/Home/index.phtml @@ -1,65 +1,116 @@ -
    -

    Dashboard

    -
    +
    -
    - - -
    Projects
    - - +
    + +
    +
    +

    +
    + +
    + +
    +
    +
    -
    -
    -

    Project Overview

    - - - - - - - - - - - - - - -
    HealthProjectLast SuccessLast FailureSuccess/Failures
    -
    -
    -

    Last 5 Builds

    - - - - - - - - - - - - - - -
    IDProjectCommitBranchStatus
    +
    +
    +
    +

    +
    + +
    +
      + + + getStatus()) { + case \PHPCI\Model\Build::STATUS_NEW: + $updated = $build->getCreated(); + $label = Lang::get('pending'); + $color = 'blue'; + break; + + case \PHPCI\Model\Build::STATUS_RUNNING: + $updated = $build->getStarted(); + $label = Lang::get('running'); + $color = 'yellow'; + break; + + case \PHPCI\Model\Build::STATUS_SUCCESS: + $updated = $build->getFinished(); + $label = Lang::get('successful'); + $color = 'green'; + break; + + case \PHPCI\Model\Build::STATUS_FAILED: + $updated = $build->getFinished(); + $label = Lang::get('failed'); + $color = 'red'; + break; + } + + if (!$updated) { + $updated = $build->getCreated(); + } + + if ($updated->format('Y-m-d') != $last->format('Y-m-d')): $last = $updated; + ?> +
    • + + + +
    • + + + + +
    • + +
      + +

      + + getProject()->getTitle(); ?> + + - + + Build #getId(); ?> + + - + +

      + +
      + getCommitId() !== 'Manual') { + print sprintf( + '%s (%s)', + $build->getCommitLink(), + substr($build->getCommitId(), 0, 7), + $build->getCommitterEmail() + ); + } else { + print Lang::get('manual_build'); + } + ?> + - getCommitMessage(); ?> +
      +
      +
    • + + + + +
    • + +
    • +
    +
    -
    +
    +
    - - \ No newline at end of file diff --git a/PHPCI/View/Plugin/index.phtml b/PHPCI/View/Plugin/index.phtml index f3f10e60..5c186012 100644 --- a/PHPCI/View/Plugin/index.phtml +++ b/PHPCI/View/Plugin/index.phtml @@ -1,177 +1,64 @@ -

    Plugins

    - -

    PHPCI cannot automatically install/remove plugins for you, as the shell_exec() - function is disabled. PHPCI will update composer.json for you, but you will need to run Composer manually to make the changes.

    - + - -

    PHPCI cannot update composer.json for you as it is not writable.

    - - - -

    has been removed.

    - - - -

    has been installed.

    - - - -

    has been added to composer.json and will be installed next time you run composer update.

    - - -
    -

    Installed Plugins

    - - - - - - - - - - - $version): ?> - - - - - - - -
    TitleVersion
    - - Remove » - -
    +
    +

    Adding requirements to the PHPCI composer.json file is no longer recommended as a method of installing your required testing tools.
    + For this reason, we have removed the ability for PHPCI to modify the composer.json file for you. + We recommend that you install testing tools using your project's own composer.json file, by adding them to the "require-dev" section of the file.

    -
    -

    Suggested Plugins

    +
    +
    +
    +
    +

    +
    - - - - - - - - - - $version): ?> - - - - - - - - -
    TitleDescription
    - - - -
    -
    - -
    -

    Search Packagist for More Plugins

    - -
    - - - - + + + + + + + + + + + + + + + + + +
    name; ?>class; ?>source; ?>
    +
    - - -
    - - - diff --git a/PHPCI/View/Project/view.phtml b/PHPCI/View/Project/view.phtml index bb3566f6..79b7ef0e 100644 --- a/PHPCI/View/Project/view.phtml +++ b/PHPCI/View/Project/view.phtml @@ -1,58 +1,60 @@ -
    -

    Project: getTitle() : ' - '; ?>

    + + + +
    + + + + + + + + +
    + + + + +
    + + + +
    +
    +
    -
    - -
    Options
    - - - getType(), array('github', 'gitlab','bitbucket'))): ?> -
    -

    To automatically build this project when new commits are pushed, add the URL below - - - getType()) - { - case 'github': - $url = (empty($_SERVER['HTTPS']) ? 'http' : 'https') . '://' . $_SERVER['HTTP_HOST'] . '/github/webhook/' . $project->getId(); - print ' as a "WebHook URL" in the Service Hooks section of your Github repository.

    ' . $url . ''; - break; - - case 'gitlab': - $url = (empty($_SERVER['HTTPS']) ? 'http' : 'https') . '://' . $_SERVER['HTTP_HOST'] . '/gitlab/webhook/' . $project->getId(); - print ' as a "WebHook URL" in the Web Hooks section of your Gitlab repository.

    ' . $url . ''; - break; - - case 'bitbucket': - $url = (empty($_SERVER['HTTPS']) ? 'http' : 'https') . '://' . $_SERVER['HTTP_HOST'] . '/bitbucket/webhook/' . $project->getId(); - print ' as a "POST" service in the Services section of your Bitbucket repository.

    ' . $url . ''; - break; - } - ?> -

    -
    -
    - +
    +
    +
    +

    ()

    +
    +
    - - - - - + + + + + + @@ -60,40 +62,103 @@
    IDProjectCommitBranchStatus
    +
    +
    + +
    + + getType(), array('github', 'gitlab', 'bitbucket'))): ?> +
    +
    +

    +
    + +
    + getType()) + { + case 'github': + $url = PHPCI_URL . 'webhook/github/' . $project->getId(); + Lang::out('webhooks_help_github', $project->getReference()); + break; + + case 'gitlab': + $url = PHPCI_URL. 'webhook/gitlab/' . $project->getId(); + Lang::out('webhooks_help_gitlab'); + break; + + case 'bitbucket': + $url = PHPCI_URL . 'webhook/bitbucket/' . $project->getId(); + Lang::out('webhooks_help_bitbucket', $project->getReference()); + break; + } + ?> +

    +
    +
    + + + getSshPublicKey()): ?> +
    +

    +
    getSshPublicKey(); ?>
    +
    + +
    +
      '; - $pages = ceil($total / 10); - $pages = $pages == 0 ? 1 : $pages; +print '
        '; - print '
      • «
      • '; +$project_url = PHPCI_URL . 'project/view/' . $project->getId() . ((!empty($branch)) ? '/' . urlencode($branch) : ''); - for($i = 1; $i <= $pages; $i++) - { - print '
      • ' . $i . '
      • '; - } +if ($page > 1) { + print '
      • '.Lang::get('prev_link').'
      • '; +} - print '
      • »
      • '; +if ($pages > 1) { - print '
      '; + $start = $page - 3; + + if ($start <= 0) { + $start = 1; + } + + $end = $page + 3; + + if ($end > $pages) { + $end = $pages; + } + + if ($start > 1) { + print '
    • 1...
    • '; + } + + for($i = $start; $i <= $end; $i++) + { + if ($pages > $end && $i == $pages) continue; + + if ($i == $page) { + print '
    • ' . $i . '
    • '; + } else { + print '
    • ' . $i . '
    • '; + } + } + + if ($pages > $end) { + print '
    • ...'.$pages.'
    • '; + } +} + + + +if ($page < $pages - 1) { + print '
    • '.Lang::get('next_link').'
    • '; +} + + + +print '
    '; ?> -
    -
    - - - - diff --git a/PHPCI/View/ProjectForm.phtml b/PHPCI/View/ProjectForm.phtml index 26f4727e..5accf682 100644 --- a/PHPCI/View/ProjectForm.phtml +++ b/PHPCI/View/ProjectForm.phtml @@ -1,26 +1,30 @@ -
    -

    -
    - +
    -
    -
    - -

    To make it easier to get started, we've generated a public / private key pair for you to use for this project. To use it, just add the following public key to the "deploy keys" section of your repository settings on Github / Bitbucket.

    - - -

    Fill in the form to the right to add your new project.

    - -

    Edit your project details using the form to the right.

    - -
    -
    -
    -
    - +
    +
    +
    +

    +
    + +
    + +
    + + +
    +
    +
    +

    + + +
    +
    +
    + +
    \ No newline at end of file + diff --git a/PHPCI/View/Session.phtml b/PHPCI/View/Session.phtml new file mode 100644 index 00000000..4597e43e --- /dev/null +++ b/PHPCI/View/Session.phtml @@ -0,0 +1,103 @@ + + + + + <?php Lang::out('log_in_to_phpci'); ?> + + + + + + + + + + + + + + diff --git a/PHPCI/View/Session/forgotPassword.phtml b/PHPCI/View/Session/forgotPassword.phtml new file mode 100644 index 00000000..df04f592 --- /dev/null +++ b/PHPCI/View/Session/forgotPassword.phtml @@ -0,0 +1,31 @@ + + +

    + +

    + + +
    + +
    + +
    + +
    + + +
    +
    +
    + + +
    + +
    + +
    +
    +
    + + + \ No newline at end of file diff --git a/PHPCI/View/Session/login.phtml b/PHPCI/View/Session/login.phtml index 59027b0e..842c4c20 100644 --- a/PHPCI/View/Session/login.phtml +++ b/PHPCI/View/Session/login.phtml @@ -1,91 +1,9 @@ - - - - Log in to PHPCI + + +

    + + - - - - - - - - - -
    -
    - -
    - -

    Incorrect email address or password

    - - -
    - - -
    -
    - - \ No newline at end of file + + + \ No newline at end of file diff --git a/PHPCI/View/Session/resetPassword.phtml b/PHPCI/View/Session/resetPassword.phtml new file mode 100644 index 00000000..16c2ee66 --- /dev/null +++ b/PHPCI/View/Session/resetPassword.phtml @@ -0,0 +1,26 @@ + + + +
    + +
    + +
    +
    +
    + + +
    + +
    + +
    +
    +
    + +
    + +
    + + + diff --git a/PHPCI/View/Settings/index.phtml b/PHPCI/View/Settings/index.phtml index c8206811..c97cbbc2 100644 --- a/PHPCI/View/Settings/index.phtml +++ b/PHPCI/View/Settings/index.phtml @@ -1,62 +1,132 @@ - + + +

    - Your settings have been saved. +

    + +

    + +

    + + + +

    + +

    + +

    - Your Github account has been linked. +

    - Your Github account could not be linked. +

    -
    -
    -
    -

    Github Application

    +
    +
    + +
    +
    - - -

    - Before you can start using Github, you need to sign in and grant PHPCI access to your account. -

    - +
    +

    +
    + +
    +
    - -

    - PHPCI is successfully linked to Github account - - - -

    - -
    +
    +

    +
    -
    - -
    +
    +
    + -
    -
    -

    Where to find these...

    -
    + if (isset($settings['phpci']['github']['id'])) { + $id = $settings['phpci']['github']['id']; + } -
    -

    If you own the application you would like to use, you can find this information within your - applications settings area.

    + $returnTo = PHPCI_URL . 'settings/github-callback'; + $githubUri = 'https://github.com/login/oauth/authorize?client_id='.$id.'&scope=repo&redirect_uri=' . $returnTo; + ?> + + +

    + +

    + +

    + + + + + +

    + + +
    + +
    + +
    + +
    +
    +
    +

    +
    + +
    +

    + +

    +
    + +
    +
    + + +
    +
    +

    +
    + +
    + +

    + +

    + + + + +
    +
    + +
    +
    +

    Authentication Settings

    +
    + +
    +

    + Be careful: This setting disables authentication and uses your current admin account for all actions within phpci with admin rights. +

    + + +
    diff --git a/PHPCI/View/SummaryTable.phtml b/PHPCI/View/SummaryTable.phtml index 087b5cef..5e2192b4 100644 --- a/PHPCI/View/SummaryTable.phtml +++ b/PHPCI/View/SummaryTable.phtml @@ -1,45 +1,34 @@ getId()])) { - - // Use the latest build information to determine current status: - $latestBuild = $builds[$project->getId()][0]; - - switch ($latestBuild->getStatus()) { + // Get the most recent build status to determine the main block colour. + $last_build = $builds[$project->getId()][0]; + $status = $last_build->getStatus(); + switch($status) { case 0: - $cls = 'active'; - $status = 'Pending'; + $subcls = 'blue'; break; - case 1: - $cls = 'warning'; - $status = 'Running'; + $subcls = 'yellow'; break; - case 2: - $cls = 'success'; - $status = 'Success'; + $subcls = 'green'; break; - case 3: - $cls = 'danger'; - $status = 'Failed'; + $subcls = 'red'; break; } - // Use the last 5 builds to determine project health: - $successes = 0; $failures = 0; foreach ($builds[$project->getId()] as $build) { @@ -51,47 +40,122 @@ foreach($projects as $project): $statuses[] = 'running'; break; case 2: - $successes++; $statuses[] = 'ok'; - $success = is_null($success) && !is_null($build->getFinished()) ? $build->getFinished()->format('Y-m-d H:i:s') : $success; + $success = is_null($success) && !is_null($build->getFinished()) ? Lang::formatDateTime($build->getFinished()) : $success; break; case 3: $failures++; $statuses[] = 'failed'; - $failure = is_null($failure) && !is_null($build->getFinished()) ? $build->getFinished()->format('Y-m-d H:i:s') : $failure; + $failure = is_null($failure) && !is_null($build->getFinished()) ? Lang::formatDateTime($build->getFinished()) : $failure; break; } } } - if ($failures == 0) { - $health = 'Good'; - $subcls = 'success'; - } elseif ($failures > $successes) { - $health = 'Bad'; - $subcls = 'danger'; - } else { - $health = 'Warning'; - $subcls = 'warning'; + $buildCount = count($builds[$project->getId()]); + $lastSuccess = $successful[$project->getId()]; + $lastFailure = $failed[$project->getId()]; + $message = Lang::get('no_builds_yet'); + $shortMessage = Lang::get('no_builds_yet'); + + if ($buildCount > 0) { + if ($failures > 0) { + $shortMessage = Lang::get('x_of_x_failed_short', $failures, $buildCount); + $message = Lang::get('x_of_x_failed', $failures, $buildCount); + + if (!is_null($lastSuccess) && !is_null($lastSuccess->getFinished())) { + $message .= Lang::get('last_successful_build', Lang::formatDateTime($lastSuccess->getFinished())); + } else { + $message .= Lang::get('never_built_successfully'); + } + } else { + $message = Lang::get('all_builds_passed', $buildCount); + $shortMessage = Lang::get('all_builds_passed_short', $buildCount, $buildCount); + + if (!is_null($lastFailure) && !is_null($lastFailure->getFinished())) { + $message .= Lang::get('last_failed_build', Lang::formatDateTime($lastFailure->getFinished())); + } else { + $message .= Lang::get('never_failed_build'); + } + } } ?> - - - - - - - getTitle() ?> - - - - '; - } - ?> - - build now » - + + 5) { + $containerClass = 'small-box-minimal'; + } else { + $containerClass = 'small-box-full'; + } +?> + +
    + + 5): ?> + +
    +

    + + + getTitle(); ?> + + - + +

    +
    + + + +
    +

    + + getTitle(); ?> + +

    + +

    + +

    + +
    +
    + +
    + + (getId()]; ?>) + + + + + getId()][$idx])) { + echo ''; + } else { + $build = $builds[$project->getId()][$idx]; + $link = PHPCI_URL . 'build/view/' . $build->id; + switch ($build->getStatus()) { + case 0: + $class = 'bg-blue'; + $icon = 'fa-minus'; + break; + case 1: + $class = 'bg-yellow'; + $icon = 'fa-clock-o'; + break; + case 2: + $class = 'bg-green'; + $icon = 'fa-check'; + break; + case 3: + $class = 'bg-red'; + $icon = 'fa-times'; + break; + } + echo ''; + } + } ?> +
    +
    + diff --git a/PHPCI/View/User/index.phtml b/PHPCI/View/User/index.phtml index 147c6ea6..061be90f 100644 --- a/PHPCI/View/User/index.phtml +++ b/PHPCI/View/User/index.phtml @@ -1,63 +1,61 @@ -
    -

    Users

    + +
    +
    + +
    -
    - -
    -
    - - - - - - - - - - - - getIsAdmin()) - { - case 0: - $cls = ''; - $status = 'No'; - break; +
    +
    + +
    Email AddressNameAdministrator
    + + + + + + + + + + + getIsAdmin()) + { + case 0: + $cls = ''; + $status = Lang::get('no'); + break; + + case 1: + $cls = 'warning'; + $status = Lang::get('yes'); + break; + } + ?> + + + + + + + + +
    getEmail(); ?>getName()); ?> + User()->getIsAdmin()): ?> +
    + + + +
    + +
    +
    - case 1: - $cls = 'warning'; - $status = 'Yes'; - break; - } - ?> - - getEmail(); ?> - getName(); ?> - - - User()->getIsAdmin()): ?> -
    - Edit - - -
    - - - - - -
    diff --git a/PHPCI/View/User/profile.phtml b/PHPCI/View/User/profile.phtml new file mode 100644 index 00000000..5c8b5f68 --- /dev/null +++ b/PHPCI/View/User/profile.phtml @@ -0,0 +1,14 @@ + + +

    + + +
    +
    +

    +
    +
    + +
    +
    + diff --git a/PHPCI/View/UserForm.phtml b/PHPCI/View/UserForm.phtml index 40ddcc19..4a83e7ec 100644 --- a/PHPCI/View/UserForm.phtml +++ b/PHPCI/View/UserForm.phtml @@ -1,20 +1,9 @@ -
    -

    getName() ?>

    -
    -
    -
    -
    - -

    Fill in the form to the right to add a new user.

    - -

    Edit user details using the form to the right.

    - -
    -
    -
    -
    - +
    +
    +
    + +
    -
    \ No newline at end of file +
    diff --git a/PHPCI/View/exception.phtml b/PHPCI/View/exception.phtml new file mode 100644 index 00000000..74f97fd0 --- /dev/null +++ b/PHPCI/View/exception.phtml @@ -0,0 +1,9 @@ +
    +
    +

    Sorry, there was a problem

    +
    + +
    + getMessage(); ?> +
    +
    \ No newline at end of file diff --git a/PHPCI/View/layout.phtml b/PHPCI/View/layout.phtml index da33cd3c..1960aff6 100644 --- a/PHPCI/View/layout.phtml +++ b/PHPCI/View/layout.phtml @@ -1,71 +1,322 @@ + - - PHPCI + + + <?php print $title; ?><?php print !empty($subtitle) ? ' - ' . $subtitle : ''; ?> + - - + + + - - - + + + - - + + + + - + - + + var PHPCI_STRINGS = ; + + var PHPCI_STRINGS = ; + + - - - - - - - '; + } + ?> -
    - -
    + + + +
    -
    Loading...
    - + + + + + + + + + + + + + + diff --git a/PHPCI/Worker/BuildWorker.php b/PHPCI/Worker/BuildWorker.php new file mode 100644 index 00000000..92ee4ced --- /dev/null +++ b/PHPCI/Worker/BuildWorker.php @@ -0,0 +1,210 @@ +host = $host; + $this->queue = $queue; + $this->pheanstalk = new Pheanstalk($this->host); + } + + /** + * @param int $maxJobs + */ + public function setMaxJobs($maxJobs = -1) + { + $this->maxJobs = $maxJobs; + } + + /** + * @param Logger $logger + */ + public function setLogger(Logger $logger) + { + $this->logger = $logger; + } + + /** + * Start the worker. + */ + public function startWorker() + { + $this->pheanstalk->watch($this->queue); + $this->pheanstalk->ignore('default'); + $buildStore = Factory::getStore('Build'); + + while ($this->run) { + // Get a job from the queue: + $job = $this->pheanstalk->reserve(); + + $this->checkJobLimit(); + + // Get the job data and run the job: + $jobData = json_decode($job->getData(), true); + + if (!$this->verifyJob($job, $jobData)) { + continue; + } + + $this->logger->addInfo('Received build #'.$jobData['build_id'].' from Beanstalkd'); + + // If the job comes with config data, reset our config and database connections + // and then make sure we kill the worker afterwards: + if (!empty($jobData['config'])) { + $this->logger->addDebug('Using job-specific config.'); + $currentConfig = Config::getInstance()->getArray(); + $config = new Config($jobData['config']); + Database::reset($config); + } + + try { + $build = BuildFactory::getBuildById($jobData['build_id']); + } catch (\Exception $ex) { + $this->logger->addWarning('Build #' . $jobData['build_id'] . ' does not exist in the database.'); + $this->pheanstalk->delete($job); + } + + try { + // Logging relevant to this build should be stored + // against the build itself. + $buildDbLog = new BuildDBLogHandler($build, Logger::INFO); + $this->logger->pushHandler($buildDbLog); + + $builder = new Builder($build, $this->logger); + $builder->execute(); + + // After execution we no longer want to record the information + // back to this specific build so the handler should be removed. + $this->logger->popHandler($buildDbLog); + } catch (\PDOException $ex) { + // If we've caught a PDO Exception, it is probably not the fault of the build, but of a failed + // connection or similar. Release the job and kill the worker. + $this->run = false; + $this->pheanstalk->release($job); + } catch (\Exception $ex) { + $build->setStatus(Build::STATUS_FAILED); + $build->setFinished(new \DateTime()); + $build->setLog($build->getLog() . PHP_EOL . PHP_EOL . $ex->getMessage()); + $buildStore->save($build); + $build->sendStatusPostback(); + } + + // Reset the config back to how it was prior to running this job: + if (!empty($currentConfig)) { + $config = new Config($currentConfig); + Database::reset($config); + } + + // Delete the job when we're done: + $this->pheanstalk->delete($job); + } + } + + /** + * Stops the worker after the current build. + */ + public function stopWorker() + { + $this->run = false; + } + + /** + * Checks if this worker has done the amount of jobs it is allowed to do, and if so tells it to stop + * after this job completes. + */ + protected function checkJobLimit() + { + // Make sure we don't run more than maxJobs jobs on this worker: + $this->totalJobs++; + + if ($this->maxJobs != -1 && $this->maxJobs <= $this->totalJobs) { + $this->stopWorker(); + } + } + + /** + * Checks that the job received is actually from PHPCI, and has a valid type. + * @param Job $job + * @param $jobData + * @return bool + */ + protected function verifyJob(Job $job, $jobData) + { + if (empty($jobData) || !is_array($jobData)) { + // Probably not from PHPCI. + $this->pheanstalk->delete($job); + return false; + } + + if (!array_key_exists('type', $jobData) || $jobData['type'] !== 'phpci.build') { + // Probably not from PHPCI. + $this->pheanstalk->delete($job); + return false; + } + + return true; + } +} diff --git a/PHPCI/ZeroConfigPlugin.php b/PHPCI/ZeroConfigPlugin.php new file mode 100644 index 00000000..73f7746c --- /dev/null +++ b/PHPCI/ZeroConfigPlugin.php @@ -0,0 +1,21 @@ + + */ +interface ZeroConfigPlugin +{ + public static function canExecute($stage, Builder $builder, Build $build); +} diff --git a/PHPCI/build/.gitignore b/PHPCI/build/.gitignore old mode 100755 new mode 100644 index c96a04f0..d6b7ef32 --- a/PHPCI/build/.gitignore +++ b/PHPCI/build/.gitignore @@ -1,2 +1,2 @@ * -!.gitignore \ No newline at end of file +!.gitignore diff --git a/README.md b/README.md index f1233c2d..77683b91 100644 --- a/README.md +++ b/README.md @@ -1,141 +1,41 @@ PHPCI ===== -PHPCI is a free and open source continuous integration tool specifically designed for PHP. We've built it with simplicity in mind, so whilst it doesn't do *everything* Jenkins can do, it is a breeze to set up and use. - -_**Please be aware that PHPCI is a beta-release project, so whilst it is very stable, there may be bugs and/or missing features.**_ +PHPCI is a free and open source (BSD License) continuous integration tool specifically designed for PHP. We've built it with simplicity in mind, so whilst it doesn't do *everything* Jenkins can do, it is a breeze to set up and use. **Current Build Status** -![Build Status](http://phpci.block8.net/build-status/image/2) +[![Build Status](http://phpci.block8.net/build-status/image/2?branch=master)](http://phpci.block8.net/build-status/view/2?branch=master) -##What it does: +**Chat Room** + +We have a chat room for discussing PHPCI, you can access it here: [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/Block8/PHPCI?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge) + +**Support the development of PHPCI** + +We [now accept donations](https://www.phptesting.org/support) to directly support the ongoing development of PHPCI. There is of course no obligation to donate, nor any commitment if you do. + +[Donate](https://www.phptesting.org/support) + +## What it does: * Clones your project from Github, Bitbucket or a local path * Allows you to set up and tear down test databases. * Installs your project's Composer dependencies. -* Runs through any combination of the following plugins: - * PHP Unit - * PHP Mess Detector - * PHP Copy/Paste Detector - * PHP Code Sniffer - * PHP Spec - * Atoum +* Runs through any combination of the [supported plugins](https://www.phptesting.org/wiki#plugins). * You can mark directories for the plugins to ignore. * You can mark certain plugins as being allowed to fail (but still run.) -##What it doesn't do (yet): +### What it doesn't do (yet): * Virtualised testing. * Multiple PHP-version tests. -* Multiple testing workers. * Install PEAR or PECL extensions. -* Deployments. +* Deployments - We strongly recommend using [Deployer](http://phpdeployment.org) -##Installing PHPCI: -####Pre-requisites: -* PHP 5.3.3+ -* A web server. We prefer nginx. -* A MySQL server to connect to (doesn't have to be on the same server.) -* PHPCI needs to be able to run `exec()`, so make sure this is not disabled -* Php-openssl must be available. +## Getting Started: +We've got documentation on our website on [installing PHPCI](https://www.phptesting.org/install-phpci) and [adding support for PHPCI to your projects](https://www.phptesting.org/wiki/Adding-PHPCI-Support-to-Your-Projects). +## Contributing +Contributions from others would be very much appreciated! Please read our [guide to contributing](https://github.com/Block8/PHPCI/blob/master/.github/CONTRIBUTING.md) for more information on how to get involved. -####Installing from Github: -* Step 1: `git clone https://github.com/Block8/PHPCI.git` -* Step 2: `cd PHPCI` -* Step 3: `composer install` -* Step 4: `chmod +x ./console && ./console phpci:install` - * When prompted, enter your database host, username, password and the database name that PHPCI should use. - * The script will attempt to create the database if it does not exist already. - * If you intend to use the MySQL plugin to create / destroy databases, the user you entered above will need CREATE / DELETE permissions on the server. -* Add a virtual host to your web server, pointing to the directory "public" where you cloned PHPCI into. -* You'll need to set up rewrite rules to point all non-existant requests to PHPCI. - -**Apache Example (require mod_rewrite installed)**: - -```sh - - RewriteEngine On - RewriteBase /path/to/phpci/public - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule . /index.php [L] - -``` - -**Nginx Example**: - - - location / { - try_files $uri $uri/ index.php - } - -Finally, you'll want to set up PHPCI to run as a regular cronjob, so run `crontab -e` and enter the following: - - * * * * * /usr/bin/php /path/to/phpci/console phpci:run-builds - -Obviously, make sure you change the `/path/to/phpci` to the directory in which you installed PHPCI, and update the PHP path if necessary. - -##Adding support for PHPCI to your projects: -Similar to Travis CI, to support PHPCI in your project, you simply need to add a `phpci.yml` file to the root of your repository. The file should look something like this: - - build_settings: - ignore: - - "vendor" - - "tests" - mysql: - host: "localhost" - user: "root" - pass: "" - campfire: - url: "https://youraccount.campfirenow.com" - authToken: "605b32dd" - roomId: "570102" - setup: - mysql: - - "DROP DATABASE IF EXISTS test;" - - "CREATE DATABASE test;" - - "GRANT ALL PRIVILEGES ON test.* TO test@'localhost' IDENTIFIED BY 'test';" - composer: - action: "install" - - test: - php_unit: - config: - - "PHPUnit-all.xml" - - "PHPUnit-ubuntu-fix.xml" - directory: - - "tests/" - run_from: "phpunit/" - php_mess_detector: - allow_failures: true - php_code_sniffer: - standard: "PSR2" - php_cpd: - allow_failures: true - grunt: - task: "build" - - complete: - mysql: - - "DROP DATABASE IF EXISTS test;" - failure: - campfire: - message: "Phpci : build %buildurl% failed." - -As mentioned earlier, PHPCI is powered by plugins, there are several phases in which plugins can be run: - -* `setup` - This phase is designed to initialise the build procedure. -* `test` - The tests that should be run during the build. Plugins run during this phase will contribute to the success or failure of the build. -* `complete` - Always called when the `test` phase completes, regardless of success or failure. -* `success` - Called upon success of the `test` phase. -* `failure` - Called upon the failure of the `test` phase. - -The `ignore` section is merely an array of paths that should be ignored in all tests (where possible.) - -##Contributing -Contributions from others would be very much appreciated! If you just want to make a simple change, simply fork the repository, and send us a pull request when you're ready. - -If you'd like to get more involved in developing PHPCI or to become a maintainer / committer on the main PHPCI repository, join the [mailing list](https://groups.google.com/forum/#!forum/php-ci). - -##Questions? -Your best place to go is the [mailing list](https://groups.google.com/forum/#!forum/php-ci), if you're already a member of the mailing list, you can simply email php-ci@googlegroups.com. +## Questions? +Your best place to go is the [mailing list](https://groups.google.com/forum/#!forum/php-ci). If you're already a member of the mailing list, you can simply email php-ci@googlegroups.com. diff --git a/Tests/PHPCI/Command/CreateAdminCommandTest.php b/Tests/PHPCI/Command/CreateAdminCommandTest.php new file mode 100644 index 00000000..10b7b7f3 --- /dev/null +++ b/Tests/PHPCI/Command/CreateAdminCommandTest.php @@ -0,0 +1,79 @@ +command = $this->getMockBuilder('PHPCI\\Command\\CreateAdminCommand') + ->setConstructorArgs(array($this->getMock('PHPCI\\Store\\UserStore'))) + ->setMethods(array('reloadConfig')) + ->getMock() + ; + + $this->dialog = $this->getMockBuilder('Symfony\\Component\\Console\\Helper\\DialogHelper') + ->setMethods(array( + 'ask', + 'askAndValidate', + 'askHiddenResponse', + )) + ->getMock() + ; + + $this->application = new Application(); + } + + /** + * @return CommandTester + */ + protected function getCommandTester() + { + $this->application->getHelperSet()->set($this->dialog, 'dialog'); + $this->application->add($this->command); + $command = $this->application->find('phpci:create-admin'); + $commandTester = new CommandTester($command); + + return $commandTester; + } + + public function testExecute() + { + $this->dialog->expects($this->at(0))->method('askAndValidate')->will($this->returnValue('test@example.com')); + $this->dialog->expects($this->at(1))->method('ask')->will($this->returnValue('A name')); + $this->dialog->expects($this->at(2))->method('askHiddenResponse')->will($this->returnValue('foobar123')); + + $commandTester = $this->getCommandTester(); + $commandTester->execute(array()); + + $this->assertEquals('User account created!' . PHP_EOL, $commandTester->getDisplay()); + } +} diff --git a/Tests/PHPCI/Command/CreateBuildCommandTest.php b/Tests/PHPCI/Command/CreateBuildCommandTest.php new file mode 100644 index 00000000..e82ae517 --- /dev/null +++ b/Tests/PHPCI/Command/CreateBuildCommandTest.php @@ -0,0 +1,86 @@ +getMock('PHPCI\\Model\\Project'); + + $projectStoreMock = $this->getMockBuilder('PHPCI\\Store\\ProjectStore') + ->getMock(); + $projectStoreMock->method('getById') + ->will($this->returnValueMap(array( + array(1, 'read', $projectMock), + array(2, 'read', null), + ))); + + $buildServiceMock = $this->getMockBuilder('PHPCI\\Service\\BuildService') + ->disableOriginalConstructor() + ->getMock(); + $buildServiceMock->method('createBuild') + ->withConsecutive( + array($projectMock, null, null, null, null, null), + array($projectMock, '92c8c6e', null, null, null, null), + array($projectMock, null, 'master', null, null, null) + ); + + $this->command = $this->getMockBuilder('PHPCI\\Command\\CreateBuildCommand') + ->setConstructorArgs(array($projectStoreMock, $buildServiceMock)) + ->setMethods(array('reloadConfig')) + ->getMock(); + + $this->application = new Application(); + } + + protected function getCommandTester() + { + $this->application->add($this->command); + + $command = $this->application->find('phpci:create-build'); + $commandTester = new CommandTester($command); + + return $commandTester; + } + + public function testExecute() + { + $commandTester = $this->getCommandTester(); + + $commandTester->execute(array('projectId' => 1)); + $commandTester->execute(array('projectId' => 1, '--commit' => '92c8c6e')); + $commandTester->execute(array('projectId' => 1, '--branch' => 'master')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testExecuteWithUnknownProjectId() + { + $commandTester = $this->getCommandTester(); + $commandTester->execute(array('projectId' => 2)); + } +} diff --git a/Tests/PHPCI/Command/InstallCommandTest.php b/Tests/PHPCI/Command/InstallCommandTest.php new file mode 100644 index 00000000..5d70f31c --- /dev/null +++ b/Tests/PHPCI/Command/InstallCommandTest.php @@ -0,0 +1,280 @@ +application = new Application(); + $this->application->setHelperSet(new HelperSet()); + } + + /** + * @return \PHPUnit_Framework_MockObject_MockBuilder + */ + protected function getDialogHelperMock() + { + // We check that there's no interaction with user. + $dialog = $this->getMockBuilder('Symfony\\Component\\Console\\Helper\\DialogHelper') + ->setMethods(array( + 'ask', + 'askConfirmation', + 'askAndValidate', + 'askHiddenResponse', + 'askHiddenResponseAndValidate', + )) + ->getMock(); + + return $dialog; + } + + /** + * @return \PHPUnit_Framework_MockObject_MockBuilder + */ + protected function getInstallCommandMock() + { + // Current command, we need to mock all method that interact with + // Database & File system. + $command = $this->getMockBuilder('PHPCI\\Command\\InstallCommand') + ->setMethods(array( + 'reloadConfig', + 'verifyNotInstalled', + 'verifyDatabaseDetails', + 'setupDatabase', + 'createAdminUser', + 'writeConfigFile', + 'checkRequirements', + )) + ->getMock(); + + $self = $this; + + $command->expects($this->once())->method('verifyNotInstalled')->willReturn(true); + $command->expects($this->once())->method('verifyDatabaseDetails')->willReturn(true); + $command->expects($this->once())->method('setupDatabase')->willReturn(true); + $command->expects($this->once())->method('createAdminUser')->will( + $this->returnCallback(function ($adm) use ($self) { + $self->admin = $adm; + }) + ); + $command->expects($this->once())->method('writeConfigFile')->will( + $this->returnCallback(function ($cfg) use ($self) { + $self->config = $cfg; + }) + ); + $command->expects($this->once())->method('checkRequirements'); + + return $command; + } + + protected function getCommandTester($dialog) + { + $this->application->getHelperSet()->set($dialog, 'dialog'); + $this->application->add($this->getInstallCommandMock()); + $command = $this->application->find('phpci:install'); + $commandTester = new CommandTester($command); + + return $commandTester; + } + + protected function getConfig($exclude = null) + { + $config = array( + '--db-host' => 'localhost', + '--db-name' => 'phpci1', + '--db-user' => 'phpci2', + '--db-pass' => 'phpci3', + '--admin-mail' => 'phpci@phpci.test', + '--admin-name' => 'phpci4', + '--admin-pass' => 'phpci5', + '--url' => 'http://test.phpci.org', + '--queue-disabled' => null, + ); + + if (!is_null($exclude)) { + unset($config[$exclude]); + } + + return $config; + } + + protected function executeWithoutParam($param = null, $dialog) + { + // Clean result variables. + $this->admin = array(); + $this->config = array(); + + // Get tester and execute with extracted parameters. + $commandTester = $this->getCommandTester($dialog); + $parameters = $this->getConfig($param); + $commandTester->execute($parameters); + } + + public function testAutomaticInstallation() + { + $dialog = $this->getDialogHelperMock(); + $dialog->expects($this->never())->method('ask'); + $dialog->expects($this->never())->method('askConfirmation'); + $dialog->expects($this->never())->method('askAndValidate'); + $dialog->expects($this->never())->method('askHiddenResponse'); + $dialog->expects($this->never())->method('askHiddenResponseAndValidate'); + + $this->executeWithoutParam(null, $dialog); + } + + public function testDatabaseHostnameConfig() + { + $dialog = $this->getDialogHelperMock(); + + // We specified an input value for hostname. + $dialog->expects($this->once())->method('ask')->willReturn('testedvalue'); + $dialog->expects($this->never())->method('askConfirmation'); + $dialog->expects($this->never())->method('askAndValidate'); + $dialog->expects($this->never())->method('askHiddenResponse'); + $dialog->expects($this->never())->method('askHiddenResponseAndValidate'); + + $this->executeWithoutParam('--db-host', $dialog); + + // Check that specified arguments are correctly loaded. + $this->assertEquals('testedvalue', $this->config['b8']['database']['servers']['read']); + $this->assertEquals('testedvalue', $this->config['b8']['database']['servers']['write']); + } + + public function testDatabaseNameConfig() + { + $dialog = $this->getDialogHelperMock(); + + // We specified an input value for hostname. + $dialog->expects($this->once())->method('ask')->willReturn('testedvalue'); + $dialog->expects($this->never())->method('askConfirmation'); + $dialog->expects($this->never())->method('askAndValidate'); + $dialog->expects($this->never())->method('askHiddenResponse'); + $dialog->expects($this->never())->method('askHiddenResponseAndValidate'); + + $this->executeWithoutParam('--db-name', $dialog); + + // Check that specified arguments are correctly loaded. + $this->assertEquals('testedvalue', $this->config['b8']['database']['name']); + } + + public function testDatabaseUserConfig() + { + $dialog = $this->getDialogHelperMock(); + + // We specified an input value for hostname. + $dialog->expects($this->once())->method('ask')->willReturn('testedvalue'); + $dialog->expects($this->never())->method('askConfirmation'); + $dialog->expects($this->never())->method('askAndValidate'); + $dialog->expects($this->never())->method('askHiddenResponse'); + $dialog->expects($this->never())->method('askHiddenResponseAndValidate'); + + $this->executeWithoutParam('--db-user', $dialog); + + // Check that specified arguments are correctly loaded. + $this->assertEquals('testedvalue', $this->config['b8']['database']['username']); + } + + public function testDatabasePasswordConfig() + { + $dialog = $this->getDialogHelperMock(); + + // We specified an input value for hostname. + $dialog->expects($this->never())->method('ask'); + $dialog->expects($this->never())->method('askConfirmation'); + $dialog->expects($this->never())->method('askAndValidate'); + $dialog->expects($this->once())->method('askHiddenResponse')->willReturn('testedvalue'); + $dialog->expects($this->never())->method('askHiddenResponseAndValidate'); + + $this->executeWithoutParam('--db-pass', $dialog); + + // Check that specified arguments are correctly loaded. + $this->assertEquals('testedvalue', $this->config['b8']['database']['password']); + } + + public function testPhpciUrlConfig() + { + $dialog = $this->getDialogHelperMock(); + + // We specified an input value for hostname. + $dialog->expects($this->never())->method('ask'); + $dialog->expects($this->never())->method('askConfirmation'); + $dialog->expects($this->once())->method('askAndValidate')->willReturn('http://testedvalue.com'); + $dialog->expects($this->never())->method('askHiddenResponse'); + $dialog->expects($this->never())->method('askHiddenResponseAndValidate'); + + $this->executeWithoutParam('--url', $dialog); + + // Check that specified arguments are correctly loaded. + $this->assertEquals('http://testedvalue.com', $this->config['phpci']['url']); + } + + public function testAdminEmailConfig() + { + $dialog = $this->getDialogHelperMock(); + + // We specified an input value for hostname. + $dialog->expects($this->never())->method('ask'); + $dialog->expects($this->never())->method('askConfirmation'); + $dialog->expects($this->once())->method('askAndValidate')->willReturn('test@phpci.com'); + $dialog->expects($this->never())->method('askHiddenResponse'); + $dialog->expects($this->never())->method('askHiddenResponseAndValidate'); + + $this->executeWithoutParam('--admin-mail', $dialog); + + // Check that specified arguments are correctly loaded. + $this->assertEquals('test@phpci.com', $this->admin['mail']); + } + + public function testAdminNameConfig() + { + $dialog = $this->getDialogHelperMock(); + + // Define expectation for dialog. + $dialog->expects($this->once())->method('ask')->willReturn('testedvalue'); + $dialog->expects($this->never())->method('askConfirmation'); + $dialog->expects($this->never())->method('askAndValidate'); + $dialog->expects($this->never())->method('askHiddenResponse'); + $dialog->expects($this->never())->method('askHiddenResponseAndValidate'); + + $this->executeWithoutParam('--admin-name', $dialog); + + // Check that specified arguments are correctly loaded. + $this->assertEquals('testedvalue', $this->admin['name']); + } + + public function testAdminPasswordConfig() + { + $dialog = $this->getDialogHelperMock(); + + // We specified an input value for hostname. + $dialog->expects($this->never())->method('ask'); + $dialog->expects($this->never())->method('askConfirmation'); + $dialog->expects($this->never())->method('askAndValidate'); + $dialog->expects($this->once())->method('askHiddenResponse')->willReturn('testedvalue'); + $dialog->expects($this->never())->method('askHiddenResponseAndValidate'); + + $this->executeWithoutParam('--admin-pass', $dialog); + + // Check that specified arguments are correctly loaded. + $this->assertEquals('testedvalue', $this->admin['pass']); + } +} diff --git a/Tests/PHPCI/Controller/WebhookControllerTest.php b/Tests/PHPCI/Controller/WebhookControllerTest.php new file mode 100644 index 00000000..b3b31e42 --- /dev/null +++ b/Tests/PHPCI/Controller/WebhookControllerTest.php @@ -0,0 +1,41 @@ +prophesize('b8\Config')->reveal(), + $this->prophesize('b8\Http\Request')->reveal(), + $this->prophesize('b8\Http\Response')->reveal() + ); + + $error = $webController->handleAction('test', []); + + $this->assertInstanceOf('b8\Http\Response\JsonResponse', $error); + + $responseData = $error->getData(); + $this->assertEquals(500, $responseData['code']); + + $this->assertEquals('failed', $responseData['body']['status']); + + $this->assertEquals('application/json', $responseData['headers']['Content-Type']); + + // @todo: we can't text the result is JSON file with + // $this->assertJson((string) $error); + // since the flush method automatically add the header and break the + // testing framework. + } +} diff --git a/Tests/PHPCI/Helper/AnsiConverterTest.php b/Tests/PHPCI/Helper/AnsiConverterTest.php new file mode 100644 index 00000000..09930d12 --- /dev/null +++ b/Tests/PHPCI/Helper/AnsiConverterTest.php @@ -0,0 +1,35 @@ +markTestSkipped('External library do not support PHP 5.3'); + } + } + + public function testConvert_convertToHtml() + { + $input = "\e[31mThis is red !\e[0m"; + + $expectedOutput = 'This is red !'; + + $actualOutput = AnsiConverter::convert($input); + + $this->assertEquals($expectedOutput, $actualOutput); + } +} diff --git a/Tests/PHPCI/Helper/BuildInterpolatorTest.php b/Tests/PHPCI/Helper/BuildInterpolatorTest.php new file mode 100644 index 00000000..8961b2d8 --- /dev/null +++ b/Tests/PHPCI/Helper/BuildInterpolatorTest.php @@ -0,0 +1,56 @@ +testedInterpolator = new BuildInterpolator(); + } + + public function testInterpolate_LeavesStringsUnchangedByDefault() + { + $string = "Hello World"; + $expectedOutput = "Hello World"; + + $actualOutput = $this->testedInterpolator->interpolate($string); + + $this->assertEquals($expectedOutput, $actualOutput); + } + + public function testInterpolate_LeavesStringsUnchangedWhenBuildIsSet() + { + $build = $this->prophesize('PHPCI\\Model\\Build')->reveal(); + + $string = "Hello World"; + $expectedOutput = "Hello World"; + + $this->testedInterpolator->setupInterpolationVars( + $build, + "/buildpath/", + "phpci.com" + ); + + $actualOutput = $this->testedInterpolator->interpolate($string); + + $this->assertEquals($expectedOutput, $actualOutput); + } +} + diff --git a/Tests/PHPCI/Helper/CommandExecutorTest.php b/Tests/PHPCI/Helper/CommandExecutorTest.php new file mode 100644 index 00000000..5d5dc08b --- /dev/null +++ b/Tests/PHPCI/Helper/CommandExecutorTest.php @@ -0,0 +1,83 @@ +markTestSkipped("Cannot test UnixCommandExecutor on ".PHP_OS); + return; + } + parent::setUp(); + $mockBuildLogger = $this->prophesize('PHPCI\Logging\BuildLogger'); + $class = IS_WIN ? 'PHPCI\Helper\WindowsCommandExecutor' : 'PHPCI\Helper\UnixCommandExecutor'; + $this->testedExecutor = new $class($mockBuildLogger->reveal(), __DIR__ . "/"); + } + + public function testGetLastOutput_ReturnsOutputOfCommand() + { + $this->testedExecutor->executeCommand(array('echo "%s"', 'Hello World')); + $output = $this->testedExecutor->getLastOutput(); + $this->assertEquals("Hello World", $output); + } + + public function testGetLastOutput_ForgetsPreviousCommandOutput() + { + $this->testedExecutor->executeCommand(array('echo "%s"', 'Hello World')); + $this->testedExecutor->executeCommand(array('echo "%s"', 'Hello Tester')); + $output = $this->testedExecutor->getLastOutput(); + $this->assertEquals("Hello Tester", $output); + } + + public function testExecuteCommand_ReturnsTrueForValidCommands() + { + $returnValue = $this->testedExecutor->executeCommand(array('echo "%s"', 'Hello World')); + $this->assertTrue($returnValue); + } + + public function testExecuteCommand_ReturnsFalseForInvalidCommands() + { + $returnValue = $this->testedExecutor->executeCommand(array('eerfdcvcho "%s" > /dev/null 2>&1', 'Hello World')); + $this->assertFalse($returnValue); + } + + public function testFindBinary_ReturnsPathInSpecifiedRoot() + { + $thisFileName = "CommandExecutorTest.php"; + $returnValue = $this->testedExecutor->findBinary($thisFileName); + $this->assertEquals(__DIR__ . "/" . $thisFileName, $returnValue); + } + + /** + * @expectedException \Exception + * @expectedMessageRegex WorldWidePeace + */ + public function testFindBinary_ThrowsWhenNotFound() + { + $thisFileName = "WorldWidePeace"; + $this->testedExecutor->findBinary($thisFileName); + } + + public function testFindBinary_ReturnsNullWihQuietArgument() + { + $thisFileName = "WorldWidePeace"; + $this->assertNull($this->testedExecutor->findBinary($thisFileName, true)); + } +} diff --git a/Tests/PHPCI/Helper/LangTest.php b/Tests/PHPCI/Helper/LangTest.php new file mode 100644 index 00000000..b71ee41d --- /dev/null +++ b/Tests/PHPCI/Helper/LangTest.php @@ -0,0 +1,35 @@ +prophesize('DateTime'); + $dateTime->format(DateTime::ISO8601)->willReturn("ISODATE"); + $dateTime->format(DateTime::RFC2822)->willReturn("RFCDATE"); + + $this->assertEquals('', Lang::formatDateTime($dateTime->reveal(), 'FORMAT')); + } + + public function testLang_UseDefaultFormat() + { + $dateTime = $this->prophesize('DateTime'); + $dateTime->format(DateTime::ISO8601)->willReturn("ISODATE"); + $dateTime->format(DateTime::RFC2822)->willReturn("RFCDATE"); + + $this->assertEquals('', Lang::formatDateTime($dateTime->reveal())); + } +} diff --git a/Tests/PHPCI/Helper/MailerFactoryTest.php b/Tests/PHPCI/Helper/MailerFactoryTest.php new file mode 100644 index 00000000..d59685d1 --- /dev/null +++ b/Tests/PHPCI/Helper/MailerFactoryTest.php @@ -0,0 +1,72 @@ + + */ +class MailerFactoryTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestGetMailConfig() + { + $config = array( + 'smtp_address' => 'mail.example.com', + 'smtp_port' => 225, + 'smtp_encryption' => true, + 'smtp_username' => 'example.user', + 'smtp_password' => 'examplepassword', + 'default_mailto_address' => 'phpci@example.com', + ); + + $factory = new MailerFactory(array('email_settings' => $config)); + + $this->assertEquals($config['smtp_address'], $factory->getMailConfig('smtp_address')); + $this->assertEquals($config['smtp_port'], $factory->getMailConfig('smtp_port')); + $this->assertEquals($config['smtp_encryption'], $factory->getMailConfig('smtp_encryption')); + $this->assertEquals($config['smtp_username'], $factory->getMailConfig('smtp_username')); + $this->assertEquals($config['smtp_password'], $factory->getMailConfig('smtp_password')); + $this->assertEquals($config['default_mailto_address'], $factory->getMailConfig('default_mailto_address')); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestMailer() + { + $config = array( + 'smtp_address' => 'mail.example.com', + 'smtp_port' => 225, + 'smtp_encryption' => true, + 'smtp_username' => 'example.user', + 'smtp_password' => 'examplepassword', + 'default_mailto_address' => 'phpci@example.com', + ); + + $factory = new MailerFactory(array('email_settings' => $config)); + $mailer = $factory->getSwiftMailerFromConfig(); + + $this->assertEquals($config['smtp_address'], $mailer->getTransport()->getHost()); + $this->assertEquals($config['smtp_port'], $mailer->getTransport()->getPort()); + $this->assertEquals('tls', $mailer->getTransport()->getEncryption()); + $this->assertEquals($config['smtp_username'], $mailer->getTransport()->getUsername()); + $this->assertEquals($config['smtp_password'], $mailer->getTransport()->getPassword()); + } +} diff --git a/Tests/PHPCI/Logging/BuildLoggerTest.php b/Tests/PHPCI/Logging/BuildLoggerTest.php new file mode 100644 index 00000000..77a1fc22 --- /dev/null +++ b/Tests/PHPCI/Logging/BuildLoggerTest.php @@ -0,0 +1,114 @@ +mockLogger = $this->prophesize('\Psr\Log\LoggerInterface'); + $this->mockBuild = $this->prophesize('\PHPCI\Model\Build'); + + $this->testedBuildLogger = new BuildLogger( + $this->mockLogger->reveal(), + $this->mockBuild->reveal() + ); + } + + public function testLog_CallsWrappedLogger() + { + $level = LogLevel::NOTICE; + $message = "Testing"; + $contextIn = array(); + + $this->mockLogger->log($level, $message, Argument::type('array')) + ->shouldBeCalledTimes(1); + + $this->testedBuildLogger->log($message, $level, $contextIn); + } + + public function testLog_CallsWrappedLoggerForEachMessage() + { + $level = LogLevel::NOTICE; + $message = array("One", "Two", "Three"); + $contextIn = array(); + + $this->mockLogger->log($level, "One", Argument::type('array')) + ->shouldBeCalledTimes(1); + + $this->mockLogger->log($level, "Two", Argument::type('array')) + ->shouldBeCalledTimes(1); + + $this->mockLogger->log($level, "Three", Argument::type('array')) + ->shouldBeCalledTimes(1); + + $this->testedBuildLogger->log($message, $level, $contextIn); + } + + public function testLog_AddsBuildToContext() + { + $level = LogLevel::NOTICE; + $message = "Testing"; + $contextIn = array(); + + $expectedContext = array( + 'build' => $this->mockBuild->reveal() + ); + + $this->mockLogger->log($level, $message, $expectedContext) + ->shouldBeCalledTimes(1); + + $this->testedBuildLogger->log($message, $level, $contextIn); + } + + public function testLogFailure_LogsAsErrorLevel() + { + $message = "Testing"; + $expectedLevel = LogLevel::ERROR; + + $this->mockLogger->log($expectedLevel, + Argument::type('string'), + Argument::type('array')) + ->shouldBeCalledTimes(1); + + $this->testedBuildLogger->logFailure($message); + } + + public function testLogFailure_AddsExceptionContext() + { + $message = "Testing"; + + $exception = new \Exception("Expected Exception"); + + + $this->mockLogger->log(Argument::type('string'), + Argument::type('string'), + Argument::withEntry('exception', $exception)) + ->shouldBeCalledTimes(1); + + $this->testedBuildLogger->logFailure($message, $exception); + } +} + diff --git a/Tests/PHPCI/Logging/LoggerConfigTest.php b/Tests/PHPCI/Logging/LoggerConfigTest.php new file mode 100644 index 00000000..7fb95ef8 --- /dev/null +++ b/Tests/PHPCI/Logging/LoggerConfigTest.php @@ -0,0 +1,94 @@ +getFor("something"); + $this->assertInstanceOf('\Psr\Log\LoggerInterface', $logger); + } + + public function testGetFor_ReturnsMonologInstance() + { + $config = new LoggerConfig(array()); + $logger = $config->getFor("something"); + $this->assertInstanceOf('\Monolog\Logger', $logger); + } + + public function testGetFor_AttachesAlwaysPresentHandlers() + { + $expectedHandler = new \Monolog\Handler\NullHandler(); + $config = new LoggerConfig(array( + LoggerConfig::KEY_ALWAYS_LOADED => function() use ($expectedHandler) { + return array($expectedHandler); + } + )); + + /** @var \Monolog\Logger $logger */ + $logger = $config->getFor("something"); + $actualHandler = $logger->popHandler(); + + $this->assertEquals($expectedHandler, $actualHandler); + } + + public function testGetFor_AttachesSpecificHandlers() + { + $expectedHandler = new \Monolog\Handler\NullHandler(); + $config = new LoggerConfig(array( + "Specific" => function() use ($expectedHandler) { + return array($expectedHandler); + } + )); + + /** @var \Monolog\Logger $logger */ + $logger = $config->getFor("Specific"); + $actualHandler = $logger->popHandler(); + + $this->assertSame($expectedHandler, $actualHandler); + } + + public function testGetFor_IgnoresAlternativeHandlers() + { + $expectedHandler = new \Monolog\Handler\NullHandler(); + $alternativeHandler = new \Monolog\Handler\NullHandler(); + + $config = new LoggerConfig(array( + "Specific" => function() use ($expectedHandler) { + return array($expectedHandler); + }, + "Other" => function() use ($alternativeHandler) { + return array($alternativeHandler); + } + )); + + /** @var \Monolog\Logger $logger */ + $logger = $config->getFor("Specific"); + $actualHandler = $logger->popHandler(); + + $this->assertSame($expectedHandler, $actualHandler); + $this->assertNotSame($alternativeHandler, $actualHandler); + } + + public function testGetFor_SameInstance() + { + $config = new LoggerConfig(array()); + + $logger1 = $config->getFor("something"); + $logger2 = $config->getFor("something"); + + $this->assertSame($logger1, $logger2); + } +} diff --git a/Tests/PHPCI/Model/BuildTest.php b/Tests/PHPCI/Model/BuildTest.php new file mode 100644 index 00000000..24508fe7 --- /dev/null +++ b/Tests/PHPCI/Model/BuildTest.php @@ -0,0 +1,86 @@ + + */ +class BuildTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestIsAValidModel() + { + $build = new Build(); + $this->assertTrue($build instanceof \b8\Model); + $this->assertTrue($build instanceof Model); + $this->assertTrue($build instanceof Model\Base\BuildBase); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestBaseBuildDefaults() + { + $build = new Build(); + $this->assertEquals('#', $build->getCommitLink()); + $this->assertEquals('#', $build->getBranchLink()); + $this->assertEquals(null, $build->getFileLinkTemplate()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestIsSuccessful() + { + $build = new Build(); + $build->setStatus(Build::STATUS_NEW); + $this->assertFalse($build->isSuccessful()); + + $build->setStatus(Build::STATUS_RUNNING); + $this->assertFalse($build->isSuccessful()); + + $build->setStatus(Build::STATUS_FAILED); + $this->assertFalse($build->isSuccessful()); + + $build->setStatus(Build::STATUS_SUCCESS); + $this->assertTrue($build->isSuccessful()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestBuildExtra() + { + $info = array( + 'item1' => 'Item One', + 'item2' => 2, + ); + + $build = new Build(); + $build->setExtra(json_encode($info)); + + $this->assertEquals('Item One', $build->getExtra('item1')); + $this->assertEquals(2, $build->getExtra('item2')); + $this->assertNull($build->getExtra('item3')); + $this->assertEquals($info, $build->getExtra()); + } +} diff --git a/Tests/PHPCI/Model/ProjectTest.php b/Tests/PHPCI/Model/ProjectTest.php new file mode 100644 index 00000000..b3bfb799 --- /dev/null +++ b/Tests/PHPCI/Model/ProjectTest.php @@ -0,0 +1,111 @@ + + */ +class ProjectTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestIsAValidModel() + { + $project = new Project(); + $this->assertTrue($project instanceof \b8\Model); + $this->assertTrue($project instanceof Model); + $this->assertTrue($project instanceof Model\Base\ProjectBase); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestGitDefaultBranch() + { + $project = new Project(); + $project->setType('git'); + + $this->assertEquals('master', $project->getBranch()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestGithubDefaultBranch() + { + $project = new Project(); + $project->setType('github'); + + $this->assertEquals('master', $project->getBranch()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestGitlabDefaultBranch() + { + $project = new Project(); + $project->setType('gitlab'); + + $this->assertEquals('master', $project->getBranch()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestBitbucketDefaultBranch() + { + $project = new Project(); + $project->setType('bitbucket'); + + $this->assertEquals('master', $project->getBranch()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestMercurialDefaultBranch() + { + $project = new Project(); + $project->setType('hg'); + + $this->assertEquals('default', $project->getBranch()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestProjectAccessInformation() + { + $info = array( + 'item1' => 'Item One', + 'item2' => 2, + ); + + $project = new Project(); + $project->setAccessInformation($info); + + $this->assertEquals('Item One', $project->getAccessInformation('item1')); + $this->assertEquals(2, $project->getAccessInformation('item2')); + $this->assertNull($project->getAccessInformation('item3')); + $this->assertEquals($info, $project->getAccessInformation()); + } +} diff --git a/Tests/PHPCI/Plugin/Email.php b/Tests/PHPCI/Plugin/Email.php deleted file mode 100644 index 8000da7e..00000000 --- a/Tests/PHPCI/Plugin/Email.php +++ /dev/null @@ -1,256 +0,0 @@ -mockBuild = $this->getMock( - '\PHPCI\Model\Build', - array('getLog'), - array(), - "mockBuild", - false - ); - - $this->mockBuild->expects($this->any()) - ->method('getLog') - ->will($this->returnValue("Build Log")); - - $this->mockCiBuilder = $this->getMock( - '\PHPCI\Builder', - array('getSystemConfig', - 'getBuildProjectTitle', - 'getBuild', - 'log'), - array(), - "mockBuilder", - false - ); - - $this->mockCiBuilder->buildPath = "/"; - - $this->mockCiBuilder->expects($this->any()) - ->method('getSystemConfig') - ->with('phpci') - ->will($this->returnValue(array( - 'email_settings' => array( - 'from_address' => "test-from-address@example.com" - ) - ))); - $this->mockCiBuilder->expects($this->any()) - ->method('getBuildProjectTitle') - ->will($this->returnValue('Test-Project')); - $this->mockCiBuilder->expects($this->any()) - ->method('getBuild') - ->will($this->returnValue($this->mockBuild)); - - $this->mockMailer = $this->getMock( - '\Swift_Mailer', - array('send'), - array(), - "mockMailer", - false - ); - - $this->loadEmailPluginWithOptions(); - } - - protected function loadEmailPluginWithOptions($arrOptions = array()) - { - $this->testedEmailPlugin = new EmailPlugin( - $this->mockCiBuilder, - $arrOptions, - $this->mockMailer - ); - } - - /** - * @covers PHPUnit::execute - */ - public function testExecute_ReturnsFalseWithoutArgs() - { - $returnValue = $this->testedEmailPlugin->execute(); - // As no addresses will have been mailed as non are configured. - $expectedReturn = false; - - $this->assertEquals($expectedReturn, $returnValue); - } - - /** - * @covers PHPUnit::execute - */ - public function testExecute_BuildsBasicEmails() - { - $this->loadEmailPluginWithOptions(array( - 'addresses' => array('test-receiver@example.com') - )); - - /** @var \Swift_Message $actualMail */ - $actualMail = null; - $this->catchMailPassedToSend($actualMail); - - $returnValue = $this->testedEmailPlugin->execute(); - $expectedReturn = true; - - $this->assertSystemMail( - 'test-receiver@example.com', - 'test-from-address@example.com', - "Log Output:
    Build Log
    ", - "PHPCI - Test-Project - Passing Build", - $actualMail - ); - - $this->assertEquals($expectedReturn, $returnValue); - - - } - - /** - * @covers PHPUnit::sendEmail - */ - public function testSendEmail_CallsMailerSend() - { - $this->mockMailer->expects($this->once()) - ->method('send'); - $this->testedEmailPlugin->sendEmail("test@email.com", "hello", "body"); - } - - /** - * @covers PHPUnit::sendEmail - */ - public function testSendEmail_BuildsAMessageObject() - { - $subject = "Test mail"; - $body = "Message Body"; - $toAddress = "test@example.com"; - - $this->mockMailer->expects($this->once()) - ->method('send') - ->with($this->isInstanceOf('\Swift_Message'), $this->anything()); - $this->testedEmailPlugin->sendEmail($toAddress, $subject, $body); - } - - /** - * @covers PHPUnit::sendEmail - */ - public function testSendEmail_BuildsExpectedMessage() - { - $subject = "Test mail"; - $body = "Message Body"; - $toAddress = "test@example.com"; - $expectedMessage = \Swift_Message::newInstance($subject) - ->setFrom('test-from-address@example.com') - ->setTo($toAddress) - ->setBody($body); - - /** @var \Swift_Message $actualMail */ - $actualMail = null; - $this->catchMailPassedToSend($actualMail); - - $this->testedEmailPlugin->sendEmail($toAddress, $subject, $body); - - $this->assertSystemMail( - $toAddress, - 'test-from-address@example.com', - $body, - $subject, - $actualMail - ); - } - - /** - * @param \Swift_Message $actualMail passed by ref and populated with - * the message object the mock mailer - * receives. - */ - protected function catchMailPassedToSend(&$actualMail) - { - $this->mockMailer->expects($this->once()) - ->method('send') - ->will( - $this->returnCallback( - function ($passedMail) use (&$actualMail) { - $actualMail = $passedMail; - return array(); - } - ) - ); - } - - /** - * Asserts that the actual mail object is populated as expected. - * - * @param string $expectedToAddress - * @param $expectedFromAddress - * @param string $expectedBody - * @param string $expectedSubject - * @param \Swift_Message $actualMail - */ - protected function assertSystemMail($expectedToAddress, - $expectedFromAddress, - $expectedBody, - $expectedSubject, - $actualMail) - { - if (! ($actualMail instanceof \Swift_Message)) { - $type = is_object($actualMail) ? get_class($actualMail) : gettype($actualMail); - throw new \Exception("Expected Swift_Message got " . $type); - } - $this->assertEquals( - array($expectedFromAddress => null), - $actualMail->getFrom() - ); - - $this->assertEquals( - array($expectedToAddress => null), - $actualMail->getTo() - ); - - $this->assertEquals( - $expectedBody, - $actualMail->getBody() - ); - - $this->assertEquals( - $expectedSubject, - $actualMail->getSubject() - ); - } -} \ No newline at end of file diff --git a/Tests/PHPCI/Plugin/EmailTest.php b/Tests/PHPCI/Plugin/EmailTest.php new file mode 100644 index 00000000..0787d41d --- /dev/null +++ b/Tests/PHPCI/Plugin/EmailTest.php @@ -0,0 +1,408 @@ +message = array(); + $this->mailDelivered = true; + $self = $this; + + $this->mockProject = $this->getMock( + '\PHPCI\Model\Project', + array('getTitle'), + array(), + "mockProject", + false + ); + + $this->mockProject->expects($this->any()) + ->method('getTitle') + ->will($this->returnValue("Test-Project")); + + $this->mockBuild = $this->getMock( + '\PHPCI\Model\Build', + array('getLog', 'getStatus', 'getProject', 'getCommitterEmail'), + array(), + "mockBuild", + false + ); + + $this->mockBuild->expects($this->any()) + ->method('getLog') + ->will($this->returnValue("Build Log")); + + $this->mockBuild->expects($this->any()) + ->method('getStatus') + ->will($this->returnCallback(function () use ($self) { + return $self->buildStatus; + })); + + $this->mockBuild->expects($this->any()) + ->method('getProject') + ->will($this->returnValue($this->mockProject)); + + $this->mockBuild->expects($this->any()) + ->method('getCommitterEmail') + ->will($this->returnValue('committer-email@example.com')); + + $this->mockCiBuilder = $this->getMock( + '\PHPCI\Builder', + array( + 'getSystemConfig', + 'getBuild', + 'log' + ), + array(), + "mockBuilder_email", + false + ); + + $this->mockCiBuilder->buildPath = "/"; + + $this->mockCiBuilder->expects($this->any()) + ->method('getSystemConfig') + ->with('phpci') + ->will( + $this->returnValue( + array( + 'email_settings' => array( + 'from_address' => "test-from-address@example.com" + ) + ) + ) + ); + } + + protected function loadEmailPluginWithOptions($arrOptions = array(), $buildStatus = null, $mailDelivered = true) + { + $this->mailDelivered = $mailDelivered; + + if (is_null($buildStatus)) { + $this->buildStatus = Build::STATUS_SUCCESS; + } else { + $this->buildStatus = $buildStatus; + } + + // Reset current message. + $this->message = array(); + + $self = $this; + + $this->testedEmailPlugin = $this->getMock( + '\PHPCI\Plugin\Email', + array('sendEmail'), + array( + $this->mockCiBuilder, + $this->mockBuild, + $arrOptions + ) + ); + + $this->testedEmailPlugin->expects($this->any()) + ->method('sendEmail') + ->will($this->returnCallback(function ($to, $cc, $subject, $body) use ($self) { + $self->message['to'][] = $to; + $self->message['cc'] = $cc; + $self->message['subject'] = $subject; + $self->message['body'] = $body; + + return $self->mailDelivered; + })); + } + + /** + * @covers PHPUnit::execute + */ + public function testReturnsFalseWithoutArgs() + { + $this->loadEmailPluginWithOptions(); + + $returnValue = $this->testedEmailPlugin->execute(); + + // As no addresses will have been mailed as non are configured. + $expectedReturn = false; + + $this->assertEquals($expectedReturn, $returnValue); + } + + /** + * @covers PHPUnit::execute + */ + public function testBuildsBasicEmails() + { + $this->loadEmailPluginWithOptions( + array( + 'addresses' => array('test-receiver@example.com') + ), + Build::STATUS_SUCCESS + ); + + $this->testedEmailPlugin->execute(); + + $this->assertContains('test-receiver@example.com', $this->message['to']); + } + + /** + * @covers PHPUnit::execute + */ + public function testBuildsDefaultEmails() + { + $this->loadEmailPluginWithOptions( + array( + 'default_mailto_address' => 'default-mailto-address@example.com' + ), + Build::STATUS_SUCCESS + ); + + $this->testedEmailPlugin->execute(); + + $this->assertContains('default-mailto-address@example.com', $this->message['to']); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_UniqueRecipientsFromWithCommitter() + { + $this->loadEmailPluginWithOptions( + array( + 'addresses' => array('test-receiver@example.com', 'test-receiver2@example.com') + ) + ); + + $returnValue = $this->testedEmailPlugin->execute(); + $this->assertTrue($returnValue); + + $this->assertCount(2, $this->message['to']); + + $this->assertContains('test-receiver@example.com', $this->message['to']); + $this->assertContains('test-receiver2@example.com', $this->message['to']); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_UniqueRecipientsWithCommitter() + { + $this->loadEmailPluginWithOptions( + array( + 'committer' => true, + 'addresses' => array('test-receiver@example.com', 'committer@test.com') + ) + ); + + $returnValue = $this->testedEmailPlugin->execute(); + $this->assertTrue($returnValue); + + $this->assertContains('test-receiver@example.com', $this->message['to']); + $this->assertContains('committer@test.com', $this->message['to']); + } + + /** + * @covers PHPUnit::execute + */ + public function testCcDefaultEmails() + { + $this->loadEmailPluginWithOptions( + array( + 'default_mailto_address' => 'default-mailto-address@example.com', + 'cc' => array( + 'cc-email-1@example.com', + 'cc-email-2@example.com', + 'cc-email-3@example.com', + ), + ), + Build::STATUS_SUCCESS + ); + + $this->testedEmailPlugin->execute(); + + $this->assertEquals( + array( + 'cc-email-1@example.com', + 'cc-email-2@example.com', + 'cc-email-3@example.com', + ), + $this->message['cc'] + ); + } + + /** + * @covers PHPUnit::execute + */ + public function testBuildsCommitterEmails() + { + $this->loadEmailPluginWithOptions( + array( + 'committer' => true + ), + Build::STATUS_SUCCESS + ); + + $this->testedEmailPlugin->execute(); + + $this->assertContains('committer-email@example.com', $this->message['to']); + } + + /** + * @covers PHPUnit::execute + */ + public function testMailSuccessfulBuildHaveProjectName() + { + $this->loadEmailPluginWithOptions( + array( + 'addresses' => array('test-receiver@example.com') + ), + Build::STATUS_SUCCESS + ); + + $this->testedEmailPlugin->execute(); + + $this->assertContains('Test-Project', $this->message['subject']); + $this->assertContains('Test-Project', $this->message['body']); + } + + /** + * @covers PHPUnit::execute + */ + public function testMailFailingBuildHaveProjectName() + { + $this->loadEmailPluginWithOptions( + array( + 'addresses' => array('test-receiver@example.com') + ), + Build::STATUS_FAILED + ); + + $this->testedEmailPlugin->execute(); + + $this->assertContains('Test-Project', $this->message['subject']); + $this->assertContains('Test-Project', $this->message['body']); + } + + /** + * @covers PHPUnit::execute + */ + public function testMailSuccessfulBuildHaveStatus() + { + $this->loadEmailPluginWithOptions( + array( + 'addresses' => array('test-receiver@example.com') + ), + Build::STATUS_SUCCESS + ); + + $this->testedEmailPlugin->execute(); + + $this->assertContains('Passing', $this->message['subject']); + $this->assertContains('successful', $this->message['body']); + } + + /** + * @covers PHPUnit::execute + */ + public function testMailFailingBuildHaveStatus() + { + $this->loadEmailPluginWithOptions( + array( + 'addresses' => array('test-receiver@example.com') + ), + Build::STATUS_FAILED + ); + + $this->testedEmailPlugin->execute(); + + $this->assertContains('Failing', $this->message['subject']); + $this->assertContains('failed', $this->message['body']); + } + + /** + * @covers PHPUnit::execute + */ + public function testMailDeliverySuccess() + { + $this->loadEmailPluginWithOptions( + array( + 'addresses' => array('test-receiver@example.com') + ), + Build::STATUS_FAILED, + true + ); + + $returnValue = $this->testedEmailPlugin->execute(); + + $this->assertEquals(true, $returnValue); + } + + /** + * @covers PHPUnit::execute + */ + public function testMailDeliveryFail() + { + $this->loadEmailPluginWithOptions( + array( + 'addresses' => array('test-receiver@example.com') + ), + Build::STATUS_FAILED, + false + ); + + $returnValue = $this->testedEmailPlugin->execute(); + + $this->assertEquals(false, $returnValue); + } +} diff --git a/Tests/PHPCI/Plugin/PHPUnitTest.php b/Tests/PHPCI/Plugin/PHPUnitTest.php deleted file mode 100644 index 1ff1764c..00000000 --- a/Tests/PHPCI/Plugin/PHPUnitTest.php +++ /dev/null @@ -1,132 +0,0 @@ -mockCiBuilder = $this->getMock( - '\PHPCI\Builder', - array(), - array(), - "mockBuilder", - false - ); - $this->mockCiBuilder->buildPath = "/"; - - $this->loadPhpUnitWithOptions(); - } - - protected function loadPhpUnitWithOptions($arrOptions = array()) - { - $this->testedPhpUnit = new PHPUnit($this->mockCiBuilder, $arrOptions); - } - - /** - * @covers PHPUnit::execute - */ - public function testExecute_ReturnsTrueWithoutArgs() - { - $returnValue = $this->testedPhpUnit->execute(); - $expectedReturn = true; - - $this->assertEquals($expectedReturn, $returnValue); - } - - /** - * @covers PHPUnit::execute - * @covers PHPUnit::runDir - */ - public function testExecute_CallsExecuteCommandOnceWhenGivenStringDirectory() - { - chdir('/'); - - $this->loadPhpUnitWithOptions(array( - 'directory' => "Fake/Test/Path" - )); - - $this->mockCiBuilder->expects($this->once())->method("executeCommand"); - - $returnValue = $this->testedPhpUnit->execute(); - } - - /** - * @covers PHPUnit::execute - * @covers PHPUnit::runConfigFile - */ - public function testExecute_CallsExecuteCommandOnceWhenGivenStringConfig() - { - chdir('/'); - - $this->loadPhpUnitWithOptions(array( - 'config' => "Fake/Test/config.xml" - )); - - $this->mockCiBuilder->expects($this->once())->method("executeCommand"); - - $returnValue = $this->testedPhpUnit->execute(); - } - - /** - * @covers PHPUnit::execute - * @covers PHPUnit::runDir - */ - public function testExecute_CallsExecuteCommandManyTimesWhenGivenArrayDirectory() - { - chdir('/'); - - $this->loadPhpUnitWithOptions(array( - 'directory' => array(0, 1) - )); - - $this->mockCiBuilder->expects($this->at(0))->method("executeCommand"); - $this->mockCiBuilder->expects($this->at(1))->method("executeCommand"); - - $returnValue = $this->testedPhpUnit->execute(); - } - - /** - * @covers PHPUnit::execute - * @covers PHPUnit::runConfigFile - */ - public function testExecute_CallsExecuteCommandManyTimesWhenGivenArrayConfig() - { - chdir('/'); - - $this->loadPhpUnitWithOptions(array( - 'config' => array(0, 1) - )); - - $this->mockCiBuilder->expects($this->at(0))->method("executeCommand"); - $this->mockCiBuilder->expects($this->at(1))->method("executeCommand"); - - $returnValue = $this->testedPhpUnit->execute(); - } - -} \ No newline at end of file diff --git a/Tests/PHPCI/Plugin/PharTest.php b/Tests/PHPCI/Plugin/PharTest.php new file mode 100644 index 00000000..edb17d69 --- /dev/null +++ b/Tests/PHPCI/Plugin/PharTest.php @@ -0,0 +1,209 @@ +cleanSource(); + } + + protected function getPlugin(array $options = array()) + { + $build = $this + ->getMockBuilder('PHPCI\Model\Build') + ->disableOriginalConstructor() + ->getMock(); + + $phpci = $this + ->getMockBuilder('PHPCI\Builder') + ->disableOriginalConstructor() + ->getMock(); + + return new PharPlugin($phpci, $build, $options); + } + + protected function buildTemp() + { + $directory = tempnam(APPLICATION_PATH . '/Tests/temp', 'source'); + unlink($directory); + return $directory; + } + + protected function buildSource() + { + $directory = $this->buildTemp(); + mkdir($directory); + file_put_contents($directory . '/one.php', 'directory = $directory; + return $directory; + } + + protected function cleanSource() + { + if ($this->directory) { + $filenames = array( + '/build.phar', + '/stub.php', + '/views/index.phtml', + '/views', + '/config/config.ini', + '/config', + '/two.php', + '/one.php', + ); + foreach ($filenames as $filename) { + if (is_dir($this->directory . $filename)) { + rmdir($this->directory . $filename); + } else if (is_file($this->directory . $filename)) { + unlink($this->directory . $filename); + } + } + rmdir($this->directory); + $this->directory = null; + } + } + + protected function checkReadonly() + { + if (ini_get('phar.readonly')) { + $this->markTestSkipped('phar writing disabled in php.ini.'); + } + } + + public function testPlugin() + { + $plugin = $this->getPlugin(); + $this->assertInstanceOf('PHPCI\Plugin', $plugin); + $this->assertInstanceOf('PHPCI\Model\Build', $plugin->getBuild()); + $this->assertInstanceOf('PHPCI\Builder', $plugin->getPHPCI()); + } + + public function testDirectory() + { + $plugin = $this->getPlugin(); + $plugin->getPHPCI()->buildPath = 'foo'; + $this->assertEquals('foo', $plugin->getDirectory()); + + $plugin = $this->getPlugin(array('directory' => 'dirname')); + $this->assertEquals('dirname', $plugin->getDirectory()); + } + + public function testFilename() + { + $plugin = $this->getPlugin(); + $this->assertEquals('build.phar', $plugin->getFilename()); + + $plugin = $this->getPlugin(array('filename' => 'another.phar')); + $this->assertEquals('another.phar', $plugin->getFilename()); + } + + public function testRegExp() + { + $plugin = $this->getPlugin(); + $this->assertEquals('/\.php$/', $plugin->getRegExp()); + + $plugin = $this->getPlugin(array('regexp' => '/\.(php|phtml)$/')); + $this->assertEquals('/\.(php|phtml)$/', $plugin->getRegExp()); + } + + public function testStub() + { + $plugin = $this->getPlugin(); + $this->assertNull($plugin->getStub()); + + $plugin = $this->getPlugin(array('stub' => 'stub.php')); + $this->assertEquals('stub.php', $plugin->getStub()); + } + + public function testExecute() + { + $this->checkReadonly(); + + $plugin = $this->getPlugin(); + $path = $this->buildSource(); + $plugin->getPHPCI()->buildPath = $path; + + $this->assertTrue($plugin->execute()); + + $this->assertFileExists($path . '/build.phar'); + PHPPhar::loadPhar($path . '/build.phar'); + $this->assertFileEquals($path . '/one.php', 'phar://build.phar/one.php'); + $this->assertFileEquals($path . '/two.php', 'phar://build.phar/two.php'); + $this->assertFileNotExists('phar://build.phar/config/config.ini'); + $this->assertFileNotExists('phar://build.phar/views/index.phtml'); + } + + public function testExecuteRegExp() + { + $this->checkReadonly(); + + $plugin = $this->getPlugin(array('regexp' => '/\.(php|phtml)$/')); + $path = $this->buildSource(); + $plugin->getPHPCI()->buildPath = $path; + + $this->assertTrue($plugin->execute()); + + $this->assertFileExists($path . '/build.phar'); + PHPPhar::loadPhar($path . '/build.phar'); + $this->assertFileEquals($path . '/one.php', 'phar://build.phar/one.php'); + $this->assertFileEquals($path . '/two.php', 'phar://build.phar/two.php'); + $this->assertFileNotExists('phar://build.phar/config/config.ini'); + $this->assertFileEquals($path . '/views/index.phtml', 'phar://build.phar/views/index.phtml'); + } + + public function testExecuteStub() + { + $this->checkReadonly(); + + $content = << +STUB; + + $path = $this->buildSource(); + file_put_contents($path . '/stub.php', $content); + + $plugin = $this->getPlugin(array('stub' => 'stub.php')); + $plugin->getPHPCI()->buildPath = $path; + + $this->assertTrue($plugin->execute()); + + $this->assertFileExists($path . '/build.phar'); + $phar = new PHPPhar($path . '/build.phar'); + $this->assertEquals($content, trim($phar->getStub())); // + trim because PHP adds newline char + } + + public function testExecuteUnknownDirectory() + { + $this->checkReadonly(); + + $directory = $this->buildTemp(); + + $plugin = $this->getPlugin(array('directory' => $directory)); + $plugin->getPHPCI()->buildPath = $this->buildSource(); + + $this->assertFalse($plugin->execute()); + } +} diff --git a/Tests/PHPCI/Plugin/Util/ComposerPluginInformationTest.php b/Tests/PHPCI/Plugin/Util/ComposerPluginInformationTest.php new file mode 100644 index 00000000..1174d44f --- /dev/null +++ b/Tests/PHPCI/Plugin/Util/ComposerPluginInformationTest.php @@ -0,0 +1,59 @@ +testedInformation = ComposerPluginInformation::buildFromYaml($file); + } + + protected function phpciSetup() + { + $this->setUpFromFile( + __DIR__ . "/../../../../vendor/composer/installed.json" + ); + } + + public function testBuildFromYaml_ReturnsInstance() + { + $this->phpciSetup(); + $this->assertInstanceOf( + '\PHPCI\Plugin\Util\ComposerPluginInformation', + $this->testedInformation + ); + } + + public function testGetInstalledPlugins_ReturnsStdClassArray() + { + $this->phpciSetup(); + $plugins = $this->testedInformation->getInstalledPlugins(); + $this->assertInternalType("array", $plugins); + $this->assertContainsOnly("stdClass", $plugins); + } + + public function testGetPluginClasses_ReturnsStringArray() + { + $this->phpciSetup(); + $classes = $this->testedInformation->getPluginClasses(); + $this->assertInternalType("array", $classes); + $this->assertContainsOnly("string", $classes); + } +} + diff --git a/Tests/PHPCI/Plugin/Util/ExamplePluginConfig.php b/Tests/PHPCI/Plugin/Util/ExamplePluginConfig.php new file mode 100644 index 00000000..5b0819c4 --- /dev/null +++ b/Tests/PHPCI/Plugin/Util/ExamplePluginConfig.php @@ -0,0 +1,22 @@ +registerResource( + // This function will be called when the resource is needed. + function() { + return array( + 'bar' => "Hello", + ); + }, + "requiredArgument", + null + ); +}; diff --git a/Tests/PHPCI/Plugin/Util/ExecutorTest.php b/Tests/PHPCI/Plugin/Util/ExecutorTest.php new file mode 100644 index 00000000..e9083cec --- /dev/null +++ b/Tests/PHPCI/Plugin/Util/ExecutorTest.php @@ -0,0 +1,165 @@ +mockBuildLogger = $this->prophesize('\PHPCI\Logging\BuildLogger'); + $this->mockFactory = $this->prophesize('\PHPCI\Plugin\Util\Factory'); + $this->mockStore = $this->prophesize('\PHPCI\Store\BuildStore'); + $this->testedExecutor = new Executor( + $this->mockFactory->reveal(), + $this->mockBuildLogger->reveal(), + $this->mockStore->reveal() + ); + } + + public function testExecutePlugin_AssumesPHPCINamespaceIfNoneGiven() + { + $options = array(); + $pluginName = 'PhpUnit'; + $pluginNamespace = 'PHPCI\\Plugin\\'; + + $this->mockFactory->buildPlugin($pluginNamespace . $pluginName, $options) + ->shouldBeCalledTimes(1) + ->willReturn($this->prophesize('PHPCI\Plugin')->reveal()); + + $this->testedExecutor->executePlugin($pluginName, $options); + } + + public function testExecutePlugin_KeepsCalledNameSpace() + { + $options = array(); + $pluginClass = $this->getFakePluginClassName('ExamplePluginFull'); + + $this->mockFactory->buildPlugin($pluginClass, $options) + ->shouldBeCalledTimes(1) + ->willReturn($this->prophesize('PHPCI\Plugin')->reveal()); + + $this->testedExecutor->executePlugin($pluginClass, $options); + } + + public function testExecutePlugin_CallsExecuteOnFactoryBuildPlugin() + { + $options = array(); + $pluginName = 'PhpUnit'; + $build = new \PHPCI\Model\Build(); + + $mockPlugin = $this->prophesize('PHPCI\Plugin'); + $mockPlugin->execute()->shouldBeCalledTimes(1); + + $this->mockFactory->buildPlugin(Argument::any(), Argument::any())->willReturn($mockPlugin->reveal()); + $this->mockFactory->getResourceFor('PHPCI\Model\Build')->willReturn($build); + + $this->testedExecutor->executePlugin($pluginName, $options); + } + + public function testExecutePlugin_ReturnsPluginSuccess() + { + $options = array(); + $pluginName = 'PhpUnit'; + + $expectedReturnValue = true; + + $mockPlugin = $this->prophesize('PHPCI\Plugin'); + $mockPlugin->execute()->willReturn($expectedReturnValue); + + $this->mockFactory->buildPlugin(Argument::any(), Argument::any())->willReturn($mockPlugin->reveal()); + + $returnValue = $this->testedExecutor->executePlugin($pluginName, $options); + + $this->assertEquals($expectedReturnValue, $returnValue); + } + + public function testExecutePlugin_LogsFailureForNonExistentClasses() + { + $options = array(); + $pluginName = 'DOESNTEXIST'; + + $this->mockBuildLogger->logFailure('Plugin does not exist: ' . $pluginName)->shouldBeCalledTimes(1); + + $this->testedExecutor->executePlugin($pluginName, $options); + } + + public function testExecutePlugin_LogsFailureWhenExceptionsAreThrownByPlugin() + { + $options = array(); + $pluginName = 'PhpUnit'; + + $expectedException = new \RuntimeException("Generic Error"); + + $mockPlugin = $this->prophesize('PHPCI\Plugin'); + $mockPlugin->execute()->willThrow($expectedException); + + $this->mockFactory->buildPlugin(Argument::any(), Argument::any())->willReturn($mockPlugin->reveal()); + + $this->mockBuildLogger->logFailure('Exception: ' . $expectedException->getMessage(), $expectedException) + ->shouldBeCalledTimes(1); + + $this->testedExecutor->executePlugin($pluginName, $options); + } + + public function testExecutePlugins_CallsEachPluginForStage() + { + $phpUnitPluginOptions = array(); + $behatPluginOptions = array(); + $build = new \PHPCI\Model\Build(); + + $config = array( + 'stageOne' => array( + 'PhpUnit' => $phpUnitPluginOptions, + 'Behat' => $behatPluginOptions, + ) + ); + + $pluginNamespace = 'PHPCI\\Plugin\\'; + + $mockPhpUnitPlugin = $this->prophesize('PHPCI\Plugin'); + $mockPhpUnitPlugin->execute()->shouldBeCalledTimes(1)->willReturn(true); + + $this->mockFactory->buildPlugin($pluginNamespace . 'PhpUnit', $phpUnitPluginOptions) + ->willReturn($mockPhpUnitPlugin->reveal()); + $this->mockFactory->getResourceFor('PHPCI\Model\Build')->willReturn($build); + + $mockBehatPlugin = $this->prophesize('PHPCI\Plugin'); + $mockBehatPlugin->execute()->shouldBeCalledTimes(1)->willReturn(true); + + $this->mockFactory->buildPlugin($pluginNamespace . 'Behat', $behatPluginOptions) + ->willReturn($mockBehatPlugin->reveal()); + + $this->testedExecutor->executePlugins($config, 'stageOne'); + } + + protected function getFakePluginClassName($pluginName) + { + $pluginNamespace = '\\Tests\\PHPCI\\Plugin\\Util\\Fake\\'; + + return $pluginNamespace . $pluginName; + } +} + diff --git a/Tests/PHPCI/Plugin/Util/FactoryTest.php b/Tests/PHPCI/Plugin/Util/FactoryTest.php new file mode 100644 index 00000000..e609011a --- /dev/null +++ b/Tests/PHPCI/Plugin/Util/FactoryTest.php @@ -0,0 +1,219 @@ +testedFactory = new Factory(); + + // Setup a resource that can be returned and asserted against + $this->expectedResource = new \stdClass(); + $resourceLink = $this->expectedResource; + $this->resourceLoader = function() use (&$resourceLink) { + return $resourceLink; + }; + } + + protected function tearDown() + { + // Nothing to do. + } + + + public function testRegisterResourceThrowsExceptionWithoutTypeAndName() + { + $this->setExpectedException('InvalidArgumentException', 'Type or Name must be specified'); + $this->testedFactory->registerResource($this->resourceLoader, null, null); + } + + public function testRegisterResourceThrowsExceptionIfLoaderIsntFunction() + { + $this->setExpectedException('InvalidArgumentException', '$loader is expected to be a function'); + $this->testedFactory->registerResource(array("dummy"), "TestName", "TestClass"); + } + + public function testBuildPluginWorksWithConstructorlessPlugins() + { + $pluginClass = $this->getFakePluginClassName('ExamplePluginWithNoConstructorArgs'); + $plugin = $this->testedFactory->buildPlugin($pluginClass); + $this->assertInstanceOf($pluginClass, $plugin); + } + + public function testBuildPluginFailsForNonPluginClasses() + { + $this->setExpectedException('InvalidArgumentException', 'Requested class must implement \PHPCI\Plugin'); + $plugin = $this->testedFactory->buildPlugin("stdClass"); + } + + public function testBuildPluginWorksWithSingleOptionalArgConstructor() + { + $pluginClass = $this->getFakePluginClassName('ExamplePluginWithSingleOptionalArg'); + $plugin = $this->testedFactory->buildPlugin($pluginClass); + $this->assertInstanceOf($pluginClass, $plugin); + } + + public function testBuildPluginThrowsExceptionIfMissingResourcesForRequiredArg() + { + $this->setExpectedException( + 'DomainException', + 'Unsatisfied dependency: requiredArgument' + ); + + $pluginClass = $this->getFakePluginClassName('ExamplePluginWithSingleRequiredArg'); + $plugin = $this->testedFactory->buildPlugin($pluginClass); + } + + public function testBuildPluginLoadsArgumentsBasedOnName() + { + $pluginClass = $this->getFakePluginClassName('ExamplePluginWithSingleRequiredArg'); + + $this->testedFactory->registerResource( + $this->resourceLoader, + "requiredArgument" + ); + + /** @var ExamplePluginWithSingleRequiredArg $plugin */ + $plugin = $this->testedFactory->buildPlugin($pluginClass); + + $this->assertEquals($this->expectedResource, $plugin->RequiredArgument); + } + + public function testBuildPluginLoadsArgumentsBasedOnType() + { + $pluginClass = $this->getFakePluginClassName('ExamplePluginWithSingleTypedRequiredArg'); + + $this->testedFactory->registerResource( + $this->resourceLoader, + null, + "stdClass" + ); + + /** @var ExamplePluginWithSingleTypedRequiredArg $plugin */ + $plugin = $this->testedFactory->buildPlugin($pluginClass); + + $this->assertEquals($this->expectedResource, $plugin->RequiredArgument); + } + + public function testBuildPluginLoadsFullExample() + { + $pluginClass = $this->getFakePluginClassName('ExamplePluginFull'); + + $this->registerBuildAndBuilder(); + + /** @var ExamplePluginFull $plugin */ + $plugin = $this->testedFactory->buildPlugin($pluginClass); + + $this->assertInstanceOf($pluginClass, $plugin); + } + + public function testBuildPluginLoadsFullExampleWithOptions() + { + $pluginClass = $this->getFakePluginClassName('ExamplePluginFull'); + + $expectedArgs = array( + 'thing' => "stuff" + ); + + $this->registerBuildAndBuilder(); + + /** @var ExamplePluginFull $plugin */ + $plugin = $this->testedFactory->buildPlugin( + $pluginClass, + $expectedArgs + ); + + $this->assertInternalType('array', $plugin->Options); + $this->assertArrayHasKey('thing', $plugin->Options); + } + + public function testAddConfigFromFile_ReturnsTrueForValidFile() + { + $result = $this->testedFactory->addConfigFromFile( + realpath(__DIR__ . "/ExamplePluginConfig.php") + ); + + $this->assertTrue($result); + } + + public function testAddConfigFromFile_RegistersResources() + { + $this->testedFactory->addConfigFromFile( + realpath(__DIR__ . "/ExamplePluginConfig.php") + ); + + $pluginClass = $this->getFakePluginClassName('ExamplePluginWithSingleRequiredArg'); + $plugin = $this->testedFactory->buildPlugin($pluginClass); + + // The Example config file defines an array as the resource. + $this->assertEquals( + array("bar" => "Hello"), + $plugin->RequiredArgument + ); + } + + /** + * Registers mocked Builder and Build classes so that realistic plugins + * can be tested. + */ + private function registerBuildAndBuilder() + { + $self = $this; + + $this->testedFactory->registerResource( + function () use ($self) { + return $self->getMock( + 'PHPCI\Builder', + array(), + array(), + '', + false + ); + }, + null, + 'PHPCI\\Builder' + ); + + $this->testedFactory->registerResource( + function () use ($self) { + return $self->getMock( + 'PHPCI\Model\Build', + array(), + array(), + '', + false + ); + }, + null, + 'PHPCI\\Model\\Build' + ); + } + + protected function getFakePluginClassName($pluginName) + { + $pluginNamespace = '\\Tests\\PHPCI\\Plugin\\Util\\Fake\\'; + + return $pluginNamespace . $pluginName; + } +} + diff --git a/Tests/PHPCI/Plugin/Util/Fake/ExamplePluginFull.php b/Tests/PHPCI/Plugin/Util/Fake/ExamplePluginFull.php new file mode 100644 index 00000000..fa81a352 --- /dev/null +++ b/Tests/PHPCI/Plugin/Util/Fake/ExamplePluginFull.php @@ -0,0 +1,36 @@ +Options = $options; + } + + public function execute() + { + + } +} diff --git a/Tests/PHPCI/Plugin/Util/Fake/ExamplePluginWithNoConstructorArgs.php b/Tests/PHPCI/Plugin/Util/Fake/ExamplePluginWithNoConstructorArgs.php new file mode 100644 index 00000000..544ec4e3 --- /dev/null +++ b/Tests/PHPCI/Plugin/Util/Fake/ExamplePluginWithNoConstructorArgs.php @@ -0,0 +1,20 @@ +RequiredArgument = $requiredArgument; + } + + public function execute() + { + + } +} diff --git a/Tests/PHPCI/Plugin/Util/Fake/ExamplePluginWithSingleTypedRequiredArg.php b/Tests/PHPCI/Plugin/Util/Fake/ExamplePluginWithSingleTypedRequiredArg.php new file mode 100644 index 00000000..b256c58c --- /dev/null +++ b/Tests/PHPCI/Plugin/Util/Fake/ExamplePluginWithSingleTypedRequiredArg.php @@ -0,0 +1,29 @@ +RequiredArgument = $requiredArgument; + } + + public function execute() + { + + } +} diff --git a/Tests/PHPCI/Plugin/Util/FilesPluginInformationTest.php b/Tests/PHPCI/Plugin/Util/FilesPluginInformationTest.php new file mode 100644 index 00000000..4af2d7a6 --- /dev/null +++ b/Tests/PHPCI/Plugin/Util/FilesPluginInformationTest.php @@ -0,0 +1,34 @@ +getInstalledPlugins(); + $this->assertContainsOnlyInstancesOf('stdClass', $pluginInfos); + } + + public function testGetPluginClasses_returnsStrings() + { + $pluginDirPath = realpath(__DIR__ . "/../../../../PHPCI/Plugin/"); + $test = FilesPluginInformation::newFromDir($pluginDirPath); + $pluginInfos = $test->getPluginClasses(); + $this->assertContainsOnly('string', $pluginInfos); + } +} + diff --git a/Tests/PHPCI/Plugin/Util/TapParserTest.php b/Tests/PHPCI/Plugin/Util/TapParserTest.php new file mode 100644 index 00000000..8d31d7c9 --- /dev/null +++ b/Tests/PHPCI/Plugin/Util/TapParserTest.php @@ -0,0 +1,303 @@ +parse(); + + $this->assertEquals(array( + array('pass' => true, 'severity' => 'success', 'message' => 'SomeTest::testAnother'), + array('pass' => false, 'severity' => 'fail', 'message' => ''), + ), $result); + + $this->assertEquals(1, $parser->getTotalFailures()); + } + + public function testSimple2() + { + $content = <<parse(); + + $this->assertEquals(array( + array('pass' => true, 'severity' => 'success', 'message' => 'SomeTest::testAnother'), + array('pass' => false, 'severity' => 'fail', 'message' => ''), + ), $result); + + $this->assertEquals(1, $parser->getTotalFailures()); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessageRegExp /No TAP/ + */ + public function testNoTapData() + { + $content = <<parse(); + } + + public function testTapCoverage() + { + $content = <<parse(); + + $this->assertEquals(array(), $result); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessageRegExp /Duplicated TAP/ + */ + public function testDuplicateOutput() + { + $content = <<parse(); + } + + public function testSuiteAndTest() + { + $content = <<parse(); + + $this->assertEquals(array( + array('pass' => true, 'severity' => 'success', 'message' => 'SomeTest::testAnother',), + array('pass' => false, 'severity' => 'fail', 'message' => 'Failure: SomeTest::testAnother'), + array('pass' => false, 'severity' => 'error', 'message' => 'Error: SomeTest::testAnother'), + ), $result); + + $this->assertEquals(2, $parser->getTotalFailures()); + } + + + public function testSkipped() + { + $content = <<parse(); + + $this->assertEquals(array( + array('pass' => true, 'severity' => 'skipped', 'message' => ''), + array('pass' => true, 'severity' => 'skipped', 'message' => 'foobar'), + array('pass' => true, 'severity' => 'skipped', 'message' => 'foo, skipped: bar'), + ), $result); + + $this->assertEquals(0, $parser->getTotalFailures()); + } + + public function testTodo() + { + $content = <<parse(); + + $this->assertEquals(array( + array('pass' => true, 'severity' => 'todo', 'message' => 'SomeTest::testAnother, todo: really implement this test'), + array('pass' => true, 'severity' => 'todo', 'message' => 'really implement this test'), + array('pass' => true, 'severity' => 'todo', 'message' => 'this is a message, todo: really implement this test'), + array('pass' => true, 'severity' => 'todo', 'message' => ''), + ), $result); + + $this->assertEquals(0, $parser->getTotalFailures()); + } + + public function testYamlDiagnostic() + { + // From https://phpunit.de/manual/current/en/logging.html#logging.tap + $content = <<parse(); + + $this->assertEquals(array( + array( + 'pass' => false, + 'severity' => 'fail', + 'message' => 'FOO' . PHP_EOL . 'BAR', + ), + ), $result); + + $this->assertEquals(1, $parser->getTotalFailures()); + } + + public function testFailureAndError() + { + // From https://phpunit.de/manual/current/en/logging.html#logging.tap + $content = <<parse(); + + $this->assertEquals(array( + array( + 'pass' => false, + 'severity' => 'fail', + 'message' => 'Failure: testFailure::FailureErrorTest', + ), + array( + 'pass' => false, + 'severity' => 'error', + 'message' => 'Error: testError::FailureErrorTest', + ) + ), $result); + + $this->assertEquals(2, $parser->getTotalFailures()); + } + + /** + * @expectedException \Exception + */ + public function testGarbage() + { + $content = "Garbage !"; + + $parser = new TapParser($content); + $parser->parse(); + } + + /** + * @expectedException \Exception + */ + public function testInvalidTestCount() + { + $content = <<parse(); + } + + /** + * @expectedException \Exception + */ + public function testEndlessYaml() + { + $content = <<parse(); + } + + public function testCodeception() + { + $content = <<< TAP +TAP version 13 +ok 1 - try to access the dashboard as a guest (Auth/GuestAccessDashboardAndRedirectCept) +ok 2 - see the login page (Auth/SeeLoginCept) +ok 3 - click forgot password and see the email form (Auth/SeeLoginForgotPasswordCept) +ok 4 - see powered by runmybusiness branding (Auth/ShouldSeePoweredByBrandingCept) +ok 5 - submit invalid credentials (Auth/SubmitLoginAndFailCept) +ok 6 - submit valid credentials and see the dashboard (Auth/SubmitLoginAndSucceedCept) +1..6 +TAP; + + $parser = new TapParser($content); + $result = $parser->parse(); + + $this->assertEquals( + array( + array('pass' => true, 'severity' => 'success', 'message' => 'try to access the dashboard as a guest (Auth/GuestAccessDashboardAndRedirectCept)'), + array('pass' => true, 'severity' => 'success', 'message' => 'see the login page (Auth/SeeLoginCept)'), + array('pass' => true, 'severity' => 'success', 'message' => 'click forgot password and see the email form (Auth/SeeLoginForgotPasswordCept)'), + array('pass' => true, 'severity' => 'success', 'message' => 'see powered by runmybusiness branding (Auth/ShouldSeePoweredByBrandingCept)'), + array('pass' => true, 'severity' => 'success', 'message' => 'submit invalid credentials (Auth/SubmitLoginAndFailCept)'), + array('pass' => true, 'severity' => 'success', 'message' => 'submit valid credentials and see the dashboard (Auth/SubmitLoginAndSucceedCept)'), + ), + $result + ); + + $this->assertEquals(0, $parser->getTotalFailures()); + + } +} diff --git a/Tests/PHPCI/ProcessControl/PosixProcessControlTest.php b/Tests/PHPCI/ProcessControl/PosixProcessControlTest.php new file mode 100644 index 00000000..96bbf5b4 --- /dev/null +++ b/Tests/PHPCI/ProcessControl/PosixProcessControlTest.php @@ -0,0 +1,17 @@ +object = new PosixProcessControl(); + } + + public function testIsAvailable() + { + $this->assertEquals(function_exists('posix_kill'), PosixProcessControl::isAvailable()); + } +} diff --git a/Tests/PHPCI/ProcessControl/ProcessControlTest.php b/Tests/PHPCI/ProcessControl/ProcessControlTest.php new file mode 100644 index 00000000..52022743 --- /dev/null +++ b/Tests/PHPCI/ProcessControl/ProcessControlTest.php @@ -0,0 +1,126 @@ +pipes = array(); + + $this->process = proc_open($this->getTestCommand(), $desc, $this->pipes); + usleep(500); + + $this->assertTrue(is_resource($this->process)); + $this->assertTrue($this->isRunning()); + + $status = proc_get_status($this->process); + return $status['pid']; + } + + /** End the running process. + * + * @return int + */ + protected function endProcess() + { + if (!is_resource($this->process)) { + return; + } + array_map('fclose', $this->pipes); + $exitCode = proc_close($this->process); + $this->assertFalse($this->isRunning()); + $this->process = null; + return $exitCode; + } + + /** + * @return bool + */ + protected function isRunning() + { + if (!is_resource($this->process)) { + return false; + } + $status = proc_get_status($this->process); + return $status['running']; + } + + public function testIsRunning() + { + if (!$this->object->isAvailable()) { + $this->markTestSkipped(); + } + + $pid = $this->startProcess(); + + $this->assertTrue($this->object->isRunning($pid)); + + fwrite($this->pipes[0], PHP_EOL); + + $exitCode = $this->endProcess(); + + $this->assertEquals(0, $exitCode); + $this->assertFalse($this->object->isRunning($pid)); + } + + public function testSoftKill() + { + if (!$this->object->isAvailable()) { + $this->markTestSkipped(); + } + + $pid = $this->startProcess(); + + $this->object->kill($pid); + usleep(500); + + $this->assertFalse($this->isRunning()); + } + + public function testForcefullyKill() + { + if (!$this->object->isAvailable()) { + $this->markTestSkipped(); + } + + $pid = $this->startProcess(); + + $this->object->kill($pid, true); + usleep(500); + + $this->assertFalse($this->isRunning()); + } + + abstract public function testIsAvailable(); + + abstract public function getTestCommand(); + + protected function tearDown() + { + parent::tearDown(); + $this->endProcess(); + } +} diff --git a/Tests/PHPCI/ProcessControl/UnixProcessControlTest.php b/Tests/PHPCI/ProcessControl/UnixProcessControlTest.php new file mode 100644 index 00000000..9b102a73 --- /dev/null +++ b/Tests/PHPCI/ProcessControl/UnixProcessControlTest.php @@ -0,0 +1,22 @@ +object = new UnixProcessControl(); + } + + public function getTestCommand() + { + return "read SOMETHING"; + } + + public function testIsAvailable() + { + $this->assertEquals(DIRECTORY_SEPARATOR === '/', UnixProcessControl::isAvailable()); + } +} diff --git a/Tests/PHPCI/ProcessControl/WindowsProcessControlTest.php b/Tests/PHPCI/ProcessControl/WindowsProcessControlTest.php new file mode 100644 index 00000000..ed10fc95 --- /dev/null +++ b/Tests/PHPCI/ProcessControl/WindowsProcessControlTest.php @@ -0,0 +1,22 @@ +object = new WindowsProcessControl; + } + + public function getTestCommand() + { + return "pause"; + } + + public function testIsAvailable() + { + $this->assertEquals(DIRECTORY_SEPARATOR === '\\', WindowsProcessControl::isAvailable()); + } +} diff --git a/Tests/PHPCI/Service/BuildServiceTest.php b/Tests/PHPCI/Service/BuildServiceTest.php new file mode 100644 index 00000000..3c96b131 --- /dev/null +++ b/Tests/PHPCI/Service/BuildServiceTest.php @@ -0,0 +1,149 @@ + + */ +class BuildServiceTest extends \PHPUnit_Framework_TestCase +{ + + /** + * @var BuildService $testedService + */ + protected $testedService; + + /** + * @var \ $mockBuildStore + */ + protected $mockBuildStore; + + public function setUp() + { + $this->mockBuildStore = $this->getMock('PHPCI\Store\BuildStore'); + $this->mockBuildStore->expects($this->any()) + ->method('save') + ->will($this->returnArgument(0)); + + $this->testedService = new BuildService($this->mockBuildStore); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_CreateBasicBuild() + { + $project = new Project(); + $project->setType('github'); + $project->setId(101); + + $returnValue = $this->testedService->createBuild($project); + + $this->assertEquals(101, $returnValue->getProjectId()); + $this->assertEquals(Build::STATUS_NEW, $returnValue->getStatus()); + $this->assertNull($returnValue->getStarted()); + $this->assertNull($returnValue->getFinished()); + $this->assertNull($returnValue->getLog()); + $this->assertEquals(\PHPCI\Helper\Lang::get('manual_build'), $returnValue->getCommitMessage()); + $this->assertNull($returnValue->getCommitterEmail()); + $this->assertNull($returnValue->getExtra()); + $this->assertEquals('master', $returnValue->getBranch()); + $this->assertInstanceOf('DateTime', $returnValue->getCreated()); + $this->assertEquals('Manual', $returnValue->getCommitId()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_CreateBuildWithOptions() + { + $project = new Project(); + $project->setType('hg'); + $project->setId(101); + + $returnValue = $this->testedService->createBuild($project, '123', 'testbranch', 'test@example.com', 'test'); + + $this->assertEquals('testbranch', $returnValue->getBranch()); + $this->assertEquals('123', $returnValue->getCommitId()); + $this->assertEquals('test', $returnValue->getCommitMessage()); + $this->assertEquals('test@example.com', $returnValue->getCommitterEmail()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_CreateBuildWithExtra() + { + $project = new Project(); + $project->setType('bitbucket'); + $project->setId(101); + + $returnValue = $this->testedService->createBuild($project, null, null, null, null, array('item1' => 1001)); + + $this->assertEquals(1001, $returnValue->getExtra('item1')); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_CreateDuplicateBuild() + { + $build = new Build(); + $build->setId(1); + $build->setProject(101); + $build->setCommitId('abcde'); + $build->setStatus(Build::STATUS_FAILED); + $build->setLog('Test'); + $build->setBranch('example_branch'); + $build->setStarted(new \DateTime()); + $build->setFinished(new \DateTime()); + $build->setCommitMessage('test'); + $build->setCommitterEmail('test@example.com'); + $build->setExtra(json_encode(array('item1' => 1001))); + + $returnValue = $this->testedService->createDuplicateBuild($build); + + $this->assertNotEquals($build->getId(), $returnValue->getId()); + $this->assertEquals($build->getProjectId(), $returnValue->getProjectId()); + $this->assertEquals($build->getCommitId(), $returnValue->getCommitId()); + $this->assertNotEquals($build->getStatus(), $returnValue->getStatus()); + $this->assertEquals(Build::STATUS_NEW, $returnValue->getStatus()); + $this->assertNull($returnValue->getLog()); + $this->assertEquals($build->getBranch(), $returnValue->getBranch()); + $this->assertNotEquals($build->getCreated(), $returnValue->getCreated()); + $this->assertNull($returnValue->getStarted()); + $this->assertNull($returnValue->getFinished()); + $this->assertEquals('test', $returnValue->getCommitMessage()); + $this->assertEquals('test@example.com', $returnValue->getCommitterEmail()); + $this->assertEquals($build->getExtra('item1'), $returnValue->getExtra('item1')); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_DeleteBuild() + { + $store = $this->getMock('PHPCI\Store\BuildStore'); + $store->expects($this->once()) + ->method('delete') + ->will($this->returnValue(true)); + + $service = new BuildService($store); + $build = new Build(); + + $this->assertEquals(true, $service->deleteBuild($build)); + } +} diff --git a/Tests/PHPCI/Service/BuiltStatusServiceTest.php b/Tests/PHPCI/Service/BuiltStatusServiceTest.php new file mode 100644 index 00000000..91e115e4 --- /dev/null +++ b/Tests/PHPCI/Service/BuiltStatusServiceTest.php @@ -0,0 +1,212 @@ + + */ +class BuildStatusServiceTest extends \PHPUnit_Framework_TestCase +{ + const BRANCH = 'master'; + + /** @var Project */ + protected $project; + + protected $timezone; + + public function setUp() + { + $project = new Project(); + $project->setId(3); + $project->setBranch(self::BRANCH); + $project->setTitle('Test'); + + $this->project = $project; + $this->timezone = date_default_timezone_get(); + + date_default_timezone_set('UTC'); + } + + public function tearDown() + { + date_default_timezone_set($this->timezone); + } + + /** + * @param $configId + * @param bool $setProject + * @return Build + */ + protected function getBuild($configId, $setProject = true) + { + $config = array( + '1' => array( + 'status' => Build::STATUS_RUNNING, + 'id' => 77, + 'finishDateTime' => null, + 'startedDate' => '2014-10-25 21:20:02', + 'previousBuild' => null, + ), + '2' => array( + 'status' => Build::STATUS_RUNNING, + 'id' => 78, + 'finishDateTime' => null, + 'startedDate' => '2014-10-25 21:20:02', + 'previousBuild' => 4, + ), + '3' => array( + 'status' => Build::STATUS_SUCCESS, + 'id' => 7, + 'finishDateTime' => '2014-10-25 21:50:02', + 'startedDate' => '2014-10-25 21:20:02', + 'previousBuild' => null, + ), + '4' => array( + 'status' => Build::STATUS_FAILED, + 'id' => 13, + 'finishDateTime' => '2014-10-13 13:13:13', + 'previousBuild' => null, + ), + '5' => array( + 'status' => Build::STATUS_NEW, + 'id' => 1000, + 'finishDateTime' => '2014-12-25 21:12:21', + 'previousBuild' => 3, + ) + ); + + $build = new Build(); + $build->setId($config[$configId]['id']); + $build->setBranch(self::BRANCH); + $build->setStatus($config[$configId]['status']); + if ($config[$configId]['finishDateTime']) { + $build->setFinished(new \DateTime($config[$configId]['finishDateTime'])); + } + if (!empty($config[$configId]['startedDate'])) { + $build->setStarted(new \DateTime('2014-10-25 21:20:02')); + } + + $project = $this->getProjectMock($config[$configId]['previousBuild'], $setProject); + + $build->setProjectObject($project); + + return $build; + } + + /** + * @param null|int $prevBuildId + * @param bool $setProject + * @return Project + */ + protected function getProjectMock($prevBuildId = null, $setProject = true) { + + $project = $this->getMock('PHPCI\Model\Project', array('getLatestBuild')); + + $prevBuild = ($prevBuildId) ? $this->getBuild($prevBuildId, false) : null; + + $project->expects($this->any()) + ->method('getLatestBuild') + ->will($this->returnValue($prevBuild)); + + /* @var $project Project */ + + $project->setId(3); + $project->setBranch(self::BRANCH); + $project->setTitle('Test'); + + if ($setProject) { + $this->project = $project; + } + + return $project; + + } + + /** + * @dataProvider finishedProvider + * + * @param int $buildConfigId + * @param array $expectedResult + */ + public function testFinished($buildConfigId, array $expectedResult) + { + $build = $this->getBuild($buildConfigId); + $service = new BuildStatusService(self::BRANCH, $this->project, $build); + $service->setUrl('http://phpci.dev/'); + $this->assertEquals($expectedResult, $service->toArray()); + } + + public function finishedProvider() + { + return array( + 'buildingStatus' => array( + 1, + array( + 'name' => 'Test / master', + 'activity' => 'Building', + 'lastBuildLabel' => '', + 'lastBuildStatus' => '', + 'lastBuildTime' => '', + 'webUrl' => 'http://phpci.dev/build/view/77', + ) + ), + 'buildingStatusWithPrev' => array( + 2, + array( + 'name' => 'Test / master', + 'activity' => 'Building', + 'lastBuildLabel' => 13, + 'lastBuildStatus' => 'Failure', + 'lastBuildTime' => '2014-10-13T13:13:13+0000', + 'webUrl' => 'http://phpci.dev/build/view/78', + ) + ), + 'successStatus' => array( + 3, + array( + 'name' => 'Test / master', + 'activity' => 'Sleeping', + 'lastBuildLabel' => 7, + 'lastBuildStatus' => 'Success', + 'lastBuildTime' => '2014-10-25T21:50:02+0000', + 'webUrl' => 'http://phpci.dev/build/view/7', + ) + ), + 'failureStatus' => array( + 4, + array( + 'name' => 'Test / master', + 'activity' => 'Sleeping', + 'lastBuildLabel' => 13, + 'lastBuildStatus' => 'Failure', + 'lastBuildTime' => '2014-10-13T13:13:13+0000', + 'webUrl' => 'http://phpci.dev/build/view/13', + ) + ), + 'pending' => array( + 5, + array( + 'name' => 'Test / master', + 'activity' => 'Pending', + 'lastBuildLabel' => 7, + 'lastBuildStatus' => 'Success', + 'lastBuildTime' => '2014-10-25T21:50:02+0000', + 'webUrl' => 'http://phpci.dev/build/view/1000', + ) + ), + ); + } +} \ No newline at end of file diff --git a/Tests/PHPCI/Service/ProjectServiceTest.php b/Tests/PHPCI/Service/ProjectServiceTest.php new file mode 100644 index 00000000..229a5d22 --- /dev/null +++ b/Tests/PHPCI/Service/ProjectServiceTest.php @@ -0,0 +1,144 @@ + + */ +class ProjectServiceTest extends \PHPUnit_Framework_TestCase +{ + + /** + * @var ProjectService $testedService + */ + protected $testedService; + + /** + * @var \ $mockProjectStore + */ + protected $mockProjectStore; + + public function setUp() + { + $this->mockProjectStore = $this->getMock('PHPCI\Store\ProjectStore'); + $this->mockProjectStore->expects($this->any()) + ->method('save') + ->will($this->returnArgument(0)); + + $this->testedService = new ProjectService($this->mockProjectStore); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_CreateBasicProject() + { + $returnValue = $this->testedService->createProject('Test Project', 'github', 'block8/phpci'); + + $this->assertEquals('Test Project', $returnValue->getTitle()); + $this->assertEquals('github', $returnValue->getType()); + $this->assertEquals('block8/phpci', $returnValue->getReference()); + $this->assertEquals('master', $returnValue->getBranch()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_CreateProjectWithOptions() + { + $options = array( + 'ssh_private_key' => 'private', + 'ssh_public_key' => 'public', + 'allow_public_status' => 1, + 'build_config' => 'config', + 'branch' => 'testbranch', + ); + + $returnValue = $this->testedService->createProject('Test Project', 'github', 'block8/phpci', $options); + + $this->assertEquals('private', $returnValue->getSshPrivateKey()); + $this->assertEquals('public', $returnValue->getSshPublicKey()); + $this->assertEquals('config', $returnValue->getBuildConfig()); + $this->assertEquals('testbranch', $returnValue->getBranch()); + $this->assertEquals(1, $returnValue->getAllowPublicStatus()); + } + + /** + * @link https://github.com/Block8/PHPCI/issues/484 + * @covers PHPUnit::execute + */ + public function testExecute_CreateGitlabProjectWithoutPort() + { + $reference = 'git@gitlab.block8.net:block8/phpci.git'; + $returnValue = $this->testedService->createProject('Gitlab', 'gitlab', $reference); + + $this->assertEquals('git', $returnValue->getAccessInformation('user')); + $this->assertEquals('gitlab.block8.net', $returnValue->getAccessInformation('domain')); + $this->assertEquals('block8/phpci', $returnValue->getReference()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_UpdateExistingProject() + { + $project = new Project(); + $project->setTitle('Before Title'); + $project->setReference('Before Reference'); + $project->setType('github'); + + $returnValue = $this->testedService->updateProject($project, 'After Title', 'bitbucket', 'After Reference'); + + $this->assertEquals('After Title', $returnValue->getTitle()); + $this->assertEquals('After Reference', $returnValue->getReference()); + $this->assertEquals('bitbucket', $returnValue->getType()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_EmptyPublicStatus() + { + $project = new Project(); + $project->setAllowPublicStatus(1); + + $options = array( + 'ssh_private_key' => 'private', + 'ssh_public_key' => 'public', + 'build_config' => 'config', + ); + + $returnValue = $this->testedService->updateProject($project, 'Test Project', 'github', 'block8/phpci', $options); + + $this->assertEquals(0, $returnValue->getAllowPublicStatus()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_DeleteProject() + { + $store = $this->getMock('PHPCI\Store\ProjectStore'); + $store->expects($this->once()) + ->method('delete') + ->will($this->returnValue(true)); + + $service = new ProjectService($store); + $project = new Project(); + + $this->assertEquals(true, $service->deleteProject($project)); + } +} diff --git a/Tests/PHPCI/Service/UserServiceTest.php b/Tests/PHPCI/Service/UserServiceTest.php new file mode 100644 index 00000000..8c325479 --- /dev/null +++ b/Tests/PHPCI/Service/UserServiceTest.php @@ -0,0 +1,117 @@ + + */ +class UserServiceTest extends \PHPUnit_Framework_TestCase +{ + + /** + * @var UserService $testedService + */ + protected $testedService; + + /** + * @var \ $mockBuildStore + */ + protected $mockUserStore; + + public function setUp() + { + $this->mockUserStore = $this->getMock('PHPCI\Store\UserStore'); + $this->mockUserStore->expects($this->any()) + ->method('save') + ->will($this->returnArgument(0)); + + $this->testedService = new UserService($this->mockUserStore); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_CreateNonAdminUser() + { + $user = $this->testedService->createUser('Test', 'test@example.com', 'testing', 0); + + $this->assertEquals('Test', $user->getName()); + $this->assertEquals('test@example.com', $user->getEmail()); + $this->assertEquals(0, $user->getIsAdmin()); + $this->assertTrue(password_verify('testing', $user->getHash())); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_CreateAdminUser() + { + $user = $this->testedService->createUser('Test', 'test@example.com', 'testing', 1); + $this->assertEquals(1, $user->getIsAdmin()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_RevokeAdminStatus() + { + $user = new User(); + $user->setEmail('test@example.com'); + $user->setName('Test'); + $user->setIsAdmin(1); + + $user = $this->testedService->updateUser($user, 'Test', 'test@example.com', 'testing', 0); + $this->assertEquals(0, $user->getIsAdmin()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_GrantAdminStatus() + { + $user = new User(); + $user->setEmail('test@example.com'); + $user->setName('Test'); + $user->setIsAdmin(0); + + $user = $this->testedService->updateUser($user, 'Test', 'test@example.com', 'testing', 1); + $this->assertEquals(1, $user->getIsAdmin()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_ChangesPasswordIfNotEmpty() + { + $user = new User(); + $user->setHash(password_hash('testing', PASSWORD_DEFAULT)); + + $user = $this->testedService->updateUser($user, 'Test', 'test@example.com', 'newpassword', 0); + $this->assertFalse(password_verify('testing', $user->getHash())); + $this->assertTrue(password_verify('newpassword', $user->getHash())); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_DoesNotChangePasswordIfEmpty() + { + $user = new User(); + $user->setHash(password_hash('testing', PASSWORD_DEFAULT)); + + $user = $this->testedService->updateUser($user, 'Test', 'test@example.com', '', 0); + $this->assertTrue(password_verify('testing', $user->getHash())); + } +} diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php new file mode 100644 index 00000000..a0e11105 --- /dev/null +++ b/Tests/bootstrap.php @@ -0,0 +1,43 @@ +loadYaml($configFile); +} + +require_once(dirname(__DIR__) . '/vars.php'); + +\PHPCI\Helper\Lang::init($config); +\PHPCI\Helper\Lang::setLanguage("en"); diff --git a/Tests/temp/.gitignore b/Tests/temp/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/Tests/temp/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/bootstrap.php b/bootstrap.php index b84f6428..ebecad81 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -8,49 +8,72 @@ */ // Let PHP take a guess as to the default timezone, if the user hasn't set one: -date_default_timezone_set(@date_default_timezone_get()); +use PHPCI\Logging\LoggerConfig; -// Set up a basic autoloader for PHPCI: -$autoload = function ($class) { - $file = str_replace(array('\\', '_'), '/', $class); - $file .= '.php'; - - if (substr($file, 0, 1) == '/') { - $file = substr($file, 1); - } - - if (is_file(dirname(__FILE__) . '/' . $file)) { - include(dirname(__FILE__) . '/' . $file); - return; - } -}; - -spl_autoload_register($autoload, true, true); - - - - -if (!file_exists(dirname(__FILE__) . '/PHPCI/config.yml')) { - if (defined('PHPCI_IS_CONSOLE') && PHPCI_IS_CONSOLE) { - file_put_contents('php://stderr', 'Please install PHPCI with "composer install" before using console'); - exit(1); - } else { - header('Location: install.php'); - die; - } +$timezone = ini_get('date.timezone'); +if (empty($timezone)) { + date_default_timezone_set('UTC'); } +$configFile = dirname(__FILE__) . '/PHPCI/config.yml'; +$configEnv = getenv('phpci_config_file'); +$usingCustomConfigFile = false; + +if (!empty($configEnv) && file_exists($configEnv)) { + $configFile = $configEnv; + $usingCustomConfigFile = true; +} + +// If we don't have a config file at all, fail at this point and tell the user to install: +if (!file_exists($configFile) && (!defined('PHPCI_IS_CONSOLE') || !PHPCI_IS_CONSOLE)) { + $message = 'PHPCI has not yet been installed - Please use the command "./console phpci:install" '; + $message .= '(or "php ./console phpci:install" for Windows) to install it.'; + + die($message); +} + +// If composer has not been run, fail at this point and tell the user to install: +if (!file_exists(dirname(__FILE__) . '/vendor/autoload.php') && defined('PHPCI_IS_CONSOLE') && PHPCI_IS_CONSOLE) { + $message = 'Please install PHPCI with "composer install" (or "php composer.phar install"'; + $message .= ' for Windows) before using console'; + + file_put_contents('php://stderr', $message); + exit(1); +} // Load Composer autoloader: require_once(dirname(__FILE__) . '/vendor/autoload.php'); +\PHPCI\ErrorHandler::register(); + +if (defined('PHPCI_IS_CONSOLE') && PHPCI_IS_CONSOLE) { + $loggerConfig = LoggerConfig::newFromFile(__DIR__ . "/loggerconfig.php"); +} + // Load configuration if present: $conf = array(); $conf['b8']['app']['namespace'] = 'PHPCI'; $conf['b8']['app']['default_controller'] = 'Home'; $conf['b8']['view']['path'] = dirname(__FILE__) . '/PHPCI/View/'; +$conf['using_custom_file'] = $usingCustomConfigFile; $config = new b8\Config($conf); -$config->loadYaml(dirname(__FILE__) . '/PHPCI/config.yml'); + +if (file_exists($configFile)) { + $config->loadYaml($configFile); +} + +/** + * Allow to modify PHPCI configuration without modify versioned code. + * Daemons should be killed to apply changes in the file. + * + * @ticket 781 + */ +$localVarsFile = dirname(__FILE__) . '/local_vars.php'; +if (is_readable($localVarsFile)) { + require_once $localVarsFile; +} require_once(dirname(__FILE__) . '/vars.php'); + +\PHPCI\Helper\Lang::init($config); diff --git a/build/.gitignore b/build/.gitignore deleted file mode 100755 index c96a04f0..00000000 --- a/build/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/changelog.md b/changelog.md new file mode 100644 index 00000000..541e4a13 --- /dev/null +++ b/changelog.md @@ -0,0 +1,10 @@ +# PHPCI Changelog + +## v.Next + +### New Features: +- SSH-based Mercurial clones (Commit: [e98647bd](https://github.com/Block8/PHPCI/commit/e98647bd97d49741242d252514b8703504a62869), PR: [#812](https://github.com/Block8/PHPCI/pull/812)) +- Ability to archive projects (Commit: [1466ad06](https://github.com/Block8/PHPCI/commit/1466ad06ef708cbab2b53112fc59e8c1d70c2e33), PR: [#771](https://github.com/Block8/PHPCI/pull/771)) + +### Bug Fixes and Tweaks: + diff --git a/composer.json b/composer.json index 9e3ff52a..a9080a94 100644 --- a/composer.json +++ b/composer.json @@ -22,24 +22,58 @@ "source": "https://github.com/Block8/PHPCI" }, + "autoload": { + "psr-4": { + "PHPCI\\": "PHPCI" + } + }, + + "autoload-dev": { + "psr-4": { + "Tests\\PHPCI\\": "Tests/PHPCI/" + } + }, + "require": { - "block8/b8framework" : "1.*", - "ircmaxell/password-compat": "1.*", - "swiftmailer/swiftmailer" : "5.0.*", - "symfony/yaml" : "2.*", - "symfony/console" : "2.*" + "php": ">=5.3.8", + "ext-pdo": "*", + "ext-pdo_mysql": "*", + "block8/b8framework": "~1.0", + "ircmaxell/password-compat": "~1.0", + "swiftmailer/swiftmailer": "~5.0", + "symfony/yaml": "~2.1", + "symfony/console": "~2.1", + "psr/log": "~1.0", + "monolog/monolog": "~1.6", + "pimple/pimple": "~1.1", + "robmorgan/phinx": "~0.4", + "sensiolabs/ansi-to-html": "~1.1", + "pda/pheanstalk": "~3.1", + "maknz/slack": "~1.7", + "hipchat/hipchat-php": "~1.4", + "mremi/flowdock": "~1.0" + }, + + "require-dev": { + "phpunit/phpunit": "~4.5", + "phpmd/phpmd": "~2.0", + "squizlabs/php_codesniffer": "~2.3", + "block8/php-docblock-checker": "~1.0", + "phploc/phploc": "~2.0", + "jakub-onderka/php-parallel-lint": "0.8.*" }, "suggest": { - "phpunit/phpunit": "PHP unit testing framework", + "block8/php-docblock-checker": "PHP Docblock Checker", "phpmd/phpmd": "PHP Mess Detector", "sebastian/phpcpd": "PHP Copy/Paste Detector", "squizlabs/php_codesniffer": "PHP Code Sniffer", "phpspec/phpspec": "PHP Spec", - "fabpot/php-cs-fixer": "PHP Code Sniffer Fixer", + "fabpot/php-cs-fixer": "PHP Coding Standards Fixer", "phploc/phploc": "PHP Lines of Code", "atoum/atoum": "Atoum", "jakub-onderka/php-parallel-lint": "Parallel Linting Tool", - "behat/behat": "Behat BDD Testing" + "behat/behat": "Behat BDD Testing", + "phptal/phptal": "PHPTAL templating engine" } } diff --git a/composer.lock b/composer.lock index 4921df46..9714ff78 100644 --- a/composer.lock +++ b/composer.lock @@ -1,22 +1,24 @@ { "_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" + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" ], - "hash": "04cca0ac809838a65555d04534cc95ae", + "hash": "de65276e03e231d7072c744a3c63662e", + "content-hash": "1d9f6f487e6d906bbed73e2667c276d6", "packages": [ { "name": "block8/b8framework", - "version": "1.0.1", + "version": "1.1.10", "source": { "type": "git", "url": "https://github.com/Block8/b8framework.git", - "reference": "0497ae34ba7ef828db23b35a1f75d4debfa7eed5" + "reference": "5d2d2863ce15a6b91e0b2aed4250dd29c6224446" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Block8/b8framework/zipball/0497ae34ba7ef828db23b35a1f75d4debfa7eed5", - "reference": "0497ae34ba7ef828db23b35a1f75d4debfa7eed5", + "url": "https://api.github.com/repos/Block8/b8framework/zipball/5d2d2863ce15a6b91e0b2aed4250dd29c6224446", + "reference": "5d2d2863ce15a6b91e0b2aed4250dd29c6224446", "shasum": "" }, "require": { @@ -50,22 +52,333 @@ "mvc", "php" ], - "time": "2013-10-09 14:06:12" + "time": "2015-10-05 10:50:20" }, { - "name": "ircmaxell/password-compat", - "version": "1.0.3", + "name": "guzzle/guzzle", + "version": "v3.9.3", "source": { "type": "git", - "url": "https://github.com/ircmaxell/password_compat.git", - "reference": "1fc1521b5e9794ea77e4eca30717be9635f1d4f4" + "url": "https://github.com/guzzle/guzzle3.git", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/1fc1521b5e9794ea77e4eca30717be9635f1d4f4", - "reference": "1fc1521b5e9794ea77e4eca30717be9635f1d4f4", + "url": "https://api.github.com/repos/guzzle/guzzle3/zipball/0645b70d953bc1c067bbc8d5bc53194706b628d9", + "reference": "0645b70d953bc1c067bbc8d5bc53194706b628d9", "shasum": "" }, + "require": { + "ext-curl": "*", + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1" + }, + "replace": { + "guzzle/batch": "self.version", + "guzzle/cache": "self.version", + "guzzle/common": "self.version", + "guzzle/http": "self.version", + "guzzle/inflection": "self.version", + "guzzle/iterator": "self.version", + "guzzle/log": "self.version", + "guzzle/parser": "self.version", + "guzzle/plugin": "self.version", + "guzzle/plugin-async": "self.version", + "guzzle/plugin-backoff": "self.version", + "guzzle/plugin-cache": "self.version", + "guzzle/plugin-cookie": "self.version", + "guzzle/plugin-curlauth": "self.version", + "guzzle/plugin-error-response": "self.version", + "guzzle/plugin-history": "self.version", + "guzzle/plugin-log": "self.version", + "guzzle/plugin-md5": "self.version", + "guzzle/plugin-mock": "self.version", + "guzzle/plugin-oauth": "self.version", + "guzzle/service": "self.version", + "guzzle/stream": "self.version" + }, + "require-dev": { + "doctrine/cache": "~1.3", + "monolog/monolog": "~1.0", + "phpunit/phpunit": "3.7.*", + "psr/log": "~1.0", + "symfony/class-loader": "~2.1", + "zendframework/zend-cache": "2.*,<2.3", + "zendframework/zend-log": "2.*,<2.3" + }, + "suggest": { + "guzzlehttp/guzzle": "Guzzle 5 has moved to a new package name. The package you have installed, Guzzle 3, is deprecated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.9-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle": "src/", + "Guzzle\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Guzzle Community", + "homepage": "https://github.com/guzzle/guzzle/contributors" + } + ], + "description": "PHP HTTP client. This library is deprecated in favor of https://packagist.org/packages/guzzlehttp/guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2015-03-18 18:23:50" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "d094e337976dff9d8e2424e8485872194e768662" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662", + "reference": "d094e337976dff9d8e2424e8485872194e768662", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "~1.0", + "guzzlehttp/psr7": "~1.1", + "php": ">=5.5.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "~4.0", + "psr/log": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.2-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2016-03-21 20:02:09" + }, + { + "name": "guzzlehttp/promises", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bb9024c526b22f3fe6ae55a561fd70653d470aa8", + "reference": "bb9024c526b22f3fe6ae55a561fd70653d470aa8", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-03-08 01:15:46" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "31382fef2889136415751badebbd1cb022a4ed72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/31382fef2889136415751badebbd1cb022a4ed72", + "reference": "31382fef2889136415751badebbd1cb022a4ed72", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "PSR-7 message implementation", + "keywords": [ + "http", + "message", + "stream", + "uri" + ], + "time": "2016-04-13 19:56:01" + }, + { + "name": "hipchat/hipchat-php", + "version": "v1.4", + "source": { + "type": "git", + "url": "https://github.com/hipchat/hipchat-php.git", + "reference": "5936c0a48d2d514d94bfc1d774b04c42cd3bc39e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hipchat/hipchat-php/zipball/5936c0a48d2d514d94bfc1d774b04c42cd3bc39e", + "reference": "5936c0a48d2d514d94bfc1d774b04c42cd3bc39e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "HipChat": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "HipChat", + "email": "support@hipchat.com", + "homepage": "https://www.hipchat.com", + "role": "Company" + } + ], + "description": "PHP library for HipChat", + "homepage": "http://github.com/hipchat/hipchat-php", + "keywords": [ + "hipchat" + ], + "time": "2015-04-28 22:48:40" + }, + { + "name": "ircmaxell/password-compat", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/ircmaxell/password_compat.git", + "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/5c5cde8822a69545767f7c7f3058cb15ff84614c", + "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, "type": "library", "autoload": { "files": [ @@ -89,29 +402,496 @@ "hashing", "password" ], - "time": "2013-04-30 19:58:08" + "time": "2014-11-20 16:49:30" }, { - "name": "swiftmailer/swiftmailer", - "version": "v5.0.2", + "name": "maknz/slack", + "version": "1.7.0", "source": { "type": "git", - "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "f3917ecef35a4e4d98b303eb9fee463bc983f379" + "url": "https://github.com/maknz/slack.git", + "reference": "7f21fefc70c76b304adc1b3a780c8740dfcfb595" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/f3917ecef35a4e4d98b303eb9fee463bc983f379", - "reference": "f3917ecef35a4e4d98b303eb9fee463bc983f379", + "url": "https://api.github.com/repos/maknz/slack/zipball/7f21fefc70c76b304adc1b3a780c8740dfcfb595", + "reference": "7f21fefc70c76b304adc1b3a780c8740dfcfb595", "shasum": "" }, "require": { - "php": ">=5.2.4" + "ext-mbstring": "*", + "guzzlehttp/guzzle": "~6.0|~5.0|~4.0", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "0.9.*", + "phpunit/phpunit": "4.2.*" + }, + "suggest": { + "illuminate/support": "Required for Laravel support" + }, + "type": "library", + "autoload": { + "psr-4": { + "Maknz\\Slack\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "maknz", + "email": "github@mak.geek.nz" + } + ], + "description": "A simple PHP package for sending messages to Slack, with a focus on ease of use and elegant syntax. Includes Laravel support out of the box.", + "keywords": [ + "laravel", + "slack" + ], + "time": "2015-06-03 03:35:16" + }, + { + "name": "monolog/monolog", + "version": "1.19.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "5f56ed5212dc509c8dc8caeba2715732abb32dbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/5f56ed5212dc509c8dc8caeba2715732abb32dbf", + "reference": "5f56ed5212dc509c8dc8caeba2715732abb32dbf", + "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.9", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "raven/raven": "^0.13", + "ruflin/elastica": ">=0.90 <3.0", + "swiftmailer/swiftmailer": "~5.3" + }, + "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", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "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" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-master": "2.0.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": "2016-04-12 18:29:35" + }, + { + "name": "mremi/flowdock", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/mremi/Flowdock.git", + "reference": "e34ca78bbd4f974366e979186e876d78ae488e51" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mremi/Flowdock/zipball/e34ca78bbd4f974366e979186e876d78ae488e51", + "reference": "e34ca78bbd4f974366e979186e876d78ae488e51", + "shasum": "" + }, + "require": { + "guzzle/guzzle": "~3.7", + "php": ">=5.3.3", + "symfony/console": "~2.5" + }, + "require-dev": { + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mremi\\Flowdock": "src", + "Mremi\\Flowdock\\Tests": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rémi Marseille", + "email": "marseille.remi@gmail.com" + } + ], + "description": "A PHP5 library to interact with the Flowdock API", + "homepage": "https://github.com/mremi/Flowdock", + "keywords": [ + "api", + "flowdock" + ], + "time": "2015-01-08 21:03:29" + }, + { + "name": "pda/pheanstalk", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/pda/pheanstalk.git", + "reference": "430e77c551479aad0c6ada0450ee844cf656a18b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pda/pheanstalk/zipball/430e77c551479aad0c6ada0450ee844cf656a18b", + "reference": "430e77c551479aad0c6ada0450ee844cf656a18b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Pheanstalk\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Annesley", + "email": "paul@annesley.cc", + "homepage": "http://paul.annesley.cc/", + "role": "Developer" + } + ], + "description": "PHP client for beanstalkd queue", + "homepage": "https://github.com/pda/pheanstalk", + "keywords": [ + "beanstalkd" + ], + "time": "2015-08-07 21:42:41" + }, + { + "name": "pimple/pimple", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/2019c145fe393923f3441b23f29bbdfaa5c58c4d", + "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "Pimple is a simple Dependency Injection Container for PHP 5.3", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ], + "time": "2013-11-22 08:30:29" + }, + { + "name": "psr/http-message", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", + "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2015-05-04 20:22:00" + }, + { + "name": "psr/log", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "shasum": "" + }, + "type": "library", + "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": "2012-12-21 11:40:51" + }, + { + "name": "robmorgan/phinx", + "version": "v0.5.3", + "source": { + "type": "git", + "url": "https://github.com/robmorgan/phinx.git", + "reference": "4e7fee7792f4bf3dbf55ee29001850ba26c86a88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/robmorgan/phinx/zipball/4e7fee7792f4bf3dbf55ee29001850ba26c86a88", + "reference": "4e7fee7792f4bf3dbf55ee29001850ba26c86a88", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "symfony/config": "~2.8|~3.0", + "symfony/console": "~2.8|~3.0", + "symfony/yaml": "~2.8|~3.0" + }, + "require-dev": { + "phpunit/phpunit": "^3.7|^4.0|^5.0" + }, + "bin": [ + "bin/phinx" + ], + "type": "library", + "autoload": { + "psr-4": { + "Phinx\\": "src/Phinx" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rob Morgan", + "email": "robbym@gmail.com", + "homepage": "http://robmorgan.id.au", + "role": "Lead Developer" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "homepage": "http://shadowhand.me", + "role": "Developer" + } + ], + "description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.", + "homepage": "https://phinx.org", + "keywords": [ + "database", + "database migrations", + "db", + "migrations", + "phinx" + ], + "time": "2016-03-07 14:09:22" + }, + { + "name": "sensiolabs/ansi-to-html", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/sensiolabs/ansi-to-html.git", + "reference": "02598b975c510e9e7d07d0be0a89c7a6b43464d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sensiolabs/ansi-to-html/zipball/02598b975c510e9e7d07d0be0a89c7a6b43464d6", + "reference": "02598b975c510e9e7d07d0be0a89c7a6b43464d6", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "suggest": { + "twig/twig": "Provides nice templating features" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-0": { + "SensioLabs\\AnsiConverter": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A library to convert a text with ANSI codes to HTML", + "time": "2015-07-22 03:07:58" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v5.4.1", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "0697e6aa65c83edf97bb0f23d8763f94e3f11421" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/0697e6aa65c83edf97bb0f23d8763f94e3f11421", + "reference": "0697e6aa65c83edf97bb0f23d8763f94e3f11421", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "mockery/mockery": "~0.9.1,<0.9.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.4-dev" } }, "autoload": { @@ -125,54 +905,583 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Chris Corbyn" }, { - "name": "Chris Corbyn" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" } ], "description": "Swiftmailer, free feature-rich PHP mailer", "homepage": "http://swiftmailer.org", "keywords": [ + "email", "mail", "mailer" ], - "time": "2013-08-30 12:35:21" + "time": "2015-06-06 14:19:39" }, { - "name": "symfony/console", - "version": "v2.3.5", - "target-dir": "Symfony/Component/Console", + "name": "symfony/config", + "version": "v3.0.4", "source": { "type": "git", - "url": "https://github.com/symfony/Console.git", - "reference": "f880062d56edefb25b36f2defa65aafe65959dc7" + "url": "https://github.com/symfony/config.git", + "reference": "980ee40c28f00acff8906c11b778aab5f0db74c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Console/zipball/f880062d56edefb25b36f2defa65aafe65959dc7", - "reference": "f880062d56edefb25b36f2defa65aafe65959dc7", + "url": "https://api.github.com/repos/symfony/config/zipball/980ee40c28f00acff8906c11b778aab5f0db74c2", + "reference": "980ee40c28f00acff8906c11b778aab5f0db74c2", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/filesystem": "~2.8|~3.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2016-03-04 07:55:57" + }, + { + "name": "symfony/console", + "version": "v2.8.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "9a5aef5fc0d4eff86853d44202b02be8d5a20154" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/9a5aef5fc0d4eff86853d44202b02be8d5a20154", + "reference": "9a5aef5fc0d4eff86853d44202b02be8d5a20154", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1|~3.0.0", + "symfony/process": "~2.1|~3.0.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2016-03-17 09:19:04" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "47d2d8cade9b1c3987573d2943bb9352536cdb87" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/47d2d8cade9b1c3987573d2943bb9352536cdb87", + "reference": "47d2d8cade9b1c3987573d2943bb9352536cdb87", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.0,>=2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2016-03-07 14:04:32" + }, + { + "name": "symfony/filesystem", + "version": "v3.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "f82499a459dcade2ea56df94cc58b19c8bde3d20" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/f82499a459dcade2ea56df94cc58b19c8bde3d20", + "reference": "f82499a459dcade2ea56df94cc58b19c8bde3d20", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2016-03-27 10:24:39" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "1289d16209491b584839022f29257ad859b8532d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/1289d16209491b584839022f29257ad859b8532d", + "reference": "1289d16209491b584839022f29257ad859b8532d", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2016-01-20 09:13:37" + }, + { + "name": "symfony/yaml", + "version": "v2.8.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "584e52cb8f788a887553ba82db6caacb1d6260bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/584e52cb8f788a887553ba82db6caacb1d6260bb", + "reference": "584e52cb8f788a887553ba82db6caacb1d6260bb", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2016-03-04 07:54:35" + } + ], + "packages-dev": [ + { + "name": "block8/php-docblock-checker", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/Block8/php-docblock-checker.git", + "reference": "1f0a4e6b7e2487b1f4891bb50ffe74eaa9de89e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Block8/php-docblock-checker/zipball/1f0a4e6b7e2487b1f4891bb50ffe74eaa9de89e3", + "reference": "1f0a4e6b7e2487b1f4891bb50ffe74eaa9de89e3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-token-stream": "1.*", + "symfony/console": "~2.1" + }, + "require-dev": { + "phploc/phploc": "~2.0", + "phpmd/phpmd": "~2.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "bin": [ + "phpdoccheck" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Dan Cryer", + "email": "dan.cryer@block8.co.uk", + "homepage": "http://www.block8.co.uk", + "role": "Developer" + } + ], + "description": "A simple tool for checking that your PHP classes and methods use docblocks.", + "homepage": "https://www.phptesting.org/", + "keywords": [ + "checker", + "code quality", + "comment", + "docblock", + "php", + "phpci", + "testing" + ], + "time": "2014-10-23 13:03:24" + }, + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "jakub-onderka/php-parallel-lint", + "version": "v0.8", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Parallel-Lint.git", + "reference": "2b242dcdbdd7369d2a746518ac31bb30c514b728" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Parallel-Lint/zipball/2b242dcdbdd7369d2a746518ac31bb30c514b728", + "reference": "2b242dcdbdd7369d2a746518ac31bb30c514b728", "shasum": "" }, "require": { "php": ">=5.3.3" }, "require-dev": { - "symfony/event-dispatcher": "~2.1" + "jakub-onderka/php-console-highlighter": "~0.3", + "nette/tester": "~1.3" }, "suggest": { - "symfony/event-dispatcher": "" + "jakub-onderka/php-console-highlighter": "Highlight syntax in code snippet" + }, + "bin": [ + "parallel-lint" + ], + "type": "library", + "autoload": { + "classmap": [ + "./" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "jakub.onderka@gmail.com" + } + ], + "description": "This tool check syntax of PHP files about 20x faster than serial check.", + "homepage": "https://github.com/JakubOnderka/PHP-Parallel-Lint", + "time": "2014-10-05 10:19:39" + }, + { + "name": "pdepend/pdepend", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/pdepend/pdepend.git", + "reference": "b086687f3a01dc6bb92d633aef071d2c5dd0db06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/b086687f3a01dc6bb92d633aef071d2c5dd0db06", + "reference": "b086687f3a01dc6bb92d633aef071d2c5dd0db06", + "shasum": "" + }, + "require": { + "php": ">=5.3.7", + "symfony/config": "^2.3.0|^3", + "symfony/dependency-injection": "^2.3.0|^3", + "symfony/filesystem": "^2.3.0|^3" + }, + "require-dev": { + "phpunit/phpunit": "^4.4.0,<4.8", + "squizlabs/php_codesniffer": "^2.0.0" + }, + "bin": [ + "src/bin/pdepend" + ], + "type": "library", + "autoload": { + "psr-4": { + "PDepend\\": "src/main/php/PDepend" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Official version of pdepend to be handled with Composer", + "time": "2016-03-10 15:15:04" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-0": { - "Symfony\\Component\\Console\\": "" + "phpDocumentor": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", @@ -181,31 +1490,266 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" } ], - "description": "Symfony Console Component", - "homepage": "http://symfony.com", - "time": "2013-09-25 06:04:15" + "time": "2015-02-03 12:10:50" }, { - "name": "symfony/yaml", - "version": "v2.3.5", - "target-dir": "Symfony/Component/Yaml", + "name": "phploc/phploc", + "version": "2.1.5", "source": { "type": "git", - "url": "https://github.com/symfony/Yaml.git", - "reference": "6bb881b948368482e1abf1a75c08bcf88a1c5fc3" + "url": "https://github.com/sebastianbergmann/phploc.git", + "reference": "50e063abd41833b3a5d29a2e8fbef5859ac28bdc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/6bb881b948368482e1abf1a75c08bcf88a1c5fc3", - "reference": "6bb881b948368482e1abf1a75c08bcf88a1c5fc3", + "url": "https://api.github.com/repos/sebastianbergmann/phploc/zipball/50e063abd41833b3a5d29a2e8fbef5859ac28bdc", + "reference": "50e063abd41833b3a5d29a2e8fbef5859ac28bdc", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/finder-facade": "~1.1", + "sebastian/git": "~2.0", + "sebastian/version": "~1.0.3", + "symfony/console": "~2.5" + }, + "require-dev": { + "phpunit/phpunit": "~4" + }, + "bin": [ + "phploc" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "A tool for quickly measuring the size of a PHP project.", + "homepage": "https://github.com/sebastianbergmann/phploc", + "time": "2015-10-22 13:44:19" + }, + { + "name": "phpmd/phpmd", + "version": "2.4.3", + "source": { + "type": "git", + "url": "https://github.com/phpmd/phpmd.git", + "reference": "2b9c2417a18696dfb578b38c116cd0ddc19b256e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpmd/phpmd/zipball/2b9c2417a18696dfb578b38c116cd0ddc19b256e", + "reference": "2b9c2417a18696dfb578b38c116cd0ddc19b256e", + "shasum": "" + }, + "require": { + "pdepend/pdepend": "^2.0.4", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0", + "squizlabs/php_codesniffer": "^2.0" + }, + "bin": [ + "src/bin/phpmd" + ], + "type": "project", + "autoload": { + "psr-0": { + "PHPMD\\": "src/main/php" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Manuel Pichler", + "email": "github@manuel-pichler.de", + "homepage": "https://github.com/manuelpichler", + "role": "Project Founder" + }, + { + "name": "Other contributors", + "homepage": "https://github.com/phpmd/phpmd/graphs/contributors", + "role": "Contributors" + }, + { + "name": "Marc Würth", + "email": "ravage@bluewin.ch", + "homepage": "https://github.com/ravage84", + "role": "Project Maintainer" + } + ], + "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.", + "homepage": "http://phpmd.org/", + "keywords": [ + "mess detection", + "mess detector", + "pdepend", + "phpmd", + "pmd" + ], + "time": "2016-04-04 11:52:04" + }, + { + "name": "phpspec/prophecy", + "version": "v1.6.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972", + "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "~2.0", + "sebastian/comparator": "~1.1", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "phpspec/phpspec": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2016-02-15 07:46:21" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-10-06 15:47:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", "shasum": "" }, "require": { @@ -214,13 +1758,865 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "1.4.x-dev" } }, "autoload": { - "psr-0": { - "Symfony\\Component\\Yaml\\": "" + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-06-21 13:08:43" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2015-06-21 08:01:12" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2015-09-15 10:49:45" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.24", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "a1066c562c52900a142a0e2bbf0582994671385e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1066c562c52900a142a0e2bbf0582994671385e", + "reference": "a1066c562c52900a142a0e2bbf0582994671385e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": ">=1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.1", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2016-03-14 06:16:08" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2015-10-02 06:51:40" + }, + { + "name": "sebastian/comparator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2015-07-26 15:48:44" + }, + { + "name": "sebastian/diff", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2015-12-08 07:14:41" + }, + { + "name": "sebastian/environment", + "version": "1.3.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", + "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-02-26 18:40:46" + }, + { + "name": "sebastian/exporter", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "7ae5513327cb536431847bcc0c10edba2701064e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e", + "reference": "7ae5513327cb536431847bcc0c10edba2701064e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2015-06-21 07:55:53" + }, + { + "name": "sebastian/finder-facade", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/finder-facade.git", + "reference": "2a6f7f57efc0aa2d23297d9fd9e2a03111a8c0b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/finder-facade/zipball/2a6f7f57efc0aa2d23297d9fd9e2a03111a8c0b9", + "reference": "2a6f7f57efc0aa2d23297d9fd9e2a03111a8c0b9", + "shasum": "" + }, + "require": { + "symfony/finder": "~2.3|~3.0", + "theseer/fdomdocument": "~1.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", + "homepage": "https://github.com/sebastianbergmann/finder-facade", + "time": "2016-02-17 07:02:23" + }, + { + "name": "sebastian/git", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/git.git", + "reference": "38638de3e94830a5cd7a5956135589b967609cd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/git/zipball/38638de3e94830a5cd7a5956135589b967609cd5", + "reference": "38638de3e94830a5cd7a5956135589b967609cd5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Simple wrapper for Git", + "homepage": "http://www.github.com/sebastianbergmann/git", + "keywords": [ + "git" + ], + "time": "2016-02-21 15:02:23" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12 03:26:01" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2015-11-11 19:50:13" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21 13:59:46" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.6.0", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "1bcdf03b068a530ac1962ce671dead356eeba43b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1bcdf03b068a530ac1962ce671dead356eeba43b", + "reference": "1bcdf03b068a530ac1962ce671dead356eeba43b", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2016-04-03 22:58:34" + }, + { + "name": "symfony/dependency-injection", + "version": "v3.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "6a9058101b591edced21ca3c83c80a3978f5c6b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/6a9058101b591edced21ca3c83c80a3978f5c6b0", + "reference": "6a9058101b591edced21ca3c83c80a3978f5c6b0", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "require-dev": { + "symfony/config": "~2.8|~3.0", + "symfony/expression-language": "~2.8|~3.0", + "symfony/yaml": "~2.8|~3.0" + }, + "suggest": { + "symfony/config": "", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -233,28 +2629,112 @@ }, { "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" + "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Yaml Component", - "homepage": "http://symfony.com", - "time": "2013-09-22 18:04:39" + "description": "Symfony DependencyInjection Component", + "homepage": "https://symfony.com", + "time": "2016-03-30 10:41:14" + }, + { + "name": "symfony/finder", + "version": "v3.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "c54e407b35bc098916704e9fd090da21da4c4f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/c54e407b35bc098916704e9fd090da21da4c4f52", + "reference": "c54e407b35bc098916704e9fd090da21da4c4f52", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2016-03-10 11:13:05" + }, + { + "name": "theseer/fdomdocument", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/fDOMDocument.git", + "reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/d9ad139d6c2e8edf5e313ffbe37ff13344cf0684", + "reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "lib-libxml": "*", + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "lead" + } + ], + "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.", + "homepage": "https://github.com/theseer/fDOMDocument", + "time": "2015-05-27 22:58:02" } ], - "packages-dev": [ - - ], - "aliases": [ - - ], + "aliases": [], "minimum-stability": "stable", - "stability-flags": [ - - ], - "platform": [ - - ], - "platform-dev": [ - - ] + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.3.8", + "ext-pdo": "*", + "ext-pdo_mysql": "*" + }, + "platform-dev": [] } diff --git a/console b/console index 3a56e7cd..992462e5 100755 --- a/console +++ b/console @@ -13,16 +13,32 @@ define('PHPCI_IS_CONSOLE', true); require('bootstrap.php'); use PHPCI\Command\RunCommand; +use PHPCI\Command\RebuildCommand; use PHPCI\Command\GenerateCommand; use PHPCI\Command\UpdateCommand; use PHPCI\Command\InstallCommand; use PHPCI\Command\DaemonCommand; +use PHPCI\Command\PollCommand; +use PHPCI\Command\CreateAdminCommand; +use PHPCI\Command\CreateBuildCommand; +use PHPCI\Command\WorkerCommand; +use PHPCI\Command\RebuildQueueCommand; +use PHPCI\Service\BuildService; use Symfony\Component\Console\Application; +use b8\Store\Factory; $application = new Application(); -$application->add(new RunCommand); + +$application->add(new RunCommand($loggerConfig->getFor('RunCommand'))); +$application->add(new RebuildCommand($loggerConfig->getFor('RunCommand'))); $application->add(new InstallCommand); -$application->add(new UpdateCommand); +$application->add(new UpdateCommand($loggerConfig->getFor('UpdateCommand'))); $application->add(new GenerateCommand); -$application->add(new DaemonCommand); +$application->add(new DaemonCommand($loggerConfig->getFor('DaemonCommand'))); +$application->add(new PollCommand($loggerConfig->getFor('PollCommand'))); +$application->add(new CreateAdminCommand(Factory::getStore('User'))); +$application->add(new CreateBuildCommand(Factory::getStore('Project'), new BuildService(Factory::getStore('Build')))); +$application->add(new WorkerCommand($loggerConfig->getFor('WorkerCommand'))); +$application->add(new RebuildQueueCommand($loggerConfig->getFor('RebuildQueueCommand'))); + $application->run(); diff --git a/console.bat b/console.bat new file mode 100644 index 00000000..391a2617 --- /dev/null +++ b/console.bat @@ -0,0 +1 @@ +@php console %* \ No newline at end of file diff --git a/daemon/.gitignore b/daemon/.gitignore index c96a04f0..d6b7ef32 100644 --- a/daemon/.gitignore +++ b/daemon/.gitignore @@ -1,2 +1,2 @@ * -!.gitignore \ No newline at end of file +!.gitignore diff --git a/daemonise b/daemonise index 0f1a6d1d..c6238568 100755 --- a/daemonise +++ b/daemonise @@ -16,5 +16,5 @@ use PHPCI\Command\DaemoniseCommand; use Symfony\Component\Console\Application; $application = new Application(); -$application->add(new DaemoniseCommand); +$application->add(new DaemoniseCommand($loggerConfig->getFor('DaemoniseCommand'))); $application->run(); diff --git a/loggerconfig.php.example b/loggerconfig.php.example new file mode 100644 index 00000000..ffa22204 --- /dev/null +++ b/loggerconfig.php.example @@ -0,0 +1,24 @@ + function() { + return array( + new \Monolog\Handler\StreamHandler(__DIR__ . DIRECTORY_SEPARATOR . 'errors.log', \Monolog\Logger::ERROR), + ); + }, + /** Loggers for the RunCommand */ + 'RunCommand' => function() { + return array( + new \Monolog\Handler\RotatingFileHandler(__DIR__ . DIRECTORY_SEPARATOR . 'everything',3, \Monolog\Logger::DEBUG), + ); + }, +); diff --git a/phinx.php b/phinx.php new file mode 100644 index 00000000..d64749dd --- /dev/null +++ b/phinx.php @@ -0,0 +1,37 @@ +get('b8.database.servers.write'); + +if (!is_array($writeServers)) { + $writeServers = array($writeServers); +} + +$conf = array( + 'paths' => array( + 'migrations' => 'PHPCI/Migrations', + ), + + 'environments' => array( + 'default_migration_table' => 'migration', + 'default_database' => 'phpci', + 'phpci' => array( + 'adapter' => 'mysql', + 'host' => end($writeServers), + 'name' => $config->get('b8.database.name'), + 'user' => $config->get('b8.database.username'), + 'pass' => $config->get('b8.database.password'), + ), + ), +); + +return $conf; diff --git a/phpci.yml b/phpci.yml deleted file mode 100644 index 42b2dba3..00000000 --- a/phpci.yml +++ /dev/null @@ -1,26 +0,0 @@ -build_settings: - verbose: false - ignore: - - "vendor" - - "Tests" - - "PHPCI/Command" # PHPMD complains about un-used parameters, but they are required. - - "public/install.php" # PHPCS really doesn't like PHP mixed with HTML (and so it shouldn't) - irc: - server: "irc.freenode.net" - port: 6667 - room: "#phpci" - nick: "phpcidev" - -test: - php_mess_detector: - php_code_sniffer: - standard: "PSR2" - php_loc: - -success: - irc: - message: "Build Success. %PROJECT_TITLE% - %COMMIT% - %BUILD_URI%" - -failure: - irc: - message: "Build Failed. %PROJECT_TITLE% - %COMMIT% - %BUILD_URI%" \ No newline at end of file diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 00000000..a392a201 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,19 @@ + + + + Codestyle ruleset for PHPCI + + + + PHPCI + + + + + PHPCI/Migrations/* + PHPCI/Model/Base/* + PHPCI/Languages/* + Tests/* + vendor/* + + diff --git a/phpmd.xml b/phpmd.xml new file mode 100644 index 00000000..69b822fd --- /dev/null +++ b/phpmd.xml @@ -0,0 +1,17 @@ + + + + PHPCI rule set + + + + + + + diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 00000000..fdddf34d --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,33 @@ + + + + + + ./Tests/PHPCI/Command + + + ./Tests/PHPCI/Helper + + + ./Tests/PHPCI/Logging + + + ./Tests/PHPCI/Plugin + + + ./Tests/PHPCI/Model + + + ./Tests/PHPCI/Service + + + diff --git a/pluginconfig.php.example b/pluginconfig.php.example new file mode 100644 index 00000000..5c182401 --- /dev/null +++ b/pluginconfig.php.example @@ -0,0 +1,22 @@ +registerResource( + // This function will be called when the resource is needed. + function() { + return array( + 'Foo' => "Stuff", + 'Bar' => "More Stuff" + ); + }, + + // In addition to the function for building the resource the system + // also needs to be told when to load the resource. Either or both + // of the following arguments can be used (null to ignore) + + // This resource will only be given when the argument name is: + "ResourceArray", + + // The resource will only be given when the type hint is: + PHPCI\Plugin\Util\Factory::TYPE_ARRAY + ); +}; diff --git a/public/.htaccess.dist b/public/.htaccess.dist new file mode 100644 index 00000000..ef528060 --- /dev/null +++ b/public/.htaccess.dist @@ -0,0 +1,7 @@ + + RewriteEngine On + RewriteBase / + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule . /index.php [L] + diff --git a/public/assets/css/AdminLTE-custom.css b/public/assets/css/AdminLTE-custom.css new file mode 100644 index 00000000..ff75c31a --- /dev/null +++ b/public/assets/css/AdminLTE-custom.css @@ -0,0 +1,84 @@ +.phpci .main-header .logo, .phpci .main-header .logo:hover { + background-image: url('/assets/img/logo-large.png'); + background-repeat: no-repeat; + background-size: 40%; + background-position: 65px; + text-indent: -5000px; +} + +.build-info-panel { + +} + + .build-info-panel .box-header h1.box-title { + border: 0; + font-size: 1.5em; + font-weight: bold; + margin-left: 110px; + } + + .build-info-panel h1.box-title span { + font-weight: normal; + } + + .build-info-panel img { + border: 2px solid #fff; + border-radius: 50%; + margin-top: -40px; + } + + .build-info-panel #build-info { + margin-left: 110px; + min-height: 50px; + } + + .build-info-panel .commit-message { + margin-bottom: 20px; + } + +.small-box h3 a, .small-box h4 a { + color: #fff; +} + +.pagination>li>span { + font-weight: bold; + background: #337ab7; + color: #fff; +} + +#plugins table td { + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; +} + +.small-box > .inner { + border-bottom: 1px solid rgba(0, 0, 0, 0.15); +} + +.small-box > .small-box-footer-project { + width: 60%; + float: left; +} +.small-box > .small-box-footer-build { + width: 8%; + float: left; +} +.small-box-minimal > .inner { + width: 71%; + float: left; +} +.small-box-minimal > .small-box-footer-build { + width: 5%; + border: 1px solid rgba(0, 0, 0, 0.15); + margin-left: 1px; + margin-top: 19px; + font-size: 11px; +} + +#phpunit-data th div { margin: 0 0.5em; } +#phpunit-data .success td { background: none; color: #00a65a; } +#phpunit-data .fail td { background: none; color: #f56954; } +#phpunit-data .error td { background: none; color: #f56954; } +#phpunit-data .skipped td { background: none; color: #e08e0b; } +#phpunit-data .todo td { background: none; color: #00c0ef; } diff --git a/public/assets/css/AdminLTE-skins.min.css b/public/assets/css/AdminLTE-skins.min.css new file mode 100644 index 00000000..1710db5c --- /dev/null +++ b/public/assets/css/AdminLTE-skins.min.css @@ -0,0 +1 @@ +.skin-blue .main-header .navbar{background-color:#3c8dbc}.skin-blue .main-header .navbar .nav>li>a{color:#fff}.skin-blue .main-header .navbar .nav>li>a:hover,.skin-blue .main-header .navbar .nav>li>a:active,.skin-blue .main-header .navbar .nav>li>a:focus,.skin-blue .main-header .navbar .nav .open>a,.skin-blue .main-header .navbar .nav .open>a:hover,.skin-blue .main-header .navbar .nav .open>a:focus,.skin-blue .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-blue .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-blue .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-blue .main-header .navbar .dropdown-menu li a{color:#fff}.skin-blue .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-blue .main-header .logo{background-color:#367fa9;color:#fff;border-bottom:0 solid transparent}.skin-blue .main-header .logo:hover{background-color:#357ca5}.skin-blue .main-header li.user-header{background-color:#3c8dbc}.skin-blue .content-header{background:transparent}.skin-blue .wrapper,.skin-blue .main-sidebar,.skin-blue .left-side{background-color:#222d32}.skin-blue .user-panel>.info,.skin-blue .user-panel>.info>a{color:#fff}.skin-blue .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-blue .sidebar-menu>li>a{border-left:3px solid transparent}.skin-blue .sidebar-menu>li:hover>a,.skin-blue .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#3c8dbc}.skin-blue .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-blue .sidebar a{color:#b8c7ce}.skin-blue .sidebar a:hover{text-decoration:none}.skin-blue .treeview-menu>li>a{color:#8aa4af}.skin-blue .treeview-menu>li.active>a,.skin-blue .treeview-menu>li>a:hover{color:#fff}.skin-blue .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-blue .sidebar-form input[type="text"],.skin-blue .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-blue .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-blue .sidebar-form input[type="text"]:focus,.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-blue .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-blue .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8}.skin-blue-light .main-header .navbar{background-color:#3c8dbc}.skin-blue-light .main-header .navbar .nav>li>a{color:#fff}.skin-blue-light .main-header .navbar .nav>li>a:hover,.skin-blue-light .main-header .navbar .nav>li>a:active,.skin-blue-light .main-header .navbar .nav>li>a:focus,.skin-blue-light .main-header .navbar .nav .open>a,.skin-blue-light .main-header .navbar .nav .open>a:hover,.skin-blue-light .main-header .navbar .nav .open>a:focus,.skin-blue-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-blue-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-blue-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-blue-light .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-blue-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-blue-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-blue-light .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-blue-light .main-header .logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue-light .main-header .logo:hover{background-color:#3b8ab8}.skin-blue-light .main-header li.user-header{background-color:#3c8dbc}.skin-blue-light .content-header{background:transparent}.skin-blue-light .wrapper,.skin-blue-light .main-sidebar,.skin-blue-light .left-side{background-color:#f9fafc}.skin-blue-light .content-wrapper,.skin-blue-light .main-footer{border-left:1px solid #d2d6de}.skin-blue-light .user-panel>.info,.skin-blue-light .user-panel>.info>a{color:#444}.skin-blue-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-blue-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-blue-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-blue-light .sidebar-menu>li:hover>a,.skin-blue-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-blue-light .sidebar-menu>li.active{border-left-color:#3c8dbc}.skin-blue-light .sidebar-menu>li.active>a{font-weight:600}.skin-blue-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-blue-light .sidebar a{color:#444}.skin-blue-light .sidebar a:hover{text-decoration:none}.skin-blue-light .treeview-menu>li>a{color:#777}.skin-blue-light .treeview-menu>li.active>a,.skin-blue-light .treeview-menu>li>a:hover{color:#000}.skin-blue-light .treeview-menu>li.active>a{font-weight:600}.skin-blue-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-blue-light .sidebar-form input[type="text"],.skin-blue-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-blue-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-blue-light .sidebar-form input[type="text"]:focus,.skin-blue-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-blue-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-blue-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-blue-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-blue-light .main-footer{border-top-color:#d2d6de}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8}.skin-black .main-header{-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.skin-black .main-header .navbar-toggle{color:#333}.skin-black .main-header .navbar-brand{color:#333;border-right:1px solid #eee}.skin-black .main-header>.navbar{background-color:#fff}.skin-black .main-header>.navbar .nav>li>a{color:#333}.skin-black .main-header>.navbar .nav>li>a:hover,.skin-black .main-header>.navbar .nav>li>a:active,.skin-black .main-header>.navbar .nav>li>a:focus,.skin-black .main-header>.navbar .nav .open>a,.skin-black .main-header>.navbar .nav .open>a:hover,.skin-black .main-header>.navbar .nav .open>a:focus,.skin-black .main-header>.navbar .nav>.active>a{background:#fff;color:#999}.skin-black .main-header>.navbar .sidebar-toggle{color:#333}.skin-black .main-header>.navbar .sidebar-toggle:hover{color:#999;background:#fff}.skin-black .main-header>.navbar>.sidebar-toggle{color:#333;border-right:1px solid #eee}.skin-black .main-header>.navbar .navbar-nav>li>a{border-right:1px solid #eee}.skin-black .main-header>.navbar .navbar-custom-menu .navbar-nav>li>a,.skin-black .main-header>.navbar .navbar-right>li>a{border-left:1px solid #eee;border-right-width:0}.skin-black .main-header>.logo{background-color:#fff;color:#333;border-bottom:0 solid transparent;border-right:1px solid #eee}.skin-black .main-header>.logo:hover{background-color:#fcfcfc}@media (max-width:767px){.skin-black .main-header>.logo{background-color:#222;color:#fff;border-bottom:0 solid transparent;border-right:none}.skin-black .main-header>.logo:hover{background-color:#1f1f1f}}.skin-black .main-header li.user-header{background-color:#222}.skin-black .content-header{background:transparent;box-shadow:none}.skin-black .wrapper,.skin-black .main-sidebar,.skin-black .left-side{background-color:#222d32}.skin-black .user-panel>.info,.skin-black .user-panel>.info>a{color:#fff}.skin-black .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-black .sidebar-menu>li>a{border-left:3px solid transparent}.skin-black .sidebar-menu>li:hover>a,.skin-black .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#fff}.skin-black .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-black .sidebar a{color:#b8c7ce}.skin-black .sidebar a:hover{text-decoration:none}.skin-black .treeview-menu>li>a{color:#8aa4af}.skin-black .treeview-menu>li.active>a,.skin-black .treeview-menu>li>a:hover{color:#fff}.skin-black .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-black .sidebar-form input[type="text"],.skin-black .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-black .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-black .sidebar-form input[type="text"]:focus,.skin-black .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-black .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-black .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-black-light .main-header{-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.skin-black-light .main-header .navbar-toggle{color:#333}.skin-black-light .main-header .navbar-brand{color:#333;border-right:1px solid #eee}.skin-black-light .main-header>.navbar{background-color:#fff}.skin-black-light .main-header>.navbar .nav>li>a{color:#333}.skin-black-light .main-header>.navbar .nav>li>a:hover,.skin-black-light .main-header>.navbar .nav>li>a:active,.skin-black-light .main-header>.navbar .nav>li>a:focus,.skin-black-light .main-header>.navbar .nav .open>a,.skin-black-light .main-header>.navbar .nav .open>a:hover,.skin-black-light .main-header>.navbar .nav .open>a:focus,.skin-black-light .main-header>.navbar .nav>.active>a{background:#fff;color:#999}.skin-black-light .main-header>.navbar .sidebar-toggle{color:#333}.skin-black-light .main-header>.navbar .sidebar-toggle:hover{color:#999;background:#fff}.skin-black-light .main-header>.navbar>.sidebar-toggle{color:#333;border-right:1px solid #eee}.skin-black-light .main-header>.navbar .navbar-nav>li>a{border-right:1px solid #eee}.skin-black-light .main-header>.navbar .navbar-custom-menu .navbar-nav>li>a,.skin-black-light .main-header>.navbar .navbar-right>li>a{border-left:1px solid #eee;border-right-width:0}.skin-black-light .main-header>.logo{background-color:#fff;color:#333;border-bottom:0 solid transparent;border-right:1px solid #eee}.skin-black-light .main-header>.logo:hover{background-color:#fcfcfc}@media (max-width:767px){.skin-black-light .main-header>.logo{background-color:#222;color:#fff;border-bottom:0 solid transparent;border-right:none}.skin-black-light .main-header>.logo:hover{background-color:#1f1f1f}}.skin-black-light .main-header li.user-header{background-color:#222}.skin-black-light .content-header{background:transparent;box-shadow:none}.skin-black-light .wrapper,.skin-black-light .main-sidebar,.skin-black-light .left-side{background-color:#f9fafc}.skin-black-light .content-wrapper,.skin-black-light .main-footer{border-left:1px solid #d2d6de}.skin-black-light .user-panel>.info,.skin-black-light .user-panel>.info>a{color:#444}.skin-black-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-black-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-black-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-black-light .sidebar-menu>li:hover>a,.skin-black-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-black-light .sidebar-menu>li.active{border-left-color:#fff}.skin-black-light .sidebar-menu>li.active>a{font-weight:600}.skin-black-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-black-light .sidebar a{color:#444}.skin-black-light .sidebar a:hover{text-decoration:none}.skin-black-light .treeview-menu>li>a{color:#777}.skin-black-light .treeview-menu>li.active>a,.skin-black-light .treeview-menu>li>a:hover{color:#000}.skin-black-light .treeview-menu>li.active>a{font-weight:600}.skin-black-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-black-light .sidebar-form input[type="text"],.skin-black-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-black-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-black-light .sidebar-form input[type="text"]:focus,.skin-black-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-black-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-black-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-black-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-green .main-header .navbar{background-color:#00a65a}.skin-green .main-header .navbar .nav>li>a{color:#fff}.skin-green .main-header .navbar .nav>li>a:hover,.skin-green .main-header .navbar .nav>li>a:active,.skin-green .main-header .navbar .nav>li>a:focus,.skin-green .main-header .navbar .nav .open>a,.skin-green .main-header .navbar .nav .open>a:hover,.skin-green .main-header .navbar .nav .open>a:focus,.skin-green .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-green .main-header .navbar .sidebar-toggle{color:#fff}.skin-green .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-green .main-header .navbar .sidebar-toggle{color:#fff}.skin-green .main-header .navbar .sidebar-toggle:hover{background-color:#008d4c}@media (max-width:767px){.skin-green .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-green .main-header .navbar .dropdown-menu li a{color:#fff}.skin-green .main-header .navbar .dropdown-menu li a:hover{background:#008d4c}}.skin-green .main-header .logo{background-color:#008d4c;color:#fff;border-bottom:0 solid transparent}.skin-green .main-header .logo:hover{background-color:#008749}.skin-green .main-header li.user-header{background-color:#00a65a}.skin-green .content-header{background:transparent}.skin-green .wrapper,.skin-green .main-sidebar,.skin-green .left-side{background-color:#222d32}.skin-green .user-panel>.info,.skin-green .user-panel>.info>a{color:#fff}.skin-green .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-green .sidebar-menu>li>a{border-left:3px solid transparent}.skin-green .sidebar-menu>li:hover>a,.skin-green .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#00a65a}.skin-green .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-green .sidebar a{color:#b8c7ce}.skin-green .sidebar a:hover{text-decoration:none}.skin-green .treeview-menu>li>a{color:#8aa4af}.skin-green .treeview-menu>li.active>a,.skin-green .treeview-menu>li>a:hover{color:#fff}.skin-green .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-green .sidebar-form input[type="text"],.skin-green .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-green .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-green .sidebar-form input[type="text"]:focus,.skin-green .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-green .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-green .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-green-light .main-header .navbar{background-color:#00a65a}.skin-green-light .main-header .navbar .nav>li>a{color:#fff}.skin-green-light .main-header .navbar .nav>li>a:hover,.skin-green-light .main-header .navbar .nav>li>a:active,.skin-green-light .main-header .navbar .nav>li>a:focus,.skin-green-light .main-header .navbar .nav .open>a,.skin-green-light .main-header .navbar .nav .open>a:hover,.skin-green-light .main-header .navbar .nav .open>a:focus,.skin-green-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-green-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-green-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-green-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-green-light .main-header .navbar .sidebar-toggle:hover{background-color:#008d4c}@media (max-width:767px){.skin-green-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-green-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-green-light .main-header .navbar .dropdown-menu li a:hover{background:#008d4c}}.skin-green-light .main-header .logo{background-color:#00a65a;color:#fff;border-bottom:0 solid transparent}.skin-green-light .main-header .logo:hover{background-color:#00a157}.skin-green-light .main-header li.user-header{background-color:#00a65a}.skin-green-light .content-header{background:transparent}.skin-green-light .wrapper,.skin-green-light .main-sidebar,.skin-green-light .left-side{background-color:#f9fafc}.skin-green-light .content-wrapper,.skin-green-light .main-footer{border-left:1px solid #d2d6de}.skin-green-light .user-panel>.info,.skin-green-light .user-panel>.info>a{color:#444}.skin-green-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-green-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-green-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-green-light .sidebar-menu>li:hover>a,.skin-green-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-green-light .sidebar-menu>li.active{border-left-color:#00a65a}.skin-green-light .sidebar-menu>li.active>a{font-weight:600}.skin-green-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-green-light .sidebar a{color:#444}.skin-green-light .sidebar a:hover{text-decoration:none}.skin-green-light .treeview-menu>li>a{color:#777}.skin-green-light .treeview-menu>li.active>a,.skin-green-light .treeview-menu>li>a:hover{color:#000}.skin-green-light .treeview-menu>li.active>a{font-weight:600}.skin-green-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-green-light .sidebar-form input[type="text"],.skin-green-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-green-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-green-light .sidebar-form input[type="text"]:focus,.skin-green-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-green-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-green-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-green-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-red .main-header .navbar{background-color:#dd4b39}.skin-red .main-header .navbar .nav>li>a{color:#fff}.skin-red .main-header .navbar .nav>li>a:hover,.skin-red .main-header .navbar .nav>li>a:active,.skin-red .main-header .navbar .nav>li>a:focus,.skin-red .main-header .navbar .nav .open>a,.skin-red .main-header .navbar .nav .open>a:hover,.skin-red .main-header .navbar .nav .open>a:focus,.skin-red .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-red .main-header .navbar .sidebar-toggle{color:#fff}.skin-red .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-red .main-header .navbar .sidebar-toggle{color:#fff}.skin-red .main-header .navbar .sidebar-toggle:hover{background-color:#d73925}@media (max-width:767px){.skin-red .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-red .main-header .navbar .dropdown-menu li a{color:#fff}.skin-red .main-header .navbar .dropdown-menu li a:hover{background:#d73925}}.skin-red .main-header .logo{background-color:#d73925;color:#fff;border-bottom:0 solid transparent}.skin-red .main-header .logo:hover{background-color:#d33724}.skin-red .main-header li.user-header{background-color:#dd4b39}.skin-red .content-header{background:transparent}.skin-red .wrapper,.skin-red .main-sidebar,.skin-red .left-side{background-color:#222d32}.skin-red .user-panel>.info,.skin-red .user-panel>.info>a{color:#fff}.skin-red .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-red .sidebar-menu>li>a{border-left:3px solid transparent}.skin-red .sidebar-menu>li:hover>a,.skin-red .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#dd4b39}.skin-red .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-red .sidebar a{color:#b8c7ce}.skin-red .sidebar a:hover{text-decoration:none}.skin-red .treeview-menu>li>a{color:#8aa4af}.skin-red .treeview-menu>li.active>a,.skin-red .treeview-menu>li>a:hover{color:#fff}.skin-red .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-red .sidebar-form input[type="text"],.skin-red .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-red .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-red .sidebar-form input[type="text"]:focus,.skin-red .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-red .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-red .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-red-light .main-header .navbar{background-color:#dd4b39}.skin-red-light .main-header .navbar .nav>li>a{color:#fff}.skin-red-light .main-header .navbar .nav>li>a:hover,.skin-red-light .main-header .navbar .nav>li>a:active,.skin-red-light .main-header .navbar .nav>li>a:focus,.skin-red-light .main-header .navbar .nav .open>a,.skin-red-light .main-header .navbar .nav .open>a:hover,.skin-red-light .main-header .navbar .nav .open>a:focus,.skin-red-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-red-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-red-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-red-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-red-light .main-header .navbar .sidebar-toggle:hover{background-color:#d73925}@media (max-width:767px){.skin-red-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-red-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-red-light .main-header .navbar .dropdown-menu li a:hover{background:#d73925}}.skin-red-light .main-header .logo{background-color:#dd4b39;color:#fff;border-bottom:0 solid transparent}.skin-red-light .main-header .logo:hover{background-color:#dc4735}.skin-red-light .main-header li.user-header{background-color:#dd4b39}.skin-red-light .content-header{background:transparent}.skin-red-light .wrapper,.skin-red-light .main-sidebar,.skin-red-light .left-side{background-color:#f9fafc}.skin-red-light .content-wrapper,.skin-red-light .main-footer{border-left:1px solid #d2d6de}.skin-red-light .user-panel>.info,.skin-red-light .user-panel>.info>a{color:#444}.skin-red-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-red-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-red-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-red-light .sidebar-menu>li:hover>a,.skin-red-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-red-light .sidebar-menu>li.active{border-left-color:#dd4b39}.skin-red-light .sidebar-menu>li.active>a{font-weight:600}.skin-red-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-red-light .sidebar a{color:#444}.skin-red-light .sidebar a:hover{text-decoration:none}.skin-red-light .treeview-menu>li>a{color:#777}.skin-red-light .treeview-menu>li.active>a,.skin-red-light .treeview-menu>li>a:hover{color:#000}.skin-red-light .treeview-menu>li.active>a{font-weight:600}.skin-red-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-red-light .sidebar-form input[type="text"],.skin-red-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-red-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-red-light .sidebar-form input[type="text"]:focus,.skin-red-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-red-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-red-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-red-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-yellow .main-header .navbar{background-color:#f39c12}.skin-yellow .main-header .navbar .nav>li>a{color:#fff}.skin-yellow .main-header .navbar .nav>li>a:hover,.skin-yellow .main-header .navbar .nav>li>a:active,.skin-yellow .main-header .navbar .nav>li>a:focus,.skin-yellow .main-header .navbar .nav .open>a,.skin-yellow .main-header .navbar .nav .open>a:hover,.skin-yellow .main-header .navbar .nav .open>a:focus,.skin-yellow .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-yellow .main-header .navbar .sidebar-toggle{color:#fff}.skin-yellow .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-yellow .main-header .navbar .sidebar-toggle{color:#fff}.skin-yellow .main-header .navbar .sidebar-toggle:hover{background-color:#e08e0b}@media (max-width:767px){.skin-yellow .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-yellow .main-header .navbar .dropdown-menu li a{color:#fff}.skin-yellow .main-header .navbar .dropdown-menu li a:hover{background:#e08e0b}}.skin-yellow .main-header .logo{background-color:#e08e0b;color:#fff;border-bottom:0 solid transparent}.skin-yellow .main-header .logo:hover{background-color:#db8b0b}.skin-yellow .main-header li.user-header{background-color:#f39c12}.skin-yellow .content-header{background:transparent}.skin-yellow .wrapper,.skin-yellow .main-sidebar,.skin-yellow .left-side{background-color:#222d32}.skin-yellow .user-panel>.info,.skin-yellow .user-panel>.info>a{color:#fff}.skin-yellow .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-yellow .sidebar-menu>li>a{border-left:3px solid transparent}.skin-yellow .sidebar-menu>li:hover>a,.skin-yellow .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#f39c12}.skin-yellow .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-yellow .sidebar a{color:#b8c7ce}.skin-yellow .sidebar a:hover{text-decoration:none}.skin-yellow .treeview-menu>li>a{color:#8aa4af}.skin-yellow .treeview-menu>li.active>a,.skin-yellow .treeview-menu>li>a:hover{color:#fff}.skin-yellow .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-yellow .sidebar-form input[type="text"],.skin-yellow .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-yellow .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-yellow .sidebar-form input[type="text"]:focus,.skin-yellow .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-yellow .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-yellow .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-yellow-light .main-header .navbar{background-color:#f39c12}.skin-yellow-light .main-header .navbar .nav>li>a{color:#fff}.skin-yellow-light .main-header .navbar .nav>li>a:hover,.skin-yellow-light .main-header .navbar .nav>li>a:active,.skin-yellow-light .main-header .navbar .nav>li>a:focus,.skin-yellow-light .main-header .navbar .nav .open>a,.skin-yellow-light .main-header .navbar .nav .open>a:hover,.skin-yellow-light .main-header .navbar .nav .open>a:focus,.skin-yellow-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-yellow-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-yellow-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-yellow-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-yellow-light .main-header .navbar .sidebar-toggle:hover{background-color:#e08e0b}@media (max-width:767px){.skin-yellow-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-yellow-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-yellow-light .main-header .navbar .dropdown-menu li a:hover{background:#e08e0b}}.skin-yellow-light .main-header .logo{background-color:#f39c12;color:#fff;border-bottom:0 solid transparent}.skin-yellow-light .main-header .logo:hover{background-color:#f39a0d}.skin-yellow-light .main-header li.user-header{background-color:#f39c12}.skin-yellow-light .content-header{background:transparent}.skin-yellow-light .wrapper,.skin-yellow-light .main-sidebar,.skin-yellow-light .left-side{background-color:#f9fafc}.skin-yellow-light .content-wrapper,.skin-yellow-light .main-footer{border-left:1px solid #d2d6de}.skin-yellow-light .user-panel>.info,.skin-yellow-light .user-panel>.info>a{color:#444}.skin-yellow-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-yellow-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-yellow-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-yellow-light .sidebar-menu>li:hover>a,.skin-yellow-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-yellow-light .sidebar-menu>li.active{border-left-color:#f39c12}.skin-yellow-light .sidebar-menu>li.active>a{font-weight:600}.skin-yellow-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-yellow-light .sidebar a{color:#444}.skin-yellow-light .sidebar a:hover{text-decoration:none}.skin-yellow-light .treeview-menu>li>a{color:#777}.skin-yellow-light .treeview-menu>li.active>a,.skin-yellow-light .treeview-menu>li>a:hover{color:#000}.skin-yellow-light .treeview-menu>li.active>a{font-weight:600}.skin-yellow-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-yellow-light .sidebar-form input[type="text"],.skin-yellow-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-yellow-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-yellow-light .sidebar-form input[type="text"]:focus,.skin-yellow-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-yellow-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-yellow-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-yellow-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-purple .main-header .navbar{background-color:#605ca8}.skin-purple .main-header .navbar .nav>li>a{color:#fff}.skin-purple .main-header .navbar .nav>li>a:hover,.skin-purple .main-header .navbar .nav>li>a:active,.skin-purple .main-header .navbar .nav>li>a:focus,.skin-purple .main-header .navbar .nav .open>a,.skin-purple .main-header .navbar .nav .open>a:hover,.skin-purple .main-header .navbar .nav .open>a:focus,.skin-purple .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-purple .main-header .navbar .sidebar-toggle{color:#fff}.skin-purple .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-purple .main-header .navbar .sidebar-toggle{color:#fff}.skin-purple .main-header .navbar .sidebar-toggle:hover{background-color:#555299}@media (max-width:767px){.skin-purple .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-purple .main-header .navbar .dropdown-menu li a{color:#fff}.skin-purple .main-header .navbar .dropdown-menu li a:hover{background:#555299}}.skin-purple .main-header .logo{background-color:#555299;color:#fff;border-bottom:0 solid transparent}.skin-purple .main-header .logo:hover{background-color:#545096}.skin-purple .main-header li.user-header{background-color:#605ca8}.skin-purple .content-header{background:transparent}.skin-purple .wrapper,.skin-purple .main-sidebar,.skin-purple .left-side{background-color:#222d32}.skin-purple .user-panel>.info,.skin-purple .user-panel>.info>a{color:#fff}.skin-purple .sidebar-menu>li.header{color:#4b646f;background:#1a2226}.skin-purple .sidebar-menu>li>a{border-left:3px solid transparent}.skin-purple .sidebar-menu>li:hover>a,.skin-purple .sidebar-menu>li.active>a{color:#fff;background:#1e282c;border-left-color:#605ca8}.skin-purple .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#2c3b41}.skin-purple .sidebar a{color:#b8c7ce}.skin-purple .sidebar a:hover{text-decoration:none}.skin-purple .treeview-menu>li>a{color:#8aa4af}.skin-purple .treeview-menu>li.active>a,.skin-purple .treeview-menu>li>a:hover{color:#fff}.skin-purple .sidebar-form{border-radius:3px;border:1px solid #374850;margin:10px 10px}.skin-purple .sidebar-form input[type="text"],.skin-purple .sidebar-form .btn{box-shadow:none;background-color:#374850;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-purple .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-purple .sidebar-form input[type="text"]:focus,.skin-purple .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-purple .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-purple .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-purple-light .main-header .navbar{background-color:#605ca8}.skin-purple-light .main-header .navbar .nav>li>a{color:#fff}.skin-purple-light .main-header .navbar .nav>li>a:hover,.skin-purple-light .main-header .navbar .nav>li>a:active,.skin-purple-light .main-header .navbar .nav>li>a:focus,.skin-purple-light .main-header .navbar .nav .open>a,.skin-purple-light .main-header .navbar .nav .open>a:hover,.skin-purple-light .main-header .navbar .nav .open>a:focus,.skin-purple-light .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-purple-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-purple-light .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-purple-light .main-header .navbar .sidebar-toggle{color:#fff}.skin-purple-light .main-header .navbar .sidebar-toggle:hover{background-color:#555299}@media (max-width:767px){.skin-purple-light .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-purple-light .main-header .navbar .dropdown-menu li a{color:#fff}.skin-purple-light .main-header .navbar .dropdown-menu li a:hover{background:#555299}}.skin-purple-light .main-header .logo{background-color:#605ca8;color:#fff;border-bottom:0 solid transparent}.skin-purple-light .main-header .logo:hover{background-color:#5d59a6}.skin-purple-light .main-header li.user-header{background-color:#605ca8}.skin-purple-light .content-header{background:transparent}.skin-purple-light .wrapper,.skin-purple-light .main-sidebar,.skin-purple-light .left-side{background-color:#f9fafc}.skin-purple-light .content-wrapper,.skin-purple-light .main-footer{border-left:1px solid #d2d6de}.skin-purple-light .user-panel>.info,.skin-purple-light .user-panel>.info>a{color:#444}.skin-purple-light .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-purple-light .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-purple-light .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-purple-light .sidebar-menu>li:hover>a,.skin-purple-light .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-purple-light .sidebar-menu>li.active{border-left-color:#605ca8}.skin-purple-light .sidebar-menu>li.active>a{font-weight:600}.skin-purple-light .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-purple-light .sidebar a{color:#444}.skin-purple-light .sidebar a:hover{text-decoration:none}.skin-purple-light .treeview-menu>li>a{color:#777}.skin-purple-light .treeview-menu>li.active>a,.skin-purple-light .treeview-menu>li>a:hover{color:#000}.skin-purple-light .treeview-menu>li.active>a{font-weight:600}.skin-purple-light .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-purple-light .sidebar-form input[type="text"],.skin-purple-light .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px;-webkit-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.skin-purple-light .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-purple-light .sidebar-form input[type="text"]:focus,.skin-purple-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-purple-light .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-purple-light .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-purple-light.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}} \ No newline at end of file diff --git a/public/assets/css/AdminLTE.min.css b/public/assets/css/AdminLTE.min.css new file mode 100644 index 00000000..b5ec266b --- /dev/null +++ b/public/assets/css/AdminLTE.min.css @@ -0,0 +1,7 @@ +@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic);/*! + * AdminLTE v2.3.0 + * Author: Almsaeed Studio + * Website: Almsaeed Studio + * License: Open source - MIT + * Please visit http://opensource.org/licenses/MIT for more information +!*/html,body{min-height:100%}.layout-boxed html,.layout-boxed body{height:100%}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:400;overflow-x:hidden;overflow-y:auto}.wrapper{min-height:100%;position:static;overflow:hidden}.wrapper:before,.wrapper:after{content:" ";display:table}.wrapper:after{clear:both}.layout-boxed .wrapper{max-width:1250px;margin:0 auto;min-height:100%;box-shadow:0 0 8px rgba(0,0,0,0.5);position:relative}.layout-boxed{background:url('../img/boxed-bg.jpg') repeat fixed}.content-wrapper,.right-side,.main-footer{-webkit-transition:-webkit-transform .3s ease-in-out,margin .3s ease-in-out;-moz-transition:-moz-transform .3s ease-in-out,margin .3s ease-in-out;-o-transition:-o-transform .3s ease-in-out,margin .3s ease-in-out;transition:transform .3s ease-in-out,margin .3s ease-in-out;margin-left:230px;z-index:820}.layout-top-nav .content-wrapper,.layout-top-nav .right-side,.layout-top-nav .main-footer{margin-left:0}@media (max-width:767px){.content-wrapper,.right-side,.main-footer{margin-left:0}}@media (min-width:768px){.sidebar-collapse .content-wrapper,.sidebar-collapse .right-side,.sidebar-collapse .main-footer{margin-left:0}}@media (max-width:767px){.sidebar-open .content-wrapper,.sidebar-open .right-side,.sidebar-open .main-footer{-webkit-transform:translate(230px, 0);-ms-transform:translate(230px, 0);-o-transform:translate(230px, 0);transform:translate(230px, 0)}}.content-wrapper,.right-side{min-height:100%;background-color:#ecf0f5;z-index:800}.main-footer{background:#fff;padding:15px;color:#444;border-top:1px solid #d2d6de}.fixed .main-header,.fixed .main-sidebar,.fixed .left-side{position:fixed}.fixed .main-header{top:0;right:0;left:0}.fixed .content-wrapper,.fixed .right-side{padding-top:50px}@media (max-width:767px){.fixed .content-wrapper,.fixed .right-side{padding-top:100px}}.fixed.layout-boxed .wrapper{max-width:100%}body.hold-transition .content-wrapper,body.hold-transition .right-side,body.hold-transition .main-footer,body.hold-transition .main-sidebar,body.hold-transition .left-side,body.hold-transition .main-header>.navbar,body.hold-transition .main-header .logo{-webkit-transition:none;-o-transition:none;transition:none}.content{min-height:250px;padding:15px;margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:'Source Sans Pro',sans-serif}a{color:#3c8dbc}a:hover,a:active,a:focus{outline:none;text-decoration:none;color:#72afd2}.page-header{margin:10px 0 20px 0;font-size:22px}.page-header>small{color:#666;display:block;margin-top:5px}.main-header{position:relative;max-height:100px;z-index:1030}.main-header>.navbar{-webkit-transition:margin-left .3s ease-in-out;-o-transition:margin-left .3s ease-in-out;transition:margin-left .3s ease-in-out;margin-bottom:0;margin-left:230px;border:none;min-height:50px;border-radius:0}.layout-top-nav .main-header>.navbar{margin-left:0}.main-header #navbar-search-input.form-control{background:rgba(255,255,255,0.2);border-color:transparent}.main-header #navbar-search-input.form-control:focus,.main-header #navbar-search-input.form-control:active{border-color:rgba(0,0,0,0.1);background:rgba(255,255,255,0.9)}.main-header #navbar-search-input.form-control::-moz-placeholder{color:#ccc;opacity:1}.main-header #navbar-search-input.form-control:-ms-input-placeholder{color:#ccc}.main-header #navbar-search-input.form-control::-webkit-input-placeholder{color:#ccc}.main-header .navbar-custom-menu,.main-header .navbar-right{float:right}@media (max-width:991px){.main-header .navbar-custom-menu a,.main-header .navbar-right a{color:inherit;background:transparent}}@media (max-width:767px){.main-header .navbar-right{float:none}.navbar-collapse .main-header .navbar-right{margin:7.5px -15px}.main-header .navbar-right>li{color:inherit;border:0}}.main-header .sidebar-toggle{float:left;background-color:transparent;background-image:none;padding:15px 15px;font-family:fontAwesome}.main-header .sidebar-toggle:before{content:"\f0c9"}.main-header .sidebar-toggle:hover{color:#fff}.main-header .sidebar-toggle:focus,.main-header .sidebar-toggle:active{background:transparent}.main-header .sidebar-toggle .icon-bar{display:none}.main-header .navbar .nav>li.user>a>.fa,.main-header .navbar .nav>li.user>a>.glyphicon,.main-header .navbar .nav>li.user>a>.ion{margin-right:5px}.main-header .navbar .nav>li>a>.label{position:absolute;top:9px;right:7px;text-align:center;font-size:9px;padding:2px 3px;line-height:.9}.main-header .logo{-webkit-transition:width .3s ease-in-out;-o-transition:width .3s ease-in-out;transition:width .3s ease-in-out;display:block;float:left;height:50px;font-size:20px;line-height:50px;text-align:center;width:230px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;padding:0 15px;font-weight:300;overflow:hidden}.main-header .logo .logo-lg{display:block}.main-header .logo .logo-mini{display:none}.main-header .navbar-brand{color:#fff}.content-header{position:relative;padding:15px 15px 0 15px}.content-header>h1{margin:0;font-size:24px}.content-header>h1>small{font-size:15px;display:inline-block;padding-left:4px;font-weight:300}.content-header>.breadcrumb{float:right;background:transparent;margin-top:0;margin-bottom:0;font-size:12px;padding:7px 5px;position:absolute;top:15px;right:10px;border-radius:2px}.content-header>.breadcrumb>li>a{color:#444;text-decoration:none;display:inline-block}.content-header>.breadcrumb>li>a>.fa,.content-header>.breadcrumb>li>a>.glyphicon,.content-header>.breadcrumb>li>a>.ion{margin-right:5px}.content-header>.breadcrumb>li+li:before{content:'>\00a0'}@media (max-width:991px){.content-header>.breadcrumb{position:relative;margin-top:5px;top:0;right:0;float:none;background:#d2d6de;padding-left:10px}.content-header>.breadcrumb li:before{color:#97a0b3}}.navbar-toggle{color:#fff;border:0;margin:0;padding:15px 15px}@media (max-width:991px){.navbar-custom-menu .navbar-nav>li{float:left}.navbar-custom-menu .navbar-nav{margin:0;float:left}.navbar-custom-menu .navbar-nav>li>a{padding-top:15px;padding-bottom:15px;line-height:20px}}@media (max-width:767px){.main-header{position:relative}.main-header .logo,.main-header .navbar{width:100%;float:none}.main-header .navbar{margin:0}.main-header .navbar-custom-menu{float:right}}@media (max-width:991px){.navbar-collapse.pull-left{float:none!important}.navbar-collapse.pull-left+.navbar-custom-menu{display:block;position:absolute;top:0;right:40px}}.main-sidebar,.left-side{position:absolute;top:0;left:0;padding-top:50px;min-height:100%;width:230px;z-index:810;-webkit-transition:-webkit-transform .3s ease-in-out,width .3s ease-in-out;-moz-transition:-moz-transform .3s ease-in-out,width .3s ease-in-out;-o-transition:-o-transform .3s ease-in-out,width .3s ease-in-out;transition:transform .3s ease-in-out,width .3s ease-in-out}@media (max-width:767px){.main-sidebar,.left-side{padding-top:100px}}@media (max-width:767px){.main-sidebar,.left-side{-webkit-transform:translate(-230px, 0);-ms-transform:translate(-230px, 0);-o-transform:translate(-230px, 0);transform:translate(-230px, 0)}}@media (min-width:768px){.sidebar-collapse .main-sidebar,.sidebar-collapse .left-side{-webkit-transform:translate(-230px, 0);-ms-transform:translate(-230px, 0);-o-transform:translate(-230px, 0);transform:translate(-230px, 0)}}@media (max-width:767px){.sidebar-open .main-sidebar,.sidebar-open .left-side{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}}.sidebar{padding-bottom:10px}.sidebar-form input:focus{border-color:transparent}.user-panel{position:relative;width:100%;padding:10px;overflow:hidden}.user-panel:before,.user-panel:after{content:" ";display:table}.user-panel:after{clear:both}.user-panel>.image>img{width:100%;max-width:45px;height:auto}.user-panel>.info{padding:5px 5px 5px 15px;line-height:1;position:absolute;left:55px}.user-panel>.info>p{font-weight:600;margin-bottom:9px}.user-panel>.info>a{text-decoration:none;padding-right:5px;margin-top:3px;font-size:11px}.user-panel>.info>a>.fa,.user-panel>.info>a>.ion,.user-panel>.info>a>.glyphicon{margin-right:3px}.sidebar-menu{list-style:none;margin:0;padding:0}.sidebar-menu>li{position:relative;margin:0;padding:0}.sidebar-menu>li>a{padding:12px 5px 12px 15px;display:block}.sidebar-menu>li>a>.fa,.sidebar-menu>li>a>.glyphicon,.sidebar-menu>li>a>.ion{width:20px}.sidebar-menu>li .label,.sidebar-menu>li .badge{margin-top:3px;margin-right:5px}.sidebar-menu li.header{padding:10px 25px 10px 15px;font-size:12px}.sidebar-menu li>a>.fa-angle-left{width:auto;height:auto;padding:0;margin-right:10px;margin-top:3px}.sidebar-menu li.active>a>.fa-angle-left{-webkit-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.sidebar-menu li.active>.treeview-menu{display:block}.sidebar-menu .treeview-menu{display:none;list-style:none;padding:0;margin:0;padding-left:5px}.sidebar-menu .treeview-menu .treeview-menu{padding-left:20px}.sidebar-menu .treeview-menu>li{margin:0}.sidebar-menu .treeview-menu>li>a{padding:5px 5px 5px 15px;display:block;font-size:14px}.sidebar-menu .treeview-menu>li>a>.fa,.sidebar-menu .treeview-menu>li>a>.glyphicon,.sidebar-menu .treeview-menu>li>a>.ion{width:20px}.sidebar-menu .treeview-menu>li>a>.fa-angle-left,.sidebar-menu .treeview-menu>li>a>.fa-angle-down{width:auto}@media (min-width:768px){.sidebar-mini.sidebar-collapse .content-wrapper,.sidebar-mini.sidebar-collapse .right-side,.sidebar-mini.sidebar-collapse .main-footer{margin-left:50px!important;z-index:840}.sidebar-mini.sidebar-collapse .main-sidebar{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0);width:50px!important;z-index:850}.sidebar-mini.sidebar-collapse .sidebar-menu>li{position:relative}.sidebar-mini.sidebar-collapse .sidebar-menu>li>a{margin-right:0}.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>span{border-top-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li:not(.treeview)>a>span{border-bottom-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{padding-top:5px;padding-bottom:5px;border-bottom-right-radius:4px}.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>a>span:not(.pull-right),.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>.treeview-menu{display:block!important;position:absolute;width:180px;left:50px}.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>a>span{top:0;margin-left:-3px;padding:12px 5px 12px 20px;background-color:inherit}.sidebar-mini.sidebar-collapse .sidebar-menu>li:hover>.treeview-menu{top:44px;margin-left:0}.sidebar-mini.sidebar-collapse .main-sidebar .user-panel>.info,.sidebar-mini.sidebar-collapse .sidebar-form,.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>span,.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu,.sidebar-mini.sidebar-collapse .sidebar-menu>li>a>.pull-right,.sidebar-mini.sidebar-collapse .sidebar-menu li.header{display:none!important;-webkit-transform:translateZ(0)}.sidebar-mini.sidebar-collapse .main-header .logo{width:50px}.sidebar-mini.sidebar-collapse .main-header .logo>.logo-mini{display:block;margin-left:-15px;margin-right:-15px;font-size:18px}.sidebar-mini.sidebar-collapse .main-header .logo>.logo-lg{display:none}.sidebar-mini.sidebar-collapse .main-header .navbar{margin-left:50px}}.sidebar-menu,.main-sidebar .user-panel,.sidebar-menu>li.header{white-space:nowrap;overflow:hidden}.sidebar-menu:hover{overflow:visible}.sidebar-form,.sidebar-menu>li.header{overflow:hidden;text-overflow:clip}.sidebar-menu li>a{position:relative}.sidebar-menu li>a>.pull-right{position:absolute;top:50%;right:10px;margin-top:-7px}.control-sidebar-bg{position:fixed;z-index:1000;bottom:0}.control-sidebar-bg,.control-sidebar{top:0;right:-230px;width:230px;-webkit-transition:right .3s ease-in-out;-o-transition:right .3s ease-in-out;transition:right .3s ease-in-out}.control-sidebar{position:absolute;padding-top:50px;z-index:1010}@media (max-width:768px){.control-sidebar{padding-top:100px}}.control-sidebar>.tab-content{padding:10px 15px}.control-sidebar.control-sidebar-open,.control-sidebar.control-sidebar-open+.control-sidebar-bg{right:0}.control-sidebar-open .control-sidebar-bg,.control-sidebar-open .control-sidebar{right:0}@media (min-width:768px){.control-sidebar-open .content-wrapper,.control-sidebar-open .right-side,.control-sidebar-open .main-footer{margin-right:230px}}.nav-tabs.control-sidebar-tabs>li:first-of-type>a,.nav-tabs.control-sidebar-tabs>li:first-of-type>a:hover,.nav-tabs.control-sidebar-tabs>li:first-of-type>a:focus{border-left-width:0}.nav-tabs.control-sidebar-tabs>li>a{border-radius:0}.nav-tabs.control-sidebar-tabs>li>a,.nav-tabs.control-sidebar-tabs>li>a:hover{border-top:none;border-right:none;border-left:1px solid transparent;border-bottom:1px solid transparent}.nav-tabs.control-sidebar-tabs>li>a .icon{font-size:16px}.nav-tabs.control-sidebar-tabs>li.active>a,.nav-tabs.control-sidebar-tabs>li.active>a:hover,.nav-tabs.control-sidebar-tabs>li.active>a:focus,.nav-tabs.control-sidebar-tabs>li.active>a:active{border-top:none;border-right:none;border-bottom:none}@media (max-width:768px){.nav-tabs.control-sidebar-tabs{display:table}.nav-tabs.control-sidebar-tabs>li{display:table-cell}}.control-sidebar-heading{font-weight:400;font-size:16px;padding:10px 0;margin-bottom:10px}.control-sidebar-subheading{display:block;font-weight:400;font-size:14px}.control-sidebar-menu{list-style:none;padding:0;margin:0 -15px}.control-sidebar-menu>li>a{display:block;padding:10px 15px}.control-sidebar-menu>li>a:before,.control-sidebar-menu>li>a:after{content:" ";display:table}.control-sidebar-menu>li>a:after{clear:both}.control-sidebar-menu>li>a>.control-sidebar-subheading{margin-top:0}.control-sidebar-menu .menu-icon{float:left;width:35px;height:35px;border-radius:50%;text-align:center;line-height:35px}.control-sidebar-menu .menu-info{margin-left:45px;margin-top:3px}.control-sidebar-menu .menu-info>.control-sidebar-subheading{margin:0}.control-sidebar-menu .menu-info>p{margin:0;font-size:11px}.control-sidebar-menu .progress{margin:0}.control-sidebar-dark{color:#b8c7ce}.control-sidebar-dark,.control-sidebar-dark+.control-sidebar-bg{background:#222d32}.control-sidebar-dark .nav-tabs.control-sidebar-tabs{border-bottom:#1c2529}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a{background:#181f23;color:#b8c7ce}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:focus{border-left-color:#141a1d;border-bottom-color:#141a1d}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:focus,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:active{background:#1c2529}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li>a:hover{color:#fff}.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:hover,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:focus,.control-sidebar-dark .nav-tabs.control-sidebar-tabs>li.active>a:active{background:#222d32;color:#fff}.control-sidebar-dark .control-sidebar-heading,.control-sidebar-dark .control-sidebar-subheading{color:#fff}.control-sidebar-dark .control-sidebar-menu>li>a:hover{background:#1e282c}.control-sidebar-dark .control-sidebar-menu>li>a .menu-info>p{color:#b8c7ce}.control-sidebar-light{color:#5e5e5e}.control-sidebar-light,.control-sidebar-light+.control-sidebar-bg{background:#f9fafc;border-left:1px solid #d2d6de}.control-sidebar-light .nav-tabs.control-sidebar-tabs{border-bottom:#d2d6de}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a{background:#e8ecf4;color:#444}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:focus{border-left-color:#d2d6de;border-bottom-color:#d2d6de}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:focus,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li>a:active{background:#eff1f7}.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:hover,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:focus,.control-sidebar-light .nav-tabs.control-sidebar-tabs>li.active>a:active{background:#f9fafc;color:#111}.control-sidebar-light .control-sidebar-heading,.control-sidebar-light .control-sidebar-subheading{color:#111}.control-sidebar-light .control-sidebar-menu{margin-left:-14px}.control-sidebar-light .control-sidebar-menu>li>a:hover{background:#f4f4f5}.control-sidebar-light .control-sidebar-menu>li>a .menu-info>p{color:#5e5e5e}.dropdown-menu{box-shadow:none;border-color:#eee}.dropdown-menu>li>a{color:#777}.dropdown-menu>li>a>.glyphicon,.dropdown-menu>li>a>.fa,.dropdown-menu>li>a>.ion{margin-right:10px}.dropdown-menu>li>a:hover{background-color:#e1e3e9;color:#333}.dropdown-menu>.divider{background-color:#eee}.navbar-nav>.notifications-menu>.dropdown-menu,.navbar-nav>.messages-menu>.dropdown-menu,.navbar-nav>.tasks-menu>.dropdown-menu{width:280px;padding:0 0 0 0;margin:0;top:100%}.navbar-nav>.notifications-menu>.dropdown-menu>li,.navbar-nav>.messages-menu>.dropdown-menu>li,.navbar-nav>.tasks-menu>.dropdown-menu>li{position:relative}.navbar-nav>.notifications-menu>.dropdown-menu>li.header,.navbar-nav>.messages-menu>.dropdown-menu>li.header,.navbar-nav>.tasks-menu>.dropdown-menu>li.header{border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0;background-color:#ffffff;padding:7px 10px;border-bottom:1px solid #f4f4f4;color:#444444;font-size:14px}.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px;font-size:12px;background-color:#fff;padding:7px 10px;border-bottom:1px solid #eeeeee;color:#444!important;text-align:center}@media (max-width:991px){.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a{background:#fff!important;color:#444!important}}.navbar-nav>.notifications-menu>.dropdown-menu>li.footer>a:hover,.navbar-nav>.messages-menu>.dropdown-menu>li.footer>a:hover,.navbar-nav>.tasks-menu>.dropdown-menu>li.footer>a:hover{text-decoration:none;font-weight:normal}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu,.navbar-nav>.messages-menu>.dropdown-menu>li .menu,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu{max-height:200px;margin:0;padding:0;list-style:none;overflow-x:hidden}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a{display:block;white-space:nowrap;border-bottom:1px solid #f4f4f4}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a:hover,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:hover,.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a:hover{background:#f4f4f4;text-decoration:none}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a{color:#444444;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:10px}.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.glyphicon,.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.fa,.navbar-nav>.notifications-menu>.dropdown-menu>li .menu>li>a>.ion{width:20px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a{margin:0;padding:10px 10px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>div>img{margin:auto 10px auto auto;width:40px;height:40px}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>h4{padding:0;margin:0 0 0 45px;color:#444444;font-size:15px;position:relative}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>h4>small{color:#999999;font-size:10px;position:absolute;top:0;right:0}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>p{margin:0 0 0 45px;font-size:12px;color:#888888}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:before,.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:after{content:" ";display:table}.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a:after{clear:both}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a{padding:10px}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a>h3{font-size:14px;padding:0;margin:0 0 10px 0;color:#666666}.navbar-nav>.tasks-menu>.dropdown-menu>li .menu>li>a>.progress{padding:0;margin:0}.navbar-nav>.user-menu>.dropdown-menu{border-top-right-radius:0;border-top-left-radius:0;padding:1px 0 0 0;border-top-width:0;width:280px}.navbar-nav>.user-menu>.dropdown-menu,.navbar-nav>.user-menu>.dropdown-menu>.user-body{border-bottom-right-radius:4px;border-bottom-left-radius:4px}.navbar-nav>.user-menu>.dropdown-menu>li.user-header{height:175px;padding:10px;text-align:center}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>img{z-index:5;height:90px;width:90px;border:3px solid;border-color:transparent;border-color:rgba(255,255,255,0.2)}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>p{z-index:5;color:#fff;color:rgba(255,255,255,0.8);font-size:17px;margin-top:10px}.navbar-nav>.user-menu>.dropdown-menu>li.user-header>p>small{display:block;font-size:12px}.navbar-nav>.user-menu>.dropdown-menu>.user-body{padding:15px;border-bottom:1px solid #f4f4f4;border-top:1px solid #dddddd}.navbar-nav>.user-menu>.dropdown-menu>.user-body:before,.navbar-nav>.user-menu>.dropdown-menu>.user-body:after{content:" ";display:table}.navbar-nav>.user-menu>.dropdown-menu>.user-body:after{clear:both}.navbar-nav>.user-menu>.dropdown-menu>.user-body a{color:#444 !important}@media (max-width:991px){.navbar-nav>.user-menu>.dropdown-menu>.user-body a{background:#fff !important;color:#444 !important}}.navbar-nav>.user-menu>.dropdown-menu>.user-footer{background-color:#f9f9f9;padding:10px}.navbar-nav>.user-menu>.dropdown-menu>.user-footer:before,.navbar-nav>.user-menu>.dropdown-menu>.user-footer:after{content:" ";display:table}.navbar-nav>.user-menu>.dropdown-menu>.user-footer:after{clear:both}.navbar-nav>.user-menu>.dropdown-menu>.user-footer .btn-default{color:#666666}@media (max-width:991px){.navbar-nav>.user-menu>.dropdown-menu>.user-footer .btn-default:hover{background-color:#f9f9f9}}.navbar-nav>.user-menu .user-image{float:left;width:25px;height:25px;border-radius:50%;margin-right:10px;margin-top:-2px}@media (max-width:767px){.navbar-nav>.user-menu .user-image{float:none;margin-right:0;margin-top:-8px;line-height:10px}}.open:not(.dropup)>.animated-dropdown-menu{backface-visibility:visible !important;-webkit-animation:flipInX .7s both;-o-animation:flipInX .7s both;animation:flipInX .7s both}@keyframes flipInX{0%{transform:perspective(400px) rotate3d(1, 0, 0, 90deg);transition-timing-function:ease-in;opacity:0}40%{transform:perspective(400px) rotate3d(1, 0, 0, -20deg);transition-timing-function:ease-in}60%{transform:perspective(400px) rotate3d(1, 0, 0, 10deg);opacity:1}80%{transform:perspective(400px) rotate3d(1, 0, 0, -5deg)}100%{transform:perspective(400px)}}@-webkit-keyframes flipInX{0%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, 90deg);-webkit-transition-timing-function:ease-in;opacity:0}40%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, -20deg);-webkit-transition-timing-function:ease-in}60%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, 10deg);opacity:1}80%{-webkit-transform:perspective(400px) rotate3d(1, 0, 0, -5deg)}100%{-webkit-transform:perspective(400px)}}.navbar-custom-menu>.navbar-nav>li{position:relative}.navbar-custom-menu>.navbar-nav>li>.dropdown-menu{position:absolute;right:0;left:auto}@media (max-width:991px){.navbar-custom-menu>.navbar-nav{float:right}.navbar-custom-menu>.navbar-nav>li{position:static}.navbar-custom-menu>.navbar-nav>li>.dropdown-menu{position:absolute;right:5%;left:auto;border:1px solid #ddd;background:#fff}}.form-control{border-radius:0;box-shadow:none;border-color:#d2d6de}.form-control:focus{border-color:#3c8dbc;box-shadow:none}.form-control::-moz-placeholder,.form-control:-ms-input-placeholder,.form-control::-webkit-input-placeholder{color:#bbb;opacity:1}.form-control:not(select){-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-group.has-success label{color:#00a65a}.form-group.has-success .form-control{border-color:#00a65a;box-shadow:none}.form-group.has-warning label{color:#f39c12}.form-group.has-warning .form-control{border-color:#f39c12;box-shadow:none}.form-group.has-error label{color:#dd4b39}.form-group.has-error .form-control{border-color:#dd4b39;box-shadow:none}.input-group .input-group-addon{border-radius:0;border-color:#d2d6de;background-color:#fff}.btn-group-vertical .btn.btn-flat:first-of-type,.btn-group-vertical .btn.btn-flat:last-of-type{border-radius:0}.icheck>label{padding-left:0}.form-control-feedback.fa{line-height:34px}.input-lg+.form-control-feedback.fa,.input-group-lg+.form-control-feedback.fa,.form-group-lg .form-control+.form-control-feedback.fa{line-height:46px}.input-sm+.form-control-feedback.fa,.input-group-sm+.form-control-feedback.fa,.form-group-sm .form-control+.form-control-feedback.fa{line-height:30px}.progress,.progress>.progress-bar{-webkit-box-shadow:none;box-shadow:none}.progress,.progress>.progress-bar,.progress .progress-bar,.progress>.progress-bar .progress-bar{border-radius:1px}.progress.sm,.progress-sm{height:10px}.progress.sm,.progress-sm,.progress.sm .progress-bar,.progress-sm .progress-bar{border-radius:1px}.progress.xs,.progress-xs{height:7px}.progress.xs,.progress-xs,.progress.xs .progress-bar,.progress-xs .progress-bar{border-radius:1px}.progress.xxs,.progress-xxs{height:3px}.progress.xxs,.progress-xxs,.progress.xxs .progress-bar,.progress-xxs .progress-bar{border-radius:1px}.progress.vertical{position:relative;width:30px;height:200px;display:inline-block;margin-right:10px}.progress.vertical>.progress-bar{width:100%;position:absolute;bottom:0}.progress.vertical.sm,.progress.vertical.progress-sm{width:20px}.progress.vertical.xs,.progress.vertical.progress-xs{width:10px}.progress.vertical.xxs,.progress.vertical.progress-xxs{width:3px}.progress-group .progress-text{font-weight:600}.progress-group .progress-number{float:right}.table tr>td .progress{margin:0}.progress-bar-light-blue,.progress-bar-primary{background-color:#3c8dbc}.progress-striped .progress-bar-light-blue,.progress-striped .progress-bar-primary{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-green,.progress-bar-success{background-color:#00a65a}.progress-striped .progress-bar-green,.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-aqua,.progress-bar-info{background-color:#00c0ef}.progress-striped .progress-bar-aqua,.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-yellow,.progress-bar-warning{background-color:#f39c12}.progress-striped .progress-bar-yellow,.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-red,.progress-bar-danger{background-color:#dd4b39}.progress-striped .progress-bar-red,.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.small-box{border-radius:2px;position:relative;display:block;margin-bottom:20px;box-shadow:0 1px 1px rgba(0,0,0,0.1)}.small-box>.inner{padding:10px}.small-box>.small-box-footer{position:relative;text-align:center;padding:3px 0;color:#fff;color:rgba(255,255,255,0.8);display:block;z-index:10;background:rgba(0,0,0,0.1);text-decoration:none}.small-box>.small-box-footer:hover{color:#fff;background:rgba(0,0,0,0.15)}.small-box h3{font-size:38px;font-weight:bold;margin:0 0 10px 0;white-space:nowrap;padding:0}.small-box p{font-size:15px}.small-box p>small{display:block;color:#f9f9f9;font-size:13px;margin-top:5px}.small-box h3,.small-box p{z-index:5px}.small-box .icon{-webkit-transition:all .3s linear;-o-transition:all .3s linear;transition:all .3s linear;position:absolute;top:-10px;right:10px;z-index:0;font-size:90px;color:rgba(0,0,0,0.15)}.small-box:hover{text-decoration:none;color:#f9f9f9}.small-box:hover .icon{font-size:95px}@media (max-width:767px){.small-box{text-align:center}.small-box .icon{display:none}.small-box p{font-size:12px}}.box{position:relative;border-radius:3px;background:#ffffff;border-top:3px solid #d2d6de;margin-bottom:20px;width:100%;box-shadow:0 1px 1px rgba(0,0,0,0.1)}.box.box-primary{border-top-color:#3c8dbc}.box.box-info{border-top-color:#00c0ef}.box.box-danger{border-top-color:#dd4b39}.box.box-warning{border-top-color:#f39c12}.box.box-success{border-top-color:#00a65a}.box.box-default{border-top-color:#d2d6de}.box.collapsed-box .box-body,.box.collapsed-box .box-footer{display:none}.box .nav-stacked>li{border-bottom:1px solid #f4f4f4;margin:0}.box .nav-stacked>li:last-of-type{border-bottom:none}.box.height-control .box-body{max-height:300px;overflow:auto}.box .border-right{border-right:1px solid #f4f4f4}.box .border-left{border-left:1px solid #f4f4f4}.box.box-solid{border-top:0}.box.box-solid>.box-header .btn.btn-default{background:transparent}.box.box-solid>.box-header .btn:hover,.box.box-solid>.box-header a:hover{background:rgba(0,0,0,0.1)}.box.box-solid.box-default{border:1px solid #d2d6de}.box.box-solid.box-default>.box-header{color:#444;background:#d2d6de;background-color:#d2d6de}.box.box-solid.box-default>.box-header a,.box.box-solid.box-default>.box-header .btn{color:#444}.box.box-solid.box-primary{border:1px solid #3c8dbc}.box.box-solid.box-primary>.box-header{color:#fff;background:#3c8dbc;background-color:#3c8dbc}.box.box-solid.box-primary>.box-header a,.box.box-solid.box-primary>.box-header .btn{color:#fff}.box.box-solid.box-info{border:1px solid #00c0ef}.box.box-solid.box-info>.box-header{color:#fff;background:#00c0ef;background-color:#00c0ef}.box.box-solid.box-info>.box-header a,.box.box-solid.box-info>.box-header .btn{color:#fff}.box.box-solid.box-danger{border:1px solid #dd4b39}.box.box-solid.box-danger>.box-header{color:#fff;background:#dd4b39;background-color:#dd4b39}.box.box-solid.box-danger>.box-header a,.box.box-solid.box-danger>.box-header .btn{color:#fff}.box.box-solid.box-warning{border:1px solid #f39c12}.box.box-solid.box-warning>.box-header{color:#fff;background:#f39c12;background-color:#f39c12}.box.box-solid.box-warning>.box-header a,.box.box-solid.box-warning>.box-header .btn{color:#fff}.box.box-solid.box-success{border:1px solid #00a65a}.box.box-solid.box-success>.box-header{color:#fff;background:#00a65a;background-color:#00a65a}.box.box-solid.box-success>.box-header a,.box.box-solid.box-success>.box-header .btn{color:#fff}.box.box-solid>.box-header>.box-tools .btn{border:0;box-shadow:none}.box.box-solid[class*='bg']>.box-header{color:#fff}.box .box-group>.box{margin-bottom:5px}.box .knob-label{text-align:center;color:#333;font-weight:100;font-size:12px;margin-bottom:0.3em}.box>.overlay,.overlay-wrapper>.overlay,.box>.loading-img,.overlay-wrapper>.loading-img{position:absolute;top:0;left:0;width:100%;height:100%}.box .overlay,.overlay-wrapper .overlay{z-index:50;background:rgba(255,255,255,0.7);border-radius:3px}.box .overlay>.fa,.overlay-wrapper .overlay>.fa{position:absolute;top:50%;left:50%;margin-left:-15px;margin-top:-15px;color:#000;font-size:30px}.box .overlay.dark,.overlay-wrapper .overlay.dark{background:rgba(0,0,0,0.5)}.box-header:before,.box-body:before,.box-footer:before,.box-header:after,.box-body:after,.box-footer:after{content:" ";display:table}.box-header:after,.box-body:after,.box-footer:after{clear:both}.box-header{color:#444;display:block;padding:10px;position:relative}.box-header.with-border{border-bottom:1px solid #f4f4f4}.collapsed-box .box-header.with-border{border-bottom:none}.box-header>.fa,.box-header>.glyphicon,.box-header>.ion,.box-header .box-title{display:inline-block;font-size:18px;margin:0;line-height:1}.box-header>.fa,.box-header>.glyphicon,.box-header>.ion{margin-right:5px}.box-header>.box-tools{position:absolute;right:10px;top:5px}.box-header>.box-tools [data-toggle="tooltip"]{position:relative}.box-header>.box-tools.pull-right .dropdown-menu{right:0;left:auto}.btn-box-tool{padding:5px;font-size:12px;background:transparent;color:#97a0b3}.open .btn-box-tool,.btn-box-tool:hover{color:#606c84}.btn-box-tool.btn:active{box-shadow:none}.box-body{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px;padding:10px}.no-header .box-body{border-top-right-radius:3px;border-top-left-radius:3px}.box-body>.table{margin-bottom:0}.box-body .fc{margin-top:5px}.box-body .full-width-chart{margin:-19px}.box-body.no-padding .full-width-chart{margin:-9px}.box-body .box-pane{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:3px}.box-body .box-pane-right{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:0}.box-footer{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px;border-top:1px solid #f4f4f4;padding:10px;background-color:#fff}.chart-legend{margin:10px 0}@media (max-width:991px){.chart-legend>li{float:left;margin-right:10px}}.box-comments{background:#f7f7f7}.box-comments .box-comment{padding:8px 0;border-bottom:1px solid #eee}.box-comments .box-comment:before,.box-comments .box-comment:after{content:" ";display:table}.box-comments .box-comment:after{clear:both}.box-comments .box-comment:last-of-type{border-bottom:0}.box-comments .box-comment:first-of-type{padding-top:0}.box-comments .box-comment img{float:left}.box-comments .comment-text{margin-left:40px;color:#555}.box-comments .username{color:#444;display:block;font-weight:600}.box-comments .text-muted{font-weight:400;font-size:12px}.todo-list{margin:0;padding:0;list-style:none;overflow:auto}.todo-list>li{border-radius:2px;padding:10px;background:#f4f4f4;margin-bottom:2px;border-left:2px solid #e6e7e8;color:#444}.todo-list>li:last-of-type{margin-bottom:0}.todo-list>li>input[type='checkbox']{margin:0 10px 0 5px}.todo-list>li .text{display:inline-block;margin-left:5px;font-weight:600}.todo-list>li .label{margin-left:10px;font-size:9px}.todo-list>li .tools{display:none;float:right;color:#dd4b39}.todo-list>li .tools>.fa,.todo-list>li .tools>.glyphicon,.todo-list>li .tools>.ion{margin-right:5px;cursor:pointer}.todo-list>li:hover .tools{display:inline-block}.todo-list>li.done{color:#999}.todo-list>li.done .text{text-decoration:line-through;font-weight:500}.todo-list>li.done .label{background:#d2d6de !important}.todo-list .danger{border-left-color:#dd4b39}.todo-list .warning{border-left-color:#f39c12}.todo-list .info{border-left-color:#00c0ef}.todo-list .success{border-left-color:#00a65a}.todo-list .primary{border-left-color:#3c8dbc}.todo-list .handle{display:inline-block;cursor:move;margin:0 5px}.chat{padding:5px 20px 5px 10px}.chat .item{margin-bottom:10px}.chat .item:before,.chat .item:after{content:" ";display:table}.chat .item:after{clear:both}.chat .item>img{width:40px;height:40px;border:2px solid transparent;border-radius:50%}.chat .item>.online{border:2px solid #00a65a}.chat .item>.offline{border:2px solid #dd4b39}.chat .item>.message{margin-left:55px;margin-top:-40px}.chat .item>.message>.name{display:block;font-weight:600}.chat .item>.attachment{border-radius:3px;background:#f4f4f4;margin-left:65px;margin-right:15px;padding:10px}.chat .item>.attachment>h4{margin:0 0 5px 0;font-weight:600;font-size:14px}.chat .item>.attachment>p,.chat .item>.attachment>.filename{font-weight:600;font-size:13px;font-style:italic;margin:0}.chat .item>.attachment:before,.chat .item>.attachment:after{content:" ";display:table}.chat .item>.attachment:after{clear:both}.box-input{max-width:200px}.modal .panel-body{color:#444}.info-box{display:block;min-height:90px;background:#fff;width:100%;box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:2px;margin-bottom:15px}.info-box small{font-size:14px}.info-box .progress{background:rgba(0,0,0,0.2);margin:5px -10px 5px -10px;height:2px}.info-box .progress,.info-box .progress .progress-bar{border-radius:0}.info-box .progress .progress-bar{background:#fff}.info-box-icon{border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px;display:block;float:left;height:90px;width:90px;text-align:center;font-size:45px;line-height:90px;background:rgba(0,0,0,0.2)}.info-box-icon>img{max-width:100%}.info-box-content{padding:5px 10px;margin-left:90px}.info-box-number{display:block;font-weight:bold;font-size:18px}.progress-description,.info-box-text{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.info-box-text{text-transform:uppercase}.info-box-more{display:block}.progress-description{margin:0}.timeline{position:relative;margin:0 0 30px 0;padding:0;list-style:none}.timeline:before{content:'';position:absolute;top:0;bottom:0;width:4px;background:#ddd;left:31px;margin:0;border-radius:2px}.timeline>li{position:relative;margin-right:10px;margin-bottom:15px}.timeline>li:before,.timeline>li:after{content:" ";display:table}.timeline>li:after{clear:both}.timeline>li>.timeline-item{-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px;margin-top:0;background:#fff;color:#444;margin-left:60px;margin-right:15px;padding:0;position:relative}.timeline>li>.timeline-item>.time{color:#999;float:right;padding:10px;font-size:12px}.timeline>li>.timeline-item>.timeline-header{margin:0;color:#555;border-bottom:1px solid #f4f4f4;padding:10px;font-size:16px;line-height:1.1}.timeline>li>.timeline-item>.timeline-header>a{font-weight:600}.timeline>li>.timeline-item>.timeline-body,.timeline>li>.timeline-item>.timeline-footer{padding:10px}.timeline>li>.fa,.timeline>li>.glyphicon,.timeline>li>.ion{width:30px;height:30px;font-size:15px;line-height:30px;position:absolute;color:#666;background:#d2d6de;border-radius:50%;text-align:center;left:18px;top:0}.timeline>.time-label>span{font-weight:600;padding:5px;display:inline-block;background-color:#fff;border-radius:4px}.timeline-inverse>li>.timeline-item{background:#f0f0f0;border:1px solid #ddd;-webkit-box-shadow:none;box-shadow:none}.timeline-inverse>li>.timeline-item>.timeline-header{border-bottom-color:#ddd}.btn{border-radius:3px;-webkit-box-shadow:none;box-shadow:none;border:1px solid transparent}.btn.uppercase{text-transform:uppercase}.btn.btn-flat{border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;border-width:1px}.btn:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:focus{outline:none}.btn.btn-file{position:relative;overflow:hidden}.btn.btn-file>input[type='file']{position:absolute;top:0;right:0;min-width:100%;min-height:100%;font-size:100px;text-align:right;opacity:0;filter:alpha(opacity=0);outline:none;background:white;cursor:inherit;display:block}.btn-default{background-color:#f4f4f4;color:#444;border-color:#ddd}.btn-default:hover,.btn-default:active,.btn-default.hover{background-color:#e7e7e7}.btn-primary{background-color:#3c8dbc;border-color:#367fa9}.btn-primary:hover,.btn-primary:active,.btn-primary.hover{background-color:#367fa9}.btn-success{background-color:#00a65a;border-color:#008d4c}.btn-success:hover,.btn-success:active,.btn-success.hover{background-color:#008d4c}.btn-info{background-color:#00c0ef;border-color:#00acd6}.btn-info:hover,.btn-info:active,.btn-info.hover{background-color:#00acd6}.btn-danger{background-color:#dd4b39;border-color:#d73925}.btn-danger:hover,.btn-danger:active,.btn-danger.hover{background-color:#d73925}.btn-warning{background-color:#f39c12;border-color:#e08e0b}.btn-warning:hover,.btn-warning:active,.btn-warning.hover{background-color:#e08e0b}.btn-outline{border:1px solid #fff;background:transparent;color:#fff}.btn-outline:hover,.btn-outline:focus,.btn-outline:active{color:rgba(255,255,255,0.7);border-color:rgba(255,255,255,0.7)}.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn[class*='bg-']:hover{-webkit-box-shadow:inset 0 0 100px rgba(0,0,0,0.2);box-shadow:inset 0 0 100px rgba(0,0,0,0.2)}.btn-app{border-radius:3px;position:relative;padding:15px 5px;margin:0 0 10px 10px;min-width:80px;height:60px;text-align:center;color:#666;border:1px solid #ddd;background-color:#f4f4f4;font-size:12px}.btn-app>.fa,.btn-app>.glyphicon,.btn-app>.ion{font-size:20px;display:block}.btn-app:hover{background:#f4f4f4;color:#444;border-color:#aaa}.btn-app:active,.btn-app:focus{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-app>.badge{position:absolute;top:-3px;right:-10px;font-size:10px;font-weight:400}.callout{border-radius:3px;margin:0 0 20px 0;padding:15px 30px 15px 15px;border-left:5px solid #eee}.callout a{color:#fff;text-decoration:underline}.callout a:hover{color:#eee}.callout h4{margin-top:0;font-weight:600}.callout p:last-child{margin-bottom:0}.callout code,.callout .highlight{background-color:#fff}.callout.callout-danger{border-color:#c23321}.callout.callout-warning{border-color:#c87f0a}.callout.callout-info{border-color:#0097bc}.callout.callout-success{border-color:#00733e}.alert{border-radius:3px}.alert h4{font-weight:600}.alert .icon{margin-right:10px}.alert .close{color:#000;opacity:.2;filter:alpha(opacity=20)}.alert .close:hover{opacity:.5;filter:alpha(opacity=50)}.alert a{color:#fff;text-decoration:underline}.alert-success{border-color:#008d4c}.alert-danger,.alert-error{border-color:#d73925}.alert-warning{border-color:#e08e0b}.alert-info{border-color:#00acd6}.nav>li>a:hover,.nav>li>a:active,.nav>li>a:focus{color:#444;background:#f7f7f7}.nav-pills>li>a{border-radius:0;border-top:3px solid transparent;color:#444}.nav-pills>li>a>.fa,.nav-pills>li>a>.glyphicon,.nav-pills>li>a>.ion{margin-right:5px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{border-top-color:#3c8dbc}.nav-pills>li.active>a{font-weight:600}.nav-stacked>li>a{border-radius:0;border-top:0;border-left:3px solid transparent;color:#444}.nav-stacked>li.active>a,.nav-stacked>li.active>a:hover{background:transparent;color:#444;border-top:0;border-left-color:#3c8dbc}.nav-stacked>li.header{border-bottom:1px solid #ddd;color:#777;margin-bottom:10px;padding:5px 10px;text-transform:uppercase}.nav-tabs-custom{margin-bottom:20px;background:#fff;box-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px}.nav-tabs-custom>.nav-tabs{margin:0;border-bottom-color:#f4f4f4;border-top-right-radius:3px;border-top-left-radius:3px}.nav-tabs-custom>.nav-tabs>li{border-top:3px solid transparent;margin-bottom:-2px;margin-right:5px}.nav-tabs-custom>.nav-tabs>li>a{color:#444;border-radius:0}.nav-tabs-custom>.nav-tabs>li>a.text-muted{color:#999}.nav-tabs-custom>.nav-tabs>li>a,.nav-tabs-custom>.nav-tabs>li>a:hover{background:transparent;margin:0}.nav-tabs-custom>.nav-tabs>li>a:hover{color:#999}.nav-tabs-custom>.nav-tabs>li:not(.active)>a:hover,.nav-tabs-custom>.nav-tabs>li:not(.active)>a:focus,.nav-tabs-custom>.nav-tabs>li:not(.active)>a:active{border-color:transparent}.nav-tabs-custom>.nav-tabs>li.active{border-top-color:#3c8dbc}.nav-tabs-custom>.nav-tabs>li.active>a,.nav-tabs-custom>.nav-tabs>li.active:hover>a{background-color:#fff;color:#444}.nav-tabs-custom>.nav-tabs>li.active>a{border-top-color:transparent;border-left-color:#f4f4f4;border-right-color:#f4f4f4}.nav-tabs-custom>.nav-tabs>li:first-of-type{margin-left:0}.nav-tabs-custom>.nav-tabs>li:first-of-type.active>a{border-left-color:transparent}.nav-tabs-custom>.nav-tabs.pull-right{float:none!important}.nav-tabs-custom>.nav-tabs.pull-right>li{float:right}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type{margin-right:0}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type>a{border-left-width:1px}.nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type.active>a{border-left-color:#f4f4f4;border-right-color:transparent}.nav-tabs-custom>.nav-tabs>li.header{line-height:35px;padding:0 10px;font-size:20px;color:#444}.nav-tabs-custom>.nav-tabs>li.header>.fa,.nav-tabs-custom>.nav-tabs>li.header>.glyphicon,.nav-tabs-custom>.nav-tabs>li.header>.ion{margin-right:5px}.nav-tabs-custom>.tab-content{background:#fff;padding:10px;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.nav-tabs-custom .dropdown.open>a:active,.nav-tabs-custom .dropdown.open>a:focus{background:transparent;color:#999}.pagination>li>a{background:#fafafa;color:#666}.pagination.pagination-flat>li>a{border-radius:0 !important}.products-list{list-style:none;margin:0;padding:0}.products-list>.item{border-radius:3px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.1);box-shadow:0 1px 1px rgba(0,0,0,0.1);padding:10px 0;background:#fff}.products-list>.item:before,.products-list>.item:after{content:" ";display:table}.products-list>.item:after{clear:both}.products-list .product-img{float:left}.products-list .product-img img{width:50px;height:50px}.products-list .product-info{margin-left:60px}.products-list .product-title{font-weight:600}.products-list .product-description{display:block;color:#999;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.product-list-in-box>.item{-webkit-box-shadow:none;box-shadow:none;border-radius:0;border-bottom:1px solid #f4f4f4}.product-list-in-box>.item:last-of-type{border-bottom-width:0}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{border-top:1px solid #f4f4f4}.table>thead>tr>th{border-bottom:2px solid #f4f4f4}.table tr td .progress{margin-top:5px}.table-bordered{border:1px solid #f4f4f4}.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 #f4f4f4}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table.no-border,.table.no-border td,.table.no-border th{border:0}table.text-center,table.text-center td,table.text-center th{text-align:center}.table.align th{text-align:left}.table.align td{text-align:right}.label-default{background-color:#d2d6de;color:#444}.direct-chat .box-body{border-bottom-right-radius:0;border-bottom-left-radius:0;position:relative;overflow-x:hidden;padding:0}.direct-chat.chat-pane-open .direct-chat-contacts{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.direct-chat-messages{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0);padding:10px;height:250px;overflow:auto}.direct-chat-msg,.direct-chat-text{display:block}.direct-chat-msg{margin-bottom:10px}.direct-chat-msg:before,.direct-chat-msg:after{content:" ";display:table}.direct-chat-msg:after{clear:both}.direct-chat-messages,.direct-chat-contacts{-webkit-transition:-webkit-transform .5s ease-in-out;-moz-transition:-moz-transform .5s ease-in-out;-o-transition:-o-transform .5s ease-in-out;transition:transform .5s ease-in-out}.direct-chat-text{border-radius:5px;position:relative;padding:5px 10px;background:#d2d6de;border:1px solid #d2d6de;margin:5px 0 0 50px;color:#444}.direct-chat-text:after,.direct-chat-text:before{position:absolute;right:100%;top:15px;border:solid transparent;border-right-color:#d2d6de;content:' ';height:0;width:0;pointer-events:none}.direct-chat-text:after{border-width:5px;margin-top:-5px}.direct-chat-text:before{border-width:6px;margin-top:-6px}.right .direct-chat-text{margin-right:50px;margin-left:0}.right .direct-chat-text:after,.right .direct-chat-text:before{right:auto;left:100%;border-right-color:transparent;border-left-color:#d2d6de}.direct-chat-img{border-radius:50%;float:left;width:40px;height:40px}.right .direct-chat-img{float:right}.direct-chat-info{display:block;margin-bottom:2px;font-size:12px}.direct-chat-name{font-weight:600}.direct-chat-timestamp{color:#999}.direct-chat-contacts-open .direct-chat-contacts{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.direct-chat-contacts{-webkit-transform:translate(101%, 0);-ms-transform:translate(101%, 0);-o-transform:translate(101%, 0);transform:translate(101%, 0);position:absolute;top:0;bottom:0;height:250px;width:100%;background:#222d32;color:#fff;overflow:auto}.contacts-list>li{border-bottom:1px solid rgba(0,0,0,0.2);padding:10px;margin:0}.contacts-list>li:before,.contacts-list>li:after{content:" ";display:table}.contacts-list>li:after{clear:both}.contacts-list>li:last-of-type{border-bottom:none}.contacts-list-img{border-radius:50%;width:40px;float:left}.contacts-list-info{margin-left:45px;color:#fff}.contacts-list-name,.contacts-list-status{display:block}.contacts-list-name{font-weight:600}.contacts-list-status{font-size:12px}.contacts-list-date{color:#aaa;font-weight:normal}.contacts-list-msg{color:#999}.direct-chat-danger .right>.direct-chat-text{background:#dd4b39;border-color:#dd4b39;color:#fff}.direct-chat-danger .right>.direct-chat-text:after,.direct-chat-danger .right>.direct-chat-text:before{border-left-color:#dd4b39}.direct-chat-primary .right>.direct-chat-text{background:#3c8dbc;border-color:#3c8dbc;color:#fff}.direct-chat-primary .right>.direct-chat-text:after,.direct-chat-primary .right>.direct-chat-text:before{border-left-color:#3c8dbc}.direct-chat-warning .right>.direct-chat-text{background:#f39c12;border-color:#f39c12;color:#fff}.direct-chat-warning .right>.direct-chat-text:after,.direct-chat-warning .right>.direct-chat-text:before{border-left-color:#f39c12}.direct-chat-info .right>.direct-chat-text{background:#00c0ef;border-color:#00c0ef;color:#fff}.direct-chat-info .right>.direct-chat-text:after,.direct-chat-info .right>.direct-chat-text:before{border-left-color:#00c0ef}.direct-chat-success .right>.direct-chat-text{background:#00a65a;border-color:#00a65a;color:#fff}.direct-chat-success .right>.direct-chat-text:after,.direct-chat-success .right>.direct-chat-text:before{border-left-color:#00a65a}.users-list>li{width:25%;float:left;padding:10px;text-align:center}.users-list>li img{border-radius:50%;max-width:100%;height:auto}.users-list>li>a:hover,.users-list>li>a:hover .users-list-name{color:#999}.users-list-name,.users-list-date{display:block}.users-list-name{font-weight:600;color:#444;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.users-list-date{color:#999;font-size:12px}.carousel-control.left,.carousel-control.right{background-image:none}.carousel-control>.fa{font-size:40px;position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-20px}.modal{background:rgba(0,0,0,0.3)}.modal-content{border-radius:0;-webkit-box-shadow:0 2px 3px rgba(0,0,0,0.125);box-shadow:0 2px 3px rgba(0,0,0,0.125);border:0}@media (min-width:768px){.modal-content{-webkit-box-shadow:0 2px 3px rgba(0,0,0,0.125);box-shadow:0 2px 3px rgba(0,0,0,0.125)}}.modal-header{border-bottom-color:#f4f4f4}.modal-footer{border-top-color:#f4f4f4}.modal-primary .modal-header,.modal-primary .modal-footer{border-color:#307095}.modal-warning .modal-header,.modal-warning .modal-footer{border-color:#c87f0a}.modal-info .modal-header,.modal-info .modal-footer{border-color:#0097bc}.modal-success .modal-header,.modal-success .modal-footer{border-color:#00733e}.modal-danger .modal-header,.modal-danger .modal-footer{border-color:#c23321}.box-widget{border:none;position:relative}.widget-user .widget-user-header{padding:20px;height:120px;border-top-right-radius:3px;border-top-left-radius:3px}.widget-user .widget-user-username{margin-top:0;margin-bottom:5px;font-size:25px;font-weight:300;text-shadow:0 1px 1px rgba(0,0,0,0.2)}.widget-user .widget-user-desc{margin-top:0}.widget-user .widget-user-image{position:absolute;top:65px;left:50%;margin-left:-45px}.widget-user .widget-user-image>img{width:90px;height:auto;border:3px solid #fff}.widget-user .box-footer{padding-top:30px}.widget-user-2 .widget-user-header{padding:20px;border-top-right-radius:3px;border-top-left-radius:3px}.widget-user-2 .widget-user-username{margin-top:5px;margin-bottom:5px;font-size:25px;font-weight:300}.widget-user-2 .widget-user-desc{margin-top:0}.widget-user-2 .widget-user-username,.widget-user-2 .widget-user-desc{margin-left:75px}.widget-user-2 .widget-user-image>img{width:65px;height:auto;float:left}.mailbox-messages>.table{margin:0}.mailbox-controls{padding:5px}.mailbox-controls.with-border{border-bottom:1px solid #f4f4f4}.mailbox-read-info{border-bottom:1px solid #f4f4f4;padding:10px}.mailbox-read-info h3{font-size:20px;margin:0}.mailbox-read-info h5{margin:0;padding:5px 0 0 0}.mailbox-read-time{color:#999;font-size:13px}.mailbox-read-message{padding:10px}.mailbox-attachments li{float:left;width:200px;border:1px solid #eee;margin-bottom:10px;margin-right:10px}.mailbox-attachment-name{font-weight:bold;color:#666}.mailbox-attachment-icon,.mailbox-attachment-info,.mailbox-attachment-size{display:block}.mailbox-attachment-info{padding:10px;background:#f4f4f4}.mailbox-attachment-size{color:#999;font-size:12px}.mailbox-attachment-icon{text-align:center;font-size:65px;color:#666;padding:20px 10px}.mailbox-attachment-icon.has-img{padding:0}.mailbox-attachment-icon.has-img>img{max-width:100%;height:auto}.lockscreen{background:#d2d6de}.lockscreen-logo{font-size:35px;text-align:center;margin-bottom:25px;font-weight:300}.lockscreen-logo a{color:#444}.lockscreen-wrapper{max-width:400px;margin:0 auto;margin-top:10%}.lockscreen .lockscreen-name{text-align:center;font-weight:600}.lockscreen-item{border-radius:4px;padding:0;background:#fff;position:relative;margin:10px auto 30px auto;width:290px}.lockscreen-image{border-radius:50%;position:absolute;left:-10px;top:-25px;background:#fff;padding:5px;z-index:10}.lockscreen-image>img{border-radius:50%;width:70px;height:70px}.lockscreen-credentials{margin-left:70px}.lockscreen-credentials .form-control{border:0}.lockscreen-credentials .btn{background-color:#fff;border:0;padding:0 10px}.lockscreen-footer{margin-top:10px}.login-logo,.register-logo{font-size:35px;text-align:center;margin-bottom:25px;font-weight:300}.login-logo a,.register-logo a{color:#444}.login-page,.register-page{background:#d2d6de}.login-box,.register-box{width:360px;margin:7% auto}@media (max-width:768px){.login-box,.register-box{width:90%;margin-top:20px}}.login-box-body,.register-box-body{background:#fff;padding:20px;border-top:0;color:#666}.login-box-body .form-control-feedback,.register-box-body .form-control-feedback{color:#777}.login-box-msg,.register-box-msg{margin:0;text-align:center;padding:0 20px 20px 20px}.social-auth-links{margin:10px 0}.error-page{width:600px;margin:20px auto 0 auto}@media (max-width:991px){.error-page{width:100%}}.error-page>.headline{float:left;font-size:100px;font-weight:300}@media (max-width:991px){.error-page>.headline{float:none;text-align:center}}.error-page>.error-content{margin-left:190px;display:block}@media (max-width:991px){.error-page>.error-content{margin-left:0}}.error-page>.error-content>h3{font-weight:300;font-size:25px}@media (max-width:991px){.error-page>.error-content>h3{text-align:center}}.invoice{position:relative;background:#fff;border:1px solid #f4f4f4;padding:20px;margin:10px 25px}.invoice-title{margin-top:0}.profile-user-img{margin:0 auto;width:100px;padding:3px;border:3px solid #d2d6de}.profile-username{font-size:21px;margin-top:5px}.post{border-bottom:1px solid #d2d6de;margin-bottom:15px;padding-bottom:15px;color:#666}.post:last-of-type{border-bottom:0;margin-bottom:0;padding-bottom:0}.post .user-block{margin-bottom:15px}.btn-social{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.btn-social>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)}.btn-social.btn-lg{padding-left:61px}.btn-social.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em}.btn-social.btn-sm{padding-left:38px}.btn-social.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em}.btn-social.btn-xs{padding-left:30px}.btn-social.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em}.btn-social-icon{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;height:34px;width:34px;padding:0}.btn-social-icon>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)}.btn-social-icon.btn-lg{padding-left:61px}.btn-social-icon.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em}.btn-social-icon.btn-sm{padding-left:38px}.btn-social-icon.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em}.btn-social-icon.btn-xs{padding-left:30px}.btn-social-icon.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em}.btn-social-icon>:first-child{border:none;text-align:center;width:100%}.btn-social-icon.btn-lg{height:45px;width:45px;padding-left:0;padding-right:0}.btn-social-icon.btn-sm{height:30px;width:30px;padding-left:0;padding-right:0}.btn-social-icon.btn-xs{height:22px;width:22px;padding-left:0;padding-right:0}.btn-adn{color:#fff;background-color:#d87a68;border-color:rgba(0,0,0,0.2)}.btn-adn:hover,.btn-adn:focus,.btn-adn.focus,.btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{background-image:none}.btn-adn .badge{color:#d87a68;background-color:#fff}.btn-bitbucket{color:#fff;background-color:#205081;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:hover,.btn-bitbucket:focus,.btn-bitbucket.focus,.btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{background-image:none}.btn-bitbucket .badge{color:#205081;background-color:#fff}.btn-dropbox{color:#fff;background-color:#1087dd;border-color:rgba(0,0,0,0.2)}.btn-dropbox:hover,.btn-dropbox:focus,.btn-dropbox.focus,.btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{background-image:none}.btn-dropbox .badge{color:#1087dd;background-color:#fff}.btn-facebook{color:#fff;background-color:#3b5998;border-color:rgba(0,0,0,0.2)}.btn-facebook:hover,.btn-facebook:focus,.btn-facebook.focus,.btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{background-image:none}.btn-facebook .badge{color:#3b5998;background-color:#fff}.btn-flickr{color:#fff;background-color:#ff0084;border-color:rgba(0,0,0,0.2)}.btn-flickr:hover,.btn-flickr:focus,.btn-flickr.focus,.btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{background-image:none}.btn-flickr .badge{color:#ff0084;background-color:#fff}.btn-foursquare{color:#fff;background-color:#f94877;border-color:rgba(0,0,0,0.2)}.btn-foursquare:hover,.btn-foursquare:focus,.btn-foursquare.focus,.btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{background-image:none}.btn-foursquare .badge{color:#f94877;background-color:#fff}.btn-github{color:#fff;background-color:#444;border-color:rgba(0,0,0,0.2)}.btn-github:hover,.btn-github:focus,.btn-github.focus,.btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{background-image:none}.btn-github .badge{color:#444;background-color:#fff}.btn-google{color:#fff;background-color:#dd4b39;border-color:rgba(0,0,0,0.2)}.btn-google:hover,.btn-google:focus,.btn-google.focus,.btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{background-image:none}.btn-google .badge{color:#dd4b39;background-color:#fff}.btn-instagram{color:#fff;background-color:#3f729b;border-color:rgba(0,0,0,0.2)}.btn-instagram:hover,.btn-instagram:focus,.btn-instagram.focus,.btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{background-image:none}.btn-instagram .badge{color:#3f729b;background-color:#fff}.btn-linkedin{color:#fff;background-color:#007bb6;border-color:rgba(0,0,0,0.2)}.btn-linkedin:hover,.btn-linkedin:focus,.btn-linkedin.focus,.btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{background-image:none}.btn-linkedin .badge{color:#007bb6;background-color:#fff}.btn-microsoft{color:#fff;background-color:#2672ec;border-color:rgba(0,0,0,0.2)}.btn-microsoft:hover,.btn-microsoft:focus,.btn-microsoft.focus,.btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{background-image:none}.btn-microsoft .badge{color:#2672ec;background-color:#fff}.btn-openid{color:#fff;background-color:#f7931e;border-color:rgba(0,0,0,0.2)}.btn-openid:hover,.btn-openid:focus,.btn-openid.focus,.btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{background-image:none}.btn-openid .badge{color:#f7931e;background-color:#fff}.btn-pinterest{color:#fff;background-color:#cb2027;border-color:rgba(0,0,0,0.2)}.btn-pinterest:hover,.btn-pinterest:focus,.btn-pinterest.focus,.btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{background-image:none}.btn-pinterest .badge{color:#cb2027;background-color:#fff}.btn-reddit{color:#000;background-color:#eff7ff;border-color:rgba(0,0,0,0.2)}.btn-reddit:hover,.btn-reddit:focus,.btn-reddit.focus,.btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{background-image:none}.btn-reddit .badge{color:#eff7ff;background-color:#000}.btn-soundcloud{color:#fff;background-color:#f50;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:hover,.btn-soundcloud:focus,.btn-soundcloud.focus,.btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{background-image:none}.btn-soundcloud .badge{color:#f50;background-color:#fff}.btn-tumblr{color:#fff;background-color:#2c4762;border-color:rgba(0,0,0,0.2)}.btn-tumblr:hover,.btn-tumblr:focus,.btn-tumblr.focus,.btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{background-image:none}.btn-tumblr .badge{color:#2c4762;background-color:#fff}.btn-twitter{color:#fff;background-color:#55acee;border-color:rgba(0,0,0,0.2)}.btn-twitter:hover,.btn-twitter:focus,.btn-twitter.focus,.btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{background-image:none}.btn-twitter .badge{color:#55acee;background-color:#fff}.btn-vimeo{color:#fff;background-color:#1ab7ea;border-color:rgba(0,0,0,0.2)}.btn-vimeo:hover,.btn-vimeo:focus,.btn-vimeo.focus,.btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{background-image:none}.btn-vimeo .badge{color:#1ab7ea;background-color:#fff}.btn-vk{color:#fff;background-color:#587ea3;border-color:rgba(0,0,0,0.2)}.btn-vk:hover,.btn-vk:focus,.btn-vk.focus,.btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{background-image:none}.btn-vk .badge{color:#587ea3;background-color:#fff}.btn-yahoo{color:#fff;background-color:#720e9e;border-color:rgba(0,0,0,0.2)}.btn-yahoo:hover,.btn-yahoo:focus,.btn-yahoo.focus,.btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{background-image:none}.btn-yahoo .badge{color:#720e9e;background-color:#fff}.fc-button{background:#f4f4f4;background-image:none;color:#444;border-color:#ddd;border-bottom-color:#ddd}.fc-button:hover,.fc-button:active,.fc-button.hover{background-color:#e9e9e9}.fc-header-title h2{font-size:15px;line-height:1.6em;color:#666;margin-left:10px}.fc-header-right{padding-right:10px}.fc-header-left{padding-left:10px}.fc-widget-header{background:#fafafa}.fc-grid{width:100%;border:0}.fc-widget-header:first-of-type,.fc-widget-content:first-of-type{border-left:0;border-right:0}.fc-widget-header:last-of-type,.fc-widget-content:last-of-type{border-right:0}.fc-toolbar{padding:10px;margin:0}.fc-day-number{font-size:20px;font-weight:300;padding-right:10px}.fc-color-picker{list-style:none;margin:0;padding:0}.fc-color-picker>li{float:left;font-size:30px;margin-right:5px;line-height:30px}.fc-color-picker>li .fa{-webkit-transition:-webkit-transform linear .3s;-moz-transition:-moz-transform linear .3s;-o-transition:-o-transform linear .3s;transition:transform linear .3s}.fc-color-picker>li .fa:hover{-webkit-transform:rotate(30deg);-ms-transform:rotate(30deg);-o-transform:rotate(30deg);transform:rotate(30deg)}#add-new-event{-webkit-transition:all linear .3s;-o-transition:all linear .3s;transition:all linear .3s}.external-event{padding:5px 10px;font-weight:bold;margin-bottom:4px;box-shadow:0 1px 1px rgba(0,0,0,0.1);text-shadow:0 1px 1px rgba(0,0,0,0.1);border-radius:3px;cursor:move}.external-event:hover{box-shadow:inset 0 0 90px rgba(0,0,0,0.2)}.select2-container--default.select2-container--focus,.select2-selection.select2-container--focus,.select2-container--default:focus,.select2-selection:focus,.select2-container--default:active,.select2-selection:active{outline:none}.select2-container--default .select2-selection--single,.select2-selection .select2-selection--single{border:1px solid #d2d6de;border-radius:0;padding:6px 12px;height:34px}.select2-container--default.select2-container--open{border-color:#3c8dbc}.select2-dropdown{border:1px solid #d2d6de;border-radius:0}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#3c8dbc;color:white}.select2-results__option{padding:6px 12px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{padding-left:0;padding-right:0;height:auto;margin-top:-4px}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:6px;padding-left:20px}.select2-container--default .select2-selection--single .select2-selection__arrow{height:28px;right:3px}.select2-container--default .select2-selection--single .select2-selection__arrow b{margin-top:0}.select2-dropdown .select2-search__field,.select2-search--inline .select2-search__field{border:1px solid #d2d6de}.select2-dropdown .select2-search__field:focus,.select2-search--inline .select2-search__field:focus{outline:none;border:1px solid #3c8dbc}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option[aria-selected=true],.select2-container--default .select2-results__option[aria-selected=true]:hover{color:#444}.select2-container--default .select2-selection--multiple{border:1px solid #d2d6de;border-radius:0}.select2-container--default .select2-selection--multiple:focus{border-color:#3c8dbc}.select2-container--default.select2-container--focus .select2-selection--multiple{border-color:#d2d6de}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#3c8dbc;border-color:#367fa9;padding:1px 10px;color:#fff}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{margin-right:5px;color:rgba(255,255,255,0.7)}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#fff}.select2-container .select2-selection--single .select2-selection__rendered{padding-right:10px}.pad{padding:10px}.margin{margin:10px}.margin-bottom{margin-bottom:20px}.margin-bottom-none{margin-bottom:0}.margin-r-5{margin-right:5px}.inline{display:inline}.description-block{display:block;margin:10px 0;text-align:center}.description-block.margin-bottom{margin-bottom:25px}.description-block>.description-header{margin:0;padding:0;font-weight:600;font-size:16px}.description-block>.description-text{text-transform:uppercase}.bg-red,.bg-yellow,.bg-aqua,.bg-blue,.bg-light-blue,.bg-green,.bg-navy,.bg-teal,.bg-olive,.bg-lime,.bg-orange,.bg-fuchsia,.bg-purple,.bg-maroon,.bg-black,.bg-red-active,.bg-yellow-active,.bg-aqua-active,.bg-blue-active,.bg-light-blue-active,.bg-green-active,.bg-navy-active,.bg-teal-active,.bg-olive-active,.bg-lime-active,.bg-orange-active,.bg-fuchsia-active,.bg-purple-active,.bg-maroon-active,.bg-black-active,.callout.callout-danger,.callout.callout-warning,.callout.callout-info,.callout.callout-success,.alert-success,.alert-danger,.alert-error,.alert-warning,.alert-info,.label-danger,.label-info,.label-warning,.label-primary,.label-success,.modal-primary .modal-body,.modal-primary .modal-header,.modal-primary .modal-footer,.modal-warning .modal-body,.modal-warning .modal-header,.modal-warning .modal-footer,.modal-info .modal-body,.modal-info .modal-header,.modal-info .modal-footer,.modal-success .modal-body,.modal-success .modal-header,.modal-success .modal-footer,.modal-danger .modal-body,.modal-danger .modal-header,.modal-danger .modal-footer{color:#fff !important}.bg-gray{color:#000;background-color:#d2d6de !important}.bg-gray-light{background-color:#f7f7f7}.bg-black{background-color:#111 !important}.bg-red,.callout.callout-danger,.alert-danger,.alert-error,.label-danger,.modal-danger .modal-body{background-color:#dd4b39 !important}.bg-yellow,.callout.callout-warning,.alert-warning,.label-warning,.modal-warning .modal-body{background-color:#f39c12 !important}.bg-aqua,.callout.callout-info,.alert-info,.label-info,.modal-info .modal-body{background-color:#00c0ef !important}.bg-blue{background-color:#0073b7 !important}.bg-light-blue,.label-primary,.modal-primary .modal-body{background-color:#3c8dbc !important}.bg-green,.callout.callout-success,.alert-success,.label-success,.modal-success .modal-body{background-color:#00a65a !important}.bg-navy{background-color:#001f3f !important}.bg-teal{background-color:#39cccc !important}.bg-olive{background-color:#3d9970 !important}.bg-lime{background-color:#01ff70 !important}.bg-orange{background-color:#ff851b !important}.bg-fuchsia{background-color:#f012be !important}.bg-purple{background-color:#605ca8 !important}.bg-maroon{background-color:#d81b60 !important}.bg-gray-active{color:#000;background-color:#b5bbc8 !important}.bg-black-active{background-color:#000 !important}.bg-red-active,.modal-danger .modal-header,.modal-danger .modal-footer{background-color:#d33724 !important}.bg-yellow-active,.modal-warning .modal-header,.modal-warning .modal-footer{background-color:#db8b0b !important}.bg-aqua-active,.modal-info .modal-header,.modal-info .modal-footer{background-color:#00a7d0 !important}.bg-blue-active{background-color:#005384 !important}.bg-light-blue-active,.modal-primary .modal-header,.modal-primary .modal-footer{background-color:#357ca5 !important}.bg-green-active,.modal-success .modal-header,.modal-success .modal-footer{background-color:#008d4c !important}.bg-navy-active{background-color:#001a35 !important}.bg-teal-active{background-color:#30bbbb !important}.bg-olive-active{background-color:#368763 !important}.bg-lime-active{background-color:#00e765 !important}.bg-orange-active{background-color:#ff7701 !important}.bg-fuchsia-active{background-color:#db0ead !important}.bg-purple-active{background-color:#555299 !important}.bg-maroon-active{background-color:#ca195a !important}[class^="bg-"].disabled{opacity:.65;filter:alpha(opacity=65)}.text-red{color:#dd4b39 !important}.text-yellow{color:#f39c12 !important}.text-aqua{color:#00c0ef !important}.text-blue{color:#0073b7 !important}.text-black{color:#111 !important}.text-light-blue{color:#3c8dbc !important}.text-green{color:#00a65a !important}.text-gray{color:#d2d6de !important}.text-navy{color:#001f3f !important}.text-teal{color:#39cccc !important}.text-olive{color:#3d9970 !important}.text-lime{color:#01ff70 !important}.text-orange{color:#ff851b !important}.text-fuchsia{color:#f012be !important}.text-purple{color:#605ca8 !important}.text-maroon{color:#d81b60 !important}.link-muted{color:#7a869d}.link-muted:hover,.link-muted:focus{color:#606c84}.link-black{color:#666}.link-black:hover,.link-black:focus{color:#999}.hide{display:none !important}.no-border{border:0 !important}.no-padding{padding:0 !important}.no-margin{margin:0 !important}.no-shadow{box-shadow:none!important}.list-unstyled,.chart-legend,.contacts-list,.users-list,.mailbox-attachments{list-style:none;margin:0;padding:0}.list-group-unbordered>.list-group-item{border-left:0;border-right:0;border-radius:0;padding-left:0;padding-right:0}.flat{border-radius:0 !important}.text-bold,.text-bold.table td,.text-bold.table th{font-weight:700}.text-sm{font-size:12px}.jqstooltip{padding:5px!important;width:auto!important;height:auto!important}.bg-teal-gradient{background:#39cccc !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #39cccc), color-stop(1, #7adddd)) !important;background:-ms-linear-gradient(bottom, #39cccc, #7adddd) !important;background:-moz-linear-gradient(center bottom, #39cccc 0, #7adddd 100%) !important;background:-o-linear-gradient(#7adddd, #39cccc) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#7adddd', endColorstr='#39cccc', GradientType=0) !important;color:#fff}.bg-light-blue-gradient{background:#3c8dbc !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #3c8dbc), color-stop(1, #67a8ce)) !important;background:-ms-linear-gradient(bottom, #3c8dbc, #67a8ce) !important;background:-moz-linear-gradient(center bottom, #3c8dbc 0, #67a8ce 100%) !important;background:-o-linear-gradient(#67a8ce, #3c8dbc) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#67a8ce', endColorstr='#3c8dbc', GradientType=0) !important;color:#fff}.bg-blue-gradient{background:#0073b7 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #0073b7), color-stop(1, #0089db)) !important;background:-ms-linear-gradient(bottom, #0073b7, #0089db) !important;background:-moz-linear-gradient(center bottom, #0073b7 0, #0089db 100%) !important;background:-o-linear-gradient(#0089db, #0073b7) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0089db', endColorstr='#0073b7', GradientType=0) !important;color:#fff}.bg-aqua-gradient{background:#00c0ef !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #00c0ef), color-stop(1, #14d1ff)) !important;background:-ms-linear-gradient(bottom, #00c0ef, #14d1ff) !important;background:-moz-linear-gradient(center bottom, #00c0ef 0, #14d1ff 100%) !important;background:-o-linear-gradient(#14d1ff, #00c0ef) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#14d1ff', endColorstr='#00c0ef', GradientType=0) !important;color:#fff}.bg-yellow-gradient{background:#f39c12 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #f39c12), color-stop(1, #f7bc60)) !important;background:-ms-linear-gradient(bottom, #f39c12, #f7bc60) !important;background:-moz-linear-gradient(center bottom, #f39c12 0, #f7bc60 100%) !important;background:-o-linear-gradient(#f7bc60, #f39c12) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f7bc60', endColorstr='#f39c12', GradientType=0) !important;color:#fff}.bg-purple-gradient{background:#605ca8 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #605ca8), color-stop(1, #9491c4)) !important;background:-ms-linear-gradient(bottom, #605ca8, #9491c4) !important;background:-moz-linear-gradient(center bottom, #605ca8 0, #9491c4 100%) !important;background:-o-linear-gradient(#9491c4, #605ca8) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#9491c4', endColorstr='#605ca8', GradientType=0) !important;color:#fff}.bg-green-gradient{background:#00a65a !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #00a65a), color-stop(1, #00ca6d)) !important;background:-ms-linear-gradient(bottom, #00a65a, #00ca6d) !important;background:-moz-linear-gradient(center bottom, #00a65a 0, #00ca6d 100%) !important;background:-o-linear-gradient(#00ca6d, #00a65a) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ca6d', endColorstr='#00a65a', GradientType=0) !important;color:#fff}.bg-red-gradient{background:#dd4b39 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #dd4b39), color-stop(1, #e47365)) !important;background:-ms-linear-gradient(bottom, #dd4b39, #e47365) !important;background:-moz-linear-gradient(center bottom, #dd4b39 0, #e47365 100%) !important;background:-o-linear-gradient(#e47365, #dd4b39) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e47365', endColorstr='#dd4b39', GradientType=0) !important;color:#fff}.bg-black-gradient{background:#111 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #111), color-stop(1, #2b2b2b)) !important;background:-ms-linear-gradient(bottom, #111, #2b2b2b) !important;background:-moz-linear-gradient(center bottom, #111 0, #2b2b2b 100%) !important;background:-o-linear-gradient(#2b2b2b, #111) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#2b2b2b', endColorstr='#111111', GradientType=0) !important;color:#fff}.bg-maroon-gradient{background:#d81b60 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #d81b60), color-stop(1, #e73f7c)) !important;background:-ms-linear-gradient(bottom, #d81b60, #e73f7c) !important;background:-moz-linear-gradient(center bottom, #d81b60 0, #e73f7c 100%) !important;background:-o-linear-gradient(#e73f7c, #d81b60) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e73f7c', endColorstr='#d81b60', GradientType=0) !important;color:#fff}.description-block .description-icon{font-size:16px}.no-pad-top{padding-top:0}.position-static{position:static!important}.list-header{font-size:15px;padding:10px 4px;font-weight:bold;color:#666}.list-seperator{height:1px;background:#f4f4f4;margin:15px 0 9px 0}.list-link>a{padding:4px;color:#777}.list-link>a:hover{color:#222}.font-light{font-weight:300}.user-block:before,.user-block:after{content:" ";display:table}.user-block:after{clear:both}.user-block img{width:40px;height:40px;float:left}.user-block .username,.user-block .description,.user-block .comment{display:block;margin-left:50px}.user-block .username{font-size:16px;font-weight:600}.user-block .description{color:#999;font-size:13px}.user-block.user-block-sm .username,.user-block.user-block-sm .description,.user-block.user-block-sm .comment{margin-left:40px}.user-block.user-block-sm .username{font-size:14px}.img-sm,.img-md,.img-lg,.box-comments .box-comment img,.user-block.user-block-sm img{float:left}.img-sm,.box-comments .box-comment img,.user-block.user-block-sm img{width:30px!important;height:30px!important}.img-sm+.img-push{margin-left:40px}.img-md{width:60px;height:60px}.img-md+.img-push{margin-left:70px}.img-lg{width:100px;height:100px}.img-lg+.img-push{margin-left:110px}.img-bordered{border:3px solid #d2d6de;padding:3px}.img-bordered-sm{border:2px solid #d2d6de;padding:2px}.attachment-block{border:1px solid #f4f4f4;padding:5px;margin-bottom:10px;background:#f7f7f7}.attachment-block .attachment-img{max-width:100px;max-height:100px;height:auto;float:left}.attachment-block .attachment-pushed{margin-left:110px}.attachment-block .attachment-heading{margin:0}.attachment-block .attachment-text{color:#555}.connectedSortable{min-height:100px}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sort-highlight{background:#f4f4f4;border:1px dashed #ddd;margin-bottom:10px}.full-opacity-hover{opacity:.65;filter:alpha(opacity=65)}.full-opacity-hover:hover{opacity:1;filter:alpha(opacity=100)}.chart{position:relative;overflow:hidden;width:100%}.chart svg,.chart canvas{width:100%!important}@media print{.no-print,.main-sidebar,.left-side,.main-header,.content-header{display:none!important}.content-wrapper,.right-side,.main-footer{margin-left:0!important;min-height:0!important;-webkit-transform:translate(0, 0) !important;-ms-transform:translate(0, 0) !important;-o-transform:translate(0, 0) !important;transform:translate(0, 0) !important}.fixed .content-wrapper,.fixed .right-side{padding-top:0!important}.invoice{width:100%;border:0;margin:0;padding:0}.invoice-col{float:left;width:33.3333333%}.table-responsive{overflow:auto}.table-responsive>.table tr th,.table-responsive>.table tr td{white-space:normal!important}} \ No newline at end of file diff --git a/public/assets/css/ansi-colors.css b/public/assets/css/ansi-colors.css new file mode 100644 index 00000000..47a14dbf --- /dev/null +++ b/public/assets/css/ansi-colors.css @@ -0,0 +1,33 @@ +.ansi_color_bg_black { background-color: #FFF } +.ansi_color_bg_red { background-color: #900 } +.ansi_color_bg_green { background-color: #090 } +.ansi_color_bg_yellow { background-color: #990 } +.ansi_color_bg_blue { background-color: #009 } +.ansi_color_bg_magenta { background-color: #909 } +.ansi_color_bg_cyan { background-color: #099 } +.ansi_color_bg_white { background-color: #000 } +.ansi_color_bg_brblack { background-color: #FFF } +.ansi_color_bg_brred { background-color: #F00 } +.ansi_color_bg_brgreen { background-color: #0F0 } +.ansi_color_bg_bryellow { background-color: #FF0 } +.ansi_color_bg_brblue { background-color: #00F } +.ansi_color_bg_brmagenta { background-color: #F0F } +.ansi_color_bg_brcyan { background-color: #0FF } +.ansi_color_bg_brwhite { background-color: #000 } + +.ansi_color_fg_black { color: #FFF } +.ansi_color_fg_red { color: #900 } +.ansi_color_fg_green { color: #090 } +.ansi_color_fg_yellow { color: #990 } +.ansi_color_fg_blue { color: #009 } +.ansi_color_fg_magenta { color: #909 } +.ansi_color_fg_cyan { color: #099 } +.ansi_color_fg_white { color: #000 } +.ansi_color_fg_brblack { color: #FFF } +.ansi_color_fg_brred { color: #F00 } +.ansi_color_fg_brgreen { color: #0F0 } +.ansi_color_fg_bryellow { color: #FF0 } +.ansi_color_fg_brblue { color: #00F } +.ansi_color_fg_brmagenta { color: #F0F } +.ansi_color_fg_brcyan { color: #0FF } +.ansi_color_fg_brwhite { color: #000 } diff --git a/public/assets/css/bootstrap.min.css b/public/assets/css/bootstrap.min.css deleted file mode 100644 index e3cf0d55..00000000 --- a/public/assets/css/bootstrap.min.css +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * Bootstrap v3.0.0 - * - * Copyright 2013 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world by @mdo and @fat. - *//*! normalize.css v2.1.0 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{margin:.67em 0;font-size:2em}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}hr{height:0;-moz-box-sizing:content-box;box-sizing:content-box}mark{color:#000;background:#ff0}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid #c0c0c0}legend{padding:0;border:0}button,input,select,textarea{margin:0;font-family:inherit;font-size:100%}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{padding:0;box-sizing:border-box}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}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,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}@page{margin:2cm .5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.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}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.428571429;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 #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}img{vertical-align:middle}.img-responsive{display:inline-block;height:auto;max-width:100%}.img-rounded{border-radius:6px}.img-circle{border-radius:500px}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16.099999999999998px;font-weight:200;line-height:1.4}@media(min-width:768px){.lead{font-size:21px}}small{font-size:85%}cite{font-style:normal}.text-muted{color:#999}.text-primary{color:#428bca}.text-warning{color:#c09853}.text-danger{color:#b94a48}.text-success{color:#468847}.text-info{color:#3a87ad}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:500;line-height:1.1}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:#999}h1,h2,h3{margin-top:20px;margin-bottom:10px}h4,h5,h6{margin-top:10px;margin-bottom:10px}h1,.h1{font-size:38px}h2,.h2{font-size:32px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}h1 small,.h1 small{font-size:24px}h2 small,.h2 small{font-size:18px}h3 small,.h3 small,h4 small,.h4 small{font-size:14px}.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;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dt,dd{line-height:1.428571429}dt{font-weight:bold}dd{margin-left:0}.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}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{font-size:17.5px;font-weight:300;line-height:1.25}blockquote p:last-child{margin-bottom:0}blockquote small{display:block;line-height:1.428571429;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:1.428571429}code,pre{font-family:Monaco,Menlo,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;white-space:nowrap;background-color:#f9f2f4;border-radius:4px}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.428571429;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}@media(min-width:768px){.row{margin-right:-15px;margin-left:-15px}}.row .row{margin-right:-15px;margin-left:-15px}.col-1,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-10,.col-11,.col-12,.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,.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{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-1,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-10,.col-11,.col-12{float:left}.col-1{width:8.333333333333332%}.col-2{width:16.666666666666664%}.col-3{width:25%}.col-4{width:33.33333333333333%}.col-5{width:41.66666666666667%}.col-6{width:50%}.col-7{width:58.333333333333336%}.col-8{width:66.66666666666666%}.col-9{width:75%}.col-10{width:83.33333333333334%}.col-11{width:91.66666666666666%}.col-12{width:100%}@media(min-width:768px){.container{max-width:728px}.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-1{width:8.333333333333332%}.col-sm-2{width:16.666666666666664%}.col-sm-3{width:25%}.col-sm-4{width:33.33333333333333%}.col-sm-5{width:41.66666666666667%}.col-sm-6{width:50%}.col-sm-7{width:58.333333333333336%}.col-sm-8{width:66.66666666666666%}.col-sm-9{width:75%}.col-sm-10{width:83.33333333333334%}.col-sm-11{width:91.66666666666666%}.col-sm-12{width:100%}.col-push-1{left:8.333333333333332%}.col-push-2{left:16.666666666666664%}.col-push-3{left:25%}.col-push-4{left:33.33333333333333%}.col-push-5{left:41.66666666666667%}.col-push-6{left:50%}.col-push-7{left:58.333333333333336%}.col-push-8{left:66.66666666666666%}.col-push-9{left:75%}.col-push-10{left:83.33333333333334%}.col-push-11{left:91.66666666666666%}.col-pull-1{right:8.333333333333332%}.col-pull-2{right:16.666666666666664%}.col-pull-3{right:25%}.col-pull-4{right:33.33333333333333%}.col-pull-5{right:41.66666666666667%}.col-pull-6{right:50%}.col-pull-7{right:58.333333333333336%}.col-pull-8{right:66.66666666666666%}.col-pull-9{right:75%}.col-pull-10{right:83.33333333333334%}.col-pull-11{right:91.66666666666666%}}@media(min-width:992px){.container{max-width:940px}.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-1{width:8.333333333333332%}.col-lg-2{width:16.666666666666664%}.col-lg-3{width:25%}.col-lg-4{width:33.33333333333333%}.col-lg-5{width:41.66666666666667%}.col-lg-6{width:50%}.col-lg-7{width:58.333333333333336%}.col-lg-8{width:66.66666666666666%}.col-lg-9{width:75%}.col-lg-10{width:83.33333333333334%}.col-lg-11{width:91.66666666666666%}.col-lg-12{width:100%}.col-offset-1{margin-left:8.333333333333332%}.col-offset-2{margin-left:16.666666666666664%}.col-offset-3{margin-left:25%}.col-offset-4{margin-left:33.33333333333333%}.col-offset-5{margin-left:41.66666666666667%}.col-offset-6{margin-left:50%}.col-offset-7{margin-left:58.333333333333336%}.col-offset-8{margin-left:66.66666666666666%}.col-offset-9{margin-left:75%}.col-offset-10{margin-left:83.33333333333334%}.col-offset-11{margin-left:91.66666666666666%}}@media(min-width:1200px){.container{max-width:1170px}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{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.428571429;vertical-align:top;border-top:1px solid #ddd}.table thead>tr>th{vertical-align:bottom}.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-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-"]{display:table-column;float:none}table td[class^="col-"],table th[class^="col-"]{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>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;border-color:#d6e9c6}.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;border-color:#eed3d7}.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;border-color:#fbeed5}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td{background-color:#d0e9c6;border-color:#c9e2b3}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td{background-color:#ebcccc;border-color:#e6c1c7}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td{background-color:#faf2cc;border-color:#f8e5be}fieldset{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;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}select[multiple],select[size]{height:auto}select optgroup{font-family:inherit;font-size:inherit;font-style:inherit}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}input[type="number"]::-webkit-outer-spin-button,input[type="number"]::-webkit-inner-spin-button{height:auto}.form-control:-moz-placeholder{color:#999}.form-control::-moz-placeholder{color:#999}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control{display:block;width:100%;height:38px;padding:8px 12px;font-size:14px;line-height:1.428571429;color:#555;vertical-align:middle;background-color:#fff;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-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:rgba(82,168,236,0.8);outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee}textarea.form-control{height:auto}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:20px;padding-left:20px;margin-top:10px;margin-bottom:10px;vertical-align:middle}.radio label,.checkbox label{display:inline;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"]{float:left;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}.form-control.input-large{height:56px;padding:14px 16px;font-size:18px;border-radius:6px}.form-control.input-small{height:30px;padding:5px 10px;font-size:12px;border-radius:3px}select.input-large{height:56px;line-height:56px}select.input-small{height:30px;line-height:30px}.has-warning .help-block,.has-warning .control-label{color:#c09853}.has-warning .form-control{padding-right:32px;border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.has-warning .input-group-addon{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.has-error .help-block,.has-error .control-label{color:#b94a48}.has-error .form-control{padding-right:32px;border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.has-error .input-group-addon{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.has-success .help-block,.has-success .control-label{color:#468847}.has-success .form-control{padding-right:32px;border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.has-success .input-group-addon{color:#468847;background-color:#dff0d8;border-color:#468847}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}.input-group{display:table;border-collapse:separate}.input-group.col{float:none;padding-right:0;padding-left:0}.input-group .form-control{width:100%;margin-bottom:0}.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:8px 12px;font-size:14px;font-weight:normal;line-height:1.428571429;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-group-addon.input-small{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-large{padding:14px 16px;font-size:18px;border-radius:6px}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){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>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-4px}.input-group-btn>.btn:hover,.input-group-btn>.btn:active{z-index:2}.form-inline .form-control,.form-inline .radio,.form-inline .checkbox{display:inline-block}.form-inline .radio,.form-inline .checkbox{margin-top:0;margin-bottom:0}.form-horizontal .control-label{padding-top:6px}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}@media(min-width:768px){.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}}.form-horizontal .form-group .row{margin-right:-15px;margin-left:-15px}@media(min-width:768px){.form-horizontal .control-label{text-align:right}}.btn{display:inline-block;padding:8px 12px;margin-bottom:0;font-size:14px;font-weight:500;line-height:1.428571429;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;border:1px solid transparent;border-radius:4px}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#fff;text-decoration:none}.btn:active,.btn.active{outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:default;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#fff;background-color:#474949;border-color:#474949}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active{background-color:#3a3c3c;border-color:#2e2f2f}.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:#474949;border-color:#474949}.btn-primary{color:#fff;background-color:#428bca;border-color:#428bca}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active{background-color:#357ebd;border-color:#3071a9}.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:#428bca}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#f0ad4e}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active{background-color:#eea236;border-color:#ec971f}.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:#f0ad4e}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d9534f}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active{background-color:#d43f3a;border-color:#c9302c}.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:#d9534f}.btn-success{color:#fff;background-color:#5cb85c;border-color:#5cb85c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active{background-color:#4cae4c;border-color:#449d44}.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:#5cb85c}.btn-info{color:#fff;background-color:#5bc0de;border-color:#5bc0de}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active{background-color:#46b8da;border-color:#31b0d5}.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:#5bc0de}.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:#333;text-decoration:none}.btn-large{padding:14px 16px;font-size:18px;border-radius:6px}.btn-small{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0}.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;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-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 #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.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;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.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.428571429;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#fff;text-decoration:none;background-color:#357ebd;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca,0%,#357ebd,100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#357ebd;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca,0%,#357ebd,100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.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-header{display:block;padding:3px 20px;font-size:12px;line-height:1.428571429;color:#999}.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{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.list-group{padding-left:0;margin-bottom:20px;background-color:#fff}.list-group-item{position:relative;display:block;padding:10px 30px 10px 15px;margin-bottom:-1px;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-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;margin-right:-15px}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item .list-group-item-text{color:#555}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#f5f5f5}a.list-group-item.active{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}a.list-group-item.active .list-group-item-heading{color:inherit}a.list-group-item.active .list-group-item-text{color:#e1edf7}.panel{padding:15px;margin-bottom:20px;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-heading{padding:10px 15px;margin:-15px -15px 15px;background-color:#f5f5f5;border-bottom:1px solid #ddd;border-top-right-radius:3px;border-top-left-radius:3px}.panel-title{margin-top:0;margin-bottom:0;font-size:17.5px;font-weight:500}.panel-footer{padding:10px 15px;margin:15px -15px -15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-primary{border-color:#428bca}.panel-primary .panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success .panel-heading{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.panel-warning{border-color:#fbeed5}.panel-warning .panel-heading{color:#c09853;background-color:#fcf8e3;border-color:#fbeed5}.panel-danger{border-color:#eed3d7}.panel-danger .panel-heading{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.panel-info{border-color:#bce8f1}.panel-info .panel-heading{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.list-group-flush{margin:15px -15px -15px}.list-group-flush .list-group-item{border-width:1px 0}.list-group-flush .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.list-group-flush .list-group-item:last-child{border-bottom:0}.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,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;border-radius:6px}.well-small{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;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.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:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav>li+.nav-header{margin-top:9px}.nav.open>a,.nav.open>a:hover,.nav.open>a:focus{color:#fff;background-color:#428bca;border-color:#428bca}.nav.open>a .caret,.nav.open>a:hover .caret,.nav.open>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.nav>.pull-right{float:right}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.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.428571429;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee}.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{display:table-cell;float:none;width:1%}.nav-tabs.nav-justified>li>a{text-align:center}.nav-tabs.nav-justified>li>a{margin-right:0;border-bottom:1px solid #ddd}.nav-tabs.nav-justified>.active>a{border-bottom-color:#fff}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:5px}.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>a{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{display:table-cell;float:none;width:1%}.nav-justified>li>a{text-align:center}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-bottom:1px solid #ddd}.nav-tabs-justified>.active>a{border-bottom-color:#fff}.tabbable:before,.tabbable:after{display:table;content:" "}.tabbable:after{clear:both}.tabbable:before,.tabbable:after{display:table;content:" "}.tabbable:after{clear:both}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.nav .caret{border-top-color:#428bca;border-bottom-color:#428bca}.nav a:hover .caret{border-top-color:#2a6496;border-bottom-color:#2a6496}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;padding-right:15px;padding-left:15px;margin-bottom:20px;background-color:#eee;border-radius:4px}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}.navbar-nav{margin-top:10px;margin-bottom:15px}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px;line-height:20px;color:#777;border-radius:4px}.navbar-nav>li>a:hover,.navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-nav>.active>a,.navbar-nav>.active>a:hover,.navbar-nav>.active>a:focus{color:#555;background-color:#d5d5d5}.navbar-nav>.disabled>a,.navbar-nav>.disabled>a:hover,.navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-nav.pull-right{width:100%}.navbar-static-top{border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;border-radius:0}.navbar-fixed-top{top:0}.navbar-fixed-bottom{bottom:0;margin-bottom:0}.navbar-brand{display:block;max-width:200px;padding:15px 15px;margin-right:auto;margin-left:auto;font-size:18px;font-weight:500;line-height:20px;color:#777;text-align:center}.navbar-brand:hover,.navbar-brand:focus{color:#5e5e5e;text-decoration:none;background-color:transparent}.navbar-toggle{position:absolute;top:9px;right:10px;width:48px;height:32px;padding:8px 12px;background-color:transparent;border:1px solid #ddd;border-radius:4px}.navbar-toggle:hover,.navbar-toggle:focus{background-color:#ddd}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;background-color:#ccc;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}.navbar-form{margin-top:6px;margin-bottom:6px}.navbar-form .form-control,.navbar-form .radio,.navbar-form .checkbox{display:inline-block}.navbar-form .radio,.navbar-form .checkbox{margin-top:0;margin-bottom:0}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-nav>.dropdown>a:hover .caret,.navbar-nav>.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar-nav>.open>a,.navbar-nav>.open>a:hover,.navbar-nav>.open>a:focus{color:#555;background-color:#d5d5d5}.navbar-nav>.open>a .caret,.navbar-nav>.open>a:hover .caret,.navbar-nav>.open>a:focus .caret{border-top-color:#555;border-bottom-color:#555}.navbar-nav>.dropdown>a .caret{border-top-color:#777;border-bottom-color:#777}.navbar-nav.pull-right>li>.dropdown-menu,.navbar-nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar-inverse{background-color:#222}.navbar-inverse .navbar-brand{color:#999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .navbar-nav>li>a{color:#999}.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-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.dropdown>a:hover .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-nav>.dropdown>a .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .navbar-nav>.open>a .caret,.navbar-inverse .navbar-nav>.open>a:hover .caret,.navbar-inverse .navbar-nav>.open>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}@media screen and (min-width:768px){.navbar-brand{float:left;margin-right:5px;margin-left:-15px}.navbar-nav{float:left;margin-top:0;margin-bottom:0}.navbar-nav>li{float:left}.navbar-nav>li>a{border-radius:0}.navbar-nav.pull-right{float:right;width:auto}.navbar-toggle{position:relative;top:auto;left:auto;display:none}.nav-collapse.collapse{display:block!important;height:auto!important;overflow:visible!important}}.navbar-btn{margin-top:6px}.navbar-text{margin-top:15px;margin-bottom:15px}.navbar-link{color:#777}.navbar-link:hover{color:#333}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.btn .caret{border-top-color:#fff}.dropup .btn .caret{border-bottom-color:#fff}.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:active,.btn-group-vertical>.btn:active{z-index:2}.btn-group .btn+.btn{margin-left:-1px}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar .btn-group{float:left}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group,.btn-toolbar>.btn-group+.btn-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-bottom-left-radius:0;border-top-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-bottom-left-radius:0;border-top-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-large+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn .caret{margin-left:0}.btn-large .caret{border-width:5px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-group-vertical>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn+.btn{margin-top:-1px}.btn-group-vertical .btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical .btn:first-child{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical .btn:last-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%}.btn-group-justified .btn{display:table-cell;float:none;width:1%}.btn-group[data-toggle="buttons"]>.btn>input[type="radio"],.btn-group[data-toggle="buttons"]>.btn>input[type="checkbox"]{display:none}.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:#999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{float:left;padding:4px 12px;line-height:1.428571429;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination>li:first-child>a,.pagination>li:first-child>span{border-left-width:1px;border-bottom-left-radius:4px;border-top-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>a:focus,.pagination>.active>a,.pagination>.active>span{background-color:#f5f5f5}.pagination>.active>a,.pagination>.active>span{color:#999;cursor:default}.pagination>.disabled>span,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;cursor:not-allowed;background-color:#fff}.pagination-large>li>a,.pagination-large>li>span{padding:14px 16px;font-size:18px}.pagination-large>li:first-child>a,.pagination-large>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-large>li:last-child>a,.pagination-large>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-small>li>a,.pagination-small>li>span{padding:5px 10px;font-size:12px}.pagination-small>li:first-child>a,.pagination-small>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-small>li:last-child>a,.pagination-small>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:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.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:#f5f5f5}.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:#999;cursor:not-allowed;background-color:#fff}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:auto;overflow-y:scroll}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.fade.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.modal-dialog{position:relative;top:0;right:0;left:0;z-index:1050;width:auto;padding:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1030;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.fade.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{min-height:16.428571429px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.428571429}.modal-body{position:relative;padding:20px}.modal-footer{padding:19px 20px 20px;margin-top:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.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}@media screen and (min-width:768px){.modal-dialog{right:auto;left:50%;width:560px;padding-top:30px;padding-bottom:30px;margin-left:-280px}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}}.tooltip{position:absolute;z-index:1030;display:block;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:1;filter:alpha(opacity=100)}.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:rgba(0,0,0,0.9);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-top-color:rgba(0,0,0,0.9);border-width:5px 5px 0}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-top-color:rgba(0,0,0,0.9);border-width:5px 5px 0}.tooltip.top-right .tooltip-arrow{right:5px;bottom:0;border-top-color:rgba(0,0,0,0.9);border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:rgba(0,0,0,0.9);border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:rgba(0,0,0,0.9);border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:rgba(0,0,0,0.9);border-width:0 5px 5px}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-bottom-color:rgba(0,0,0,0.9);border-width:0 5px 5px}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-bottom-color:rgba(0,0,0,0.9);border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);background-clip:padding-box;-webkit-bg-clip:padding-box;-moz-bg-clip:padding}.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{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0;content:" "}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0;content:" "}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0;content:" "}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0;content:" "}.alert{padding:10px 35px 10px 15px;margin-bottom:20px;color:#c09853;background-color:#fcf8e3;border:1px solid #fbeed5;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert hr{border-top-color:#f8e5be}.alert .alert-link{font-weight:500;color:#a47e3c}.alert .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#356635}.alert-danger{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger hr{border-top-color:#e6c1c7}.alert-danger .alert-link{color:#953b39}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#2d6987}.alert-block{padding-top:15px;padding-bottom:15px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.thumbnail,.img-thumbnail{padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail{display:block}.thumbnail>img,.img-thumbnail{display:inline-block;height:auto;max-width:100%}a.thumbnail:hover,a.thumbnail:focus{border-color:#428bca}.thumbnail>img{margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#333}.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}.label{display:inline;padding:.25em .6em;font-size:75%;font-weight:500;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#999;border-radius:.25em}.label[href]:hover,.label[href]:focus{color:#fff;text-decoration:none;cursor:pointer;background-color:#808080}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.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:middle;background-color:#999;border-radius:10px}.badge:empty{display:none}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.btn .badge{position:relative;top:-1px}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 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,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar{background-color:#428bca;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-color:#d9534f;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-color:#5cb85c;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-color:#f0ad4e;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px;cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.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;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:inline-block;height:auto;max-width:100%;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,0.6);opacity:.5;filter:alpha(opacity=50)}.carousel-control.left{background-color:rgba(0,0,0,0.0001);background-color:transparent;background-image:-webkit-gradient(linear,0 top,100% top,from(rgba(0,0,0,0.5)),to(rgba(0,0,0,0.0001)));background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,0.5) 0),color-stop(rgba(0,0,0,0.0001) 100%));background-image:-moz-linear-gradient(left,rgba(0,0,0,0.5) 0,rgba(0,0,0,0.0001) 100%);background-image:linear-gradient(to right,rgba(0,0,0,0.5) 0,rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000',endColorstr='#00000000',GradientType=1)}.carousel-control.right{right:0;left:auto;background-color:rgba(0,0,0,0.5);background-color:transparent;background-image:-webkit-gradient(linear,0 top,100% top,from(rgba(0,0,0,0.0001)),to(rgba(0,0,0,0.5)));background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,0.0001) 0),color-stop(rgba(0,0,0,0.5) 100%));background-image:-moz-linear-gradient(left,rgba(0,0,0,0.0001) 0,rgba(0,0,0,0.5) 100%);background-image:linear-gradient(to right,rgba(0,0,0,0.0001) 0,rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000',endColorstr='#80000000',GradientType=1)}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .glyphicon,.carousel-control .icon-prev,.carousel-control .icon-next{position:absolute;top:50%;left:50%;z-index:5;display:inline-block;width:20px;height:20px;margin-top:-10px;margin-left:-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:120px;padding-left:0;margin-left:-60px;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;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,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.jumbotron{padding:30px;margin-bottom:30px;font-size:21px;font-weight:200;line-height:2.1428571435;color:inherit;background-color:#eee}.jumbotron h1{line-height:1;color:inherit}.jumbotron p{line-height:1.4}@media screen and (min-width:768px){.jumbotron{padding:50px 60px;border-radius:6px}.jumbotron h1{font-size:63px}}.clearfix:before,.clearfix:after{display:table;content:" "}.clearfix:after{clear:both}.pull-right{float:right}.pull-left{float:left}.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}.affix{position:fixed}@-ms-viewport{width:device-width}@media screen and (max-width:400px){@-ms-viewport{width:320px}}.hidden{display:none!important;visibility:hidden!important}.visible-sm{display:block!important}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}.visible-md{display:none!important}tr.visible-md{display:none!important}th.visible-md,td.visible-md{display:none!important}.visible-lg{display:none!important}tr.visible-lg{display:none!important}th.visible-lg,td.visible-lg{display:none!important}.hidden-sm{display:none!important}tr.hidden-sm{display:none!important}th.hidden-sm,td.hidden-sm{display:none!important}.hidden-md{display:block!important}tr.hidden-md{display:table-row!important}th.hidden-md,td.hidden-md{display:table-cell!important}.hidden-lg{display:block!important}tr.hidden-lg{display:table-row!important}th.hidden-lg,td.hidden-lg{display:table-cell!important}@media(min-width:768px) and (max-width:991px){.visible-sm{display:none!important}tr.visible-sm{display:none!important}th.visible-sm,td.visible-sm{display:none!important}.visible-md{display:block!important}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}.visible-lg{display:none!important}tr.visible-lg{display:none!important}th.visible-lg,td.visible-lg{display:none!important}.hidden-sm{display:block!important}tr.hidden-sm{display:table-row!important}th.hidden-sm,td.hidden-sm{display:table-cell!important}.hidden-md{display:none!important}tr.hidden-md{display:none!important}th.hidden-md,td.hidden-md{display:none!important}.hidden-lg{display:block!important}tr.hidden-lg{display:table-row!important}th.hidden-lg,td.hidden-lg{display:table-cell!important}}@media(min-width:992px){.visible-sm{display:none!important}tr.visible-sm{display:none!important}th.visible-sm,td.visible-sm{display:none!important}.visible-md{display:none!important}tr.visible-md{display:none!important}th.visible-md,td.visible-md{display:none!important}.visible-lg{display:block!important}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}.hidden-sm{display:block!important}tr.hidden-sm{display:table-row!important}th.hidden-sm,td.hidden-sm{display:table-cell!important}.hidden-md{display:block!important}tr.hidden-md{display:table-row!important}th.hidden-md,td.hidden-md{display:table-cell!important}.hidden-lg{display:none!important}tr.hidden-lg{display:none!important}th.hidden-lg,td.hidden-lg{display:none!important}}.visible-print{display:none!important}tr.visible-print{display:none!important}th.visible-print,td.visible-print{display:none!important}@media print{.visible-print{display:block!important}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}.hidden-print{display:none!important}tr.hidden-print{display:none!important}th.hidden-print,td.hidden-print{display:none!important}} \ No newline at end of file diff --git a/public/assets/css/phpci.css b/public/assets/css/phpci.css deleted file mode 100644 index ce34fe82..00000000 --- a/public/assets/css/phpci.css +++ /dev/null @@ -1,152 +0,0 @@ -body -{ - background: #f9f9f9; - font-family: Roboto, Arial, Sans-Serif; - font-style: normal; - font-weight: 300; - padding-top: 70px; -} - - strong, th, .control-label { - font-weight: 500; - } - - .btn, .dropdown-menu>li>a, .controls, .controls input, .controls label { - font-weight: 300; - } - -#content -{ - -border: 10px solid #369; - padding: 10px; -} - -td .label { - margin-right: 5px; - line-height: 2; -} -#project-overview td .label { - margin-right: 0; -} - -.success-message { - background-color: #4F8A10; -} - -.error-message { - background-color: #FF4747; -} - -#latest-builds td, -#project-overview td, -#users td { - vertical-align: middle; -} - -.widget-title, .modal-header, .table th, div.dataTables_wrapper .ui-widget-header, .ui-dialog .ui-dialog-titlebar { - background-color: #efefef; - background-image: -webkit-gradient(linear, 0 0%, 0 100%, from(#fdfdfd), to(#eaeaea)); - background-image: -webkit-linear-gradient(top, #fdfdfd 0%, #eaeaea 100%); - background-image: -moz-linear-gradient(top, #fdfdfd 0%, #eaeaea 100%); - background-image: -ms-linear-gradient(top, #fdfdfd 0%, #eaeaea 100%); - background-image: -o-linear-gradient(top, #fdfdfd 0%, #eaeaea 100%); - background-image: -linear-gradient(top, #fdfdfd 0%, #eaeaea 100%); - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fdfdfd', endColorstr='#eaeaea',GradientType=0 ); /* IE6-9 */ - border-bottom: 1px solid #CDCDCD; -} - -#title -{ - border-bottom: 1px solid #ccc; - margin: -10px -10px 15px -10px; - padding: 10px; -} - - #title h1 - { - font-size: 2em; - margin: 0; - padding: 0; - } - - h2 { - color: #246; - font-size: 1.8em; - } - -.icon-build-ok -{ - background: url('../img/icon-build-ok.png') no-repeat top left; -} - -.icon-build-failed -{ - background: url('../img/icon-build-failed.png') no-repeat top left; -} - -.icon-build-pending -{ - background: url('../img/icon-build-pending.png') no-repeat top left; -} - -.icon-build-running -{ - background: url('../img/icon-build-running.png') no-repeat top left; -} - -.navbar-brand { - padding: 10px 15px; -} - -.box { - background: #fff; - box-shadow: 0 0 5px rgba(0,0,0,0.1); - margin-bottom: 15px; - padding: 10px; -} - .box .title { - border-bottom: 1px solid #eee; - cursor: move; - font-size: 1.2em; - margin: 0; - margin-bottom: 20px; - padding: 8px 0; - } - - .box .box-content { - } - - .box .box-content table, .box .box-content pre { - margin-bottom: 0; - } - - .box .box-content pre { - background: #fff; - border: 0; - } - -.ui-sortable-placeholder { - border: 1px dashed #ccc; - background: #ffe; - height: 100px; - margin-bottom: 15px; - visibility: visible !important; -} - -.ui-sortable-placeholder * { visibility: hidden; } - -.ui-plugin { padding-top: 15px; } - - -#loading { - font-family: Roboto, Arial, Sans-Serif; - - background: #369; - color: #fff; - display: none; - position: fixed; - bottom: 20px; - font-size: 2em; - right: 20px; - padding: 15px 50px; -} \ No newline at end of file diff --git a/public/assets/fonts/glyphicons-halflings-regular.eot b/public/assets/fonts/glyphicons-halflings-regular.eot new file mode 100755 index 00000000..423bd5d3 Binary files /dev/null and b/public/assets/fonts/glyphicons-halflings-regular.eot differ diff --git a/public/assets/fonts/glyphicons-halflings-regular.svg b/public/assets/fonts/glyphicons-halflings-regular.svg new file mode 100755 index 00000000..44694887 --- /dev/null +++ b/public/assets/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/assets/fonts/glyphicons-halflings-regular.ttf b/public/assets/fonts/glyphicons-halflings-regular.ttf new file mode 100755 index 00000000..a498ef4e Binary files /dev/null and b/public/assets/fonts/glyphicons-halflings-regular.ttf differ diff --git a/public/assets/fonts/glyphicons-halflings-regular.woff b/public/assets/fonts/glyphicons-halflings-regular.woff new file mode 100755 index 00000000..d83c539b Binary files /dev/null and b/public/assets/fonts/glyphicons-halflings-regular.woff differ diff --git a/public/assets/img/ajax-loader.gif b/public/assets/img/ajax-loader.gif new file mode 100755 index 00000000..e65dcffc Binary files /dev/null and b/public/assets/img/ajax-loader.gif differ diff --git a/public/assets/img/ajax-loader1.gif b/public/assets/img/ajax-loader1.gif new file mode 100755 index 00000000..ff412c92 Binary files /dev/null and b/public/assets/img/ajax-loader1.gif differ diff --git a/public/assets/img/avatar.png b/public/assets/img/avatar.png new file mode 100755 index 00000000..ae1cb7b5 Binary files /dev/null and b/public/assets/img/avatar.png differ diff --git a/public/assets/img/avatar04.png b/public/assets/img/avatar04.png new file mode 100755 index 00000000..10d44d44 Binary files /dev/null and b/public/assets/img/avatar04.png differ diff --git a/public/assets/img/avatar2.png b/public/assets/img/avatar2.png new file mode 100755 index 00000000..65fa3552 Binary files /dev/null and b/public/assets/img/avatar2.png differ diff --git a/public/assets/img/avatar3.png b/public/assets/img/avatar3.png new file mode 100755 index 00000000..4d65e2c6 Binary files /dev/null and b/public/assets/img/avatar3.png differ diff --git a/public/assets/img/avatar5.png b/public/assets/img/avatar5.png new file mode 100755 index 00000000..670e6d95 Binary files /dev/null and b/public/assets/img/avatar5.png differ diff --git a/public/assets/img/blur-background04.jpg b/public/assets/img/blur-background04.jpg new file mode 100755 index 00000000..e208d771 Binary files /dev/null and b/public/assets/img/blur-background04.jpg differ diff --git a/public/assets/img/blur-background08.jpg b/public/assets/img/blur-background08.jpg new file mode 100755 index 00000000..3632e07d Binary files /dev/null and b/public/assets/img/blur-background08.jpg differ diff --git a/public/assets/img/blur-background09.jpg b/public/assets/img/blur-background09.jpg new file mode 100755 index 00000000..5ee3c17a Binary files /dev/null and b/public/assets/img/blur-background09.jpg differ diff --git a/public/assets/img/bootstrap-colorpicker/alpha-horizontal.png b/public/assets/img/bootstrap-colorpicker/alpha-horizontal.png new file mode 100755 index 00000000..7789694f Binary files /dev/null and b/public/assets/img/bootstrap-colorpicker/alpha-horizontal.png differ diff --git a/public/assets/img/bootstrap-colorpicker/alpha.png b/public/assets/img/bootstrap-colorpicker/alpha.png new file mode 100755 index 00000000..a4129735 Binary files /dev/null and b/public/assets/img/bootstrap-colorpicker/alpha.png differ diff --git a/public/assets/img/bootstrap-colorpicker/hue-horizontal.png b/public/assets/img/bootstrap-colorpicker/hue-horizontal.png new file mode 100755 index 00000000..4ad05e78 Binary files /dev/null and b/public/assets/img/bootstrap-colorpicker/hue-horizontal.png differ diff --git a/public/assets/img/bootstrap-colorpicker/hue.png b/public/assets/img/bootstrap-colorpicker/hue.png new file mode 100755 index 00000000..7f5f4749 Binary files /dev/null and b/public/assets/img/bootstrap-colorpicker/hue.png differ diff --git a/public/assets/img/bootstrap-colorpicker/saturation.png b/public/assets/img/bootstrap-colorpicker/saturation.png new file mode 100755 index 00000000..a1598af6 Binary files /dev/null and b/public/assets/img/bootstrap-colorpicker/saturation.png differ diff --git a/public/assets/img/build-failed.png b/public/assets/img/build-failed.png deleted file mode 100644 index 2aa1e125..00000000 Binary files a/public/assets/img/build-failed.png and /dev/null differ diff --git a/public/assets/img/build-ok.png b/public/assets/img/build-ok.png deleted file mode 100644 index e74657b7..00000000 Binary files a/public/assets/img/build-ok.png and /dev/null differ diff --git a/public/assets/img/credit/american-express.png b/public/assets/img/credit/american-express.png new file mode 100755 index 00000000..bd7fcfbe Binary files /dev/null and b/public/assets/img/credit/american-express.png differ diff --git a/public/assets/img/credit/cirrus.png b/public/assets/img/credit/cirrus.png new file mode 100755 index 00000000..18236bef Binary files /dev/null and b/public/assets/img/credit/cirrus.png differ diff --git a/public/assets/img/credit/mastercard.png b/public/assets/img/credit/mastercard.png new file mode 100755 index 00000000..2d7addcf Binary files /dev/null and b/public/assets/img/credit/mastercard.png differ diff --git a/public/assets/img/credit/mestro.png b/public/assets/img/credit/mestro.png new file mode 100755 index 00000000..9b5153a9 Binary files /dev/null and b/public/assets/img/credit/mestro.png differ diff --git a/public/assets/img/credit/paypal.png b/public/assets/img/credit/paypal.png new file mode 100755 index 00000000..1d8e8a24 Binary files /dev/null and b/public/assets/img/credit/paypal.png differ diff --git a/public/assets/img/credit/paypal2.png b/public/assets/img/credit/paypal2.png new file mode 100755 index 00000000..7c2fda42 Binary files /dev/null and b/public/assets/img/credit/paypal2.png differ diff --git a/public/assets/img/credit/visa.png b/public/assets/img/credit/visa.png new file mode 100755 index 00000000..9a378542 Binary files /dev/null and b/public/assets/img/credit/visa.png differ diff --git a/public/assets/img/favicon.png b/public/assets/img/favicon.png index 1e33cffa..0b6f60ec 100644 Binary files a/public/assets/img/favicon.png and b/public/assets/img/favicon.png differ diff --git a/public/assets/img/glyphicons-halflings-white.png b/public/assets/img/glyphicons-halflings-white.png index 3bf6484a..c1ab5815 100644 Binary files a/public/assets/img/glyphicons-halflings-white.png and b/public/assets/img/glyphicons-halflings-white.png differ diff --git a/public/assets/img/glyphicons-halflings.png b/public/assets/img/glyphicons-halflings.png index a9969993..f241c76f 100644 Binary files a/public/assets/img/glyphicons-halflings.png and b/public/assets/img/glyphicons-halflings.png differ diff --git a/public/assets/img/icon-build-failed.png b/public/assets/img/icon-build-failed.png index 9dde31bd..8ddd4b14 100644 Binary files a/public/assets/img/icon-build-failed.png and b/public/assets/img/icon-build-failed.png differ diff --git a/public/assets/img/icon-build-ok.png b/public/assets/img/icon-build-ok.png index 650e5f1f..90e5f35e 100644 Binary files a/public/assets/img/icon-build-ok.png and b/public/assets/img/icon-build-ok.png differ diff --git a/public/assets/img/icon-build-pending.png b/public/assets/img/icon-build-pending.png index f23051c7..2c8d1196 100644 Binary files a/public/assets/img/icon-build-pending.png and b/public/assets/img/icon-build-pending.png differ diff --git a/public/assets/img/icon-build-running.png b/public/assets/img/icon-build-running.png index 593f00b5..3e9b4f71 100644 Binary files a/public/assets/img/icon-build-running.png and b/public/assets/img/icon-build-running.png differ diff --git a/public/assets/img/icons.png b/public/assets/img/icons.png new file mode 100755 index 00000000..96c1afd3 Binary files /dev/null and b/public/assets/img/icons.png differ diff --git a/public/assets/img/logo-icon.png b/public/assets/img/logo-icon.png new file mode 100644 index 00000000..c3cb1143 Binary files /dev/null and b/public/assets/img/logo-icon.png differ diff --git a/public/assets/img/logo-icon.svg b/public/assets/img/logo-icon.svg new file mode 100644 index 00000000..1c44f696 --- /dev/null +++ b/public/assets/img/logo-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/img/logo-large.png b/public/assets/img/logo-large.png index dd3e1d34..08bb5604 100644 Binary files a/public/assets/img/logo-large.png and b/public/assets/img/logo-large.png differ diff --git a/public/assets/img/logo.png b/public/assets/img/logo.png index 4c56e4a7..61fbbebd 100644 Binary files a/public/assets/img/logo.png and b/public/assets/img/logo.png differ diff --git a/public/assets/img/sprite-skin-flat.png b/public/assets/img/sprite-skin-flat.png new file mode 100755 index 00000000..84a36653 Binary files /dev/null and b/public/assets/img/sprite-skin-flat.png differ diff --git a/public/assets/img/sprite-skin-nice.png b/public/assets/img/sprite-skin-nice.png new file mode 100755 index 00000000..25e4d509 Binary files /dev/null and b/public/assets/img/sprite-skin-nice.png differ diff --git a/public/assets/img/user-bg.png b/public/assets/img/user-bg.png new file mode 100755 index 00000000..a491bc0d Binary files /dev/null and b/public/assets/img/user-bg.png differ diff --git a/public/assets/img/user.jpg b/public/assets/img/user.jpg new file mode 100755 index 00000000..6eab19ae Binary files /dev/null and b/public/assets/img/user.jpg differ diff --git a/public/assets/img/user2.jpg b/public/assets/img/user2.jpg new file mode 100755 index 00000000..ce9e457a Binary files /dev/null and b/public/assets/img/user2.jpg differ diff --git a/public/assets/js/AdminLTE/app.min.js b/public/assets/js/AdminLTE/app.min.js new file mode 100644 index 00000000..679f18f1 --- /dev/null +++ b/public/assets/js/AdminLTE/app.min.js @@ -0,0 +1,13 @@ +/*! AdminLTE app.js + * ================ + * Main JS application file for AdminLTE v2. This file + * should be included in all pages. It controls some layout + * options and implements exclusive AdminLTE plugins. + * + * @Author Almsaeed Studio + * @Support + * @Email + * @version 2.3.0 + * @license MIT + */ +function _init(){"use strict";$.AdminLTE.layout={activate:function(){var a=this;a.fix(),a.fixSidebar(),$(window,".wrapper").resize(function(){a.fix(),a.fixSidebar()})},fix:function(){var a=$(".main-header").outerHeight()+$(".main-footer").outerHeight(),b=$(window).height(),c=$(".sidebar").height();if($("body").hasClass("fixed"))$(".content-wrapper, .right-side").css("min-height",b-$(".main-footer").outerHeight());else{var d;b>=c?($(".content-wrapper, .right-side").css("min-height",b-a),d=b-a):($(".content-wrapper, .right-side").css("min-height",c),d=c);var e=$($.AdminLTE.options.controlSidebarOptions.selector);"undefined"!=typeof e&&e.height()>d&&$(".content-wrapper, .right-side").css("min-height",e.height())}},fixSidebar:function(){return $("body").hasClass("fixed")?("undefined"==typeof $.fn.slimScroll&&window.console&&window.console.error("Error: the fixed layout requires the slimscroll plugin!"),void($.AdminLTE.options.sidebarSlimScroll&&"undefined"!=typeof $.fn.slimScroll&&($(".sidebar").slimScroll({destroy:!0}).height("auto"),$(".sidebar").slimscroll({height:$(window).height()-$(".main-header").height()+"px",color:"rgba(0,0,0,0.2)",size:"3px"})))):void("undefined"!=typeof $.fn.slimScroll&&$(".sidebar").slimScroll({destroy:!0}).height("auto"))}},$.AdminLTE.pushMenu={activate:function(a){var b=$.AdminLTE.options.screenSizes;$(a).on("click",function(a){a.preventDefault(),$(window).width()>b.sm-1?$("body").hasClass("sidebar-collapse")?$("body").removeClass("sidebar-collapse").trigger("expanded.pushMenu"):$("body").addClass("sidebar-collapse").trigger("collapsed.pushMenu"):$("body").hasClass("sidebar-open")?$("body").removeClass("sidebar-open").removeClass("sidebar-collapse").trigger("collapsed.pushMenu"):$("body").addClass("sidebar-open").trigger("expanded.pushMenu")}),$(".content-wrapper").click(function(){$(window).width()<=b.sm-1&&$("body").hasClass("sidebar-open")&&$("body").removeClass("sidebar-open")}),($.AdminLTE.options.sidebarExpandOnHover||$("body").hasClass("fixed")&&$("body").hasClass("sidebar-mini"))&&this.expandOnHover()},expandOnHover:function(){var a=this,b=$.AdminLTE.options.screenSizes.sm-1;$(".main-sidebar").hover(function(){$("body").hasClass("sidebar-mini")&&$("body").hasClass("sidebar-collapse")&&$(window).width()>b&&a.expand()},function(){$("body").hasClass("sidebar-mini")&&$("body").hasClass("sidebar-expanded-on-hover")&&$(window).width()>b&&a.collapse()})},expand:function(){$("body").removeClass("sidebar-collapse").addClass("sidebar-expanded-on-hover")},collapse:function(){$("body").hasClass("sidebar-expanded-on-hover")&&$("body").removeClass("sidebar-expanded-on-hover").addClass("sidebar-collapse")}},$.AdminLTE.tree=function(a){var b=this,c=$.AdminLTE.options.animationSpeed;$(document).on("click",a+" li a",function(a){var d=$(this),e=d.next();if(e.is(".treeview-menu")&&e.is(":visible"))e.slideUp(c,function(){e.removeClass("menu-open")}),e.parent("li").removeClass("active");else if(e.is(".treeview-menu")&&!e.is(":visible")){var f=d.parents("ul").first(),g=f.find("ul:visible").slideUp(c);g.removeClass("menu-open");var h=d.parent("li");e.slideDown(c,function(){e.addClass("menu-open"),f.find("li.active").removeClass("active"),h.addClass("active"),b.layout.fix()})}e.is(".treeview-menu")&&a.preventDefault()})},$.AdminLTE.controlSidebar={activate:function(){var a=this,b=$.AdminLTE.options.controlSidebarOptions,c=$(b.selector),d=$(b.toggleBtnSelector);d.on("click",function(d){d.preventDefault(),c.hasClass("control-sidebar-open")||$("body").hasClass("control-sidebar-open")?a.close(c,b.slide):a.open(c,b.slide)});var e=$(".control-sidebar-bg");a._fix(e),$("body").hasClass("fixed")?a._fixForFixed(c):$(".content-wrapper, .right-side").height() .box-body, > .box-footer, > form >.box-body, > form > .box-footer");c.hasClass("collapsed-box")?(a.children(":first").removeClass(b.icons.open).addClass(b.icons.collapse),d.slideDown(b.animationSpeed,function(){c.removeClass("collapsed-box")})):(a.children(":first").removeClass(b.icons.collapse).addClass(b.icons.open),d.slideUp(b.animationSpeed,function(){c.addClass("collapsed-box")}))},remove:function(a){var b=a.parents(".box").first();b.slideUp(this.animationSpeed)}}}if("undefined"==typeof jQuery)throw new Error("AdminLTE requires jQuery");$.AdminLTE={},$.AdminLTE.options={navbarMenuSlimscroll:!0,navbarMenuSlimscrollWidth:"3px",navbarMenuHeight:"200px",animationSpeed:500,sidebarToggleSelector:"[data-toggle='offcanvas']",sidebarPushMenu:!0,sidebarSlimScroll:!0,sidebarExpandOnHover:!1,enableBoxRefresh:!0,enableBSToppltip:!0,BSTooltipSelector:"[data-toggle='tooltip']",enableFastclick:!0,enableControlSidebar:!0,controlSidebarOptions:{toggleBtnSelector:"[data-toggle='control-sidebar']",selector:".control-sidebar",slide:!0},enableBoxWidget:!0,boxWidgetOptions:{boxWidgetIcons:{collapse:"fa-minus",open:"fa-plus",remove:"fa-times"},boxWidgetSelectors:{remove:'[data-widget="remove"]',collapse:'[data-widget="collapse"]'}},directChat:{enable:!0,contactToggleSelector:'[data-widget="chat-pane-toggle"]'},colors:{lightBlue:"#3c8dbc",red:"#f56954",green:"#00a65a",aqua:"#00c0ef",yellow:"#f39c12",blue:"#0073b7",navy:"#001F3F",teal:"#39CCCC",olive:"#3D9970",lime:"#01FF70",orange:"#FF851B",fuchsia:"#F012BE",purple:"#8E24AA",maroon:"#D81B60",black:"#222222",gray:"#d2d6de"},screenSizes:{xs:480,sm:768,md:992,lg:1200}},$(function(){"use strict";$("body").removeClass("hold-transition"),"undefined"!=typeof AdminLTEOptions&&$.extend(!0,$.AdminLTE.options,AdminLTEOptions);var a=$.AdminLTE.options;_init(),$.AdminLTE.layout.activate(),$.AdminLTE.tree(".sidebar"),a.enableControlSidebar&&$.AdminLTE.controlSidebar.activate(),a.navbarMenuSlimscroll&&"undefined"!=typeof $.fn.slimscroll&&$(".navbar .menu").slimscroll({height:a.navbarMenuHeight,alwaysVisible:!1,size:a.navbarMenuSlimscrollWidth}).css("width","100%"),a.sidebarPushMenu&&$.AdminLTE.pushMenu.activate(a.sidebarToggleSelector),a.enableBSToppltip&&$("body").tooltip({selector:a.BSTooltipSelector}),a.enableBoxWidget&&$.AdminLTE.boxWidget.activate(),a.enableFastclick&&"undefined"!=typeof FastClick&&FastClick.attach(document.body),a.directChat.enable&&$(document).on("click",a.directChat.contactToggleSelector,function(){var a=$(this).parents(".direct-chat").first();a.toggleClass("direct-chat-contacts-open")}),$('.btn-group[data-toggle="btn-toggle"]').each(function(){var a=$(this);$(this).find(".btn").on("click",function(b){a.find(".btn.active").removeClass("active"),$(this).addClass("active"),b.preventDefault()})})}),function(a){"use strict";a.fn.boxRefresh=function(b){function c(a){a.append(f),e.onLoadStart.call(a)}function d(a){a.find(f).remove(),e.onLoadDone.call(a)}var e=a.extend({trigger:".refresh-btn",source:"",onLoadStart:function(a){return a},onLoadDone:function(a){return a}},b),f=a('
    ');return this.each(function(){if(""===e.source)return void(window.console&&window.console.log("Please specify a source first - boxRefresh()"));var b=a(this),f=b.find(e.trigger).first();f.on("click",function(a){a.preventDefault(),c(b),b.find(".box-body").load(e.source,function(){d(b)})})})}}(jQuery),function(a){"use strict";a.fn.activateBox=function(){a.AdminLTE.boxWidget.activate(this)}}(jQuery),function(a){"use strict";a.fn.todolist=function(b){var c=a.extend({onCheck:function(a){return a},onUncheck:function(a){return a}},b);return this.each(function(){"undefined"!=typeof a.fn.iCheck?(a("input",this).on("ifChecked",function(){var b=a(this).parents("li").first();b.toggleClass("done"),c.onCheck.call(b)}),a("input",this).on("ifUnchecked",function(){var b=a(this).parents("li").first();b.toggleClass("done"),c.onUncheck.call(b)})):a("input",this).on("change",function(){var b=a(this).parents("li").first();b.toggleClass("done"),a("input",b).is(":checked")?c.onCheck.call(b):c.onUncheck.call(b)})})}}(jQuery); \ No newline at end of file diff --git a/public/assets/js/AdminLTE/dashboard.js b/public/assets/js/AdminLTE/dashboard.js new file mode 100755 index 00000000..49f9c26f --- /dev/null +++ b/public/assets/js/AdminLTE/dashboard.js @@ -0,0 +1,252 @@ +/* + * Author: Abdullah A Almsaeed + * Date: 4 Jan 2014 + * Description: + * This is a demo file used only for the main dashboard (index.html) + **/ + +$(function() { + "use strict"; + + //Make the dashboard widgets sortable Using jquery UI + $(".connectedSortable").sortable({ + placeholder: "sort-highlight", + connectWith: ".connectedSortable", + handle: ".box-header, .nav-tabs", + forcePlaceholderSize: true, + zIndex: 999999 + }).disableSelection(); + $(".connectedSortable .box-header, .connectedSortable .nav-tabs-custom").css("cursor", "move"); + //jQuery UI sortable for the todo list + $(".todo-list").sortable({ + placeholder: "sort-highlight", + handle: ".handle", + forcePlaceholderSize: true, + zIndex: 999999 + }).disableSelection(); + ; + + //bootstrap WYSIHTML5 - text editor + $(".textarea").wysihtml5(); + + $('.daterange').daterangepicker( + { + ranges: { + 'Today': [moment(), moment()], + 'Yesterday': [moment().subtract('days', 1), moment().subtract('days', 1)], + 'Last 7 Days': [moment().subtract('days', 6), moment()], + 'Last 30 Days': [moment().subtract('days', 29), moment()], + 'This Month': [moment().startOf('month'), moment().endOf('month')], + 'Last Month': [moment().subtract('month', 1).startOf('month'), moment().subtract('month', 1).endOf('month')] + }, + startDate: moment().subtract('days', 29), + endDate: moment() + }, + function(start, end) { + alert("You chose: " + start.format('MMMM D, YYYY') + ' - ' + end.format('MMMM D, YYYY')); + }); + + /* jQueryKnob */ + $(".knob").knob(); + + //jvectormap data + var visitorsData = { + "US": 398, //USA + "SA": 400, //Saudi Arabia + "CA": 1000, //Canada + "DE": 500, //Germany + "FR": 760, //France + "CN": 300, //China + "AU": 700, //Australia + "BR": 600, //Brazil + "IN": 800, //India + "GB": 320, //Great Britain + "RU": 3000 //Russia + }; + //World map by jvectormap + $('#world-map').vectorMap({ + map: 'world_mill_en', + backgroundColor: "transparent", + regionStyle: { + initial: { + fill: '#e4e4e4', + "fill-opacity": 1, + stroke: 'none', + "stroke-width": 0, + "stroke-opacity": 1 + } + }, + series: { + regions: [{ + values: visitorsData, + scale: ["#92c1dc", "#ebf4f9"], + normalizeFunction: 'polynomial' + }] + }, + onRegionLabelShow: function(e, el, code) { + if (typeof visitorsData[code] != "undefined") + el.html(el.html() + ': ' + visitorsData[code] + ' new visitors'); + } + }); + + //Sparkline charts + var myvalues = [1000, 1200, 920, 927, 931, 1027, 819, 930, 1021]; + $('#sparkline-1').sparkline(myvalues, { + type: 'line', + lineColor: '#92c1dc', + fillColor: "#ebf4f9", + height: '50', + width: '80' + }); + myvalues = [515, 519, 520, 522, 652, 810, 370, 627, 319, 630, 921]; + $('#sparkline-2').sparkline(myvalues, { + type: 'line', + lineColor: '#92c1dc', + fillColor: "#ebf4f9", + height: '50', + width: '80' + }); + myvalues = [15, 19, 20, 22, 33, 27, 31, 27, 19, 30, 21]; + $('#sparkline-3').sparkline(myvalues, { + type: 'line', + lineColor: '#92c1dc', + fillColor: "#ebf4f9", + height: '50', + width: '80' + }); + + //The Calender + $("#calendar").datepicker(); + + //SLIMSCROLL FOR CHAT WIDGET + $('#chat-box').slimScroll({ + height: '250px' + }); + + /* Morris.js Charts */ + // Sales chart + var area = new Morris.Area({ + element: 'revenue-chart', + resize: true, + data: [ + {y: '2011 Q1', item1: 2666, item2: 2666}, + {y: '2011 Q2', item1: 2778, item2: 2294}, + {y: '2011 Q3', item1: 4912, item2: 1969}, + {y: '2011 Q4', item1: 3767, item2: 3597}, + {y: '2012 Q1', item1: 6810, item2: 1914}, + {y: '2012 Q2', item1: 5670, item2: 4293}, + {y: '2012 Q3', item1: 4820, item2: 3795}, + {y: '2012 Q4', item1: 15073, item2: 5967}, + {y: '2013 Q1', item1: 10687, item2: 4460}, + {y: '2013 Q2', item1: 8432, item2: 5713} + ], + xkey: 'y', + ykeys: ['item1', 'item2'], + labels: ['Item 1', 'Item 2'], + lineColors: ['#a0d0e0', '#3c8dbc'], + hideHover: 'auto' + }); + var line = new Morris.Line({ + element: 'line-chart', + resize: true, + data: [ + {y: '2011 Q1', item1: 2666}, + {y: '2011 Q2', item1: 2778}, + {y: '2011 Q3', item1: 4912}, + {y: '2011 Q4', item1: 3767}, + {y: '2012 Q1', item1: 6810}, + {y: '2012 Q2', item1: 5670}, + {y: '2012 Q3', item1: 4820}, + {y: '2012 Q4', item1: 15073}, + {y: '2013 Q1', item1: 10687}, + {y: '2013 Q2', item1: 8432} + ], + xkey: 'y', + ykeys: ['item1'], + labels: ['Item 1'], + lineColors: ['#efefef'], + lineWidth: 2, + hideHover: 'auto', + gridTextColor: "#fff", + gridStrokeWidth: 0.4, + pointSize: 4, + pointStrokeColors: ["#efefef"], + gridLineColor: "#efefef", + gridTextFamily: "Open Sans", + gridTextSize: 10 + }); + + //Donut Chart + var donut = new Morris.Donut({ + element: 'sales-chart', + resize: true, + colors: ["#3c8dbc", "#f56954", "#00a65a"], + data: [ + {label: "Download Sales", value: 12}, + {label: "In-Store Sales", value: 30}, + {label: "Mail-Order Sales", value: 20} + ], + hideHover: 'auto' + }); + /*Bar chart + var bar = new Morris.Bar({ + element: 'bar-chart', + resize: true, + data: [ + {y: '2006', a: 100, b: 90}, + {y: '2007', a: 75, b: 65}, + {y: '2008', a: 50, b: 40}, + {y: '2009', a: 75, b: 65}, + {y: '2010', a: 50, b: 40}, + {y: '2011', a: 75, b: 65}, + {y: '2012', a: 100, b: 90} + ], + barColors: ['#00a65a', '#f56954'], + xkey: 'y', + ykeys: ['a', 'b'], + labels: ['CPU', 'DISK'], + hideHover: 'auto' + });*/ + //Fix for charts under tabs + $('.box ul.nav a').on('shown.bs.tab', function(e) { + area.redraw(); + donut.redraw(); + }); + + + /* BOX REFRESH PLUGIN EXAMPLE (usage with morris charts) */ + $("#loading-example").boxRefresh({ + source: "ajax/dashboard-boxrefresh-demo.php", + onLoadDone: function(box) { + bar = new Morris.Bar({ + element: 'bar-chart', + resize: true, + data: [ + {y: '2006', a: 100, b: 90}, + {y: '2007', a: 75, b: 65}, + {y: '2008', a: 50, b: 40}, + {y: '2009', a: 75, b: 65}, + {y: '2010', a: 50, b: 40}, + {y: '2011', a: 75, b: 65}, + {y: '2012', a: 100, b: 90} + ], + barColors: ['#00a65a', '#f56954'], + xkey: 'y', + ykeys: ['a', 'b'], + labels: ['CPU', 'DISK'], + hideHover: 'auto' + }); + } + }); + + /* The todo list plugin */ + $(".todo-list").todolist({ + onCheck: function(ele) { + //console.log("The element has been checked") + }, + onUncheck: function(ele) { + //console.log("The element has been unchecked") + } + }); + +}); \ No newline at end of file diff --git a/public/assets/js/AdminLTE/demo.js b/public/assets/js/AdminLTE/demo.js new file mode 100755 index 00000000..44f871f3 --- /dev/null +++ b/public/assets/js/AdminLTE/demo.js @@ -0,0 +1,81 @@ +$(function() { + /* For demo purposes */ + var demo = $("
    ").css({ + position: "fixed", + top: "150px", + right: "0", + background: "rgba(0, 0, 0, 0.7)", + "border-radius": "5px 0px 0px 5px", + padding: "10px 15px", + "font-size": "16px", + "z-index": "999999", + cursor: "pointer", + color: "#ddd" + }).html("").addClass("no-print"); + + var demo_settings = $("
    ").css({ + "padding": "10px", + position: "fixed", + top: "130px", + right: "-200px", + background: "#fff", + border: "3px solid rgba(0, 0, 0, 0.7)", + "width": "200px", + "z-index": "999999" + }).addClass("no-print"); + demo_settings.append( + "

    Layout Options

    " + + "
    " + + "
    " + + "" + + "
    " + + "
    " + ); + demo_settings.append( + "

    Skins

    " + + "
    " + + "
    " + + "" + + "
    " + + "
    " + + + "
    " + + "
    " + + "" + + "
    " + + "
    " + ); + + demo.click(function() { + if (!$(this).hasClass("open")) { + $(this).css("right", "200px"); + demo_settings.css("right", "0"); + $(this).addClass("open"); + } else { + $(this).css("right", "0"); + demo_settings.css("right", "-200px"); + $(this).removeClass("open") + } + }); + + $("body").append(demo); + $("body").append(demo_settings); +}); + +function change_layout() { + $("body").toggleClass("fixed"); + fix_sidebar(); +} +function change_skin(cls) { + $("body").removeClass("skin-blue skin-black"); + $("body").addClass(cls); +} diff --git a/public/assets/js/bootstrap.min.js b/public/assets/js/bootstrap.min.js old mode 100644 new mode 100755 index 032b0a6b..856dc42f --- a/public/assets/js/bootstrap.min.js +++ b/public/assets/js/bootstrap.min.js @@ -1,6 +1,7 @@ -/** -* bootstrap.js v3.0.0 by @fat and @mdo -* Copyright 2013 Twitter Inc. -* http://www.apache.org/licenses/LICENSE-2.0 -*/ -if(!jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("webkitTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger("webkitTransitionEnd")};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(window.jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]');if(a.length){var b=this.$element.find("input").prop("checked",!this.$element.hasClass("active"));"radio"===b.prop("type")&&a.find(".active").removeClass("active")}this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(window.jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover"},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h]();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .accordion-group > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find("[data-toggle=collapse][data-parent="+i+"]").not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(window.jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title:empty").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip},b.prototype.destroy=function(){this.hide().$element.off("."+this.type).removeData(this.type)};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(window.jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(window.jQuery); \ No newline at end of file +/*! + * Bootstrap v3.1.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + ++function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function f(){e.trigger("closed.bs.alert").remove()}var c=a(this),d=c.attr("data-target");d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,""));var e=a(d);b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger(b=a.Event("close.bs.alert"));if(b.isDefaultPrevented())return;e.removeClass("in"),a.support.transition&&e.hasClass("fade")?e.one(a.support.transition.end,f).emulateTransitionEnd(150):f()};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.isLoading=!1};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",f.resetText||d.data("resetText",d[e]()),d[e](f[b]||this.options[b]),setTimeout(a.proxy(function(){b=="loadingText"?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},b.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");c.prop("type")=="radio"&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f=typeof c=="object"&&c;e||d.data("bs.button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.pause=="hover"&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();if(b>this.$items.length-1||b<0)return;return this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){if(this.sliding)return;return this.slide("next")},b.prototype.prev=function(){if(this.sliding)return;return this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});this.$element.trigger(j);if(j.isDefaultPrevented())return;return this.sliding=!0,f&&this.pause(),this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(d.css("transition-duration").slice(0,-1)*1e3)):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")),f&&this.cycle(),this};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),typeof c=="object"&&c),g=typeof c=="string"?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),typeof c=="number"?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),c.data()),g=c.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=c.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){function e(d){a(b).remove(),a(c).each(function(){var b=f(a(this)),c={relatedTarget:this};if(!b.hasClass("open"))return;b.trigger(d=a.Event("hide.bs.dropdown",c));if(d.isDefaultPrevented())return;b.removeClass("open").trigger("hidden.bs.dropdown",c)})}function f(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}"use strict";var b=".dropdown-backdrop",c="[data-toggle=dropdown]",d=function(b){a(b).on("click.bs.dropdown",this.toggle)};d.prototype.toggle=function(b){var c=a(this);if(c.is(".disabled, :disabled"))return;var d=f(c),g=d.hasClass("open");e();if(!g){"ontouchstart"in document.documentElement&&!d.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?typeof c=="string"?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||(typeof b.content=="function"?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f=typeof c=="object"&&c;if(!e&&c=="destroy")return;e||d.data("bs.popover",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,""));if(b.parent("li").hasClass("active"))return;var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});b.trigger(f);if(f.isDefaultPrevented())return;var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})},b.prototype.activate=function(b,c,d){function g(){e.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),f?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var e=c.find("> .active"),f=d&&a.support.transition&&e.hasClass("fade");f?e.one(a.support.transition.end,g).emulateTransitionEnd(150):g(),e.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),typeof c=="string"&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(this.transitioning||this.$element.hasClass("in"))return;var b=a.Event("show.bs.collapse");this.$element.trigger(b);if(b.isDefaultPrevented())return;var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("collapse in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])},b.prototype.hide=function(){if(this.transitioning||!this.$element.hasClass("in"))return;var b=a.Event("hide.bs.collapse");this.$element.trigger(b);if(b.isDefaultPrevented())return;var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};if(!a.support.transition)return d.call(this);this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350)},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),typeof c=="object"&&c);!e&&f.toggle&&c=="show"&&(c=!c),e||d.data("bs.collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":c.data(),i=c.attr("data-parent"),j=i&&a(i);if(!g||!g.transitioning)j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(c).addClass("collapsed"),c[f.hasClass("in")?"addClass":"removeClass"]("collapsed");f.collapse(h)})}(jQuery),+function(a){function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(a.style[c]!==undefined)return{end:b[c]};return!1}"use strict",a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery) \ No newline at end of file diff --git a/public/assets/js/build-plugins/codeception.js b/public/assets/js/build-plugins/codeception.js new file mode 100644 index 00000000..01343f2b --- /dev/null +++ b/public/assets/js/build-plugins/codeception.js @@ -0,0 +1,113 @@ +var codeceptionPlugin = ActiveBuild.UiPlugin.extend({ + id: 'build-codeception-errors', + css: 'col-lg-12 col-md-12 col-sm-12 col-xs-12', + title: Lang.get('codeception'), + lastData: null, + lastMeta: null, + displayOnUpdate: false, + box: true, + rendered: false, + + register: function() { + var self = this; + var query_data = ActiveBuild.registerQuery('codeception-data', -1, {key: 'codeception-data'}); + var query_meta_data = ActiveBuild.registerQuery('codeception-meta', -1, {key: 'codeception-meta'}); + + $(window).on('codeception-data', function(data) { + self.onUpdateData(data); + }); + + $(window).on('codeception-meta', function(data) { + self.onUpdateMeta(data); + }); + + $(window).on('build-updated', function() { + if (!self.rendered) { + self.displayOnUpdate = true; + query_data(); + query_meta_data(); + } + }); + }, + + render: function() { + return $('' + + '' + + '' + + '' + + '' + + '
    '+Lang.get('codeception_suite')+''+Lang.get('codeception_feature')+''+Lang.get('codeception_time')+'
    '); + }, + + onUpdateData: function(e) { + if (!e.queryData) { + $('#build-codeception-errors').hide(); + return; + } + + this.rendered = true; + this.lastData = e.queryData; + + var tests = this.lastData[0].meta_value; + var tbody = $('#codeception-data tbody'); + tbody.empty(); + + if (tests.length == 0) { + $('#build-codeception-errors').hide(); + return; + } + + for (var i in tests) { + + var rows = $('' + + ''+tests[i].suite+'' + + ''+tests[i].feature+'' + + ''+tests[i].time+''+ + '' + + '' + + '' + + ''+Lang.get('name')+': '+tests[i].name+'
    ' + + ''+Lang.get('file')+': '+tests[i].file+'
    ' + + (tests[i].message + ? ''+Lang.get('message')+': '+tests[i].message+'' + : '') + + '' + + ''); + + if (!tests[i].pass) { + rows.first().addClass('danger'); + } else { + rows.first().addClass('success'); + } + + tbody.append(rows); + } + + $('#build-codeception-errors').show(); + }, + + onUpdateMeta: function(e) { + if (!e.queryData) { + return; + } + + $('#build-codeception-errors').show(); + $('#build-codeception-errors td').tooltip(); + + this.lastMeta = e.queryData; + + var data = this.lastMeta[0].meta_value; + var tfoot = $('#codeception-data tfoot'); + tfoot.empty(); + + var row = $('' + + '' + + Lang.get('codeception_synopsis', data.tests, data.timetaken, data.failures) + + '' + + ''); + + tfoot.append(row); + } +}); + +ActiveBuild.registerPlugin(new codeceptionPlugin()); diff --git a/public/assets/js/build-plugins/loc.js b/public/assets/js/build-plugins/loc.js index aa75c073..89e703b8 100644 --- a/public/assets/js/build-plugins/loc.js +++ b/public/assets/js/build-plugins/loc.js @@ -1,65 +1,106 @@ -var locPlugin = PHPCI.UiPlugin.extend({ +var locPlugin = ActiveBuild.UiPlugin.extend({ id: 'build-lines-chart', - css: 'col-lg-6 col-md-6 col-sm-12 col-xs-12', - title: 'Lines of Code', + css: 'col-xs-12', + title: Lang.get('lines_of_code'), lastData: null, displayOnUpdate: false, + rendered: false, + chartData: null, register: function() { var self = this; - var query = PHPCI.registerQuery('phploc-lines', -1, {num_builds: 10, key: 'phploc'}) + var query = ActiveBuild.registerQuery('phploc-lines', -1, {num_builds: 10, key: 'phploc'}) $(window).on('phploc-lines', function(data) { self.onUpdate(data); }); $(window).on('build-updated', function(data) { - if (data.queryData.status > 1) { - self.displayOnUpdate = true; + if (data.queryData.status > 1 && !self.rendered) { query(); } }); - - google.load("visualization", "1", {packages:["corechart"]}); }, render: function() { - return $('
    ').text('This chart will display once the build has completed.'); + var self = this; + var container = $('
    '); + container.append(''); + + $(document).on('shown.bs.tab', function () { + $('#build-lines-chart').hide(); + self.drawChart(); + }); + + return container; }, onUpdate: function(e) { this.lastData = e.queryData; - - if (this.displayOnUpdate) { - this.displayChart(); - } + this.displayChart(); }, displayChart: function() { - var build = this.lastData; + var self = this; + var builds = this.lastData; + self.rendered = true; - if (!build || !build.length) { - return; - } - - $('#phploc-lines').empty().animate({height: '275px'}); - - var data = [["Build", "Lines", "Comment Lines", "Non-Comment Lines", "Logical Lines"]]; - for (var idx in build) { - data.push(['Build ' + build[idx].build_id, parseInt(build[idx].meta_value.LOC), parseInt(build[idx].meta_value.CLOC), parseInt(build[idx].meta_value.NCLOC), parseInt(build[idx].meta_value.LLOC)]); - } - - var data = google.visualization.arrayToDataTable(data); - - var options = { - hAxis: {title: 'Builds'}, - vAxis: {title: 'Lines'}, - backgroundColor: { fill: 'transparent' } + self.chartData = { + labels: [], + datasets: [ + { + label: Lang.get('lines'), + strokeColor: "rgba(60,141,188,1)", + pointColor: "rgba(60,141,188,1)", + data: [] + }, + { + label: Lang.get('logical_lines'), + strokeColor: "rgba(245,105,84,1)", + pointColor: "rgba(245,105,84,1)", + data: [] + }, + { + label: Lang.get('comment_lines'), + strokeColor: "rgba(0,166,90,1)", + pointColor: "rgba(0,166,90,1)", + data: [] + }, + { + label: Lang.get('noncomment_lines'), + strokeColor: "rgba(0,192,239,1)", + pointColor: "rgba(0,192,239,1)", + data: [] + } + ] }; - var chart = new google.visualization.LineChart(document.getElementById('phploc-lines')); - chart.draw(data, options); + for (var i in builds) { + self.chartData.labels.push('Build ' + builds[i].build_id); + self.chartData.datasets[0].data.push(builds[i].meta_value.LOC); + self.chartData.datasets[1].data.push(builds[i].meta_value.LLOC); + self.chartData.datasets[2].data.push(builds[i].meta_value.CLOC); + self.chartData.datasets[3].data.push(builds[i].meta_value.NCLOC); + } + + self.drawChart(); + }, + + drawChart: function () { + var self = this; + + if ($('#information').hasClass('active') && self.chartData && self.lastData) { + $('#build-lines-chart').show(); + + var ctx = $("#phploc-lines-chart").get(0).getContext("2d"); + var phpLocChart = new Chart(ctx); + + phpLocChart.Line(self.chartData, { + datasetFill: false, + multiTooltipTemplate: "<%=datasetLabel%>: <%= value %>" + }); + } } }); -PHPCI.registerPlugin(new locPlugin()); \ No newline at end of file +ActiveBuild.registerPlugin(new locPlugin()); diff --git a/public/assets/js/build-plugins/log.js b/public/assets/js/build-plugins/log.js deleted file mode 100644 index a283ccf8..00000000 --- a/public/assets/js/build-plugins/log.js +++ /dev/null @@ -1,23 +0,0 @@ -var logPlugin = PHPCI.UiPlugin.extend({ - id: 'build-log', - css: 'col-lg-12 col-md-12 col-sm-12 col-xs-12', - title: 'Build Log', - - init: function(){ - this._super(); - }, - - render: function() { - var container = $('
    ');
    -        container.css({height: '300px', 'overflow-y': 'auto'});
    -        container.html(PHPCI.buildData.log);
    -
    -        return container;
    -    },
    -
    -    onUpdate: function(e) {
    -        $('#build-log pre').html(e.queryData.log);
    -    }
    -});
    -
    -PHPCI.registerPlugin(new logPlugin());
    \ No newline at end of file
    diff --git a/public/assets/js/build-plugins/phpspec.js b/public/assets/js/build-plugins/phpspec.js
    new file mode 100644
    index 00000000..da1f494b
    --- /dev/null
    +++ b/public/assets/js/build-plugins/phpspec.js
    @@ -0,0 +1,80 @@
    +var phpspecPlugin = ActiveBuild.UiPlugin.extend({
    +    id: 'build-phpspec-errors',
    +    css: 'col-lg-12 col-md-12 col-sm-12 col-xs-12',
    +    title: Lang.get('phpspec'),
    +    lastData: null,
    +    displayOnUpdate: false,
    +    box: true,
    +    rendered: false,
    +
    +    register: function() {
    +        var self = this;
    +        var query = ActiveBuild.registerQuery('phpspec', -1, {key: 'phpspec'})
    +
    +        $(window).on('phpspec', function(data) {
    +            self.onUpdate(data);
    +        });
    +
    +        $(window).on('build-updated', function() {
    +            if (!self.rendered) {
    +                self.displayOnUpdate = true;
    +                query();
    +            }
    +        });
    +    },
    +
    +    render: function() {
    +
    +        return $('' +
    +            '' +
    +            '' +
    +            '   ' +
    +            '   ' +
    +            '   ' +
    +            '' +
    +            '
    '+Lang.get('suite')+''+Lang.get('test')+''+Lang.get('result')+'
    '); + }, + + onUpdate: function(e) { + if (!e.queryData) { + $('#build-phpspec-errors').hide(); + return; + } + + this.rendered = true; + this.lastData = e.queryData; + + var tests = this.lastData[0].meta_value; + var tbody = $('#phpspec-data tbody'); + tbody.empty(); + + for (var i in tests.suites) { + var test_suite = tests.suites[i]; + + for(var k in test_suite.cases){ + var test_case = test_suite.cases[k]; + + var row = $( + ''+ + ''+test_suite.name+''+ + ''+test_case.name+''+ + ''+(test_case.message ? test_case.message : Lang.get('ok'))+''+ + '' + ); + + if (test_case.status!='passed') { + row.addClass('danger'); + } else { + row.addClass('success'); + } + + tbody.append(row); + } + } + + // show plugin once preparation of grid is done + $('#build-phpspec-errors').show(); + } +}); + +ActiveBuild.registerPlugin(new phpspecPlugin()); diff --git a/public/assets/js/build-plugins/phptallint.js b/public/assets/js/build-plugins/phptallint.js new file mode 100644 index 00000000..fe549b99 --- /dev/null +++ b/public/assets/js/build-plugins/phptallint.js @@ -0,0 +1,79 @@ +var phptalPlugin = ActiveBuild.UiPlugin.extend({ + id: 'build-phptal', + css: 'col-lg-6 col-md-12 col-sm-12 col-xs-12', + title: 'PHPTAL Lint', + lastData: null, + box: true, + rendered: false, + + register: function() { + var self = this; + var query = ActiveBuild.registerQuery('phptallint-data', -1, {key: 'phptallint-data'}) + + $(window).on('phptallint-data', function(data) { + self.onUpdate(data); + }); + + $(window).on('build-updated', function() { + if (!self.rendered) { + query(); + } + }); + }, + + render: function() { + return $('
    ' + + '' + + '' + + ' ' + + ' ' + + ' ' + + '' + + '
    FileLineMessage
    '); + }, + + onUpdate: function(e) { + if (!e.queryData) { + $('#build-phptal').hide(); + return; + } + + this.rendered = true; + this.lastData = e.queryData; + + var errors = this.lastData[0].meta_value; + var tbody = $('#phptal-data tbody'); + tbody.empty(); + + if (errors.length == 0) { + $('#build-phptal').hide(); + return; + } + + for (var i in errors) { + var file = errors[i].file; + + if (ActiveBuild.fileLinkTemplate) { + var fileLink = ActiveBuild.fileLinkTemplate.replace('{FILE}', file); + fileLink = fileLink.replace('{LINE}', errors[i].line); + + file = '' + file + ''; + } + + var row = $('' + + ''+file+'' + + ''+errors[i].line+'' + + ''+errors[i].message+''); + + if (errors[i].type == 'error') { + row.addClass('danger'); + } + + tbody.append(row); + } + + $('#build-phptal').show(); + } +}); + +ActiveBuild.registerPlugin(new phptalPlugin()); diff --git a/public/assets/js/build-plugins/phpunit.js b/public/assets/js/build-plugins/phpunit.js new file mode 100644 index 00000000..71495603 --- /dev/null +++ b/public/assets/js/build-plugins/phpunit.js @@ -0,0 +1,147 @@ +var phpunitPlugin = ActiveBuild.UiPlugin.extend({ + id: 'build-phpunit-errors', + css: 'col-lg-6 col-md-12 col-sm-12 col-xs-12', + title: Lang.get('phpunit'), + lastData: null, + displayOnUpdate: false, + box: true, + rendered: false, + statusMap: { + success : 'ok', + fail: 'remove', + error: 'warning-sign', + todo: 'info-sign', + skipped: 'exclamation-sign' + }, + + register: function() { + var self = this; + var query = ActiveBuild.registerQuery('phpunit-data', -1, {key: 'phpunit-data'}) + + $(window).on('phpunit-data', function(data) { + self.onUpdate(data); + }); + + $(window).on('build-updated', function() { + if (!self.rendered) { + self.displayOnUpdate = true; + query(); + } + }); + + $(document).on('click', '#phpunit-data .test-toggle', function(ev) { + var input = $(ev.target); + $('#phpunit-data tbody ' + input.data('target')).toggle(input.prop('checked')); + }); + }, + + render: function() { + + return $('
    ' + + '' + + '' + + ' ' + + '' + + '
    '+Lang.get('test_message')+'
    '); + }, + + onUpdate: function(e) { + if (!e.queryData) { + $('#build-phpunit-errors').hide(); + return; + } + + this.rendered = true; + this.lastData = e.queryData; + + var tests = this.lastData[0].meta_value; + var thead = $('#phpunit-data thead tr'); + var tbody = $('#phpunit-data tbody'); + thead.empty().append(''+Lang.get('test_message')+''); + tbody.empty(); + + if (tests.length == 0) { + $('#build-phpunit-errors').hide(); + return; + } + + var counts = { success: 0, fail: 0, error: 0, skipped: 0, todo: 0 }, total = 0; + + for (var i in tests) { + var content = $(''), + message = $('
    ').appendTo(content), + severity = tests[i].severity || (tests[i].pass ? 'success' : 'failed'); + + if (tests[i].message) { + message.text(tests[i].message); + } else if (tests[i].test && tests[i].suite) { + message.text(tests[i].suite + '::' + tests[i].test); + } else { + message.html('' + Lang.get('test_no_message') + ''); + } + + if (tests[i].data) { + content.append('
    ' + this.repr(tests[i].data) + '
    '); + } + + $('').append(content).appendTo(tbody); + + counts[severity]++; + total++; + } + + var checkboxes = $(''); + thead.append(checkboxes).append('' + Lang.get('test_total', total) + ''); + + for (var key in counts) { + var count = counts[key]; + if(count > 0) { + checkboxes.append( + '
     ' + + Lang.get('test_'+key, count)+ '
    ' + ); + } + } + + tbody.find('.success').hide(); + + $('#build-phpunit-errors').show(); + }, + + repr: function(data) + { + switch(typeof(data)) { + case 'boolean': + return '' + (data ? 'true' : 'false') + ''; + case 'string': + return '"' + data + '"'; + case 'undefined': case null: + return 'null'; + case 'object': + var rows = []; + if(data instanceof Array) { + for(var i in data) { + rows.push('' + this.repr(data[i]) + ','); + } + } else { + for(var key in data) { + rows.push( + '' + + '' + this.repr(key) + '' + + '=>' + + '' + this.repr(data[key]) + ',' + + ''); + } + } + return '' + + '' + + rows.join('') + + '' + + '
    array(
    )
    '; + } + return '???'; + } +}); + +ActiveBuild.registerPlugin(new phpunitPlugin()); diff --git a/public/assets/js/build-plugins/summary.js b/public/assets/js/build-plugins/summary.js new file mode 100644 index 00000000..7ccea67b --- /dev/null +++ b/public/assets/js/build-plugins/summary.js @@ -0,0 +1,67 @@ +var SummaryPlugin = ActiveBuild.UiPlugin.extend({ + id: 'build-summary', + css: 'col-xs-12', + title: Lang.get('build-summary'), + box: true, + statusIcons: [ 'fa-clock-o', 'fa-cogs', 'fa-check', 'fa-remove' ], + statusLabels: [ Lang.get('pending'), Lang.get('running'), Lang.get('successful'), Lang.get('failed') ], + statusClasses: ['text-blue', 'text-yellow', 'text-green', 'text-red'], + + register: function() { + var self = this; + var query = ActiveBuild.registerQuery('plugin-summary', 5, {key: 'plugin-summary'}) + + $(window).on('plugin-summary', function(data) { + self.onUpdate(data); + }); + + $(window).on('build-updated', function() { + query(); + }); + }, + + render: function() { + return $( + '
    ' + + '' + + '' + + '' + + '' + + '' + + '
    '+Lang.get('stage')+''+Lang.get('plugin')+''+Lang.get('status')+''+Lang.get('duration')+' (s)
    ' + ); + }, + + onUpdate: function(e) { + if (!e.queryData) { + $('#build-summary').hide(); + return; + } + + var tbody = $('#plugin-summary tbody'), + summary = e.queryData[0].meta_value; + tbody.empty(); + + for(var stage in summary) { + for(var plugin in summary[stage]) { + var data = summary[stage][plugin], + duration = data.started ? ((data.ended || Math.floor(Date.now()/1000)) - data.started) : '-'; + tbody.append( + '' + + '' + Lang.get('stage_'+stage) + '' + + '' + plugin + '' + + '' + + ' ' + + this.statusLabels[data.status] + + '' + + '' + duration + '' + + '' + ); + } + } + + $('#build-summary').show(); + } +}); + +ActiveBuild.registerPlugin(new SummaryPlugin()); diff --git a/public/assets/js/build-plugins/time.js b/public/assets/js/build-plugins/time.js deleted file mode 100644 index 4e697bf6..00000000 --- a/public/assets/js/build-plugins/time.js +++ /dev/null @@ -1,39 +0,0 @@ -var timePlugin = PHPCI.UiPlugin.extend({ - id: 'build-time', - css: 'col-lg-12 col-md-12 col-sm-12 col-xs-12', - title: null, - box: true, - - init: function(){ - this._super(); - }, - - render: function() { - return '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
    Build CreatedBuild StartedBuild Finished
    ' + PHPCI.buildData.created + '' + PHPCI.buildData.started + '' + PHPCI.buildData.finished + '
    '; - }, - - onUpdate: function(e) { - var build = e.queryData; - - $('#created').text(build.created); - $('#started').text(build.started); - $('#finished').text(build.finished); - } -}); - -PHPCI.registerPlugin(new timePlugin()); \ No newline at end of file diff --git a/public/assets/js/build-plugins/warnings.js b/public/assets/js/build-plugins/warnings.js index b77d7bfb..046fb9c6 100644 --- a/public/assets/js/build-plugins/warnings.js +++ b/public/assets/js/build-plugins/warnings.js @@ -1,56 +1,72 @@ -var plugin = PHPCI.UiPlugin.extend({ +var warningsPlugin = ActiveBuild.UiPlugin.extend({ id: 'build-warnings-chart', - css: 'col-lg-6 col-md-6 col-sm-12 col-xs-12', - title: 'Quality Trend', + css: 'col-xs-12', + title: Lang.get('quality_trend'), + keys: { + 'codeception-errors': Lang.get('codeception_errors'), + 'phplint-errors': Lang.get('phplint_errors'), + 'phpunit-errors': Lang.get('phpunit_errors'), + 'phptallint-errors': Lang.get('phptal_errors'), + 'phptallint-warnings': Lang.get('phptal_warnings') + }, data: {}, - keys: null, displayOnUpdate: false, + rendered: false, + chartData: null, register: function() { var self = this; - var query1 = PHPCI.registerQuery('phpmd-warnings', -1, {num_builds: 10, key: 'phpmd-warnings'}) - var query2 = PHPCI.registerQuery('phpcs-warnings', -1, {num_builds: 10, key: 'phpcs-warnings'}) - var query3 = PHPCI.registerQuery('phpcs-errors', -1, {num_builds: 10, key: 'phpcs-errors'}) - $(window).on('phpmd-warnings phpcs-warnings phpcs-errors', function(data) { + var queries = []; + for (var key in self.keys) { + queries.push(ActiveBuild.registerQuery(key, -1, {num_builds: 10, key: key})); + } + + $(window).on('codeception-errors phptallint-warnings phptallint-errors phplint-errors phpunit-errors', function(data) { self.onUpdate(data); }); $(window).on('build-updated', function(data) { - if (data.queryData.status > 1) { + if (!self.rendered && data.queryData.status > 1) { self.displayOnUpdate = true; - query1(); - query2(); - query3(); + for (var query in queries) { + queries[query](); + } } }); - - google.load("visualization", "1", {packages:["corechart"]}); }, render: function() { - return $('
    ').text('This chart will display once the build has completed.'); + var self = this; + var container = $('
    '); + container.append(''); + + $(document).on('shown.bs.tab', function () { + $('#build-warnings-chart').hide(); + self.drawChart(); + }); + + return container; }, onUpdate: function(e) { var self = this; - var build = e.queryData; + var builds = e.queryData; - if (!build || !build.length) { + if (!builds || !builds.length) { return; } - for (var i in build) { - var buildId = build[i]['build_id']; - var metaKey = build[i]['meta_key']; - var metaVal = build[i]['meta_value']; + for (var i in builds) { + var buildId = builds[i]['build_id']; + var metaKey = builds[i]['meta_key']; + var metaVal = builds[i]['meta_value']; if (!self.data[buildId]) { self.data[buildId] = {}; } self.data[buildId][metaKey] = metaVal; - self.keys = Object.keys(self.data[buildId]); } if (self.displayOnUpdate) { @@ -60,39 +76,55 @@ var plugin = PHPCI.UiPlugin.extend({ displayChart: function() { var self = this; + self.rendered = true; - $('#build-warnings').empty().animate({height: '275px'}); + var colors = ['#4D4D4D', '#5DA5DA', '#FAA43A', '#60BD68', '#F17CB0', '#B2912F', '#B276B2', '#DECF3F', '#F15854']; - var titles = ['Build']; - var keys = self.keys; - - for (var i in keys) { - var t = {'phpmd-warnings': 'PHPMD Warnings', 'phpcs-warnings': 'PHPCS Warnings', 'phpcs-errors': 'PHPCS Errors'}; - titles.push(t[keys[i]]); - } - - var data = [titles]; - - for (var build in self.data) { - var thisBuild = ['#' + build]; - - for (var i in keys) { - thisBuild.push(parseInt(self.data[build][keys[i]])); - } - - data.push(thisBuild); - } - - var data = google.visualization.arrayToDataTable(data); - var options = { - hAxis: {title: 'Build'}, - vAxis: {title: 'Warnings'}, - backgroundColor: { fill: 'transparent' } + self.chartData = { + labels: [], + datasets: [] }; - var chart = new google.visualization.LineChart(document.getElementById('build-warnings')); - chart.draw(data, options); + for (var key in self.keys) { + var color = colors.shift(); + + self.chartData.datasets.push({ + label: self.keys[key], + strokeColor: color, + pointColor: color, + data: [] + }); + } + + for (var build in self.data) { + self.chartData.labels.push('Build ' + build); + + var i = 0; + for (var key in self.keys) { + + self.chartData.datasets[i].data.push(parseInt(self.data[build][key])); + i++; + } + } + + self.drawChart(); + }, + + drawChart: function () { + var self = this; + + if ($('#information').hasClass('active') && self.chartData) { + $('#build-warnings-chart').show(); + + var ctx = $("#build-warnings-linechart").get(0).getContext("2d"); + var buildWarningsChart = new Chart(ctx); + + buildWarningsChart.Line(self.chartData, { + datasetFill: false, + multiTooltipTemplate: "<%=datasetLabel%>: <%= value %>" + }); + } } }); -PHPCI.registerPlugin(new plugin()); \ No newline at end of file +ActiveBuild.registerPlugin(new warningsPlugin()); diff --git a/public/assets/js/build.js b/public/assets/js/build.js new file mode 100644 index 00000000..4b9c10a1 --- /dev/null +++ b/public/assets/js/build.js @@ -0,0 +1,202 @@ +var Build = Class.extend({ + buildId: null, + plugins: {}, + observers: {}, + buildData: {}, + queries: {}, + updateInterval: null, + + init: function(build) { + var self = this; + self.buildId = build; + }, + + setupBuild: function (buildData, linkTemplate) { + var self = this; + self.buildData = buildData; + self.fileLinkTemplate = linkTemplate; + + self.registerQuery('build-updated', 10); + + $(window).on('build-updated', function(data) { + + self.buildData = data.queryData; + + // If the build has finished, stop updating every 10 seconds: + if (self.buildData.status > 1) { + self.cancelQuery('build-updated'); + $(window).trigger({type: 'build-complete'}); + } + + $('.build-duration').data('duration', self.buildData.duration ? self.buildData.duration : ''); + $('.build-started').data('date', self.buildData.started ? self.buildData.started : ''); + $('.build-finished').data('date', self.buildData.finished ? self.buildData.finished : ''); + $('#log pre').html(self.buildData.log); + $('.errors-table tbody').append(self.buildData.error_html); + + if (self.buildData.errors == 0) { + $('.errors-label').hide(); + } else { + $('.errors-label').text(self.buildData.errors); + $('.errors-label').show(); + } + + switch (self.buildData.status) { + case 0: + $('body').removeClass('skin-red skin-green skin-yellow'); + $('body').addClass('skin-blue'); + break; + + case 1: + $('body').removeClass('skin-red skin-green skin-blue'); + $('body').addClass('skin-yellow'); + break; + + case 2: + $('body').removeClass('skin-red skin-blue skin-yellow'); + $('body').addClass('skin-green'); + break; + + case 3: + $('body').removeClass('skin-blue skin-green skin-yellow'); + $('body').addClass('skin-red'); + break; + + } + + PHPCI.uiUpdated(); + }); + }, + + registerQuery: function(name, seconds, query) { + var self = this; + var uri = 'build/meta/' + self.buildId; + var query = query || {}; + + var cb = function() { + var fullUri = window.PHPCI_URL + uri; + + if (name == 'build-updated') { + fullUri = window.PHPCI_URL + 'build/data/' + self.buildId + '?since=' + self.buildData.since; + } + + $.ajax({ + dataType: "json", + url: fullUri, + data: query, + success: function(data) { + $(window).trigger({type: name, queryData: data}); + }, + error: handleFailedAjax + }); + }; + + if (seconds != -1) { + self.queries[name] = setInterval(cb, seconds * 1000); + } + + return cb; + }, + + cancelQuery: function (name) { + clearInterval(this.queries[name]); + }, + + registerPlugin: function(plugin) { + this.plugins[plugin.id] = plugin; + plugin.register(); + }, + + storePluginOrder: function () { + var renderOrder = []; + + $('.ui-plugin > div').each(function() { + renderOrder.push($(this).attr('id')); + }); + + localStorage.setItem('phpci-plugin-order', JSON.stringify(renderOrder)); + }, + + renderPlugins: function() { + var self = this; + var rendered = []; + var renderOrder = localStorage.getItem('phpci-plugin-order'); + + if (renderOrder) { + renderOrder = JSON.parse(renderOrder); + } else { + renderOrder = ['build-lines-chart', 'build-warnings-chart']; + } + + for (var idx in renderOrder) { + var key = renderOrder[idx]; + + // Plugins have changed, clear the order. + if (typeof self.plugins[key] == 'undefined') { + localStorage.setItem('phpci-plugin-order', []); + } + + self.renderPlugin(self.plugins[key]); + rendered.push(key); + } + + for (var key in this.plugins) { + if (rendered.indexOf(key) == -1) { + self.renderPlugin(self.plugins[key]); + } + } + + $('#plugins').sortable({ + handle: '.box-title', + connectWith: '#plugins', + update: self.storePluginOrder + }); + + $(window).trigger({type: 'build-updated', queryData: self.buildData}); + }, + + renderPlugin: function(plugin) { + var output = plugin.render(); + + if (!plugin.box) { + output = $('
    ').append(output); + } + + var container = $('
    ').addClass('ui-plugin ' + plugin.css).attr('id', plugin.id); + var content = $('
    ').append(output); + content.addClass('box box-default'); + + if (plugin.title) { + content.prepend('

    '+plugin.title+'

    '); + } + + container.append(content); + + $('#plugins').append(container); + }, + + UiPlugin: Class.extend({ + id: null, + css: 'col-lg-4 col-md-6 col-sm-12 col-xs-12', + box: false, + + init: function(){ + }, + + register: function() { + var self = this; + + $(window).on('build-updated', function(data) { + self.onUpdate(data); + }); + }, + + render: function () { + return ''; + }, + + onUpdate: function (build) { + + } + }) +}); diff --git a/public/assets/js/init.js b/public/assets/js/init.js new file mode 100644 index 00000000..0ef49c4f --- /dev/null +++ b/public/assets/js/init.js @@ -0,0 +1,36 @@ +/** + * @file init.js + * Initialization of frontend of the application goes here + * + * @author Pavel Pavlov + * @date 12/31/13 + * @time 3:44 AM + * @license LICENSE.md + * + * @package PHPCI + */ + +$(function () { + $('#latest-builds').on('latest-builds:reload', bindAppDeleteEvents); + $('#latest-builds').trigger('latest-builds:reload'); +}); + +function bindAppDeleteEvents () { + $('.phpci-app-delete-build').on('click', function (e) { + e.preventDefault(); + + confirmDelete(e.target.href, 'Build').onClose = function () { + window.location.reload(); + }; + + return false; + }); + + $('.phpci-app-delete-user').on('click', function (e) { + e.preventDefault(); + + confirmDelete(e.target.href, 'User', true); + + return false; + }); +} diff --git a/public/assets/js/moment.min.js b/public/assets/js/moment.min.js new file mode 100644 index 00000000..5eb9f6c7 --- /dev/null +++ b/public/assets/js/moment.min.js @@ -0,0 +1,10 @@ +//! moment.js +//! version : 2.8.4 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com +(function(a){function b(a,b,c){switch(arguments.length){case 2:return null!=a?a:b;case 3:return null!=a?a:null!=b?b:c;default:throw new Error("Implement me")}}function c(a,b){return zb.call(a,b)}function d(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function e(a){tb.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+a)}function f(a,b){var c=!0;return m(function(){return c&&(e(a),c=!1),b.apply(this,arguments)},b)}function g(a,b){qc[a]||(e(b),qc[a]=!0)}function h(a,b){return function(c){return p(a.call(this,c),b)}}function i(a,b){return function(c){return this.localeData().ordinal(a.call(this,c),b)}}function j(){}function k(a,b){b!==!1&&F(a),n(this,a),this._d=new Date(+a._d)}function l(a){var b=y(a),c=b.year||0,d=b.quarter||0,e=b.month||0,f=b.week||0,g=b.day||0,h=b.hour||0,i=b.minute||0,j=b.second||0,k=b.millisecond||0;this._milliseconds=+k+1e3*j+6e4*i+36e5*h,this._days=+g+7*f,this._months=+e+3*d+12*c,this._data={},this._locale=tb.localeData(),this._bubble()}function m(a,b){for(var d in b)c(b,d)&&(a[d]=b[d]);return c(b,"toString")&&(a.toString=b.toString),c(b,"valueOf")&&(a.valueOf=b.valueOf),a}function n(a,b){var c,d,e;if("undefined"!=typeof b._isAMomentObject&&(a._isAMomentObject=b._isAMomentObject),"undefined"!=typeof b._i&&(a._i=b._i),"undefined"!=typeof b._f&&(a._f=b._f),"undefined"!=typeof b._l&&(a._l=b._l),"undefined"!=typeof b._strict&&(a._strict=b._strict),"undefined"!=typeof b._tzm&&(a._tzm=b._tzm),"undefined"!=typeof b._isUTC&&(a._isUTC=b._isUTC),"undefined"!=typeof b._offset&&(a._offset=b._offset),"undefined"!=typeof b._pf&&(a._pf=b._pf),"undefined"!=typeof b._locale&&(a._locale=b._locale),Ib.length>0)for(c in Ib)d=Ib[c],e=b[d],"undefined"!=typeof e&&(a[d]=e);return a}function o(a){return 0>a?Math.ceil(a):Math.floor(a)}function p(a,b,c){for(var d=""+Math.abs(a),e=a>=0;d.lengthd;d++)(c&&a[d]!==b[d]||!c&&A(a[d])!==A(b[d]))&&g++;return g+f}function x(a){if(a){var b=a.toLowerCase().replace(/(.)s$/,"$1");a=jc[a]||kc[b]||b}return a}function y(a){var b,d,e={};for(d in a)c(a,d)&&(b=x(d),b&&(e[b]=a[d]));return e}function z(b){var c,d;if(0===b.indexOf("week"))c=7,d="day";else{if(0!==b.indexOf("month"))return;c=12,d="month"}tb[b]=function(e,f){var g,h,i=tb._locale[b],j=[];if("number"==typeof e&&(f=e,e=a),h=function(a){var b=tb().utc().set(d,a);return i.call(tb._locale,b,e||"")},null!=f)return h(f);for(g=0;c>g;g++)j.push(h(g));return j}}function A(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function B(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function C(a,b,c){return hb(tb([a,11,31+b-c]),b,c).week}function D(a){return E(a)?366:365}function E(a){return a%4===0&&a%100!==0||a%400===0}function F(a){var b;a._a&&-2===a._pf.overflow&&(b=a._a[Bb]<0||a._a[Bb]>11?Bb:a._a[Cb]<1||a._a[Cb]>B(a._a[Ab],a._a[Bb])?Cb:a._a[Db]<0||a._a[Db]>24||24===a._a[Db]&&(0!==a._a[Eb]||0!==a._a[Fb]||0!==a._a[Gb])?Db:a._a[Eb]<0||a._a[Eb]>59?Eb:a._a[Fb]<0||a._a[Fb]>59?Fb:a._a[Gb]<0||a._a[Gb]>999?Gb:-1,a._pf._overflowDayOfYear&&(Ab>b||b>Cb)&&(b=Cb),a._pf.overflow=b)}function G(b){return null==b._isValid&&(b._isValid=!isNaN(b._d.getTime())&&b._pf.overflow<0&&!b._pf.empty&&!b._pf.invalidMonth&&!b._pf.nullInput&&!b._pf.invalidFormat&&!b._pf.userInvalidated,b._strict&&(b._isValid=b._isValid&&0===b._pf.charsLeftOver&&0===b._pf.unusedTokens.length&&b._pf.bigHour===a)),b._isValid}function H(a){return a?a.toLowerCase().replace("_","-"):a}function I(a){for(var b,c,d,e,f=0;f0;){if(d=J(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&w(e,c,!0)>=b-1)break;b--}f++}return null}function J(a){var b=null;if(!Hb[a]&&Jb)try{b=tb.locale(),require("./locale/"+a),tb.locale(b)}catch(c){}return Hb[a]}function K(a,b){var c,d;return b._isUTC?(c=b.clone(),d=(tb.isMoment(a)||v(a)?+a:+tb(a))-+c,c._d.setTime(+c._d+d),tb.updateOffset(c,!1),c):tb(a).local()}function L(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function M(a){var b,c,d=a.match(Nb);for(b=0,c=d.length;c>b;b++)d[b]=pc[d[b]]?pc[d[b]]:L(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function N(a,b){return a.isValid()?(b=O(b,a.localeData()),lc[b]||(lc[b]=M(b)),lc[b](a)):a.localeData().invalidDate()}function O(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(Ob.lastIndex=0;d>=0&&Ob.test(a);)a=a.replace(Ob,c),Ob.lastIndex=0,d-=1;return a}function P(a,b){var c,d=b._strict;switch(a){case"Q":return Zb;case"DDDD":return _b;case"YYYY":case"GGGG":case"gggg":return d?ac:Rb;case"Y":case"G":case"g":return cc;case"YYYYYY":case"YYYYY":case"GGGGG":case"ggggg":return d?bc:Sb;case"S":if(d)return Zb;case"SS":if(d)return $b;case"SSS":if(d)return _b;case"DDD":return Qb;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return Ub;case"a":case"A":return b._locale._meridiemParse;case"x":return Xb;case"X":return Yb;case"Z":case"ZZ":return Vb;case"T":return Wb;case"SSSS":return Tb;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"ww":case"WW":return d?$b:Pb;case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"W":case"e":case"E":return Pb;case"Do":return d?b._locale._ordinalParse:b._locale._ordinalParseLenient;default:return c=new RegExp(Y(X(a.replace("\\","")),"i"))}}function Q(a){a=a||"";var b=a.match(Vb)||[],c=b[b.length-1]||[],d=(c+"").match(hc)||["-",0,0],e=+(60*d[1])+A(d[2]);return"+"===d[0]?-e:e}function R(a,b,c){var d,e=c._a;switch(a){case"Q":null!=b&&(e[Bb]=3*(A(b)-1));break;case"M":case"MM":null!=b&&(e[Bb]=A(b)-1);break;case"MMM":case"MMMM":d=c._locale.monthsParse(b,a,c._strict),null!=d?e[Bb]=d:c._pf.invalidMonth=b;break;case"D":case"DD":null!=b&&(e[Cb]=A(b));break;case"Do":null!=b&&(e[Cb]=A(parseInt(b.match(/\d{1,2}/)[0],10)));break;case"DDD":case"DDDD":null!=b&&(c._dayOfYear=A(b));break;case"YY":e[Ab]=tb.parseTwoDigitYear(b);break;case"YYYY":case"YYYYY":case"YYYYYY":e[Ab]=A(b);break;case"a":case"A":c._isPm=c._locale.isPM(b);break;case"h":case"hh":c._pf.bigHour=!0;case"H":case"HH":e[Db]=A(b);break;case"m":case"mm":e[Eb]=A(b);break;case"s":case"ss":e[Fb]=A(b);break;case"S":case"SS":case"SSS":case"SSSS":e[Gb]=A(1e3*("0."+b));break;case"x":c._d=new Date(A(b));break;case"X":c._d=new Date(1e3*parseFloat(b));break;case"Z":case"ZZ":c._useUTC=!0,c._tzm=Q(b);break;case"dd":case"ddd":case"dddd":d=c._locale.weekdaysParse(b),null!=d?(c._w=c._w||{},c._w.d=d):c._pf.invalidWeekday=b;break;case"w":case"ww":case"W":case"WW":case"d":case"e":case"E":a=a.substr(0,1);case"gggg":case"GGGG":case"GGGGG":a=a.substr(0,2),b&&(c._w=c._w||{},c._w[a]=A(b));break;case"gg":case"GG":c._w=c._w||{},c._w[a]=tb.parseTwoDigitYear(b)}}function S(a){var c,d,e,f,g,h,i;c=a._w,null!=c.GG||null!=c.W||null!=c.E?(g=1,h=4,d=b(c.GG,a._a[Ab],hb(tb(),1,4).year),e=b(c.W,1),f=b(c.E,1)):(g=a._locale._week.dow,h=a._locale._week.doy,d=b(c.gg,a._a[Ab],hb(tb(),g,h).year),e=b(c.w,1),null!=c.d?(f=c.d,g>f&&++e):f=null!=c.e?c.e+g:g),i=ib(d,e,f,h,g),a._a[Ab]=i.year,a._dayOfYear=i.dayOfYear}function T(a){var c,d,e,f,g=[];if(!a._d){for(e=V(a),a._w&&null==a._a[Cb]&&null==a._a[Bb]&&S(a),a._dayOfYear&&(f=b(a._a[Ab],e[Ab]),a._dayOfYear>D(f)&&(a._pf._overflowDayOfYear=!0),d=db(f,0,a._dayOfYear),a._a[Bb]=d.getUTCMonth(),a._a[Cb]=d.getUTCDate()),c=0;3>c&&null==a._a[c];++c)a._a[c]=g[c]=e[c];for(;7>c;c++)a._a[c]=g[c]=null==a._a[c]?2===c?1:0:a._a[c];24===a._a[Db]&&0===a._a[Eb]&&0===a._a[Fb]&&0===a._a[Gb]&&(a._nextDay=!0,a._a[Db]=0),a._d=(a._useUTC?db:cb).apply(null,g),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()+a._tzm),a._nextDay&&(a._a[Db]=24)}}function U(a){var b;a._d||(b=y(a._i),a._a=[b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],T(a))}function V(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function W(b){if(b._f===tb.ISO_8601)return void $(b);b._a=[],b._pf.empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,j=0;for(e=O(b._f,b._locale).match(Nb)||[],c=0;c0&&b._pf.unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),j+=d.length),pc[f]?(d?b._pf.empty=!1:b._pf.unusedTokens.push(f),R(f,d,b)):b._strict&&!d&&b._pf.unusedTokens.push(f);b._pf.charsLeftOver=i-j,h.length>0&&b._pf.unusedInput.push(h),b._pf.bigHour===!0&&b._a[Db]<=12&&(b._pf.bigHour=a),b._isPm&&b._a[Db]<12&&(b._a[Db]+=12),b._isPm===!1&&12===b._a[Db]&&(b._a[Db]=0),T(b),F(b)}function X(a){return a.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e})}function Y(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function Z(a){var b,c,e,f,g;if(0===a._f.length)return a._pf.invalidFormat=!0,void(a._d=new Date(0/0));for(f=0;fg)&&(e=g,c=b));m(a,c||b)}function $(a){var b,c,d=a._i,e=dc.exec(d);if(e){for(a._pf.iso=!0,b=0,c=fc.length;c>b;b++)if(fc[b][1].exec(d)){a._f=fc[b][0]+(e[6]||" ");break}for(b=0,c=gc.length;c>b;b++)if(gc[b][1].exec(d)){a._f+=gc[b][0];break}d.match(Vb)&&(a._f+="Z"),W(a)}else a._isValid=!1}function _(a){$(a),a._isValid===!1&&(delete a._isValid,tb.createFromInputFallback(a))}function ab(a,b){var c,d=[];for(c=0;ca&&h.setFullYear(a),h}function db(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function eb(a,b){if("string"==typeof a)if(isNaN(a)){if(a=b.weekdaysParse(a),"number"!=typeof a)return null}else a=parseInt(a,10);return a}function fb(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function gb(a,b,c){var d=tb.duration(a).abs(),e=yb(d.as("s")),f=yb(d.as("m")),g=yb(d.as("h")),h=yb(d.as("d")),i=yb(d.as("M")),j=yb(d.as("y")),k=e0,k[4]=c,fb.apply({},k)}function hb(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=tb(a).add(f,"d"),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function ib(a,b,c,d,e){var f,g,h=db(a,0,1).getUTCDay();return h=0===h?7:h,c=null!=c?c:e,f=e-h+(h>d?7:0)-(e>h?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:D(a-1)+g}}function jb(b){var c,d=b._i,e=b._f;return b._locale=b._locale||tb.localeData(b._l),null===d||e===a&&""===d?tb.invalid({nullInput:!0}):("string"==typeof d&&(b._i=d=b._locale.preparse(d)),tb.isMoment(d)?new k(d,!0):(e?u(e)?Z(b):W(b):bb(b),c=new k(b),c._nextDay&&(c.add(1,"d"),c._nextDay=a),c))}function kb(a,b){var c,d;if(1===b.length&&u(b[0])&&(b=b[0]),!b.length)return tb();for(c=b[0],d=1;d=0?"+":"-";return b+p(Math.abs(a),6)},gg:function(){return p(this.weekYear()%100,2)},gggg:function(){return p(this.weekYear(),4)},ggggg:function(){return p(this.weekYear(),5)},GG:function(){return p(this.isoWeekYear()%100,2)},GGGG:function(){return p(this.isoWeekYear(),4)},GGGGG:function(){return p(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.localeData().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.localeData().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return A(this.milliseconds()/100)},SS:function(){return p(A(this.milliseconds()/10),2)},SSS:function(){return p(this.milliseconds(),3)},SSSS:function(){return p(this.milliseconds(),3)},Z:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+p(A(a/60),2)+":"+p(A(a)%60,2)},ZZ:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+p(A(a/60),2)+p(A(a)%60,2)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},x:function(){return this.valueOf()},X:function(){return this.unix()},Q:function(){return this.quarter()}},qc={},rc=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"];nc.length;)vb=nc.pop(),pc[vb+"o"]=i(pc[vb],vb);for(;oc.length;)vb=oc.pop(),pc[vb+vb]=h(pc[vb],2);pc.DDDD=h(pc.DDD,3),m(j.prototype,{set:function(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b;this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(a){return this._months[a.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(a){return this._monthsShort[a.month()]},monthsParse:function(a,b,c){var d,e,f;for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),d=0;12>d;d++){if(e=tb.utc([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(a){return this._weekdays[a.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(a){return this._weekdaysShort[a.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(a){return this._weekdaysMin[a.day()]},weekdaysParse:function(a){var b,c,d;for(this._weekdaysParse||(this._weekdaysParse=[]),b=0;7>b;b++)if(this._weekdaysParse[b]||(c=tb([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b},_longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY LT",LLLL:"dddd, MMMM D, YYYY LT"},longDateFormat:function(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b},isPM:function(a){return"p"===(a+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(a,b,c){var d=this._calendar[a];return"function"==typeof d?d.apply(b,[c]):d},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)},pastFuture:function(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)},ordinal:function(a){return this._ordinal.replace("%d",a)},_ordinal:"%d",_ordinalParse:/\d{1,2}/,preparse:function(a){return a},postformat:function(a){return a},week:function(a){return hb(a,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),tb=function(b,c,e,f){var g;return"boolean"==typeof e&&(f=e,e=a),g={},g._isAMomentObject=!0,g._i=b,g._f=c,g._l=e,g._strict=f,g._isUTC=!1,g._pf=d(),jb(g)},tb.suppressDeprecationWarnings=!1,tb.createFromInputFallback=f("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),tb.min=function(){var a=[].slice.call(arguments,0);return kb("isBefore",a)},tb.max=function(){var a=[].slice.call(arguments,0);return kb("isAfter",a)},tb.utc=function(b,c,e,f){var g;return"boolean"==typeof e&&(f=e,e=a),g={},g._isAMomentObject=!0,g._useUTC=!0,g._isUTC=!0,g._l=e,g._i=b,g._f=c,g._strict=f,g._pf=d(),jb(g).utc()},tb.unix=function(a){return tb(1e3*a)},tb.duration=function(a,b){var d,e,f,g,h=a,i=null;return tb.isDuration(a)?h={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(h={},b?h[b]=a:h.milliseconds=a):(i=Lb.exec(a))?(d="-"===i[1]?-1:1,h={y:0,d:A(i[Cb])*d,h:A(i[Db])*d,m:A(i[Eb])*d,s:A(i[Fb])*d,ms:A(i[Gb])*d}):(i=Mb.exec(a))?(d="-"===i[1]?-1:1,f=function(a){var b=a&&parseFloat(a.replace(",","."));return(isNaN(b)?0:b)*d},h={y:f(i[2]),M:f(i[3]),d:f(i[4]),h:f(i[5]),m:f(i[6]),s:f(i[7]),w:f(i[8])}):"object"==typeof h&&("from"in h||"to"in h)&&(g=r(tb(h.from),tb(h.to)),h={},h.ms=g.milliseconds,h.M=g.months),e=new l(h),tb.isDuration(a)&&c(a,"_locale")&&(e._locale=a._locale),e},tb.version=wb,tb.defaultFormat=ec,tb.ISO_8601=function(){},tb.momentProperties=Ib,tb.updateOffset=function(){},tb.relativeTimeThreshold=function(b,c){return mc[b]===a?!1:c===a?mc[b]:(mc[b]=c,!0)},tb.lang=f("moment.lang is deprecated. Use moment.locale instead.",function(a,b){return tb.locale(a,b)}),tb.locale=function(a,b){var c;return a&&(c="undefined"!=typeof b?tb.defineLocale(a,b):tb.localeData(a),c&&(tb.duration._locale=tb._locale=c)),tb._locale._abbr},tb.defineLocale=function(a,b){return null!==b?(b.abbr=a,Hb[a]||(Hb[a]=new j),Hb[a].set(b),tb.locale(a),Hb[a]):(delete Hb[a],null)},tb.langData=f("moment.langData is deprecated. Use moment.localeData instead.",function(a){return tb.localeData(a)}),tb.localeData=function(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return tb._locale;if(!u(a)){if(b=J(a))return b;a=[a]}return I(a)},tb.isMoment=function(a){return a instanceof k||null!=a&&c(a,"_isAMomentObject")},tb.isDuration=function(a){return a instanceof l};for(vb=rc.length-1;vb>=0;--vb)z(rc[vb]);tb.normalizeUnits=function(a){return x(a)},tb.invalid=function(a){var b=tb.utc(0/0);return null!=a?m(b._pf,a):b._pf.userInvalidated=!0,b},tb.parseZone=function(){return tb.apply(null,arguments).parseZone()},tb.parseTwoDigitYear=function(a){return A(a)+(A(a)>68?1900:2e3)},m(tb.fn=k.prototype,{clone:function(){return tb(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){var a=tb(this).utc();return 00:!1},parsingFlags:function(){return m({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(a){return this.zone(0,a)},local:function(a){return this._isUTC&&(this.zone(0,a),this._isUTC=!1,a&&this.add(this._dateTzOffset(),"m")),this},format:function(a){var b=N(this,a||tb.defaultFormat);return this.localeData().postformat(b)},add:s(1,"add"),subtract:s(-1,"subtract"),diff:function(a,b,c){var d,e,f,g=K(a,this),h=6e4*(this.zone()-g.zone());return b=x(b),"year"===b||"month"===b?(d=432e5*(this.daysInMonth()+g.daysInMonth()),e=12*(this.year()-g.year())+(this.month()-g.month()),f=this-tb(this).startOf("month")-(g-tb(g).startOf("month")),f-=6e4*(this.zone()-tb(this).startOf("month").zone()-(g.zone()-tb(g).startOf("month").zone())),e+=f/d,"year"===b&&(e/=12)):(d=this-g,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-h)/864e5:"week"===b?(d-h)/6048e5:d),c?e:o(e)},from:function(a,b){return tb.duration({to:this,from:a}).locale(this.locale()).humanize(!b)},fromNow:function(a){return this.from(tb(),a)},calendar:function(a){var b=a||tb(),c=K(b,this).startOf("day"),d=this.diff(c,"days",!0),e=-6>d?"sameElse":-1>d?"lastWeek":0>d?"lastDay":1>d?"sameDay":2>d?"nextDay":7>d?"nextWeek":"sameElse";return this.format(this.localeData().calendar(e,this,tb(b)))},isLeapYear:function(){return E(this.year())},isDST:function(){return this.zone()+a):(c=tb.isMoment(a)?+a:+tb(a),c<+this.clone().startOf(b))},isBefore:function(a,b){var c;return b=x("undefined"!=typeof b?b:"millisecond"),"millisecond"===b?(a=tb.isMoment(a)?a:tb(a),+a>+this):(c=tb.isMoment(a)?+a:+tb(a),+this.clone().endOf(b)a?this:a}),max:f("moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(a){return a=tb.apply(null,arguments),a>this?this:a}),zone:function(a,b){var c,d=this._offset||0;return null==a?this._isUTC?d:this._dateTzOffset():("string"==typeof a&&(a=Q(a)),Math.abs(a)<16&&(a=60*a),!this._isUTC&&b&&(c=this._dateTzOffset()),this._offset=a,this._isUTC=!0,null!=c&&this.subtract(c,"m"),d!==a&&(!b||this._changeInProgress?t(this,tb.duration(d-a,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,tb.updateOffset(this,!0),this._changeInProgress=null)),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return this._tzm?this.zone(this._tzm):"string"==typeof this._i&&this.zone(this._i),this},hasAlignedHourOffset:function(a){return a=a?tb(a).zone():0,(this.zone()-a)%60===0},daysInMonth:function(){return B(this.year(),this.month())},dayOfYear:function(a){var b=yb((tb(this).startOf("day")-tb(this).startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")},quarter:function(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)},weekYear:function(a){var b=hb(this,this.localeData()._week.dow,this.localeData()._week.doy).year;return null==a?b:this.add(a-b,"y")},isoWeekYear:function(a){var b=hb(this,1,4).year;return null==a?b:this.add(a-b,"y")},week:function(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")},isoWeek:function(a){var b=hb(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")},weekday:function(a){var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")},isoWeekday:function(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)},isoWeeksInYear:function(){return C(this.year(),1,4)},weeksInYear:function(){var a=this.localeData()._week;return C(this.year(),a.dow,a.doy)},get:function(a){return a=x(a),this[a]()},set:function(a,b){return a=x(a),"function"==typeof this[a]&&this[a](b),this},locale:function(b){var c;return b===a?this._locale._abbr:(c=tb.localeData(b),null!=c&&(this._locale=c),this)},lang:f("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(b){return b===a?this.localeData():this.locale(b)}),localeData:function(){return this._locale},_dateTzOffset:function(){return 15*Math.round(this._d.getTimezoneOffset()/15)}}),tb.fn.millisecond=tb.fn.milliseconds=ob("Milliseconds",!1),tb.fn.second=tb.fn.seconds=ob("Seconds",!1),tb.fn.minute=tb.fn.minutes=ob("Minutes",!1),tb.fn.hour=tb.fn.hours=ob("Hours",!0),tb.fn.date=ob("Date",!0),tb.fn.dates=f("dates accessor is deprecated. Use date instead.",ob("Date",!0)),tb.fn.year=ob("FullYear",!0),tb.fn.years=f("years accessor is deprecated. Use year instead.",ob("FullYear",!0)),tb.fn.days=tb.fn.day,tb.fn.months=tb.fn.month,tb.fn.weeks=tb.fn.week,tb.fn.isoWeeks=tb.fn.isoWeek,tb.fn.quarters=tb.fn.quarter,tb.fn.toJSON=tb.fn.toISOString,m(tb.duration.fn=l.prototype,{_bubble:function(){var a,b,c,d=this._milliseconds,e=this._days,f=this._months,g=this._data,h=0;g.milliseconds=d%1e3,a=o(d/1e3),g.seconds=a%60,b=o(a/60),g.minutes=b%60,c=o(b/60),g.hours=c%24,e+=o(c/24),h=o(pb(e)),e-=o(qb(h)),f+=o(e/30),e%=30,h+=o(f/12),f%=12,g.days=e,g.months=f,g.years=h},abs:function(){return this._milliseconds=Math.abs(this._milliseconds),this._days=Math.abs(this._days),this._months=Math.abs(this._months),this._data.milliseconds=Math.abs(this._data.milliseconds),this._data.seconds=Math.abs(this._data.seconds),this._data.minutes=Math.abs(this._data.minutes),this._data.hours=Math.abs(this._data.hours),this._data.months=Math.abs(this._data.months),this._data.years=Math.abs(this._data.years),this},weeks:function(){return o(this.days()/7)},valueOf:function(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*A(this._months/12)},humanize:function(a){var b=gb(this,!a,this.localeData());return a&&(b=this.localeData().pastFuture(+this,b)),this.localeData().postformat(b)},add:function(a,b){var c=tb.duration(a,b);return this._milliseconds+=c._milliseconds,this._days+=c._days,this._months+=c._months,this._bubble(),this},subtract:function(a,b){var c=tb.duration(a,b);return this._milliseconds-=c._milliseconds,this._days-=c._days,this._months-=c._months,this._bubble(),this},get:function(a){return a=x(a),this[a.toLowerCase()+"s"]()},as:function(a){var b,c;if(a=x(a),"month"===a||"year"===a)return b=this._days+this._milliseconds/864e5,c=this._months+12*pb(b),"month"===a?c:c/12;switch(b=this._days+Math.round(qb(this._months/12)),a){case"week":return b/7+this._milliseconds/6048e5;case"day":return b+this._milliseconds/864e5;case"hour":return 24*b+this._milliseconds/36e5;case"minute":return 24*b*60+this._milliseconds/6e4;case"second":return 24*b*60*60+this._milliseconds/1e3; +case"millisecond":return Math.floor(24*b*60*60*1e3)+this._milliseconds;default:throw new Error("Unknown unit "+a)}},lang:tb.fn.lang,locale:tb.fn.locale,toIsoString:f("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",function(){return this.toISOString()}),toISOString:function(){var a=Math.abs(this.years()),b=Math.abs(this.months()),c=Math.abs(this.days()),d=Math.abs(this.hours()),e=Math.abs(this.minutes()),f=Math.abs(this.seconds()+this.milliseconds()/1e3);return this.asSeconds()?(this.asSeconds()<0?"-":"")+"P"+(a?a+"Y":"")+(b?b+"M":"")+(c?c+"D":"")+(d||e||f?"T":"")+(d?d+"H":"")+(e?e+"M":"")+(f?f+"S":""):"P0D"},localeData:function(){return this._locale}}),tb.duration.fn.toString=tb.duration.fn.toISOString;for(vb in ic)c(ic,vb)&&rb(vb.toLowerCase());tb.duration.fn.asMilliseconds=function(){return this.as("ms")},tb.duration.fn.asSeconds=function(){return this.as("s")},tb.duration.fn.asMinutes=function(){return this.as("m")},tb.duration.fn.asHours=function(){return this.as("h")},tb.duration.fn.asDays=function(){return this.as("d")},tb.duration.fn.asWeeks=function(){return this.as("weeks")},tb.duration.fn.asMonths=function(){return this.as("M")},tb.duration.fn.asYears=function(){return this.as("y")},tb.locale("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===A(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),function(a){a(tb)}(function(a){return a.defineLocale("af",{months:"Januarie_Februarie_Maart_April_Mei_Junie_Julie_Augustus_September_Oktober_November_Desember".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Aug_Sep_Okt_Nov_Des".split("_"),weekdays:"Sondag_Maandag_Dinsdag_Woensdag_Donderdag_Vrydag_Saterdag".split("_"),weekdaysShort:"Son_Maa_Din_Woe_Don_Vry_Sat".split("_"),weekdaysMin:"So_Ma_Di_Wo_Do_Vr_Sa".split("_"),meridiem:function(a,b,c){return 12>a?c?"vm":"VM":c?"nm":"NM"},longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Vandag om] LT",nextDay:"[Môre om] LT",nextWeek:"dddd [om] LT",lastDay:"[Gister om] LT",lastWeek:"[Laas] dddd [om] LT",sameElse:"L"},relativeTime:{future:"oor %s",past:"%s gelede",s:"'n paar sekondes",m:"'n minuut",mm:"%d minute",h:"'n uur",hh:"%d ure",d:"'n dag",dd:"%d dae",M:"'n maand",MM:"%d maande",y:"'n jaar",yy:"%d jaar"},ordinalParse:/\d{1,2}(ste|de)/,ordinal:function(a){return a+(1===a||8===a||a>=20?"ste":"de")},week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("ar-ma",{months:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),monthsShort:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),weekdays:"الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},week:{dow:6,doy:12}})}),function(a){a(tb)}(function(a){var b={1:"١",2:"٢",3:"٣",4:"٤",5:"٥",6:"٦",7:"٧",8:"٨",9:"٩",0:"٠"},c={"١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9","٠":"0"};return a.defineLocale("ar-sa",{months:"يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر".split("_"),monthsShort:"يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر".split("_"),weekdays:"الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},meridiem:function(a){return 12>a?"ص":"م"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},preparse:function(a){return a.replace(/[١٢٣٤٥٦٧٨٩٠]/g,function(a){return c[a]}).replace(/،/g,",")},postformat:function(a){return a.replace(/\d/g,function(a){return b[a]}).replace(/,/g,"،")},week:{dow:6,doy:12}})}),function(a){a(tb)}(function(a){var b={1:"١",2:"٢",3:"٣",4:"٤",5:"٥",6:"٦",7:"٧",8:"٨",9:"٩",0:"٠"},c={"١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9","٠":"0"},d=function(a){return 0===a?0:1===a?1:2===a?2:a%100>=3&&10>=a%100?3:a%100>=11?4:5},e={s:["أقل من ثانية","ثانية واحدة",["ثانيتان","ثانيتين"],"%d ثوان","%d ثانية","%d ثانية"],m:["أقل من دقيقة","دقيقة واحدة",["دقيقتان","دقيقتين"],"%d دقائق","%d دقيقة","%d دقيقة"],h:["أقل من ساعة","ساعة واحدة",["ساعتان","ساعتين"],"%d ساعات","%d ساعة","%d ساعة"],d:["أقل من يوم","يوم واحد",["يومان","يومين"],"%d أيام","%d يومًا","%d يوم"],M:["أقل من شهر","شهر واحد",["شهران","شهرين"],"%d أشهر","%d شهرا","%d شهر"],y:["أقل من عام","عام واحد",["عامان","عامين"],"%d أعوام","%d عامًا","%d عام"]},f=function(a){return function(b,c){var f=d(b),g=e[a][d(b)];return 2===f&&(g=g[c?0:1]),g.replace(/%d/i,b)}},g=["كانون الثاني يناير","شباط فبراير","آذار مارس","نيسان أبريل","أيار مايو","حزيران يونيو","تموز يوليو","آب أغسطس","أيلول سبتمبر","تشرين الأول أكتوبر","تشرين الثاني نوفمبر","كانون الأول ديسمبر"];return a.defineLocale("ar",{months:g,monthsShort:g,weekdays:"الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},meridiem:function(a){return 12>a?"ص":"م"},calendar:{sameDay:"[اليوم عند الساعة] LT",nextDay:"[غدًا عند الساعة] LT",nextWeek:"dddd [عند الساعة] LT",lastDay:"[أمس عند الساعة] LT",lastWeek:"dddd [عند الساعة] LT",sameElse:"L"},relativeTime:{future:"بعد %s",past:"منذ %s",s:f("s"),m:f("m"),mm:f("m"),h:f("h"),hh:f("h"),d:f("d"),dd:f("d"),M:f("M"),MM:f("M"),y:f("y"),yy:f("y")},preparse:function(a){return a.replace(/[١٢٣٤٥٦٧٨٩٠]/g,function(a){return c[a]}).replace(/،/g,",")},postformat:function(a){return a.replace(/\d/g,function(a){return b[a]}).replace(/,/g,"،")},week:{dow:6,doy:12}})}),function(a){a(tb)}(function(a){var b={1:"-inci",5:"-inci",8:"-inci",70:"-inci",80:"-inci",2:"-nci",7:"-nci",20:"-nci",50:"-nci",3:"-üncü",4:"-üncü",100:"-üncü",6:"-ncı",9:"-uncu",10:"-uncu",30:"-uncu",60:"-ıncı",90:"-ıncı"};return a.defineLocale("az",{months:"yanvar_fevral_mart_aprel_may_iyun_iyul_avqust_sentyabr_oktyabr_noyabr_dekabr".split("_"),monthsShort:"yan_fev_mar_apr_may_iyn_iyl_avq_sen_okt_noy_dek".split("_"),weekdays:"Bazar_Bazar ertəsi_Çərşənbə axşamı_Çərşənbə_Cümə axşamı_Cümə_Şənbə".split("_"),weekdaysShort:"Baz_BzE_ÇAx_Çər_CAx_Cüm_Şən".split("_"),weekdaysMin:"Bz_BE_ÇA_Çə_CA_Cü_Şə".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[bugün saat] LT",nextDay:"[sabah saat] LT",nextWeek:"[gələn həftə] dddd [saat] LT",lastDay:"[dünən] LT",lastWeek:"[keçən həftə] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s əvvəl",s:"birneçə saniyyə",m:"bir dəqiqə",mm:"%d dəqiqə",h:"bir saat",hh:"%d saat",d:"bir gün",dd:"%d gün",M:"bir ay",MM:"%d ay",y:"bir il",yy:"%d il"},meridiem:function(a){return 4>a?"gecə":12>a?"səhər":17>a?"gündüz":"axşam"},ordinalParse:/\d{1,2}-(ıncı|inci|nci|üncü|ncı|uncu)/,ordinal:function(a){if(0===a)return a+"-ıncı";var c=a%10,d=a%100-c,e=a>=100?100:null;return a+(b[c]||b[d]||b[e])},week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){function b(a,b){var c=a.split("_");return b%10===1&&b%100!==11?c[0]:b%10>=2&&4>=b%10&&(10>b%100||b%100>=20)?c[1]:c[2]}function c(a,c,d){var e={mm:c?"хвіліна_хвіліны_хвілін":"хвіліну_хвіліны_хвілін",hh:c?"гадзіна_гадзіны_гадзін":"гадзіну_гадзіны_гадзін",dd:"дзень_дні_дзён",MM:"месяц_месяцы_месяцаў",yy:"год_гады_гадоў"};return"m"===d?c?"хвіліна":"хвіліну":"h"===d?c?"гадзіна":"гадзіну":a+" "+b(e[d],+a)}function d(a,b){var c={nominative:"студзень_люты_сакавік_красавік_травень_чэрвень_ліпень_жнівень_верасень_кастрычнік_лістапад_снежань".split("_"),accusative:"студзеня_лютага_сакавіка_красавіка_траўня_чэрвеня_ліпеня_жніўня_верасня_кастрычніка_лістапада_снежня".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function e(a,b){var c={nominative:"нядзеля_панядзелак_аўторак_серада_чацвер_пятніца_субота".split("_"),accusative:"нядзелю_панядзелак_аўторак_сераду_чацвер_пятніцу_суботу".split("_")},d=/\[ ?[Вв] ?(?:мінулую|наступную)? ?\] ?dddd/.test(b)?"accusative":"nominative";return c[d][a.day()]}return a.defineLocale("be",{months:d,monthsShort:"студ_лют_сак_крас_трав_чэрв_ліп_жнів_вер_каст_ліст_снеж".split("_"),weekdays:e,weekdaysShort:"нд_пн_ат_ср_чц_пт_сб".split("_"),weekdaysMin:"нд_пн_ат_ср_чц_пт_сб".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY г.",LLL:"D MMMM YYYY г., LT",LLLL:"dddd, D MMMM YYYY г., LT"},calendar:{sameDay:"[Сёння ў] LT",nextDay:"[Заўтра ў] LT",lastDay:"[Учора ў] LT",nextWeek:function(){return"[У] dddd [ў] LT"},lastWeek:function(){switch(this.day()){case 0:case 3:case 5:case 6:return"[У мінулую] dddd [ў] LT";case 1:case 2:case 4:return"[У мінулы] dddd [ў] LT"}},sameElse:"L"},relativeTime:{future:"праз %s",past:"%s таму",s:"некалькі секунд",m:c,mm:c,h:c,hh:c,d:"дзень",dd:c,M:"месяц",MM:c,y:"год",yy:c},meridiem:function(a){return 4>a?"ночы":12>a?"раніцы":17>a?"дня":"вечара"},ordinalParse:/\d{1,2}-(і|ы|га)/,ordinal:function(a,b){switch(b){case"M":case"d":case"DDD":case"w":case"W":return a%10!==2&&a%10!==3||a%100===12||a%100===13?a+"-ы":a+"-і";case"D":return a+"-га";default:return a}},week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){return a.defineLocale("bg",{months:"януари_февруари_март_април_май_юни_юли_август_септември_октомври_ноември_декември".split("_"),monthsShort:"янр_фев_мар_апр_май_юни_юли_авг_сеп_окт_ное_дек".split("_"),weekdays:"неделя_понеделник_вторник_сряда_четвъртък_петък_събота".split("_"),weekdaysShort:"нед_пон_вто_сря_чет_пет_съб".split("_"),weekdaysMin:"нд_пн_вт_ср_чт_пт_сб".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"D.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Днес в] LT",nextDay:"[Утре в] LT",nextWeek:"dddd [в] LT",lastDay:"[Вчера в] LT",lastWeek:function(){switch(this.day()){case 0:case 3:case 6:return"[В изминалата] dddd [в] LT";case 1:case 2:case 4:case 5:return"[В изминалия] dddd [в] LT"}},sameElse:"L"},relativeTime:{future:"след %s",past:"преди %s",s:"няколко секунди",m:"минута",mm:"%d минути",h:"час",hh:"%d часа",d:"ден",dd:"%d дни",M:"месец",MM:"%d месеца",y:"година",yy:"%d години"},ordinalParse:/\d{1,2}-(ев|ен|ти|ви|ри|ми)/,ordinal:function(a){var b=a%10,c=a%100;return 0===a?a+"-ев":0===c?a+"-ен":c>10&&20>c?a+"-ти":1===b?a+"-ви":2===b?a+"-ри":7===b||8===b?a+"-ми":a+"-ти"},week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){var b={1:"১",2:"২",3:"৩",4:"৪",5:"৫",6:"৬",7:"৭",8:"৮",9:"৯",0:"০"},c={"১":"1","২":"2","৩":"3","৪":"4","৫":"5","৬":"6","৭":"7","৮":"8","৯":"9","০":"0"};return a.defineLocale("bn",{months:"জানুয়ারী_ফেবুয়ারী_মার্চ_এপ্রিল_মে_জুন_জুলাই_অগাস্ট_সেপ্টেম্বর_অক্টোবর_নভেম্বর_ডিসেম্বর".split("_"),monthsShort:"জানু_ফেব_মার্চ_এপর_মে_জুন_জুল_অগ_সেপ্ট_অক্টো_নভ_ডিসেম্".split("_"),weekdays:"রবিবার_সোমবার_মঙ্গলবার_বুধবার_বৃহস্পত্তিবার_শুক্রুবার_শনিবার".split("_"),weekdaysShort:"রবি_সোম_মঙ্গল_বুধ_বৃহস্পত্তি_শুক্রু_শনি".split("_"),weekdaysMin:"রব_সম_মঙ্গ_বু_ব্রিহ_শু_শনি".split("_"),longDateFormat:{LT:"A h:mm সময়",LTS:"A h:mm:ss সময়",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, LT",LLLL:"dddd, D MMMM YYYY, LT"},calendar:{sameDay:"[আজ] LT",nextDay:"[আগামীকাল] LT",nextWeek:"dddd, LT",lastDay:"[গতকাল] LT",lastWeek:"[গত] dddd, LT",sameElse:"L"},relativeTime:{future:"%s পরে",past:"%s আগে",s:"কএক সেকেন্ড",m:"এক মিনিট",mm:"%d মিনিট",h:"এক ঘন্টা",hh:"%d ঘন্টা",d:"এক দিন",dd:"%d দিন",M:"এক মাস",MM:"%d মাস",y:"এক বছর",yy:"%d বছর"},preparse:function(a){return a.replace(/[১২৩৪৫৬৭৮৯০]/g,function(a){return c[a]})},postformat:function(a){return a.replace(/\d/g,function(a){return b[a]})},meridiem:function(a){return 4>a?"রাত":10>a?"শকাল":17>a?"দুপুর":20>a?"বিকেল":"রাত"},week:{dow:0,doy:6}})}),function(a){a(tb)}(function(a){var b={1:"༡",2:"༢",3:"༣",4:"༤",5:"༥",6:"༦",7:"༧",8:"༨",9:"༩",0:"༠"},c={"༡":"1","༢":"2","༣":"3","༤":"4","༥":"5","༦":"6","༧":"7","༨":"8","༩":"9","༠":"0"};return a.defineLocale("bo",{months:"ཟླ་བ་དང་པོ_ཟླ་བ་གཉིས་པ_ཟླ་བ་གསུམ་པ_ཟླ་བ་བཞི་པ_ཟླ་བ་ལྔ་པ_ཟླ་བ་དྲུག་པ_ཟླ་བ་བདུན་པ_ཟླ་བ་བརྒྱད་པ_ཟླ་བ་དགུ་པ_ཟླ་བ་བཅུ་པ_ཟླ་བ་བཅུ་གཅིག་པ_ཟླ་བ་བཅུ་གཉིས་པ".split("_"),monthsShort:"ཟླ་བ་དང་པོ_ཟླ་བ་གཉིས་པ_ཟླ་བ་གསུམ་པ_ཟླ་བ་བཞི་པ_ཟླ་བ་ལྔ་པ_ཟླ་བ་དྲུག་པ_ཟླ་བ་བདུན་པ_ཟླ་བ་བརྒྱད་པ_ཟླ་བ་དགུ་པ_ཟླ་བ་བཅུ་པ_ཟླ་བ་བཅུ་གཅིག་པ_ཟླ་བ་བཅུ་གཉིས་པ".split("_"),weekdays:"གཟའ་ཉི་མ་_གཟའ་ཟླ་བ་_གཟའ་མིག་དམར་_གཟའ་ལྷག་པ་_གཟའ་ཕུར་བུ_གཟའ་པ་སངས་_གཟའ་སྤེན་པ་".split("_"),weekdaysShort:"ཉི་མ་_ཟླ་བ་_མིག་དམར་_ལྷག་པ་_ཕུར་བུ_པ་སངས་_སྤེན་པ་".split("_"),weekdaysMin:"ཉི་མ་_ཟླ་བ་_མིག་དམར་_ལྷག་པ་_ཕུར་བུ_པ་སངས་_སྤེན་པ་".split("_"),longDateFormat:{LT:"A h:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, LT",LLLL:"dddd, D MMMM YYYY, LT"},calendar:{sameDay:"[དི་རིང] LT",nextDay:"[སང་ཉིན] LT",nextWeek:"[བདུན་ཕྲག་རྗེས་མ], LT",lastDay:"[ཁ་སང] LT",lastWeek:"[བདུན་ཕྲག་མཐའ་མ] dddd, LT",sameElse:"L"},relativeTime:{future:"%s ལ་",past:"%s སྔན་ལ",s:"ལམ་སང",m:"སྐར་མ་གཅིག",mm:"%d སྐར་མ",h:"ཆུ་ཚོད་གཅིག",hh:"%d ཆུ་ཚོད",d:"ཉིན་གཅིག",dd:"%d ཉིན་",M:"ཟླ་བ་གཅིག",MM:"%d ཟླ་བ",y:"ལོ་གཅིག",yy:"%d ལོ"},preparse:function(a){return a.replace(/[༡༢༣༤༥༦༧༨༩༠]/g,function(a){return c[a]})},postformat:function(a){return a.replace(/\d/g,function(a){return b[a]})},meridiem:function(a){return 4>a?"མཚན་མོ":10>a?"ཞོགས་ཀས":17>a?"ཉིན་གུང":20>a?"དགོང་དག":"མཚན་མོ"},week:{dow:0,doy:6}})}),function(a){a(tb)}(function(b){function c(a,b,c){var d={mm:"munutenn",MM:"miz",dd:"devezh"};return a+" "+f(d[c],a)}function d(a){switch(e(a)){case 1:case 3:case 4:case 5:case 9:return a+" bloaz";default:return a+" vloaz"}}function e(a){return a>9?e(a%10):a}function f(a,b){return 2===b?g(a):a}function g(b){var c={m:"v",b:"v",d:"z"};return c[b.charAt(0)]===a?b:c[b.charAt(0)]+b.substring(1)}return b.defineLocale("br",{months:"Genver_C'hwevrer_Meurzh_Ebrel_Mae_Mezheven_Gouere_Eost_Gwengolo_Here_Du_Kerzu".split("_"),monthsShort:"Gen_C'hwe_Meu_Ebr_Mae_Eve_Gou_Eos_Gwe_Her_Du_Ker".split("_"),weekdays:"Sul_Lun_Meurzh_Merc'her_Yaou_Gwener_Sadorn".split("_"),weekdaysShort:"Sul_Lun_Meu_Mer_Yao_Gwe_Sad".split("_"),weekdaysMin:"Su_Lu_Me_Mer_Ya_Gw_Sa".split("_"),longDateFormat:{LT:"h[e]mm A",LTS:"h[e]mm:ss A",L:"DD/MM/YYYY",LL:"D [a viz] MMMM YYYY",LLL:"D [a viz] MMMM YYYY LT",LLLL:"dddd, D [a viz] MMMM YYYY LT"},calendar:{sameDay:"[Hiziv da] LT",nextDay:"[Warc'hoazh da] LT",nextWeek:"dddd [da] LT",lastDay:"[Dec'h da] LT",lastWeek:"dddd [paset da] LT",sameElse:"L"},relativeTime:{future:"a-benn %s",past:"%s 'zo",s:"un nebeud segondennoù",m:"ur vunutenn",mm:c,h:"un eur",hh:"%d eur",d:"un devezh",dd:c,M:"ur miz",MM:c,y:"ur bloaz",yy:d},ordinalParse:/\d{1,2}(añ|vet)/,ordinal:function(a){var b=1===a?"añ":"vet";return a+b},week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){function b(a,b,c){var d=a+" ";switch(c){case"m":return b?"jedna minuta":"jedne minute";case"mm":return d+=1===a?"minuta":2===a||3===a||4===a?"minute":"minuta";case"h":return b?"jedan sat":"jednog sata";case"hh":return d+=1===a?"sat":2===a||3===a||4===a?"sata":"sati";case"dd":return d+=1===a?"dan":"dana";case"MM":return d+=1===a?"mjesec":2===a||3===a||4===a?"mjeseca":"mjeseci";case"yy":return d+=1===a?"godina":2===a||3===a||4===a?"godine":"godina"}}return a.defineLocale("bs",{months:"januar_februar_mart_april_maj_juni_juli_august_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj._jun._jul._aug._sep._okt._nov._dec.".split("_"),weekdays:"nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sri._čet._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_če_pe_su".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedjelju] [u] LT";case 3:return"[u] [srijedu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[jučer u] LT",lastWeek:function(){switch(this.day()){case 0:case 3:return"[prošlu] dddd [u] LT";case 6:return"[prošle] [subote] [u] LT";case 1:case 2:case 4:case 5:return"[prošli] dddd [u] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"prije %s",s:"par sekundi",m:b,mm:b,h:b,hh:b,d:"dan",dd:b,M:"mjesec",MM:b,y:"godinu",yy:b},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){return a.defineLocale("ca",{months:"gener_febrer_març_abril_maig_juny_juliol_agost_setembre_octubre_novembre_desembre".split("_"),monthsShort:"gen._febr._mar._abr._mai._jun._jul._ag._set._oct._nov._des.".split("_"),weekdays:"diumenge_dilluns_dimarts_dimecres_dijous_divendres_dissabte".split("_"),weekdaysShort:"dg._dl._dt._dc._dj._dv._ds.".split("_"),weekdaysMin:"Dg_Dl_Dt_Dc_Dj_Dv_Ds".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:function(){return"[avui a "+(1!==this.hours()?"les":"la")+"] LT"},nextDay:function(){return"[demà a "+(1!==this.hours()?"les":"la")+"] LT"},nextWeek:function(){return"dddd [a "+(1!==this.hours()?"les":"la")+"] LT"},lastDay:function(){return"[ahir a "+(1!==this.hours()?"les":"la")+"] LT"},lastWeek:function(){return"[el] dddd [passat a "+(1!==this.hours()?"les":"la")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"fa %s",s:"uns segons",m:"un minut",mm:"%d minuts",h:"una hora",hh:"%d hores",d:"un dia",dd:"%d dies",M:"un mes",MM:"%d mesos",y:"un any",yy:"%d anys"},ordinalParse:/\d{1,2}(r|n|t|è|a)/,ordinal:function(a,b){var c=1===a?"r":2===a?"n":3===a?"r":4===a?"t":"è";return("w"===b||"W"===b)&&(c="a"),a+c},week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){function b(a){return a>1&&5>a&&1!==~~(a/10)}function c(a,c,d,e){var f=a+" ";switch(d){case"s":return c||e?"pár sekund":"pár sekundami";case"m":return c?"minuta":e?"minutu":"minutou";case"mm":return c||e?f+(b(a)?"minuty":"minut"):f+"minutami";break;case"h":return c?"hodina":e?"hodinu":"hodinou";case"hh":return c||e?f+(b(a)?"hodiny":"hodin"):f+"hodinami";break;case"d":return c||e?"den":"dnem";case"dd":return c||e?f+(b(a)?"dny":"dní"):f+"dny";break;case"M":return c||e?"měsíc":"měsícem";case"MM":return c||e?f+(b(a)?"měsíce":"měsíců"):f+"měsíci";break;case"y":return c||e?"rok":"rokem";case"yy":return c||e?f+(b(a)?"roky":"let"):f+"lety"}}var d="leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec".split("_"),e="led_úno_bře_dub_kvě_čvn_čvc_srp_zář_říj_lis_pro".split("_");return a.defineLocale("cs",{months:d,monthsShort:e,monthsParse:function(a,b){var c,d=[];for(c=0;12>c;c++)d[c]=new RegExp("^"+a[c]+"$|^"+b[c]+"$","i");return d}(d,e),weekdays:"neděle_pondělí_úterý_středa_čtvrtek_pátek_sobota".split("_"),weekdaysShort:"ne_po_út_st_čt_pá_so".split("_"),weekdaysMin:"ne_po_út_st_čt_pá_so".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd D. MMMM YYYY LT"},calendar:{sameDay:"[dnes v] LT",nextDay:"[zítra v] LT",nextWeek:function(){switch(this.day()){case 0:return"[v neděli v] LT";case 1:case 2:return"[v] dddd [v] LT";case 3:return"[ve středu v] LT";case 4:return"[ve čtvrtek v] LT";case 5:return"[v pátek v] LT";case 6:return"[v sobotu v] LT"}},lastDay:"[včera v] LT",lastWeek:function(){switch(this.day()){case 0:return"[minulou neděli v] LT";case 1:case 2:return"[minulé] dddd [v] LT";case 3:return"[minulou středu v] LT";case 4:case 5:return"[minulý] dddd [v] LT";case 6:return"[minulou sobotu v] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"před %s",s:c,m:c,mm:c,h:c,hh:c,d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("cv",{months:"кăрлач_нарăс_пуш_ака_май_çĕртме_утă_çурла_авăн_юпа_чӳк_раштав".split("_"),monthsShort:"кăр_нар_пуш_ака_май_çĕр_утă_çур_ав_юпа_чӳк_раш".split("_"),weekdays:"вырсарникун_тунтикун_ытларикун_юнкун_кĕçнерникун_эрнекун_шăматкун".split("_"),weekdaysShort:"выр_тун_ытл_юн_кĕç_эрн_шăм".split("_"),weekdaysMin:"вр_тн_ыт_юн_кç_эр_шм".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD-MM-YYYY",LL:"YYYY [çулхи] MMMM [уйăхĕн] D[-мĕшĕ]",LLL:"YYYY [çулхи] MMMM [уйăхĕн] D[-мĕшĕ], LT",LLLL:"dddd, YYYY [çулхи] MMMM [уйăхĕн] D[-мĕшĕ], LT"},calendar:{sameDay:"[Паян] LT [сехетре]",nextDay:"[Ыран] LT [сехетре]",lastDay:"[Ĕнер] LT [сехетре]",nextWeek:"[Çитес] dddd LT [сехетре]",lastWeek:"[Иртнĕ] dddd LT [сехетре]",sameElse:"L"},relativeTime:{future:function(a){var b=/сехет$/i.exec(a)?"рен":/çул$/i.exec(a)?"тан":"ран";return a+b},past:"%s каялла",s:"пĕр-ик çеккунт",m:"пĕр минут",mm:"%d минут",h:"пĕр сехет",hh:"%d сехет",d:"пĕр кун",dd:"%d кун",M:"пĕр уйăх",MM:"%d уйăх",y:"пĕр çул",yy:"%d çул"},ordinalParse:/\d{1,2}-мĕш/,ordinal:"%d-мĕш",week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){return a.defineLocale("cy",{months:"Ionawr_Chwefror_Mawrth_Ebrill_Mai_Mehefin_Gorffennaf_Awst_Medi_Hydref_Tachwedd_Rhagfyr".split("_"),monthsShort:"Ion_Chwe_Maw_Ebr_Mai_Meh_Gor_Aws_Med_Hyd_Tach_Rhag".split("_"),weekdays:"Dydd Sul_Dydd Llun_Dydd Mawrth_Dydd Mercher_Dydd Iau_Dydd Gwener_Dydd Sadwrn".split("_"),weekdaysShort:"Sul_Llun_Maw_Mer_Iau_Gwe_Sad".split("_"),weekdaysMin:"Su_Ll_Ma_Me_Ia_Gw_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Heddiw am] LT",nextDay:"[Yfory am] LT",nextWeek:"dddd [am] LT",lastDay:"[Ddoe am] LT",lastWeek:"dddd [diwethaf am] LT",sameElse:"L"},relativeTime:{future:"mewn %s",past:"%s yn ôl",s:"ychydig eiliadau",m:"munud",mm:"%d munud",h:"awr",hh:"%d awr",d:"diwrnod",dd:"%d diwrnod",M:"mis",MM:"%d mis",y:"blwyddyn",yy:"%d flynedd"},ordinalParse:/\d{1,2}(fed|ain|af|il|ydd|ed|eg)/,ordinal:function(a){var b=a,c="",d=["","af","il","ydd","ydd","ed","ed","ed","fed","fed","fed","eg","fed","eg","eg","fed","eg","eg","fed","eg","fed"];return b>20?c=40===b||50===b||60===b||80===b||100===b?"fed":"ain":b>0&&(c=d[b]),a+c},week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("da",{months:"januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tir_ons_tor_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd [d.] D. MMMM YYYY LT"},calendar:{sameDay:"[I dag kl.] LT",nextDay:"[I morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[I går kl.] LT",lastWeek:"[sidste] dddd [kl] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"få sekunder",m:"et minut",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dage",M:"en måned",MM:"%d måneder",y:"et år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){function b(a,b,c){var d={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[a+" Tage",a+" Tagen"],M:["ein Monat","einem Monat"],MM:[a+" Monate",a+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[a+" Jahre",a+" Jahren"]};return b?d[c][0]:d[c][1]}return a.defineLocale("de-at",{months:"Jänner_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jän._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[Heute um] LT [Uhr]",sameElse:"L",nextDay:"[Morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[Gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",m:b,mm:"%d Minuten",h:b,hh:"%d Stunden",d:b,dd:b,M:b,MM:b,y:b,yy:b},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){function b(a,b,c){var d={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[a+" Tage",a+" Tagen"],M:["ein Monat","einem Monat"],MM:[a+" Monate",a+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[a+" Jahre",a+" Jahren"]};return b?d[c][0]:d[c][1]}return a.defineLocale("de",{months:"Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[Heute um] LT [Uhr]",sameElse:"L",nextDay:"[Morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[Gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",m:b,mm:"%d Minuten",h:b,hh:"%d Stunden",d:b,dd:b,M:b,MM:b,y:b,yy:b},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("el",{monthsNominativeEl:"Ιανουάριος_Φεβρουάριος_Μάρτιος_Απρίλιος_Μάιος_Ιούνιος_Ιούλιος_Αύγουστος_Σεπτέμβριος_Οκτώβριος_Νοέμβριος_Δεκέμβριος".split("_"),monthsGenitiveEl:"Ιανουαρίου_Φεβρουαρίου_Μαρτίου_Απριλίου_Μαΐου_Ιουνίου_Ιουλίου_Αυγούστου_Σεπτεμβρίου_Οκτωβρίου_Νοεμβρίου_Δεκεμβρίου".split("_"),months:function(a,b){return/D/.test(b.substring(0,b.indexOf("MMMM")))?this._monthsGenitiveEl[a.month()]:this._monthsNominativeEl[a.month()]},monthsShort:"Ιαν_Φεβ_Μαρ_Απρ_Μαϊ_Ιουν_Ιουλ_Αυγ_Σεπ_Οκτ_Νοε_Δεκ".split("_"),weekdays:"Κυριακή_Δευτέρα_Τρίτη_Τετάρτη_Πέμπτη_Παρασκευή_Σάββατο".split("_"),weekdaysShort:"Κυρ_Δευ_Τρι_Τετ_Πεμ_Παρ_Σαβ".split("_"),weekdaysMin:"Κυ_Δε_Τρ_Τε_Πε_Πα_Σα".split("_"),meridiem:function(a,b,c){return a>11?c?"μμ":"ΜΜ":c?"πμ":"ΠΜ"},isPM:function(a){return"μ"===(a+"").toLowerCase()[0]},meridiemParse:/[ΠΜ]\.?Μ?\.?/i,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendarEl:{sameDay:"[Σήμερα {}] LT",nextDay:"[Αύριο {}] LT",nextWeek:"dddd [{}] LT",lastDay:"[Χθες {}] LT",lastWeek:function(){switch(this.day()){case 6:return"[το προηγούμενο] dddd [{}] LT";default:return"[την προηγούμενη] dddd [{}] LT"}},sameElse:"L"},calendar:function(a,b){var c=this._calendarEl[a],d=b&&b.hours();return"function"==typeof c&&(c=c.apply(b)),c.replace("{}",d%12===1?"στη":"στις")},relativeTime:{future:"σε %s",past:"%s πριν",s:"λίγα δευτερόλεπτα",m:"ένα λεπτό",mm:"%d λεπτά",h:"μία ώρα",hh:"%d ώρες",d:"μία μέρα",dd:"%d μέρες",M:"ένας μήνας",MM:"%d μήνες",y:"ένας χρόνος",yy:"%d χρόνια"},ordinalParse:/\d{1,2}η/,ordinal:"%dη",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("en-au",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},ordinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(a){var b=a%10,c=1===~~(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c},week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("en-ca",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"YYYY-MM-DD",LL:"D MMMM, YYYY",LLL:"D MMMM, YYYY LT",LLLL:"dddd, D MMMM, YYYY LT"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},ordinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(a){var b=a%10,c=1===~~(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}})}),function(a){a(tb)}(function(a){return a.defineLocale("en-gb",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},ordinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(a){var b=a%10,c=1===~~(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c},week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("eo",{months:"januaro_februaro_marto_aprilo_majo_junio_julio_aŭgusto_septembro_oktobro_novembro_decembro".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aŭg_sep_okt_nov_dec".split("_"),weekdays:"Dimanĉo_Lundo_Mardo_Merkredo_Ĵaŭdo_Vendredo_Sabato".split("_"),weekdaysShort:"Dim_Lun_Mard_Merk_Ĵaŭ_Ven_Sab".split("_"),weekdaysMin:"Di_Lu_Ma_Me_Ĵa_Ve_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"YYYY-MM-DD",LL:"D[-an de] MMMM, YYYY",LLL:"D[-an de] MMMM, YYYY LT",LLLL:"dddd, [la] D[-an de] MMMM, YYYY LT"},meridiem:function(a,b,c){return a>11?c?"p.t.m.":"P.T.M.":c?"a.t.m.":"A.T.M."},calendar:{sameDay:"[Hodiaŭ je] LT",nextDay:"[Morgaŭ je] LT",nextWeek:"dddd [je] LT",lastDay:"[Hieraŭ je] LT",lastWeek:"[pasinta] dddd [je] LT",sameElse:"L"},relativeTime:{future:"je %s",past:"antaŭ %s",s:"sekundoj",m:"minuto",mm:"%d minutoj",h:"horo",hh:"%d horoj",d:"tago",dd:"%d tagoj",M:"monato",MM:"%d monatoj",y:"jaro",yy:"%d jaroj"},ordinalParse:/\d{1,2}a/,ordinal:"%da",week:{dow:1,doy:7}}) +}),function(a){a(tb)}(function(a){var b="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),c="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_");return a.defineLocale("es",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(a,d){return/-MMM-/.test(d)?c[a.month()]:b[a.month()]},weekdays:"domingo_lunes_martes_miércoles_jueves_viernes_sábado".split("_"),weekdaysShort:"dom._lun._mar._mié._jue._vie._sáb.".split("_"),weekdaysMin:"Do_Lu_Ma_Mi_Ju_Vi_Sá".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[mañana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un día",dd:"%d días",M:"un mes",MM:"%d meses",y:"un año",yy:"%d años"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){function b(a,b,c,d){var e={s:["mõne sekundi","mõni sekund","paar sekundit"],m:["ühe minuti","üks minut"],mm:[a+" minuti",a+" minutit"],h:["ühe tunni","tund aega","üks tund"],hh:[a+" tunni",a+" tundi"],d:["ühe päeva","üks päev"],M:["kuu aja","kuu aega","üks kuu"],MM:[a+" kuu",a+" kuud"],y:["ühe aasta","aasta","üks aasta"],yy:[a+" aasta",a+" aastat"]};return b?e[c][2]?e[c][2]:e[c][1]:d?e[c][0]:e[c][1]}return a.defineLocale("et",{months:"jaanuar_veebruar_märts_aprill_mai_juuni_juuli_august_september_oktoober_november_detsember".split("_"),monthsShort:"jaan_veebr_märts_apr_mai_juuni_juuli_aug_sept_okt_nov_dets".split("_"),weekdays:"pühapäev_esmaspäev_teisipäev_kolmapäev_neljapäev_reede_laupäev".split("_"),weekdaysShort:"P_E_T_K_N_R_L".split("_"),weekdaysMin:"P_E_T_K_N_R_L".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[Täna,] LT",nextDay:"[Homme,] LT",nextWeek:"[Järgmine] dddd LT",lastDay:"[Eile,] LT",lastWeek:"[Eelmine] dddd LT",sameElse:"L"},relativeTime:{future:"%s pärast",past:"%s tagasi",s:b,m:b,mm:b,h:b,hh:b,d:b,dd:"%d päeva",M:b,MM:b,y:b,yy:b},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("eu",{months:"urtarrila_otsaila_martxoa_apirila_maiatza_ekaina_uztaila_abuztua_iraila_urria_azaroa_abendua".split("_"),monthsShort:"urt._ots._mar._api._mai._eka._uzt._abu._ira._urr._aza._abe.".split("_"),weekdays:"igandea_astelehena_asteartea_asteazkena_osteguna_ostirala_larunbata".split("_"),weekdaysShort:"ig._al._ar._az._og._ol._lr.".split("_"),weekdaysMin:"ig_al_ar_az_og_ol_lr".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"YYYY-MM-DD",LL:"YYYY[ko] MMMM[ren] D[a]",LLL:"YYYY[ko] MMMM[ren] D[a] LT",LLLL:"dddd, YYYY[ko] MMMM[ren] D[a] LT",l:"YYYY-M-D",ll:"YYYY[ko] MMM D[a]",lll:"YYYY[ko] MMM D[a] LT",llll:"ddd, YYYY[ko] MMM D[a] LT"},calendar:{sameDay:"[gaur] LT[etan]",nextDay:"[bihar] LT[etan]",nextWeek:"dddd LT[etan]",lastDay:"[atzo] LT[etan]",lastWeek:"[aurreko] dddd LT[etan]",sameElse:"L"},relativeTime:{future:"%s barru",past:"duela %s",s:"segundo batzuk",m:"minutu bat",mm:"%d minutu",h:"ordu bat",hh:"%d ordu",d:"egun bat",dd:"%d egun",M:"hilabete bat",MM:"%d hilabete",y:"urte bat",yy:"%d urte"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){var b={1:"۱",2:"۲",3:"۳",4:"۴",5:"۵",6:"۶",7:"۷",8:"۸",9:"۹",0:"۰"},c={"۱":"1","۲":"2","۳":"3","۴":"4","۵":"5","۶":"6","۷":"7","۸":"8","۹":"9","۰":"0"};return a.defineLocale("fa",{months:"ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر".split("_"),monthsShort:"ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر".split("_"),weekdays:"یک‌شنبه_دوشنبه_سه‌شنبه_چهارشنبه_پنج‌شنبه_جمعه_شنبه".split("_"),weekdaysShort:"یک‌شنبه_دوشنبه_سه‌شنبه_چهارشنبه_پنج‌شنبه_جمعه_شنبه".split("_"),weekdaysMin:"ی_د_س_چ_پ_ج_ش".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},meridiem:function(a){return 12>a?"قبل از ظهر":"بعد از ظهر"},calendar:{sameDay:"[امروز ساعت] LT",nextDay:"[فردا ساعت] LT",nextWeek:"dddd [ساعت] LT",lastDay:"[دیروز ساعت] LT",lastWeek:"dddd [پیش] [ساعت] LT",sameElse:"L"},relativeTime:{future:"در %s",past:"%s پیش",s:"چندین ثانیه",m:"یک دقیقه",mm:"%d دقیقه",h:"یک ساعت",hh:"%d ساعت",d:"یک روز",dd:"%d روز",M:"یک ماه",MM:"%d ماه",y:"یک سال",yy:"%d سال"},preparse:function(a){return a.replace(/[۰-۹]/g,function(a){return c[a]}).replace(/،/g,",")},postformat:function(a){return a.replace(/\d/g,function(a){return b[a]}).replace(/,/g,"،")},ordinalParse:/\d{1,2}م/,ordinal:"%dم",week:{dow:6,doy:12}})}),function(a){a(tb)}(function(a){function b(a,b,d,e){var f="";switch(d){case"s":return e?"muutaman sekunnin":"muutama sekunti";case"m":return e?"minuutin":"minuutti";case"mm":f=e?"minuutin":"minuuttia";break;case"h":return e?"tunnin":"tunti";case"hh":f=e?"tunnin":"tuntia";break;case"d":return e?"päivän":"päivä";case"dd":f=e?"päivän":"päivää";break;case"M":return e?"kuukauden":"kuukausi";case"MM":f=e?"kuukauden":"kuukautta";break;case"y":return e?"vuoden":"vuosi";case"yy":f=e?"vuoden":"vuotta"}return f=c(a,e)+" "+f}function c(a,b){return 10>a?b?e[a]:d[a]:a}var d="nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän".split(" "),e=["nolla","yhden","kahden","kolmen","neljän","viiden","kuuden",d[7],d[8],d[9]];return a.defineLocale("fi",{months:"tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu".split("_"),monthsShort:"tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu".split("_"),weekdays:"sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai".split("_"),weekdaysShort:"su_ma_ti_ke_to_pe_la".split("_"),weekdaysMin:"su_ma_ti_ke_to_pe_la".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"Do MMMM[ta] YYYY",LLL:"Do MMMM[ta] YYYY, [klo] LT",LLLL:"dddd, Do MMMM[ta] YYYY, [klo] LT",l:"D.M.YYYY",ll:"Do MMM YYYY",lll:"Do MMM YYYY, [klo] LT",llll:"ddd, Do MMM YYYY, [klo] LT"},calendar:{sameDay:"[tänään] [klo] LT",nextDay:"[huomenna] [klo] LT",nextWeek:"dddd [klo] LT",lastDay:"[eilen] [klo] LT",lastWeek:"[viime] dddd[na] [klo] LT",sameElse:"L"},relativeTime:{future:"%s päästä",past:"%s sitten",s:b,m:b,mm:b,h:b,hh:b,d:b,dd:b,M:b,MM:b,y:b,yy:b},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("fo",{months:"januar_februar_mars_apríl_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"),weekdays:"sunnudagur_mánadagur_týsdagur_mikudagur_hósdagur_fríggjadagur_leygardagur".split("_"),weekdaysShort:"sun_mán_týs_mik_hós_frí_ley".split("_"),weekdaysMin:"su_má_tý_mi_hó_fr_le".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D. MMMM, YYYY LT"},calendar:{sameDay:"[Í dag kl.] LT",nextDay:"[Í morgin kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[Í gjár kl.] LT",lastWeek:"[síðstu] dddd [kl] LT",sameElse:"L"},relativeTime:{future:"um %s",past:"%s síðani",s:"fá sekund",m:"ein minutt",mm:"%d minuttir",h:"ein tími",hh:"%d tímar",d:"ein dagur",dd:"%d dagar",M:"ein mánaði",MM:"%d mánaðir",y:"eitt ár",yy:"%d ár"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("fr-ca",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"Di_Lu_Ma_Me_Je_Ve_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Aujourd'hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinalParse:/\d{1,2}(er|)/,ordinal:function(a){return a+(1===a?"er":"")}})}),function(a){a(tb)}(function(a){return a.defineLocale("fr",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"Di_Lu_Ma_Me_Je_Ve_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Aujourd'hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinalParse:/\d{1,2}(er|)/,ordinal:function(a){return a+(1===a?"er":"")},week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("gl",{months:"Xaneiro_Febreiro_Marzo_Abril_Maio_Xuño_Xullo_Agosto_Setembro_Outubro_Novembro_Decembro".split("_"),monthsShort:"Xan._Feb._Mar._Abr._Mai._Xuñ._Xul._Ago._Set._Out._Nov._Dec.".split("_"),weekdays:"Domingo_Luns_Martes_Mércores_Xoves_Venres_Sábado".split("_"),weekdaysShort:"Dom._Lun._Mar._Mér._Xov._Ven._Sáb.".split("_"),weekdaysMin:"Do_Lu_Ma_Mé_Xo_Ve_Sá".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:function(){return"[hoxe "+(1!==this.hours()?"ás":"á")+"] LT"},nextDay:function(){return"[mañá "+(1!==this.hours()?"ás":"á")+"] LT"},nextWeek:function(){return"dddd ["+(1!==this.hours()?"ás":"a")+"] LT"},lastDay:function(){return"[onte "+(1!==this.hours()?"á":"a")+"] LT"},lastWeek:function(){return"[o] dddd [pasado "+(1!==this.hours()?"ás":"a")+"] LT"},sameElse:"L"},relativeTime:{future:function(a){return"uns segundos"===a?"nuns segundos":"en "+a},past:"hai %s",s:"uns segundos",m:"un minuto",mm:"%d minutos",h:"unha hora",hh:"%d horas",d:"un día",dd:"%d días",M:"un mes",MM:"%d meses",y:"un ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){return a.defineLocale("he",{months:"ינואר_פברואר_מרץ_אפריל_מאי_יוני_יולי_אוגוסט_ספטמבר_אוקטובר_נובמבר_דצמבר".split("_"),monthsShort:"ינו׳_פבר׳_מרץ_אפר׳_מאי_יוני_יולי_אוג׳_ספט׳_אוק׳_נוב׳_דצמ׳".split("_"),weekdays:"ראשון_שני_שלישי_רביעי_חמישי_שישי_שבת".split("_"),weekdaysShort:"א׳_ב׳_ג׳_ד׳_ה׳_ו׳_ש׳".split("_"),weekdaysMin:"א_ב_ג_ד_ה_ו_ש".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [ב]MMMM YYYY",LLL:"D [ב]MMMM YYYY LT",LLLL:"dddd, D [ב]MMMM YYYY LT",l:"D/M/YYYY",ll:"D MMM YYYY",lll:"D MMM YYYY LT",llll:"ddd, D MMM YYYY LT"},calendar:{sameDay:"[היום ב־]LT",nextDay:"[מחר ב־]LT",nextWeek:"dddd [בשעה] LT",lastDay:"[אתמול ב־]LT",lastWeek:"[ביום] dddd [האחרון בשעה] LT",sameElse:"L"},relativeTime:{future:"בעוד %s",past:"לפני %s",s:"מספר שניות",m:"דקה",mm:"%d דקות",h:"שעה",hh:function(a){return 2===a?"שעתיים":a+" שעות"},d:"יום",dd:function(a){return 2===a?"יומיים":a+" ימים"},M:"חודש",MM:function(a){return 2===a?"חודשיים":a+" חודשים"},y:"שנה",yy:function(a){return 2===a?"שנתיים":a+" שנים"}}})}),function(a){a(tb)}(function(a){var b={1:"१",2:"२",3:"३",4:"४",5:"५",6:"६",7:"७",8:"८",9:"९",0:"०"},c={"१":"1","२":"2","३":"3","४":"4","५":"5","६":"6","७":"7","८":"8","९":"9","०":"0"};return a.defineLocale("hi",{months:"जनवरी_फ़रवरी_मार्च_अप्रैल_मई_जून_जुलाई_अगस्त_सितम्बर_अक्टूबर_नवम्बर_दिसम्बर".split("_"),monthsShort:"जन._फ़र._मार्च_अप्रै._मई_जून_जुल._अग._सित._अक्टू._नव._दिस.".split("_"),weekdays:"रविवार_सोमवार_मंगलवार_बुधवार_गुरूवार_शुक्रवार_शनिवार".split("_"),weekdaysShort:"रवि_सोम_मंगल_बुध_गुरू_शुक्र_शनि".split("_"),weekdaysMin:"र_सो_मं_बु_गु_शु_श".split("_"),longDateFormat:{LT:"A h:mm बजे",LTS:"A h:mm:ss बजे",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, LT",LLLL:"dddd, D MMMM YYYY, LT"},calendar:{sameDay:"[आज] LT",nextDay:"[कल] LT",nextWeek:"dddd, LT",lastDay:"[कल] LT",lastWeek:"[पिछले] dddd, LT",sameElse:"L"},relativeTime:{future:"%s में",past:"%s पहले",s:"कुछ ही क्षण",m:"एक मिनट",mm:"%d मिनट",h:"एक घंटा",hh:"%d घंटे",d:"एक दिन",dd:"%d दिन",M:"एक महीने",MM:"%d महीने",y:"एक वर्ष",yy:"%d वर्ष"},preparse:function(a){return a.replace(/[१२३४५६७८९०]/g,function(a){return c[a]})},postformat:function(a){return a.replace(/\d/g,function(a){return b[a]})},meridiem:function(a){return 4>a?"रात":10>a?"सुबह":17>a?"दोपहर":20>a?"शाम":"रात"},week:{dow:0,doy:6}})}),function(a){a(tb)}(function(a){function b(a,b,c){var d=a+" ";switch(c){case"m":return b?"jedna minuta":"jedne minute";case"mm":return d+=1===a?"minuta":2===a||3===a||4===a?"minute":"minuta";case"h":return b?"jedan sat":"jednog sata";case"hh":return d+=1===a?"sat":2===a||3===a||4===a?"sata":"sati";case"dd":return d+=1===a?"dan":"dana";case"MM":return d+=1===a?"mjesec":2===a||3===a||4===a?"mjeseca":"mjeseci";case"yy":return d+=1===a?"godina":2===a||3===a||4===a?"godine":"godina"}}return a.defineLocale("hr",{months:"sječanj_veljača_ožujak_travanj_svibanj_lipanj_srpanj_kolovoz_rujan_listopad_studeni_prosinac".split("_"),monthsShort:"sje._vel._ožu._tra._svi._lip._srp._kol._ruj._lis._stu._pro.".split("_"),weekdays:"nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sri._čet._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_če_pe_su".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedjelju] [u] LT";case 3:return"[u] [srijedu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[jučer u] LT",lastWeek:function(){switch(this.day()){case 0:case 3:return"[prošlu] dddd [u] LT";case 6:return"[prošle] [subote] [u] LT";case 1:case 2:case 4:case 5:return"[prošli] dddd [u] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"prije %s",s:"par sekundi",m:b,mm:b,h:b,hh:b,d:"dan",dd:b,M:"mjesec",MM:b,y:"godinu",yy:b},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){function b(a,b,c,d){var e=a;switch(c){case"s":return d||b?"néhány másodperc":"néhány másodperce";case"m":return"egy"+(d||b?" perc":" perce");case"mm":return e+(d||b?" perc":" perce");case"h":return"egy"+(d||b?" óra":" órája");case"hh":return e+(d||b?" óra":" órája");case"d":return"egy"+(d||b?" nap":" napja");case"dd":return e+(d||b?" nap":" napja");case"M":return"egy"+(d||b?" hónap":" hónapja");case"MM":return e+(d||b?" hónap":" hónapja");case"y":return"egy"+(d||b?" év":" éve");case"yy":return e+(d||b?" év":" éve")}return""}function c(a){return(a?"":"[múlt] ")+"["+d[this.day()]+"] LT[-kor]"}var d="vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton".split(" ");return a.defineLocale("hu",{months:"január_február_március_április_május_június_július_augusztus_szeptember_október_november_december".split("_"),monthsShort:"jan_feb_márc_ápr_máj_jún_júl_aug_szept_okt_nov_dec".split("_"),weekdays:"vasárnap_hétfő_kedd_szerda_csütörtök_péntek_szombat".split("_"),weekdaysShort:"vas_hét_kedd_sze_csüt_pén_szo".split("_"),weekdaysMin:"v_h_k_sze_cs_p_szo".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"YYYY.MM.DD.",LL:"YYYY. MMMM D.",LLL:"YYYY. MMMM D., LT",LLLL:"YYYY. MMMM D., dddd LT"},meridiem:function(a,b,c){return 12>a?c===!0?"de":"DE":c===!0?"du":"DU"},calendar:{sameDay:"[ma] LT[-kor]",nextDay:"[holnap] LT[-kor]",nextWeek:function(){return c.call(this,!0)},lastDay:"[tegnap] LT[-kor]",lastWeek:function(){return c.call(this,!1)},sameElse:"L"},relativeTime:{future:"%s múlva",past:"%s",s:b,m:b,mm:b,h:b,hh:b,d:b,dd:b,M:b,MM:b,y:b,yy:b},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){function b(a,b){var c={nominative:"հունվար_փետրվար_մարտ_ապրիլ_մայիս_հունիս_հուլիս_օգոստոս_սեպտեմբեր_հոկտեմբեր_նոյեմբեր_դեկտեմբեր".split("_"),accusative:"հունվարի_փետրվարի_մարտի_ապրիլի_մայիսի_հունիսի_հուլիսի_օգոստոսի_սեպտեմբերի_հոկտեմբերի_նոյեմբերի_դեկտեմբերի".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function c(a){var b="հնվ_փտր_մրտ_ապր_մյս_հնս_հլս_օգս_սպտ_հկտ_նմբ_դկտ".split("_");return b[a.month()]}function d(a){var b="կիրակի_երկուշաբթի_երեքշաբթի_չորեքշաբթի_հինգշաբթի_ուրբաթ_շաբաթ".split("_");return b[a.day()]}return a.defineLocale("hy-am",{months:b,monthsShort:c,weekdays:d,weekdaysShort:"կրկ_երկ_երք_չրք_հնգ_ուրբ_շբթ".split("_"),weekdaysMin:"կրկ_երկ_երք_չրք_հնգ_ուրբ_շբթ".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY թ.",LLL:"D MMMM YYYY թ., LT",LLLL:"dddd, D MMMM YYYY թ., LT"},calendar:{sameDay:"[այսօր] LT",nextDay:"[վաղը] LT",lastDay:"[երեկ] LT",nextWeek:function(){return"dddd [օրը ժամը] LT"},lastWeek:function(){return"[անցած] dddd [օրը ժամը] LT"},sameElse:"L"},relativeTime:{future:"%s հետո",past:"%s առաջ",s:"մի քանի վայրկյան",m:"րոպե",mm:"%d րոպե",h:"ժամ",hh:"%d ժամ",d:"օր",dd:"%d օր",M:"ամիս",MM:"%d ամիս",y:"տարի",yy:"%d տարի"},meridiem:function(a){return 4>a?"գիշերվա":12>a?"առավոտվա":17>a?"ցերեկվա":"երեկոյան"},ordinalParse:/\d{1,2}|\d{1,2}-(ին|րդ)/,ordinal:function(a,b){switch(b){case"DDD":case"w":case"W":case"DDDo":return 1===a?a+"-ին":a+"-րդ";default:return a}},week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){return a.defineLocale("id",{months:"Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nov_Des".split("_"),weekdays:"Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu".split("_"),weekdaysShort:"Min_Sen_Sel_Rab_Kam_Jum_Sab".split("_"),weekdaysMin:"Mg_Sn_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"LT.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] LT",LLLL:"dddd, D MMMM YYYY [pukul] LT"},meridiem:function(a){return 11>a?"pagi":15>a?"siang":19>a?"sore":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Besok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kemarin pukul] LT",lastWeek:"dddd [lalu pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lalu",s:"beberapa detik",m:"semenit",mm:"%d menit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){function b(a){return a%100===11?!0:a%10===1?!1:!0}function c(a,c,d,e){var f=a+" ";switch(d){case"s":return c||e?"nokkrar sekúndur":"nokkrum sekúndum";case"m":return c?"mínúta":"mínútu";case"mm":return b(a)?f+(c||e?"mínútur":"mínútum"):c?f+"mínúta":f+"mínútu";case"hh":return b(a)?f+(c||e?"klukkustundir":"klukkustundum"):f+"klukkustund";case"d":return c?"dagur":e?"dag":"degi";case"dd":return b(a)?c?f+"dagar":f+(e?"daga":"dögum"):c?f+"dagur":f+(e?"dag":"degi");case"M":return c?"mánuður":e?"mánuð":"mánuði";case"MM":return b(a)?c?f+"mánuðir":f+(e?"mánuði":"mánuðum"):c?f+"mánuður":f+(e?"mánuð":"mánuði");case"y":return c||e?"ár":"ári";case"yy":return b(a)?f+(c||e?"ár":"árum"):f+(c||e?"ár":"ári")}}return a.defineLocale("is",{months:"janúar_febrúar_mars_apríl_maí_júní_júlí_ágúst_september_október_nóvember_desember".split("_"),monthsShort:"jan_feb_mar_apr_maí_jún_júl_ágú_sep_okt_nóv_des".split("_"),weekdays:"sunnudagur_mánudagur_þriðjudagur_miðvikudagur_fimmtudagur_föstudagur_laugardagur".split("_"),weekdaysShort:"sun_mán_þri_mið_fim_fös_lau".split("_"),weekdaysMin:"Su_Má_Þr_Mi_Fi_Fö_La".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] LT",LLLL:"dddd, D. MMMM YYYY [kl.] LT"},calendar:{sameDay:"[í dag kl.] LT",nextDay:"[á morgun kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[í gær kl.] LT",lastWeek:"[síðasta] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"eftir %s",past:"fyrir %s síðan",s:c,m:c,mm:c,h:"klukkustund",hh:c,d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("it",{months:"gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"),monthsShort:"gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"),weekdays:"Domenica_Lunedì_Martedì_Mercoledì_Giovedì_Venerdì_Sabato".split("_"),weekdaysShort:"Dom_Lun_Mar_Mer_Gio_Ven_Sab".split("_"),weekdaysMin:"D_L_Ma_Me_G_V_S".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Oggi alle] LT",nextDay:"[Domani alle] LT",nextWeek:"dddd [alle] LT",lastDay:"[Ieri alle] LT",lastWeek:function(){switch(this.day()){case 0:return"[la scorsa] dddd [alle] LT";default:return"[lo scorso] dddd [alle] LT"}},sameElse:"L"},relativeTime:{future:function(a){return(/^[0-9].+$/.test(a)?"tra":"in")+" "+a},past:"%s fa",s:"alcuni secondi",m:"un minuto",mm:"%d minuti",h:"un'ora",hh:"%d ore",d:"un giorno",dd:"%d giorni",M:"un mese",MM:"%d mesi",y:"un anno",yy:"%d anni"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("ja",{months:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日".split("_"),weekdaysShort:"日_月_火_水_木_金_土".split("_"),weekdaysMin:"日_月_火_水_木_金_土".split("_"),longDateFormat:{LT:"Ah時m分",LTS:"LTs秒",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日LT",LLLL:"YYYY年M月D日LT dddd"},meridiem:function(a){return 12>a?"午前":"午後"},calendar:{sameDay:"[今日] LT",nextDay:"[明日] LT",nextWeek:"[来週]dddd LT",lastDay:"[昨日] LT",lastWeek:"[前週]dddd LT",sameElse:"L"},relativeTime:{future:"%s後",past:"%s前",s:"数秒",m:"1分",mm:"%d分",h:"1時間",hh:"%d時間",d:"1日",dd:"%d日",M:"1ヶ月",MM:"%dヶ月",y:"1年",yy:"%d年"}})}),function(a){a(tb)}(function(a){function b(a,b){var c={nominative:"იანვარი_თებერვალი_მარტი_აპრილი_მაისი_ივნისი_ივლისი_აგვისტო_სექტემბერი_ოქტომბერი_ნოემბერი_დეკემბერი".split("_"),accusative:"იანვარს_თებერვალს_მარტს_აპრილის_მაისს_ივნისს_ივლისს_აგვისტს_სექტემბერს_ოქტომბერს_ნოემბერს_დეკემბერს".split("_")},d=/D[oD] *MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function c(a,b){var c={nominative:"კვირა_ორშაბათი_სამშაბათი_ოთხშაბათი_ხუთშაბათი_პარასკევი_შაბათი".split("_"),accusative:"კვირას_ორშაბათს_სამშაბათს_ოთხშაბათს_ხუთშაბათს_პარასკევს_შაბათს".split("_")},d=/(წინა|შემდეგ)/.test(b)?"accusative":"nominative";return c[d][a.day()]}return a.defineLocale("ka",{months:b,monthsShort:"იან_თებ_მარ_აპრ_მაი_ივნ_ივლ_აგვ_სექ_ოქტ_ნოე_დეკ".split("_"),weekdays:c,weekdaysShort:"კვი_ორშ_სამ_ოთხ_ხუთ_პარ_შაბ".split("_"),weekdaysMin:"კვ_ორ_სა_ოთ_ხუ_პა_შა".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[დღეს] LT[-ზე]",nextDay:"[ხვალ] LT[-ზე]",lastDay:"[გუშინ] LT[-ზე]",nextWeek:"[შემდეგ] dddd LT[-ზე]",lastWeek:"[წინა] dddd LT-ზე",sameElse:"L"},relativeTime:{future:function(a){return/(წამი|წუთი|საათი|წელი)/.test(a)?a.replace(/ი$/,"ში"):a+"ში"},past:function(a){return/(წამი|წუთი|საათი|დღე|თვე)/.test(a)?a.replace(/(ი|ე)$/,"ის წინ"):/წელი/.test(a)?a.replace(/წელი$/,"წლის წინ"):void 0},s:"რამდენიმე წამი",m:"წუთი",mm:"%d წუთი",h:"საათი",hh:"%d საათი",d:"დღე",dd:"%d დღე",M:"თვე",MM:"%d თვე",y:"წელი",yy:"%d წელი"},ordinalParse:/0|1-ლი|მე-\d{1,2}|\d{1,2}-ე/,ordinal:function(a){return 0===a?a:1===a?a+"-ლი":20>a||100>=a&&a%20===0||a%100===0?"მე-"+a:a+"-ე"},week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){return a.defineLocale("km",{months:"មករា_កុម្ភៈ_មិនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ".split("_"),monthsShort:"មករា_កុម្ភៈ_មិនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ".split("_"),weekdays:"អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍".split("_"),weekdaysShort:"អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍".split("_"),weekdaysMin:"អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[ថ្ងៃនៈ ម៉ោង] LT",nextDay:"[ស្អែក ម៉ោង] LT",nextWeek:"dddd [ម៉ោង] LT",lastDay:"[ម្សិលមិញ ម៉ោង] LT",lastWeek:"dddd [សប្តាហ៍មុន] [ម៉ោង] LT",sameElse:"L"},relativeTime:{future:"%sទៀត",past:"%sមុន",s:"ប៉ុន្មានវិនាទី",m:"មួយនាទី",mm:"%d នាទី",h:"មួយម៉ោង",hh:"%d ម៉ោង",d:"មួយថ្ងៃ",dd:"%d ថ្ងៃ",M:"មួយខែ",MM:"%d ខែ",y:"មួយឆ្នាំ",yy:"%d ឆ្នាំ"},week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("ko",{months:"1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),monthsShort:"1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),weekdays:"일요일_월요일_화요일_수요일_목요일_금요일_토요일".split("_"),weekdaysShort:"일_월_화_수_목_금_토".split("_"),weekdaysMin:"일_월_화_수_목_금_토".split("_"),longDateFormat:{LT:"A h시 m분",LTS:"A h시 m분 s초",L:"YYYY.MM.DD",LL:"YYYY년 MMMM D일",LLL:"YYYY년 MMMM D일 LT",LLLL:"YYYY년 MMMM D일 dddd LT"},meridiem:function(a){return 12>a?"오전":"오후"},calendar:{sameDay:"오늘 LT",nextDay:"내일 LT",nextWeek:"dddd LT",lastDay:"어제 LT",lastWeek:"지난주 dddd LT",sameElse:"L"},relativeTime:{future:"%s 후",past:"%s 전",s:"몇초",ss:"%d초",m:"일분",mm:"%d분",h:"한시간",hh:"%d시간",d:"하루",dd:"%d일",M:"한달",MM:"%d달",y:"일년",yy:"%d년"},ordinalParse:/\d{1,2}일/,ordinal:"%d일",meridiemParse:/(오전|오후)/,isPM:function(a){return"오후"===a}})}),function(a){a(tb)}(function(a){function b(a,b,c){var d={m:["eng Minutt","enger Minutt"],h:["eng Stonn","enger Stonn"],d:["een Dag","engem Dag"],M:["ee Mount","engem Mount"],y:["ee Joer","engem Joer"]};return b?d[c][0]:d[c][1]}function c(a){var b=a.substr(0,a.indexOf(" "));return e(b)?"a "+a:"an "+a}function d(a){var b=a.substr(0,a.indexOf(" "));return e(b)?"viru "+a:"virun "+a}function e(a){if(a=parseInt(a,10),isNaN(a))return!1;if(0>a)return!0;if(10>a)return a>=4&&7>=a?!0:!1;if(100>a){var b=a%10,c=a/10;return e(0===b?c:b)}if(1e4>a){for(;a>=10;)a/=10;return e(a)}return a/=1e3,e(a)}return a.defineLocale("lb",{months:"Januar_Februar_Mäerz_Abrëll_Mee_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Febr._Mrz._Abr._Mee_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),weekdays:"Sonndeg_Méindeg_Dënschdeg_Mëttwoch_Donneschdeg_Freideg_Samschdeg".split("_"),weekdaysShort:"So._Mé._Dë._Më._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mé_Dë_Më_Do_Fr_Sa".split("_"),longDateFormat:{LT:"H:mm [Auer]",LTS:"H:mm:ss [Auer]",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[Haut um] LT",sameElse:"L",nextDay:"[Muer um] LT",nextWeek:"dddd [um] LT",lastDay:"[Gëschter um] LT",lastWeek:function(){switch(this.day()){case 2:case 4:return"[Leschten] dddd [um] LT";default:return"[Leschte] dddd [um] LT"}}},relativeTime:{future:c,past:d,s:"e puer Sekonnen",m:b,mm:"%d Minutten",h:b,hh:"%d Stonnen",d:b,dd:"%d Deeg",M:b,MM:"%d Méint",y:b,yy:"%d Joer"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){function b(a,b,c,d){return b?"kelios sekundės":d?"kelių sekundžių":"kelias sekundes"}function c(a,b,c,d){return b?e(c)[0]:d?e(c)[1]:e(c)[2]}function d(a){return a%10===0||a>10&&20>a}function e(a){return h[a].split("_")}function f(a,b,f,g){var h=a+" ";return 1===a?h+c(a,b,f[0],g):b?h+(d(a)?e(f)[1]:e(f)[0]):g?h+e(f)[1]:h+(d(a)?e(f)[1]:e(f)[2])}function g(a,b){var c=-1===b.indexOf("dddd HH:mm"),d=i[a.day()];return c?d:d.substring(0,d.length-2)+"į"}var h={m:"minutė_minutės_minutę",mm:"minutės_minučių_minutes",h:"valanda_valandos_valandą",hh:"valandos_valandų_valandas",d:"diena_dienos_dieną",dd:"dienos_dienų_dienas",M:"mėnuo_mėnesio_mėnesį",MM:"mėnesiai_mėnesių_mėnesius",y:"metai_metų_metus",yy:"metai_metų_metus"},i="sekmadienis_pirmadienis_antradienis_trečiadienis_ketvirtadienis_penktadienis_šeštadienis".split("_");return a.defineLocale("lt",{months:"sausio_vasario_kovo_balandžio_gegužės_birželio_liepos_rugpjūčio_rugsėjo_spalio_lapkričio_gruodžio".split("_"),monthsShort:"sau_vas_kov_bal_geg_bir_lie_rgp_rgs_spa_lap_grd".split("_"),weekdays:g,weekdaysShort:"Sek_Pir_Ant_Tre_Ket_Pen_Šeš".split("_"),weekdaysMin:"S_P_A_T_K_Pn_Š".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"YYYY-MM-DD",LL:"YYYY [m.] MMMM D [d.]",LLL:"YYYY [m.] MMMM D [d.], LT [val.]",LLLL:"YYYY [m.] MMMM D [d.], dddd, LT [val.]",l:"YYYY-MM-DD",ll:"YYYY [m.] MMMM D [d.]",lll:"YYYY [m.] MMMM D [d.], LT [val.]",llll:"YYYY [m.] MMMM D [d.], ddd, LT [val.]"},calendar:{sameDay:"[Šiandien] LT",nextDay:"[Rytoj] LT",nextWeek:"dddd LT",lastDay:"[Vakar] LT",lastWeek:"[Praėjusį] dddd LT",sameElse:"L"},relativeTime:{future:"po %s",past:"prieš %s",s:b,m:c,mm:f,h:c,hh:f,d:c,dd:f,M:c,MM:f,y:c,yy:f},ordinalParse:/\d{1,2}-oji/,ordinal:function(a){return a+"-oji"},week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){function b(a,b,c){var d=a.split("_");return c?b%10===1&&11!==b?d[2]:d[3]:b%10===1&&11!==b?d[0]:d[1]}function c(a,c,e){return a+" "+b(d[e],a,c)}var d={mm:"minūti_minūtes_minūte_minūtes",hh:"stundu_stundas_stunda_stundas",dd:"dienu_dienas_diena_dienas",MM:"mēnesi_mēnešus_mēnesis_mēneši",yy:"gadu_gadus_gads_gadi"};return a.defineLocale("lv",{months:"janvāris_februāris_marts_aprīlis_maijs_jūnijs_jūlijs_augusts_septembris_oktobris_novembris_decembris".split("_"),monthsShort:"jan_feb_mar_apr_mai_jūn_jūl_aug_sep_okt_nov_dec".split("_"),weekdays:"svētdiena_pirmdiena_otrdiena_trešdiena_ceturtdiena_piektdiena_sestdiena".split("_"),weekdaysShort:"Sv_P_O_T_C_Pk_S".split("_"),weekdaysMin:"Sv_P_O_T_C_Pk_S".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"YYYY. [gada] D. MMMM",LLL:"YYYY. [gada] D. MMMM, LT",LLLL:"YYYY. [gada] D. MMMM, dddd, LT"},calendar:{sameDay:"[Šodien pulksten] LT",nextDay:"[Rīt pulksten] LT",nextWeek:"dddd [pulksten] LT",lastDay:"[Vakar pulksten] LT",lastWeek:"[Pagājušā] dddd [pulksten] LT",sameElse:"L"},relativeTime:{future:"%s vēlāk",past:"%s agrāk",s:"dažas sekundes",m:"minūti",mm:c,h:"stundu",hh:c,d:"dienu",dd:c,M:"mēnesi",MM:c,y:"gadu",yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("mk",{months:"јануари_февруари_март_април_мај_јуни_јули_август_септември_октомври_ноември_декември".split("_"),monthsShort:"јан_фев_мар_апр_мај_јун_јул_авг_сеп_окт_ное_дек".split("_"),weekdays:"недела_понеделник_вторник_среда_четврток_петок_сабота".split("_"),weekdaysShort:"нед_пон_вто_сре_чет_пет_саб".split("_"),weekdaysMin:"нe_пo_вт_ср_че_пе_сa".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"D.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Денес во] LT",nextDay:"[Утре во] LT",nextWeek:"dddd [во] LT",lastDay:"[Вчера во] LT",lastWeek:function(){switch(this.day()){case 0:case 3:case 6:return"[Во изминатата] dddd [во] LT";case 1:case 2:case 4:case 5:return"[Во изминатиот] dddd [во] LT"}},sameElse:"L"},relativeTime:{future:"после %s",past:"пред %s",s:"неколку секунди",m:"минута",mm:"%d минути",h:"час",hh:"%d часа",d:"ден",dd:"%d дена",M:"месец",MM:"%d месеци",y:"година",yy:"%d години"},ordinalParse:/\d{1,2}-(ев|ен|ти|ви|ри|ми)/,ordinal:function(a){var b=a%10,c=a%100;return 0===a?a+"-ев":0===c?a+"-ен":c>10&&20>c?a+"-ти":1===b?a+"-ви":2===b?a+"-ри":7===b||8===b?a+"-ми":a+"-ти" +},week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){return a.defineLocale("ml",{months:"ജനുവരി_ഫെബ്രുവരി_മാർച്ച്_ഏപ്രിൽ_മേയ്_ജൂൺ_ജൂലൈ_ഓഗസ്റ്റ്_സെപ്റ്റംബർ_ഒക്ടോബർ_നവംബർ_ഡിസംബർ".split("_"),monthsShort:"ജനു._ഫെബ്രു._മാർ._ഏപ്രി._മേയ്_ജൂൺ_ജൂലൈ._ഓഗ._സെപ്റ്റ._ഒക്ടോ._നവം._ഡിസം.".split("_"),weekdays:"ഞായറാഴ്ച_തിങ്കളാഴ്ച_ചൊവ്വാഴ്ച_ബുധനാഴ്ച_വ്യാഴാഴ്ച_വെള്ളിയാഴ്ച_ശനിയാഴ്ച".split("_"),weekdaysShort:"ഞായർ_തിങ്കൾ_ചൊവ്വ_ബുധൻ_വ്യാഴം_വെള്ളി_ശനി".split("_"),weekdaysMin:"ഞാ_തി_ചൊ_ബു_വ്യാ_വെ_ശ".split("_"),longDateFormat:{LT:"A h:mm -നു",LTS:"A h:mm:ss -നു",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, LT",LLLL:"dddd, D MMMM YYYY, LT"},calendar:{sameDay:"[ഇന്ന്] LT",nextDay:"[നാളെ] LT",nextWeek:"dddd, LT",lastDay:"[ഇന്നലെ] LT",lastWeek:"[കഴിഞ്ഞ] dddd, LT",sameElse:"L"},relativeTime:{future:"%s കഴിഞ്ഞ്",past:"%s മുൻപ്",s:"അൽപ നിമിഷങ്ങൾ",m:"ഒരു മിനിറ്റ്",mm:"%d മിനിറ്റ്",h:"ഒരു മണിക്കൂർ",hh:"%d മണിക്കൂർ",d:"ഒരു ദിവസം",dd:"%d ദിവസം",M:"ഒരു മാസം",MM:"%d മാസം",y:"ഒരു വർഷം",yy:"%d വർഷം"},meridiem:function(a){return 4>a?"രാത്രി":12>a?"രാവിലെ":17>a?"ഉച്ച കഴിഞ്ഞ്":20>a?"വൈകുന്നേരം":"രാത്രി"}})}),function(a){a(tb)}(function(a){var b={1:"१",2:"२",3:"३",4:"४",5:"५",6:"६",7:"७",8:"८",9:"९",0:"०"},c={"१":"1","२":"2","३":"3","४":"4","५":"5","६":"6","७":"7","८":"8","९":"9","०":"0"};return a.defineLocale("mr",{months:"जानेवारी_फेब्रुवारी_मार्च_एप्रिल_मे_जून_जुलै_ऑगस्ट_सप्टेंबर_ऑक्टोबर_नोव्हेंबर_डिसेंबर".split("_"),monthsShort:"जाने._फेब्रु._मार्च._एप्रि._मे._जून._जुलै._ऑग._सप्टें._ऑक्टो._नोव्हें._डिसें.".split("_"),weekdays:"रविवार_सोमवार_मंगळवार_बुधवार_गुरूवार_शुक्रवार_शनिवार".split("_"),weekdaysShort:"रवि_सोम_मंगळ_बुध_गुरू_शुक्र_शनि".split("_"),weekdaysMin:"र_सो_मं_बु_गु_शु_श".split("_"),longDateFormat:{LT:"A h:mm वाजता",LTS:"A h:mm:ss वाजता",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, LT",LLLL:"dddd, D MMMM YYYY, LT"},calendar:{sameDay:"[आज] LT",nextDay:"[उद्या] LT",nextWeek:"dddd, LT",lastDay:"[काल] LT",lastWeek:"[मागील] dddd, LT",sameElse:"L"},relativeTime:{future:"%s नंतर",past:"%s पूर्वी",s:"सेकंद",m:"एक मिनिट",mm:"%d मिनिटे",h:"एक तास",hh:"%d तास",d:"एक दिवस",dd:"%d दिवस",M:"एक महिना",MM:"%d महिने",y:"एक वर्ष",yy:"%d वर्षे"},preparse:function(a){return a.replace(/[१२३४५६७८९०]/g,function(a){return c[a]})},postformat:function(a){return a.replace(/\d/g,function(a){return b[a]})},meridiem:function(a){return 4>a?"रात्री":10>a?"सकाळी":17>a?"दुपारी":20>a?"सायंकाळी":"रात्री"},week:{dow:0,doy:6}})}),function(a){a(tb)}(function(a){return a.defineLocale("ms-my",{months:"Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember".split("_"),monthsShort:"Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ogs_Sep_Okt_Nov_Dis".split("_"),weekdays:"Ahad_Isnin_Selasa_Rabu_Khamis_Jumaat_Sabtu".split("_"),weekdaysShort:"Ahd_Isn_Sel_Rab_Kha_Jum_Sab".split("_"),weekdaysMin:"Ah_Is_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"LT.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] LT",LLLL:"dddd, D MMMM YYYY [pukul] LT"},meridiem:function(a){return 11>a?"pagi":15>a?"tengahari":19>a?"petang":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Esok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kelmarin pukul] LT",lastWeek:"dddd [lepas pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lepas",s:"beberapa saat",m:"seminit",mm:"%d minit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){var b={1:"၁",2:"၂",3:"၃",4:"၄",5:"၅",6:"၆",7:"၇",8:"၈",9:"၉",0:"၀"},c={"၁":"1","၂":"2","၃":"3","၄":"4","၅":"5","၆":"6","၇":"7","၈":"8","၉":"9","၀":"0"};return a.defineLocale("my",{months:"ဇန်နဝါရီ_ဖေဖော်ဝါရီ_မတ်_ဧပြီ_မေ_ဇွန်_ဇူလိုင်_သြဂုတ်_စက်တင်ဘာ_အောက်တိုဘာ_နိုဝင်ဘာ_ဒီဇင်ဘာ".split("_"),monthsShort:"ဇန်_ဖေ_မတ်_ပြီ_မေ_ဇွန်_လိုင်_သြ_စက်_အောက်_နို_ဒီ".split("_"),weekdays:"တနင်္ဂနွေ_တနင်္လာ_အင်္ဂါ_ဗုဒ္ဓဟူး_ကြာသပတေး_သောကြာ_စနေ".split("_"),weekdaysShort:"နွေ_လာ_င်္ဂါ_ဟူး_ကြာ_သော_နေ".split("_"),weekdaysMin:"နွေ_လာ_င်္ဂါ_ဟူး_ကြာ_သော_နေ".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[ယနေ.] LT [မှာ]",nextDay:"[မနက်ဖြန်] LT [မှာ]",nextWeek:"dddd LT [မှာ]",lastDay:"[မနေ.က] LT [မှာ]",lastWeek:"[ပြီးခဲ့သော] dddd LT [မှာ]",sameElse:"L"},relativeTime:{future:"လာမည့် %s မှာ",past:"လွန်ခဲ့သော %s က",s:"စက္ကန်.အနည်းငယ်",m:"တစ်မိနစ်",mm:"%d မိနစ်",h:"တစ်နာရီ",hh:"%d နာရီ",d:"တစ်ရက်",dd:"%d ရက်",M:"တစ်လ",MM:"%d လ",y:"တစ်နှစ်",yy:"%d နှစ်"},preparse:function(a){return a.replace(/[၁၂၃၄၅၆၇၈၉၀]/g,function(a){return c[a]})},postformat:function(a){return a.replace(/\d/g,function(a){return b[a]})},week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("nb",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tirs_ons_tors_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"H.mm",LTS:"LT.ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] LT",LLLL:"dddd D. MMMM YYYY [kl.] LT"},calendar:{sameDay:"[i dag kl.] LT",nextDay:"[i morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[i går kl.] LT",lastWeek:"[forrige] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"for %s siden",s:"noen sekunder",m:"ett minutt",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dager",M:"en måned",MM:"%d måneder",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){var b={1:"१",2:"२",3:"३",4:"४",5:"५",6:"६",7:"७",8:"८",9:"९",0:"०"},c={"१":"1","२":"2","३":"3","४":"4","५":"5","६":"6","७":"7","८":"8","९":"9","०":"0"};return a.defineLocale("ne",{months:"जनवरी_फेब्रुवरी_मार्च_अप्रिल_मई_जुन_जुलाई_अगष्ट_सेप्टेम्बर_अक्टोबर_नोभेम्बर_डिसेम्बर".split("_"),monthsShort:"जन._फेब्रु._मार्च_अप्रि._मई_जुन_जुलाई._अग._सेप्ट._अक्टो._नोभे._डिसे.".split("_"),weekdays:"आइतबार_सोमबार_मङ्गलबार_बुधबार_बिहिबार_शुक्रबार_शनिबार".split("_"),weekdaysShort:"आइत._सोम._मङ्गल._बुध._बिहि._शुक्र._शनि.".split("_"),weekdaysMin:"आइ._सो._मङ्_बु._बि._शु._श.".split("_"),longDateFormat:{LT:"Aको h:mm बजे",LTS:"Aको h:mm:ss बजे",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, LT",LLLL:"dddd, D MMMM YYYY, LT"},preparse:function(a){return a.replace(/[१२३४५६७८९०]/g,function(a){return c[a]})},postformat:function(a){return a.replace(/\d/g,function(a){return b[a]})},meridiem:function(a){return 3>a?"राती":10>a?"बिहान":15>a?"दिउँसो":18>a?"बेलुका":20>a?"साँझ":"राती"},calendar:{sameDay:"[आज] LT",nextDay:"[भोली] LT",nextWeek:"[आउँदो] dddd[,] LT",lastDay:"[हिजो] LT",lastWeek:"[गएको] dddd[,] LT",sameElse:"L"},relativeTime:{future:"%sमा",past:"%s अगाडी",s:"केही समय",m:"एक मिनेट",mm:"%d मिनेट",h:"एक घण्टा",hh:"%d घण्टा",d:"एक दिन",dd:"%d दिन",M:"एक महिना",MM:"%d महिना",y:"एक बर्ष",yy:"%d बर्ष"},week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){var b="jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_"),c="jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_");return a.defineLocale("nl",{months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:function(a,d){return/-MMM-/.test(d)?c[a.month()]:b[a.month()]},weekdays:"zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"),weekdaysShort:"zo._ma._di._wo._do._vr._za.".split("_"),weekdaysMin:"Zo_Ma_Di_Wo_Do_Vr_Za".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[vandaag om] LT",nextDay:"[morgen om] LT",nextWeek:"dddd [om] LT",lastDay:"[gisteren om] LT",lastWeek:"[afgelopen] dddd [om] LT",sameElse:"L"},relativeTime:{future:"over %s",past:"%s geleden",s:"een paar seconden",m:"één minuut",mm:"%d minuten",h:"één uur",hh:"%d uur",d:"één dag",dd:"%d dagen",M:"één maand",MM:"%d maanden",y:"één jaar",yy:"%d jaar"},ordinalParse:/\d{1,2}(ste|de)/,ordinal:function(a){return a+(1===a||8===a||a>=20?"ste":"de")},week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("nn",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"),weekdays:"sundag_måndag_tysdag_onsdag_torsdag_fredag_laurdag".split("_"),weekdaysShort:"sun_mån_tys_ons_tor_fre_lau".split("_"),weekdaysMin:"su_må_ty_on_to_fr_lø".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[I dag klokka] LT",nextDay:"[I morgon klokka] LT",nextWeek:"dddd [klokka] LT",lastDay:"[I går klokka] LT",lastWeek:"[Føregåande] dddd [klokka] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"for %s sidan",s:"nokre sekund",m:"eit minutt",mm:"%d minutt",h:"ein time",hh:"%d timar",d:"ein dag",dd:"%d dagar",M:"ein månad",MM:"%d månader",y:"eit år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){function b(a){return 5>a%10&&a%10>1&&~~(a/10)%10!==1}function c(a,c,d){var e=a+" ";switch(d){case"m":return c?"minuta":"minutę";case"mm":return e+(b(a)?"minuty":"minut");case"h":return c?"godzina":"godzinę";case"hh":return e+(b(a)?"godziny":"godzin");case"MM":return e+(b(a)?"miesiące":"miesięcy");case"yy":return e+(b(a)?"lata":"lat")}}var d="styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień".split("_"),e="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia".split("_");return a.defineLocale("pl",{months:function(a,b){return/D MMMM/.test(b)?e[a.month()]:d[a.month()]},monthsShort:"sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru".split("_"),weekdays:"niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota".split("_"),weekdaysShort:"nie_pon_wt_śr_czw_pt_sb".split("_"),weekdaysMin:"N_Pn_Wt_Śr_Cz_Pt_So".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Dziś o] LT",nextDay:"[Jutro o] LT",nextWeek:"[W] dddd [o] LT",lastDay:"[Wczoraj o] LT",lastWeek:function(){switch(this.day()){case 0:return"[W zeszłą niedzielę o] LT";case 3:return"[W zeszłą środę o] LT";case 6:return"[W zeszłą sobotę o] LT";default:return"[W zeszły] dddd [o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"%s temu",s:"kilka sekund",m:c,mm:c,h:c,hh:c,d:"1 dzień",dd:"%d dni",M:"miesiąc",MM:c,y:"rok",yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("pt-br",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [às] LT",LLLL:"dddd, D [de] MMMM [de] YYYY [às] LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"%s atrás",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº"})}),function(a){a(tb)}(function(a){return a.defineLocale("pt",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"há %s",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){function b(a,b,c){var d={mm:"minute",hh:"ore",dd:"zile",MM:"luni",yy:"ani"},e=" ";return(a%100>=20||a>=100&&a%100===0)&&(e=" de "),a+e+d[c]}return a.defineLocale("ro",{months:"ianuarie_februarie_martie_aprilie_mai_iunie_iulie_august_septembrie_octombrie_noiembrie_decembrie".split("_"),monthsShort:"ian._febr._mart._apr._mai_iun._iul._aug._sept._oct._nov._dec.".split("_"),weekdays:"duminică_luni_marți_miercuri_joi_vineri_sâmbătă".split("_"),weekdaysShort:"Dum_Lun_Mar_Mie_Joi_Vin_Sâm".split("_"),weekdaysMin:"Du_Lu_Ma_Mi_Jo_Vi_Sâ".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY H:mm",LLLL:"dddd, D MMMM YYYY H:mm"},calendar:{sameDay:"[azi la] LT",nextDay:"[mâine la] LT",nextWeek:"dddd [la] LT",lastDay:"[ieri la] LT",lastWeek:"[fosta] dddd [la] LT",sameElse:"L"},relativeTime:{future:"peste %s",past:"%s în urmă",s:"câteva secunde",m:"un minut",mm:b,h:"o oră",hh:b,d:"o zi",dd:b,M:"o lună",MM:b,y:"un an",yy:b},week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){function b(a,b){var c=a.split("_");return b%10===1&&b%100!==11?c[0]:b%10>=2&&4>=b%10&&(10>b%100||b%100>=20)?c[1]:c[2]}function c(a,c,d){var e={mm:c?"минута_минуты_минут":"минуту_минуты_минут",hh:"час_часа_часов",dd:"день_дня_дней",MM:"месяц_месяца_месяцев",yy:"год_года_лет"};return"m"===d?c?"минута":"минуту":a+" "+b(e[d],+a)}function d(a,b){var c={nominative:"январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь".split("_"),accusative:"января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function e(a,b){var c={nominative:"янв_фев_март_апр_май_июнь_июль_авг_сен_окт_ноя_дек".split("_"),accusative:"янв_фев_мар_апр_мая_июня_июля_авг_сен_окт_ноя_дек".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function f(a,b){var c={nominative:"воскресенье_понедельник_вторник_среда_четверг_пятница_суббота".split("_"),accusative:"воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу".split("_")},d=/\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/.test(b)?"accusative":"nominative";return c[d][a.day()]}return a.defineLocale("ru",{months:d,monthsShort:e,weekdays:f,weekdaysShort:"вс_пн_вт_ср_чт_пт_сб".split("_"),weekdaysMin:"вс_пн_вт_ср_чт_пт_сб".split("_"),monthsParse:[/^янв/i,/^фев/i,/^мар/i,/^апр/i,/^ма[й|я]/i,/^июн/i,/^июл/i,/^авг/i,/^сен/i,/^окт/i,/^ноя/i,/^дек/i],longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY г.",LLL:"D MMMM YYYY г., LT",LLLL:"dddd, D MMMM YYYY г., LT"},calendar:{sameDay:"[Сегодня в] LT",nextDay:"[Завтра в] LT",lastDay:"[Вчера в] LT",nextWeek:function(){return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT"},lastWeek:function(a){if(a.week()===this.week())return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT";switch(this.day()){case 0:return"[В прошлое] dddd [в] LT";case 1:case 2:case 4:return"[В прошлый] dddd [в] LT";case 3:case 5:case 6:return"[В прошлую] dddd [в] LT"}},sameElse:"L"},relativeTime:{future:"через %s",past:"%s назад",s:"несколько секунд",m:c,mm:c,h:"час",hh:c,d:"день",dd:c,M:"месяц",MM:c,y:"год",yy:c},meridiemParse:/ночи|утра|дня|вечера/i,isPM:function(a){return/^(дня|вечера)$/.test(a)},meridiem:function(a){return 4>a?"ночи":12>a?"утра":17>a?"дня":"вечера"},ordinalParse:/\d{1,2}-(й|го|я)/,ordinal:function(a,b){switch(b){case"M":case"d":case"DDD":return a+"-й";case"D":return a+"-го";case"w":case"W":return a+"-я";default:return a}},week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){function b(a){return a>1&&5>a}function c(a,c,d,e){var f=a+" ";switch(d){case"s":return c||e?"pár sekúnd":"pár sekundami";case"m":return c?"minúta":e?"minútu":"minútou";case"mm":return c||e?f+(b(a)?"minúty":"minút"):f+"minútami";break;case"h":return c?"hodina":e?"hodinu":"hodinou";case"hh":return c||e?f+(b(a)?"hodiny":"hodín"):f+"hodinami";break;case"d":return c||e?"deň":"dňom";case"dd":return c||e?f+(b(a)?"dni":"dní"):f+"dňami";break;case"M":return c||e?"mesiac":"mesiacom";case"MM":return c||e?f+(b(a)?"mesiace":"mesiacov"):f+"mesiacmi";break;case"y":return c||e?"rok":"rokom";case"yy":return c||e?f+(b(a)?"roky":"rokov"):f+"rokmi"}}var d="január_február_marec_apríl_máj_jún_júl_august_september_október_november_december".split("_"),e="jan_feb_mar_apr_máj_jún_júl_aug_sep_okt_nov_dec".split("_");return a.defineLocale("sk",{months:d,monthsShort:e,monthsParse:function(a,b){var c,d=[];for(c=0;12>c;c++)d[c]=new RegExp("^"+a[c]+"$|^"+b[c]+"$","i");return d}(d,e),weekdays:"nedeľa_pondelok_utorok_streda_štvrtok_piatok_sobota".split("_"),weekdaysShort:"ne_po_ut_st_št_pi_so".split("_"),weekdaysMin:"ne_po_ut_st_št_pi_so".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd D. MMMM YYYY LT"},calendar:{sameDay:"[dnes o] LT",nextDay:"[zajtra o] LT",nextWeek:function(){switch(this.day()){case 0:return"[v nedeľu o] LT";case 1:case 2:return"[v] dddd [o] LT";case 3:return"[v stredu o] LT";case 4:return"[vo štvrtok o] LT";case 5:return"[v piatok o] LT";case 6:return"[v sobotu o] LT"}},lastDay:"[včera o] LT",lastWeek:function(){switch(this.day()){case 0:return"[minulú nedeľu o] LT";case 1:case 2:return"[minulý] dddd [o] LT";case 3:return"[minulú stredu o] LT";case 4:case 5:return"[minulý] dddd [o] LT";case 6:return"[minulú sobotu o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"pred %s",s:c,m:c,mm:c,h:c,hh:c,d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){function b(a,b,c){var d=a+" ";switch(c){case"m":return b?"ena minuta":"eno minuto";case"mm":return d+=1===a?"minuta":2===a?"minuti":3===a||4===a?"minute":"minut";case"h":return b?"ena ura":"eno uro";case"hh":return d+=1===a?"ura":2===a?"uri":3===a||4===a?"ure":"ur";case"dd":return d+=1===a?"dan":"dni";case"MM":return d+=1===a?"mesec":2===a?"meseca":3===a||4===a?"mesece":"mesecev";case"yy":return d+=1===a?"leto":2===a?"leti":3===a||4===a?"leta":"let"}}return a.defineLocale("sl",{months:"januar_februar_marec_april_maj_junij_julij_avgust_september_oktober_november_december".split("_"),monthsShort:"jan._feb._mar._apr._maj._jun._jul._avg._sep._okt._nov._dec.".split("_"),weekdays:"nedelja_ponedeljek_torek_sreda_četrtek_petek_sobota".split("_"),weekdaysShort:"ned._pon._tor._sre._čet._pet._sob.".split("_"),weekdaysMin:"ne_po_to_sr_če_pe_so".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[danes ob] LT",nextDay:"[jutri ob] LT",nextWeek:function(){switch(this.day()){case 0:return"[v] [nedeljo] [ob] LT";case 3:return"[v] [sredo] [ob] LT";case 6:return"[v] [soboto] [ob] LT";case 1:case 2:case 4:case 5:return"[v] dddd [ob] LT"}},lastDay:"[včeraj ob] LT",lastWeek:function(){switch(this.day()){case 0:case 3:case 6:return"[prejšnja] dddd [ob] LT";case 1:case 2:case 4:case 5:return"[prejšnji] dddd [ob] LT"}},sameElse:"L"},relativeTime:{future:"čez %s",past:"%s nazaj",s:"nekaj sekund",m:b,mm:b,h:b,hh:b,d:"en dan",dd:b,M:"en mesec",MM:b,y:"eno leto",yy:b},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){return a.defineLocale("sq",{months:"Janar_Shkurt_Mars_Prill_Maj_Qershor_Korrik_Gusht_Shtator_Tetor_Nëntor_Dhjetor".split("_"),monthsShort:"Jan_Shk_Mar_Pri_Maj_Qer_Kor_Gus_Sht_Tet_Nën_Dhj".split("_"),weekdays:"E Diel_E Hënë_E Martë_E Mërkurë_E Enjte_E Premte_E Shtunë".split("_"),weekdaysShort:"Die_Hën_Mar_Mër_Enj_Pre_Sht".split("_"),weekdaysMin:"D_H_Ma_Më_E_P_Sh".split("_"),meridiem:function(a){return 12>a?"PD":"MD"},longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Sot në] LT",nextDay:"[Nesër në] LT",nextWeek:"dddd [në] LT",lastDay:"[Dje në] LT",lastWeek:"dddd [e kaluar në] LT",sameElse:"L"},relativeTime:{future:"në %s",past:"%s më parë",s:"disa sekonda",m:"një minutë",mm:"%d minuta",h:"një orë",hh:"%d orë",d:"një ditë",dd:"%d ditë",M:"një muaj",MM:"%d muaj",y:"një vit",yy:"%d vite"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){var b={words:{m:["један минут","једне минуте"],mm:["минут","минуте","минута"],h:["један сат","једног сата"],hh:["сат","сата","сати"],dd:["дан","дана","дана"],MM:["месец","месеца","месеци"],yy:["година","године","година"]},correctGrammaticalCase:function(a,b){return 1===a?b[0]:a>=2&&4>=a?b[1]:b[2]},translate:function(a,c,d){var e=b.words[d];return 1===d.length?c?e[0]:e[1]:a+" "+b.correctGrammaticalCase(a,e)}};return a.defineLocale("sr-cyrl",{months:["јануар","фебруар","март","април","мај","јун","јул","август","септембар","октобар","новембар","децембар"],monthsShort:["јан.","феб.","мар.","апр.","мај","јун","јул","авг.","сеп.","окт.","нов.","дец."],weekdays:["недеља","понедељак","уторак","среда","четвртак","петак","субота"],weekdaysShort:["нед.","пон.","уто.","сре.","чет.","пет.","суб."],weekdaysMin:["не","по","ут","ср","че","пе","су"],longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[данас у] LT",nextDay:"[сутра у] LT",nextWeek:function(){switch(this.day()){case 0:return"[у] [недељу] [у] LT";case 3:return"[у] [среду] [у] LT";case 6:return"[у] [суботу] [у] LT";case 1:case 2:case 4:case 5:return"[у] dddd [у] LT"}},lastDay:"[јуче у] LT",lastWeek:function(){var a=["[прошле] [недеље] [у] LT","[прошлог] [понедељка] [у] LT","[прошлог] [уторка] [у] LT","[прошле] [среде] [у] LT","[прошлог] [четвртка] [у] LT","[прошлог] [петка] [у] LT","[прошле] [суботе] [у] LT"];return a[this.day()]},sameElse:"L"},relativeTime:{future:"за %s",past:"пре %s",s:"неколико секунди",m:b.translate,mm:b.translate,h:b.translate,hh:b.translate,d:"дан",dd:b.translate,M:"месец",MM:b.translate,y:"годину",yy:b.translate},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){var b={words:{m:["jedan minut","jedne minute"],mm:["minut","minute","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mesec","meseca","meseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(a,b){return 1===a?b[0]:a>=2&&4>=a?b[1]:b[2]},translate:function(a,c,d){var e=b.words[d];return 1===d.length?c?e[0]:e[1]:a+" "+b.correctGrammaticalCase(a,e)}};return a.defineLocale("sr",{months:["januar","februar","mart","april","maj","jun","jul","avgust","septembar","oktobar","novembar","decembar"],monthsShort:["jan.","feb.","mar.","apr.","maj","jun","jul","avg.","sep.","okt.","nov.","dec."],weekdays:["nedelja","ponedeljak","utorak","sreda","četvrtak","petak","subota"],weekdaysShort:["ned.","pon.","uto.","sre.","čet.","pet.","sub."],weekdaysMin:["ne","po","ut","sr","če","pe","su"],longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedelju] [u] LT";case 3:return"[u] [sredu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[juče u] LT",lastWeek:function(){var a=["[prošle] [nedelje] [u] LT","[prošlog] [ponedeljka] [u] LT","[prošlog] [utorka] [u] LT","[prošle] [srede] [u] LT","[prošlog] [četvrtka] [u] LT","[prošlog] [petka] [u] LT","[prošle] [subote] [u] LT"];return a[this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",m:b.translate,mm:b.translate,h:b.translate,hh:b.translate,d:"dan",dd:b.translate,M:"mesec",MM:b.translate,y:"godinu",yy:b.translate},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){return a.defineLocale("sv",{months:"januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag".split("_"),weekdaysShort:"sön_mån_tis_ons_tor_fre_lör".split("_"),weekdaysMin:"sö_må_ti_on_to_fr_lö".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Idag] LT",nextDay:"[Imorgon] LT",lastDay:"[Igår] LT",nextWeek:"dddd LT",lastWeek:"[Förra] dddd[en] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"för %s sedan",s:"några sekunder",m:"en minut",mm:"%d minuter",h:"en timme",hh:"%d timmar",d:"en dag",dd:"%d dagar",M:"en månad",MM:"%d månader",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}(e|a)/,ordinal:function(a){var b=a%10,c=1===~~(a%100/10)?"e":1===b?"a":2===b?"a":3===b?"e":"e";return a+c},week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("ta",{months:"ஜனவரி_பிப்ரவரி_மார்ச்_ஏப்ரல்_மே_ஜூன்_ஜூலை_ஆகஸ்ட்_செப்டெம்பர்_அக்டோபர்_நவம்பர்_டிசம்பர்".split("_"),monthsShort:"ஜனவரி_பிப்ரவரி_மார்ச்_ஏப்ரல்_மே_ஜூன்_ஜூலை_ஆகஸ்ட்_செப்டெம்பர்_அக்டோபர்_நவம்பர்_டிசம்பர்".split("_"),weekdays:"ஞாயிற்றுக்கிழமை_திங்கட்கிழமை_செவ்வாய்கிழமை_புதன்கிழமை_வியாழக்கிழமை_வெள்ளிக்கிழமை_சனிக்கிழமை".split("_"),weekdaysShort:"ஞாயிறு_திங்கள்_செவ்வாய்_புதன்_வியாழன்_வெள்ளி_சனி".split("_"),weekdaysMin:"ஞா_தி_செ_பு_வி_வெ_ச".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, LT",LLLL:"dddd, D MMMM YYYY, LT"},calendar:{sameDay:"[இன்று] LT",nextDay:"[நாளை] LT",nextWeek:"dddd, LT",lastDay:"[நேற்று] LT",lastWeek:"[கடந்த வாரம்] dddd, LT",sameElse:"L"},relativeTime:{future:"%s இல்",past:"%s முன்",s:"ஒரு சில விநாடிகள்",m:"ஒரு நிமிடம்",mm:"%d நிமிடங்கள்",h:"ஒரு மணி நேரம்",hh:"%d மணி நேரம்",d:"ஒரு நாள்",dd:"%d நாட்கள்",M:"ஒரு மாதம்",MM:"%d மாதங்கள்",y:"ஒரு வருடம்",yy:"%d ஆண்டுகள்"},ordinalParse:/\d{1,2}வது/,ordinal:function(a){return a+"வது"},meridiem:function(a){return a>=6&&10>=a?" காலை":a>=10&&14>=a?" நண்பகல்":a>=14&&18>=a?" எற்பாடு":a>=18&&20>=a?" மாலை":a>=20&&24>=a?" இரவு":a>=0&&6>=a?" வைகறை":void 0},week:{dow:0,doy:6}})}),function(a){a(tb)}(function(a){return a.defineLocale("th",{months:"มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม".split("_"),monthsShort:"มกรา_กุมภา_มีนา_เมษา_พฤษภา_มิถุนา_กรกฎา_สิงหา_กันยา_ตุลา_พฤศจิกา_ธันวา".split("_"),weekdays:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์".split("_"),weekdaysShort:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์".split("_"),weekdaysMin:"อา._จ._อ._พ._พฤ._ศ._ส.".split("_"),longDateFormat:{LT:"H นาฬิกา m นาที",LTS:"LT s วินาที",L:"YYYY/MM/DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY เวลา LT",LLLL:"วันddddที่ D MMMM YYYY เวลา LT"},meridiem:function(a){return 12>a?"ก่อนเที่ยง":"หลังเที่ยง"},calendar:{sameDay:"[วันนี้ เวลา] LT",nextDay:"[พรุ่งนี้ เวลา] LT",nextWeek:"dddd[หน้า เวลา] LT",lastDay:"[เมื่อวานนี้ เวลา] LT",lastWeek:"[วัน]dddd[ที่แล้ว เวลา] LT",sameElse:"L"},relativeTime:{future:"อีก %s",past:"%sที่แล้ว",s:"ไม่กี่วินาที",m:"1 นาที",mm:"%d นาที",h:"1 ชั่วโมง",hh:"%d ชั่วโมง",d:"1 วัน",dd:"%d วัน",M:"1 เดือน",MM:"%d เดือน",y:"1 ปี",yy:"%d ปี"}})}),function(a){a(tb)}(function(a){return a.defineLocale("tl-ph",{months:"Enero_Pebrero_Marso_Abril_Mayo_Hunyo_Hulyo_Agosto_Setyembre_Oktubre_Nobyembre_Disyembre".split("_"),monthsShort:"Ene_Peb_Mar_Abr_May_Hun_Hul_Ago_Set_Okt_Nob_Dis".split("_"),weekdays:"Linggo_Lunes_Martes_Miyerkules_Huwebes_Biyernes_Sabado".split("_"),weekdaysShort:"Lin_Lun_Mar_Miy_Huw_Biy_Sab".split("_"),weekdaysMin:"Li_Lu_Ma_Mi_Hu_Bi_Sab".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"MM/D/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY LT",LLLL:"dddd, MMMM DD, YYYY LT"},calendar:{sameDay:"[Ngayon sa] LT",nextDay:"[Bukas sa] LT",nextWeek:"dddd [sa] LT",lastDay:"[Kahapon sa] LT",lastWeek:"dddd [huling linggo] LT",sameElse:"L"},relativeTime:{future:"sa loob ng %s",past:"%s ang nakalipas",s:"ilang segundo",m:"isang minuto",mm:"%d minuto",h:"isang oras",hh:"%d oras",d:"isang araw",dd:"%d araw",M:"isang buwan",MM:"%d buwan",y:"isang taon",yy:"%d taon"},ordinalParse:/\d{1,2}/,ordinal:function(a){return a},week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){var b={1:"'inci",5:"'inci",8:"'inci",70:"'inci",80:"'inci",2:"'nci",7:"'nci",20:"'nci",50:"'nci",3:"'üncü",4:"'üncü",100:"'üncü",6:"'ncı",9:"'uncu",10:"'uncu",30:"'uncu",60:"'ıncı",90:"'ıncı"};return a.defineLocale("tr",{months:"Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık".split("_"),monthsShort:"Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara".split("_"),weekdays:"Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi".split("_"),weekdaysShort:"Paz_Pts_Sal_Çar_Per_Cum_Cts".split("_"),weekdaysMin:"Pz_Pt_Sa_Ça_Pe_Cu_Ct".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[bugün saat] LT",nextDay:"[yarın saat] LT",nextWeek:"[haftaya] dddd [saat] LT",lastDay:"[dün] LT",lastWeek:"[geçen hafta] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s önce",s:"birkaç saniye",m:"bir dakika",mm:"%d dakika",h:"bir saat",hh:"%d saat",d:"bir gün",dd:"%d gün",M:"bir ay",MM:"%d ay",y:"bir yıl",yy:"%d yıl"},ordinalParse:/\d{1,2}'(inci|nci|üncü|ncı|uncu|ıncı)/,ordinal:function(a){if(0===a)return a+"'ıncı";var c=a%10,d=a%100-c,e=a>=100?100:null;return a+(b[c]||b[d]||b[e])},week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){return a.defineLocale("tzm-latn",{months:"innayr_brˤayrˤ_marˤsˤ_ibrir_mayyw_ywnyw_ywlywz_ɣwšt_šwtanbir_ktˤwbrˤ_nwwanbir_dwjnbir".split("_"),monthsShort:"innayr_brˤayrˤ_marˤsˤ_ibrir_mayyw_ywnyw_ywlywz_ɣwšt_šwtanbir_ktˤwbrˤ_nwwanbir_dwjnbir".split("_"),weekdays:"asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas".split("_"),weekdaysShort:"asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas".split("_"),weekdaysMin:"asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[asdkh g] LT",nextDay:"[aska g] LT",nextWeek:"dddd [g] LT",lastDay:"[assant g] LT",lastWeek:"dddd [g] LT",sameElse:"L"},relativeTime:{future:"dadkh s yan %s",past:"yan %s",s:"imik",m:"minuḍ",mm:"%d minuḍ",h:"saɛa",hh:"%d tassaɛin",d:"ass",dd:"%d ossan",M:"ayowr",MM:"%d iyyirn",y:"asgas",yy:"%d isgasn"},week:{dow:6,doy:12}})}),function(a){a(tb)}(function(a){return a.defineLocale("tzm",{months:"ⵉⵏⵏⴰⵢⵔ_ⴱⵕⴰⵢⵕ_ⵎⴰⵕⵚ_ⵉⴱⵔⵉⵔ_ⵎⴰⵢⵢⵓ_ⵢⵓⵏⵢⵓ_ⵢⵓⵍⵢⵓⵣ_ⵖⵓⵛⵜ_ⵛⵓⵜⴰⵏⴱⵉⵔ_ⴽⵟⵓⴱⵕ_ⵏⵓⵡⴰⵏⴱⵉⵔ_ⴷⵓⵊⵏⴱⵉⵔ".split("_"),monthsShort:"ⵉⵏⵏⴰⵢⵔ_ⴱⵕⴰⵢⵕ_ⵎⴰⵕⵚ_ⵉⴱⵔⵉⵔ_ⵎⴰⵢⵢⵓ_ⵢⵓⵏⵢⵓ_ⵢⵓⵍⵢⵓⵣ_ⵖⵓⵛⵜ_ⵛⵓⵜⴰⵏⴱⵉⵔ_ⴽⵟⵓⴱⵕ_ⵏⵓⵡⴰⵏⴱⵉⵔ_ⴷⵓⵊⵏⴱⵉⵔ".split("_"),weekdays:"ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ".split("_"),weekdaysShort:"ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ".split("_"),weekdaysMin:"ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[ⴰⵙⴷⵅ ⴴ] LT",nextDay:"[ⴰⵙⴽⴰ ⴴ] LT",nextWeek:"dddd [ⴴ] LT",lastDay:"[ⴰⵚⴰⵏⵜ ⴴ] LT",lastWeek:"dddd [ⴴ] LT",sameElse:"L"},relativeTime:{future:"ⴷⴰⴷⵅ ⵙ ⵢⴰⵏ %s",past:"ⵢⴰⵏ %s",s:"ⵉⵎⵉⴽ",m:"ⵎⵉⵏⵓⴺ",mm:"%d ⵎⵉⵏⵓⴺ",h:"ⵙⴰⵄⴰ",hh:"%d ⵜⴰⵙⵙⴰⵄⵉⵏ",d:"ⴰⵙⵙ",dd:"%d oⵙⵙⴰⵏ",M:"ⴰⵢoⵓⵔ",MM:"%d ⵉⵢⵢⵉⵔⵏ",y:"ⴰⵙⴳⴰⵙ",yy:"%d ⵉⵙⴳⴰⵙⵏ"},week:{dow:6,doy:12}}) +}),function(a){a(tb)}(function(a){function b(a,b){var c=a.split("_");return b%10===1&&b%100!==11?c[0]:b%10>=2&&4>=b%10&&(10>b%100||b%100>=20)?c[1]:c[2]}function c(a,c,d){var e={mm:"хвилина_хвилини_хвилин",hh:"година_години_годин",dd:"день_дні_днів",MM:"місяць_місяці_місяців",yy:"рік_роки_років"};return"m"===d?c?"хвилина":"хвилину":"h"===d?c?"година":"годину":a+" "+b(e[d],+a)}function d(a,b){var c={nominative:"січень_лютий_березень_квітень_травень_червень_липень_серпень_вересень_жовтень_листопад_грудень".split("_"),accusative:"січня_лютого_березня_квітня_травня_червня_липня_серпня_вересня_жовтня_листопада_грудня".split("_")},d=/D[oD]? *MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function e(a,b){var c={nominative:"неділя_понеділок_вівторок_середа_четвер_п’ятниця_субота".split("_"),accusative:"неділю_понеділок_вівторок_середу_четвер_п’ятницю_суботу".split("_"),genitive:"неділі_понеділка_вівторка_середи_четверга_п’ятниці_суботи".split("_")},d=/(\[[ВвУу]\]) ?dddd/.test(b)?"accusative":/\[?(?:минулої|наступної)? ?\] ?dddd/.test(b)?"genitive":"nominative";return c[d][a.day()]}function f(a){return function(){return a+"о"+(11===this.hours()?"б":"")+"] LT"}}return a.defineLocale("uk",{months:d,monthsShort:"січ_лют_бер_квіт_трав_черв_лип_серп_вер_жовт_лист_груд".split("_"),weekdays:e,weekdaysShort:"нд_пн_вт_ср_чт_пт_сб".split("_"),weekdaysMin:"нд_пн_вт_ср_чт_пт_сб".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY р.",LLL:"D MMMM YYYY р., LT",LLLL:"dddd, D MMMM YYYY р., LT"},calendar:{sameDay:f("[Сьогодні "),nextDay:f("[Завтра "),lastDay:f("[Вчора "),nextWeek:f("[У] dddd ["),lastWeek:function(){switch(this.day()){case 0:case 3:case 5:case 6:return f("[Минулої] dddd [").call(this);case 1:case 2:case 4:return f("[Минулого] dddd [").call(this)}},sameElse:"L"},relativeTime:{future:"за %s",past:"%s тому",s:"декілька секунд",m:c,mm:c,h:"годину",hh:c,d:"день",dd:c,M:"місяць",MM:c,y:"рік",yy:c},meridiem:function(a){return 4>a?"ночі":12>a?"ранку":17>a?"дня":"вечора"},ordinalParse:/\d{1,2}-(й|го)/,ordinal:function(a,b){switch(b){case"M":case"d":case"DDD":case"w":case"W":return a+"-й";case"D":return a+"-го";default:return a}},week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){return a.defineLocale("uz",{months:"январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь".split("_"),monthsShort:"янв_фев_мар_апр_май_июн_июл_авг_сен_окт_ноя_дек".split("_"),weekdays:"Якшанба_Душанба_Сешанба_Чоршанба_Пайшанба_Жума_Шанба".split("_"),weekdaysShort:"Якш_Душ_Сеш_Чор_Пай_Жум_Шан".split("_"),weekdaysMin:"Як_Ду_Се_Чо_Па_Жу_Ша".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"D MMMM YYYY, dddd LT"},calendar:{sameDay:"[Бугун соат] LT [да]",nextDay:"[Эртага] LT [да]",nextWeek:"dddd [куни соат] LT [да]",lastDay:"[Кеча соат] LT [да]",lastWeek:"[Утган] dddd [куни соат] LT [да]",sameElse:"L"},relativeTime:{future:"Якин %s ичида",past:"Бир неча %s олдин",s:"фурсат",m:"бир дакика",mm:"%d дакика",h:"бир соат",hh:"%d соат",d:"бир кун",dd:"%d кун",M:"бир ой",MM:"%d ой",y:"бир йил",yy:"%d йил"},week:{dow:1,doy:7}})}),function(a){a(tb)}(function(a){return a.defineLocale("vi",{months:"tháng 1_tháng 2_tháng 3_tháng 4_tháng 5_tháng 6_tháng 7_tháng 8_tháng 9_tháng 10_tháng 11_tháng 12".split("_"),monthsShort:"Th01_Th02_Th03_Th04_Th05_Th06_Th07_Th08_Th09_Th10_Th11_Th12".split("_"),weekdays:"chủ nhật_thứ hai_thứ ba_thứ tư_thứ năm_thứ sáu_thứ bảy".split("_"),weekdaysShort:"CN_T2_T3_T4_T5_T6_T7".split("_"),weekdaysMin:"CN_T2_T3_T4_T5_T6_T7".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM [năm] YYYY",LLL:"D MMMM [năm] YYYY LT",LLLL:"dddd, D MMMM [năm] YYYY LT",l:"DD/M/YYYY",ll:"D MMM YYYY",lll:"D MMM YYYY LT",llll:"ddd, D MMM YYYY LT"},calendar:{sameDay:"[Hôm nay lúc] LT",nextDay:"[Ngày mai lúc] LT",nextWeek:"dddd [tuần tới lúc] LT",lastDay:"[Hôm qua lúc] LT",lastWeek:"dddd [tuần rồi lúc] LT",sameElse:"L"},relativeTime:{future:"%s tới",past:"%s trước",s:"vài giây",m:"một phút",mm:"%d phút",h:"một giờ",hh:"%d giờ",d:"một ngày",dd:"%d ngày",M:"một tháng",MM:"%d tháng",y:"một năm",yy:"%d năm"},ordinalParse:/\d{1,2}/,ordinal:function(a){return a},week:{dow:1,doy:4}})}),function(a){a(tb)}(function(a){return a.defineLocale("zh-cn",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"周日_周一_周二_周三_周四_周五_周六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"Ah点mm",LTS:"Ah点m分s秒",L:"YYYY-MM-DD",LL:"YYYY年MMMD日",LLL:"YYYY年MMMD日LT",LLLL:"YYYY年MMMD日ddddLT",l:"YYYY-MM-DD",ll:"YYYY年MMMD日",lll:"YYYY年MMMD日LT",llll:"YYYY年MMMD日ddddLT"},meridiem:function(a,b){var c=100*a+b;return 600>c?"凌晨":900>c?"早上":1130>c?"上午":1230>c?"中午":1800>c?"下午":"晚上"},calendar:{sameDay:function(){return 0===this.minutes()?"[今天]Ah[点整]":"[今天]LT"},nextDay:function(){return 0===this.minutes()?"[明天]Ah[点整]":"[明天]LT"},lastDay:function(){return 0===this.minutes()?"[昨天]Ah[点整]":"[昨天]LT"},nextWeek:function(){var b,c;return b=a().startOf("week"),c=this.unix()-b.unix()>=604800?"[下]":"[本]",0===this.minutes()?c+"dddAh点整":c+"dddAh点mm"},lastWeek:function(){var b,c;return b=a().startOf("week"),c=this.unix()c?"早上":1130>c?"上午":1230>c?"中午":1800>c?"下午":"晚上"},calendar:{sameDay:"[今天]LT",nextDay:"[明天]LT",nextWeek:"[下]ddddLT",lastDay:"[昨天]LT",lastWeek:"[上]ddddLT",sameElse:"L"},ordinalParse:/\d{1,2}(日|月|週)/,ordinal:function(a,b){switch(b){case"d":case"D":case"DDD":return a+"日";case"M":return a+"月";case"w":case"W":return a+"週";default:return a}},relativeTime:{future:"%s內",past:"%s前",s:"幾秒",m:"一分鐘",mm:"%d分鐘",h:"一小時",hh:"%d小時",d:"一天",dd:"%d天",M:"一個月",MM:"%d個月",y:"一年",yy:"%d年"}})}),tb.locale("en"),Jb?module.exports=tb:"function"==typeof define&&define.amd?(define("moment",function(a,b,c){return c.config&&c.config()&&c.config().noGlobal===!0&&(xb.moment=ub),tb}),sb(!0)):sb()}).call(this); \ No newline at end of file diff --git a/public/assets/js/phpci.js b/public/assets/js/phpci.js index d662825b..4a55b459 100644 --- a/public/assets/js/phpci.js +++ b/public/assets/js/phpci.js @@ -1,219 +1,475 @@ -/** -* Used for delete buttons in the system, just to prevent accidental clicks. -*/ -function confirmDelete(url) -{ - if(confirm('Are you sure you want to delete this?')) - { - window.location.href = url; - } - else - { - return false; - } + +var PHPCI = { + intervals: {}, + + init: function () { + // Setup the date locale + moment.locale(PHPCI_LANGUAGE); + + $(document).ready(function () { + // Format datetimes + $('time[datetime]').each(function() { + var thisDate = $(this).attr('datetime'); + var formattedDate = moment(thisDate).format($(this).data('format') || 'lll'); + $(this).text(formattedDate); + }); + + // Update latest builds every 5 seconds: + PHPCI.getBuilds(); + PHPCI.intervals.getBuilds = setInterval(PHPCI.getBuilds, 5000); + + // Update latest project builds every 10 seconds: + if (typeof PHPCI_PROJECT_ID != 'undefined') { + PHPCI.intervals.getProjectBuilds = setInterval(PHPCI.getProjectBuilds, 10000); + } + + PHPCI.uiUpdated(); + }); + + $(window).on('builds-updated', function (e, data) { + PHPCI.updateHeaderBuilds(data); + }); + }, + + getBuilds: function () { + $.ajax({ + url: PHPCI_URL + 'build/latest', + + success: function (data) { + $(window).trigger('builds-updated', [data]); + }, + + error: PHPCI.handleFailedAjax + }); + }, + + getProjectBuilds: function () { + $.ajax({ + url: PHPCI_URL + 'project/builds/' + PHPCI_PROJECT_ID + '?branch=' + PHPCI_PROJECT_BRANCH, + + success: function (data) { + $('#latest-builds').html(data); + }, + + error: PHPCI.handleFailedAjax + }); + }, + + updateHeaderBuilds: function (data) { + $('.phpci-pending-list').empty(); + $('.phpci-running-list').empty(); + + if (!data.pending.count) { + $('.phpci-pending').hide(); + } else { + $('.phpci-pending').show(); + $('.phpci-pending .header').text(Lang.get('n_builds_pending', data.pending.count)); + + $.each(data.pending.items, function (idx, build) { + $('.phpci-pending-list').append(build.header_row); + }); + } + + if (!data.running.count) { + $('.phpci-running').hide(); + } else { + $('.phpci-running').show(); + $('.phpci-running .header').text(Lang.get('n_builds_running', data.running.count)); + + $.each(data.running.items, function (idx, build) { + $('.phpci-running-list').append(build.header_row); + }); + } + + }, + + get: function (uri, success) { + + $.ajax({ + url: window.PHPCI_URL + uri, + + success: function (data) { + success(); + PHPCI.uiUpdated(); + }, + + error: PHPCI.handleFailedAjax + }); + }, + + handleFailedAjax: function (xhr) { + if (xhr.status == 401) { + window.location.href = window.PHPCI_URL + 'session/login'; + } + }, + + uiUpdated: function () { + $('.duration').each(function () { + var seconds = $(this).data('duration'); + + if (seconds == 0) { + return; + } + + $(this).text(moment.duration(seconds, 'seconds').humanize()); + }); + + $('.datetime').each(function () { + var dateString = $(this).data('date'); + + if (!dateString) { + return; + } + + $(this).text(moment(dateString).format('lll')); + }); + } +}; + +PHPCI.init(); + +function handleFailedAjax(xhr) { + PHPCI.handleFailedAjax(xhr); } /** -* Used to initialise the project form: -*/ + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind + * for the details of code below + */ +if (!Function.prototype.bind) { + Function.prototype.bind = function (oThis) { + if (typeof this !== "function") { + // closest thing possible to the ECMAScript 5 internal IsCallable function + throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); + } + + var aArgs = Array.prototype.slice.call(arguments, 1), + fToBind = this, + fNOP = function () { + }, + fBound = function () { + return fToBind.apply(this instanceof fNOP && oThis + ? this + : oThis, + aArgs.concat(Array.prototype.slice.call(arguments))); + }; + + fNOP.prototype = this.prototype; + fBound.prototype = new fNOP(); + + return fBound; + }; +} + +/** + * Used for delete buttons in the system, just to prevent accidental clicks. + */ +function confirmDelete(url, subject, reloadAfter) { + + var dialog = new PHPCIConfirmDialog({ + message: subject + ' will be permanently deleted. Are you sure?', + confirmBtnCaption: 'Delete', + /* + confirm-btn click handler + */ + confirmed: function (e) { + var dialog = this; + e.preventDefault(); + + /* + Call delete URL + */ + $.ajax({ + url: url, + success: function (data) { + if (reloadAfter) { + dialog.onClose = function () { + window.location.reload(); + }; + } + + dialog.showStatusMessage('Successfully deleted!', 1000); + }, + error: function (data) { + dialog.showStatusMessage('Deletion failed! Server says "' + data.statusText + '"'); + + if (data.status == 401) { + handleFailedAjax(data); + } + } + }); + } + }); + + dialog.show(); + return dialog; +} + +/** + * PHPCIConfirmDialog constructor options object + * @type {{message: string, title: string, confirmBtnCaption: string, cancelBtnCaption: string, confirmed: Function}} + */ +var PHPCIConfirmDialogOptions = { + message: 'The action will be performed and cannot be undone. Are you sure?', + title: 'Confirmation Dialog', + confirmBtnCaption: 'Ok', + cancelBtnCaption: 'Cancel', + confirmed: function (e) { + this.close(); + } +}; + +var PHPCIConfirmDialog = Class.extend({ + /** + * @private + * @var {bool} Determines whether the dialog has been confirmed + */ + confirmed: false, + + /** + * @param {PHPCIConfirmDialogOptions} options + */ + init: function (options) { + + options = options ? $.extend(PHPCIConfirmDialogOptions, options) : PHPCIConfirmDialogOptions; + + if (!$('#confirm-dialog').length) { + /* + Add the dialog html to a page on first use. No need to have it there before first use. + */ + $('body').append( + '' + ); + } + + /* + Define dialog controls + */ + this.$dialog = $('#confirm-dialog'); + this.$cancelBtn = this.$dialog.find('div.modal-footer button.btn-default'); + this.$confirmBtn = this.$dialog.find('div.modal-footer button.btn-primary'); + this.$title = this.$dialog.find('h4.modal-title'); + this.$body = this.$dialog.find('div.modal-body'); + + /* + Initialize its values + */ + this.$title.html(options.title ? options.title : PHPCIConfirmDialogOptions.title); + this.$body.html(options.message ? options.message : PHPCIConfirmDialogOptions.message); + this.$confirmBtn.html( + options.confirmBtnCaption ? options.confirmBtnCaption : PHPCIConfirmDialogOptions.confirmBtnCaption + ); + + this.$cancelBtn.html( + options.cancelBtnCaption ? options.cancelBtnCaption : PHPCIConfirmDialogOptions.cancelBtnCaption + ); + + /* + Events + */ + this.confirmBtnClick = options.confirmed; + + /* + Re-bind handlers + */ + this.$confirmBtn.unbind('click'); + this.$confirmBtn.click(this.onConfirm.bind(this)); + + this.$confirmBtn.unbind('hidden.bs.modal'); + + /* + Bind the close event of the dialog to the set of onClose* methods + */ + this.$dialog.on('hidden.bs.modal', function () {this.onClose()}.bind(this)); + this.$dialog.on('hidden.bs.modal', function () { + if (this.confirmed) { + this.onCloseConfirmed(); + } else { + this.onCloseCanceled(); + } + }.bind(this)); + + /* + Restore state if was changed previously + */ + this.$cancelBtn.show(); + this.$confirmBtn.show(); + this.confirmed = false; + }, + + /** + * Show dialog + */ + show: function () { + this.$dialog.modal('show'); + }, + + /** + * Hide dialog + */ + close: function () { + this.$dialog.modal('hide'); + }, + + onConfirm: function (e) { + this.confirmed = true; + $(this).attr('disabled', 'disabled'); + this.confirmBtnClick(e); + }, + + /** + * Called only when confirmed dialog was closed + */ + onCloseConfirmed: function () {}, + + /** + * Called only when canceled dialog was closed + */ + onCloseCanceled: function () {}, + + /** + * Called always when the dialog was closed + */ + onClose: function () {}, + + showStatusMessage: function (message, closeTimeout) { + this.$confirmBtn.hide(); + this.$cancelBtn.html('Close'); + + /* + Status message + */ + this.$body.html(message); + + if (closeTimeout) { + window.setTimeout(function () { + /* + Hide the dialog + */ + this.close(); + }.bind(this), closeTimeout); + } + } +}); + +/** + * Used to initialise the project form: + */ function setupProjectForm() { $('.github-container').hide(); - $('#element-reference').change(function() - { - var el = $(this); - var val = el.val(); + $('#element-reference').change(function() + { + var el = $(this); + var val = el.val(); + var type = $('#element-type').val(); + var acceptable = { + 'github': { + 'ssh': /git\@github\.com\:([a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+)\.git/, + 'git': /git\:\/\/github.com\/([a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+)\.git/, + 'http': /https\:\/\/github\.com\/([a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+)(\.git)?/ + }, + 'bitbucket': { + 'ssh': /git\@bitbucket\.org\:([a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+)\.git/, + 'http': /https\:\/\/[a-zA-Z0-9_\-]+\@bitbucket.org\/([a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+)\.git/, + 'anon': /https\:\/\/bitbucket.org\/([a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+)(\.git)?/ + } - var acceptable = { - 'github_ssh': /git\@github\.com\:([a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+)\.git/, - 'github_git': /git\:\/\/github.com\/([a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+)\.git/, - 'github_http': /https\:\/\/github\.com\/([a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+)(\.git)?/, - 'bb_ssh': /git\@bitbucket\.org\:([a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+)\.git/, - 'bb_http': /https\:\/\/[a-zA-Z0-9_\-]+\@bitbucket.org\/([a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+)\.git/, - 'bb_anon': /https\:\/\/bitbucket.org\/([a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+)(\.git)?/ - }; + }; - for(var i in acceptable) { - if(val.match(acceptable[i])) { - el.val(val.replace(acceptable[i], '$1')); - } - } - }); + if( acceptable[type] !== undefined ) { + for(var i in acceptable[type]) { + if(val.match(acceptable[type][i])) { + el.val(val.replace(acceptable[type][i], '$1')); + } + } + } + }); - $('#element-type').change(function() - { + $('#element-type').change(function() { if ($(this).val() == 'github') { $('#loading').show(); - $.getJSON(window.PHPCI_URL + 'project/github-repositories', function (data) { - $('#loading').hide(); + $.ajax({ + dataType: "json", + url: window.PHPCI_URL + 'project/github-repositories', + success: function (data) { + $('#loading').hide(); - if (data.repos) { - $('#element-github').empty(); + if (data && data.repos) { + $('#element-github').empty(); - for (var i in data.repos) { - var name = data.repos[i]; - $('#element-github').append($('').text(name).val(name)); + for (var i in data.repos) { + var name = data.repos[i]; + $('#element-github').append($('').text(name).val(name)); + } + + $('.github-container').slideDown(); } - - $('.github-container').slideDown(); - } + }, + error: handleFailedAjax }); } else { $('.github-container').slideUp(); } - }); + $('#element-reference').trigger('change'); + }); - $('#element-github').change(function() - { - var val = $('#element-github').val(); + $('#element-github').change(function() + { + var val = $('#element-github').val(); - if(val != 'choose') { - $('#element-type').val('github'); - $('#element-reference').val(val); + if(val != 'choose') { + $('#element-type').val('github'); + $('#element-reference').val(val); - $('label[for=element-reference]').hide(); - $('label[for=element-type]').hide(); - $('#element-reference').hide(); - $('#element-type').hide(); - $('#element-token').val(window.github_token); - $('#element-title').val(val); - } - else { - $('label[for=element-reference]').show(); - $('label[for=element-type]').show(); - $('#element-reference').show(); - $('#element-type').show(); - $('#element-reference').val(''); - $('#element-token').val(''); - } - }); + $('label[for=element-reference]').hide(); + $('label[for=element-type]').hide(); + $('#element-reference').hide(); + $('#element-type').hide(); + $('#element-token').val(window.github_token); + $('#element-title').val(val); + } + else { + $('label[for=element-reference]').show(); + $('label[for=element-type]').show(); + $('#element-reference').show(); + $('#element-type').show(); + $('#element-reference').val(''); + $('#element-token').val(''); + } + }); } -var PHPCIObject = Class.extend({ - buildId: null, - plugins: {}, - observers: {}, - buildData: {}, - queries: {}, - updateInterval: null, +var Lang = { + get: function () { + var args = Array.prototype.slice.call(arguments);; + var string = args.shift(); - init: function(build) { - this.buildId = build; - this.registerQuery('build-updated', 10); - }, - - registerQuery: function(name, seconds, query) { - var self = this; - var uri = 'build/meta/' + self.buildId; - var query = query || {}; - - if (name == 'build-updated') { - uri = 'build/data/' + self.buildId; + if (PHPCI_STRINGS[string]) { + args.unshift(PHPCI_STRINGS[string]); + return sprintf.apply(sprintf[0], args); } - var cb = function() { - $.getJSON(window.PHPCI_URL + uri, query, function(data) { - $(window).trigger({type: name, queryData: data}); - }); - }; + return 'MISSING: ' + string; + } +}; - if (seconds != -1) { - setInterval(cb, seconds * 1000); - } - - return cb; - }, - - registerPlugin: function(plugin) { - this.plugins[plugin.id] = plugin; - plugin.register(); - }, - - storePluginOrder: function () { - var renderOrder = []; - - $('.ui-plugin > div').each(function() { - renderOrder.push($(this).attr('id')); - }); - - localStorage.setItem('phpci-plugin-order', JSON.stringify(renderOrder)); - }, - - renderPlugins: function() { - var self = this; - var rendered = []; - var renderOrder = localStorage.getItem('phpci-plugin-order'); - - if (renderOrder) { - renderOrder = JSON.parse(renderOrder); - } else { - renderOrder = ['build-time', 'build-lines-chart', 'build-warnings-chart', 'build-log']; - } - - for (var idx in renderOrder) { - var key = renderOrder[idx]; - self.renderPlugin(self.plugins[key]); - rendered.push(key); - } - - for (var key in this.plugins) { - if (rendered.indexOf(key) == -1) { - self.renderPlugin(self.plugins[key]); - } - } - - $('#plugins').sortable({ - handle: '.title', - connectWith: '#plugins', - update: self.storePluginOrder - }); - - $(window).trigger({type: 'build-updated', queryData: self.buildData}); - }, - - renderPlugin: function(plugin) { - var output = $('
    ').addClass('box-content').append(plugin.render()); - var container = $('
    ').addClass('ui-plugin ' + plugin.css); - var content = $('
    ').attr('id', plugin.id).append(output); - - if (plugin.box) { - content.addClass('box'); - } - - if (plugin.title) { - content.prepend('

    '+plugin.title+'

    '); - } - - content.append(output); - container.append(content); - - $('#plugins').append(container); - }, - - UiPlugin: Class.extend({ - id: null, - css: 'col-lg-4 col-md-6 col-sm-12 col-xs-12', - box: true, - - init: function(){ - }, - - register: function() { - var self = this; - - $(window).on('build-updated', function(data) { - self.onUpdate(data); - }); - }, - - render: function () { - return ''; - }, - - onUpdate: function (build) { - - } - }) -}); \ No newline at end of file +moment.locale(PHPCI_LANGUAGE); \ No newline at end of file diff --git a/public/assets/js/sprintf.js b/public/assets/js/sprintf.js new file mode 100644 index 00000000..d5bcd097 --- /dev/null +++ b/public/assets/js/sprintf.js @@ -0,0 +1,4 @@ +/*! sprintf-js | Alexandru Marasteanu (http://alexei.ro/) | BSD-3-Clause */ + +!function(a){function b(){var a=arguments[0],c=b.cache;return c[a]&&c.hasOwnProperty(a)||(c[a]=b.parse(a)),b.format.call(null,c[a],arguments)}function c(a){return Object.prototype.toString.call(a).slice(8,-1).toLowerCase()}function d(a,b){return Array(b+1).join(a)}var e={not_string:/[^s]/,number:/[dief]/,text:/^[^\x25]+/,modulo:/^\x25{2}/,placeholder:/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fiosuxX])/,key:/^([a-z_][a-z_\d]*)/i,key_access:/^\.([a-z_][a-z_\d]*)/i,index_access:/^\[(\d+)\]/,sign:/^[\+\-]/};b.format=function(a,f){var g,h,i,j,k,l,m,n=1,o=a.length,p="",q=[],r=!0,s="";for(h=0;o>h;h++)if(p=c(a[h]),"string"===p)q[q.length]=a[h];else if("array"===p){if(j=a[h],j[2])for(g=f[n],i=0;i=0),j[8]){case"b":g=g.toString(2);break;case"c":g=String.fromCharCode(g);break;case"d":case"i":g=parseInt(g,10);break;case"e":g=j[7]?g.toExponential(j[7]):g.toExponential();break;case"f":g=j[7]?parseFloat(g).toFixed(j[7]):parseFloat(g);break;case"o":g=g.toString(8);break;case"s":g=(g=String(g))&&j[7]?g.substring(0,j[7]):g;break;case"u":g>>>=0;break;case"x":g=g.toString(16);break;case"X":g=g.toString(16).toUpperCase()}!e.number.test(j[8])||r&&!j[3]?s="":(s=r?"+":"-",g=g.toString().replace(e.sign,"")),l=j[4]?"0"===j[4]?"0":j[4].charAt(1):" ",m=j[6]-(s+g).length,k=j[6]&&m>0?d(l,m):"",q[q.length]=j[5]?s+g+k:"0"===l?s+k+g:k+s+g}return q.join("")},b.cache={},b.parse=function(a){for(var b=a,c=[],d=[],f=0;b;){if(null!==(c=e.text.exec(b)))d[d.length]=c[0];else if(null!==(c=e.modulo.exec(b)))d[d.length]="%";else{if(null===(c=e.placeholder.exec(b)))throw new SyntaxError("[sprintf] unexpected placeholder");if(c[2]){f|=1;var g=[],h=c[2],i=[];if(null===(i=e.key.exec(h)))throw new SyntaxError("[sprintf] failed to parse named argument key");for(g[g.length]=i[1];""!==(h=h.substring(i[0].length));)if(null!==(i=e.key_access.exec(h)))g[g.length]=i[1];else{if(null===(i=e.index_access.exec(h)))throw new SyntaxError("[sprintf] failed to parse named argument key");g[g.length]=i[1]}c[2]=g}else f|=2;if(3===f)throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported");d[d.length]=c}b=b.substring(c[0].length)}return d};var f=function(a,c,d){return d=(c||[]).slice(0),d.splice(0,0,a),b.apply(null,d)};"undefined"!=typeof exports?(exports.sprintf=b,exports.vsprintf=f):(a.sprintf=b,a.vsprintf=f,"function"==typeof define&&define.amd&&define(function(){return{sprintf:b,vsprintf:f}}))}("undefined"==typeof window?this:window); +//# sourceMappingURL=sprintf.min.map \ No newline at end of file diff --git a/public/assets/js/sprintf.min.map b/public/assets/js/sprintf.min.map new file mode 100644 index 00000000..bced4235 --- /dev/null +++ b/public/assets/js/sprintf.min.map @@ -0,0 +1 @@ +{"version":3,"file":"sprintf.min.js","sources":["../src/sprintf.js"],"names":["window","sprintf","key","arguments","cache","hasOwnProperty","parse","format","call","get_type","variable","Object","prototype","toString","slice","toLowerCase","str_repeat","input","multiplier","Array","join","re","not_string","number","json","not_json","text","modulo","placeholder","key_access","index_access","sign","parse_tree","argv","arg","i","k","match","pad","pad_character","pad_length","cursor","tree_length","length","node_type","output","is_positive","Error","test","isNaN","TypeError","String","fromCharCode","parseInt","JSON","stringify","toExponential","parseFloat","toFixed","toPrecision","substring","toUpperCase","replace","charAt","fmt","_fmt","arg_names","exec","SyntaxError","field_list","replacement_field","field_match","vsprintf","_argv","splice","apply","exports","define","amd","this"],"mappings":";;CAAA,SAAUA,GAeN,QAASC,KACL,GAAIC,GAAMC,UAAU,GAAIC,EAAQH,EAAQG,KAIxC,OAHMA,GAAMF,IAAQE,EAAMC,eAAeH,KACrCE,EAAMF,GAAOD,EAAQK,MAAMJ,IAExBD,EAAQM,OAAOC,KAAK,KAAMJ,EAAMF,GAAMC,WA+JjD,QAASM,GAASC,GACd,MAAOC,QAAOC,UAAUC,SAASL,KAAKE,GAAUI,MAAM,EAAG,IAAIC,cAGjE,QAASC,GAAWC,EAAOC,GACvB,MAAOC,OAAMD,EAAa,GAAGE,KAAKH,GAvLtC,GAAII,IACAC,WAAY,OACZC,OAAQ,UACRC,KAAM,MACNC,SAAU,OACVC,KAAM,YACNC,OAAQ,WACRC,YAAa,yFACb1B,IAAK,sBACL2B,WAAY,wBACZC,aAAc,aACdC,KAAM,UAWV9B,GAAQM,OAAS,SAASyB,EAAYC,GAClC,GAAiEC,GAAkBC,EAAGC,EAAGC,EAAOC,EAAKC,EAAeC,EAAhHC,EAAS,EAAGC,EAAcV,EAAWW,OAAQC,EAAY,GAASC,KAA0DC,GAAc,EAAMf,EAAO,EAC3J,KAAKI,EAAI,EAAOO,EAAJP,EAAiBA,IAEzB,GADAS,EAAYnC,EAASuB,EAAWG,IACd,WAAdS,EACAC,EAAOA,EAAOF,QAAUX,EAAWG,OAElC,IAAkB,UAAdS,EAAuB,CAE5B,GADAP,EAAQL,EAAWG,GACfE,EAAM,GAEN,IADAH,EAAMD,EAAKQ,GACNL,EAAI,EAAGA,EAAIC,EAAM,GAAGM,OAAQP,IAAK,CAClC,IAAKF,EAAI7B,eAAegC,EAAM,GAAGD,IAC7B,KAAM,IAAIW,OAAM9C,EAAQ,yCAA0CoC,EAAM,GAAGD,IAE/EF,GAAMA,EAAIG,EAAM,GAAGD,QAIvBF,GADKG,EAAM,GACLJ,EAAKI,EAAM,IAGXJ,EAAKQ,IAOf,IAJqB,YAAjBhC,EAASyB,KACTA,EAAMA,KAGNb,EAAGC,WAAW0B,KAAKX,EAAM,KAAOhB,EAAGI,SAASuB,KAAKX,EAAM,KAAyB,UAAjB5B,EAASyB,IAAoBe,MAAMf,GAClG,KAAM,IAAIgB,WAAUjD,EAAQ,0CAA2CQ,EAASyB,IAOpF,QAJIb,EAAGE,OAAOyB,KAAKX,EAAM,MACrBS,EAAcZ,GAAO,GAGjBG,EAAM,IACV,IAAK,IACDH,EAAMA,EAAIrB,SAAS,EACvB,MACA,KAAK,IACDqB,EAAMiB,OAAOC,aAAalB,EAC9B,MACA,KAAK,IACL,IAAK,IACDA,EAAMmB,SAASnB,EAAK,GACxB,MACA,KAAK,IACDA,EAAMoB,KAAKC,UAAUrB,EAAK,KAAMG,EAAM,GAAKgB,SAAShB,EAAM,IAAM,EACpE,MACA,KAAK,IACDH,EAAMG,EAAM,GAAKH,EAAIsB,cAAcnB,EAAM,IAAMH,EAAIsB,eACvD,MACA,KAAK,IACDtB,EAAMG,EAAM,GAAKoB,WAAWvB,GAAKwB,QAAQrB,EAAM,IAAMoB,WAAWvB,EACpE,MACA,KAAK,IACDA,EAAMG,EAAM,GAAKoB,WAAWvB,GAAKyB,YAAYtB,EAAM,IAAMoB,WAAWvB,EACxE,MACA,KAAK,IACDA,EAAMA,EAAIrB,SAAS,EACvB,MACA,KAAK,IACDqB,GAAQA,EAAMiB,OAAOjB,KAASG,EAAM,GAAKH,EAAI0B,UAAU,EAAGvB,EAAM,IAAMH,CAC1E,MACA,KAAK,IACDA,KAAc,CAClB,MACA,KAAK,IACDA,EAAMA,EAAIrB,SAAS,GACvB,MACA,KAAK,IACDqB,EAAMA,EAAIrB,SAAS,IAAIgD,cAG3BxC,EAAGG,KAAKwB,KAAKX,EAAM,IACnBQ,EAAOA,EAAOF,QAAUT,IAGpBb,EAAGE,OAAOyB,KAAKX,EAAM,KAASS,IAAeT,EAAM,GAKnDN,EAAO,IAJPA,EAAOe,EAAc,IAAM,IAC3BZ,EAAMA,EAAIrB,WAAWiD,QAAQzC,EAAGU,KAAM,KAK1CQ,EAAgBF,EAAM,GAAkB,MAAbA,EAAM,GAAa,IAAMA,EAAM,GAAG0B,OAAO,GAAK,IACzEvB,EAAaH,EAAM,IAAMN,EAAOG,GAAKS,OACrCL,EAAMD,EAAM,IAAMG,EAAa,EAAIxB,EAAWuB,EAAeC,GAAoB,GACjFK,EAAOA,EAAOF,QAAUN,EAAM,GAAKN,EAAOG,EAAMI,EAAyB,MAAlBC,EAAwBR,EAAOO,EAAMJ,EAAMI,EAAMP,EAAOG,GAI3H,MAAOW,GAAOzB,KAAK,KAGvBnB,EAAQG,SAERH,EAAQK,MAAQ,SAAS0D,GAErB,IADA,GAAIC,GAAOD,EAAK3B,KAAYL,KAAiBkC,EAAY,EAClDD,GAAM,CACT,GAAqC,QAAhC5B,EAAQhB,EAAGK,KAAKyC,KAAKF,IACtBjC,EAAWA,EAAWW,QAAUN,EAAM,OAErC,IAAuC,QAAlCA,EAAQhB,EAAGM,OAAOwC,KAAKF,IAC7BjC,EAAWA,EAAWW,QAAU,QAE/B,CAAA,GAA4C,QAAvCN,EAAQhB,EAAGO,YAAYuC,KAAKF,IAgClC,KAAM,IAAIG,aAAY,mCA/BtB,IAAI/B,EAAM,GAAI,CACV6B,GAAa,CACb,IAAIG,MAAiBC,EAAoBjC,EAAM,GAAIkC,IACnD,IAAuD,QAAlDA,EAAclD,EAAGnB,IAAIiE,KAAKG,IAe3B,KAAM,IAAIF,aAAY,+CAbtB,KADAC,EAAWA,EAAW1B,QAAU4B,EAAY,GACwC,MAA5ED,EAAoBA,EAAkBV,UAAUW,EAAY,GAAG5B,UACnE,GAA8D,QAAzD4B,EAAclD,EAAGQ,WAAWsC,KAAKG,IAClCD,EAAWA,EAAW1B,QAAU4B,EAAY,OAE3C,CAAA,GAAgE,QAA3DA,EAAclD,EAAGS,aAAaqC,KAAKG,IAIzC,KAAM,IAAIF,aAAY,+CAHtBC,GAAWA,EAAW1B,QAAU4B,EAAY,GAUxDlC,EAAM,GAAKgC,MAGXH,IAAa,CAEjB,IAAkB,IAAdA,EACA,KAAM,IAAInB,OAAM,4EAEpBf,GAAWA,EAAWW,QAAUN,EAKpC4B,EAAOA,EAAKL,UAAUvB,EAAM,GAAGM,QAEnC,MAAOX,GAGX,IAAIwC,GAAW,SAASR,EAAK/B,EAAMwC,GAG/B,MAFAA,IAASxC,OAAYnB,MAAM,GAC3B2D,EAAMC,OAAO,EAAG,EAAGV,GACZ/D,EAAQ0E,MAAM,KAAMF,GAiBR,oBAAZG,UACPA,QAAQ3E,QAAUA,EAClB2E,QAAQJ,SAAWA,IAGnBxE,EAAOC,QAAUA,EACjBD,EAAOwE,SAAWA,EAEI,kBAAXK,SAAyBA,OAAOC,KACvCD,OAAO,WACH,OACI5E,QAASA,EACTuE,SAAUA,OAKT,mBAAXxE,QAAyB+E,KAAO/E"} diff --git a/public/assets/plugins/bootstrap-slider/bootstrap-slider.js b/public/assets/plugins/bootstrap-slider/bootstrap-slider.js new file mode 100644 index 00000000..2e072fe6 --- /dev/null +++ b/public/assets/plugins/bootstrap-slider/bootstrap-slider.js @@ -0,0 +1,1167 @@ +/*! ========================================================= + * bootstrap-slider.js + * + * Maintainers: + * Kyle Kemp + * - Twitter: @seiyria + * - Github: seiyria + * Rohit Kalkur + * - Twitter: @Rovolutionary + * - Github: rovolution + * + * ========================================================= + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ + + +/** + * Bridget makes jQuery widgets + * v1.0.1 + * MIT license + */ +( function( $ ) { + + ( function( $ ) { + + 'use strict'; + + // -------------------------- utils -------------------------- // + + var slice = Array.prototype.slice; + + function noop() {} + + // -------------------------- definition -------------------------- // + + function defineBridget( $ ) { + + // bail if no jQuery + if ( !$ ) { + return; + } + + // -------------------------- addOptionMethod -------------------------- // + + /** + * adds option method -> $().plugin('option', {...}) + * @param {Function} PluginClass - constructor class + */ + function addOptionMethod( PluginClass ) { + // don't overwrite original option method + if ( PluginClass.prototype.option ) { + return; + } + + // option setter + PluginClass.prototype.option = function( opts ) { + // bail out if not an object + if ( !$.isPlainObject( opts ) ){ + return; + } + this.options = $.extend( true, this.options, opts ); + }; + } + + + // -------------------------- plugin bridge -------------------------- // + + // helper function for logging errors + // $.error breaks jQuery chaining + var logError = typeof console === 'undefined' ? noop : + function( message ) { + console.error( message ); + }; + + /** + * jQuery plugin bridge, access methods like $elem.plugin('method') + * @param {String} namespace - plugin name + * @param {Function} PluginClass - constructor class + */ + function bridge( namespace, PluginClass ) { + // add to jQuery fn namespace + $.fn[ namespace ] = function( options ) { + if ( typeof options === 'string' ) { + // call plugin method when first argument is a string + // get arguments for method + var args = slice.call( arguments, 1 ); + + for ( var i=0, len = this.length; i < len; i++ ) { + var elem = this[i]; + var instance = $.data( elem, namespace ); + if ( !instance ) { + logError( "cannot call methods on " + namespace + " prior to initialization; " + + "attempted to call '" + options + "'" ); + continue; + } + if ( !$.isFunction( instance[options] ) || options.charAt(0) === '_' ) { + logError( "no such method '" + options + "' for " + namespace + " instance" ); + continue; + } + + // trigger method with arguments + var returnValue = instance[ options ].apply( instance, args); + + // break look and return first value if provided + if ( returnValue !== undefined && returnValue !== instance) { + return returnValue; + } + } + // return this if no return value + return this; + } else { + var objects = this.map( function() { + var instance = $.data( this, namespace ); + if ( instance ) { + // apply options & init + instance.option( options ); + instance._init(); + } else { + // initialize new instance + instance = new PluginClass( this, options ); + $.data( this, namespace, instance ); + } + return $(this); + }); + + if(!objects || objects.length > 1) { + return objects; + } else { + return objects[0]; + } + } + }; + + } + + // -------------------------- bridget -------------------------- // + + /** + * converts a Prototypical class into a proper jQuery plugin + * the class must have a ._init method + * @param {String} namespace - plugin name, used in $().pluginName + * @param {Function} PluginClass - constructor class + */ + $.bridget = function( namespace, PluginClass ) { + addOptionMethod( PluginClass ); + bridge( namespace, PluginClass ); + }; + + return $.bridget; + + } + + // get jquery from browser global + defineBridget( $ ); + + })( $ ); + + + /************************************************* + + BOOTSTRAP-SLIDER SOURCE CODE + + **************************************************/ + + (function( $ ) { + + var ErrorMsgs = { + formatInvalidInputErrorMsg : function(input) { + return "Invalid input value '" + input + "' passed in"; + }, + callingContextNotSliderInstance : "Calling context element does not have instance of Slider bound to it. Check your code to make sure the JQuery object returned from the call to the slider() initializer is calling the method" + }; + + + + /************************************************* + + CONSTRUCTOR + + **************************************************/ + var Slider = function(element, options) { + createNewSlider.call(this, element, options); + return this; + }; + + function createNewSlider(element, options) { + /************************************************* + + Create Markup + + **************************************************/ + if(typeof element === "string") { + this.element = document.querySelector(element); + } else if(element instanceof HTMLElement) { + this.element = element; + } + + var origWidth = this.element.style.width; + var updateSlider = false; + var parent = this.element.parentNode; + var sliderTrackSelection; + var sliderMinHandle; + var sliderMaxHandle; + + if (this.sliderElem) { + updateSlider = true; + } else { + /* Create elements needed for slider */ + this.sliderElem = document.createElement("div"); + this.sliderElem.className = "slider"; + + /* Create slider track elements */ + var sliderTrack = document.createElement("div"); + sliderTrack.className = "slider-track"; + + sliderTrackSelection = document.createElement("div"); + sliderTrackSelection.className = "slider-selection"; + + sliderMinHandle = document.createElement("div"); + sliderMinHandle.className = "slider-handle min-slider-handle"; + + sliderMaxHandle = document.createElement("div"); + sliderMaxHandle.className = "slider-handle max-slider-handle"; + + sliderTrack.appendChild(sliderTrackSelection); + sliderTrack.appendChild(sliderMinHandle); + sliderTrack.appendChild(sliderMaxHandle); + + var createAndAppendTooltipSubElements = function(tooltipElem) { + var arrow = document.createElement("div"); + arrow.className = "tooltip-arrow"; + + var inner = document.createElement("div"); + inner.className = "tooltip-inner"; + + tooltipElem.appendChild(arrow); + tooltipElem.appendChild(inner); + }; + + /* Create tooltip elements */ + var sliderTooltip = document.createElement("div"); + sliderTooltip.className = "tooltip tooltip-main"; + createAndAppendTooltipSubElements(sliderTooltip); + + var sliderTooltipMin = document.createElement("div"); + sliderTooltipMin.className = "tooltip tooltip-min"; + createAndAppendTooltipSubElements(sliderTooltipMin); + + var sliderTooltipMax = document.createElement("div"); + sliderTooltipMax.className = "tooltip tooltip-max"; + createAndAppendTooltipSubElements(sliderTooltipMax); + + + /* Append components to sliderElem */ + this.sliderElem.appendChild(sliderTrack); + this.sliderElem.appendChild(sliderTooltip); + this.sliderElem.appendChild(sliderTooltipMin); + this.sliderElem.appendChild(sliderTooltipMax); + + /* Append slider element to parent container, right before the original element */ + parent.insertBefore(this.sliderElem, this.element); + + /* Hide original element */ + this.element.style.display = "none"; + } + /* If JQuery exists, cache JQ references */ + if($) { + this.$element = $(this.element); + this.$sliderElem = $(this.sliderElem); + } + + /************************************************* + + Process Options + + **************************************************/ + options = options ? options : {}; + var optionTypes = Object.keys(this.defaultOptions); + + for(var i = 0; i < optionTypes.length; i++) { + var optName = optionTypes[i]; + + // First check if an option was passed in via the constructor + var val = options[optName]; + // If no data attrib, then check data atrributes + val = (typeof val !== 'undefined') ? val : getDataAttrib(this.element, optName); + // Finally, if nothing was specified, use the defaults + val = (val !== null) ? val : this.defaultOptions[optName]; + + // Set all options on the instance of the Slider + if(!this.options) { + this.options = {}; + } + this.options[optName] = val; + } + + function getDataAttrib(element, optName) { + var dataName = "data-slider-" + optName; + var dataValString = element.getAttribute(dataName); + + try { + return JSON.parse(dataValString); + } + catch(err) { + return dataValString; + } + } + + /************************************************* + + Setup + + **************************************************/ + this.eventToCallbackMap = {}; + this.sliderElem.id = this.options.id; + + this.touchCapable = 'ontouchstart' in window || (window.DocumentTouch && document instanceof window.DocumentTouch); + + this.tooltip = this.sliderElem.querySelector('.tooltip-main'); + this.tooltipInner = this.tooltip.querySelector('.tooltip-inner'); + + this.tooltip_min = this.sliderElem.querySelector('.tooltip-min'); + this.tooltipInner_min = this.tooltip_min.querySelector('.tooltip-inner'); + + this.tooltip_max = this.sliderElem.querySelector('.tooltip-max'); + this.tooltipInner_max= this.tooltip_max.querySelector('.tooltip-inner'); + + if (updateSlider === true) { + // Reset classes + this._removeClass(this.sliderElem, 'slider-horizontal'); + this._removeClass(this.sliderElem, 'slider-vertical'); + this._removeClass(this.tooltip, 'hide'); + this._removeClass(this.tooltip_min, 'hide'); + this._removeClass(this.tooltip_max, 'hide'); + + // Undo existing inline styles for track + ["left", "top", "width", "height"].forEach(function(prop) { + this._removeProperty(this.trackSelection, prop); + }, this); + + // Undo inline styles on handles + [this.handle1, this.handle2].forEach(function(handle) { + this._removeProperty(handle, 'left'); + this._removeProperty(handle, 'top'); + }, this); + + // Undo inline styles and classes on tooltips + [this.tooltip, this.tooltip_min, this.tooltip_max].forEach(function(tooltip) { + this._removeProperty(tooltip, 'left'); + this._removeProperty(tooltip, 'top'); + this._removeProperty(tooltip, 'margin-left'); + this._removeProperty(tooltip, 'margin-top'); + + this._removeClass(tooltip, 'right'); + this._removeClass(tooltip, 'top'); + }, this); + } + + if(this.options.orientation === 'vertical') { + this._addClass(this.sliderElem,'slider-vertical'); + + this.stylePos = 'top'; + this.mousePos = 'pageY'; + this.sizePos = 'offsetHeight'; + + this._addClass(this.tooltip, 'right'); + this.tooltip.style.left = '100%'; + + this._addClass(this.tooltip_min, 'right'); + this.tooltip_min.style.left = '100%'; + + this._addClass(this.tooltip_max, 'right'); + this.tooltip_max.style.left = '100%'; + } else { + this._addClass(this.sliderElem, 'slider-horizontal'); + this.sliderElem.style.width = origWidth; + + this.options.orientation = 'horizontal'; + this.stylePos = 'left'; + this.mousePos = 'pageX'; + this.sizePos = 'offsetWidth'; + + this._addClass(this.tooltip, 'top'); + this.tooltip.style.top = -this.tooltip.outerHeight - 14 + 'px'; + + this._addClass(this.tooltip_min, 'top'); + this.tooltip_min.style.top = -this.tooltip_min.outerHeight - 14 + 'px'; + + this._addClass(this.tooltip_max, 'top'); + this.tooltip_max.style.top = -this.tooltip_max.outerHeight - 14 + 'px'; + } + + if (this.options.value instanceof Array) { + this.options.range = true; + } else if (this.options.range) { + // User wants a range, but value is not an array + this.options.value = [this.options.value, this.options.max]; + } + + this.trackSelection = sliderTrackSelection || this.trackSelection; + if (this.options.selection === 'none') { + this._addClass(this.trackSelection, 'hide'); + } + + this.handle1 = sliderMinHandle || this.handle1; + this.handle2 = sliderMaxHandle || this.handle2; + + if (updateSlider === true) { + // Reset classes + this._removeClass(this.handle1, 'round triangle'); + this._removeClass(this.handle2, 'round triangle hide'); + } + + var availableHandleModifiers = ['round', 'triangle', 'custom']; + var isValidHandleType = availableHandleModifiers.indexOf(this.options.handle) !== -1; + if (isValidHandleType) { + this._addClass(this.handle1, this.options.handle); + this._addClass(this.handle2, this.options.handle); + } + + this.offset = this._offset(this.sliderElem); + this.size = this.sliderElem[this.sizePos]; + this.setValue(this.options.value); + + /****************************************** + + Bind Event Listeners + + ******************************************/ + + // Bind keyboard handlers + this.handle1Keydown = this._keydown.bind(this, 0); + this.handle1.addEventListener("keydown", this.handle1Keydown, false); + + this.handle2Keydown = this._keydown.bind(this, 0); + this.handle2.addEventListener("keydown", this.handle2Keydown, false); + + if (this.touchCapable) { + // Bind touch handlers + this.mousedown = this._mousedown.bind(this); + this.sliderElem.addEventListener("touchstart", this.mousedown, false); + } else { + // Bind mouse handlers + this.mousedown = this._mousedown.bind(this); + this.sliderElem.addEventListener("mousedown", this.mousedown, false); + } + + // Bind tooltip-related handlers + if(this.options.tooltip === 'hide') { + this._addClass(this.tooltip, 'hide'); + this._addClass(this.tooltip_min, 'hide'); + this._addClass(this.tooltip_max, 'hide'); + } else if(this.options.tooltip === 'always') { + this._showTooltip(); + this._alwaysShowTooltip = true; + } else { + this.showTooltip = this._showTooltip.bind(this); + this.hideTooltip = this._hideTooltip.bind(this); + + this.sliderElem.addEventListener("mouseenter", this.showTooltip, false); + this.sliderElem.addEventListener("mouseleave", this.hideTooltip, false); + + this.handle1.addEventListener("focus", this.showTooltip, false); + this.handle1.addEventListener("blur", this.hideTooltip, false); + + this.handle2.addEventListener("focus", this.showTooltip, false); + this.handle2.addEventListener("blur", this.hideTooltip, false); + } + + if(this.options.enabled) { + this.enable(); + } else { + this.disable(); + } + } + + /************************************************* + + INSTANCE PROPERTIES/METHODS + + - Any methods bound to the prototype are considered + part of the plugin's `public` interface + + **************************************************/ + Slider.prototype = { + _init: function() {}, // NOTE: Must exist to support bridget + + constructor: Slider, + + defaultOptions: { + id: "", + min: 0, + max: 10, + step: 1, + precision: 0, + orientation: 'horizontal', + value: 5, + range: false, + selection: 'before', + tooltip: 'show', + tooltip_split: false, + handle: 'round', + reversed: false, + enabled: true, + formatter: function(val) { + if(val instanceof Array) { + return val[0] + " : " + val[1]; + } else { + return val; + } + }, + natural_arrow_keys: false + }, + + over: false, + + inDrag: false, + + getValue: function() { + if (this.options.range) { + return this.options.value; + } + return this.options.value[0]; + }, + + setValue: function(val, triggerSlideEvent) { + if (!val) { + val = 0; + } + this.options.value = this._validateInputValue(val); + var applyPrecision = this._applyPrecision.bind(this); + + if (this.options.range) { + this.options.value[0] = applyPrecision(this.options.value[0]); + this.options.value[1] = applyPrecision(this.options.value[1]); + + this.options.value[0] = Math.max(this.options.min, Math.min(this.options.max, this.options.value[0])); + this.options.value[1] = Math.max(this.options.min, Math.min(this.options.max, this.options.value[1])); + } else { + this.options.value = applyPrecision(this.options.value); + this.options.value = [ Math.max(this.options.min, Math.min(this.options.max, this.options.value))]; + this._addClass(this.handle2, 'hide'); + if (this.options.selection === 'after') { + this.options.value[1] = this.options.max; + } else { + this.options.value[1] = this.options.min; + } + } + + this.diff = this.options.max - this.options.min; + if (this.diff > 0) { + this.percentage = [ + (this.options.value[0] - this.options.min) * 100 / this.diff, + (this.options.value[1] - this.options.min) * 100 / this.diff, + this.options.step * 100 / this.diff + ]; + } else { + this.percentage = [0, 0, 100]; + } + + this._layout(); + + var sliderValue = this.options.range ? this.options.value : this.options.value[0]; + this._setDataVal(sliderValue); + + if(triggerSlideEvent === true) { + this._trigger('slide', sliderValue); + } + + return this; + }, + + destroy: function(){ + // Remove event handlers on slider elements + this._removeSliderEventHandlers(); + + // Remove the slider from the DOM + this.sliderElem.parentNode.removeChild(this.sliderElem); + /* Show original element */ + this.element.style.display = ""; + + // Clear out custom event bindings + this._cleanUpEventCallbacksMap(); + + // Remove data values + this.element.removeAttribute("data"); + + // Remove JQuery handlers/data + if($) { + this._unbindJQueryEventHandlers(); + this.$element.removeData('slider'); + } + }, + + disable: function() { + this.options.enabled = false; + this.handle1.removeAttribute("tabindex"); + this.handle2.removeAttribute("tabindex"); + this._addClass(this.sliderElem, 'slider-disabled'); + this._trigger('slideDisabled'); + + return this; + }, + + enable: function() { + this.options.enabled = true; + this.handle1.setAttribute("tabindex", 0); + this.handle2.setAttribute("tabindex", 0); + this._removeClass(this.sliderElem, 'slider-disabled'); + this._trigger('slideEnabled'); + + return this; + }, + + toggle: function() { + if(this.options.enabled) { + this.disable(); + } else { + this.enable(); + } + + return this; + }, + + isEnabled: function() { + return this.options.enabled; + }, + + on: function(evt, callback) { + if($) { + this.$element.on(evt, callback); + this.$sliderElem.on(evt, callback); + } else { + this._bindNonQueryEventHandler(evt, callback); + } + return this; + }, + + getAttribute: function(attribute) { + if(attribute) { + return this.options[attribute]; + } else { + return this.options; + } + }, + + setAttribute: function(attribute, value) { + this.options[attribute] = value; + return this; + }, + + refresh: function() { + this._removeSliderEventHandlers(); + createNewSlider.call(this, this.element, this.options); + if($) { + // Bind new instance of slider to the element + $.data(this.element, 'slider', this); + } + return this; + }, + + /******************************+ + + HELPERS + + - Any method that is not part of the public interface. + - Place it underneath this comment block and write its signature like so: + + _fnName : function() {...} + + ********************************/ + _removeSliderEventHandlers: function() { + // Remove event listeners from handle1 + this.handle1.removeEventListener("keydown", this.handle1Keydown, false); + this.handle1.removeEventListener("focus", this.showTooltip, false); + this.handle1.removeEventListener("blur", this.hideTooltip, false); + + // Remove event listeners from handle2 + this.handle2.removeEventListener("keydown", this.handle2Keydown, false); + this.handle2.removeEventListener("focus", this.handle2Keydown, false); + this.handle2.removeEventListener("blur", this.handle2Keydown, false); + + // Remove event listeners from sliderElem + this.sliderElem.removeEventListener("mouseenter", this.showTooltip, false); + this.sliderElem.removeEventListener("mouseleave", this.hideTooltip, false); + this.sliderElem.removeEventListener("touchstart", this.mousedown, false); + this.sliderElem.removeEventListener("mousedown", this.mousedown, false); + }, + _bindNonQueryEventHandler: function(evt, callback) { + if(this.eventToCallbackMap[evt]===undefined) { + this.eventToCallbackMap[evt] = []; + } + this.eventToCallbackMap[evt].push(callback); + }, + _cleanUpEventCallbacksMap: function() { + var eventNames = Object.keys(this.eventToCallbackMap); + for(var i = 0; i < eventNames.length; i++) { + var eventName = eventNames[i]; + this.eventToCallbackMap[eventName] = null; + } + }, + _showTooltip: function() { + if (this.options.tooltip_split === false ){ + this._addClass(this.tooltip, 'in'); + } else { + this._addClass(this.tooltip_min, 'in'); + this._addClass(this.tooltip_max, 'in'); + } + this.over = true; + }, + _hideTooltip: function() { + if (this.inDrag === false && this.alwaysShowTooltip !== true) { + this._removeClass(this.tooltip, 'in'); + this._removeClass(this.tooltip_min, 'in'); + this._removeClass(this.tooltip_max, 'in'); + } + this.over = false; + }, + _layout: function() { + var positionPercentages; + + if(this.options.reversed) { + positionPercentages = [ 100 - this.percentage[0], this.percentage[1] ]; + } else { + positionPercentages = [ this.percentage[0], this.percentage[1] ]; + } + + this.handle1.style[this.stylePos] = positionPercentages[0]+'%'; + this.handle2.style[this.stylePos] = positionPercentages[1]+'%'; + + if (this.options.orientation === 'vertical') { + this.trackSelection.style.top = Math.min(positionPercentages[0], positionPercentages[1]) +'%'; + this.trackSelection.style.height = Math.abs(positionPercentages[0] - positionPercentages[1]) +'%'; + } else { + this.trackSelection.style.left = Math.min(positionPercentages[0], positionPercentages[1]) +'%'; + this.trackSelection.style.width = Math.abs(positionPercentages[0] - positionPercentages[1]) +'%'; + + var offset_min = this.tooltip_min.getBoundingClientRect(); + var offset_max = this.tooltip_max.getBoundingClientRect(); + + if (offset_min.right > offset_max.left) { + this._removeClass(this.tooltip_max, 'top'); + this._addClass(this.tooltip_max, 'bottom'); + this.tooltip_max.style.top = 18 + 'px'; + } else { + this._removeClass(this.tooltip_max, 'bottom'); + this._addClass(this.tooltip_max, 'top'); + this.tooltip_max.style.top = -30 + 'px'; + } + } + + + var formattedTooltipVal; + + if (this.options.range) { + formattedTooltipVal = this.options.formatter(this.options.value); + this._setText(this.tooltipInner, formattedTooltipVal); + this.tooltip.style[this.stylePos] = (positionPercentages[1] + positionPercentages[0])/2 + '%'; + + if (this.options.orientation === 'vertical') { + this._css(this.tooltip, 'margin-top', -this.tooltip.offsetHeight / 2 + 'px'); + } else { + this._css(this.tooltip, 'margin-left', -this.tooltip.offsetWidth / 2 + 'px'); + } + + if (this.options.orientation === 'vertical') { + this._css(this.tooltip, 'margin-top', -this.tooltip.offsetHeight / 2 + 'px'); + } else { + this._css(this.tooltip, 'margin-left', -this.tooltip.offsetWidth / 2 + 'px'); + } + + var innerTooltipMinText = this.options.formatter(this.options.value[0]); + this._setText(this.tooltipInner_min, innerTooltipMinText); + + var innerTooltipMaxText = this.options.formatter(this.options.value[1]); + this._setText(this.tooltipInner_max, innerTooltipMaxText); + + this.tooltip_min.style[this.stylePos] = positionPercentages[0] + '%'; + + if (this.options.orientation === 'vertical') { + this._css(this.tooltip_min, 'margin-top', -this.tooltip_min.offsetHeight / 2 + 'px'); + } else { + this._css(this.tooltip_min, 'margin-left', -this.tooltip_min.offsetWidth / 2 + 'px'); + } + + this.tooltip_max.style[this.stylePos] = positionPercentages[1] + '%'; + + if (this.options.orientation === 'vertical') { + this._css(this.tooltip_max, 'margin-top', -this.tooltip_max.offsetHeight / 2 + 'px'); + } else { + this._css(this.tooltip_max, 'margin-left', -this.tooltip_max.offsetWidth / 2 + 'px'); + } + } else { + formattedTooltipVal = this.options.formatter(this.options.value[0]); + this._setText(this.tooltipInner, formattedTooltipVal); + + this.tooltip.style[this.stylePos] = positionPercentages[0] + '%'; + if (this.options.orientation === 'vertical') { + this._css(this.tooltip, 'margin-top', -this.tooltip.offsetHeight / 2 + 'px'); + } else { + this._css(this.tooltip, 'margin-left', -this.tooltip.offsetWidth / 2 + 'px'); + } + } + }, + _removeProperty: function(element, prop) { + if (element.style.removeProperty) { + element.style.removeProperty(prop); + } else { + element.style.removeAttribute(prop); + } + }, + _mousedown: function(ev) { + if(!this.options.enabled) { + return false; + } + + this._triggerFocusOnHandle(); + + this.offset = this._offset(this.sliderElem); + this.size = this.sliderElem[this.sizePos]; + + var percentage = this._getPercentage(ev); + + if (this.options.range) { + var diff1 = Math.abs(this.percentage[0] - percentage); + var diff2 = Math.abs(this.percentage[1] - percentage); + this.dragged = (diff1 < diff2) ? 0 : 1; + } else { + this.dragged = 0; + } + + this.percentage[this.dragged] = this.options.reversed ? 100 - percentage : percentage; + this._layout(); + + this.mousemove = this._mousemove.bind(this); + this.mouseup = this._mouseup.bind(this); + + if (this.touchCapable) { + // Touch: Bind touch events: + document.addEventListener("touchmove", this.mousemove, false); + document.addEventListener("touchend", this.mouseup, false); + } else { + // Bind mouse events: + document.addEventListener("mousemove", this.mousemove, false); + document.addEventListener("mouseup", this.mouseup, false); + } + + this.inDrag = true; + + var val = this._calculateValue(); + this._trigger('slideStart', val); + this._setDataVal(val); + this.setValue(val); + + this._pauseEvent(ev); + + return true; + }, + _triggerFocusOnHandle: function(handleIdx) { + if(handleIdx === 0) { + this.handle1.focus(); + } + if(handleIdx === 1) { + this.handle2.focus(); + } + }, + _keydown: function(handleIdx, ev) { + if(!this.options.enabled) { + return false; + } + + var dir; + switch (ev.keyCode) { + case 37: // left + case 40: // down + dir = -1; + break; + case 39: // right + case 38: // up + dir = 1; + break; + } + if (!dir) { + return; + } + + // use natural arrow keys instead of from min to max + if (this.options.natural_arrow_keys) { + var ifVerticalAndNotReversed = (this.options.orientation === 'vertical' && !this.options.reversed); + var ifHorizontalAndReversed = (this.options.orientation === 'horizontal' && this.options.reversed); + + if (ifVerticalAndNotReversed || ifHorizontalAndReversed) { + dir = dir * -1; + } + } + + var oneStepValuePercentageChange = dir * this.percentage[2]; + var percentage = this.percentage[handleIdx] + oneStepValuePercentageChange; + + if (percentage > 100) { + percentage = 100; + } else if (percentage < 0) { + percentage = 0; + } + + this.dragged = handleIdx; + this._adjustPercentageForRangeSliders(percentage); + this.percentage[this.dragged] = percentage; + this._layout(); + + var val = this._calculateValue(); + + this._trigger('slideStart', val); + this._setDataVal(val); + this.setValue(val, true); + + this._trigger('slideStop', val); + this._setDataVal(val); + + this._pauseEvent(ev); + + return false; + }, + _pauseEvent: function(ev) { + if(ev.stopPropagation) { + ev.stopPropagation(); + } + if(ev.preventDefault) { + ev.preventDefault(); + } + ev.cancelBubble=true; + ev.returnValue=false; + }, + _mousemove: function(ev) { + if(!this.options.enabled) { + return false; + } + + var percentage = this._getPercentage(ev); + this._adjustPercentageForRangeSliders(percentage); + this.percentage[this.dragged] = this.options.reversed ? 100 - percentage : percentage; + this._layout(); + + var val = this._calculateValue(); + this.setValue(val, true); + + return false; + }, + _adjustPercentageForRangeSliders: function(percentage) { + if (this.options.range) { + if (this.dragged === 0 && this.percentage[1] < percentage) { + this.percentage[0] = this.percentage[1]; + this.dragged = 1; + } else if (this.dragged === 1 && this.percentage[0] > percentage) { + this.percentage[1] = this.percentage[0]; + this.dragged = 0; + } + } + }, + _mouseup: function() { + if(!this.options.enabled) { + return false; + } + if (this.touchCapable) { + // Touch: Unbind touch event handlers: + document.removeEventListener("touchmove", this.mousemove, false); + document.removeEventListener("touchend", this.mouseup, false); + } else { + // Unbind mouse event handlers: + document.removeEventListener("mousemove", this.mousemove, false); + document.removeEventListener("mouseup", this.mouseup, false); + } + + this.inDrag = false; + if (this.over === false) { + this._hideTooltip(); + } + var val = this._calculateValue(); + + this._layout(); + this._setDataVal(val); + this._trigger('slideStop', val); + + return false; + }, + _calculateValue: function() { + var val; + if (this.options.range) { + val = [this.options.min,this.options.max]; + if (this.percentage[0] !== 0){ + val[0] = (Math.max(this.options.min, this.options.min + Math.round((this.diff * this.percentage[0]/100)/this.options.step)*this.options.step)); + val[0] = this._applyPrecision(val[0]); + } + if (this.percentage[1] !== 100){ + val[1] = (Math.min(this.options.max, this.options.min + Math.round((this.diff * this.percentage[1]/100)/this.options.step)*this.options.step)); + val[1] = this._applyPrecision(val[1]); + } + this.options.value = val; + } else { + val = (this.options.min + Math.round((this.diff * this.percentage[0]/100)/this.options.step)*this.options.step); + if (val < this.options.min) { + val = this.options.min; + } + else if (val > this.options.max) { + val = this.options.max; + } + val = parseFloat(val); + val = this._applyPrecision(val); + this.options.value = [val, this.options.value[1]]; + } + return val; + }, + _applyPrecision: function(val) { + var precision = this.options.precision || this._getNumDigitsAfterDecimalPlace(this.step); + return this._applyToFixedAndParseFloat(val, precision); + }, + _getNumDigitsAfterDecimalPlace: function(num) { + var match = (''+num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); + if (!match) { return 0; } + return Math.max(0, (match[1] ? match[1].length : 0) - (match[2] ? +match[2] : 0)); + }, + _applyToFixedAndParseFloat: function(num, toFixedInput) { + var truncatedNum = num.toFixed(toFixedInput); + return parseFloat(truncatedNum); + }, + /* + Credits to Mike Samuel for the following method! + Source: http://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number + */ + _getPercentage: function(ev) { + if (this.touchCapable && (ev.type === 'touchstart' || ev.type === 'touchmove')) { + ev = ev.touches[0]; + } + var percentage = (ev[this.mousePos] - this.offset[this.stylePos])*100/this.size; + percentage = Math.round(percentage/this.percentage[2])*this.percentage[2]; + return Math.max(0, Math.min(100, percentage)); + }, + _validateInputValue: function(val) { + if(typeof val === 'number') { + return val; + } else if(val instanceof Array) { + this._validateArray(val); + return val; + } else { + throw new Error( ErrorMsgs.formatInvalidInputErrorMsg(val) ); + } + }, + _validateArray: function(val) { + for(var i = 0; i < val.length; i++) { + var input = val[i]; + if (typeof input !== 'number') { throw new Error( ErrorMsgs.formatInvalidInputErrorMsg(input) ); } + } + }, + _setDataVal: function(val) { + var value = "value: '" + val + "'"; + this.element.setAttribute('data', value); + this.element.setAttribute('value', val); + }, + _trigger: function(evt, val) { + val = val || undefined; + + var callbackFnArray = this.eventToCallbackMap[evt]; + if(callbackFnArray && callbackFnArray.length) { + for(var i = 0; i < callbackFnArray.length; i++) { + var callbackFn = callbackFnArray[i]; + callbackFn(val); + } + } + + /* If JQuery exists, trigger JQuery events */ + if($) { + this._triggerJQueryEvent(evt, val); + } + }, + _triggerJQueryEvent: function(evt, val) { + var eventData = { + type: evt, + value: val + }; + this.$element.trigger(eventData); + this.$sliderElem.trigger(eventData); + }, + _unbindJQueryEventHandlers: function() { + this.$element.off(); + this.$sliderElem.off(); + }, + _setText: function(element, text) { + if(typeof element.innerText !== "undefined") { + element.innerText = text; + } else if(typeof element.textContent !== "undefined") { + element.textContent = text; + } + }, + _removeClass: function(element, classString) { + var classes = classString.split(" "); + var newClasses = element.className; + + for(var i = 0; i < classes.length; i++) { + var classTag = classes[i]; + var regex = new RegExp("(?:\\s|^)" + classTag + "(?:\\s|$)"); + newClasses = newClasses.replace(regex, " "); + } + + element.className = newClasses.trim(); + }, + _addClass: function(element, classString) { + var classes = classString.split(" "); + var newClasses = element.className; + + for(var i = 0; i < classes.length; i++) { + var classTag = classes[i]; + var regex = new RegExp("(?:\\s|^)" + classTag + "(?:\\s|$)"); + var ifClassExists = regex.test(newClasses); + + if(!ifClassExists) { + newClasses += " " + classTag; + } + } + + element.className = newClasses.trim(); + }, + _offset: function (obj) { + var ol = 0; + var ot = 0; + if (obj.offsetParent) { + do { + ol += obj.offsetLeft; + ot += obj.offsetTop; + } while (obj = obj.offsetParent); + } + return { + left: ol, + top: ot + }; + }, + _css: function(elementRef, styleName, value) { + elementRef.style[styleName] = value; + } + }; + + /********************************* + + Attach to global namespace + + *********************************/ + if($) { + var namespace = $.fn.slider ? 'bootstrapSlider' : 'slider'; + $.bridget(namespace, Slider); + } else { + window.Slider = Slider; + } + + + })( $ ); + +})( window.jQuery ); \ No newline at end of file diff --git a/public/assets/plugins/bootstrap-slider/slider.css b/public/assets/plugins/bootstrap-slider/slider.css new file mode 100644 index 00000000..a96db7fb --- /dev/null +++ b/public/assets/plugins/bootstrap-slider/slider.css @@ -0,0 +1,169 @@ +/*! + * Slider for Bootstrap + * + * Copyright 2012 Stefan Petre + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + */ +.slider { + display: block; + vertical-align: middle; + position: relative; + +} +.slider.slider-horizontal { + width: 100%; + height: 20px; + margin-bottom: 20px; +} +.slider.slider-horizontal:last-of-type { + margin-bottom: 0; +} +.slider.slider-horizontal .slider-track { + height: 10px; + width: 100%; + margin-top: -5px; + top: 50%; + left: 0; +} +.slider.slider-horizontal .slider-selection { + height: 100%; + top: 0; + bottom: 0; +} +.slider.slider-horizontal .slider-handle { + margin-left: -10px; + margin-top: -5px; +} +.slider.slider-horizontal .slider-handle.triangle { + border-width: 0 10px 10px 10px; + width: 0; + height: 0; + border-bottom-color: #0480be; + margin-top: 0; +} +.slider.slider-vertical { + height: 230px; + width: 20px; + margin-right: 20px; + display: inline-block; +} +.slider.slider-vertical:last-of-type { + margin-right: 0; +} +.slider.slider-vertical .slider-track { + width: 10px; + height: 100%; + margin-left: -5px; + left: 50%; + top: 0; +} +.slider.slider-vertical .slider-selection { + width: 100%; + left: 0; + top: 0; + bottom: 0; +} +.slider.slider-vertical .slider-handle { + margin-left: -5px; + margin-top: -10px; +} +.slider.slider-vertical .slider-handle.triangle { + border-width: 10px 0 10px 10px; + width: 1px; + height: 1px; + border-left-color: #0480be; + margin-left: 0; +} +.slider input { + display: none; +} +.slider .tooltip-inner { + white-space: nowrap; +} +.slider-track { + position: absolute; + cursor: pointer; + background-color: #f7f7f7; + background-image: -moz-linear-gradient(top, #f0f0f0, #f9f9f9); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f0f0f0), to(#f9f9f9)); + background-image: -webkit-linear-gradient(top, #f0f0f0, #f9f9f9); + background-image: -o-linear-gradient(top, #f0f0f0, #f9f9f9); + background-image: linear-gradient(to bottom, #f0f0f0, #f9f9f9); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0f0f0', endColorstr='#fff9f9f9', GradientType=0); + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.slider-selection { + position: absolute; + background-color: #f7f7f7; + background-image: -moz-linear-gradient(top, #f9f9f9, #f5f5f5); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f9f9f9), to(#f5f5f5)); + background-image: -webkit-linear-gradient(top, #f9f9f9, #f5f5f5); + background-image: -o-linear-gradient(top, #f9f9f9, #f5f5f5); + background-image: linear-gradient(to bottom, #f9f9f9, #f5f5f5); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#fff5f5f5', GradientType=0); + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.slider-handle { + position: absolute; + width: 20px; + height: 20px; + background-color: #444; + -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); + -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); + box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); + opacity: 1; + border: 0px solid transparent; +} +.slider-handle.round { + -webkit-border-radius: 20px; + -moz-border-radius: 20px; + border-radius: 20px; +} +.slider-handle.triangle { + background: transparent none; +} + +.slider-disabled .slider-selection { + opacity: 0.5; +} + +#red .slider-selection { + background: #f56954; +} + +#blue .slider-selection { + background: #3c8dbc; +} + +#green .slider-selection { + background: #00a65a; +} + +#yellow .slider-selection { + background: #f39c12; +} + +#aqua .slider-selection { + background: #00c0ef; +} + +#purple .slider-selection { + background: #932ab6; +} \ No newline at end of file diff --git a/public/assets/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.js b/public/assets/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.js new file mode 100644 index 00000000..acccf91e --- /dev/null +++ b/public/assets/plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.js @@ -0,0 +1,14975 @@ +// TODO: in future try to replace most inline compability checks with polyfills for code readability + +// element.textContent polyfill. +// Unsupporting browsers: IE8 + +if (Object.defineProperty && Object.getOwnPropertyDescriptor && Object.getOwnPropertyDescriptor(Element.prototype, "textContent") && !Object.getOwnPropertyDescriptor(Element.prototype, "textContent").get) { + (function() { + var innerText = Object.getOwnPropertyDescriptor(Element.prototype, "innerText"); + Object.defineProperty(Element.prototype, "textContent", + { + get: function() { + return innerText.get.call(this); + }, + set: function(s) { + return innerText.set.call(this, s); + } + } + ); + })(); +} + +// isArray polyfill for ie8 +if(!Array.isArray) { + Array.isArray = function(arg) { + return Object.prototype.toString.call(arg) === '[object Array]'; + }; +};/** + * @license wysihtml5x v0.4.15 + * https://github.com/Edicy/wysihtml5 + * + * Author: Christopher Blum (https://github.com/tiff) + * Secondary author of extended features: Oliver Pulges (https://github.com/pulges) + * + * Copyright (C) 2012 XING AG + * Licensed under the MIT license (MIT) + * + */ +var wysihtml5 = { + version: "0.4.15", + + // namespaces + commands: {}, + dom: {}, + quirks: {}, + toolbar: {}, + lang: {}, + selection: {}, + views: {}, + + INVISIBLE_SPACE: "\uFEFF", + + EMPTY_FUNCTION: function() {}, + + ELEMENT_NODE: 1, + TEXT_NODE: 3, + + BACKSPACE_KEY: 8, + ENTER_KEY: 13, + ESCAPE_KEY: 27, + SPACE_KEY: 32, + DELETE_KEY: 46 +}; +;/** + * Rangy, a cross-browser JavaScript range and selection library + * http://code.google.com/p/rangy/ + * + * Copyright 2014, Tim Down + * Licensed under the MIT license. + * Version: 1.3alpha.20140804 + * Build date: 4 August 2014 + */ + +(function(factory, global) { + if (typeof define == "function" && define.amd) { + // AMD. Register as an anonymous module. + define(factory); +/* + TODO: look into this properly. + + } else if (typeof exports == "object") { + // Node/CommonJS style for Browserify + module.exports = factory; +*/ + } else { + // No AMD or CommonJS support so we place Rangy in a global variable + global.rangy = factory(); + } +})(function() { + + var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined"; + + // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START + // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113. + var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", + "commonAncestorContainer"]; + + // Minimal set of methods required for DOM Level 2 Range compliance + var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore", + "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents", + "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"]; + + var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"]; + + // Subset of TextRange's full set of methods that we're interested in + var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "moveToElementText", "parentElement", "select", + "setEndPoint", "getBoundingClientRect"]; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Trio of functions taken from Peter Michaux's article: + // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting + function isHostMethod(o, p) { + var t = typeof o[p]; + return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown"; + } + + function isHostObject(o, p) { + return !!(typeof o[p] == OBJECT && o[p]); + } + + function isHostProperty(o, p) { + return typeof o[p] != UNDEFINED; + } + + // Creates a convenience function to save verbose repeated calls to tests functions + function createMultiplePropertyTest(testFunc) { + return function(o, props) { + var i = props.length; + while (i--) { + if (!testFunc(o, props[i])) { + return false; + } + } + return true; + }; + } + + // Next trio of functions are a convenience to save verbose repeated calls to previous two functions + var areHostMethods = createMultiplePropertyTest(isHostMethod); + var areHostObjects = createMultiplePropertyTest(isHostObject); + var areHostProperties = createMultiplePropertyTest(isHostProperty); + + function isTextRange(range) { + return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties); + } + + function getBody(doc) { + return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0]; + } + + var modules = {}; + + var api = { + version: "1.3alpha.20140804", + initialized: false, + supported: true, + + util: { + isHostMethod: isHostMethod, + isHostObject: isHostObject, + isHostProperty: isHostProperty, + areHostMethods: areHostMethods, + areHostObjects: areHostObjects, + areHostProperties: areHostProperties, + isTextRange: isTextRange, + getBody: getBody + }, + + features: {}, + + modules: modules, + config: { + alertOnFail: true, + alertOnWarn: false, + preferTextRange: false, + autoInitialize: (typeof rangyAutoInitialize == UNDEFINED) ? true : rangyAutoInitialize + } + }; + + function consoleLog(msg) { + if (isHostObject(window, "console") && isHostMethod(window.console, "log")) { + window.console.log(msg); + } + } + + function alertOrLog(msg, shouldAlert) { + if (shouldAlert) { + window.alert(msg); + } else { + consoleLog(msg); + } + } + + function fail(reason) { + api.initialized = true; + api.supported = false; + alertOrLog("Rangy is not supported on this page in your browser. Reason: " + reason, api.config.alertOnFail); + } + + api.fail = fail; + + function warn(msg) { + alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn); + } + + api.warn = warn; + + // Add utility extend() method + if ({}.hasOwnProperty) { + api.util.extend = function(obj, props, deep) { + var o, p; + for (var i in props) { + if (props.hasOwnProperty(i)) { + o = obj[i]; + p = props[i]; + if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") { + api.util.extend(o, p, true); + } + obj[i] = p; + } + } + // Special case for toString, which does not show up in for...in loops in IE <= 8 + if (props.hasOwnProperty("toString")) { + obj.toString = props.toString; + } + return obj; + }; + } else { + fail("hasOwnProperty not supported"); + } + + // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not + (function() { + var el = document.createElement("div"); + el.appendChild(document.createElement("span")); + var slice = [].slice; + var toArray; + try { + if (slice.call(el.childNodes, 0)[0].nodeType == 1) { + toArray = function(arrayLike) { + return slice.call(arrayLike, 0); + }; + } + } catch (e) {} + + if (!toArray) { + toArray = function(arrayLike) { + var arr = []; + for (var i = 0, len = arrayLike.length; i < len; ++i) { + arr[i] = arrayLike[i]; + } + return arr; + }; + } + + api.util.toArray = toArray; + })(); + + + // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or + // normalization of event properties + var addListener; + if (isHostMethod(document, "addEventListener")) { + addListener = function(obj, eventType, listener) { + obj.addEventListener(eventType, listener, false); + }; + } else if (isHostMethod(document, "attachEvent")) { + addListener = function(obj, eventType, listener) { + obj.attachEvent("on" + eventType, listener); + }; + } else { + fail("Document does not have required addEventListener or attachEvent method"); + } + + api.util.addListener = addListener; + + var initListeners = []; + + function getErrorDesc(ex) { + return ex.message || ex.description || String(ex); + } + + // Initialization + function init() { + if (api.initialized) { + return; + } + var testRange; + var implementsDomRange = false, implementsTextRange = false; + + // First, perform basic feature tests + + if (isHostMethod(document, "createRange")) { + testRange = document.createRange(); + if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) { + implementsDomRange = true; + } + } + + var body = getBody(document); + if (!body || body.nodeName.toLowerCase() != "body") { + fail("No body element found"); + return; + } + + if (body && isHostMethod(body, "createTextRange")) { + testRange = body.createTextRange(); + if (isTextRange(testRange)) { + implementsTextRange = true; + } + } + + if (!implementsDomRange && !implementsTextRange) { + fail("Neither Range nor TextRange are available"); + return; + } + + api.initialized = true; + api.features = { + implementsDomRange: implementsDomRange, + implementsTextRange: implementsTextRange + }; + + // Initialize modules + var module, errorMessage; + for (var moduleName in modules) { + if ( (module = modules[moduleName]) instanceof Module ) { + module.init(module, api); + } + } + + // Call init listeners + for (var i = 0, len = initListeners.length; i < len; ++i) { + try { + initListeners[i](api); + } catch (ex) { + errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex); + consoleLog(errorMessage); + } + } + } + + // Allow external scripts to initialize this library in case it's loaded after the document has loaded + api.init = init; + + // Execute listener immediately if already initialized + api.addInitListener = function(listener) { + if (api.initialized) { + listener(api); + } else { + initListeners.push(listener); + } + }; + + var shimListeners = []; + + api.addShimListener = function(listener) { + shimListeners.push(listener); + }; + + function shim(win) { + win = win || window; + init(); + + // Notify listeners + for (var i = 0, len = shimListeners.length; i < len; ++i) { + shimListeners[i](win); + } + } + + api.shim = api.createMissingNativeApi = shim; + + function Module(name, dependencies, initializer) { + this.name = name; + this.dependencies = dependencies; + this.initialized = false; + this.supported = false; + this.initializer = initializer; + } + + Module.prototype = { + init: function() { + var requiredModuleNames = this.dependencies || []; + for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) { + moduleName = requiredModuleNames[i]; + + requiredModule = modules[moduleName]; + if (!requiredModule || !(requiredModule instanceof Module)) { + throw new Error("required module '" + moduleName + "' not found"); + } + + requiredModule.init(); + + if (!requiredModule.supported) { + throw new Error("required module '" + moduleName + "' not supported"); + } + } + + // Now run initializer + this.initializer(this); + }, + + fail: function(reason) { + this.initialized = true; + this.supported = false; + throw new Error("Module '" + this.name + "' failed to load: " + reason); + }, + + warn: function(msg) { + api.warn("Module " + this.name + ": " + msg); + }, + + deprecationNotice: function(deprecated, replacement) { + api.warn("DEPRECATED: " + deprecated + " in module " + this.name + "is deprecated. Please use " + + replacement + " instead"); + }, + + createError: function(msg) { + return new Error("Error in Rangy " + this.name + " module: " + msg); + } + }; + + function createModule(isCore, name, dependencies, initFunc) { + var newModule = new Module(name, dependencies, function(module) { + if (!module.initialized) { + module.initialized = true; + try { + initFunc(api, module); + module.supported = true; + } catch (ex) { + var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex); + consoleLog(errorMessage); + } + } + }); + modules[name] = newModule; + } + + api.createModule = function(name) { + // Allow 2 or 3 arguments (second argument is an optional array of dependencies) + var initFunc, dependencies; + if (arguments.length == 2) { + initFunc = arguments[1]; + dependencies = []; + } else { + initFunc = arguments[2]; + dependencies = arguments[1]; + } + + var module = createModule(false, name, dependencies, initFunc); + + // Initialize the module immediately if the core is already initialized + if (api.initialized) { + module.init(); + } + }; + + api.createCoreModule = function(name, dependencies, initFunc) { + createModule(true, name, dependencies, initFunc); + }; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately + + function RangePrototype() {} + api.RangePrototype = RangePrototype; + api.rangePrototype = new RangePrototype(); + + function SelectionPrototype() {} + api.selectionPrototype = new SelectionPrototype(); + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Wait for document to load before running tests + + var docReady = false; + + var loadHandler = function(e) { + if (!docReady) { + docReady = true; + if (!api.initialized && api.config.autoInitialize) { + init(); + } + } + }; + + // Test whether we have window and document objects that we will need + if (typeof window == UNDEFINED) { + fail("No window found"); + return; + } + if (typeof document == UNDEFINED) { + fail("No document found"); + return; + } + + if (isHostMethod(document, "addEventListener")) { + document.addEventListener("DOMContentLoaded", loadHandler, false); + } + + // Add a fallback in case the DOMContentLoaded event isn't supported + addListener(window, "load", loadHandler); + + /*----------------------------------------------------------------------------------------------------------------*/ + + // DOM utility methods used by Rangy + api.createCoreModule("DomUtil", [], function(api, module) { + var UNDEF = "undefined"; + var util = api.util; + + // Perform feature tests + if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) { + module.fail("document missing a Node creation method"); + } + + if (!util.isHostMethod(document, "getElementsByTagName")) { + module.fail("document missing getElementsByTagName method"); + } + + var el = document.createElement("div"); + if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] || + !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) { + module.fail("Incomplete Element implementation"); + } + + // innerHTML is required for Range's createContextualFragment method + if (!util.isHostProperty(el, "innerHTML")) { + module.fail("Element is missing innerHTML property"); + } + + var textNode = document.createTextNode("test"); + if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] || + !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) || + !util.areHostProperties(textNode, ["data"]))) { + module.fail("Incomplete Text Node implementation"); + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been + // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that + // contains just the document as a single element and the value searched for is the document. + var arrayContains = /*Array.prototype.indexOf ? + function(arr, val) { + return arr.indexOf(val) > -1; + }:*/ + + function(arr, val) { + var i = arr.length; + while (i--) { + if (arr[i] === val) { + return true; + } + } + return false; + }; + + // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI + function isHtmlNamespace(node) { + var ns; + return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml"); + } + + function parentElement(node) { + var parent = node.parentNode; + return (parent.nodeType == 1) ? parent : null; + } + + function getNodeIndex(node) { + var i = 0; + while( (node = node.previousSibling) ) { + ++i; + } + return i; + } + + function getNodeLength(node) { + switch (node.nodeType) { + case 7: + case 10: + return 0; + case 3: + case 8: + return node.length; + default: + return node.childNodes.length; + } + } + + function getCommonAncestor(node1, node2) { + var ancestors = [], n; + for (n = node1; n; n = n.parentNode) { + ancestors.push(n); + } + + for (n = node2; n; n = n.parentNode) { + if (arrayContains(ancestors, n)) { + return n; + } + } + + return null; + } + + function isAncestorOf(ancestor, descendant, selfIsAncestor) { + var n = selfIsAncestor ? descendant : descendant.parentNode; + while (n) { + if (n === ancestor) { + return true; + } else { + n = n.parentNode; + } + } + return false; + } + + function isOrIsAncestorOf(ancestor, descendant) { + return isAncestorOf(ancestor, descendant, true); + } + + function getClosestAncestorIn(node, ancestor, selfIsAncestor) { + var p, n = selfIsAncestor ? node : node.parentNode; + while (n) { + p = n.parentNode; + if (p === ancestor) { + return n; + } + n = p; + } + return null; + } + + function isCharacterDataNode(node) { + var t = node.nodeType; + return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment + } + + function isTextOrCommentNode(node) { + if (!node) { + return false; + } + var t = node.nodeType; + return t == 3 || t == 8 ; // Text or Comment + } + + function insertAfter(node, precedingNode) { + var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode; + if (nextNode) { + parent.insertBefore(node, nextNode); + } else { + parent.appendChild(node); + } + return node; + } + + // Note that we cannot use splitText() because it is bugridden in IE 9. + function splitDataNode(node, index, positionsToPreserve) { + var newNode = node.cloneNode(false); + newNode.deleteData(0, index); + node.deleteData(index, node.length - index); + insertAfter(newNode, node); + + // Preserve positions + if (positionsToPreserve) { + for (var i = 0, position; position = positionsToPreserve[i++]; ) { + // Handle case where position was inside the portion of node after the split point + if (position.node == node && position.offset > index) { + position.node = newNode; + position.offset -= index; + } + // Handle the case where the position is a node offset within node's parent + else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) { + ++position.offset; + } + } + } + return newNode; + } + + function getDocument(node) { + if (node.nodeType == 9) { + return node; + } else if (typeof node.ownerDocument != UNDEF) { + return node.ownerDocument; + } else if (typeof node.document != UNDEF) { + return node.document; + } else if (node.parentNode) { + return getDocument(node.parentNode); + } else { + throw module.createError("getDocument: no document found for node"); + } + } + + function getWindow(node) { + var doc = getDocument(node); + if (typeof doc.defaultView != UNDEF) { + return doc.defaultView; + } else if (typeof doc.parentWindow != UNDEF) { + return doc.parentWindow; + } else { + throw module.createError("Cannot get a window object for node"); + } + } + + function getIframeDocument(iframeEl) { + if (typeof iframeEl.contentDocument != UNDEF) { + return iframeEl.contentDocument; + } else if (typeof iframeEl.contentWindow != UNDEF) { + return iframeEl.contentWindow.document; + } else { + throw module.createError("getIframeDocument: No Document object found for iframe element"); + } + } + + function getIframeWindow(iframeEl) { + if (typeof iframeEl.contentWindow != UNDEF) { + return iframeEl.contentWindow; + } else if (typeof iframeEl.contentDocument != UNDEF) { + return iframeEl.contentDocument.defaultView; + } else { + throw module.createError("getIframeWindow: No Window object found for iframe element"); + } + } + + // This looks bad. Is it worth it? + function isWindow(obj) { + return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document"); + } + + function getContentDocument(obj, module, methodName) { + var doc; + + if (!obj) { + doc = document; + } + + // Test if a DOM node has been passed and obtain a document object for it if so + else if (util.isHostProperty(obj, "nodeType")) { + doc = (obj.nodeType == 1 && obj.tagName.toLowerCase() == "iframe") ? + getIframeDocument(obj) : getDocument(obj); + } + + // Test if the doc parameter appears to be a Window object + else if (isWindow(obj)) { + doc = obj.document; + } + + if (!doc) { + throw module.createError(methodName + "(): Parameter must be a Window object or DOM node"); + } + + return doc; + } + + function getRootContainer(node) { + var parent; + while ( (parent = node.parentNode) ) { + node = parent; + } + return node; + } + + function comparePoints(nodeA, offsetA, nodeB, offsetB) { + // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing + var nodeC, root, childA, childB, n; + if (nodeA == nodeB) { + // Case 1: nodes are the same + return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1; + } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) { + // Case 2: node C (container B or an ancestor) is a child node of A + return offsetA <= getNodeIndex(nodeC) ? -1 : 1; + } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) { + // Case 3: node C (container A or an ancestor) is a child node of B + return getNodeIndex(nodeC) < offsetB ? -1 : 1; + } else { + root = getCommonAncestor(nodeA, nodeB); + if (!root) { + throw new Error("comparePoints error: nodes have no common ancestor"); + } + + // Case 4: containers are siblings or descendants of siblings + childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true); + childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true); + + if (childA === childB) { + // This shouldn't be possible + throw module.createError("comparePoints got to case 4 and childA and childB are the same!"); + } else { + n = root.firstChild; + while (n) { + if (n === childA) { + return -1; + } else if (n === childB) { + return 1; + } + n = n.nextSibling; + } + } + } + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried + var crashyTextNodes = false; + + function isBrokenNode(node) { + var n; + try { + n = node.parentNode; + return false; + } catch (e) { + return true; + } + } + + (function() { + var el = document.createElement("b"); + el.innerHTML = "1"; + var textNode = el.firstChild; + el.innerHTML = "
    "; + crashyTextNodes = isBrokenNode(textNode); + + api.features.crashyTextNodes = crashyTextNodes; + })(); + + /*----------------------------------------------------------------------------------------------------------------*/ + + function inspectNode(node) { + if (!node) { + return "[No node]"; + } + if (crashyTextNodes && isBrokenNode(node)) { + return "[Broken node]"; + } + if (isCharacterDataNode(node)) { + return '"' + node.data + '"'; + } + if (node.nodeType == 1) { + var idAttr = node.id ? ' id="' + node.id + '"' : ""; + return "<" + node.nodeName + idAttr + ">[index:" + getNodeIndex(node) + ",length:" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]"; + } + return node.nodeName; + } + + function fragmentFromNodeChildren(node) { + var fragment = getDocument(node).createDocumentFragment(), child; + while ( (child = node.firstChild) ) { + fragment.appendChild(child); + } + return fragment; + } + + var getComputedStyleProperty; + if (typeof window.getComputedStyle != UNDEF) { + getComputedStyleProperty = function(el, propName) { + return getWindow(el).getComputedStyle(el, null)[propName]; + }; + } else if (typeof document.documentElement.currentStyle != UNDEF) { + getComputedStyleProperty = function(el, propName) { + return el.currentStyle[propName]; + }; + } else { + module.fail("No means of obtaining computed style properties found"); + } + + function NodeIterator(root) { + this.root = root; + this._next = root; + } + + NodeIterator.prototype = { + _current: null, + + hasNext: function() { + return !!this._next; + }, + + next: function() { + var n = this._current = this._next; + var child, next; + if (this._current) { + child = n.firstChild; + if (child) { + this._next = child; + } else { + next = null; + while ((n !== this.root) && !(next = n.nextSibling)) { + n = n.parentNode; + } + this._next = next; + } + } + return this._current; + }, + + detach: function() { + this._current = this._next = this.root = null; + } + }; + + function createIterator(root) { + return new NodeIterator(root); + } + + function DomPosition(node, offset) { + this.node = node; + this.offset = offset; + } + + DomPosition.prototype = { + equals: function(pos) { + return !!pos && this.node === pos.node && this.offset == pos.offset; + }, + + inspect: function() { + return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]"; + }, + + toString: function() { + return this.inspect(); + } + }; + + function DOMException(codeName) { + this.code = this[codeName]; + this.codeName = codeName; + this.message = "DOMException: " + this.codeName; + } + + DOMException.prototype = { + INDEX_SIZE_ERR: 1, + HIERARCHY_REQUEST_ERR: 3, + WRONG_DOCUMENT_ERR: 4, + NO_MODIFICATION_ALLOWED_ERR: 7, + NOT_FOUND_ERR: 8, + NOT_SUPPORTED_ERR: 9, + INVALID_STATE_ERR: 11, + INVALID_NODE_TYPE_ERR: 24 + }; + + DOMException.prototype.toString = function() { + return this.message; + }; + + api.dom = { + arrayContains: arrayContains, + isHtmlNamespace: isHtmlNamespace, + parentElement: parentElement, + getNodeIndex: getNodeIndex, + getNodeLength: getNodeLength, + getCommonAncestor: getCommonAncestor, + isAncestorOf: isAncestorOf, + isOrIsAncestorOf: isOrIsAncestorOf, + getClosestAncestorIn: getClosestAncestorIn, + isCharacterDataNode: isCharacterDataNode, + isTextOrCommentNode: isTextOrCommentNode, + insertAfter: insertAfter, + splitDataNode: splitDataNode, + getDocument: getDocument, + getWindow: getWindow, + getIframeWindow: getIframeWindow, + getIframeDocument: getIframeDocument, + getBody: util.getBody, + isWindow: isWindow, + getContentDocument: getContentDocument, + getRootContainer: getRootContainer, + comparePoints: comparePoints, + isBrokenNode: isBrokenNode, + inspectNode: inspectNode, + getComputedStyleProperty: getComputedStyleProperty, + fragmentFromNodeChildren: fragmentFromNodeChildren, + createIterator: createIterator, + DomPosition: DomPosition + }; + + api.DOMException = DOMException; + }); + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Pure JavaScript implementation of DOM Range + api.createCoreModule("DomRange", ["DomUtil"], function(api, module) { + var dom = api.dom; + var util = api.util; + var DomPosition = dom.DomPosition; + var DOMException = api.DOMException; + + var isCharacterDataNode = dom.isCharacterDataNode; + var getNodeIndex = dom.getNodeIndex; + var isOrIsAncestorOf = dom.isOrIsAncestorOf; + var getDocument = dom.getDocument; + var comparePoints = dom.comparePoints; + var splitDataNode = dom.splitDataNode; + var getClosestAncestorIn = dom.getClosestAncestorIn; + var getNodeLength = dom.getNodeLength; + var arrayContains = dom.arrayContains; + var getRootContainer = dom.getRootContainer; + var crashyTextNodes = api.features.crashyTextNodes; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Utility functions + + function isNonTextPartiallySelected(node, range) { + return (node.nodeType != 3) && + (isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer)); + } + + function getRangeDocument(range) { + return range.document || getDocument(range.startContainer); + } + + function getBoundaryBeforeNode(node) { + return new DomPosition(node.parentNode, getNodeIndex(node)); + } + + function getBoundaryAfterNode(node) { + return new DomPosition(node.parentNode, getNodeIndex(node) + 1); + } + + function insertNodeAtPosition(node, n, o) { + var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node; + if (isCharacterDataNode(n)) { + if (o == n.length) { + dom.insertAfter(node, n); + } else { + n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o)); + } + } else if (o >= n.childNodes.length) { + n.appendChild(node); + } else { + n.insertBefore(node, n.childNodes[o]); + } + return firstNodeInserted; + } + + function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) { + assertRangeValid(rangeA); + assertRangeValid(rangeB); + + if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) { + throw new DOMException("WRONG_DOCUMENT_ERR"); + } + + var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset), + endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset); + + return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; + } + + function cloneSubtree(iterator) { + var partiallySelected; + for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { + partiallySelected = iterator.isPartiallySelectedSubtree(); + node = node.cloneNode(!partiallySelected); + if (partiallySelected) { + subIterator = iterator.getSubtreeIterator(); + node.appendChild(cloneSubtree(subIterator)); + subIterator.detach(); + } + + if (node.nodeType == 10) { // DocumentType + throw new DOMException("HIERARCHY_REQUEST_ERR"); + } + frag.appendChild(node); + } + return frag; + } + + function iterateSubtree(rangeIterator, func, iteratorState) { + var it, n; + iteratorState = iteratorState || { stop: false }; + for (var node, subRangeIterator; node = rangeIterator.next(); ) { + if (rangeIterator.isPartiallySelectedSubtree()) { + if (func(node) === false) { + iteratorState.stop = true; + return; + } else { + // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of + // the node selected by the Range. + subRangeIterator = rangeIterator.getSubtreeIterator(); + iterateSubtree(subRangeIterator, func, iteratorState); + subRangeIterator.detach(); + if (iteratorState.stop) { + return; + } + } + } else { + // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its + // descendants + it = dom.createIterator(node); + while ( (n = it.next()) ) { + if (func(n) === false) { + iteratorState.stop = true; + return; + } + } + } + } + } + + function deleteSubtree(iterator) { + var subIterator; + while (iterator.next()) { + if (iterator.isPartiallySelectedSubtree()) { + subIterator = iterator.getSubtreeIterator(); + deleteSubtree(subIterator); + subIterator.detach(); + } else { + iterator.remove(); + } + } + } + + function extractSubtree(iterator) { + for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { + + if (iterator.isPartiallySelectedSubtree()) { + node = node.cloneNode(false); + subIterator = iterator.getSubtreeIterator(); + node.appendChild(extractSubtree(subIterator)); + subIterator.detach(); + } else { + iterator.remove(); + } + if (node.nodeType == 10) { // DocumentType + throw new DOMException("HIERARCHY_REQUEST_ERR"); + } + frag.appendChild(node); + } + return frag; + } + + function getNodesInRange(range, nodeTypes, filter) { + var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex; + var filterExists = !!filter; + if (filterNodeTypes) { + regex = new RegExp("^(" + nodeTypes.join("|") + ")$"); + } + + var nodes = []; + iterateSubtree(new RangeIterator(range, false), function(node) { + if (filterNodeTypes && !regex.test(node.nodeType)) { + return; + } + if (filterExists && !filter(node)) { + return; + } + // Don't include a boundary container if it is a character data node and the range does not contain any + // of its character data. See issue 190. + var sc = range.startContainer; + if (node == sc && isCharacterDataNode(sc) && range.startOffset == sc.length) { + return; + } + + var ec = range.endContainer; + if (node == ec && isCharacterDataNode(ec) && range.endOffset == 0) { + return; + } + + nodes.push(node); + }); + return nodes; + } + + function inspect(range) { + var name = (typeof range.getName == "undefined") ? "Range" : range.getName(); + return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " + + dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]"; + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange) + + function RangeIterator(range, clonePartiallySelectedTextNodes) { + this.range = range; + this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes; + + + if (!range.collapsed) { + this.sc = range.startContainer; + this.so = range.startOffset; + this.ec = range.endContainer; + this.eo = range.endOffset; + var root = range.commonAncestorContainer; + + if (this.sc === this.ec && isCharacterDataNode(this.sc)) { + this.isSingleCharacterDataNode = true; + this._first = this._last = this._next = this.sc; + } else { + this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ? + this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true); + this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ? + this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true); + } + } + } + + RangeIterator.prototype = { + _current: null, + _next: null, + _first: null, + _last: null, + isSingleCharacterDataNode: false, + + reset: function() { + this._current = null; + this._next = this._first; + }, + + hasNext: function() { + return !!this._next; + }, + + next: function() { + // Move to next node + var current = this._current = this._next; + if (current) { + this._next = (current !== this._last) ? current.nextSibling : null; + + // Check for partially selected text nodes + if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) { + if (current === this.ec) { + (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo); + } + if (this._current === this.sc) { + (current = current.cloneNode(true)).deleteData(0, this.so); + } + } + } + + return current; + }, + + remove: function() { + var current = this._current, start, end; + + if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) { + start = (current === this.sc) ? this.so : 0; + end = (current === this.ec) ? this.eo : current.length; + if (start != end) { + current.deleteData(start, end - start); + } + } else { + if (current.parentNode) { + current.parentNode.removeChild(current); + } else { + } + } + }, + + // Checks if the current node is partially selected + isPartiallySelectedSubtree: function() { + var current = this._current; + return isNonTextPartiallySelected(current, this.range); + }, + + getSubtreeIterator: function() { + var subRange; + if (this.isSingleCharacterDataNode) { + subRange = this.range.cloneRange(); + subRange.collapse(false); + } else { + subRange = new Range(getRangeDocument(this.range)); + var current = this._current; + var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current); + + if (isOrIsAncestorOf(current, this.sc)) { + startContainer = this.sc; + startOffset = this.so; + } + if (isOrIsAncestorOf(current, this.ec)) { + endContainer = this.ec; + endOffset = this.eo; + } + + updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset); + } + return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes); + }, + + detach: function() { + this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null; + } + }; + + /*----------------------------------------------------------------------------------------------------------------*/ + + var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10]; + var rootContainerNodeTypes = [2, 9, 11]; + var readonlyNodeTypes = [5, 6, 10, 12]; + var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11]; + var surroundNodeTypes = [1, 3, 4, 5, 7, 8]; + + function createAncestorFinder(nodeTypes) { + return function(node, selfIsAncestor) { + var t, n = selfIsAncestor ? node : node.parentNode; + while (n) { + t = n.nodeType; + if (arrayContains(nodeTypes, t)) { + return n; + } + n = n.parentNode; + } + return null; + }; + } + + var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] ); + var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes); + var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] ); + + function assertNoDocTypeNotationEntityAncestor(node, allowSelf) { + if (getDocTypeNotationEntityAncestor(node, allowSelf)) { + throw new DOMException("INVALID_NODE_TYPE_ERR"); + } + } + + function assertValidNodeType(node, invalidTypes) { + if (!arrayContains(invalidTypes, node.nodeType)) { + throw new DOMException("INVALID_NODE_TYPE_ERR"); + } + } + + function assertValidOffset(node, offset) { + if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) { + throw new DOMException("INDEX_SIZE_ERR"); + } + } + + function assertSameDocumentOrFragment(node1, node2) { + if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) { + throw new DOMException("WRONG_DOCUMENT_ERR"); + } + } + + function assertNodeNotReadOnly(node) { + if (getReadonlyAncestor(node, true)) { + throw new DOMException("NO_MODIFICATION_ALLOWED_ERR"); + } + } + + function assertNode(node, codeName) { + if (!node) { + throw new DOMException(codeName); + } + } + + function isOrphan(node) { + return (crashyTextNodes && dom.isBrokenNode(node)) || + !arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true); + } + + function isValidOffset(node, offset) { + return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length); + } + + function isRangeValid(range) { + return (!!range.startContainer && !!range.endContainer && + !isOrphan(range.startContainer) && + !isOrphan(range.endContainer) && + isValidOffset(range.startContainer, range.startOffset) && + isValidOffset(range.endContainer, range.endOffset)); + } + + function assertRangeValid(range) { + if (!isRangeValid(range)) { + throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")"); + } + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Test the browser's innerHTML support to decide how to implement createContextualFragment + var styleEl = document.createElement("style"); + var htmlParsingConforms = false; + try { + styleEl.innerHTML = "x"; + htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node + } catch (e) { + // IE 6 and 7 throw + } + + api.features.htmlParsingConforms = htmlParsingConforms; + + var createContextualFragment = htmlParsingConforms ? + + // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See + // discussion and base code for this implementation at issue 67. + // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface + // Thanks to Aleks Williams. + function(fragmentStr) { + // "Let node the context object's start's node." + var node = this.startContainer; + var doc = getDocument(node); + + // "If the context object's start's node is null, raise an INVALID_STATE_ERR + // exception and abort these steps." + if (!node) { + throw new DOMException("INVALID_STATE_ERR"); + } + + // "Let element be as follows, depending on node's interface:" + // Document, Document Fragment: null + var el = null; + + // "Element: node" + if (node.nodeType == 1) { + el = node; + + // "Text, Comment: node's parentElement" + } else if (isCharacterDataNode(node)) { + el = dom.parentElement(node); + } + + // "If either element is null or element's ownerDocument is an HTML document + // and element's local name is "html" and element's namespace is the HTML + // namespace" + if (el === null || ( + el.nodeName == "HTML" && + dom.isHtmlNamespace(getDocument(el).documentElement) && + dom.isHtmlNamespace(el) + )) { + + // "let element be a new Element with "body" as its local name and the HTML + // namespace as its namespace."" + el = doc.createElement("body"); + } else { + el = el.cloneNode(false); + } + + // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm." + // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm." + // "In either case, the algorithm must be invoked with fragment as the input + // and element as the context element." + el.innerHTML = fragmentStr; + + // "If this raises an exception, then abort these steps. Otherwise, let new + // children be the nodes returned." + + // "Let fragment be a new DocumentFragment." + // "Append all new children to fragment." + // "Return fragment." + return dom.fragmentFromNodeChildren(el); + } : + + // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that + // previous versions of Rangy used (with the exception of using a body element rather than a div) + function(fragmentStr) { + var doc = getRangeDocument(this); + var el = doc.createElement("body"); + el.innerHTML = fragmentStr; + + return dom.fragmentFromNodeChildren(el); + }; + + function splitRangeBoundaries(range, positionsToPreserve) { + assertRangeValid(range); + + var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset; + var startEndSame = (sc === ec); + + if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) { + splitDataNode(ec, eo, positionsToPreserve); + } + + if (isCharacterDataNode(sc) && so > 0 && so < sc.length) { + sc = splitDataNode(sc, so, positionsToPreserve); + if (startEndSame) { + eo -= so; + ec = sc; + } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) { + eo++; + } + so = 0; + } + range.setStartAndEnd(sc, so, ec, eo); + } + + function rangeToHtml(range) { + assertRangeValid(range); + var container = range.commonAncestorContainer.parentNode.cloneNode(false); + container.appendChild( range.cloneContents() ); + return container.innerHTML; + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", + "commonAncestorContainer"]; + + var s2s = 0, s2e = 1, e2e = 2, e2s = 3; + var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3; + + util.extend(api.rangePrototype, { + compareBoundaryPoints: function(how, range) { + assertRangeValid(this); + assertSameDocumentOrFragment(this.startContainer, range.startContainer); + + var nodeA, offsetA, nodeB, offsetB; + var prefixA = (how == e2s || how == s2s) ? "start" : "end"; + var prefixB = (how == s2e || how == s2s) ? "start" : "end"; + nodeA = this[prefixA + "Container"]; + offsetA = this[prefixA + "Offset"]; + nodeB = range[prefixB + "Container"]; + offsetB = range[prefixB + "Offset"]; + return comparePoints(nodeA, offsetA, nodeB, offsetB); + }, + + insertNode: function(node) { + assertRangeValid(this); + assertValidNodeType(node, insertableNodeTypes); + assertNodeNotReadOnly(this.startContainer); + + if (isOrIsAncestorOf(node, this.startContainer)) { + throw new DOMException("HIERARCHY_REQUEST_ERR"); + } + + // No check for whether the container of the start of the Range is of a type that does not allow + // children of the type of node: the browser's DOM implementation should do this for us when we attempt + // to add the node + + var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset); + this.setStartBefore(firstNodeInserted); + }, + + cloneContents: function() { + assertRangeValid(this); + + var clone, frag; + if (this.collapsed) { + return getRangeDocument(this).createDocumentFragment(); + } else { + if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) { + clone = this.startContainer.cloneNode(true); + clone.data = clone.data.slice(this.startOffset, this.endOffset); + frag = getRangeDocument(this).createDocumentFragment(); + frag.appendChild(clone); + return frag; + } else { + var iterator = new RangeIterator(this, true); + clone = cloneSubtree(iterator); + iterator.detach(); + } + return clone; + } + }, + + canSurroundContents: function() { + assertRangeValid(this); + assertNodeNotReadOnly(this.startContainer); + assertNodeNotReadOnly(this.endContainer); + + // Check if the contents can be surrounded. Specifically, this means whether the range partially selects + // no non-text nodes. + var iterator = new RangeIterator(this, true); + var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || + (iterator._last && isNonTextPartiallySelected(iterator._last, this))); + iterator.detach(); + return !boundariesInvalid; + }, + + surroundContents: function(node) { + assertValidNodeType(node, surroundNodeTypes); + + if (!this.canSurroundContents()) { + throw new DOMException("INVALID_STATE_ERR"); + } + + // Extract the contents + var content = this.extractContents(); + + // Clear the children of the node + if (node.hasChildNodes()) { + while (node.lastChild) { + node.removeChild(node.lastChild); + } + } + + // Insert the new node and add the extracted contents + insertNodeAtPosition(node, this.startContainer, this.startOffset); + node.appendChild(content); + + this.selectNode(node); + }, + + cloneRange: function() { + assertRangeValid(this); + var range = new Range(getRangeDocument(this)); + var i = rangeProperties.length, prop; + while (i--) { + prop = rangeProperties[i]; + range[prop] = this[prop]; + } + return range; + }, + + toString: function() { + assertRangeValid(this); + var sc = this.startContainer; + if (sc === this.endContainer && isCharacterDataNode(sc)) { + return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : ""; + } else { + var textParts = [], iterator = new RangeIterator(this, true); + iterateSubtree(iterator, function(node) { + // Accept only text or CDATA nodes, not comments + if (node.nodeType == 3 || node.nodeType == 4) { + textParts.push(node.data); + } + }); + iterator.detach(); + return textParts.join(""); + } + }, + + // The methods below are all non-standard. The following batch were introduced by Mozilla but have since + // been removed from Mozilla. + + compareNode: function(node) { + assertRangeValid(this); + + var parent = node.parentNode; + var nodeIndex = getNodeIndex(node); + + if (!parent) { + throw new DOMException("NOT_FOUND_ERR"); + } + + var startComparison = this.comparePoint(parent, nodeIndex), + endComparison = this.comparePoint(parent, nodeIndex + 1); + + if (startComparison < 0) { // Node starts before + return (endComparison > 0) ? n_b_a : n_b; + } else { + return (endComparison > 0) ? n_a : n_i; + } + }, + + comparePoint: function(node, offset) { + assertRangeValid(this); + assertNode(node, "HIERARCHY_REQUEST_ERR"); + assertSameDocumentOrFragment(node, this.startContainer); + + if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) { + return -1; + } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) { + return 1; + } + return 0; + }, + + createContextualFragment: createContextualFragment, + + toHtml: function() { + return rangeToHtml(this); + }, + + // touchingIsIntersecting determines whether this method considers a node that borders a range intersects + // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default) + intersectsNode: function(node, touchingIsIntersecting) { + assertRangeValid(this); + assertNode(node, "NOT_FOUND_ERR"); + if (getDocument(node) !== getRangeDocument(this)) { + return false; + } + + var parent = node.parentNode, offset = getNodeIndex(node); + assertNode(parent, "NOT_FOUND_ERR"); + + var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset), + endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset); + + return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; + }, + + isPointInRange: function(node, offset) { + assertRangeValid(this); + assertNode(node, "HIERARCHY_REQUEST_ERR"); + assertSameDocumentOrFragment(node, this.startContainer); + + return (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) && + (comparePoints(node, offset, this.endContainer, this.endOffset) <= 0); + }, + + // The methods below are non-standard and invented by me. + + // Sharing a boundary start-to-end or end-to-start does not count as intersection. + intersectsRange: function(range) { + return rangesIntersect(this, range, false); + }, + + // Sharing a boundary start-to-end or end-to-start does count as intersection. + intersectsOrTouchesRange: function(range) { + return rangesIntersect(this, range, true); + }, + + intersection: function(range) { + if (this.intersectsRange(range)) { + var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset), + endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset); + + var intersectionRange = this.cloneRange(); + if (startComparison == -1) { + intersectionRange.setStart(range.startContainer, range.startOffset); + } + if (endComparison == 1) { + intersectionRange.setEnd(range.endContainer, range.endOffset); + } + return intersectionRange; + } + return null; + }, + + union: function(range) { + if (this.intersectsOrTouchesRange(range)) { + var unionRange = this.cloneRange(); + if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) { + unionRange.setStart(range.startContainer, range.startOffset); + } + if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) { + unionRange.setEnd(range.endContainer, range.endOffset); + } + return unionRange; + } else { + throw new DOMException("Ranges do not intersect"); + } + }, + + containsNode: function(node, allowPartial) { + if (allowPartial) { + return this.intersectsNode(node, false); + } else { + return this.compareNode(node) == n_i; + } + }, + + containsNodeContents: function(node) { + return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0; + }, + + containsRange: function(range) { + var intersection = this.intersection(range); + return intersection !== null && range.equals(intersection); + }, + + containsNodeText: function(node) { + var nodeRange = this.cloneRange(); + nodeRange.selectNode(node); + var textNodes = nodeRange.getNodes([3]); + if (textNodes.length > 0) { + nodeRange.setStart(textNodes[0], 0); + var lastTextNode = textNodes.pop(); + nodeRange.setEnd(lastTextNode, lastTextNode.length); + return this.containsRange(nodeRange); + } else { + return this.containsNodeContents(node); + } + }, + + getNodes: function(nodeTypes, filter) { + assertRangeValid(this); + return getNodesInRange(this, nodeTypes, filter); + }, + + getDocument: function() { + return getRangeDocument(this); + }, + + collapseBefore: function(node) { + this.setEndBefore(node); + this.collapse(false); + }, + + collapseAfter: function(node) { + this.setStartAfter(node); + this.collapse(true); + }, + + getBookmark: function(containerNode) { + var doc = getRangeDocument(this); + var preSelectionRange = api.createRange(doc); + containerNode = containerNode || dom.getBody(doc); + preSelectionRange.selectNodeContents(containerNode); + var range = this.intersection(preSelectionRange); + var start = 0, end = 0; + if (range) { + preSelectionRange.setEnd(range.startContainer, range.startOffset); + start = preSelectionRange.toString().length; + end = start + range.toString().length; + } + + return { + start: start, + end: end, + containerNode: containerNode + }; + }, + + moveToBookmark: function(bookmark) { + var containerNode = bookmark.containerNode; + var charIndex = 0; + this.setStart(containerNode, 0); + this.collapse(true); + var nodeStack = [containerNode], node, foundStart = false, stop = false; + var nextCharIndex, i, childNodes; + + while (!stop && (node = nodeStack.pop())) { + if (node.nodeType == 3) { + nextCharIndex = charIndex + node.length; + if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) { + this.setStart(node, bookmark.start - charIndex); + foundStart = true; + } + if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) { + this.setEnd(node, bookmark.end - charIndex); + stop = true; + } + charIndex = nextCharIndex; + } else { + childNodes = node.childNodes; + i = childNodes.length; + while (i--) { + nodeStack.push(childNodes[i]); + } + } + } + }, + + getName: function() { + return "DomRange"; + }, + + equals: function(range) { + return Range.rangesEqual(this, range); + }, + + isValid: function() { + return isRangeValid(this); + }, + + inspect: function() { + return inspect(this); + }, + + detach: function() { + // In DOM4, detach() is now a no-op. + } + }); + + function copyComparisonConstantsToObject(obj) { + obj.START_TO_START = s2s; + obj.START_TO_END = s2e; + obj.END_TO_END = e2e; + obj.END_TO_START = e2s; + + obj.NODE_BEFORE = n_b; + obj.NODE_AFTER = n_a; + obj.NODE_BEFORE_AND_AFTER = n_b_a; + obj.NODE_INSIDE = n_i; + } + + function copyComparisonConstants(constructor) { + copyComparisonConstantsToObject(constructor); + copyComparisonConstantsToObject(constructor.prototype); + } + + function createRangeContentRemover(remover, boundaryUpdater) { + return function() { + assertRangeValid(this); + + var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer; + + var iterator = new RangeIterator(this, true); + + // Work out where to position the range after content removal + var node, boundary; + if (sc !== root) { + node = getClosestAncestorIn(sc, root, true); + boundary = getBoundaryAfterNode(node); + sc = boundary.node; + so = boundary.offset; + } + + // Check none of the range is read-only + iterateSubtree(iterator, assertNodeNotReadOnly); + + iterator.reset(); + + // Remove the content + var returnValue = remover(iterator); + iterator.detach(); + + // Move to the new position + boundaryUpdater(this, sc, so, sc, so); + + return returnValue; + }; + } + + function createPrototypeRange(constructor, boundaryUpdater) { + function createBeforeAfterNodeSetter(isBefore, isStart) { + return function(node) { + assertValidNodeType(node, beforeAfterNodeTypes); + assertValidNodeType(getRootContainer(node), rootContainerNodeTypes); + + var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node); + (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset); + }; + } + + function setRangeStart(range, node, offset) { + var ec = range.endContainer, eo = range.endOffset; + if (node !== range.startContainer || offset !== range.startOffset) { + // Check the root containers of the range and the new boundary, and also check whether the new boundary + // is after the current end. In either case, collapse the range to the new position + if (getRootContainer(node) != getRootContainer(ec) || comparePoints(node, offset, ec, eo) == 1) { + ec = node; + eo = offset; + } + boundaryUpdater(range, node, offset, ec, eo); + } + } + + function setRangeEnd(range, node, offset) { + var sc = range.startContainer, so = range.startOffset; + if (node !== range.endContainer || offset !== range.endOffset) { + // Check the root containers of the range and the new boundary, and also check whether the new boundary + // is after the current end. In either case, collapse the range to the new position + if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) { + sc = node; + so = offset; + } + boundaryUpdater(range, sc, so, node, offset); + } + } + + // Set up inheritance + var F = function() {}; + F.prototype = api.rangePrototype; + constructor.prototype = new F(); + + util.extend(constructor.prototype, { + setStart: function(node, offset) { + assertNoDocTypeNotationEntityAncestor(node, true); + assertValidOffset(node, offset); + + setRangeStart(this, node, offset); + }, + + setEnd: function(node, offset) { + assertNoDocTypeNotationEntityAncestor(node, true); + assertValidOffset(node, offset); + + setRangeEnd(this, node, offset); + }, + + /** + * Convenience method to set a range's start and end boundaries. Overloaded as follows: + * - Two parameters (node, offset) creates a collapsed range at that position + * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at + * startOffset and ending at endOffset + * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in + * startNode and ending at endOffset in endNode + */ + setStartAndEnd: function() { + var args = arguments; + var sc = args[0], so = args[1], ec = sc, eo = so; + + switch (args.length) { + case 3: + eo = args[2]; + break; + case 4: + ec = args[2]; + eo = args[3]; + break; + } + + boundaryUpdater(this, sc, so, ec, eo); + }, + + setBoundary: function(node, offset, isStart) { + this["set" + (isStart ? "Start" : "End")](node, offset); + }, + + setStartBefore: createBeforeAfterNodeSetter(true, true), + setStartAfter: createBeforeAfterNodeSetter(false, true), + setEndBefore: createBeforeAfterNodeSetter(true, false), + setEndAfter: createBeforeAfterNodeSetter(false, false), + + collapse: function(isStart) { + assertRangeValid(this); + if (isStart) { + boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset); + } else { + boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset); + } + }, + + selectNodeContents: function(node) { + assertNoDocTypeNotationEntityAncestor(node, true); + + boundaryUpdater(this, node, 0, node, getNodeLength(node)); + }, + + selectNode: function(node) { + assertNoDocTypeNotationEntityAncestor(node, false); + assertValidNodeType(node, beforeAfterNodeTypes); + + var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node); + boundaryUpdater(this, start.node, start.offset, end.node, end.offset); + }, + + extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater), + + deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater), + + canSurroundContents: function() { + assertRangeValid(this); + assertNodeNotReadOnly(this.startContainer); + assertNodeNotReadOnly(this.endContainer); + + // Check if the contents can be surrounded. Specifically, this means whether the range partially selects + // no non-text nodes. + var iterator = new RangeIterator(this, true); + var boundariesInvalid = (iterator._first && isNonTextPartiallySelected(iterator._first, this) || + (iterator._last && isNonTextPartiallySelected(iterator._last, this))); + iterator.detach(); + return !boundariesInvalid; + }, + + splitBoundaries: function() { + splitRangeBoundaries(this); + }, + + splitBoundariesPreservingPositions: function(positionsToPreserve) { + splitRangeBoundaries(this, positionsToPreserve); + }, + + normalizeBoundaries: function() { + assertRangeValid(this); + + var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset; + + var mergeForward = function(node) { + var sibling = node.nextSibling; + if (sibling && sibling.nodeType == node.nodeType) { + ec = node; + eo = node.length; + node.appendData(sibling.data); + sibling.parentNode.removeChild(sibling); + } + }; + + var mergeBackward = function(node) { + var sibling = node.previousSibling; + if (sibling && sibling.nodeType == node.nodeType) { + sc = node; + var nodeLength = node.length; + so = sibling.length; + node.insertData(0, sibling.data); + sibling.parentNode.removeChild(sibling); + if (sc == ec) { + eo += so; + ec = sc; + } else if (ec == node.parentNode) { + var nodeIndex = getNodeIndex(node); + if (eo == nodeIndex) { + ec = node; + eo = nodeLength; + } else if (eo > nodeIndex) { + eo--; + } + } + } + }; + + var normalizeStart = true; + + if (isCharacterDataNode(ec)) { + if (ec.length == eo) { + mergeForward(ec); + } + } else { + if (eo > 0) { + var endNode = ec.childNodes[eo - 1]; + if (endNode && isCharacterDataNode(endNode)) { + mergeForward(endNode); + } + } + normalizeStart = !this.collapsed; + } + + if (normalizeStart) { + if (isCharacterDataNode(sc)) { + if (so == 0) { + mergeBackward(sc); + } + } else { + if (so < sc.childNodes.length) { + var startNode = sc.childNodes[so]; + if (startNode && isCharacterDataNode(startNode)) { + mergeBackward(startNode); + } + } + } + } else { + sc = ec; + so = eo; + } + + boundaryUpdater(this, sc, so, ec, eo); + }, + + collapseToPoint: function(node, offset) { + assertNoDocTypeNotationEntityAncestor(node, true); + assertValidOffset(node, offset); + this.setStartAndEnd(node, offset); + } + }); + + copyComparisonConstants(constructor); + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Updates commonAncestorContainer and collapsed after boundary change + function updateCollapsedAndCommonAncestor(range) { + range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); + range.commonAncestorContainer = range.collapsed ? + range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer); + } + + function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) { + range.startContainer = startContainer; + range.startOffset = startOffset; + range.endContainer = endContainer; + range.endOffset = endOffset; + range.document = dom.getDocument(startContainer); + + updateCollapsedAndCommonAncestor(range); + } + + function Range(doc) { + this.startContainer = doc; + this.startOffset = 0; + this.endContainer = doc; + this.endOffset = 0; + this.document = doc; + updateCollapsedAndCommonAncestor(this); + } + + createPrototypeRange(Range, updateBoundaries); + + util.extend(Range, { + rangeProperties: rangeProperties, + RangeIterator: RangeIterator, + copyComparisonConstants: copyComparisonConstants, + createPrototypeRange: createPrototypeRange, + inspect: inspect, + toHtml: rangeToHtml, + getRangeDocument: getRangeDocument, + rangesEqual: function(r1, r2) { + return r1.startContainer === r2.startContainer && + r1.startOffset === r2.startOffset && + r1.endContainer === r2.endContainer && + r1.endOffset === r2.endOffset; + } + }); + + api.DomRange = Range; + }); + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Wrappers for the browser's native DOM Range and/or TextRange implementation + api.createCoreModule("WrappedRange", ["DomRange"], function(api, module) { + var WrappedRange, WrappedTextRange; + var dom = api.dom; + var util = api.util; + var DomPosition = dom.DomPosition; + var DomRange = api.DomRange; + var getBody = dom.getBody; + var getContentDocument = dom.getContentDocument; + var isCharacterDataNode = dom.isCharacterDataNode; + + + /*----------------------------------------------------------------------------------------------------------------*/ + + if (api.features.implementsDomRange) { + // This is a wrapper around the browser's native DOM Range. It has two aims: + // - Provide workarounds for specific browser bugs + // - provide convenient extensions, which are inherited from Rangy's DomRange + + (function() { + var rangeProto; + var rangeProperties = DomRange.rangeProperties; + + function updateRangeProperties(range) { + var i = rangeProperties.length, prop; + while (i--) { + prop = rangeProperties[i]; + range[prop] = range.nativeRange[prop]; + } + // Fix for broken collapsed property in IE 9. + range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); + } + + function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) { + var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset); + var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset); + var nativeRangeDifferent = !range.equals(range.nativeRange); + + // Always set both boundaries for the benefit of IE9 (see issue 35) + if (startMoved || endMoved || nativeRangeDifferent) { + range.setEnd(endContainer, endOffset); + range.setStart(startContainer, startOffset); + } + } + + var createBeforeAfterNodeSetter; + + WrappedRange = function(range) { + if (!range) { + throw module.createError("WrappedRange: Range must be specified"); + } + this.nativeRange = range; + updateRangeProperties(this); + }; + + DomRange.createPrototypeRange(WrappedRange, updateNativeRange); + + rangeProto = WrappedRange.prototype; + + rangeProto.selectNode = function(node) { + this.nativeRange.selectNode(node); + updateRangeProperties(this); + }; + + rangeProto.cloneContents = function() { + return this.nativeRange.cloneContents(); + }; + + // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect, + // insertNode() is never delegated to the native range. + + rangeProto.surroundContents = function(node) { + this.nativeRange.surroundContents(node); + updateRangeProperties(this); + }; + + rangeProto.collapse = function(isStart) { + this.nativeRange.collapse(isStart); + updateRangeProperties(this); + }; + + rangeProto.cloneRange = function() { + return new WrappedRange(this.nativeRange.cloneRange()); + }; + + rangeProto.refresh = function() { + updateRangeProperties(this); + }; + + rangeProto.toString = function() { + return this.nativeRange.toString(); + }; + + // Create test range and node for feature detection + + var testTextNode = document.createTextNode("test"); + getBody(document).appendChild(testTextNode); + var range = document.createRange(); + + /*--------------------------------------------------------------------------------------------------------*/ + + // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and + // correct for it + + range.setStart(testTextNode, 0); + range.setEnd(testTextNode, 0); + + try { + range.setStart(testTextNode, 1); + + rangeProto.setStart = function(node, offset) { + this.nativeRange.setStart(node, offset); + updateRangeProperties(this); + }; + + rangeProto.setEnd = function(node, offset) { + this.nativeRange.setEnd(node, offset); + updateRangeProperties(this); + }; + + createBeforeAfterNodeSetter = function(name) { + return function(node) { + this.nativeRange[name](node); + updateRangeProperties(this); + }; + }; + + } catch(ex) { + + rangeProto.setStart = function(node, offset) { + try { + this.nativeRange.setStart(node, offset); + } catch (ex) { + this.nativeRange.setEnd(node, offset); + this.nativeRange.setStart(node, offset); + } + updateRangeProperties(this); + }; + + rangeProto.setEnd = function(node, offset) { + try { + this.nativeRange.setEnd(node, offset); + } catch (ex) { + this.nativeRange.setStart(node, offset); + this.nativeRange.setEnd(node, offset); + } + updateRangeProperties(this); + }; + + createBeforeAfterNodeSetter = function(name, oppositeName) { + return function(node) { + try { + this.nativeRange[name](node); + } catch (ex) { + this.nativeRange[oppositeName](node); + this.nativeRange[name](node); + } + updateRangeProperties(this); + }; + }; + } + + rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore"); + rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter"); + rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore"); + rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter"); + + /*--------------------------------------------------------------------------------------------------------*/ + + // Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing + // whether the native implementation can be trusted + rangeProto.selectNodeContents = function(node) { + this.setStartAndEnd(node, 0, dom.getNodeLength(node)); + }; + + /*--------------------------------------------------------------------------------------------------------*/ + + // Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for + // constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738 + + range.selectNodeContents(testTextNode); + range.setEnd(testTextNode, 3); + + var range2 = document.createRange(); + range2.selectNodeContents(testTextNode); + range2.setEnd(testTextNode, 4); + range2.setStart(testTextNode, 2); + + if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 && + range.compareBoundaryPoints(range.END_TO_START, range2) == 1) { + // This is the wrong way round, so correct for it + + rangeProto.compareBoundaryPoints = function(type, range) { + range = range.nativeRange || range; + if (type == range.START_TO_END) { + type = range.END_TO_START; + } else if (type == range.END_TO_START) { + type = range.START_TO_END; + } + return this.nativeRange.compareBoundaryPoints(type, range); + }; + } else { + rangeProto.compareBoundaryPoints = function(type, range) { + return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range); + }; + } + + /*--------------------------------------------------------------------------------------------------------*/ + + // Test for IE 9 deleteContents() and extractContents() bug and correct it. See issue 107. + + var el = document.createElement("div"); + el.innerHTML = "123"; + var textNode = el.firstChild; + var body = getBody(document); + body.appendChild(el); + + range.setStart(textNode, 1); + range.setEnd(textNode, 2); + range.deleteContents(); + + if (textNode.data == "13") { + // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and + // extractContents() + rangeProto.deleteContents = function() { + this.nativeRange.deleteContents(); + updateRangeProperties(this); + }; + + rangeProto.extractContents = function() { + var frag = this.nativeRange.extractContents(); + updateRangeProperties(this); + return frag; + }; + } else { + } + + body.removeChild(el); + body = null; + + /*--------------------------------------------------------------------------------------------------------*/ + + // Test for existence of createContextualFragment and delegate to it if it exists + if (util.isHostMethod(range, "createContextualFragment")) { + rangeProto.createContextualFragment = function(fragmentStr) { + return this.nativeRange.createContextualFragment(fragmentStr); + }; + } + + /*--------------------------------------------------------------------------------------------------------*/ + + // Clean up + getBody(document).removeChild(testTextNode); + + rangeProto.getName = function() { + return "WrappedRange"; + }; + + api.WrappedRange = WrappedRange; + + api.createNativeRange = function(doc) { + doc = getContentDocument(doc, module, "createNativeRange"); + return doc.createRange(); + }; + })(); + } + + if (api.features.implementsTextRange) { + /* + This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement() + method. For example, in the following (where pipes denote the selection boundaries): + +
    • | a
    • b |
    + + var range = document.selection.createRange(); + alert(range.parentElement().id); // Should alert "ul" but alerts "b" + + This method returns the common ancestor node of the following: + - the parentElement() of the textRange + - the parentElement() of the textRange after calling collapse(true) + - the parentElement() of the textRange after calling collapse(false) + */ + var getTextRangeContainerElement = function(textRange) { + var parentEl = textRange.parentElement(); + var range = textRange.duplicate(); + range.collapse(true); + var startEl = range.parentElement(); + range = textRange.duplicate(); + range.collapse(false); + var endEl = range.parentElement(); + var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl); + + return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer); + }; + + var textRangeIsCollapsed = function(textRange) { + return textRange.compareEndPoints("StartToEnd", textRange) == 0; + }; + + // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started + // out as an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) + // but has grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange + // bugs, handling for inputs and images, plus optimizations. + var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) { + var workingRange = textRange.duplicate(); + workingRange.collapse(isStart); + var containerElement = workingRange.parentElement(); + + // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so + // check for that + if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) { + containerElement = wholeRangeContainerElement; + } + + + // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and + // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx + if (!containerElement.canHaveHTML) { + var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement)); + return { + boundaryPosition: pos, + nodeInfo: { + nodeIndex: pos.offset, + containerElement: pos.node + } + }; + } + + var workingNode = dom.getDocument(containerElement).createElement("span"); + + // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5 + // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64 + if (workingNode.parentNode) { + workingNode.parentNode.removeChild(workingNode); + } + + var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd"; + var previousNode, nextNode, boundaryPosition, boundaryNode; + var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0; + var childNodeCount = containerElement.childNodes.length; + var end = childNodeCount; + + // Check end first. Code within the loop assumes that the endth child node of the container is definitely + // after the range boundary. + var nodeIndex = end; + + while (true) { + if (nodeIndex == childNodeCount) { + containerElement.appendChild(workingNode); + } else { + containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]); + } + workingRange.moveToElementText(workingNode); + comparison = workingRange.compareEndPoints(workingComparisonType, textRange); + if (comparison == 0 || start == end) { + break; + } else if (comparison == -1) { + if (end == start + 1) { + // We know the endth child node is after the range boundary, so we must be done. + break; + } else { + start = nodeIndex; + } + } else { + end = (end == start + 1) ? start : nodeIndex; + } + nodeIndex = Math.floor((start + end) / 2); + containerElement.removeChild(workingNode); + } + + + // We've now reached or gone past the boundary of the text range we're interested in + // so have identified the node we want + boundaryNode = workingNode.nextSibling; + + if (comparison == -1 && boundaryNode && isCharacterDataNode(boundaryNode)) { + // This is a character data node (text, comment, cdata). The working range is collapsed at the start of + // the node containing the text range's boundary, so we move the end of the working range to the + // boundary point and measure the length of its text to get the boundary's offset within the node. + workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange); + + var offset; + + if (/[\r\n]/.test(boundaryNode.data)) { + /* + For the particular case of a boundary within a text node containing rendered line breaks (within a +
     element, for example), we need a slightly complicated approach to get the boundary's offset in
    +                        IE. The facts:
    +                        
    +                        - Each line break is represented as \r in the text node's data/nodeValue properties
    +                        - Each line break is represented as \r\n in the TextRange's 'text' property
    +                        - The 'text' property of the TextRange does not contain trailing line breaks
    +                        
    +                        To get round the problem presented by the final fact above, we can use the fact that TextRange's
    +                        moveStart() and moveEnd() methods return the actual number of characters moved, which is not
    +                        necessarily the same as the number of characters it was instructed to move. The simplest approach is
    +                        to use this to store the characters moved when moving both the start and end of the range to the
    +                        start of the document body and subtracting the start offset from the end offset (the
    +                        "move-negative-gazillion" method). However, this is extremely slow when the document is large and
    +                        the range is near the end of it. Clearly doing the mirror image (i.e. moving the range boundaries to
    +                        the end of the document) has the same problem.
    +                        
    +                        Another approach that works is to use moveStart() to move the start boundary of the range up to the
    +                        end boundary one character at a time and incrementing a counter with the value returned by the
    +                        moveStart() call. However, the check for whether the start boundary has reached the end boundary is
    +                        expensive, so this method is slow (although unlike "move-negative-gazillion" is largely unaffected
    +                        by the location of the range within the document).
    +                        
    +                        The approach used below is a hybrid of the two methods above. It uses the fact that a string
    +                        containing the TextRange's 'text' property with each \r\n converted to a single \r character cannot
    +                        be longer than the text of the TextRange, so the start of the range is moved that length initially
    +                        and then a character at a time to make up for any trailing line breaks not contained in the 'text'
    +                        property. This has good performance in most situations compared to the previous two methods.
    +                        */
    +                        var tempRange = workingRange.duplicate();
    +                        var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
    +
    +                        offset = tempRange.moveStart("character", rangeLength);
    +                        while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
    +                            offset++;
    +                            tempRange.moveStart("character", 1);
    +                        }
    +                    } else {
    +                        offset = workingRange.text.length;
    +                    }
    +                    boundaryPosition = new DomPosition(boundaryNode, offset);
    +                } else {
    +
    +                    // If the boundary immediately follows a character data node and this is the end boundary, we should favour
    +                    // a position within that, and likewise for a start boundary preceding a character data node
    +                    previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
    +                    nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
    +                    if (nextNode && isCharacterDataNode(nextNode)) {
    +                        boundaryPosition = new DomPosition(nextNode, 0);
    +                    } else if (previousNode && isCharacterDataNode(previousNode)) {
    +                        boundaryPosition = new DomPosition(previousNode, previousNode.data.length);
    +                    } else {
    +                        boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
    +                    }
    +                }
    +
    +                // Clean up
    +                workingNode.parentNode.removeChild(workingNode);
    +
    +                return {
    +                    boundaryPosition: boundaryPosition,
    +                    nodeInfo: {
    +                        nodeIndex: nodeIndex,
    +                        containerElement: containerElement
    +                    }
    +                };
    +            };
    +
    +            // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that
    +            // node. This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
    +            // (http://code.google.com/p/ierange/)
    +            var createBoundaryTextRange = function(boundaryPosition, isStart) {
    +                var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
    +                var doc = dom.getDocument(boundaryPosition.node);
    +                var workingNode, childNodes, workingRange = getBody(doc).createTextRange();
    +                var nodeIsDataNode = isCharacterDataNode(boundaryPosition.node);
    +
    +                if (nodeIsDataNode) {
    +                    boundaryNode = boundaryPosition.node;
    +                    boundaryParent = boundaryNode.parentNode;
    +                } else {
    +                    childNodes = boundaryPosition.node.childNodes;
    +                    boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
    +                    boundaryParent = boundaryPosition.node;
    +                }
    +
    +                // Position the range immediately before the node containing the boundary
    +                workingNode = doc.createElement("span");
    +
    +                // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within
    +                // the element rather than immediately before or after it
    +                workingNode.innerHTML = "&#feff;";
    +
    +                // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
    +                // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
    +                if (boundaryNode) {
    +                    boundaryParent.insertBefore(workingNode, boundaryNode);
    +                } else {
    +                    boundaryParent.appendChild(workingNode);
    +                }
    +
    +                workingRange.moveToElementText(workingNode);
    +                workingRange.collapse(!isStart);
    +
    +                // Clean up
    +                boundaryParent.removeChild(workingNode);
    +
    +                // Move the working range to the text offset, if required
    +                if (nodeIsDataNode) {
    +                    workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
    +                }
    +
    +                return workingRange;
    +            };
    +
    +            /*------------------------------------------------------------------------------------------------------------*/
    +
    +            // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
    +            // prototype
    +
    +            WrappedTextRange = function(textRange) {
    +                this.textRange = textRange;
    +                this.refresh();
    +            };
    +
    +            WrappedTextRange.prototype = new DomRange(document);
    +
    +            WrappedTextRange.prototype.refresh = function() {
    +                var start, end, startBoundary;
    +
    +                // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
    +                var rangeContainerElement = getTextRangeContainerElement(this.textRange);
    +
    +                if (textRangeIsCollapsed(this.textRange)) {
    +                    end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true,
    +                        true).boundaryPosition;
    +                } else {
    +                    startBoundary = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
    +                    start = startBoundary.boundaryPosition;
    +
    +                    // An optimization used here is that if the start and end boundaries have the same parent element, the
    +                    // search scope for the end boundary can be limited to exclude the portion of the element that precedes
    +                    // the start boundary
    +                    end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false,
    +                        startBoundary.nodeInfo).boundaryPosition;
    +                }
    +
    +                this.setStart(start.node, start.offset);
    +                this.setEnd(end.node, end.offset);
    +            };
    +
    +            WrappedTextRange.prototype.getName = function() {
    +                return "WrappedTextRange";
    +            };
    +
    +            DomRange.copyComparisonConstants(WrappedTextRange);
    +
    +            var rangeToTextRange = function(range) {
    +                if (range.collapsed) {
    +                    return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
    +                } else {
    +                    var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
    +                    var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
    +                    var textRange = getBody( DomRange.getRangeDocument(range) ).createTextRange();
    +                    textRange.setEndPoint("StartToStart", startRange);
    +                    textRange.setEndPoint("EndToEnd", endRange);
    +                    return textRange;
    +                }
    +            };
    +
    +            WrappedTextRange.rangeToTextRange = rangeToTextRange;
    +
    +            WrappedTextRange.prototype.toTextRange = function() {
    +                return rangeToTextRange(this);
    +            };
    +
    +            api.WrappedTextRange = WrappedTextRange;
    +
    +            // IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which
    +            // implementation to use by default.
    +            if (!api.features.implementsDomRange || api.config.preferTextRange) {
    +                // Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work
    +                var globalObj = (function() { return this; })();
    +                if (typeof globalObj.Range == "undefined") {
    +                    globalObj.Range = WrappedTextRange;
    +                }
    +
    +                api.createNativeRange = function(doc) {
    +                    doc = getContentDocument(doc, module, "createNativeRange");
    +                    return getBody(doc).createTextRange();
    +                };
    +
    +                api.WrappedRange = WrappedTextRange;
    +            }
    +        }
    +
    +        api.createRange = function(doc) {
    +            doc = getContentDocument(doc, module, "createRange");
    +            return new api.WrappedRange(api.createNativeRange(doc));
    +        };
    +
    +        api.createRangyRange = function(doc) {
    +            doc = getContentDocument(doc, module, "createRangyRange");
    +            return new DomRange(doc);
    +        };
    +
    +        api.createIframeRange = function(iframeEl) {
    +            module.deprecationNotice("createIframeRange()", "createRange(iframeEl)");
    +            return api.createRange(iframeEl);
    +        };
    +
    +        api.createIframeRangyRange = function(iframeEl) {
    +            module.deprecationNotice("createIframeRangyRange()", "createRangyRange(iframeEl)");
    +            return api.createRangyRange(iframeEl);
    +        };
    +
    +        api.addShimListener(function(win) {
    +            var doc = win.document;
    +            if (typeof doc.createRange == "undefined") {
    +                doc.createRange = function() {
    +                    return api.createRange(doc);
    +                };
    +            }
    +            doc = win = null;
    +        });
    +    });
    +
    +    /*----------------------------------------------------------------------------------------------------------------*/
    +
    +    // This module creates a selection object wrapper that conforms as closely as possible to the Selection specification
    +    // in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections)
    +    api.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], function(api, module) {
    +        api.config.checkSelectionRanges = true;
    +
    +        var BOOLEAN = "boolean";
    +        var NUMBER = "number";
    +        var dom = api.dom;
    +        var util = api.util;
    +        var isHostMethod = util.isHostMethod;
    +        var DomRange = api.DomRange;
    +        var WrappedRange = api.WrappedRange;
    +        var DOMException = api.DOMException;
    +        var DomPosition = dom.DomPosition;
    +        var getNativeSelection;
    +        var selectionIsCollapsed;
    +        var features = api.features;
    +        var CONTROL = "Control";
    +        var getDocument = dom.getDocument;
    +        var getBody = dom.getBody;
    +        var rangesEqual = DomRange.rangesEqual;
    +
    +
    +        // Utility function to support direction parameters in the API that may be a string ("backward" or "forward") or a
    +        // Boolean (true for backwards).
    +        function isDirectionBackward(dir) {
    +            return (typeof dir == "string") ? /^backward(s)?$/i.test(dir) : !!dir;
    +        }
    +
    +        function getWindow(win, methodName) {
    +            if (!win) {
    +                return window;
    +            } else if (dom.isWindow(win)) {
    +                return win;
    +            } else if (win instanceof WrappedSelection) {
    +                return win.win;
    +            } else {
    +                var doc = dom.getContentDocument(win, module, methodName);
    +                return dom.getWindow(doc);
    +            }
    +        }
    +
    +        function getWinSelection(winParam) {
    +            return getWindow(winParam, "getWinSelection").getSelection();
    +        }
    +
    +        function getDocSelection(winParam) {
    +            return getWindow(winParam, "getDocSelection").document.selection;
    +        }
    +        
    +        function winSelectionIsBackward(sel) {
    +            var backward = false;
    +            if (sel.anchorNode) {
    +                backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
    +            }
    +            return backward;
    +        }
    +
    +        // Test for the Range/TextRange and Selection features required
    +        // Test for ability to retrieve selection
    +        var implementsWinGetSelection = isHostMethod(window, "getSelection"),
    +            implementsDocSelection = util.isHostObject(document, "selection");
    +
    +        features.implementsWinGetSelection = implementsWinGetSelection;
    +        features.implementsDocSelection = implementsDocSelection;
    +
    +        var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
    +
    +        if (useDocumentSelection) {
    +            getNativeSelection = getDocSelection;
    +            api.isSelectionValid = function(winParam) {
    +                var doc = getWindow(winParam, "isSelectionValid").document, nativeSel = doc.selection;
    +
    +                // Check whether the selection TextRange is actually contained within the correct document
    +                return (nativeSel.type != "None" || getDocument(nativeSel.createRange().parentElement()) == doc);
    +            };
    +        } else if (implementsWinGetSelection) {
    +            getNativeSelection = getWinSelection;
    +            api.isSelectionValid = function() {
    +                return true;
    +            };
    +        } else {
    +            module.fail("Neither document.selection or window.getSelection() detected.");
    +        }
    +
    +        api.getNativeSelection = getNativeSelection;
    +
    +        var testSelection = getNativeSelection();
    +        var testRange = api.createNativeRange(document);
    +        var body = getBody(document);
    +
    +        // Obtaining a range from a selection
    +        var selectionHasAnchorAndFocus = util.areHostProperties(testSelection,
    +            ["anchorNode", "focusNode", "anchorOffset", "focusOffset"]);
    +
    +        features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
    +
    +        // Test for existence of native selection extend() method
    +        var selectionHasExtend = isHostMethod(testSelection, "extend");
    +        features.selectionHasExtend = selectionHasExtend;
    +        
    +        // Test if rangeCount exists
    +        var selectionHasRangeCount = (typeof testSelection.rangeCount == NUMBER);
    +        features.selectionHasRangeCount = selectionHasRangeCount;
    +
    +        var selectionSupportsMultipleRanges = false;
    +        var collapsedNonEditableSelectionsSupported = true;
    +
    +        var addRangeBackwardToNative = selectionHasExtend ?
    +            function(nativeSelection, range) {
    +                var doc = DomRange.getRangeDocument(range);
    +                var endRange = api.createRange(doc);
    +                endRange.collapseToPoint(range.endContainer, range.endOffset);
    +                nativeSelection.addRange(getNativeRange(endRange));
    +                nativeSelection.extend(range.startContainer, range.startOffset);
    +            } : null;
    +
    +        if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
    +                typeof testSelection.rangeCount == NUMBER && features.implementsDomRange) {
    +
    +            (function() {
    +                // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are
    +                // performed on the current document's selection. See issue 109.
    +
    +                // Note also that if a selection previously existed, it is wiped by these tests. This should usually be fine
    +                // because initialization usually happens when the document loads, but could be a problem for a script that
    +                // loads and initializes Rangy later. If anyone complains, code could be added to save and restore the
    +                // selection.
    +                var sel = window.getSelection();
    +                if (sel) {
    +                    // Store the current selection
    +                    var originalSelectionRangeCount = sel.rangeCount;
    +                    var selectionHasMultipleRanges = (originalSelectionRangeCount > 1);
    +                    var originalSelectionRanges = [];
    +                    var originalSelectionBackward = winSelectionIsBackward(sel); 
    +                    for (var i = 0; i < originalSelectionRangeCount; ++i) {
    +                        originalSelectionRanges[i] = sel.getRangeAt(i);
    +                    }
    +                    
    +                    // Create some test elements
    +                    var body = getBody(document);
    +                    var testEl = body.appendChild( document.createElement("div") );
    +                    testEl.contentEditable = "false";
    +                    var textNode = testEl.appendChild( document.createTextNode("\u00a0\u00a0\u00a0") );
    +
    +                    // Test whether the native selection will allow a collapsed selection within a non-editable element
    +                    var r1 = document.createRange();
    +
    +                    r1.setStart(textNode, 1);
    +                    r1.collapse(true);
    +                    sel.addRange(r1);
    +                    collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
    +                    sel.removeAllRanges();
    +
    +                    // Test whether the native selection is capable of supporting multiple ranges.
    +                    if (!selectionHasMultipleRanges) {
    +                        // Doing the original feature test here in Chrome 36 (and presumably later versions) prints a
    +                        // console error of "Discontiguous selection is not supported." that cannot be suppressed. There's
    +                        // nothing we can do about this while retaining the feature test so we have to resort to a browser
    +                        // sniff. I'm not happy about it. See
    +                        // https://code.google.com/p/chromium/issues/detail?id=399791
    +                        var chromeMatch = window.navigator.appVersion.match(/Chrome\/(.*?) /);
    +                        if (chromeMatch && parseInt(chromeMatch[1]) >= 36) {
    +                            selectionSupportsMultipleRanges = false;
    +                        } else {
    +                            var r2 = r1.cloneRange();
    +                            r1.setStart(textNode, 0);
    +                            r2.setEnd(textNode, 3);
    +                            r2.setStart(textNode, 2);
    +                            sel.addRange(r1);
    +                            sel.addRange(r2);
    +                            selectionSupportsMultipleRanges = (sel.rangeCount == 2);
    +                        }
    +                    }
    +
    +                    // Clean up
    +                    body.removeChild(testEl);
    +                    sel.removeAllRanges();
    +
    +                    for (i = 0; i < originalSelectionRangeCount; ++i) {
    +                        if (i == 0 && originalSelectionBackward) {
    +                            if (addRangeBackwardToNative) {
    +                                addRangeBackwardToNative(sel, originalSelectionRanges[i]);
    +                            } else {
    +                                api.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend");
    +                                sel.addRange(originalSelectionRanges[i]);
    +                            }
    +                        } else {
    +                            sel.addRange(originalSelectionRanges[i]);
    +                        }
    +                    }
    +                }
    +            })();
    +        }
    +
    +        features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
    +        features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
    +
    +        // ControlRanges
    +        var implementsControlRange = false, testControlRange;
    +
    +        if (body && isHostMethod(body, "createControlRange")) {
    +            testControlRange = body.createControlRange();
    +            if (util.areHostProperties(testControlRange, ["item", "add"])) {
    +                implementsControlRange = true;
    +            }
    +        }
    +        features.implementsControlRange = implementsControlRange;
    +
    +        // Selection collapsedness
    +        if (selectionHasAnchorAndFocus) {
    +            selectionIsCollapsed = function(sel) {
    +                return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
    +            };
    +        } else {
    +            selectionIsCollapsed = function(sel) {
    +                return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
    +            };
    +        }
    +
    +        function updateAnchorAndFocusFromRange(sel, range, backward) {
    +            var anchorPrefix = backward ? "end" : "start", focusPrefix = backward ? "start" : "end";
    +            sel.anchorNode = range[anchorPrefix + "Container"];
    +            sel.anchorOffset = range[anchorPrefix + "Offset"];
    +            sel.focusNode = range[focusPrefix + "Container"];
    +            sel.focusOffset = range[focusPrefix + "Offset"];
    +        }
    +
    +        function updateAnchorAndFocusFromNativeSelection(sel) {
    +            var nativeSel = sel.nativeSelection;
    +            sel.anchorNode = nativeSel.anchorNode;
    +            sel.anchorOffset = nativeSel.anchorOffset;
    +            sel.focusNode = nativeSel.focusNode;
    +            sel.focusOffset = nativeSel.focusOffset;
    +        }
    +
    +        function updateEmptySelection(sel) {
    +            sel.anchorNode = sel.focusNode = null;
    +            sel.anchorOffset = sel.focusOffset = 0;
    +            sel.rangeCount = 0;
    +            sel.isCollapsed = true;
    +            sel._ranges.length = 0;
    +        }
    +
    +        function getNativeRange(range) {
    +            var nativeRange;
    +            if (range instanceof DomRange) {
    +                nativeRange = api.createNativeRange(range.getDocument());
    +                nativeRange.setEnd(range.endContainer, range.endOffset);
    +                nativeRange.setStart(range.startContainer, range.startOffset);
    +            } else if (range instanceof WrappedRange) {
    +                nativeRange = range.nativeRange;
    +            } else if (features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
    +                nativeRange = range;
    +            }
    +            return nativeRange;
    +        }
    +
    +        function rangeContainsSingleElement(rangeNodes) {
    +            if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
    +                return false;
    +            }
    +            for (var i = 1, len = rangeNodes.length; i < len; ++i) {
    +                if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
    +                    return false;
    +                }
    +            }
    +            return true;
    +        }
    +
    +        function getSingleElementFromRange(range) {
    +            var nodes = range.getNodes();
    +            if (!rangeContainsSingleElement(nodes)) {
    +                throw module.createError("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
    +            }
    +            return nodes[0];
    +        }
    +
    +        // Simple, quick test which only needs to distinguish between a TextRange and a ControlRange
    +        function isTextRange(range) {
    +            return !!range && typeof range.text != "undefined";
    +        }
    +
    +        function updateFromTextRange(sel, range) {
    +            // Create a Range from the selected TextRange
    +            var wrappedRange = new WrappedRange(range);
    +            sel._ranges = [wrappedRange];
    +
    +            updateAnchorAndFocusFromRange(sel, wrappedRange, false);
    +            sel.rangeCount = 1;
    +            sel.isCollapsed = wrappedRange.collapsed;
    +        }
    +
    +        function updateControlSelection(sel) {
    +            // Update the wrapped selection based on what's now in the native selection
    +            sel._ranges.length = 0;
    +            if (sel.docSelection.type == "None") {
    +                updateEmptySelection(sel);
    +            } else {
    +                var controlRange = sel.docSelection.createRange();
    +                if (isTextRange(controlRange)) {
    +                    // This case (where the selection type is "Control" and calling createRange() on the selection returns
    +                    // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
    +                    // ControlRange have been removed from the ControlRange and removed from the document.
    +                    updateFromTextRange(sel, controlRange);
    +                } else {
    +                    sel.rangeCount = controlRange.length;
    +                    var range, doc = getDocument(controlRange.item(0));
    +                    for (var i = 0; i < sel.rangeCount; ++i) {
    +                        range = api.createRange(doc);
    +                        range.selectNode(controlRange.item(i));
    +                        sel._ranges.push(range);
    +                    }
    +                    sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
    +                    updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
    +                }
    +            }
    +        }
    +
    +        function addRangeToControlSelection(sel, range) {
    +            var controlRange = sel.docSelection.createRange();
    +            var rangeElement = getSingleElementFromRange(range);
    +
    +            // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
    +            // contained by the supplied range
    +            var doc = getDocument(controlRange.item(0));
    +            var newControlRange = getBody(doc).createControlRange();
    +            for (var i = 0, len = controlRange.length; i < len; ++i) {
    +                newControlRange.add(controlRange.item(i));
    +            }
    +            try {
    +                newControlRange.add(rangeElement);
    +            } catch (ex) {
    +                throw module.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
    +            }
    +            newControlRange.select();
    +
    +            // Update the wrapped selection based on what's now in the native selection
    +            updateControlSelection(sel);
    +        }
    +
    +        var getSelectionRangeAt;
    +
    +        if (isHostMethod(testSelection, "getRangeAt")) {
    +            // try/catch is present because getRangeAt() must have thrown an error in some browser and some situation.
    +            // Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a
    +            // lesson to us all, especially me.
    +            getSelectionRangeAt = function(sel, index) {
    +                try {
    +                    return sel.getRangeAt(index);
    +                } catch (ex) {
    +                    return null;
    +                }
    +            };
    +        } else if (selectionHasAnchorAndFocus) {
    +            getSelectionRangeAt = function(sel) {
    +                var doc = getDocument(sel.anchorNode);
    +                var range = api.createRange(doc);
    +                range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset);
    +
    +                // Handle the case when the selection was selected backwards (from the end to the start in the
    +                // document)
    +                if (range.collapsed !== this.isCollapsed) {
    +                    range.setStartAndEnd(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset);
    +                }
    +
    +                return range;
    +            };
    +        }
    +
    +        function WrappedSelection(selection, docSelection, win) {
    +            this.nativeSelection = selection;
    +            this.docSelection = docSelection;
    +            this._ranges = [];
    +            this.win = win;
    +            this.refresh();
    +        }
    +
    +        WrappedSelection.prototype = api.selectionPrototype;
    +
    +        function deleteProperties(sel) {
    +            sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null;
    +            sel.rangeCount = sel.anchorOffset = sel.focusOffset = 0;
    +            sel.detached = true;
    +        }
    +
    +        var cachedRangySelections = [];
    +
    +        function actOnCachedSelection(win, action) {
    +            var i = cachedRangySelections.length, cached, sel;
    +            while (i--) {
    +                cached = cachedRangySelections[i];
    +                sel = cached.selection;
    +                if (action == "deleteAll") {
    +                    deleteProperties(sel);
    +                } else if (cached.win == win) {
    +                    if (action == "delete") {
    +                        cachedRangySelections.splice(i, 1);
    +                        return true;
    +                    } else {
    +                        return sel;
    +                    }
    +                }
    +            }
    +            if (action == "deleteAll") {
    +                cachedRangySelections.length = 0;
    +            }
    +            return null;
    +        }
    +
    +        var getSelection = function(win) {
    +            // Check if the parameter is a Rangy Selection object
    +            if (win && win instanceof WrappedSelection) {
    +                win.refresh();
    +                return win;
    +            }
    +
    +            win = getWindow(win, "getNativeSelection");
    +
    +            var sel = actOnCachedSelection(win);
    +            var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
    +            if (sel) {
    +                sel.nativeSelection = nativeSel;
    +                sel.docSelection = docSel;
    +                sel.refresh();
    +            } else {
    +                sel = new WrappedSelection(nativeSel, docSel, win);
    +                cachedRangySelections.push( { win: win, selection: sel } );
    +            }
    +            return sel;
    +        };
    +
    +        api.getSelection = getSelection;
    +
    +        api.getIframeSelection = function(iframeEl) {
    +            module.deprecationNotice("getIframeSelection()", "getSelection(iframeEl)");
    +            return api.getSelection(dom.getIframeWindow(iframeEl));
    +        };
    +
    +        var selProto = WrappedSelection.prototype;
    +
    +        function createControlSelection(sel, ranges) {
    +            // Ensure that the selection becomes of type "Control"
    +            var doc = getDocument(ranges[0].startContainer);
    +            var controlRange = getBody(doc).createControlRange();
    +            for (var i = 0, el, len = ranges.length; i < len; ++i) {
    +                el = getSingleElementFromRange(ranges[i]);
    +                try {
    +                    controlRange.add(el);
    +                } catch (ex) {
    +                    throw module.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)");
    +                }
    +            }
    +            controlRange.select();
    +
    +            // Update the wrapped selection based on what's now in the native selection
    +            updateControlSelection(sel);
    +        }
    +
    +        // Selecting a range
    +        if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
    +            selProto.removeAllRanges = function() {
    +                this.nativeSelection.removeAllRanges();
    +                updateEmptySelection(this);
    +            };
    +
    +            var addRangeBackward = function(sel, range) {
    +                addRangeBackwardToNative(sel.nativeSelection, range);
    +                sel.refresh();
    +            };
    +
    +            if (selectionHasRangeCount) {
    +                selProto.addRange = function(range, direction) {
    +                    if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
    +                        addRangeToControlSelection(this, range);
    +                    } else {
    +                        if (isDirectionBackward(direction) && selectionHasExtend) {
    +                            addRangeBackward(this, range);
    +                        } else {
    +                            var previousRangeCount;
    +                            if (selectionSupportsMultipleRanges) {
    +                                previousRangeCount = this.rangeCount;
    +                            } else {
    +                                this.removeAllRanges();
    +                                previousRangeCount = 0;
    +                            }
    +                            // Clone the native range so that changing the selected range does not affect the selection.
    +                            // This is contrary to the spec but is the only way to achieve consistency between browsers. See
    +                            // issue 80.
    +                            this.nativeSelection.addRange(getNativeRange(range).cloneRange());
    +
    +                            // Check whether adding the range was successful
    +                            this.rangeCount = this.nativeSelection.rangeCount;
    +
    +                            if (this.rangeCount == previousRangeCount + 1) {
    +                                // The range was added successfully
    +
    +                                // Check whether the range that we added to the selection is reflected in the last range extracted from
    +                                // the selection
    +                                if (api.config.checkSelectionRanges) {
    +                                    var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
    +                                    if (nativeRange && !rangesEqual(nativeRange, range)) {
    +                                        // Happens in WebKit with, for example, a selection placed at the start of a text node
    +                                        range = new WrappedRange(nativeRange);
    +                                    }
    +                                }
    +                                this._ranges[this.rangeCount - 1] = range;
    +                                updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection));
    +                                this.isCollapsed = selectionIsCollapsed(this);
    +                            } else {
    +                                // The range was not added successfully. The simplest thing is to refresh
    +                                this.refresh();
    +                            }
    +                        }
    +                    }
    +                };
    +            } else {
    +                selProto.addRange = function(range, direction) {
    +                    if (isDirectionBackward(direction) && selectionHasExtend) {
    +                        addRangeBackward(this, range);
    +                    } else {
    +                        this.nativeSelection.addRange(getNativeRange(range));
    +                        this.refresh();
    +                    }
    +                };
    +            }
    +
    +            selProto.setRanges = function(ranges) {
    +                if (implementsControlRange && implementsDocSelection && ranges.length > 1) {
    +                    createControlSelection(this, ranges);
    +                } else {
    +                    this.removeAllRanges();
    +                    for (var i = 0, len = ranges.length; i < len; ++i) {
    +                        this.addRange(ranges[i]);
    +                    }
    +                }
    +            };
    +        } else if (isHostMethod(testSelection, "empty") && isHostMethod(testRange, "select") &&
    +                   implementsControlRange && useDocumentSelection) {
    +
    +            selProto.removeAllRanges = function() {
    +                // Added try/catch as fix for issue #21
    +                try {
    +                    this.docSelection.empty();
    +
    +                    // Check for empty() not working (issue #24)
    +                    if (this.docSelection.type != "None") {
    +                        // Work around failure to empty a control selection by instead selecting a TextRange and then
    +                        // calling empty()
    +                        var doc;
    +                        if (this.anchorNode) {
    +                            doc = getDocument(this.anchorNode);
    +                        } else if (this.docSelection.type == CONTROL) {
    +                            var controlRange = this.docSelection.createRange();
    +                            if (controlRange.length) {
    +                                doc = getDocument( controlRange.item(0) );
    +                            }
    +                        }
    +                        if (doc) {
    +                            var textRange = getBody(doc).createTextRange();
    +                            textRange.select();
    +                            this.docSelection.empty();
    +                        }
    +                    }
    +                } catch(ex) {}
    +                updateEmptySelection(this);
    +            };
    +
    +            selProto.addRange = function(range) {
    +                if (this.docSelection.type == CONTROL) {
    +                    addRangeToControlSelection(this, range);
    +                } else {
    +                    api.WrappedTextRange.rangeToTextRange(range).select();
    +                    this._ranges[0] = range;
    +                    this.rangeCount = 1;
    +                    this.isCollapsed = this._ranges[0].collapsed;
    +                    updateAnchorAndFocusFromRange(this, range, false);
    +                }
    +            };
    +
    +            selProto.setRanges = function(ranges) {
    +                this.removeAllRanges();
    +                var rangeCount = ranges.length;
    +                if (rangeCount > 1) {
    +                    createControlSelection(this, ranges);
    +                } else if (rangeCount) {
    +                    this.addRange(ranges[0]);
    +                }
    +            };
    +        } else {
    +            module.fail("No means of selecting a Range or TextRange was found");
    +            return false;
    +        }
    +
    +        selProto.getRangeAt = function(index) {
    +            if (index < 0 || index >= this.rangeCount) {
    +                throw new DOMException("INDEX_SIZE_ERR");
    +            } else {
    +                // Clone the range to preserve selection-range independence. See issue 80.
    +                return this._ranges[index].cloneRange();
    +            }
    +        };
    +
    +        var refreshSelection;
    +
    +        if (useDocumentSelection) {
    +            refreshSelection = function(sel) {
    +                var range;
    +                if (api.isSelectionValid(sel.win)) {
    +                    range = sel.docSelection.createRange();
    +                } else {
    +                    range = getBody(sel.win.document).createTextRange();
    +                    range.collapse(true);
    +                }
    +
    +                if (sel.docSelection.type == CONTROL) {
    +                    updateControlSelection(sel);
    +                } else if (isTextRange(range)) {
    +                    updateFromTextRange(sel, range);
    +                } else {
    +                    updateEmptySelection(sel);
    +                }
    +            };
    +        } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == NUMBER) {
    +            refreshSelection = function(sel) {
    +                if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
    +                    updateControlSelection(sel);
    +                } else {
    +                    sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
    +                    if (sel.rangeCount) {
    +                        for (var i = 0, len = sel.rangeCount; i < len; ++i) {
    +                            sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
    +                        }
    +                        updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackward(sel.nativeSelection));
    +                        sel.isCollapsed = selectionIsCollapsed(sel);
    +                    } else {
    +                        updateEmptySelection(sel);
    +                    }
    +                }
    +            };
    +        } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && features.implementsDomRange) {
    +            refreshSelection = function(sel) {
    +                var range, nativeSel = sel.nativeSelection;
    +                if (nativeSel.anchorNode) {
    +                    range = getSelectionRangeAt(nativeSel, 0);
    +                    sel._ranges = [range];
    +                    sel.rangeCount = 1;
    +                    updateAnchorAndFocusFromNativeSelection(sel);
    +                    sel.isCollapsed = selectionIsCollapsed(sel);
    +                } else {
    +                    updateEmptySelection(sel);
    +                }
    +            };
    +        } else {
    +            module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
    +            return false;
    +        }
    +
    +        selProto.refresh = function(checkForChanges) {
    +            var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
    +            var oldAnchorNode = this.anchorNode, oldAnchorOffset = this.anchorOffset;
    +
    +            refreshSelection(this);
    +            if (checkForChanges) {
    +                // Check the range count first
    +                var i = oldRanges.length;
    +                if (i != this._ranges.length) {
    +                    return true;
    +                }
    +
    +                // Now check the direction. Checking the anchor position is the same is enough since we're checking all the
    +                // ranges after this
    +                if (this.anchorNode != oldAnchorNode || this.anchorOffset != oldAnchorOffset) {
    +                    return true;
    +                }
    +
    +                // Finally, compare each range in turn
    +                while (i--) {
    +                    if (!rangesEqual(oldRanges[i], this._ranges[i])) {
    +                        return true;
    +                    }
    +                }
    +                return false;
    +            }
    +        };
    +
    +        // Removal of a single range
    +        var removeRangeManually = function(sel, range) {
    +            var ranges = sel.getAllRanges();
    +            sel.removeAllRanges();
    +            for (var i = 0, len = ranges.length; i < len; ++i) {
    +                if (!rangesEqual(range, ranges[i])) {
    +                    sel.addRange(ranges[i]);
    +                }
    +            }
    +            if (!sel.rangeCount) {
    +                updateEmptySelection(sel);
    +            }
    +        };
    +
    +        if (implementsControlRange && implementsDocSelection) {
    +            selProto.removeRange = function(range) {
    +                if (this.docSelection.type == CONTROL) {
    +                    var controlRange = this.docSelection.createRange();
    +                    var rangeElement = getSingleElementFromRange(range);
    +
    +                    // Create a new ControlRange containing all the elements in the selected ControlRange minus the
    +                    // element contained by the supplied range
    +                    var doc = getDocument(controlRange.item(0));
    +                    var newControlRange = getBody(doc).createControlRange();
    +                    var el, removed = false;
    +                    for (var i = 0, len = controlRange.length; i < len; ++i) {
    +                        el = controlRange.item(i);
    +                        if (el !== rangeElement || removed) {
    +                            newControlRange.add(controlRange.item(i));
    +                        } else {
    +                            removed = true;
    +                        }
    +                    }
    +                    newControlRange.select();
    +
    +                    // Update the wrapped selection based on what's now in the native selection
    +                    updateControlSelection(this);
    +                } else {
    +                    removeRangeManually(this, range);
    +                }
    +            };
    +        } else {
    +            selProto.removeRange = function(range) {
    +                removeRangeManually(this, range);
    +            };
    +        }
    +
    +        // Detecting if a selection is backward
    +        var selectionIsBackward;
    +        if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) {
    +            selectionIsBackward = winSelectionIsBackward;
    +
    +            selProto.isBackward = function() {
    +                return selectionIsBackward(this);
    +            };
    +        } else {
    +            selectionIsBackward = selProto.isBackward = function() {
    +                return false;
    +            };
    +        }
    +
    +        // Create an alias for backwards compatibility. From 1.3, everything is "backward" rather than "backwards"
    +        selProto.isBackwards = selProto.isBackward;
    +
    +        // Selection stringifier
    +        // This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation.
    +        // The current spec does not yet define this method.
    +        selProto.toString = function() {
    +            var rangeTexts = [];
    +            for (var i = 0, len = this.rangeCount; i < len; ++i) {
    +                rangeTexts[i] = "" + this._ranges[i];
    +            }
    +            return rangeTexts.join("");
    +        };
    +
    +        function assertNodeInSameDocument(sel, node) {
    +            if (sel.win.document != getDocument(node)) {
    +                throw new DOMException("WRONG_DOCUMENT_ERR");
    +            }
    +        }
    +
    +        // No current browser conforms fully to the spec for this method, so Rangy's own method is always used
    +        selProto.collapse = function(node, offset) {
    +            assertNodeInSameDocument(this, node);
    +            var range = api.createRange(node);
    +            range.collapseToPoint(node, offset);
    +            this.setSingleRange(range);
    +            this.isCollapsed = true;
    +        };
    +
    +        selProto.collapseToStart = function() {
    +            if (this.rangeCount) {
    +                var range = this._ranges[0];
    +                this.collapse(range.startContainer, range.startOffset);
    +            } else {
    +                throw new DOMException("INVALID_STATE_ERR");
    +            }
    +        };
    +
    +        selProto.collapseToEnd = function() {
    +            if (this.rangeCount) {
    +                var range = this._ranges[this.rangeCount - 1];
    +                this.collapse(range.endContainer, range.endOffset);
    +            } else {
    +                throw new DOMException("INVALID_STATE_ERR");
    +            }
    +        };
    +
    +        // The spec is very specific on how selectAllChildren should be implemented so the native implementation is
    +        // never used by Rangy.
    +        selProto.selectAllChildren = function(node) {
    +            assertNodeInSameDocument(this, node);
    +            var range = api.createRange(node);
    +            range.selectNodeContents(node);
    +            this.setSingleRange(range);
    +        };
    +
    +        selProto.deleteFromDocument = function() {
    +            // Sepcial behaviour required for IE's control selections
    +            if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
    +                var controlRange = this.docSelection.createRange();
    +                var element;
    +                while (controlRange.length) {
    +                    element = controlRange.item(0);
    +                    controlRange.remove(element);
    +                    element.parentNode.removeChild(element);
    +                }
    +                this.refresh();
    +            } else if (this.rangeCount) {
    +                var ranges = this.getAllRanges();
    +                if (ranges.length) {
    +                    this.removeAllRanges();
    +                    for (var i = 0, len = ranges.length; i < len; ++i) {
    +                        ranges[i].deleteContents();
    +                    }
    +                    // The spec says nothing about what the selection should contain after calling deleteContents on each
    +                    // range. Firefox moves the selection to where the final selected range was, so we emulate that
    +                    this.addRange(ranges[len - 1]);
    +                }
    +            }
    +        };
    +
    +        // The following are non-standard extensions
    +        selProto.eachRange = function(func, returnValue) {
    +            for (var i = 0, len = this._ranges.length; i < len; ++i) {
    +                if ( func( this.getRangeAt(i) ) ) {
    +                    return returnValue;
    +                }
    +            }
    +        };
    +
    +        selProto.getAllRanges = function() {
    +            var ranges = [];
    +            this.eachRange(function(range) {
    +                ranges.push(range);
    +            });
    +            return ranges;
    +        };
    +
    +        selProto.setSingleRange = function(range, direction) {
    +            this.removeAllRanges();
    +            this.addRange(range, direction);
    +        };
    +
    +        selProto.callMethodOnEachRange = function(methodName, params) {
    +            var results = [];
    +            this.eachRange( function(range) {
    +                results.push( range[methodName].apply(range, params) );
    +            } );
    +            return results;
    +        };
    +        
    +        function createStartOrEndSetter(isStart) {
    +            return function(node, offset) {
    +                var range;
    +                if (this.rangeCount) {
    +                    range = this.getRangeAt(0);
    +                    range["set" + (isStart ? "Start" : "End")](node, offset);
    +                } else {
    +                    range = api.createRange(this.win.document);
    +                    range.setStartAndEnd(node, offset);
    +                }
    +                this.setSingleRange(range, this.isBackward());
    +            };
    +        }
    +
    +        selProto.setStart = createStartOrEndSetter(true);
    +        selProto.setEnd = createStartOrEndSetter(false);
    +        
    +        // Add select() method to Range prototype. Any existing selection will be removed.
    +        api.rangePrototype.select = function(direction) {
    +            getSelection( this.getDocument() ).setSingleRange(this, direction);
    +        };
    +
    +        selProto.changeEachRange = function(func) {
    +            var ranges = [];
    +            var backward = this.isBackward();
    +
    +            this.eachRange(function(range) {
    +                func(range);
    +                ranges.push(range);
    +            });
    +
    +            this.removeAllRanges();
    +            if (backward && ranges.length == 1) {
    +                this.addRange(ranges[0], "backward");
    +            } else {
    +                this.setRanges(ranges);
    +            }
    +        };
    +
    +        selProto.containsNode = function(node, allowPartial) {
    +            return this.eachRange( function(range) {
    +                return range.containsNode(node, allowPartial);
    +            }, true ) || false;
    +        };
    +
    +        selProto.getBookmark = function(containerNode) {
    +            return {
    +                backward: this.isBackward(),
    +                rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode])
    +            };
    +        };
    +
    +        selProto.moveToBookmark = function(bookmark) {
    +            var selRanges = [];
    +            for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) {
    +                range = api.createRange(this.win);
    +                range.moveToBookmark(rangeBookmark);
    +                selRanges.push(range);
    +            }
    +            if (bookmark.backward) {
    +                this.setSingleRange(selRanges[0], "backward");
    +            } else {
    +                this.setRanges(selRanges);
    +            }
    +        };
    +
    +        selProto.toHtml = function() {
    +            var rangeHtmls = [];
    +            this.eachRange(function(range) {
    +                rangeHtmls.push( DomRange.toHtml(range) );
    +            });
    +            return rangeHtmls.join("");
    +        };
    +
    +        if (features.implementsTextRange) {
    +            selProto.getNativeTextRange = function() {
    +                var sel, textRange;
    +                if ( (sel = this.docSelection) ) {
    +                    var range = sel.createRange();
    +                    if (isTextRange(range)) {
    +                        return range;
    +                    } else {
    +                        throw module.createError("getNativeTextRange: selection is a control selection"); 
    +                    }
    +                } else if (this.rangeCount > 0) {
    +                    return api.WrappedTextRange.rangeToTextRange( this.getRangeAt(0) );
    +                } else {
    +                    throw module.createError("getNativeTextRange: selection contains no range");
    +                }
    +            };
    +        }
    +
    +        function inspect(sel) {
    +            var rangeInspects = [];
    +            var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
    +            var focus = new DomPosition(sel.focusNode, sel.focusOffset);
    +            var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
    +
    +            if (typeof sel.rangeCount != "undefined") {
    +                for (var i = 0, len = sel.rangeCount; i < len; ++i) {
    +                    rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
    +                }
    +            }
    +            return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
    +                    ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
    +        }
    +
    +        selProto.getName = function() {
    +            return "WrappedSelection";
    +        };
    +
    +        selProto.inspect = function() {
    +            return inspect(this);
    +        };
    +
    +        selProto.detach = function() {
    +            actOnCachedSelection(this.win, "delete");
    +            deleteProperties(this);
    +        };
    +
    +        WrappedSelection.detachAll = function() {
    +            actOnCachedSelection(null, "deleteAll");
    +        };
    +
    +        WrappedSelection.inspect = inspect;
    +        WrappedSelection.isDirectionBackward = isDirectionBackward;
    +
    +        api.Selection = WrappedSelection;
    +
    +        api.selectionPrototype = selProto;
    +
    +        api.addShimListener(function(win) {
    +            if (typeof win.getSelection == "undefined") {
    +                win.getSelection = function() {
    +                    return getSelection(win);
    +                };
    +            }
    +            win = null;
    +        });
    +    });
    +    
    +
    +    /*----------------------------------------------------------------------------------------------------------------*/
    +
    +    return api;
    +}, this);;/**
    + * Selection save and restore module for Rangy.
    + * Saves and restores user selections using marker invisible elements in the DOM.
    + *
    + * Part of Rangy, a cross-browser JavaScript range and selection library
    + * http://code.google.com/p/rangy/
    + *
    + * Depends on Rangy core.
    + *
    + * Copyright 2014, Tim Down
    + * Licensed under the MIT license.
    + * Version: 1.3alpha.20140804
    + * Build date: 4 August 2014
    + */
    +(function(factory, global) {
    +    if (typeof define == "function" && define.amd) {
    +        // AMD. Register as an anonymous module with a dependency on Rangy.
    +        define(["rangy"], factory);
    +        /*
    +         } else if (typeof exports == "object") {
    +         // Node/CommonJS style for Browserify
    +         module.exports = factory;
    +         */
    +    } else {
    +        // No AMD or CommonJS support so we use the rangy global variable
    +        factory(global.rangy);
    +    }
    +})(function(rangy) {
    +    rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) {
    +        var dom = api.dom;
    +
    +        var markerTextChar = "\ufeff";
    +
    +        function gEBI(id, doc) {
    +            return (doc || document).getElementById(id);
    +        }
    +
    +        function insertRangeBoundaryMarker(range, atStart) {
    +            var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2);
    +            var markerEl;
    +            var doc = dom.getDocument(range.startContainer);
    +
    +            // Clone the Range and collapse to the appropriate boundary point
    +            var boundaryRange = range.cloneRange();
    +            boundaryRange.collapse(atStart);
    +
    +            // Create the marker element containing a single invisible character using DOM methods and insert it
    +            markerEl = doc.createElement("span");
    +            markerEl.id = markerId;
    +            markerEl.style.lineHeight = "0";
    +            markerEl.style.display = "none";
    +            markerEl.className = "rangySelectionBoundary";
    +            markerEl.appendChild(doc.createTextNode(markerTextChar));
    +
    +            boundaryRange.insertNode(markerEl);
    +            return markerEl;
    +        }
    +
    +        function setRangeBoundary(doc, range, markerId, atStart) {
    +            var markerEl = gEBI(markerId, doc);
    +            if (markerEl) {
    +                range[atStart ? "setStartBefore" : "setEndBefore"](markerEl);
    +                markerEl.parentNode.removeChild(markerEl);
    +            } else {
    +                module.warn("Marker element has been removed. Cannot restore selection.");
    +            }
    +        }
    +
    +        function compareRanges(r1, r2) {
    +            return r2.compareBoundaryPoints(r1.START_TO_START, r1);
    +        }
    +
    +        function saveRange(range, backward) {
    +            var startEl, endEl, doc = api.DomRange.getRangeDocument(range), text = range.toString();
    +
    +            if (range.collapsed) {
    +                endEl = insertRangeBoundaryMarker(range, false);
    +                return {
    +                    document: doc,
    +                    markerId: endEl.id,
    +                    collapsed: true
    +                };
    +            } else {
    +                endEl = insertRangeBoundaryMarker(range, false);
    +                startEl = insertRangeBoundaryMarker(range, true);
    +
    +                return {
    +                    document: doc,
    +                    startMarkerId: startEl.id,
    +                    endMarkerId: endEl.id,
    +                    collapsed: false,
    +                    backward: backward,
    +                    toString: function() {
    +                        return "original text: '" + text + "', new text: '" + range.toString() + "'";
    +                    }
    +                };
    +            }
    +        }
    +
    +        function restoreRange(rangeInfo, normalize) {
    +            var doc = rangeInfo.document;
    +            if (typeof normalize == "undefined") {
    +                normalize = true;
    +            }
    +            var range = api.createRange(doc);
    +            if (rangeInfo.collapsed) {
    +                var markerEl = gEBI(rangeInfo.markerId, doc);
    +                if (markerEl) {
    +                    markerEl.style.display = "inline";
    +                    var previousNode = markerEl.previousSibling;
    +
    +                    // Workaround for issue 17
    +                    if (previousNode && previousNode.nodeType == 3) {
    +                        markerEl.parentNode.removeChild(markerEl);
    +                        range.collapseToPoint(previousNode, previousNode.length);
    +                    } else {
    +                        range.collapseBefore(markerEl);
    +                        markerEl.parentNode.removeChild(markerEl);
    +                    }
    +                } else {
    +                    module.warn("Marker element has been removed. Cannot restore selection.");
    +                }
    +            } else {
    +                setRangeBoundary(doc, range, rangeInfo.startMarkerId, true);
    +                setRangeBoundary(doc, range, rangeInfo.endMarkerId, false);
    +            }
    +
    +            if (normalize) {
    +                range.normalizeBoundaries();
    +            }
    +
    +            return range;
    +        }
    +
    +        function saveRanges(ranges, backward) {
    +            var rangeInfos = [], range, doc;
    +
    +            // Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched
    +            ranges = ranges.slice(0);
    +            ranges.sort(compareRanges);
    +
    +            for (var i = 0, len = ranges.length; i < len; ++i) {
    +                rangeInfos[i] = saveRange(ranges[i], backward);
    +            }
    +
    +            // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie
    +            // between its markers
    +            for (i = len - 1; i >= 0; --i) {
    +                range = ranges[i];
    +                doc = api.DomRange.getRangeDocument(range);
    +                if (range.collapsed) {
    +                    range.collapseAfter(gEBI(rangeInfos[i].markerId, doc));
    +                } else {
    +                    range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc));
    +                    range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc));
    +                }
    +            }
    +
    +            return rangeInfos;
    +        }
    +
    +        function saveSelection(win) {
    +            if (!api.isSelectionValid(win)) {
    +                module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus.");
    +                return null;
    +            }
    +            var sel = api.getSelection(win);
    +            var ranges = sel.getAllRanges();
    +            var backward = (ranges.length == 1 && sel.isBackward());
    +
    +            var rangeInfos = saveRanges(ranges, backward);
    +
    +            // Ensure current selection is unaffected
    +            if (backward) {
    +                sel.setSingleRange(ranges[0], "backward");
    +            } else {
    +                sel.setRanges(ranges);
    +            }
    +
    +            return {
    +                win: win,
    +                rangeInfos: rangeInfos,
    +                restored: false
    +            };
    +        }
    +
    +        function restoreRanges(rangeInfos) {
    +            var ranges = [];
    +
    +            // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid
    +            // normalization affecting previously restored ranges.
    +            var rangeCount = rangeInfos.length;
    +
    +            for (var i = rangeCount - 1; i >= 0; i--) {
    +                ranges[i] = restoreRange(rangeInfos[i], true);
    +            }
    +
    +            return ranges;
    +        }
    +
    +        function restoreSelection(savedSelection, preserveDirection) {
    +            if (!savedSelection.restored) {
    +                var rangeInfos = savedSelection.rangeInfos;
    +                var sel = api.getSelection(savedSelection.win);
    +                var ranges = restoreRanges(rangeInfos), rangeCount = rangeInfos.length;
    +
    +                if (rangeCount == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backward) {
    +                    sel.removeAllRanges();
    +                    sel.addRange(ranges[0], true);
    +                } else {
    +                    sel.setRanges(ranges);
    +                }
    +
    +                savedSelection.restored = true;
    +            }
    +        }
    +
    +        function removeMarkerElement(doc, markerId) {
    +            var markerEl = gEBI(markerId, doc);
    +            if (markerEl) {
    +                markerEl.parentNode.removeChild(markerEl);
    +            }
    +        }
    +
    +        function removeMarkers(savedSelection) {
    +            var rangeInfos = savedSelection.rangeInfos;
    +            for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) {
    +                rangeInfo = rangeInfos[i];
    +                if (rangeInfo.collapsed) {
    +                    removeMarkerElement(savedSelection.doc, rangeInfo.markerId);
    +                } else {
    +                    removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId);
    +                    removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId);
    +                }
    +            }
    +        }
    +
    +        api.util.extend(api, {
    +            saveRange: saveRange,
    +            restoreRange: restoreRange,
    +            saveRanges: saveRanges,
    +            restoreRanges: restoreRanges,
    +            saveSelection: saveSelection,
    +            restoreSelection: restoreSelection,
    +            removeMarkerElement: removeMarkerElement,
    +            removeMarkers: removeMarkers
    +        });
    +    });
    +    
    +}, this);;/*
    +	Base.js, version 1.1a
    +	Copyright 2006-2010, Dean Edwards
    +	License: http://www.opensource.org/licenses/mit-license.php
    +*/
    +
    +var Base = function() {
    +	// dummy
    +};
    +
    +Base.extend = function(_instance, _static) { // subclass
    +	var extend = Base.prototype.extend;
    +	
    +	// build the prototype
    +	Base._prototyping = true;
    +	var proto = new this;
    +	extend.call(proto, _instance);
    +  proto.base = function() {
    +    // call this method from any other method to invoke that method's ancestor
    +  };
    +	delete Base._prototyping;
    +	
    +	// create the wrapper for the constructor function
    +	//var constructor = proto.constructor.valueOf(); //-dean
    +	var constructor = proto.constructor;
    +	var klass = proto.constructor = function() {
    +		if (!Base._prototyping) {
    +			if (this._constructing || this.constructor == klass) { // instantiation
    +				this._constructing = true;
    +				constructor.apply(this, arguments);
    +				delete this._constructing;
    +			} else if (arguments[0] != null) { // casting
    +				return (arguments[0].extend || extend).call(arguments[0], proto);
    +			}
    +		}
    +	};
    +	
    +	// build the class interface
    +	klass.ancestor = this;
    +	klass.extend = this.extend;
    +	klass.forEach = this.forEach;
    +	klass.implement = this.implement;
    +	klass.prototype = proto;
    +	klass.toString = this.toString;
    +	klass.valueOf = function(type) {
    +		//return (type == "object") ? klass : constructor; //-dean
    +		return (type == "object") ? klass : constructor.valueOf();
    +	};
    +	extend.call(klass, _static);
    +	// class initialisation
    +	if (typeof klass.init == "function") klass.init();
    +	return klass;
    +};
    +
    +Base.prototype = {	
    +	extend: function(source, value) {
    +		if (arguments.length > 1) { // extending with a name/value pair
    +			var ancestor = this[source];
    +			if (ancestor && (typeof value == "function") && // overriding a method?
    +				// the valueOf() comparison is to avoid circular references
    +				(!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) &&
    +				/\bbase\b/.test(value)) {
    +				// get the underlying method
    +				var method = value.valueOf();
    +				// override
    +				value = function() {
    +					var previous = this.base || Base.prototype.base;
    +					this.base = ancestor;
    +					var returnValue = method.apply(this, arguments);
    +					this.base = previous;
    +					return returnValue;
    +				};
    +				// point to the underlying method
    +				value.valueOf = function(type) {
    +					return (type == "object") ? value : method;
    +				};
    +				value.toString = Base.toString;
    +			}
    +			this[source] = value;
    +		} else if (source) { // extending with an object literal
    +			var extend = Base.prototype.extend;
    +			// if this object has a customised extend method then use it
    +			if (!Base._prototyping && typeof this != "function") {
    +				extend = this.extend || extend;
    +			}
    +			var proto = {toSource: null};
    +			// do the "toString" and other methods manually
    +			var hidden = ["constructor", "toString", "valueOf"];
    +			// if we are prototyping then include the constructor
    +			var i = Base._prototyping ? 0 : 1;
    +			while (key = hidden[i++]) {
    +				if (source[key] != proto[key]) {
    +					extend.call(this, key, source[key]);
    +
    +				}
    +			}
    +			// copy each of the source object's properties to this object
    +			for (var key in source) {
    +				if (!proto[key]) extend.call(this, key, source[key]);
    +			}
    +		}
    +		return this;
    +	}
    +};
    +
    +// initialise
    +Base = Base.extend({
    +	constructor: function() {
    +		this.extend(arguments[0]);
    +	}
    +}, {
    +	ancestor: Object,
    +	version: "1.1",
    +	
    +	forEach: function(object, block, context) {
    +		for (var key in object) {
    +			if (this.prototype[key] === undefined) {
    +				block.call(context, object[key], key, object);
    +			}
    +		}
    +	},
    +		
    +	implement: function() {
    +		for (var i = 0; i < arguments.length; i++) {
    +			if (typeof arguments[i] == "function") {
    +				// if it's a function, call it
    +				arguments[i](this.prototype);
    +			} else {
    +				// add the interface using the extend method
    +				this.prototype.extend(arguments[i]);
    +			}
    +		}
    +		return this;
    +	},
    +	
    +	toString: function() {
    +		return String(this.valueOf());
    +	}
    +});;/**
    + * Detect browser support for specific features
    + */
    +wysihtml5.browser = (function() {
    +  var userAgent   = navigator.userAgent,
    +      testElement = document.createElement("div"),
    +      // Browser sniffing is unfortunately needed since some behaviors are impossible to feature detect
    +      isGecko     = userAgent.indexOf("Gecko")        !== -1 && userAgent.indexOf("KHTML") === -1,
    +      isWebKit    = userAgent.indexOf("AppleWebKit/") !== -1,
    +      isChrome    = userAgent.indexOf("Chrome/")      !== -1,
    +      isOpera     = userAgent.indexOf("Opera/")       !== -1;
    +
    +  function iosVersion(userAgent) {
    +    return +((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [undefined, 0])[1];
    +  }
    +
    +  function androidVersion(userAgent) {
    +    return +(userAgent.match(/android (\d+)/) || [undefined, 0])[1];
    +  }
    +
    +  function isIE(version, equation) {
    +    var rv = -1,
    +        re;
    +
    +    if (navigator.appName == 'Microsoft Internet Explorer') {
    +      re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
    +    } else if (navigator.appName == 'Netscape') {
    +      re = new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})");
    +    }
    +
    +    if (re && re.exec(navigator.userAgent) != null) {
    +      rv = parseFloat(RegExp.$1);
    +    }
    +
    +    if (rv === -1) { return false; }
    +    if (!version) { return true; }
    +    if (!equation) { return version === rv; }
    +    if (equation === "<") { return version < rv; }
    +    if (equation === ">") { return version > rv; }
    +    if (equation === "<=") { return version <= rv; }
    +    if (equation === ">=") { return version >= rv; }
    +  }
    +
    +  return {
    +    // Static variable needed, publicly accessible, to be able override it in unit tests
    +    USER_AGENT: userAgent,
    +
    +    /**
    +     * Exclude browsers that are not capable of displaying and handling
    +     * contentEditable as desired:
    +     *    - iPhone, iPad (tested iOS 4.2.2) and Android (tested 2.2) refuse to make contentEditables focusable
    +     *    - IE < 8 create invalid markup and crash randomly from time to time
    +     *
    +     * @return {Boolean}
    +     */
    +    supported: function() {
    +      var userAgent                   = this.USER_AGENT.toLowerCase(),
    +          // Essential for making html elements editable
    +          hasContentEditableSupport   = "contentEditable" in testElement,
    +          // Following methods are needed in order to interact with the contentEditable area
    +          hasEditingApiSupport        = document.execCommand && document.queryCommandSupported && document.queryCommandState,
    +          // document selector apis are only supported by IE 8+, Safari 4+, Chrome and Firefox 3.5+
    +          hasQuerySelectorSupport     = document.querySelector && document.querySelectorAll,
    +          // contentEditable is unusable in mobile browsers (tested iOS 4.2.2, Android 2.2, Opera Mobile, WebOS 3.05)
    +          isIncompatibleMobileBrowser = (this.isIos() && iosVersion(userAgent) < 5) || (this.isAndroid() && androidVersion(userAgent) < 4) || userAgent.indexOf("opera mobi") !== -1 || userAgent.indexOf("hpwos/") !== -1;
    +      return hasContentEditableSupport
    +        && hasEditingApiSupport
    +        && hasQuerySelectorSupport
    +        && !isIncompatibleMobileBrowser;
    +    },
    +
    +    isTouchDevice: function() {
    +      return this.supportsEvent("touchmove");
    +    },
    +
    +    isIos: function() {
    +      return (/ipad|iphone|ipod/i).test(this.USER_AGENT);
    +    },
    +
    +    isAndroid: function() {
    +      return this.USER_AGENT.indexOf("Android") !== -1;
    +    },
    +
    +    /**
    +     * Whether the browser supports sandboxed iframes
    +     * Currently only IE 6+ offers such feature ');return b.join("")})}},fileButton:function(b,a,d){if(!(3>arguments.length)){i.call(this,a);var e=this;a.validate&&(this.validate=a.validate);var c=CKEDITOR.tools.extend({},a),f=c.onClick;c.className=(c.className?c.className+" ":"")+"cke_dialog_ui_button";c.onClick=function(c){var d=
    +a["for"];if(!f||f.call(this,c)!==false){b.getContentElement(d[0],d[1]).submit();this.disable()}};b.on("load",function(){b.getContentElement(a["for"][0],a["for"][1])._.buttons.push(e)});CKEDITOR.ui.dialog.button.call(this,b,c,d)}},html:function(){var b=/^\s*<[\w:]+\s+([^>]*)?>/,a=/^(\s*<[\w:]+(?:\s+[^>]*)?)((?:.|\r|\n)+)$/,d=/\/$/;return function(e,c,f){if(!(3>arguments.length)){var h=[],g=c.html;"<"!=g.charAt(0)&&(g=""+g+"");var k=c.focus;if(k){var j=this.focus;this.focus=function(){("function"==
    +typeof k?k:j).call(this);this.fire("focus")};c.isFocusable&&(this.isFocusable=this.isFocusable);this.keyboardFocusable=!0}CKEDITOR.ui.dialog.uiElement.call(this,e,c,h,"span",null,null,"");h=h.join("").match(b);g=g.match(a)||["","",""];d.test(g[1])&&(g[1]=g[1].slice(0,-1),g[2]="/"+g[2]);f.push([g[1]," ",h[1]||"",g[2]].join(""))}}}(),fieldset:function(b,a,d,e,c){var f=c.label;this._={children:a};CKEDITOR.ui.dialog.uiElement.call(this,b,c,e,"fieldset",null,null,function(){var a=[];f&&a.push(""+f+"");for(var b=0;ba.getChildCount()?(new CKEDITOR.dom.text(b,CKEDITOR.document)).appendTo(a):a.getChild(0).$.nodeValue=b;return this},getLabel:function(){var b=
    +CKEDITOR.document.getById(this._.labelId);return!b||1>b.getChildCount()?"":b.getChild(0).getText()},eventProcessors:o},!0);CKEDITOR.ui.dialog.button.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.uiElement,{click:function(){return!this._.disabled?this.fire("click",{dialog:this._.dialog}):!1},enable:function(){this._.disabled=!1;var b=this.getElement();b&&b.removeClass("cke_disabled")},disable:function(){this._.disabled=!0;this.getElement().addClass("cke_disabled")},isVisible:function(){return this.getElement().getFirst().isVisible()},
    +isEnabled:function(){return!this._.disabled},eventProcessors:CKEDITOR.tools.extend({},CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors,{onClick:function(b,a){this.on("click",function(){a.apply(this,arguments)})}},!0),accessKeyUp:function(){this.click()},accessKeyDown:function(){this.focus()},keyboardFocusable:!0},!0);CKEDITOR.ui.dialog.textInput.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.labeledElement,{getInputElement:function(){return CKEDITOR.document.getById(this._.inputId)},
    +focus:function(){var b=this.selectParentTab();setTimeout(function(){var a=b.getInputElement();a&&a.$.focus()},0)},select:function(){var b=this.selectParentTab();setTimeout(function(){var a=b.getInputElement();a&&(a.$.focus(),a.$.select())},0)},accessKeyUp:function(){this.select()},setValue:function(b){!b&&(b="");return CKEDITOR.ui.dialog.uiElement.prototype.setValue.apply(this,arguments)},keyboardFocusable:!0},n,!0);CKEDITOR.ui.dialog.textarea.prototype=new CKEDITOR.ui.dialog.textInput;CKEDITOR.ui.dialog.select.prototype=
    +CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.labeledElement,{getInputElement:function(){return this._.select.getElement()},add:function(b,a,d){var e=new CKEDITOR.dom.element("option",this.getDialog().getParentEditor().document),c=this.getInputElement().$;e.$.text=b;e.$.value=void 0===a||null===a?b:a;void 0===d||null===d?CKEDITOR.env.ie?c.add(e.$):c.add(e.$,null):c.add(e.$,d);return this},remove:function(b){this.getInputElement().$.remove(b);return this},clear:function(){for(var b=this.getInputElement().$;0<
    +b.length;)b.remove(0);return this},keyboardFocusable:!0},n,!0);CKEDITOR.ui.dialog.checkbox.prototype=CKEDITOR.tools.extend(new CKEDITOR.ui.dialog.uiElement,{getInputElement:function(){return this._.checkbox.getElement()},setValue:function(b,a){this.getInputElement().$.checked=b;!a&&this.fire("change",{value:b})},getValue:function(){return this.getInputElement().$.checked},accessKeyUp:function(){this.setValue(!this.getValue())},eventProcessors:{onChange:function(b,a){if(!CKEDITOR.env.ie||8','
    + +

    + diff --git a/public/assets/plugins/ckeditor/plugins/wsc/dialogs/tmp.html b/public/assets/plugins/ckeditor/plugins/wsc/dialogs/tmp.html new file mode 100644 index 00000000..6e9b012e --- /dev/null +++ b/public/assets/plugins/ckeditor/plugins/wsc/dialogs/tmp.html @@ -0,0 +1,115 @@ + + + + + iframe + + + + +
    + + + + + + + diff --git a/public/assets/plugins/ckeditor/plugins/wsc/dialogs/tmpFrameset.html b/public/assets/plugins/ckeditor/plugins/wsc/dialogs/tmpFrameset.html new file mode 100644 index 00000000..0d675f4d --- /dev/null +++ b/public/assets/plugins/ckeditor/plugins/wsc/dialogs/tmpFrameset.html @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + diff --git a/public/assets/plugins/ckeditor/plugins/wsc/dialogs/wsc.css b/public/assets/plugins/ckeditor/plugins/wsc/dialogs/wsc.css new file mode 100644 index 00000000..9e834f1d --- /dev/null +++ b/public/assets/plugins/ckeditor/plugins/wsc/dialogs/wsc.css @@ -0,0 +1,82 @@ +/* +Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +html, body +{ + background-color: transparent; + margin: 0px; + padding: 0px; +} + +body +{ + padding: 10px; +} + +body, td, input, select, textarea +{ + font-size: 11px; + font-family: 'Microsoft Sans Serif' , Arial, Helvetica, Verdana; +} + +.midtext +{ + padding:0px; + margin:10px; +} + +.midtext p +{ + padding:0px; + margin:10px; +} + +.Button +{ + border: #737357 1px solid; + color: #3b3b1f; + background-color: #c7c78f; +} + +.PopupTabArea +{ + color: #737357; + background-color: #e3e3c7; +} + +.PopupTitleBorder +{ + border-bottom: #d5d59d 1px solid; +} +.PopupTabEmptyArea +{ + padding-left: 10px; + border-bottom: #d5d59d 1px solid; +} + +.PopupTab, .PopupTabSelected +{ + border-right: #d5d59d 1px solid; + border-top: #d5d59d 1px solid; + border-left: #d5d59d 1px solid; + padding: 3px 5px 3px 5px; + color: #737357; +} + +.PopupTab +{ + margin-top: 1px; + border-bottom: #d5d59d 1px solid; + cursor: pointer; +} + +.PopupTabSelected +{ + font-weight: bold; + cursor: default; + padding-top: 4px; + border-bottom: #f1f1e3 1px solid; + background-color: #f1f1e3; +} diff --git a/public/assets/plugins/ckeditor/plugins/wsc/dialogs/wsc.js b/public/assets/plugins/ckeditor/plugins/wsc/dialogs/wsc.js new file mode 100644 index 00000000..22410bdb --- /dev/null +++ b/public/assets/plugins/ckeditor/plugins/wsc/dialogs/wsc.js @@ -0,0 +1,67 @@ +/* + Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. + For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +(function(){function w(a){if(!a)throw"Languages-by-groups list are required for construct selectbox";var c=[],d="",f;for(f in a)for(var g in a[f]){var h=a[f][g];"en_US"==h?d=h:c.push(h)}c.sort();d&&c.unshift(d);return{getCurrentLangGroup:function(c){a:{for(var d in a)for(var f in a[d])if(f.toUpperCase()===c.toUpperCase()){c=d;break a}c=""}return c},setLangList:function(){var c={},d;for(d in a)for(var f in a[d])c[a[d][f]]=f;return c}()}}var e=function(){var a=function(a,b,f){var f=f||{},g=f.expires; +if("number"==typeof g&&g){var h=new Date;h.setTime(h.getTime()+1E3*g);g=f.expires=h}g&&g.toUTCString&&(f.expires=g.toUTCString());var b=encodeURIComponent(b),a=a+"="+b,e;for(e in f)b=f[e],a+="; "+e,!0!==b&&(a+="="+b);document.cookie=a};return{postMessage:{init:function(a){document.addEventListener?window.addEventListener("message",a,!1):window.attachEvent("onmessage",a)},send:function(a){var b=a.fn||null,f=a.id||"",g=a.target||window,h=a.message||{id:f};"[object Object]"==Object.prototype.toString.call(a.message)&& +(a.message.id||(a.message.id=f),h=a.message);a=window.JSON.stringify(h,b);g.postMessage(a,"*")}},hash:{create:function(){},parse:function(){}},cookie:{set:a,get:function(a){return(a=document.cookie.match(RegExp("(?:^|; )"+a.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g,"\\$1")+"=([^;]*)")))?decodeURIComponent(a[1]):void 0},remove:function(c){a(c,"",{expires:-1})}}}}(),a=a||{};a.TextAreaNumber=null;a.load=!0;a.cmd={SpellTab:"spell",Thesaurus:"thes",GrammTab:"grammar"};a.dialog=null;a.optionNode=null;a.selectNode= +null;a.grammerSuggest=null;a.textNode={};a.iframeMain=null;a.dataTemp="";a.div_overlay=null;a.textNodeInfo={};a.selectNode={};a.selectNodeResponce={};a.langList=null;a.langSelectbox=null;a.banner="";a.show_grammar=null;a.div_overlay_no_check=null;a.targetFromFrame={};a.onLoadOverlay=null;a.LocalizationComing={};a.OverlayPlace=null;a.LocalizationButton={ChangeTo:{instance:null,text:"Change to"},ChangeAll:{instance:null,text:"Change All"},IgnoreWord:{instance:null,text:"Ignore word"},IgnoreAllWords:{instance:null, +text:"Ignore all words"},Options:{instance:null,text:"Options",optionsDialog:{instance:null}},AddWord:{instance:null,text:"Add word"},FinishChecking:{instance:null,text:"Finish Checking"}};a.LocalizationLabel={ChangeTo:{instance:null,text:"Change to"},Suggestions:{instance:null,text:"Suggestions"}};var x=function(b){for(var c in b)b[c].instance.getElement().setText(a.LocalizationComing[c])},y=function(b){for(var c in b){if(!b[c].instance.setLabel)break;b[c].instance.setLabel(a.LocalizationComing[c])}}, +j,p;a.framesetHtml=function(b){return''};a.setIframe=function(b,c){var d=a.framesetHtml(c);return b.getElement().setHtml(d)};a.setCurrentIframe=function(b){a.setIframe(a.dialog._.contents[b].Content,b)};a.setHeightBannerFrame=function(){var b=a.dialog.getContentElement("SpellTab","banner").getElement(), +c=a.dialog.getContentElement("GrammTab","banner").getElement(),d=a.dialog.getContentElement("Thesaurus","banner").getElement();b.setStyle("height","90px");c.setStyle("height","90px");d.setStyle("height","90px")};a.setHeightFrame=function(){document.getElementById(a.iframeNumber+"_"+a.dialog._.currentTabId).style.height="240px"};a.sendData=function(b){var c=b._.currentTabId,d=b._.contents[c].Content,f,g;a.setIframe(d,c);b.parts.tabs.removeAllListeners();b.parts.tabs.on("click",function(h){h=h||window.event; +h.data.getTarget().is("a")&&c!=b._.currentTabId&&(c=b._.currentTabId,d=b._.contents[c].Content,f=a.iframeNumber+"_"+c,a.div_overlay.setEnable(),d.getElement().getChildCount()?t(a.targetFromFrame[f],a.cmd[c]):(a.setIframe(d,c),g=document.getElementById(f),a.targetFromFrame[f]=g.contentWindow))})};a.buildSelectLang=function(a){var c=new CKEDITOR.dom.element("div"),d=new CKEDITOR.dom.element("select"),a="wscLang"+a;c.addClass("cke_dialog_ui_input_select");c.setAttribute("role","presentation");c.setStyles({height:"auto", +position:"absolute",right:"0",top:"-1px",width:"160px","white-space":"normal"});d.setAttribute("id",a);d.addClass("cke_dialog_ui_input_select");d.setStyles({width:"160px"});c.append(d);return c};a.buildOptionLang=function(b,c){var d=document.getElementById("wscLang"+c),f=document.createDocumentFragment(),g,h,e=[];if(0===d.options.length){for(g in b)e.push([g,b[g]]);e.sort();for(var k=0;k
    "},{type:"html",id:"Content",label:"spellContent",html:"",setup:function(b){var b=a.iframeNumber+"_"+b._.currentTabId, +c=document.getElementById(b);a.targetFromFrame[b]=c.contentWindow}},{type:"hbox",id:"bottomGroup",style:"width:560px; margin: 0 auto;",widths:["50%","50%"],children:[{type:"hbox",id:"leftCol",align:"left",width:"50%",children:[{type:"vbox",id:"rightCol1",widths:["50%","50%"],children:[{type:"text",id:"text",label:a.LocalizationLabel.ChangeTo.text+":",labelLayout:"horizontal",labelStyle:"font: 12px/25px arial, sans-serif;",width:"140px","default":"",onShow:function(){a.textNode.SpellTab=this;a.LocalizationLabel.ChangeTo.instance= +this},onHide:function(){this.reset()}},{type:"hbox",id:"rightCol",align:"right",width:"30%",children:[{type:"vbox",id:"rightCol_col__left",children:[{type:"text",id:"labelSuggestions",label:a.LocalizationLabel.Suggestions.text+":",onShow:function(){a.LocalizationLabel.Suggestions.instance=this;this.getInputElement().hide()}},{type:"html",id:"logo",html:'WebSpellChecker.net',setup:function(){this.getElement().$.src= +a.logotype;this.getElement().getParent().setStyles({"text-align":"left"})}}]},{type:"select",id:"list_of_suggestions",labelStyle:"font: 12px/25px arial, sans-serif;",size:"6",inputStyle:"width: 140px; height: auto;",items:[["loading..."]],onShow:function(){p=this},onHide:function(){this.clear()},onChange:function(){a.textNode.SpellTab.setValue(this.getValue())}}]}]}]},{type:"hbox",id:"rightCol",align:"right",width:"50%",children:[{type:"vbox",id:"rightCol_col__left",widths:["50%","50%","50%","50%"], +children:[{type:"button",id:"ChangeTo",label:a.LocalizationButton.ChangeTo.text,title:"Change to",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id);a.LocalizationButton.ChangeTo.instance=this},onClick:c},{type:"button",id:"ChangeAll",label:a.LocalizationButton.ChangeAll.text,title:"Change All",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id);a.LocalizationButton.ChangeAll.instance=this},onClick:c},{type:"button",id:"AddWord", +label:a.LocalizationButton.AddWord.text,title:"Add word",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id);a.LocalizationButton.AddWord.instance=this},onClick:c},{type:"button",id:"FinishChecking",label:a.LocalizationButton.FinishChecking.text,title:"Finish Checking",style:"width: 100%;margin-top: 9px;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id);a.LocalizationButton.FinishChecking.instance=this},onClick:c}]},{type:"vbox",id:"rightCol_col__right", +widths:["50%","50%","50%"],children:[{type:"button",id:"IgnoreWord",label:a.LocalizationButton.IgnoreWord.text,title:"Ignore word",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id);a.LocalizationButton.IgnoreWord.instance=this},onClick:c},{type:"button",id:"IgnoreAllWords",label:a.LocalizationButton.IgnoreAllWords.text,title:"Ignore all words",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id);a.LocalizationButton.IgnoreAllWords.instance= +this},onClick:c},{type:"button",id:"option",label:a.LocalizationButton.Options.text,title:"Option",style:"width: 100%;",onLoad:function(){a.LocalizationButton.Options.instance=this;"file:"==document.location.protocol&&this.disable()},onClick:function(){"file:"==document.location.protocol?alert("WSC: Options functionality is disabled when runing from file system"):b.openDialog("options")}}]}]}]},{type:"hbox",id:"BlockFinishChecking",style:"width:560px; margin: 0 auto;",widths:["70%","30%"],onShow:function(){this.getElement().hide()}, +onHide:l,children:[{type:"hbox",id:"leftCol",align:"left",width:"70%",children:[{type:"vbox",id:"rightCol1",setup:function(){this.getChild()[0].getElement().$.src=a.logotype;this.getChild()[0].getElement().getParent().setStyles({"text-align":"center"})},children:[{type:"html",id:"logo",html:'WebSpellChecker.net'}]}]},{type:"hbox",id:"rightCol",align:"right",width:"30%",children:[{type:"vbox", +id:"rightCol_col__left",children:[{type:"button",id:"Option_button",label:a.LocalizationButton.Options.text,title:"Option",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id);"file:"==document.location.protocol&&this.disable()},onClick:function(){"file:"==document.location.protocol?alert("WSC: Options functionality is disabled when runing from file system"):b.openDialog("options")}},{type:"button",id:"FinishChecking",label:a.LocalizationButton.FinishChecking.text, +title:"Finish Checking",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onClick:c}]}]}]}]},{id:"GrammTab",label:"Grammar",accessKey:"G",elements:[{type:"html",id:"banner",label:"banner",style:"",html:"
    "},{type:"html",id:"Content",label:"GrammarContent",html:"",setup:function(){var b=a.iframeNumber+"_"+a.dialog._.currentTabId,c=document.getElementById(b);a.targetFromFrame[b]=c.contentWindow}},{type:"vbox",id:"bottomGroup",style:"width:560px; margin: 0 auto;", +children:[{type:"hbox",id:"leftCol",widths:["66%","34%"],children:[{type:"vbox",children:[{type:"text",id:"text",label:"Change to:",labelLayout:"horizontal",labelStyle:"font: 12px/25px arial, sans-serif;",inputStyle:"float: right; width: 200px;","default":"",onShow:function(){a.textNode.GrammTab=this},onHide:function(){this.reset()}},{type:"html",id:"html_text",html:"
    ", +onShow:function(){a.textNodeInfo.GrammTab=this}},{type:"html",id:"radio",html:"",onShow:function(){a.grammerSuggest=this}}]},{type:"vbox",children:[{type:"button",id:"ChangeTo",label:"Change to",title:"Change to",style:"width: 133px; float: right;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onClick:c},{type:"button",id:"IgnoreWord",label:"Ignore word",title:"Ignore word",style:"width: 133px; float: right;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)}, +onClick:c},{type:"button",id:"IgnoreAllWords",label:"Ignore Problem",title:"Ignore Problem",style:"width: 133px; float: right;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onClick:c},{type:"button",id:"FinishChecking",label:"Finish Checking",title:"Finish Checking",style:"width: 133px; float: right; margin-top: 9px;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onClick:c}]}]}]},{type:"hbox",id:"BlockFinishChecking",style:"width:560px; margin: 0 auto;", +widths:["70%","30%"],onShow:function(){this.getElement().hide()},onHide:l,children:[{type:"hbox",id:"leftCol",align:"left",width:"70%",children:[{type:"vbox",id:"rightCol1",children:[{type:"html",id:"logo",html:'WebSpellChecker.net',setup:function(){this.getElement().$.src=a.logotype;this.getElement().getParent().setStyles({"text-align":"center"})}}]}]},{type:"hbox",id:"rightCol",align:"right", +width:"30%",children:[{type:"vbox",id:"rightCol_col__left",children:[{type:"button",id:"FinishChecking",label:"Finish Checking",title:"Finish Checking",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onClick:c}]}]}]}]},{id:"Thesaurus",label:"Thesaurus",accessKey:"T",elements:[{type:"html",id:"banner",label:"banner",style:"",html:"
    "},{type:"html",id:"Content",label:"spellContent",html:"",setup:function(){var b=a.iframeNumber+"_"+a.dialog._.currentTabId, +c=document.getElementById(b);a.targetFromFrame[b]=c.contentWindow}},{type:"vbox",id:"bottomGroup",style:"width:560px; margin: -10px auto; overflow: hidden;",children:[{type:"hbox",widths:["75%","25%"],children:[{type:"vbox",children:[{type:"hbox",widths:["65%","35%"],children:[{type:"text",id:"ChangeTo",label:"Change to:",labelLayout:"horizontal",inputStyle:"width: 160px;",labelStyle:"font: 12px/25px arial, sans-serif;","default":"",onShow:function(){a.textNode.Thesaurus=this},onHide:function(){this.reset()}}, +{type:"button",id:"ChangeTo",label:"Change to",title:"Change to",style:"width: 121px; margin-top: 1px;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onClick:c}]},{type:"hbox",children:[{type:"select",id:"categories",label:"Categories:",labelStyle:"font: 12px/25px arial, sans-serif;",size:"5",inputStyle:"width: 180px; height: auto;",items:[],onShow:function(){a.selectNode.categories=this},onHide:function(){this.clear()},onChange:function(){a.buildOptionSynonyms(this.getValue())}}, +{type:"select",id:"synonyms",label:"Synonyms:",labelStyle:"font: 12px/25px arial, sans-serif;",size:"5",inputStyle:"width: 180px; height: auto;",items:[],onShow:function(){a.selectNode.synonyms=this;a.textNode.Thesaurus.setValue(this.getValue())},onHide:function(){this.clear()},onChange:function(){a.textNode.Thesaurus.setValue(this.getValue())}}]}]},{type:"vbox",width:"120px",style:"margin-top:46px;",children:[{type:"html",id:"logotype",label:"WebSpellChecker.net",html:'WebSpellChecker.net', +setup:function(){this.getElement().$.src=a.logotype;this.getElement().getParent().setStyles({"text-align":"center"})}},{type:"button",id:"FinishChecking",label:"Finish Checking",title:"Finish Checking",style:"width: 121px; float: right; margin-top: 9px;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onClick:c}]}]}]},{type:"hbox",id:"BlockFinishChecking",style:"width:560px; margin: 0 auto;",widths:["70%","30%"],onShow:function(){this.getElement().hide()},children:[{type:"hbox", +id:"leftCol",align:"left",width:"70%",children:[{type:"vbox",id:"rightCol1",children:[{type:"html",id:"logo",html:'WebSpellChecker.net',setup:function(){this.getElement().$.src=a.logotype;this.getElement().getParent().setStyles({"text-align":"center"})}}]}]},{type:"hbox",id:"rightCol",align:"right",width:"30%",children:[{type:"vbox",id:"rightCol_col__left",children:[{type:"button",id:"FinishChecking", +label:"Finish Checking",title:"Finish Checking",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onClick:c}]}]}]}]}]}});CKEDITOR.dialog.add("options",function(){var b=null,c={},d={},f=null,g=null;e.cookie.get("udn");e.cookie.get("osp");var h=function(){g=this.getElement().getAttribute("title-cmd");var a=[];a[0]=d.IgnoreAllCapsWords;a[1]=d.IgnoreWordsNumbers;a[2]=d.IgnoreMixedCaseWords;a[3]=d.IgnoreDomainNames;a=a.toString().replace(/,/g,"");e.cookie.set("osp", +a);e.cookie.set("udnCmd",g?g:"ignore");"delete"!=g&&(a="",""!==j.getValue()&&(a=j.getValue()),e.cookie.set("udn",a));e.postMessage.send({id:"options_dic_send"})},i=function(){f.getElement().setHtml(a.LocalizationComing.error);f.getElement().show()};return{title:a.LocalizationComing.Options,minWidth:430,minHeight:130,resizable:CKEDITOR.DIALOG_RESIZE_NONE,contents:[{id:"OptionsTab",label:"Options",accessKey:"O",elements:[{type:"hbox",id:"options_error",children:[{type:"html",style:"display: block;text-align: center;white-space: normal!important; font-size: 12px;color:red", +html:"
    ",onShow:function(){f=this}}]},{type:"vbox",id:"Options_content",children:[{type:"hbox",id:"Options_manager",widths:["52%","48%"],children:[{type:"fieldset",label:"Spell Checking Options",style:"border: none;margin-top: 13px;padding: 10px 0 10px 10px",onShow:function(){this.getInputElement().$.children[0].innerHTML=a.LocalizationComing.SpellCheckingOptions},children:[{type:"vbox",id:"Options_checkbox",children:[{type:"checkbox",id:"IgnoreAllCapsWords",label:"Ignore All-Caps Words", +labelStyle:"margin-left: 5px; font: 12px/16px arial, sans-serif;display: inline-block;white-space: normal;",style:"float:left; min-height: 16px;","default":"",onClick:function(){d[this.id]=!this.getValue()?0:1}},{type:"checkbox",id:"IgnoreWordsNumbers",label:"Ignore Words with Numbers",labelStyle:"margin-left: 5px; font: 12px/16px arial, sans-serif;display: inline-block;white-space: normal;",style:"float:left; min-height: 16px;","default":"",onClick:function(){d[this.id]=!this.getValue()?0:1}},{type:"checkbox", +id:"IgnoreMixedCaseWords",label:"Ignore Mixed-Case Words",labelStyle:"margin-left: 5px; font: 12px/16px arial, sans-serif;display: inline-block;white-space: normal;",style:"float:left; min-height: 16px;","default":"",onClick:function(){d[this.id]=!this.getValue()?0:1}},{type:"checkbox",id:"IgnoreDomainNames",label:"Ignore Domain Names",labelStyle:"margin-left: 5px; font: 12px/16px arial, sans-serif;display: inline-block;white-space: normal;",style:"float:left; min-height: 16px;","default":"",onClick:function(){d[this.id]= +!this.getValue()?0:1}}]}]},{type:"vbox",id:"Options_DictionaryName",children:[{type:"text",id:"DictionaryName",style:"margin-bottom: 10px",label:"Dictionary Name:",labelLayout:"vertical",labelStyle:"font: 12px/25px arial, sans-serif;","default":"",onLoad:function(){j=this;this.setValue(a.userDictionaryName?a.userDictionaryName:(e.cookie.get("udn"),this.getValue()))},onShow:function(){j=this;this.setValue(!e.cookie.get("udn")?this.getValue():e.cookie.get("udn"));this.setLabel(a.LocalizationComing.DictionaryName)}, +onHide:function(){this.reset()}},{type:"hbox",id:"Options_buttons",children:[{type:"vbox",id:"Options_leftCol_col",widths:["50%","50%"],children:[{type:"button",id:"create",label:"Create",title:"Create",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onShow:function(){this.getElement().setText(a.LocalizationComing.Create)},onClick:h},{type:"button",id:"restore",label:"Restore",title:"Restore",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd", +this.id)},onShow:function(){this.getElement().setText(a.LocalizationComing.Restore)},onClick:h}]},{type:"vbox",id:"Options_rightCol_col",widths:["50%","50%"],children:[{type:"button",id:"rename",label:"Rename",title:"Rename",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd",this.id)},onShow:function(){this.getElement().setText(a.LocalizationComing.Rename)},onClick:h},{type:"button",id:"delete",label:"Remove",title:"Remove",style:"width: 100%;",onLoad:function(){this.getElement().setAttribute("title-cmd", +this.id)},onShow:function(){this.getElement().setText(a.LocalizationComing.Remove)},onClick:h}]}]}]}]},{type:"hbox",id:"Options_text",children:[{type:"html",style:"text-align: justify;margin-top: 15px;white-space: normal!important; font-size: 12px;color:#777;",html:"
    "+a.LocalizationComing.OptionsTextIntro+"
    ",onShow:function(){this.getElement().setText(a.LocalizationComing.OptionsTextIntro)}}]}]}]}],buttons:[CKEDITOR.dialog.okButton,CKEDITOR.dialog.cancelButton],onOk:function(){var a=[]; +a[0]=d.IgnoreAllCapsWords;a[1]=d.IgnoreWordsNumbers;a[2]=d.IgnoreMixedCaseWords;a[3]=d.IgnoreDomainNames;a=a.toString().replace(/,/g,"");e.cookie.set("osp",a);e.cookie.set("udn",j.getValue());e.postMessage.send({id:"options_checkbox_send"});f.getElement().hide();f.getElement().setHtml(" ")},onLoad:function(){b=this;e.postMessage.init(i);c.IgnoreAllCapsWords=b.getContentElement("OptionsTab","IgnoreAllCapsWords");c.IgnoreWordsNumbers=b.getContentElement("OptionsTab","IgnoreWordsNumbers");c.IgnoreMixedCaseWords= +b.getContentElement("OptionsTab","IgnoreMixedCaseWords");c.IgnoreDomainNames=b.getContentElement("OptionsTab","IgnoreDomainNames")},onShow:function(){var b=e.cookie.get("osp").split("");d.IgnoreAllCapsWords=b[0];d.IgnoreWordsNumbers=b[1];d.IgnoreMixedCaseWords=b[2];d.IgnoreDomainNames=b[3];!parseInt(d.IgnoreAllCapsWords,10)?c.IgnoreAllCapsWords.setValue("",!1):c.IgnoreAllCapsWords.setValue("checked",!1);!parseInt(d.IgnoreWordsNumbers,10)?c.IgnoreWordsNumbers.setValue("",!1):c.IgnoreWordsNumbers.setValue("checked", +!1);!parseInt(d.IgnoreMixedCaseWords,10)?c.IgnoreMixedCaseWords.setValue("",!1):c.IgnoreMixedCaseWords.setValue("checked",!1);!parseInt(d.IgnoreDomainNames,10)?c.IgnoreDomainNames.setValue("",!1):c.IgnoreDomainNames.setValue("checked",!1);d.IgnoreAllCapsWords=!c.IgnoreAllCapsWords.getValue()?0:1;d.IgnoreWordsNumbers=!c.IgnoreWordsNumbers.getValue()?0:1;d.IgnoreMixedCaseWords=!c.IgnoreMixedCaseWords.getValue()?0:1;d.IgnoreDomainNames=!c.IgnoreDomainNames.getValue()?0:1;c.IgnoreAllCapsWords.getElement().$.lastChild.innerHTML= +a.LocalizationComing.IgnoreAllCapsWords;c.IgnoreWordsNumbers.getElement().$.lastChild.innerHTML=a.LocalizationComing.IgnoreWordsWithNumbers;c.IgnoreMixedCaseWords.getElement().$.lastChild.innerHTML=a.LocalizationComing.IgnoreMixedCaseWords;c.IgnoreDomainNames.getElement().$.lastChild.innerHTML=a.LocalizationComing.IgnoreDomainNames}}});CKEDITOR.dialog.on("resize",function(b){var b=b.data,c=b.dialog,d=CKEDITOR.document.getById(a.iframeNumber+"_"+c._.currentTabId);"checkspell"==c._.name&&(a.bnr?d&& +d.setSize("height",b.height-310):d&&d.setSize("height",b.height-220))});CKEDITOR.on("dialogDefinition",function(b){var c=b.data.definition;a.onLoadOverlay=new q({opacity:"1",background:"#fff",target:c.dialog.parts.tabs.getParent().$});a.onLoadOverlay.setEnable();c.dialog.on("show",function(){});c.dialog.on("cancel",function(){c.dialog.getParentEditor().config.wsc_onClose.call(this.document.getWindow().getFrame());a.div_overlay.setDisable();return!1},this,null,-1)})})(); \ No newline at end of file diff --git a/public/assets/plugins/ckeditor/plugins/wsc/dialogs/wsc_ie.js b/public/assets/plugins/ckeditor/plugins/wsc/dialogs/wsc_ie.js new file mode 100644 index 00000000..6b39b006 --- /dev/null +++ b/public/assets/plugins/ckeditor/plugins/wsc/dialogs/wsc_ie.js @@ -0,0 +1,11 @@ +/* + Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved. + For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +CKEDITOR.dialog.add("checkspell",function(a){function c(a,c){var d=0;return function(){"function"==typeof window.doSpell?("undefined"!=typeof e&&window.clearInterval(e),j(a)):180==d++&&window._cancelOnError(c)}}function j(c){var f=new window._SP_FCK_LangCompare,b=CKEDITOR.getUrl(a.plugins.wsc.path+"dialogs/"),e=b+"tmpFrameset.html";window.gFCKPluginName="wsc";f.setDefaulLangCode(a.config.defaultLanguage);window.doSpell({ctrl:g,lang:a.config.wsc_lang||f.getSPLangCode(a.langCode),intLang:a.config.wsc_uiLang|| +f.getSPLangCode(a.langCode),winType:d,onCancel:function(){c.hide()},onFinish:function(b){a.focus();c.getParentEditor().setData(b.value);c.hide()},staticFrame:e,framesetPath:e,iframePath:b+"ciframe.html",schemaURI:b+"wsc.css",userDictionaryName:a.config.wsc_userDictionaryName,customDictionaryName:a.config.wsc_customDictionaryIds&&a.config.wsc_customDictionaryIds.split(","),domainName:a.config.wsc_domainName});CKEDITOR.document.getById(h).setStyle("display","none");CKEDITOR.document.getById(d).setStyle("display", +"block")}var b=CKEDITOR.tools.getNextNumber(),d="cke_frame_"+b,g="cke_data_"+b,h="cke_error_"+b,e,b=document.location.protocol||"http:",i=a.lang.wsc.notAvailable,k='', +l=a.config.wsc_customLoaderScript||b+"//loader.webspellchecker.net/sproxy_fck/sproxy.php?plugin=fck2&customerid="+a.config.wsc_customerId+"&cmd=script&doc=wsc&schema=22";a.config.wsc_customLoaderScript&&(i+='

    '+a.lang.wsc.errorLoading.replace(/%s/g,a.config.wsc_customLoaderScript)+"

    ");window._cancelOnError=function(c){if("undefined"==typeof window.WSC_Error){CKEDITOR.document.getById(d).setStyle("display", +"none");var b=CKEDITOR.document.getById(h);b.setStyle("display","block");b.setHtml(c||a.lang.wsc.notAvailable)}};return{title:a.config.wsc_dialogTitle||a.lang.wsc.title,minWidth:485,minHeight:380,buttons:[CKEDITOR.dialog.cancelButton],onShow:function(){var b=this.getContentElement("general","content").getElement();b.setHtml(k);b.getChild(2).setStyle("height",this._.contentSize.height+"px");"function"!=typeof window.doSpell&&CKEDITOR.document.getHead().append(CKEDITOR.document.createElement("script", +{attributes:{type:"text/javascript",src:l}}));b=a.getData();CKEDITOR.document.getById(g).setValue(b);e=window.setInterval(c(this,i),250)},onHide:function(){window.ooo=void 0;window.int_framsetLoaded=void 0;window.framesetLoaded=void 0;window.is_window_opened=!1},contents:[{id:"general",label:a.config.wsc_dialogTitle||a.lang.wsc.title,padding:0,elements:[{type:"html",id:"content",html:""}]}]}}); +CKEDITOR.dialog.on("resize",function(a){var a=a.data,c=a.dialog;"checkspell"==c._.name&&((c=(c=c.getContentElement("general","content").getElement())&&c.getChild(2))&&c.setSize("height",a.height),c&&c.setSize("width",a.width))}); \ No newline at end of file diff --git a/public/assets/plugins/ckeditor/skins/moono/dialog.css b/public/assets/plugins/ckeditor/skins/moono/dialog.css new file mode 100644 index 00000000..29bbaf9f --- /dev/null +++ b/public/assets/plugins/ckeditor/skins/moono/dialog.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_browser_gecko19 .cke_dialog_body{position:relative}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}a.cke_dialog_tab_disabled{color:#bababa;cursor:default}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:0 0;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:3px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 12px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:20px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#9ad717',endColorstr='#69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:2px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:24px;line-height:24px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:2px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%} \ No newline at end of file diff --git a/public/assets/plugins/ckeditor/skins/moono/dialog_ie.css b/public/assets/plugins/ckeditor/skins/moono/dialog_ie.css new file mode 100644 index 00000000..153b05cb --- /dev/null +++ b/public/assets/plugins/ckeditor/skins/moono/dialog_ie.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_browser_gecko19 .cke_dialog_body{position:relative}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}a.cke_dialog_tab_disabled{color:#bababa;cursor:default}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:0 0;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:3px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 12px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:20px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#9ad717',endColorstr='#69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:2px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:24px;line-height:24px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:2px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{padding-right:2px}.cke_rtl div.cke_dialog_ui_input_text,.cke_rtl div.cke_dialog_ui_input_password{padding-left:2px}.cke_rtl div.cke_dialog_ui_input_text{padding-right:1px}.cke_rtl .cke_dialog_ui_vbox_child,.cke_rtl .cke_dialog_ui_hbox_child,.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_last{padding-right:2px!important}.cke_hc .cke_dialog_title,.cke_hc .cke_dialog_footer,.cke_hc a.cke_dialog_tab,.cke_hc a.cke_dialog_ui_button,.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button_ok,.cke_hc a.cke_dialog_ui_button_ok:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:0} \ No newline at end of file diff --git a/public/assets/plugins/ckeditor/skins/moono/dialog_ie7.css b/public/assets/plugins/ckeditor/skins/moono/dialog_ie7.css new file mode 100644 index 00000000..459e72b8 --- /dev/null +++ b/public/assets/plugins/ckeditor/skins/moono/dialog_ie7.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_browser_gecko19 .cke_dialog_body{position:relative}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}a.cke_dialog_tab_disabled{color:#bababa;cursor:default}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:0 0;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:3px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 12px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:20px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#9ad717',endColorstr='#69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:2px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:24px;line-height:24px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:2px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{padding-right:2px}.cke_rtl div.cke_dialog_ui_input_text,.cke_rtl div.cke_dialog_ui_input_password{padding-left:2px}.cke_rtl div.cke_dialog_ui_input_text{padding-right:1px}.cke_rtl .cke_dialog_ui_vbox_child,.cke_rtl .cke_dialog_ui_hbox_child,.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_last{padding-right:2px!important}.cke_hc .cke_dialog_title,.cke_hc .cke_dialog_footer,.cke_hc a.cke_dialog_tab,.cke_hc a.cke_dialog_ui_button,.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button_ok,.cke_hc a.cke_dialog_ui_button_ok:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:0}.cke_dialog_title{zoom:1}.cke_dialog_footer{border-top:1px solid #bfbfbf}.cke_dialog_footer_buttons{position:static}.cke_dialog_footer_buttons a.cke_dialog_ui_button{vertical-align:top}.cke_dialog .cke_resizer_ltr{padding-left:4px}.cke_dialog .cke_resizer_rtl{padding-right:4px}.cke_dialog_ui_input_text,.cke_dialog_ui_input_password,.cke_dialog_ui_input_textarea,.cke_dialog_ui_input_select{padding:0!important}.cke_dialog_ui_checkbox_input,.cke_dialog_ui_ratio_input,.cke_btn_reset,.cke_btn_locked,.cke_btn_unlocked{border:1px solid transparent!important} \ No newline at end of file diff --git a/public/assets/plugins/ckeditor/skins/moono/dialog_ie8.css b/public/assets/plugins/ckeditor/skins/moono/dialog_ie8.css new file mode 100644 index 00000000..74e9fa88 --- /dev/null +++ b/public/assets/plugins/ckeditor/skins/moono/dialog_ie8.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_browser_gecko19 .cke_dialog_body{position:relative}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}a.cke_dialog_tab_disabled{color:#bababa;cursor:default}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:0 0;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:3px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 12px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:20px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#9ad717',endColorstr='#69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:2px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:24px;line-height:24px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:2px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{padding-right:2px}.cke_rtl div.cke_dialog_ui_input_text,.cke_rtl div.cke_dialog_ui_input_password{padding-left:2px}.cke_rtl div.cke_dialog_ui_input_text{padding-right:1px}.cke_rtl .cke_dialog_ui_vbox_child,.cke_rtl .cke_dialog_ui_hbox_child,.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_last{padding-right:2px!important}.cke_hc .cke_dialog_title,.cke_hc .cke_dialog_footer,.cke_hc a.cke_dialog_tab,.cke_hc a.cke_dialog_ui_button,.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button_ok,.cke_hc a.cke_dialog_ui_button_ok:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:0}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{display:block} \ No newline at end of file diff --git a/public/assets/plugins/ckeditor/skins/moono/dialog_iequirks.css b/public/assets/plugins/ckeditor/skins/moono/dialog_iequirks.css new file mode 100644 index 00000000..1c8e0558 --- /dev/null +++ b/public/assets/plugins/ckeditor/skins/moono/dialog_iequirks.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_browser_gecko19 .cke_dialog_body{position:relative}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}a.cke_dialog_tab_disabled{color:#bababa;cursor:default}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:0 0;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:3px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 12px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:20px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#9ad717',endColorstr='#69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:2px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:24px;line-height:24px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:2px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{padding-right:2px}.cke_rtl div.cke_dialog_ui_input_text,.cke_rtl div.cke_dialog_ui_input_password{padding-left:2px}.cke_rtl div.cke_dialog_ui_input_text{padding-right:1px}.cke_rtl .cke_dialog_ui_vbox_child,.cke_rtl .cke_dialog_ui_hbox_child,.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_last{padding-right:2px!important}.cke_hc .cke_dialog_title,.cke_hc .cke_dialog_footer,.cke_hc a.cke_dialog_tab,.cke_hc a.cke_dialog_ui_button,.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button_ok,.cke_hc a.cke_dialog_ui_button_ok:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:0}.cke_dialog_footer{filter:""} \ No newline at end of file diff --git a/public/assets/plugins/ckeditor/skins/moono/dialog_opera.css b/public/assets/plugins/ckeditor/skins/moono/dialog_opera.css new file mode 100644 index 00000000..742ce731 --- /dev/null +++ b/public/assets/plugins/ckeditor/skins/moono/dialog_opera.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_browser_gecko19 .cke_dialog_body{position:relative}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}a.cke_dialog_tab_disabled{color:#bababa;cursor:default}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:0 0;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:3px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 12px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:20px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#9ad717',endColorstr='#69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:2px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:24px;line-height:24px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:2px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_dialog_footer{display:block;height:38px}.cke_ltr .cke_dialog_footer>*{float:right}.cke_rtl .cke_dialog_footer>*{float:left} \ No newline at end of file diff --git a/public/assets/plugins/ckeditor/skins/moono/editor.css b/public/assets/plugins/ckeditor/skins/moono/editor.css new file mode 100644 index 00000000..2df84822 --- /dev/null +++ b/public/assets/plugins/ckeditor/skins/moono/editor.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup *:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_rtl .cke_toolgroup *:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}.cke_button__about_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_button__horizontalrule_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_button__maximize_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__pastetext_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__pastefromword_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_button__specialchar_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_button__scayt_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -984px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -1008px !important;}.cke_button__spellchecker_icon {background: url(icons.png) no-repeat 0 -1032px !important;}.cke_hidpi .cke_button__about_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_button__horizontalrule_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_button__maximize_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastetext_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastetext_icon,.cke_ltr.cke_hidpi .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastefromword_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastefromword_icon,.cke_ltr.cke_hidpi .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_button__specialchar_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;}.cke_hidpi .cke_button__scayt_icon {background: url(icons_hidpi.png) no-repeat 0 -888px !important;background-size: 16px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -936px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -984px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;background-size: 16px !important;}.cke_hidpi .cke_button__spellchecker_icon {background: url(icons_hidpi.png) no-repeat 0 -1032px !important;background-size: 16px !important;} \ No newline at end of file diff --git a/public/assets/plugins/ckeditor/skins/moono/editor_gecko.css b/public/assets/plugins/ckeditor/skins/moono/editor_gecko.css new file mode 100644 index 00000000..ecb7990a --- /dev/null +++ b/public/assets/plugins/ckeditor/skins/moono/editor_gecko.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup *:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_rtl .cke_toolgroup *:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}.cke_bottom{padding-bottom:3px}.cke_combo_text{margin-bottom:-1px;margin-top:1px}.cke_button__about_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_button__horizontalrule_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_button__maximize_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__pastetext_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__pastefromword_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_button__specialchar_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_button__scayt_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -984px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -1008px !important;}.cke_button__spellchecker_icon {background: url(icons.png) no-repeat 0 -1032px !important;}.cke_hidpi .cke_button__about_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_button__horizontalrule_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_button__maximize_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastetext_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastetext_icon,.cke_ltr.cke_hidpi .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastefromword_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastefromword_icon,.cke_ltr.cke_hidpi .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_button__specialchar_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;}.cke_hidpi .cke_button__scayt_icon {background: url(icons_hidpi.png) no-repeat 0 -888px !important;background-size: 16px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -936px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -984px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;background-size: 16px !important;}.cke_hidpi .cke_button__spellchecker_icon {background: url(icons_hidpi.png) no-repeat 0 -1032px !important;background-size: 16px !important;} \ No newline at end of file diff --git a/public/assets/plugins/ckeditor/skins/moono/editor_ie.css b/public/assets/plugins/ckeditor/skins/moono/editor_ie.css new file mode 100644 index 00000000..332d7795 --- /dev/null +++ b/public/assets/plugins/ckeditor/skins/moono/editor_ie.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup *:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_rtl .cke_toolgroup *:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}a.cke_button_disabled,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{filter:alpha(opacity = 30)}.cke_button_disabled .cke_button_icon{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00ffffff,endColorstr=#00ffffff)}.cke_button_off:hover,.cke_button_off:focus,.cke_button_off:active{filter:alpha(opacity = 100)}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{filter:alpha(opacity = 30)}.cke_toolbox_collapser{border:1px solid #a6a6a6}.cke_toolbox_collapser .cke_arrow{margin-top:1px}.cke_hc .cke_top,.cke_hc .cke_bottom,.cke_hc .cke_combo_button,.cke_hc a.cke_combo_button:hover,.cke_hc a.cke_combo_button:focus,.cke_hc .cke_toolgroup,.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc .cke_toolbox_collapser,.cke_hc .cke_toolbox_collapser:hover,.cke_hc .cke_panel_grouptitle{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_button__about_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_button__horizontalrule_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_button__maximize_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__pastetext_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__pastefromword_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_button__specialchar_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_button__scayt_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -984px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -1008px !important;}.cke_button__spellchecker_icon {background: url(icons.png) no-repeat 0 -1032px !important;}.cke_hidpi .cke_button__about_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_button__horizontalrule_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_button__maximize_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastetext_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastetext_icon,.cke_ltr.cke_hidpi .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastefromword_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastefromword_icon,.cke_ltr.cke_hidpi .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_button__specialchar_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;}.cke_hidpi .cke_button__scayt_icon {background: url(icons_hidpi.png) no-repeat 0 -888px !important;background-size: 16px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -936px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -984px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;background-size: 16px !important;}.cke_hidpi .cke_button__spellchecker_icon {background: url(icons_hidpi.png) no-repeat 0 -1032px !important;background-size: 16px !important;} \ No newline at end of file diff --git a/public/assets/plugins/ckeditor/skins/moono/editor_ie7.css b/public/assets/plugins/ckeditor/skins/moono/editor_ie7.css new file mode 100644 index 00000000..9bfeaa78 --- /dev/null +++ b/public/assets/plugins/ckeditor/skins/moono/editor_ie7.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup *:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_rtl .cke_toolgroup *:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}a.cke_button_disabled,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{filter:alpha(opacity = 30)}.cke_button_disabled .cke_button_icon{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00ffffff,endColorstr=#00ffffff)}.cke_button_off:hover,.cke_button_off:focus,.cke_button_off:active{filter:alpha(opacity = 100)}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{filter:alpha(opacity = 30)}.cke_toolbox_collapser{border:1px solid #a6a6a6}.cke_toolbox_collapser .cke_arrow{margin-top:1px}.cke_hc .cke_top,.cke_hc .cke_bottom,.cke_hc .cke_combo_button,.cke_hc a.cke_combo_button:hover,.cke_hc a.cke_combo_button:focus,.cke_hc .cke_toolgroup,.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc .cke_toolbox_collapser,.cke_hc .cke_toolbox_collapser:hover,.cke_hc .cke_panel_grouptitle{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_rtl .cke_toolgroup,.cke_rtl .cke_toolbar_separator,.cke_rtl .cke_button,.cke_rtl .cke_button *,.cke_rtl .cke_combo,.cke_rtl .cke_combo *,.cke_rtl .cke_path_item,.cke_rtl .cke_path_item *,.cke_rtl .cke_path_empty{float:none}.cke_rtl .cke_toolgroup,.cke_rtl .cke_toolbar_separator,.cke_rtl .cke_combo_button,.cke_rtl .cke_combo_button *,.cke_rtl .cke_button,.cke_rtl .cke_button_icon,{display:inline-block;vertical-align:top}.cke_toolbox{display:inline-block;padding-bottom:5px;height:100%}.cke_rtl .cke_toolbox{padding-bottom:0}.cke_toolbar{margin-bottom:5px}.cke_rtl .cke_toolbar{margin-bottom:0}.cke_toolgroup{height:26px}.cke_toolgroup,.cke_combo{position:relative}a.cke_button{float:none;vertical-align:top}.cke_toolbar_separator{display:inline-block;float:none;vertical-align:top;background-color:#c0c0c0}.cke_toolbox_collapser .cke_arrow{margin-top:0}.cke_toolbox_collapser .cke_arrow{border-width:4px}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{border-width:3px}.cke_rtl .cke_button_arrow{padding-top:8px;margin-right:2px}.cke_rtl .cke_combo_inlinelabel{display:table-cell;vertical-align:middle}.cke_menubutton{display:block;height:24px}.cke_menubutton_inner{display:block;position:relative}.cke_menubutton_icon{height:16px;width:16px}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:inline-block}.cke_menubutton_label{width:auto;vertical-align:top;line-height:24px;height:24px;margin:0 10px 0 0}.cke_menuarrow{width:5px;height:6px;padding:0;position:absolute;right:8px;top:10px;background-position:0 0}.cke_rtl .cke_menubutton_icon{position:absolute;right:0;top:0}.cke_rtl .cke_menubutton_label{float:right;clear:both;margin:0 24px 0 10px}.cke_hc .cke_rtl .cke_menubutton_label{margin-right:0}.cke_rtl .cke_menuarrow{left:8px;right:auto;background-position:0 -24px}.cke_hc .cke_menuarrow{top:5px;padding:0 5px}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{position:relative}.cke_wysiwyg_div{padding-top:0!important;padding-bottom:0!important}.cke_button__about_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_button__horizontalrule_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_button__maximize_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__pastetext_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__pastefromword_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_button__specialchar_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_button__scayt_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -984px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -1008px !important;}.cke_button__spellchecker_icon {background: url(icons.png) no-repeat 0 -1032px !important;}.cke_hidpi .cke_button__about_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_button__horizontalrule_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_button__maximize_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastetext_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastetext_icon,.cke_ltr.cke_hidpi .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastefromword_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastefromword_icon,.cke_ltr.cke_hidpi .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_button__specialchar_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;}.cke_hidpi .cke_button__scayt_icon {background: url(icons_hidpi.png) no-repeat 0 -888px !important;background-size: 16px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -936px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -984px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;background-size: 16px !important;}.cke_hidpi .cke_button__spellchecker_icon {background: url(icons_hidpi.png) no-repeat 0 -1032px !important;background-size: 16px !important;} \ No newline at end of file diff --git a/public/assets/plugins/ckeditor/skins/moono/editor_ie8.css b/public/assets/plugins/ckeditor/skins/moono/editor_ie8.css new file mode 100644 index 00000000..955ec8d2 --- /dev/null +++ b/public/assets/plugins/ckeditor/skins/moono/editor_ie8.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup *:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_rtl .cke_toolgroup *:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}a.cke_button_disabled,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{filter:alpha(opacity = 30)}.cke_button_disabled .cke_button_icon{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00ffffff,endColorstr=#00ffffff)}.cke_button_off:hover,.cke_button_off:focus,.cke_button_off:active{filter:alpha(opacity = 100)}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{filter:alpha(opacity = 30)}.cke_toolbox_collapser{border:1px solid #a6a6a6}.cke_toolbox_collapser .cke_arrow{margin-top:1px}.cke_hc .cke_top,.cke_hc .cke_bottom,.cke_hc .cke_combo_button,.cke_hc a.cke_combo_button:hover,.cke_hc a.cke_combo_button:focus,.cke_hc .cke_toolgroup,.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc .cke_toolbox_collapser,.cke_hc .cke_toolbox_collapser:hover,.cke_hc .cke_panel_grouptitle{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_toolbox_collapser .cke_arrow{border-width:4px}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{border-width:3px}.cke_toolbox_collapser .cke_arrow{margin-top:0}.cke_button__about_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_button__horizontalrule_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_button__maximize_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__pastetext_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__pastefromword_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_button__specialchar_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_button__scayt_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -984px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -1008px !important;}.cke_button__spellchecker_icon {background: url(icons.png) no-repeat 0 -1032px !important;}.cke_hidpi .cke_button__about_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_button__horizontalrule_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_button__maximize_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastetext_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastetext_icon,.cke_ltr.cke_hidpi .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastefromword_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastefromword_icon,.cke_ltr.cke_hidpi .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_button__specialchar_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;}.cke_hidpi .cke_button__scayt_icon {background: url(icons_hidpi.png) no-repeat 0 -888px !important;background-size: 16px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -936px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -984px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;background-size: 16px !important;}.cke_hidpi .cke_button__spellchecker_icon {background: url(icons_hidpi.png) no-repeat 0 -1032px !important;background-size: 16px !important;} \ No newline at end of file diff --git a/public/assets/plugins/ckeditor/skins/moono/editor_iequirks.css b/public/assets/plugins/ckeditor/skins/moono/editor_iequirks.css new file mode 100644 index 00000000..009d08c5 --- /dev/null +++ b/public/assets/plugins/ckeditor/skins/moono/editor_iequirks.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup *:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_rtl .cke_toolgroup *:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}a.cke_button_disabled,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{filter:alpha(opacity = 30)}.cke_button_disabled .cke_button_icon{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00ffffff,endColorstr=#00ffffff)}.cke_button_off:hover,.cke_button_off:focus,.cke_button_off:active{filter:alpha(opacity = 100)}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{filter:alpha(opacity = 30)}.cke_toolbox_collapser{border:1px solid #a6a6a6}.cke_toolbox_collapser .cke_arrow{margin-top:1px}.cke_hc .cke_top,.cke_hc .cke_bottom,.cke_hc .cke_combo_button,.cke_hc a.cke_combo_button:hover,.cke_hc a.cke_combo_button:focus,.cke_hc .cke_toolgroup,.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc .cke_toolbox_collapser,.cke_hc .cke_toolbox_collapser:hover,.cke_hc .cke_panel_grouptitle{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_top,.cke_contents,.cke_bottom{width:100%}.cke_button_arrow{font-size:0}.cke_rtl .cke_toolgroup,.cke_rtl .cke_toolbar_separator,.cke_rtl .cke_button,.cke_rtl .cke_button *,.cke_rtl .cke_combo,.cke_rtl .cke_combo *,.cke_rtl .cke_path_item,.cke_rtl .cke_path_item *,.cke_rtl .cke_path_empty{float:none}.cke_rtl .cke_toolgroup,.cke_rtl .cke_toolbar_separator,.cke_rtl .cke_combo_button,.cke_rtl .cke_combo_button *,.cke_rtl .cke_button,.cke_rtl .cke_button_icon,{display:inline-block;vertical-align:top}.cke_rtl .cke_button_icon{float:none}.cke_resizer{width:10px}.cke_source{white-space:normal}.cke_bottom{position:static}.cke_colorbox{font-size:0}.cke_button__about_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_button__horizontalrule_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_button__maximize_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__pastetext_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__pastetext_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__pastefromword_icon, .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__pastefromword_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_button__specialchar_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_button__scayt_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -984px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -1008px !important;}.cke_button__spellchecker_icon {background: url(icons.png) no-repeat 0 -1032px !important;}.cke_hidpi .cke_button__about_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_button__horizontalrule_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_button__maximize_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastetext_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastetext_icon,.cke_ltr.cke_hidpi .cke_button__pastetext_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__pastefromword_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__pastefromword_icon,.cke_ltr.cke_hidpi .cke_button__pastefromword_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_button__specialchar_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;}.cke_hidpi .cke_button__scayt_icon {background: url(icons_hidpi.png) no-repeat 0 -888px !important;background-size: 16px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -936px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -984px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;background-size: 16px !important;}.cke_hidpi .cke_button__spellchecker_icon {background: url(icons_hidpi.png) no-repeat 0 -1032px !important;background-size: 16px !important;} \ No newline at end of file diff --git a/public/assets/plugins/ckeditor/skins/moono/icons.png b/public/assets/plugins/ckeditor/skins/moono/icons.png new file mode 100644 index 00000000..c71008d7 Binary files /dev/null and b/public/assets/plugins/ckeditor/skins/moono/icons.png differ diff --git a/public/assets/plugins/ckeditor/skins/moono/icons_hidpi.png b/public/assets/plugins/ckeditor/skins/moono/icons_hidpi.png new file mode 100644 index 00000000..0466c2bc Binary files /dev/null and b/public/assets/plugins/ckeditor/skins/moono/icons_hidpi.png differ diff --git a/public/assets/plugins/ckeditor/skins/moono/images/arrow.png b/public/assets/plugins/ckeditor/skins/moono/images/arrow.png new file mode 100644 index 00000000..0d1eb39c Binary files /dev/null and b/public/assets/plugins/ckeditor/skins/moono/images/arrow.png differ diff --git a/public/assets/plugins/ckeditor/skins/moono/images/close.png b/public/assets/plugins/ckeditor/skins/moono/images/close.png new file mode 100644 index 00000000..04b9c97d Binary files /dev/null and b/public/assets/plugins/ckeditor/skins/moono/images/close.png differ diff --git a/public/assets/plugins/ckeditor/skins/moono/images/hidpi/close.png b/public/assets/plugins/ckeditor/skins/moono/images/hidpi/close.png new file mode 100644 index 00000000..8abca8e9 Binary files /dev/null and b/public/assets/plugins/ckeditor/skins/moono/images/hidpi/close.png differ diff --git a/public/assets/plugins/ckeditor/skins/moono/images/hidpi/lock-open.png b/public/assets/plugins/ckeditor/skins/moono/images/hidpi/lock-open.png new file mode 100644 index 00000000..aa5e740e Binary files /dev/null and b/public/assets/plugins/ckeditor/skins/moono/images/hidpi/lock-open.png differ diff --git a/public/assets/plugins/ckeditor/skins/moono/images/hidpi/lock.png b/public/assets/plugins/ckeditor/skins/moono/images/hidpi/lock.png new file mode 100644 index 00000000..5404b063 Binary files /dev/null and b/public/assets/plugins/ckeditor/skins/moono/images/hidpi/lock.png differ diff --git a/public/assets/plugins/ckeditor/skins/moono/images/hidpi/refresh.png b/public/assets/plugins/ckeditor/skins/moono/images/hidpi/refresh.png new file mode 100644 index 00000000..1ebef344 Binary files /dev/null and b/public/assets/plugins/ckeditor/skins/moono/images/hidpi/refresh.png differ diff --git a/public/assets/plugins/ckeditor/skins/moono/images/lock-open.png b/public/assets/plugins/ckeditor/skins/moono/images/lock-open.png new file mode 100644 index 00000000..3b256c06 Binary files /dev/null and b/public/assets/plugins/ckeditor/skins/moono/images/lock-open.png differ diff --git a/public/assets/plugins/ckeditor/skins/moono/images/lock.png b/public/assets/plugins/ckeditor/skins/moono/images/lock.png new file mode 100644 index 00000000..c127f9eb Binary files /dev/null and b/public/assets/plugins/ckeditor/skins/moono/images/lock.png differ diff --git a/public/assets/plugins/ckeditor/skins/moono/images/refresh.png b/public/assets/plugins/ckeditor/skins/moono/images/refresh.png new file mode 100644 index 00000000..a1a061c5 Binary files /dev/null and b/public/assets/plugins/ckeditor/skins/moono/images/refresh.png differ diff --git a/public/assets/plugins/ckeditor/skins/moono/readme.md b/public/assets/plugins/ckeditor/skins/moono/readme.md new file mode 100644 index 00000000..0fa4c1ac --- /dev/null +++ b/public/assets/plugins/ckeditor/skins/moono/readme.md @@ -0,0 +1,51 @@ +"Moono" Skin +==================== + +This skin has been chosen for the **default skin** of CKEditor 4.x, elected from the CKEditor +[skin contest](http://ckeditor.com/blog/new_ckeditor_4_skin) and further shaped by +the CKEditor team. "Moono" is maintained by the core developers. + +For more information about skins, please check the [CKEditor Skin SDK](http://docs.cksource.com/CKEditor_4.x/Skin_SDK) +documentation. + +Features +------------------- +"Moono" is a monochromatic skin, which offers a modern look coupled with gradients and transparency. +It comes with the following features: + +- Chameleon feature with brightness, +- high-contrast compatibility, +- graphics source provided in SVG. + +Directory Structure +------------------- + +CSS parts: +- **editor.css**: the main CSS file. It's simply loading several other files, for easier maintenance, +- **mainui.css**: the file contains styles of entire editor outline structures, +- **toolbar.css**: the file contains styles of the editor toolbar space (top), +- **richcombo.css**: the file contains styles of the rich combo ui elements on toolbar, +- **panel.css**: the file contains styles of the rich combo drop-down, it's not loaded +until the first panel open up, +- **elementspath.css**: the file contains styles of the editor elements path bar (bottom), +- **menu.css**: the file contains styles of all editor menus including context menu and button drop-down, +it's not loaded until the first menu open up, +- **dialog.css**: the CSS files for the dialog UI, it's not loaded until the first dialog open, +- **reset.css**: the file defines the basis of style resets among all editor UI spaces, +- **preset.css**: the file defines the default styles of some UI elements reflecting the skin preference, +- **editor_XYZ.css** and **dialog_XYZ.css**: browser specific CSS hacks. + +Other parts: +- **skin.js**: the only JavaScript part of the skin that registers the skin, its browser specific files and its icons and defines the Chameleon feature, +- **icons/**: contains all skin defined icons, +- **images/**: contains a fill general used images, +- **dev/**: contains SVG source of the skin icons. + +License +------- + +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. + +Licensed under the terms of any of the following licenses at your choice: [GPL](http://www.gnu.org/licenses/gpl.html), [LGPL](http://www.gnu.org/licenses/lgpl.html) and [MPL](http://www.mozilla.org/MPL/MPL-1.1.html). + +See LICENSE.md for more information. diff --git a/public/assets/plugins/ckeditor/styles.js b/public/assets/plugins/ckeditor/styles.js new file mode 100644 index 00000000..b58e0bda --- /dev/null +++ b/public/assets/plugins/ckeditor/styles.js @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +// This file contains style definitions that can be used by CKEditor plugins. +// +// The most common use for it is the "stylescombo" plugin, which shows a combo +// in the editor toolbar, containing all styles. Other plugins instead, like +// the div plugin, use a subset of the styles on their feature. +// +// If you don't have plugins that depend on this file, you can simply ignore it. +// Otherwise it is strongly recommended to customize this file to match your +// website requirements and design properly. + +CKEDITOR.stylesSet.add( 'default', [ + /* Block Styles */ + + // These styles are already available in the "Format" combo ("format" plugin), + // so they are not needed here by default. You may enable them to avoid + // placing the "Format" combo in the toolbar, maintaining the same features. + /* + { name: 'Paragraph', element: 'p' }, + { name: 'Heading 1', element: 'h1' }, + { name: 'Heading 2', element: 'h2' }, + { name: 'Heading 3', element: 'h3' }, + { name: 'Heading 4', element: 'h4' }, + { name: 'Heading 5', element: 'h5' }, + { name: 'Heading 6', element: 'h6' }, + { name: 'Preformatted Text',element: 'pre' }, + { name: 'Address', element: 'address' }, + */ + + { name: 'Italic Title', element: 'h2', styles: { 'font-style': 'italic' } }, + { name: 'Subtitle', element: 'h3', styles: { 'color': '#aaa', 'font-style': 'italic' } }, + { + name: 'Special Container', + element: 'div', + styles: { + padding: '5px 10px', + background: '#eee', + border: '1px solid #ccc' + } + }, + + /* Inline Styles */ + + // These are core styles available as toolbar buttons. You may opt enabling + // some of them in the Styles combo, removing them from the toolbar. + // (This requires the "stylescombo" plugin) + /* + { name: 'Strong', element: 'strong', overrides: 'b' }, + { name: 'Emphasis', element: 'em' , overrides: 'i' }, + { name: 'Underline', element: 'u' }, + { name: 'Strikethrough', element: 'strike' }, + { name: 'Subscript', element: 'sub' }, + { name: 'Superscript', element: 'sup' }, + */ + + { name: 'Marker', element: 'span', attributes: { 'class': 'marker' } }, + + { name: 'Big', element: 'big' }, + { name: 'Small', element: 'small' }, + { name: 'Typewriter', element: 'tt' }, + + { name: 'Computer Code', element: 'code' }, + { name: 'Keyboard Phrase', element: 'kbd' }, + { name: 'Sample Text', element: 'samp' }, + { name: 'Variable', element: 'var' }, + + { name: 'Deleted Text', element: 'del' }, + { name: 'Inserted Text', element: 'ins' }, + + { name: 'Cited Work', element: 'cite' }, + { name: 'Inline Quotation', element: 'q' }, + + { name: 'Language: RTL', element: 'span', attributes: { 'dir': 'rtl' } }, + { name: 'Language: LTR', element: 'span', attributes: { 'dir': 'ltr' } }, + + /* Object Styles */ + + { + name: 'Styled image (left)', + element: 'img', + attributes: { 'class': 'left' } + }, + + { + name: 'Styled image (right)', + element: 'img', + attributes: { 'class': 'right' } + }, + + { + name: 'Compact table', + element: 'table', + attributes: { + cellpadding: '5', + cellspacing: '0', + border: '1', + bordercolor: '#ccc' + }, + styles: { + 'border-collapse': 'collapse' + } + }, + + { name: 'Borderless Table', element: 'table', styles: { 'border-style': 'hidden', 'background-color': '#E6E6FA' } }, + { name: 'Square Bulleted List', element: 'ul', styles: { 'list-style-type': 'square' } } +]); + diff --git a/public/assets/plugins/colorpicker/bootstrap-colorpicker.css b/public/assets/plugins/colorpicker/bootstrap-colorpicker.css new file mode 100644 index 00000000..ba4365e2 --- /dev/null +++ b/public/assets/plugins/colorpicker/bootstrap-colorpicker.css @@ -0,0 +1,214 @@ +/*! + * Bootstrap Colorpicker + * http://mjolnic.github.io/bootstrap-colorpicker/ + * + * Originally written by (c) 2012 Stefan Petre + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0.txt + * + */ + +.colorpicker-saturation { + float: left; + width: 100px; + height: 100px; + cursor: crosshair; + background-image: url("img/saturation.png"); +} + +.colorpicker-saturation i { + position: absolute; + top: 0; + left: 0; + display: block; + width: 5px; + height: 5px; + margin: -4px 0 0 -4px; + border: 1px solid #000; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} + +.colorpicker-saturation i b { + display: block; + width: 5px; + height: 5px; + border: 1px solid #fff; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} + +.colorpicker-hue, +.colorpicker-alpha { + float: left; + width: 15px; + height: 100px; + margin-bottom: 4px; + margin-left: 4px; + cursor: row-resize; +} + +.colorpicker-hue i, +.colorpicker-alpha i { + position: absolute; + top: 0; + left: 0; + display: block; + width: 100%; + height: 1px; + margin-top: -1px; + background: #000; + border-top: 1px solid #fff; +} + +.colorpicker-hue { + background-image: url("img/hue.png"); +} + +.colorpicker-alpha { + display: none; + background-image: url("img/alpha.png"); +} + +.colorpicker { + top: 0; + left: 0; + z-index: 2500; + min-width: 130px; + padding: 4px; + margin-top: 1px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + *zoom: 1; +} + +.colorpicker:before, +.colorpicker:after { + display: table; + line-height: 0; + content: ""; +} + +.colorpicker:after { + clear: both; +} + +.colorpicker:before { + position: absolute; + top: -7px; + left: 6px; + display: inline-block; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-left: 7px solid transparent; + border-bottom-color: rgba(0, 0, 0, 0.2); + content: ''; +} + +.colorpicker:after { + position: absolute; + top: -6px; + left: 7px; + display: inline-block; + border-right: 6px solid transparent; + border-bottom: 6px solid #ffffff; + border-left: 6px solid transparent; + content: ''; +} + +.colorpicker div { + position: relative; +} + +.colorpicker.colorpicker-with-alpha { + min-width: 140px; +} + +.colorpicker.colorpicker-with-alpha .colorpicker-alpha { + display: block; +} + +.colorpicker-color { + height: 10px; + margin-top: 5px; + clear: both; + background-image: url("img/alpha.png"); + background-position: 0 100%; +} + +.colorpicker-color div { + height: 10px; +} + +.colorpicker-element .input-group-addon i { + display: block; + width: 16px; + height: 16px; + cursor: pointer; +} + +.colorpicker.colorpicker-inline { + position: relative; + display: inline-block; + float: none; +} + +.colorpicker.colorpicker-horizontal { + width: 110px; + height: auto; + min-width: 110px; +} + +.colorpicker.colorpicker-horizontal .colorpicker-saturation { + margin-bottom: 4px; +} + +.colorpicker.colorpicker-horizontal .colorpicker-color { + width: 100px; +} + +.colorpicker.colorpicker-horizontal .colorpicker-hue, +.colorpicker.colorpicker-horizontal .colorpicker-alpha { + float: left; + width: 100px; + height: 15px; + margin-bottom: 4px; + margin-left: 0; + cursor: col-resize; +} + +.colorpicker.colorpicker-horizontal .colorpicker-hue i, +.colorpicker.colorpicker-horizontal .colorpicker-alpha i { + position: absolute; + top: 0; + left: 0; + display: block; + width: 1px; + height: 15px; + margin-top: 0; + background: #ffffff; + border: none; +} + +.colorpicker.colorpicker-horizontal .colorpicker-hue { + background-image: url("img/hue-horizontal.png"); +} + +.colorpicker.colorpicker-horizontal .colorpicker-alpha { + background-image: url("img/alpha-horizontal.png"); +} + +.colorpicker.colorpicker-hidden { + display: none; +} + +.colorpicker.colorpicker-visible { + display: block; +} + +.colorpicker-inline.colorpicker-visible { + display: inline-block; +} \ No newline at end of file diff --git a/public/assets/plugins/colorpicker/bootstrap-colorpicker.js b/public/assets/plugins/colorpicker/bootstrap-colorpicker.js new file mode 100644 index 00000000..2200b0cc --- /dev/null +++ b/public/assets/plugins/colorpicker/bootstrap-colorpicker.js @@ -0,0 +1,949 @@ +/*! + * Bootstrap Colorpicker + * http://mjolnic.github.io/bootstrap-colorpicker/ + * + * Originally written by (c) 2012 Stefan Petre + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0.txt + * + * @todo Update DOCS + */ +(function($) { + 'use strict'; + + // Color object + var Color = function(val) { + this.value = { + h: 0, + s: 0, + b: 0, + a: 1 + }; + this.origFormat = null; // original string format + if (val) { + if (val.toLowerCase !== undefined) { + this.setColor(val); + } else if (val.h !== undefined) { + this.value = val; + } + } + }; + + Color.prototype = { + constructor: Color, + _sanitizeNumber: function(val) { + if (typeof val === 'number') { + return val; + } + if (isNaN(val) || (val === null) || (val === '') || (val === undefined)) { + return 1; + } + if (val.toLowerCase !== undefined) { + return parseFloat(val); + } + return 1; + }, + //parse a string to HSB + setColor: function(strVal) { + strVal = strVal.toLowerCase(); + this.value = this.stringToHSB(strVal) ||  { + h: 0, + s: 0, + b: 0, + a: 1 + }; + }, + stringToHSB: function(strVal) { + strVal = strVal.toLowerCase(); + var that = this, + result = false; + $.each(this.stringParsers, function(i, parser) { + var match = parser.re.exec(strVal), + values = match && parser.parse.apply(that, [match]), + format = parser.format || 'rgba'; + if (values) { + if (format.match(/hsla?/)) { + result = that.RGBtoHSB.apply(that, that.HSLtoRGB.apply(that, values)); + } else { + result = that.RGBtoHSB.apply(that, values); + } + that.origFormat = format; + return false; + } + return true; + }); + return result; + }, + setHue: function(h) { + this.value.h = 1 - h; + }, + setSaturation: function(s) { + this.value.s = s; + }, + setBrightness: function(b) { + this.value.b = 1 - b; + }, + setAlpha: function(a) { + this.value.a = parseInt((1 - a) * 100, 10) / 100; + }, + toRGB: function(h, s, v, a) { + h = h || this.value.h; + s = s || this.value.s; + v = v || this.value.b; + a = a || this.value.a; + + var r, g, b, i, f, p, q, t; + if (h && s === undefined && v === undefined) { + s = h.s, v = h.v, h = h.h; + } + i = Math.floor(h * 6); + f = h * 6 - i; + p = v * (1 - s); + q = v * (1 - f * s); + t = v * (1 - (1 - f) * s); + switch (i % 6) { + case 0: + r = v, g = t, b = p; + break; + case 1: + r = q, g = v, b = p; + break; + case 2: + r = p, g = v, b = t; + break; + case 3: + r = p, g = q, b = v; + break; + case 4: + r = t, g = p, b = v; + break; + case 5: + r = v, g = p, b = q; + break; + } + return { + r: Math.floor(r * 255), + g: Math.floor(g * 255), + b: Math.floor(b * 255), + a: a + }; + }, + toHex: function(h, s, b, a) { + var rgb = this.toRGB(h, s, b, a); + return '#' + ((1 << 24) | (parseInt(rgb.r) << 16) | (parseInt(rgb.g) << 8) | parseInt(rgb.b)).toString(16).substr(1); + }, + toHSL: function(h, s, b, a) { + h = h || this.value.h; + s = s || this.value.s; + b = b || this.value.b; + a = a || this.value.a; + + var H = h, + L = (2 - s) * b, + S = s * b; + if (L > 0 && L <= 1) { + S /= L; + } else { + S /= 2 - L; + } + L /= 2; + if (S > 1) { + S = 1; + } + return { + h: H, + s: S, + l: L, + a: a + }; + }, + RGBtoHSB: function(r, g, b, a) { + r /= 255; + g /= 255; + b /= 255; + + var H, S, V, C; + V = Math.max(r, g, b); + C = V - Math.min(r, g, b); + H = (C === 0 ? null : + V === r ? (g - b) / C : + V === g ? (b - r) / C + 2 : + (r - g) / C + 4 + ); + H = ((H + 360) % 6) * 60 / 360; + S = C === 0 ? 0 : C / V; + return { + h: this._sanitizeNumber(H), + s: S, + b: V, + a: this._sanitizeNumber(a) + }; + }, + HueToRGB: function(p, q, h) { + if (h < 0) { + h += 1; + } else if (h > 1) { + h -= 1; + } + if ((h * 6) < 1) { + return p + (q - p) * h * 6; + } else if ((h * 2) < 1) { + return q; + } else if ((h * 3) < 2) { + return p + (q - p) * ((2 / 3) - h) * 6; + } else { + return p; + } + }, + HSLtoRGB: function(h, s, l, a) { + if (s < 0) { + s = 0; + } + var q; + if (l <= 0.5) { + q = l * (1 + s); + } else { + q = l + s - (l * s); + } + + var p = 2 * l - q; + + var tr = h + (1 / 3); + var tg = h; + var tb = h - (1 / 3); + + var r = Math.round(this.HueToRGB(p, q, tr) * 255); + var g = Math.round(this.HueToRGB(p, q, tg) * 255); + var b = Math.round(this.HueToRGB(p, q, tb) * 255); + return [r, g, b, this._sanitizeNumber(a)]; + }, + toString: function(format) { + format = format ||  'rgba'; + switch (format) { + case 'rgb': + { + var rgb = this.toRGB(); + return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')'; + } + break; + case 'rgba': + { + var rgb = this.toRGB(); + return 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + rgb.a + ')'; + } + break; + case 'hsl': + { + var hsl = this.toHSL(); + return 'hsl(' + Math.round(hsl.h * 360) + ',' + Math.round(hsl.s * 100) + '%,' + Math.round(hsl.l * 100) + '%)'; + } + break; + case 'hsla': + { + var hsl = this.toHSL(); + return 'hsla(' + Math.round(hsl.h * 360) + ',' + Math.round(hsl.s * 100) + '%,' + Math.round(hsl.l * 100) + '%,' + hsl.a + ')'; + } + break; + case 'hex': + { + return this.toHex(); + } + break; + default: + { + return false; + } + break; + } + }, + // a set of RE's that can match strings and generate color tuples. + // from John Resig color plugin + // https://github.com/jquery/jquery-color/ + stringParsers: [{ + re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/, + format: 'hex', + parse: function(execResult) { + return [ + parseInt(execResult[1], 16), + parseInt(execResult[2], 16), + parseInt(execResult[3], 16), + 1 + ]; + } + }, { + re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/, + format: 'hex', + parse: function(execResult) { + return [ + parseInt(execResult[1] + execResult[1], 16), + parseInt(execResult[2] + execResult[2], 16), + parseInt(execResult[3] + execResult[3], 16), + 1 + ]; + } + }, { + re: /rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*?\)/, + format: 'rgb', + parse: function(execResult) { + return [ + execResult[1], + execResult[2], + execResult[3], + 1 + ]; + } + }, { + re: /rgb\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*?\)/, + format: 'rgb', + parse: function(execResult) { + return [ + 2.55 * execResult[1], + 2.55 * execResult[2], + 2.55 * execResult[3], + 1 + ]; + } + }, { + re: /rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, + format: 'rgba', + parse: function(execResult) { + return [ + execResult[1], + execResult[2], + execResult[3], + execResult[4] + ]; + } + }, { + re: /rgba\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, + format: 'rgba', + parse: function(execResult) { + return [ + 2.55 * execResult[1], + 2.55 * execResult[2], + 2.55 * execResult[3], + execResult[4] + ]; + } + }, { + re: /hsl\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*?\)/, + format: 'hsl', + parse: function(execResult) { + return [ + execResult[1] / 360, + execResult[2] / 100, + execResult[3] / 100, + execResult[4] + ]; + } + }, { + re: /hsla\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/, + format: 'hsla', + parse: function(execResult) { + return [ + execResult[1] / 360, + execResult[2] / 100, + execResult[3] / 100, + execResult[4] + ]; + } + }, { + //predefined color name + re: /^([a-z]{3,})$/, + format: 'alias', + parse: function(execResult) { + var hexval = this.colorNameToHex(execResult[0]) ||  '#000000'; + var match = this.stringParsers[0].re.exec(hexval), + values = match && this.stringParsers[0].parse.apply(this, [match]); + return values; + } + }], + colorNameToHex: function(name) { + // 140 predefined colors from the HTML Colors spec + var colors = { + "aliceblue": "#f0f8ff", + "antiquewhite": "#faebd7", + "aqua": "#00ffff", + "aquamarine": "#7fffd4", + "azure": "#f0ffff", + "beige": "#f5f5dc", + "bisque": "#ffe4c4", + "black": "#000000", + "blanchedalmond": "#ffebcd", + "blue": "#0000ff", + "blueviolet": "#8a2be2", + "brown": "#a52a2a", + "burlywood": "#deb887", + "cadetblue": "#5f9ea0", + "chartreuse": "#7fff00", + "chocolate": "#d2691e", + "coral": "#ff7f50", + "cornflowerblue": "#6495ed", + "cornsilk": "#fff8dc", + "crimson": "#dc143c", + "cyan": "#00ffff", + "darkblue": "#00008b", + "darkcyan": "#008b8b", + "darkgoldenrod": "#b8860b", + "darkgray": "#a9a9a9", + "darkgreen": "#006400", + "darkkhaki": "#bdb76b", + "darkmagenta": "#8b008b", + "darkolivegreen": "#556b2f", + "darkorange": "#ff8c00", + "darkorchid": "#9932cc", + "darkred": "#8b0000", + "darksalmon": "#e9967a", + "darkseagreen": "#8fbc8f", + "darkslateblue": "#483d8b", + "darkslategray": "#2f4f4f", + "darkturquoise": "#00ced1", + "darkviolet": "#9400d3", + "deeppink": "#ff1493", + "deepskyblue": "#00bfff", + "dimgray": "#696969", + "dodgerblue": "#1e90ff", + "firebrick": "#b22222", + "floralwhite": "#fffaf0", + "forestgreen": "#228b22", + "fuchsia": "#ff00ff", + "gainsboro": "#dcdcdc", + "ghostwhite": "#f8f8ff", + "gold": "#ffd700", + "goldenrod": "#daa520", + "gray": "#808080", + "green": "#008000", + "greenyellow": "#adff2f", + "honeydew": "#f0fff0", + "hotpink": "#ff69b4", + "indianred ": "#cd5c5c", + "indigo ": "#4b0082", + "ivory": "#fffff0", + "khaki": "#f0e68c", + "lavender": "#e6e6fa", + "lavenderblush": "#fff0f5", + "lawngreen": "#7cfc00", + "lemonchiffon": "#fffacd", + "lightblue": "#add8e6", + "lightcoral": "#f08080", + "lightcyan": "#e0ffff", + "lightgoldenrodyellow": "#fafad2", + "lightgrey": "#d3d3d3", + "lightgreen": "#90ee90", + "lightpink": "#ffb6c1", + "lightsalmon": "#ffa07a", + "lightseagreen": "#20b2aa", + "lightskyblue": "#87cefa", + "lightslategray": "#778899", + "lightsteelblue": "#b0c4de", + "lightyellow": "#ffffe0", + "lime": "#00ff00", + "limegreen": "#32cd32", + "linen": "#faf0e6", + "magenta": "#ff00ff", + "maroon": "#800000", + "mediumaquamarine": "#66cdaa", + "mediumblue": "#0000cd", + "mediumorchid": "#ba55d3", + "mediumpurple": "#9370d8", + "mediumseagreen": "#3cb371", + "mediumslateblue": "#7b68ee", + "mediumspringgreen": "#00fa9a", + "mediumturquoise": "#48d1cc", + "mediumvioletred": "#c71585", + "midnightblue": "#191970", + "mintcream": "#f5fffa", + "mistyrose": "#ffe4e1", + "moccasin": "#ffe4b5", + "navajowhite": "#ffdead", + "navy": "#000080", + "oldlace": "#fdf5e6", + "olive": "#808000", + "olivedrab": "#6b8e23", + "orange": "#ffa500", + "orangered": "#ff4500", + "orchid": "#da70d6", + "palegoldenrod": "#eee8aa", + "palegreen": "#98fb98", + "paleturquoise": "#afeeee", + "palevioletred": "#d87093", + "papayawhip": "#ffefd5", + "peachpuff": "#ffdab9", + "peru": "#cd853f", + "pink": "#ffc0cb", + "plum": "#dda0dd", + "powderblue": "#b0e0e6", + "purple": "#800080", + "red": "#ff0000", + "rosybrown": "#bc8f8f", + "royalblue": "#4169e1", + "saddlebrown": "#8b4513", + "salmon": "#fa8072", + "sandybrown": "#f4a460", + "seagreen": "#2e8b57", + "seashell": "#fff5ee", + "sienna": "#a0522d", + "silver": "#c0c0c0", + "skyblue": "#87ceeb", + "slateblue": "#6a5acd", + "slategray": "#708090", + "snow": "#fffafa", + "springgreen": "#00ff7f", + "steelblue": "#4682b4", + "tan": "#d2b48c", + "teal": "#008080", + "thistle": "#d8bfd8", + "tomato": "#ff6347", + "turquoise": "#40e0d0", + "violet": "#ee82ee", + "wheat": "#f5deb3", + "white": "#ffffff", + "whitesmoke": "#f5f5f5", + "yellow": "#ffff00", + "yellowgreen": "#9acd32" + }; + + if (typeof colors[name.toLowerCase()] !== 'undefined') { + return colors[name.toLowerCase()]; + } + return false; + } + }; + + + var defaults = { + horizontal: false, // horizontal mode layout ? + inline: false, //forces to show the colorpicker as an inline element + color: false, //forces a color + format: false, //forces a format + input: 'input', // children input selector + container: false, // container selector + component: '.add-on, .input-group-addon', // children component selector + sliders: { + saturation: { + maxLeft: 100, + maxTop: 100, + callLeft: 'setSaturation', + callTop: 'setBrightness' + }, + hue: { + maxLeft: 0, + maxTop: 100, + callLeft: false, + callTop: 'setHue' + }, + alpha: { + maxLeft: 0, + maxTop: 100, + callLeft: false, + callTop: 'setAlpha' + } + }, + slidersHorz: { + saturation: { + maxLeft: 100, + maxTop: 100, + callLeft: 'setSaturation', + callTop: 'setBrightness' + }, + hue: { + maxLeft: 100, + maxTop: 0, + callLeft: 'setHue', + callTop: false + }, + alpha: { + maxLeft: 100, + maxTop: 0, + callLeft: 'setAlpha', + callTop: false + } + }, + template: '